Deriving Home Zones from Aggregated Visit Data
This page solves one exact task: working correctly with the home-zone attribution that mobility vendors attach to aggregated visits — understanding how it is produced, how uncertain it is, and which analyses it can legitimately support.
The distinction that governs everything here is between an aggregate and a trace. This page is about consuming vendor-produced origin-zone aggregates, in which no individual’s home is identifiable and no device path is present. Working with raw traces to infer where specific people live is a different activity with different legal and ethical obligations, and it is not what a retail trade-area pipeline needs or should be doing.
Prerequisites
- Vendor origin-destination aggregates at a coarse home geography — census block group or tract — with cells below a disclosure threshold withheld.
- The vendor’s methodology note describing how home attribution is performed and what confidence it carries. If none exists, treat the attribution as substantially more uncertain than it appears.
- Population and household denominators for the same geography and vintage.
- The parent context. Mobility and foot traffic data integration covers panel bias and the ratio-versus-level rule this page assumes.
Configuration and execution parameters
| Parameter | Value for this task | Notes |
|---|---|---|
home_geography |
block group or tract | Never finer, whatever the vendor offers |
attribution_window |
30 nights or more | Shorter windows misattribute travellers |
confidence_field |
retained | Vendors publish it; keep and use it |
min_cell |
vendor threshold, at least 5 | Suppressed stays suppressed |
unattributed_share |
reported | Devices with no stable home zone |
transient_handling |
separate category | Tourists and commuters, not residents |
use |
market-level shares only | Never a claim about an individual |
The home_geography row is the one to hold firm on. Vendors sometimes offer finer attribution, and the temptation is to take it because a smaller zone looks more precise. It is not more accurate — the underlying inference has not improved — and it multiplies suppression while creating a dataset whose resolution invites uses it cannot support.
Annotated implementation
Consuming the aggregates responsibly is mostly about what the code refuses to do: it never reconstructs an individual, never publishes below the threshold, and never treats an unattributed share as zero.
from __future__ import annotations
import pandas as pd
MIN_CELL = 5
def load_origin_aggregates(path: str) -> pd.DataFrame:
"""Vendor aggregates only. Suppressed cells arrive as null and stay null."""
df = pd.read_parquet(path)
required = {"place_id", "home_zone", "month", "visitors", "visits",
"attribution_confidence"}
missing = required - set(df.columns)
assert not missing, f"aggregate is missing {missing}"
# Defensive: refuse to work with anything that looks like a trace.
forbidden = {"device_id", "advertising_id", "lat", "lon", "timestamp"}
assert not (forbidden & set(df.columns)), "trace-level columns present — stop"
df.loc[df["visitors"] < MIN_CELL, ["visitors", "visits"]] = pd.NA
return df
def origin_mix(df: pd.DataFrame, place_id: str) -> pd.DataFrame:
"""Resident shares plus an explicit unattributed and transient share."""
p = df.loc[df["place_id"] == place_id].copy()
attributed = p.loc[p["home_zone"].notna() & p["visitors"].notna()]
total_visitors = p["visitors"].sum(skipna=True)
mix = (attributed.groupby("home_zone")["visitors"].sum() / total_visitors)
suppressed = p["visitors"].isna().sum()
unattributed = p.loc[p["home_zone"].isna(), "visitors"].sum(skipna=True)
out = mix.rename("share").reset_index()
out.attrs["suppressed_cells"] = int(suppressed)
out.attrs["unattributed_share"] = float(unattributed / total_visitors)
# Shares will NOT sum to one, and that is the honest result.
return out
def weight_by_confidence(mix: pd.DataFrame, agg: pd.DataFrame) -> pd.DataFrame:
"""Down-weight zones whose attribution the vendor flags as uncertain."""
conf = agg.groupby("home_zone")["attribution_confidence"].mean()
mix = mix.merge(conf.rename("confidence"), on="home_zone", how="left")
mix["confidence"] = mix["confidence"].fillna(0.5)
mix["adjusted_share"] = mix["share"] * mix["confidence"]
return mix
The assertion against trace-level columns is worth keeping even when the vendor never supplies them. Pipelines acquire inputs over time, and a check that fails loudly the first time somebody points this function at a richer feed is considerably better than discovering the change during a review.
Failure modes and debugging
Treating unattributed visits as zero. A store in a city centre may have thirty per cent of its visits from devices with no stable home zone in the region — commuters, visitors, students. Dropping them makes the trade area look smaller and more local than it is, and hides a customer segment that is often the most valuable.
Assuming home means residence. The attribution is where a device rests overnight, which for a substantial minority is not where the census would say the person lives. Near hospitals, universities and large employers with night shifts the effect is systematic, and it is worth flagging those zones rather than treating them as ordinary.
Comparing across vendors. Two vendors’ home attributions differ in window, method and geography, so origin mixes from different sources are not comparable even when they use the same census zones. Pick one for any given analysis, and where both are available, use the second as a sense check rather than as an input.
Over-interpreting a small zone. A zone contributing four visitors is a zone about which nothing can be said, and the suppression threshold exists to prevent exactly that. Aggregating small zones into a “remainder” category preserves the total without inviting a conclusion about any of them.
Verification
- Confirm the shares do not sum to one and that the remainder is reported. A pipeline whose shares always total exactly one is normalising away the unattributed and suppressed groups.
- Check attribution confidence is being read. If the field exists and the code ignores it, low-confidence zones carry the same weight as high-confidence ones.
- Compare the metro-attributed mix against loyalty postcodes for the same store. Broad agreement is expected; a systematic shift usually reflects the panel’s demographic skew rather than an attribution error.
- Assert no trace-level column ever enters the pipeline, as a test rather than as a convention.
Frequently Asked Questions
Can this data identify where an individual lives?
Not from the aggregates this page describes, and that is by design. Cells below a disclosure threshold are withheld, home zones are coarse, and no device identifier is present, so the smallest unit of analysis is a group of households rather than a household. A pipeline that maintains those properties end to end — including in its exports — keeps the analysis at the level where it is both useful and appropriate.
Should the unattributed group be modelled or reported?
Reported, and then modelled only where the business question needs it. For most trade-area work the honest treatment is a stated share with a note about what it likely contains. Where the visitor economy is central — a store in a tourist market or a transport hub — the group deserves its own analysis using whatever the vendor can say about it at a coarse level, rather than being folded into a resident-based catchment.
How does this affect the reachable-population figure used in scoring?
It argues for reporting two numbers rather than one. The resident-attributed catchment is comparable across candidate sites and understates locations with substantial non-resident trade; a second figure covering the unattributed and distant share puts that difference on the page. Collapsing them into a single “reachable population” makes a city-centre site and a suburban one look comparable when they are not.
What is the minimum documentation this data needs internally?
Three things: which vendor and methodology produced the attribution, what window and geography it uses, and what share of visits it leaves unattributed or suppressed. Those three lines let anyone reading a trade area know what it is a picture of, and their absence is how a coarse, uncertain inference quietly becomes “where our customers live” in a slide deck.
How does home attribution affect the distance-decay fit?
It sets a floor on how precise the fit can be. Every origin zone’s travel time is computed from the zone rather than from an address, and every device in that zone is treated as if it lives at the zone’s centroid, so the near bands carry more relative error than the far ones. That is tolerable for a decay curve fitted across bands and intolerable for any claim about a specific short distance — which is why the frequency decay work bins travel time rather than fitting on raw minutes.
What changes when a vendor updates its attribution methodology?
Everything downstream that depends on levels, and rather less that depends on shares. A methodology change is a step in the series exactly like a panel change, and the only defence is recording the methodology version with each extract so a discontinuity can be attributed rather than investigated. Vendors do publish these changes; pipelines rarely record them, which is why the discontinuity usually surfaces as an unexplained shift in a trade area six weeks later.
Is a coarser home geography ever preferable?
Frequently, and not only for privacy. Suppression falls sharply at a coarser geography, so a tract-level origin mix often describes a rural market far more completely than a block-group one — the fine table looks more precise and is mostly holes. Choosing the geography from the suppression rate rather than from what the vendor offers is the practical rule.
Related
- Mobility & Foot Traffic Data Integration — the pipeline and its privacy rules.
- Handling Privacy Thresholds in Visit Aggregations — suppression on cells and margins.
- Joining Mobile Visit Data to Store Trade Areas — what the origin mix becomes.
← Back to Mobility & Foot Traffic Data Integration