Assigning Exposure to Residential Locations
This guide is part of the Environmental Exposure & Risk Surface Modeling section. Once a surface exists, one step remains before a health model can use it: attaching a number to each person. That step decides more of the final error than most of the modeling that preceded it.
Concept & Epidemiological Alignment
Exposure assignment converts a field into a variable. The conceptual target is the personal exposure — what an individual actually inhaled or drank over the relevant window — and every practical method is a proxy for it. Residential assignment proxies personal exposure with the value of a surface at or near the home address, which embeds three assumptions worth stating explicitly: that the home is where the relevant exposure occurred, that the address on the case record is the address during the etiologically relevant period, and that the surface is accurate at that location.
Each assumption fails differently. People spend large fractions of the day elsewhere, so residential assignment attenuates any exposure that varies between home and workplace. Addresses in surveillance systems are usually the address at diagnosis, which for a disease with a long latency is the wrong one. And the surface accuracy varies across the study area in ways already discussed in the interpolation and regression topics.
What makes assignment tractable despite all this is that the errors have known structure. Residential assignment of a regional pollutant produces error that is largely Berkson-like: the assigned area-level value is a kind of average of the true personal exposures within it, which leaves a health effect estimate roughly unbiased while widening its interval. Residential assignment of a sharply varying pollutant to a poorly geocoded address produces classical error, which attenuates the estimate toward the null. Knowing which regime you are in determines what the assignment step must optimise, and that distinction is developed in Exposure Measurement Error & Uncertainty.
The practical consequence is a rule. Match the assignment method to the weaker of two precisions: the surface resolution and the geocode accuracy. A 30 m surface joined to a ZIP-centroid geocode is a ZIP-level exposure with a misleading number of decimal places, and treating it as address-level exposure imports kilometres of error while claiming metres of precision.
Assignment Methods
| Method | What it returns | Right when | Risk |
|---|---|---|---|
| Point lookup | Surface value at the address | Geocode is rooftop or parcel; surface is finer than the error | Overstates precision if the geocode is coarse |
| Buffer mean | Average over a circle around the address | Geocode has known positional error; exposure is local | Radius chosen by habit rather than by the error |
| Area mean | Surface averaged over the containing tract or block group | Only area-level geography is available | Modifiable areal unit effects |
| Population-weighted area mean | Area mean weighted by where people live | Areas are large or internally heterogeneous | Needs a population raster of its own |
| Time-weighted mobility | Weighted mean over a residential history | Long latency; addresses are available over time | Address histories are incomplete and biased |
| Nearest measurement | The value at the closest monitor | No surface exists | Discontinuities and large, structured error |
Spatial Data Prerequisites
- Geometry: geocoded residence points carrying a match-type flag; the exposure surface as a raster or a polygon layer; optionally a population raster for weighted area means.
- Coordinate reference system: one projected metric system for addresses and surface. Buffer radii are distances, and a sampling operation across mismatched systems produces a silent offset rather than an error.
- Geocode quality: a per-record match type from the geocoding step, following the taxonomy in Geocoding Quality & Address Standardization. Without it, assignment cannot be matched to precision and the sensitivity analysis is impossible.
- Temporal alignment: the surface window must cover the etiologically relevant period for the outcome, not the period the data happened to be available for.
- Address vintage: whether the coordinate is the address at diagnosis, at birth, or at some other point, recorded explicitly per record.
- Protected environment: the join runs where the case table lives, which means it inherits the access controls, logging and retention rules of that environment.
Production Implementation
The routine below assigns exposure with a method chosen per record from the geocode match type, records which method was used, and never emits a coordinate to the log.
# Per-record exposure assignment with method chosen from geocode quality.
# Pinned: geopandas==0.14.4, rasterio==1.3.10, numpy==1.26.4, pandas==2.2.2
import logging
import numpy as np
import pandas as pd
import geopandas as gpd
import rasterio
from rasterio.sample import sample_gen
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("exposure.assign")
# Positional error (metres) by geocode match type — from the geocoder's own
# validation, not invented. Drives the buffer radius when one is needed.
POSITIONAL_ERROR_M = {"rooftop": 20, "parcel": 40, "street_interpolated": 120,
"zip_centroid": 3000, "city_centroid": 15000}
POINT_LOOKUP_TYPES = {"rooftop", "parcel"}
UNUSABLE_TYPES = {"city_centroid"}
def assign_exposure(cases: gpd.GeoDataFrame, raster_path: str,
match_col: str = "geocode_match", band: int = 1,
var_band: int | None = 2) -> pd.DataFrame:
"""Assign surface values per record, method chosen from geocode match type.
Returns a frame keyed by study_id with exposure, its variance where the
surface provides one, the method used and the assumed positional error.
Emits no coordinates to the log.
"""
with rasterio.open(raster_path) as src:
if cases.crs != src.crs:
cases = cases.to_crs(src.crs)
res = float(abs(src.transform.a))
rows = []
for rec in cases.sort_values("study_id").itertuples():
mt = getattr(rec, match_col)
if mt in UNUSABLE_TYPES:
rows.append({"study_id": rec.study_id, "exposure": np.nan,
"exposure_var": np.nan, "method": "unassigned",
"pos_err_m": POSITIONAL_ERROR_M.get(mt)})
continue
err = POSITIONAL_ERROR_M.get(mt, 3000)
if mt in POINT_LOOKUP_TYPES or err <= res:
vals = next(sample_gen(src, [(rec.geometry.x, rec.geometry.y)]))
method = "point"
else:
# Average the surface over a circle the size of the positional error.
win = rec.geometry.buffer(err)
out, _ = _window_mean(src, win, bands=(band, var_band))
vals = out
method = f"buffer_{err}m"
v = float(vals[band - 1])
var = float(vals[var_band - 1]) if var_band and len(vals) >= var_band else np.nan
rows.append({"study_id": rec.study_id,
"exposure": v if np.isfinite(v) else np.nan,
"exposure_var": var, "method": method, "pos_err_m": err})
out = pd.DataFrame(rows)
counts = out["method"].value_counts().to_dict()
log.info("assigned %d records: %s unassigned=%d",
len(out), counts, int(out["exposure"].isna().sum()))
return out
def _window_mean(src, geom, bands):
"""Mean of each band over the cells intersecting geom. Mask-aware."""
from rasterio.mask import mask
arr, _ = mask(src, [geom.__geo_interface__], crop=True, filled=False)
means = []
for b in range(arr.shape[0]):
layer = arr[b]
means.append(float(layer.mean()) if layer.count() else np.nan)
return np.asarray(means), arr.shape
The important structural choice is that the method is data, not a global setting. A surveillance case file mixes rooftop matches from an address-point geocoder with ZIP centroids from records that failed it, and one assignment rule cannot serve both. Writing the method and assumed positional error per record is what makes a later sensitivity analysis — restrict to rooftop matches and refit — possible at all.
The unassigned branch is equally deliberate. A city-centroid geocode carries kilometres of error; substituting the city mean is not an exposure measurement, and filling it produces a value indistinguishable in the table from a real one. Leave it null, count it, and let the analysis decide.
Parameter Selection & Tuning
The buffer radius should come from the geocoder’s measured positional error for that match type, not from convention. Radii picked to “smooth a bit” have no defensible interpretation; a radius equal to the typical positional error approximates averaging over the locations the address might actually be.
The positional error table must be empirical. Most geocoders publish validation statistics by match type, and a jurisdiction can measure its own by comparing against address points. Inventing the numbers invalidates every buffer radius derived from them.
The surface window should match the etiologically relevant exposure period. For acute outcomes that may be days; for chronic disease it is years and often precedes the address on file, which is when a residential history becomes necessary rather than optional.
The unassignable threshold — which match types get a null — should be set from the ratio of positional error to the scale of exposure variation. A ZIP centroid is unusable for a traffic pollutant and adequate for a regional one; the same match type belongs on different sides of the line for different exposures.
Variance propagation. Where the surface supplies a variance band, the buffer mean’s variance is not the mean divided by the cell count, because neighbouring cells are correlated. Use the mean of the cell variances as a conservative value unless you have modelled the correlation.
Edge Cases & Failure Modes
Geocode quality correlated with the outcome. Rural addresses and multi-unit buildings geocode worse, and both correlate with health outcomes. Dropping poorly geocoded records therefore selects the sample. Always report the assignment method distribution by case status.
Addresses at the study-area edge. A buffer centred near the boundary averages over cells with no data, biasing the result toward whatever the fill value is. Pad the surface beyond the study extent or flag edge records.
Apartment buildings and institutional addresses. Hundreds of records at one coordinate is normal, and it is not an error — but it does mean the exposure contrast within that building is zero by construction, which matters for a within-neighbourhood analysis.
Post office boxes and rural routes. These geocode to a facility, not a residence. They must be treated as unassignable regardless of what match type the geocoder reports, following the handling in Handling PO Box and Rural Route Addresses in Case Data.
Mismatched vintages. Assigning a 2024 surface to an address recorded in 2011 without acknowledging it assumes the exposure field has not changed. For pollutants with strong secular trends this is a large error and an entirely avoidable one.
Memory on a large cohort. A million records with per-record raster windows is slow. Group records by geocode match type, process the point-lookup group with one vectorised sample call, and reserve windowed reads for the minority that need them.
The vintage mismatch in that list is the one most easily quantified, because the exposure field moves between the surface year and the address year:
Compliance & Audit Controls
The assignment step is where environmental data touches protected health data, so it carries the case file’s controls. Run it inside the protected environment, log the run without any coordinate or address, and emit a table keyed by study identifier rather than by location. Where a per-address intermediate is unavoidable, treat it as the most disclosive artifact in the pipeline: it pairs a residence with an environmental attribute, which is a strong quasi-identifier.
The audit record needs the surface identifier and vintage, the positional error table and its source, the method decision rules including which match types were declared unassignable, per-record method and positional error, counts by method, and the count and geographic distribution of unassigned records. Determinism follows from a stable sort on the study identifier and a fixed rule table.
One release control is specific to exposure: a published per-person exposure value is a coordinate in disguise, because a fine surface inverts to a narrow contour band. Round released exposures to bands or aggregate them before publication, under the review process in Privacy-Preserving Spatial Analytics.
Production Implementation Checklist
Frequently Asked Questions
Should I drop records with poor geocodes? Not as a first move. Assign them with a method matched to their precision, flag them, and run the analysis both ways. Dropping them is a geographic exclusion that removes rural and multi-unit addresses preferentially, and if the association appears only in the restricted sample, that is a finding about urban cases rather than about the exposure.
What buffer radius should I use? The measured positional error of that record’s match type. A 120 m radius for street-interpolated geocodes has a meaning — it averages the surface over the range of places the address might be. A 500 m radius chosen because it looked reasonable has none.
Is a tract-level exposure ever better than an address-level one? Yes, when the addresses are poorly geocoded, or when the health outcome is only available at tract level anyway. An honest tract exposure beats an address exposure built on ZIP centroids, and it invites the right interpretation: an area-level effect, with the modifiable areal unit caveats that go with it.
How do I handle people who moved? With a residential history and a time-weighted assignment if one exists, and with an explicit statement of the address vintage if it does not. For long-latency outcomes, the address at diagnosis may be the wrong exposure entirely, and saying so is better than assigning it silently.
Does the geocode need to be inside the study area? Yes, and confirm it rather than assume it. A geocoder returning a state-centroid fallback for an unmatched address produces a coordinate that is technically inside the study area and completely uninformative. Detect those by their repetition — hundreds of records at one coordinate — and treat them as unassigned.
Related Topics
- Environmental Exposure & Risk Surface Modeling — the parent section and where assignment sits in the chain.
- Choosing a Buffer Radius for Proximity Exposure — setting the radius from evidence rather than habit.
- Handling Missing Geocodes in Exposure Assignment — what to do with the records that cannot be assigned.
- Geocoding Quality & Address Standardization — where the match types and positional errors come from.