Geocode addresses in Python with pandas
This tutorial geocodes a DataFrame of addresses with the Addrova batch API, looks at the rows the service is unsure about, and repairs their typos. It uses requests and pandas. A dedicated Python SDK is planned; see the SDK page. [À VÉRIFIER : SDK non disponible ; ce tutoriel utilise l'API HTTP.]
The base URL https://api.addrova.eu is not live yet. Everything below follows the API specification.
1. Prepare the file
import io
import os
import time
import pandas as pd
import requests
BASE = "https://api.addrova.eu"
HEADERS = {"X-API-Key": os.environ["ADDROVA_API_KEY"]}
df = pd.read_csv("customers.csv") # a column "address" with the full address
print(len(df), "rows")
The batch endpoint accepts a CSV with a header row. Limits depend on the plan: the catalogue gives 100,000 rows per batch on Starter. [À VÉRIFIER : limite par offre, voir /pricing]
2. Submit, wait, download
buf = io.StringIO()
df[["address"]].to_csv(buf, index=False)
job = requests.post(
f"{BASE}/v1/batch",
headers=HEADERS,
files={"file": ("addresses.csv", buf.getvalue(), "text/csv")},
data={"address_column": "address", "country": "FR"},
timeout=60,
).json()
while job["status"] in ("queued", "running"):
time.sleep(2)
job = requests.get(f"{BASE}/v1/batch/{job['id']}", headers=HEADERS, timeout=10).json()
result_csv = requests.get(f"{BASE}/v1/batch/{job['id']}/result", headers=HEADERS, timeout=120).text
requests.delete(f"{BASE}/v1/batch/{job['id']}", headers=HEADERS, timeout=10) # purge now
body = "\n".join(l for l in result_csv.splitlines() if not l.startswith("#")) # drop the attribution comment line
res = pd.read_csv(io.StringIO(body))
The result keeps your columns and adds addrova_label, addrova_lat, addrova_lon, addrova_precision, addrova_confidence, addrova_confidence_level, addrova_sources and addrova_status. The attribution is a trailing comment line, which the code above removes before reading the table; keep it with your data. Deleting the job right after the download is allowed by the API and keeps nothing on the service side (claim ZL-22 on the compliance page).
3. Read the statuses
print(res["addrova_status"].value_counts())
matched is a confident match, uncertain is a best candidate with low confidence, not_found has no candidate, invalid is an empty or rejected address.
4. Repair the uncertain rows
todo = res[res["addrova_status"].isin(["uncertain", "not_found"])]
def repair(address, country="FR"):
r = requests.post(
f"{BASE}/v1/repair",
headers=HEADERS,
json={"address": address, "country": country},
timeout=10,
).json()
return r
fixed = todo["address"].map(repair)
res.loc[todo.index, "repair_status"] = fixed.map(lambda r: r["status"])
res.loc[todo.index, "repair_confidence"] = fixed.map(lambda r: r.get("confidence"))
res.loc[todo.index, "repaired_label"] = fixed.map(lambda r: (r.get("result") or {}).get("label"))
Each repair returns a status (unchanged, repaired, unrepairable), the corrected result, a confidence and a list of changes with the reason (abbreviation, typo, missing…). Repair is billed on success only. Review anything with a low confidence by hand.
5. Store the results
res.to_csv("customers_geocoded.csv", index=False)
You may store the results permanently. Keep the attribution that comes with them.
Tips
- Geocode a sample of 500 rows first and read the
uncertainones. - Keep postcode and city in separate columns if you have them, and pass them with the
columnsoption of the batch endpoint. - Handle
429responses: theRetry-Afterheader tells you how long to wait.