Choosing a Geocoder for Protected Health Data
Geocoder selection is usually argued on match rate and cost, and for surveillance work both are secondary. The binding constraint is that an address paired with a reportable condition is protected health information, so the question of which service sees it comes before the question of how well it performs. This guide, part of Geocoding Quality & Address Standardization, sets out how to evaluate the three deployment models against that constraint and how to run a comparison that produces a defensible decision.
Problem Context & Constraints
Three deployment models cover almost every real option. An in-house geocoder runs inside the agency network against a reference layer the agency holds; nothing leaves. A hosted service accepts addresses over the network and returns coordinates. A hybrid matches locally against a parcel or address-point file and sends only the residue — typically the hardest ten to twenty percent — to a hosted service.
The compliance analysis differs sharply across the three, and it does not reduce to whether a vendor will sign a business associate agreement. A signed agreement makes disclosure permissible; it does not make it invisible. Every hosted call creates a record of an address at a third party, usually with a timestamp, often with an IP address identifying the submitting agency, and sometimes retained in logs for a period the agency does not control. For a reportable condition with stigma attached, that is a meaningful exposure even where it is lawful.
Against that sits a real analytic cost. Public reference layers are less complete than commercial composites, and the gap concentrates in new construction and in rural addressing — exactly the strata whose under-representation the match-rate bias diagnostic is designed to catch. Choosing the in-house option to avoid disclosure can therefore introduce a selection bias, and that trade has to be made explicitly rather than by default.
Prerequisites
- A written statement of which conditions in scope are considered sensitive, since the answer changes the acceptable exposure
- An evaluation sample of 2,000–5,000 real addresses from the surveillance stream, stratified by county and by rurality, with a hand-verified subset of 200 for positional truth
- Legal review capacity to read a candidate vendor’s data-retention and sub-processor terms, not only their agreement willingness
python3.11,pandas2.2.2,geopandas1.0.1,pyproj3.6.1 for the positional comparison
Step-by-Step Solution
Run the candidates against the same sample and score them on the three axes that decide the question. The comparison below reports positional error against the hand-verified subset rather than against each other, because two geocoders agreeing does not make either correct.
# Compare geocoder candidates on match type, positional error and stratum coverage.
# Pinned: pandas==2.2.2, geopandas==1.0.1, numpy==1.26.4, pyproj==3.6.1
import logging
import numpy as np
import pandas as pd
import geopandas as gpd
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("geocode.bakeoff")
METRIC_CRS = "EPSG:5070" # CONUS Albers equal area — metres, so errors are metres
def positional_error(candidate: gpd.GeoDataFrame, truth: gpd.GeoDataFrame,
id_col: str = "record_id") -> pd.Series:
"""Distance in metres from each candidate coordinate to the verified location.
Both frames are reprojected to a metric CRS first; comparing in degrees would
make the same error look smaller at higher latitude."""
a = candidate.set_index(id_col).to_crs(METRIC_CRS)
b = truth.set_index(id_col).to_crs(METRIC_CRS)
common = a.index.intersection(b.index)
d = a.loc[common].geometry.distance(b.loc[common].geometry)
log.info("positional error on %d verified records: median %.1f m, p90 %.1f m, max %.1f m",
len(d), d.median(), d.quantile(0.9), d.max())
return d
def score_candidate(name: str, gdf: gpd.GeoDataFrame, truth: gpd.GeoDataFrame,
stratum: str = "rurality") -> dict:
"""One row of the comparison table."""
precise = gdf["match_rank"] <= 1
by_stratum = gdf.assign(ok=precise).groupby(stratum)["ok"].mean()
spread = float(by_stratum.max() - by_stratum.min())
err = positional_error(gdf, truth)
row = {
"candidate": name,
"precise_rate": float(precise.mean()),
"stratum_spread": spread, # the equity number, not the headline number
"median_error_m": float(err.median()),
"p90_error_m": float(err.quantile(0.9)),
"worst_stratum": by_stratum.idxmin(),
}
log.info("%s: precise %.1f%%, spread %.1f pp, median err %.1f m",
name, 100 * row["precise_rate"], 100 * spread, row["median_error_m"])
return row
def residue_share(local: gpd.GeoDataFrame) -> float:
"""Fraction of records a hybrid design would have to send outside.
This is the number the compliance conversation actually turns on: it is the
exposure the agency is buying the extra match rate with."""
share = float((local["match_rank"] > 1).mean())
log.info("hybrid residue: %.1f%% of records would leave the network", 100 * share)
return share
Report stratum_spread beside precise_rate in every comparison. A candidate that matches 93% overall with a 20-point spread across rurality strata is worse for equity analysis than one that matches 88% with a 6-point spread, and the headline rate hides that completely.
Validation & Edge Cases
1. Verify the truth set independently. Two hundred hand-verified locations is a small sample and it must not be built by asking a geocoder. Use parcel records, aerial imagery, or field verification, and record the method — a truth set derived from one candidate silently declares that candidate correct.
2. Test the residue, not the whole file, for the hosted candidate. A hosted service’s headline match rate is measured on all addresses, and the ones that reach it in a hybrid design are the hard ones. Its performance on the residue is typically far below its published figure, and that is the number the hybrid decision depends on.
3. Check retention terms, not only the agreement. Ask specifically how long submitted addresses are retained, whether they are used to improve the vendor’s reference layer, which sub-processors receive them, and in which jurisdictions they are stored. A willingness to sign is compatible with all of those being unacceptable.
4. Confirm the egress carries one field. The most common implementation defect in a hybrid design is sending the whole record because it was convenient. Assert the outbound payload’s schema in code, so the constraint is enforced rather than remembered:
INFO hybrid residue: 14.0% of records would leave the network
ERROR egress schema violation: payload contains ['address','dob','condition'] — expected ['address','surrogate_key']
5. Re-run the comparison annually. Reference layers and services both change, and a decision made three years ago against a then-current comparison is not evidence about today’s options.
6. Ask what happens to the residue on the vendor’s side over time. A vendor that retains submitted addresses to improve its reference layer is, in effect, accumulating a partial register of the addresses your surveillance system has seen. Even without diagnoses attached, the pattern of submissions can be informative, and the question of retention deserves an answer in writing rather than an assurance in a sales call.
Compliance Notes
- Record the decision and its basis, including the residue share the hybrid design implies and the strata each candidate serves worst. A geocoder choice is a design decision with epidemiological consequences and belongs in the same registry as the disclosure controls described in Compliance Mapping Frameworks.
- Count the egress. A hybrid design’s compliance claim is that only the residue leaves; make that auditable by logging the outbound record count per run and reconciling it against the residue count.
- Never send a condition code or a case identifier with an address. The surrogate key exists so the vendor holds an address and a meaningless token, which is a materially different disclosure from an address and a diagnosis.
- Re-evaluate after any vendor acquisition. Sub-processor lists and storage jurisdictions change on acquisition, and an agreement that survives the change may cover a materially different data flow.
Related Topics
- Geocoding Quality & Address Standardization — the parent guide covering match types, normalization and the audit record.
- Measuring Geocoder Match-Rate Bias in Surveillance Data — the diagnostic that turns stratum spread into a published number.
- Compliance Mapping Frameworks — where the vendor decision and its lawful basis are recorded.