A multimodal accessibility model of the Washington, DC transit network that measures how many jobs each census tract can reach by rail, bus, and walking — and then shows that the headline equity conclusion is dominated by two modeling choices that are rarely stated: the travel-time budget, and whether you count the time riders spend waiting.
The same data and the same code produce a Q4/Q1 job-access ratio anywhere from 1.1 to 24.8 depending on those assumptions. This project builds a specification ladder to make that dependence explicit rather than hidden.
Across 204 DC census tracts, 45-minute job accessibility shows only a mild association with income; 20-minute accessibility shows a severe one. The income–access gradient is statistically robust in every specification (Spearman ρ ≈ 0.25–0.36, all p < 0.01), but its magnitude swings 22-fold with the cutoff and wait assumptions.
| Spec | Modes | Transfer penalty | Boarding wait | Cutoff | ρ (income vs access) | p | Q1 median jobs | Q4 median jobs | Q4/Q1 |
|---|---|---|---|---|---|---|---|---|---|
| S1 | rail | 0 | none | 20 | 0.262 | 1.4e-03 | 9,796 | 243,102 | 24.8 |
| S1 | rail | 0 | none | 30 | 0.268 | 1.1e-03 | 327,976 | 492,891 | 1.5 |
| S1 | rail | 0 | none | 45 | 0.230 | 5.2e-03 | 565,817 | 596,507 | 1.1 |
| S2 | rail+bus | 0 | none | 20 | 0.358 | 1.9e-07 | 13,575 | 297,184 | 21.9 |
| S2 | rail+bus | 0 | none | 30 | 0.337 | 1.1e-06 | 323,861 | 534,454 | 1.7 |
| S2 | rail+bus | 0 | none | 45 | 0.357 | 2.1e-07 | 607,615 | 652,719 | 1.1 |
| S4 | rail+bus | 3 min | headway/2 | 20 | 0.303 | 1.3e-05 | 1,338 | 14,151 | 10.6 |
| S4 | rail+bus | 3 min | headway/2 | 30 | 0.295 | 2.2e-05 | 9,302 | 197,356 | 21.2 |
| S4 | rail+bus | 3 min | headway/2 | 45 | 0.253 | 3.0e-04 | 347,284 | 572,376 | 1.6 |
Read it two ways:
- Down a cutoff column: the equity gap is a short-trip phenomenon. At a 45-minute budget the poorest and richest tracts reach nearly the same number of jobs (ratio ≈ 1.1); at 20 minutes the richest reach ~20× more. A study that reports only 45-minute isochrones — a common default — would conclude DC transit is close to equitable. That conclusion is an artifact of the budget.
- Across the wait boundary (S2 → S4): counting boarding waits collapses short-trip access toward zero for everyone. Under realistic waiting, a 20-minute transit commute reaches almost no jobs regardless of where you live (Q1 = 1,338, Q4 = 14,151 of 656,985 total). The apparent generosity of no-wait models is doing a large amount of the work. The point is methodological: a widely-used accessibility metric can report near-equity or an order-of-magnitude gap from identical inputs, depending on assumptions that usually go unstated.
The original brief assumed the answer — it labeled the poorest 20% of tracts "equity zones" and set out to map their disadvantage. But an early rail-only result pointed the opposite way (transit-served tracts were wealthier), and that result then inverted again once bus service was added. If one modeling choice can flip the sign of the finding, the responsible thing is to vary the choices deliberately and report the whole surface, not to pick the one that tells the cleanest story.
Each rung adds exactly one assumption:
| Spec | Adds |
|---|---|
| S1 | Rail only, walk access, no penalties |
| S2 | Adds the bus network |
| S4 | Adds a 3-minute transfer penalty and a boarding wait of half the local headway |
(S3 — transfer penalty without wait — and S5 — street-network walking — are natural further rungs; S5 in particular would reduce access unevenly where rivers and highways sever the pedestrian grid.)
Network. WMATA rail and bus GTFS feeds are parsed into a directed graph. Station-to-station in-vehicle times are the median scheduled segment time. The graph uses an arrival/departure/platform node split per stop so that staying on a vehicle is free while boarding from a platform pays the wait once — this prevents both double-counting boarding time and a class of "teleporting" shortest paths where a rider hops between nearby stops without ever riding.
- S1 graph: 444 nodes, 798 edges
- S2/S4 graph: 23,092 nodes, 95,410 edges
- 202 directed rail edges, 8,393 directed bus edges (one weekday service, derived from
calendar+calendar_dates) - Median headway: rail 2.7 min, bus 16.6 min
Accessibility. For each tract centroid, a single-source Dijkstra computes travel time to every other tract;
jobs reachablesums LODES employment over tracts inside the cutoff. Distances are computed in Maryland State Plane (EPSG:2248, US feet); walking is 3 mph within 1,200 m.
Equity. Tracts are binned into income quintiles; the gradient is summarized by Spearman ρ and the Q4/Q1 median-access ratio.
Transit-burden index. Because low access means opposite things for a car-free household versus a car-owning one, tracts are ranked by no_vehicle_rate / (jobs / median_jobs) — high where many households without a car meet poor access. The top-ranked tracts cluster east of the Anacostia (Wards 7–8): median income $34k–$59k, no-vehicle rates 0.38–0.57, reaching only dozens to a few hundred jobs in 20 minutes under S4. The index uses no geographic input, so this clustering is a check that it measures something real.
- Transit: WMATA rail + bus GTFS static (
api.wmata.com) - Geography: US Census TIGER/Line 2025 tracts, DC (state 11), 206 tracts
- Jobs: LEHD LODES8 WAC (S000, JT00), 2022 — 656,985 jobs
- Demographics: ACS 2023 5-year — median household income (B19013), vehicle availability (B08201), population (B01003)
pip install -r requirements.txt
# ingest (needs a free Census API key in .env as CENSUS_KEY)
python src/get_acs.py
python src/get_lodes.py
# edges
python src/build_edges.py
python src/build_bus_edges.py
python src/compute_headways.py
# graphs + accessibility for each specification and cutoff
python src/build_graph.py S1 && python src/build_graph.py S2 && python src/build_graph.py S4
for s in S1 S2 S4; do for c in 20 30 45; do python src/build_accessibility.py $s $c; done; done
# the ladder
python src/analyze_equity.pyGTFS feeds and TIGER shapefiles are not committed (data/raw/ is gitignored); the derived tables in data/processed/ are, so the analysis runs on clone without any API keys.
These all bias access upward or otherwise simplify, and several would move the numbers if fixed. They are listed because knowing where a model is wrong is part of the result.
- Straight-line walking. Access-to-stop distance is crow-flies, understating real walking by 20–40% and understating it most where the Anacostia, the Potomac, and rail cuts break the grid — i.e. in the very tracts the burden index flags.
- Pooled headways. Headway is trips ÷ 16-hour service window per stop, so peak and off-peak are averaged; and rail service IDs are pooled, which biases rail headways low.
- Rail–rail transfers are free. Interchange stations (Metro Center, Gallery Place, L'Enfant) collapse to single nodes, so switching lines there costs nothing. This flatters trips through the core.
- Centroid origins. Population is assumed at the tract centroid, which can sit in a park or river; tracts under 100 population are dropped.
- LODES undercounts federal jobs, which in DC is a large share — so absolute job counts are conservative.
- One weekday, schedule-based. No real-time delays, no induced demand; this is a deterministic accessibility model, not a causal estimate of what a service change would cause.
Multimodal network construction from raw GTFS; a correct time-expanded-style graph that separates riding from boarding; spatial joins and projection handling in GeoPandas; a job-accessibility metric grounded in LODES rather than a hand-picked destination; and — the core of it — a specification-ladder analysis showing that a transit-equity conclusion is largely a function of modeling assumptions, with the gradient's robustness and its magnitude reported separately and honestly.