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.

What Each Deployment Model Costs You Three deployment models compared across three dimensions. In-house geocoding sends nothing outside the network, achieves a rooftop-or-better rate near eighty-two percent, and carries a high operating burden because the reference layer must be maintained. A hosted service sends every address to a third party, achieves ninety-three percent, and carries almost no operating burden. The hybrid model sends only the local-match residue, about fourteen percent of records, achieves ninety-one percent, and carries a moderate burden. Three models, and the trade is not subtle addresses leaving the network rooftop or better operating burden In-house none 82% high Hosted every record 93% low Hybrid 14% residue only 91% moderate The hybrid row buys most of the hosted match rate for a seventh of the exposure which is why it is the default for reportable-condition surveillance in most agencies

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
  • python 3.11, pandas 2.2.2, geopandas 1.0.1, pyproj 3.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.

The Hybrid Pipeline and Its Single Exposure Point Normalized addresses enter a local matcher running against an agency parcel and address-point file. Records that match locally never leave the network. The residue, about fourteen percent, passes through a single audited egress point to a hosted service under an executed agreement, and only the normalized street address is sent — no name, no diagnosis, no record identifier that could be linked back without the agency key. Returned coordinates rejoin the local file by a surrogate key. One egress point, carrying one field inside the agency network Normalized file 41,793 records Local matcher parcel + address points matched locally 86% · never leaves residue 14% · 5,851 records Audited egress normalized street address only surrogate key, no diagnosis Hosted service under an executed agreement The surrogate key is what makes the returned coordinates rejoinable without the vendor holding a link and the egress point is what makes the exposure countable in an audit

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.

Headline Rate Against Stratum Spread Four candidate geocoders plotted by overall precise-match rate against the spread in that rate across rurality strata. The commercial composite has the highest overall rate at ninety-three percent but a nineteen-point spread. The hybrid design reaches ninety-one percent with an eight-point spread. The public reference layer reaches eighty-two percent with a six-point spread. A second commercial candidate reaches eighty-nine percent with a twenty-two point spread. The best headline number has the worst equity profile. The best headline number has the worst equity profile 80% 88% 96% overall rate public layer 82% · spread 6 hybrid 91% · spread 8 commercial B 89% · spread 22 commercial A 93% · spread 19 5 13 22 spread across rurality strata (percentage points) — lower is fairer

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.