Skip to main content

subway-access

Actions Status Documentation Status PyPI version PyPI platforms

NYC subway accessibility snapshot: street entrances, MTA stations, and ACS tract disability (example: Atlantic Brooklyn)

subway-access is a Python toolkit for reproducible NYC subway accessibility analysis. It fetches live MTA and Census data, scores every census tract for accessible-station coverage, measures elevator reliability, and produces research-ready panel datasets -- all from a single pip install.

Authored by Blaise Albis-Burdige.

What ships in the package

Data pipeline:

  • Fetch MTA subway stations, ADA status, elevator/escalator availability history, equipment assets, street-level entrances, and GTFS-Pathways from public APIs
  • Fetch ACS 5-year tract-level demographics (disability, senior, poverty rates)
  • Cache reusable local snapshot bundles per study area
  • Run the full workflow from the installed subway-access CLI

Analysis:

  • Euclidean and OSM-network walk catchments
  • Tract-level accessibility gap scoring and rolling station reliability
  • Station-level metrics combining coverage, need, and reliability
  • Borough and group-level summary aggregation
  • Export to GeoJSON, CSV, and station metric formats

Composable factor pipeline (v0.4):

  • Class-based Factor / Pipeline system inspired by Quantopian's Zipline
  • 7 built-in factors: NeedScore, Coverage, GapScore, NearestStationDistance, NearestStationTravelMinutes, StationCount, ReliabilityWeightedCoverage
  • Custom factors via subclassing -- bring in external data (housing costs, economic indicators) as first-class inputs
  • PipelineResult with .to_records() and optional .to_dataframe()

Temporal panel infrastructure (v0.4):

  • Multi-vintage ACS fetcher for longitudinal demographic data
  • Station ADA upgrade timeline construction
  • Geographic panel dataset builder (tract x year) with treatment/control splitting
  • Distance-based spatial weights matrix with PySAL bridge

Helpers (v0.4):

  • Multi-borough snapshot iteration with independent caching
  • Generic CSV export from frozen dataclasses with auto fieldnames
  • Metadata and markdown report writing utilities

10 public modules, 123 public symbols across models, io, analysis, factors, helpers, export, pipeline, temporal, and cli.

Quickstart

pip install subway-access

For the full plotting + geographic + network stack:

pip install "subway-access[all]"

Fetch a real official-data borough snapshot:

subway-access fetch-snapshot --geography borough --value Manhattan --cache-dir cache/manhattan

Then analyze the cached snapshot:

subway-access analyze-snapshot --cache-dir cache/manhattan --output-dir artifacts/manhattan

Python example

from pathlib import Path

from subway_access import analysis, models, pipeline

snapshot = pipeline.fetch_study_area_snapshot(
    models.AccessibilityQuery(geography="borough", value="Manhattan"),
    cache_dir=Path("cache/manhattan"),
)
catchments = analysis.generate_catchments(
    snapshot.stations,
    models.CatchmentRequest(minutes=10),
)
scores = analysis.score_accessibility(
    snapshot.stations,
    catchments,
    snapshot.demographics,
)
reliability = analysis.compute_reliability(
    snapshot.stations,
    snapshot.outages,
    models.TimeWindow(days=30),
)
gaps = analysis.analyze_gaps(scores)
print(len(gaps.records), len(reliability.records))

Factor pipeline

The composable factor pipeline lets you build custom classification models that run in a single pass over every tract. Each Factor receives row-level context (tract demographics, station data, catchments, and an extensible extras slot for external data) and returns a typed value.

from subway_access.factors import (
    CoverageFactor,
    FactorContext,
    GapScoreFactor,
    NearestStationDistanceFactor,
    NeedScoreFactor,
    Pipeline,
    ReliabilityWeightedCoverageFactor,
    StationCountFactor,
)

# Compose a pipeline from built-in factors.
pipe = (
    Pipeline()
    .add(NeedScoreFactor())
    .add(CoverageFactor())
    .add(GapScoreFactor())
    .add(NearestStationDistanceFactor())
    .add(StationCountFactor())
)

# Build contexts from a loaded snapshot.
contexts = [
    FactorContext(tract=t, stations=snapshot.stations, catchments=catchments)
    for t in snapshot.demographics.tracts
]

# Run all factors across all tracts.
result = pipe.run(contexts)
result.to_records()  # tuple of dicts
result.to_dataframe()  # pandas DataFrame (optional dep)

Custom factors are simple subclasses:

from subway_access.factors import Factor, FactorContext


class HousingCostFactor(Factor):
    name = "median_rent"
    dtype = "float"

    def __init__(self, rents: dict[str, float]) -> None:
        self._rents = rents

    def compute(self, context: FactorContext) -> float:
        return self._rents.get(context.tract.tract_id, 0.0)

Add reliability weighting to distinguish nominal from effective coverage:

# Build reliability scores from outage data.
reliability = analysis.compute_reliability(
    snapshot.stations, snapshot.outages, models.TimeWindow(days=365)
)
rel_scores = {r.station_id: r.reliability_score for r in reliability.records}

# Add reliability-weighted coverage to the pipeline.
pipe = pipe.add(ReliabilityWeightedCoverageFactor(rel_scores))

For a full worked example using the factor pipeline across all five boroughs with geographic choropleths, diagnostic checks, and auto-generated reporting, see examples/accessibility-change-over-time/.

Temporal panel

Build geographic panel datasets for difference-in-differences or spatial autoregressive panel estimation:

from subway_access.temporal import build_panel_dataset, build_upgrade_timeline

# Build an upgrade timeline from station data + known upgrade years.
timeline = build_upgrade_timeline(
    snapshot.stations,
    known_upgrades={"station_1": 2019, "station_2": 2021},
)

# Construct the panel (tract x year).
panel = build_panel_dataset(vintage_estimates, station_locations, timeline)
panel.treatment_group()  # tracts that gained accessibility
panel.control_group()  # tracts that did not
panel.to_dataframe()  # pandas DataFrame with (unit_id, period) index

The accessibility-change-over-time example builds a full 5-borough panel (2,317 tracts x 7 years = 16,219 observations) and produces a research report with treatment-vs-control balance checks, spatial weights, and model specification.

Examples

examples/ follows a self-contained project pattern. Each folder has its own pyproject.toml, README.md, main.py, and tracked reports/ output.

Extending with factor-factory (optional)

subway-access integrates with factor-factory --- a causal-inference engine registry --- and jellycell for tearsheet rendering, via the optional [factor-factory] and [tearsheets] extras:

pip install "subway-access[factor-factory,tearsheets]"

This unlocks the subway_access.reporting module (lazy-imports; a plain pip install subway-access leaves the library fully usable without these deps) and an "Engine audit" appendix on the accessibility case study that cross-checks the headline findings with five factor-factory engine fits: two-way fixed-effects DiD (did.twfe), Sun-Abraham IW (did.sa), augmented synthetic control (scm.augmented), regression discontinuity at the 800 m walk radius (rdd.rd_robust), and Moran's I via the registry (spatial.morans_i). Each fit emits a JSON artifact consumed by the shipped jellycell findings.md.j2 tearsheet template.

For the wiring pattern, see docs/factor-factory-integration.md and the minimal recipe in examples/factor-factory-rdd-walkthrough/.

Methodology

The workflow is intentionally explicit and reproducible:

  1. Select a study area through nyc-geo-toolkit (borough, community district, council district)
  2. Fetch official MTA and Census sources into a local cache
  3. Load cached files into typed, frozen in-memory datasets
  4. Generate Euclidean walk catchments (800 m / 10-min default) or OSM network isochrones
  5. Score tract centroids against accessible-station catchments via the factor pipeline
  6. Compute tract need, rolling reliability, and station metrics
  7. Optionally build a temporal panel for causal analysis
  8. Export publishable GeoJSON, CSV, and markdown outputs

Euclidean access remains the documented baseline. The network comparison layer shows how real walking routes change the coverage picture. The factor pipeline and temporal panel support research-grade analysis on top of the same data foundation.

Documentation

Quick links: Home, Getting Started, CLI Reference, Architecture, Python API, Contributing, Releasing, Changelog

Development

make install      # full contributor environment with all extras
make test         # pytest suite
make lint         # ruff + mypy + public API audit
make check        # lint + tests (pre-push gate)
make docs-build   # strict mkdocs build
make ci           # full local CI equivalent

License

MIT.

Metadata

Release files for subway-access 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for subway-access 0.5.1
File Size Uploaded
subway_access-0.5.1.tar.gz 3.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for subway-access 0.5.1
File Interpreter ABI Platform
subway_access-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 3.3 MB

Release files / subway_access-0.5.1.tar.gz

Download URL subway_access-0.5.1.tar.gz
Size 3.2 MB
Tags Source
SHA-256 checksum
How to use checksums
9660ea221689e280c14d12984f4b7edb66e50ed7677982ce27dd924f437368d4
BLAKE2b-256 checksum
How to use checksums
a5c0a0255078d6b126f1565f51eae215d72045b387d86312551857f66a5e264c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 21, 2026.

Transparency log

Release files / subway_access-0.5.1-py3-none-any.whl

Download URL subway_access-0.5.1-py3-none-any.whl
Size 78.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
445421c43ed8438d7e9551375936a0385b3b2b0f20dde82d7f6f7ae4a94016a6
BLAKE2b-256 checksum
How to use checksums
18368c2ae12b29a538c3c18208f67f600b95ca8711764d18fdd90ef364486756
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.1 This release

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page