Building a Daytime Population Layer from Worker Flows
This page solves one exact task: producing a daytime population estimate per zone by adding inbound commuters to the residents who stay, subtracting the residents who leave, and using the result to score sites whose trade depends on people at work rather than people at home.
Residential population is the default demographic input everywhere in this stack, and for a weekly grocery shop it is the right one. For a lunchtime format, a coffee shop, a convenience store in a business district or a pharmacy near a hospital, it is close to the wrong measurement — the customers are in the zone from nine to five and counted somewhere else entirely.
Prerequisites
- Residential population by zone from ACS or equivalent estimates.
- A commuting flow table — origin zone to workplace zone, with worker counts. Published employment-flow statistics are the standard source.
- Employment counts by workplace zone, used as a control total.
- Python packages:
pandas,geopandas. Install withpip install pandas geopandas.
Configuration and execution parameters
| Parameter | Value for this task | Notes |
|---|---|---|
zone_geography |
tract | Flow tables rarely support finer reliably |
at_home_share |
from the flow table’s non-commuting rows | Remote and non-working residents |
worker_hours |
09:00–17:00 weekday | State it; a different daypart is a different layer |
student_flows |
separate layer if available | Schools and campuses behave like workplaces |
control_to |
workplace employment totals | Flows and totals disagree; reconcile explicitly |
flow_vintage |
recorded | Commuting data lags; a five-year-old flow is common |
remote_adjustment |
explicit factor | Working patterns have changed; do not use a stale share silently |
The remote_adjustment row deserves emphasis. Published commuting tables describe where people worked when the data was collected, and the share of workers who now work from home some or all of the week is both large and market-specific. Applying an unadjusted flow table overstates city-centre daytime population and understates suburban, in a direction that has grown rather than shrunk.
Annotated implementation
The calculation is an accounting identity applied per zone, plus a reconciliation against employment totals.
from __future__ import annotations
import pandas as pd
REMOTE_SHARE_DEFAULT = 0.22 # market-specific; never leave at a default silently
def daytime_population(residents: pd.DataFrame, flows: pd.DataFrame,
employment: pd.DataFrame,
remote_share: float = REMOTE_SHARE_DEFAULT) -> pd.DataFrame:
"""Residents who stay + workers who arrive, controlled to employment totals.
residents: zone_id, population, workers_resident
flows: home_zone, work_zone, workers
employment: zone_id, jobs
"""
# Workers arriving, discounted for those now working remotely.
inbound = (flows.groupby("work_zone")["workers"].sum()
.mul(1 - remote_share).rename("inbound"))
outbound = (flows.loc[flows["home_zone"] != flows["work_zone"]]
.groupby("home_zone")["workers"].sum()
.mul(1 - remote_share).rename("outbound"))
df = (residents.set_index("zone_id")
.join(inbound, how="left").join(outbound, how="left").fillna(0.0))
df["daytime"] = df["population"] - df["outbound"] + df["inbound"]
# Flow tables and employment totals disagree; scale inbound to the control
# so a zone cannot receive more workers than it has jobs for.
df = df.join(employment.set_index("zone_id")["jobs"], how="left")
over = df["inbound"] > df["jobs"] * 1.1
df.loc[over, "inbound"] = df.loc[over, "jobs"]
df.loc[over, "daytime"] = (df.loc[over, "population"]
- df.loc[over, "outbound"] + df.loc[over, "inbound"])
df["daytime_ratio"] = df["daytime"] / df["population"].clip(lower=1)
return df.reset_index()
The reconciliation against employment totals is not cosmetic. Flow tables are surveys with their own error, and a zone whose inbound workers exceed its jobs by fifty per cent is producing a daytime figure that will look impressive in a site score and cannot be true.
Failure modes and debugging
Using a stale remote-working share. The single largest source of error in a contemporary daytime layer. A flow table collected before working patterns changed, applied without adjustment, gives a central business district a daytime population it no longer has. Where a local estimate of remote working exists, use it; where it does not, use a national figure, state it prominently, and re-check annually.
Double-counting the workers who live where they work. A resident who works in the same zone should not be added as an inbound commuter and also retained as a resident. Excluding same-zone flows from the outbound sum, as above, keeps the identity consistent.
Applying the layer to the wrong format. A daytime population is the right denominator for a lunchtime or convenience format and the wrong one for a weekly grocery shop, whose customers really are at home. Using it universally replaces one systematic error with another.
Ignoring the weekend. A daytime layer describes weekdays. A retail park whose trade peaks on Saturday needs a different treatment entirely, and applying the weekday layer to it understates the residential catchment that actually shops there.
Verification
- Check the national totals balance. Summed across zones, daytime population should approximate residential population; a large discrepancy means flows are being counted in one direction only.
- Compare the highest daytime-ratio zones against what is actually there. They should be business districts, hospitals, universities and industrial parks. Anything else is a flow-table artifact.
- Validate against observed footfall where a panel is available: a zone’s daytime ratio should correlate with the weekday-to-weekend visit ratio at stores inside it.
- Re-run with and without the remote adjustment and compare site rankings. If the ranking is insensitive, the adjustment can be treated as a refinement; if it is not, the adjustment deserves a local estimate rather than a national default.
Frequently Asked Questions
Where does the flow data come from and how current is it?
From published employment-flow statistics, which are collected on a multi-year cycle and released with a lag — so a current pipeline is typically working with a table several years old. That is tolerable for the structure of commuting, which changes slowly, and intolerable for the level of remote working, which changed abruptly. Treating the flow table as a description of where people commute and applying a separate, current adjustment for whether they commute is the practical split.
Should students be included?
Yes, as their own layer where the data allows. A university campus behaves like a large workplace for weekday trade and unlike one in the vacation, so folding students into worker flows produces a daytime figure that is right in term and badly wrong for a quarter of the year. Modelling them separately also lets a site near a campus be scored on the seasonality that actually governs it.
How does this interact with the drive-time catchment?
It replaces the population attached to zones, not the geometry. The catchment is still a drive-time polygon, and the apportionment still splits partially covered zones by area — what changes is which population figure each zone carries. That means a daytime layer drops into an existing pipeline as an alternative column rather than as a new stage.
Is a blended population ever the right answer?
Frequently, and the blend should be per format rather than global. A convenience store genuinely trades on both populations at different hours, so a weighted combination reflects its business better than either alone. What matters is that the weights are stated in the scoring configuration next to the criterion weights, and that the same blend is applied to every candidate of that format.
Can mobility data replace the flow table entirely?
Increasingly, yes, and with better currency. A panel observes where devices are during working hours directly, so a daytime population can be estimated from observation rather than from a survey plus an assumption about remote working. The catch is the panel’s own bias, which is why the two are best used together: the flow table provides the structure and the control totals, and the panel provides a current correction to the commuting share. Where only one is available, the flow table with a stated remote adjustment is the more defensible starting point.
How should the layer be stored?
As an additional population column on the same zone table rather than as a separate dataset, with the vintage of both the flow table and the remote adjustment recorded alongside. Keeping it beside the residential figure means every downstream join picks up both automatically and the choice of which to use becomes a scoring decision rather than a data-engineering one. A separate table invites two versions of the truth and a quarterly argument about which is current.
Does the layer need its own validation gate?
A light one. Two assertions cover most of the risk: that the national sum of daytime population stays within a percentage point of the residential sum, and that no zone’s inbound workers exceed its published jobs. Both are one-line checks over the finished layer, both fail loudly when a flow table is joined on the wrong key, and neither requires any external data the pipeline does not already hold.
Related
- Mobility & Foot Traffic Data Integration — observed visit data that can validate a daytime layer.
- Weighting Demographic Variables for Target Audiences — where the blended population enters the index.
- Syncing US Census ACS Data via API — the residential denominators this layer starts from.
← Back to Mobility & Foot Traffic Data Integration