pynhts 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pynhts-0.1.1/.github/workflows/ci.yml +18 -0
- pynhts-0.1.1/.github/workflows/release.yml +36 -0
- pynhts-0.1.1/.gitignore +28 -0
- pynhts-0.1.1/CHANGELOG.md +24 -0
- pynhts-0.1.1/CITATION.cff +17 -0
- pynhts-0.1.1/CONTRIBUTING.md +18 -0
- pynhts-0.1.1/LICENSE +29 -0
- pynhts-0.1.1/PKG-INFO +130 -0
- pynhts-0.1.1/README.md +92 -0
- pynhts-0.1.1/docs/data_notes.md +67 -0
- pynhts-0.1.1/docs/index.md +53 -0
- pynhts-0.1.1/docs/methods.md +57 -0
- pynhts-0.1.1/docs/roadmap.md +24 -0
- pynhts-0.1.1/docs/validation.md +48 -0
- pynhts-0.1.1/mkdocs.yml +11 -0
- pynhts-0.1.1/paper/paper.bib +74 -0
- pynhts-0.1.1/paper/paper.md +100 -0
- pynhts-0.1.1/pyproject.toml +78 -0
- pynhts-0.1.1/src/pynhts/__init__.py +7 -0
- pynhts-0.1.1/src/pynhts/__main__.py +7 -0
- pynhts-0.1.1/src/pynhts/_registry/2017.yaml +62 -0
- pynhts-0.1.1/src/pynhts/_schema/2017/household.yaml +2924 -0
- pynhts-0.1.1/src/pynhts/_schema/2017/person.yaml +6306 -0
- pynhts-0.1.1/src/pynhts/_schema/2017/trip.yaml +4527 -0
- pynhts-0.1.1/src/pynhts/_schema/2017/vehicle.yaml +5123 -0
- pynhts-0.1.1/src/pynhts/cli.py +93 -0
- pynhts-0.1.1/src/pynhts/core/__init__.py +1 -0
- pynhts-0.1.1/src/pynhts/core/data.py +389 -0
- pynhts-0.1.1/src/pynhts/core/schema.py +112 -0
- pynhts-0.1.1/src/pynhts/io/__init__.py +27 -0
- pynhts-0.1.1/src/pynhts/io/converter.py +289 -0
- pynhts-0.1.1/src/pynhts/io/fetcher.py +237 -0
- pynhts-0.1.1/src/pynhts/io/registry.py +71 -0
- pynhts-0.1.1/src/pynhts/ml/__init__.py +197 -0
- pynhts-0.1.1/src/pynhts/py.typed +0 -0
- pynhts-0.1.1/src/pynhts/recipes/__init__.py +68 -0
- pynhts-0.1.1/src/pynhts/recipes/base.py +91 -0
- pynhts-0.1.1/src/pynhts/recipes/data/stt2017.yaml +106 -0
- pynhts-0.1.1/src/pynhts/recipes/stt2017.py +197 -0
- pynhts-0.1.1/src/pynhts/stats/__init__.py +21 -0
- pynhts-0.1.1/src/pynhts/stats/distributions.py +87 -0
- pynhts-0.1.1/src/pynhts/stats/jackknife.py +43 -0
- pynhts-0.1.1/src/pynhts/stats/survey.py +221 -0
- pynhts-0.1.1/tests/data/conftest.py +8 -0
- pynhts-0.1.1/tests/data/test_convert_real.py +104 -0
- pynhts-0.1.1/tests/data/test_loader_golden.py +111 -0
- pynhts-0.1.1/tests/data/test_network.py +20 -0
- pynhts-0.1.1/tests/data/test_real_files.py +45 -0
- pynhts-0.1.1/tests/data/test_recipes.py +51 -0
- pynhts-0.1.1/tests/data/test_schema_vs_csv.py +24 -0
- pynhts-0.1.1/tests/data/test_stats_golden.py +50 -0
- pynhts-0.1.1/tests/unit/test_cli.py +34 -0
- pynhts-0.1.1/tests/unit/test_convert.py +172 -0
- pynhts-0.1.1/tests/unit/test_data.py +146 -0
- pynhts-0.1.1/tests/unit/test_fetch.py +152 -0
- pynhts-0.1.1/tests/unit/test_ml.py +74 -0
- pynhts-0.1.1/tests/unit/test_recipes_unit.py +55 -0
- pynhts-0.1.1/tests/unit/test_schema.py +46 -0
- pynhts-0.1.1/tests/unit/test_stats.py +189 -0
- pynhts-0.1.1/tools/build_schema.py +140 -0
- pynhts-0.1.1/tools/make_validation_report.py +60 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
test:
|
|
5
|
+
runs-on: ${{ matrix.os }}
|
|
6
|
+
strategy:
|
|
7
|
+
matrix:
|
|
8
|
+
os: [ubuntu-latest, windows-latest]
|
|
9
|
+
python: ["3.10", "3.13"]
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: ${{ matrix.python }}
|
|
15
|
+
- run: pip install -e ".[dev]"
|
|
16
|
+
- run: ruff check src tests
|
|
17
|
+
- run: mypy
|
|
18
|
+
- run: pytest # data/network tests are excluded by default (no microdata in CI)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
# Publishes to PyPI with Trusted Publishing (OIDC): no token or password is stored anywhere.
|
|
3
|
+
# Trigger: publishing a GitHub release, or running the workflow by hand (workflow_dispatch).
|
|
4
|
+
on:
|
|
5
|
+
release:
|
|
6
|
+
types: [published]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
- run: python -m pip install build twine
|
|
18
|
+
- run: python -m build
|
|
19
|
+
- run: python -m twine check dist/*
|
|
20
|
+
- uses: actions/upload-artifact@v4
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment: pypi
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/download-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
pynhts-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# data and caches: never commit microdata or third-party copyrighted PDFs
|
|
2
|
+
local_data/
|
|
3
|
+
data_raw/
|
|
4
|
+
*.csv
|
|
5
|
+
*.parquet
|
|
6
|
+
*.zip
|
|
7
|
+
*.feather
|
|
8
|
+
*.arrow
|
|
9
|
+
*.parquet.*
|
|
10
|
+
*.sas7bdat
|
|
11
|
+
*.xpt
|
|
12
|
+
*.dbf
|
|
13
|
+
*.RData
|
|
14
|
+
*.rds
|
|
15
|
+
lit/*.pdf
|
|
16
|
+
.cache/
|
|
17
|
+
# python
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.py[cod]
|
|
20
|
+
.venv/
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
.ruff_cache/
|
|
24
|
+
dist/
|
|
25
|
+
build/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.coverage
|
|
28
|
+
htmlcov/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1 (2026-10-17)
|
|
4
|
+
|
|
5
|
+
First version published on PyPI (0.1.0 exists only as a GitHub release). README links made absolute so
|
|
6
|
+
that they work on the PyPI page; no code changes.
|
|
7
|
+
|
|
8
|
+
## 0.1.0 (2026-10-17)
|
|
9
|
+
|
|
10
|
+
First release. Scope: NHTS **2017** only.
|
|
11
|
+
|
|
12
|
+
* `pynhts.io`: SHA-256-pinned download of the official archives, offline mode, adoption of local
|
|
13
|
+
files, deterministic and idempotent CSV to Parquet conversion, replicate weights as float32.
|
|
14
|
+
* `pynhts.core`: codebook-derived typed schema (hierarchical, label-aware column ownership);
|
|
15
|
+
`NHTSData` loader with lazy columns, validated many-to-one joins and per-column masking of
|
|
16
|
+
survey codes (`missing="nan"|"keep"`, `skip="keep"|"nan"`); `decode_labels`.
|
|
17
|
+
* `pynhts.stats`: stratified jackknife (JKn, 98 replicates) totals, means, proportions and ratios
|
|
18
|
+
with standard error, margin of error, confidence interval, domains and groups;
|
|
19
|
+
`ratio_across` for rates built from two weight systems; Student-t quantile without SciPy.
|
|
20
|
+
* `pynhts.recipes`: replication of 24 figures of FHWA's *Summary of Travel Trends: 2017 NHTS*
|
|
21
|
+
(20 reproduced, 2 estimate-only, 2 not reproduced; gaps documented, none tuned away).
|
|
22
|
+
* `pynhts.ml`: NumPy arrays with sample weights, PyTorch tensors, household graph edges.
|
|
23
|
+
* Command line: `pynhts fetch | register | info | validate`.
|
|
24
|
+
* Documentation: methods, data pitfalls, generated validation report. JOSS paper draft.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use pynhts, please cite it as below."
|
|
3
|
+
title: "pynhts: survey-weight-aware access to US National Household Travel Survey microdata"
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
date-released: 2026-10-17
|
|
6
|
+
license: BSD-3-Clause
|
|
7
|
+
repository-code: "https://github.com/sabernaseralavi-60/pynhts"
|
|
8
|
+
authors:
|
|
9
|
+
- family-names: Naseralavi
|
|
10
|
+
given-names: Seyed Saber
|
|
11
|
+
orcid: "https://orcid.org/0000-0002-5392-910X"
|
|
12
|
+
affiliation: "Department of Civil Engineering, Shahid Bahonar University of Kerman, Kerman, Iran"
|
|
13
|
+
keywords: [NHTS, travel survey, transportation, replicate weights, jackknife]
|
|
14
|
+
abstract: >-
|
|
15
|
+
Python library that downloads and verifies the 2017 NHTS public-use files, converts them to
|
|
16
|
+
typed Parquet, merges the tables without silent errors, computes jackknife (JKn) standard errors
|
|
17
|
+
and margins of error, and reproduces published FHWA tables.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. Short rules:
|
|
4
|
+
|
|
5
|
+
1. **Set up:** `uv venv && uv pip install -e ".[dev]"`.
|
|
6
|
+
2. **Checks before a pull request:** `ruff check src tests`, `mypy`, `pytest`. The default test
|
|
7
|
+
run needs no data. Tests that need the real files carry `@pytest.mark.data`
|
|
8
|
+
(`pytest -m data`, with the files registered via `pynhts register <folder>`); the live
|
|
9
|
+
download test is `@pytest.mark.network` and is manual.
|
|
10
|
+
3. **Never commit microdata** or third-party PDFs (`.gitignore` already excludes them).
|
|
11
|
+
4. **Do not tune the library to match a published figure.** If a figure does not reproduce,
|
|
12
|
+
record it with a note in `src/pynhts/recipes/data/*.yaml` and set its `expected` status
|
|
13
|
+
honestly. Explanations of the documented gaps are very welcome as issues.
|
|
14
|
+
5. **New waves:** add a registry file (`_registry/<wave>.yaml`), generate the schema with
|
|
15
|
+
`tools/build_schema.py` from the official codebook, and add golden tests before any
|
|
16
|
+
estimator is touched.
|
|
17
|
+
6. Regenerate `docs/validation.md` with `python tools/make_validation_report.py` after any change
|
|
18
|
+
that can move an estimate.
|
pynhts-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, pynhts developers
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
pynhts-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pynhts
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Typed, survey-weight-aware access to US National Household Travel Survey microdata.
|
|
5
|
+
Project-URL: Homepage, https://github.com/sabernaseralavi-60/pynhts
|
|
6
|
+
Project-URL: Issues, https://github.com/sabernaseralavi-60/pynhts/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/sabernaseralavi-60/pynhts/blob/main/CHANGELOG.md
|
|
8
|
+
License: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: NHTS,jackknife,replicate weights,transportation,travel survey
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: numpy>=1.24
|
|
19
|
+
Requires-Dist: pandas>=2.0
|
|
20
|
+
Requires-Dist: platformdirs>=3.10
|
|
21
|
+
Requires-Dist: pooch>=1.8
|
|
22
|
+
Requires-Dist: pyarrow>=14
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
26
|
+
Requires-Dist: openpyxl; extra == 'dev'
|
|
27
|
+
Requires-Dist: pandas-stubs; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
31
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
32
|
+
Provides-Extra: graph
|
|
33
|
+
Requires-Dist: torch-geometric>=2.4; extra == 'graph'
|
|
34
|
+
Requires-Dist: torch>=2.0; extra == 'graph'
|
|
35
|
+
Provides-Extra: torch
|
|
36
|
+
Requires-Dist: torch>=2.0; extra == 'torch'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# pynhts
|
|
40
|
+
|
|
41
|
+
[](https://github.com/sabernaseralavi-60/pynhts/actions/workflows/ci.yml)
|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
Typed, survey-weight-aware Python access to US National Household Travel Survey (NHTS)
|
|
47
|
+
microdata.
|
|
48
|
+
|
|
49
|
+
> **Scope and status (0.1.0, alpha).** Only the **2017** wave is supported; other waves come
|
|
50
|
+
> after this one is solid. The API may still change. Results are validated
|
|
51
|
+
> against FHWA's published tables: 20 of 24 figures reproduce completely and 4 gaps are reported
|
|
52
|
+
> openly in [docs/validation.md](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/validation.md).
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
pip install pynhts
|
|
58
|
+
pynhts fetch --replicates # download, verify (SHA-256) and convert; about 0.4 GB of Parquet, plus 1.8 GB of raw CSV that you may delete afterwards
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Already have the NHTS 2017 CSV files? `pynhts register <folder>` adopts them after checking
|
|
62
|
+
their hashes.
|
|
63
|
+
|
|
64
|
+
## What it does
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import pynhts
|
|
68
|
+
data = pynhts.load(2017)
|
|
69
|
+
|
|
70
|
+
# lazy, validated tables: columns are joined from the table that owns them
|
|
71
|
+
trips = data.trips(["TRPMILES", "TRPTRANS", "HHFAMINC", "R_AGE"])
|
|
72
|
+
|
|
73
|
+
# weighted statistics with jackknife (JKn) standard errors and 95 % margins of error
|
|
74
|
+
s = data.survey("person", ["R_AGE_IMP", "DRIVER"])
|
|
75
|
+
s.total(domain=s.rows["DRIVER"] == "01") # 223,277 thousand drivers, MOE 827 thousand
|
|
76
|
+
data.survey("trip", ["TRPMILES"]).mean("TRPMILES") # 10.70 miles, MOE 0.40
|
|
77
|
+
|
|
78
|
+
# replicate a published table and see every gap
|
|
79
|
+
from pynhts import recipes
|
|
80
|
+
print(recipes.to_markdown(recipes.run("stt2017", data)))
|
|
81
|
+
|
|
82
|
+
# machine-learning bridge
|
|
83
|
+
from pynhts import ml
|
|
84
|
+
arr = ml.to_arrays(trips, ["TRPMILES", "R_AGE"], target="TRPTRANS", weight="WTTRDFIN")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Module | Purpose |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `pynhts.io` | verified download, offline mode, deterministic Parquet cache |
|
|
90
|
+
| `pynhts.core` | codebook-derived schema, `NHTSData` loader, missing-code handling |
|
|
91
|
+
| `pynhts.stats` | JKn totals, means, proportions, ratios; margins of error |
|
|
92
|
+
| `pynhts.recipes` | replication of published tables with honest status per figure |
|
|
93
|
+
| `pynhts.ml` | NumPy / PyTorch / graph export (PyTorch optional) |
|
|
94
|
+
|
|
95
|
+
## Why a library
|
|
96
|
+
|
|
97
|
+
Correct NHTS analysis has many silent traps (a data server that returns HTTP 200 for missing
|
|
98
|
+
files, string codes with leading zeros, `-1` skips that are not missing data, 107 copied
|
|
99
|
+
parent columns in the child tables, a vehicle `PERSONID` that is not the person key, ...).
|
|
100
|
+
They are documented in [docs/data_notes.md](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/data_notes.md) and each is covered by a test.
|
|
101
|
+
|
|
102
|
+
## Validation
|
|
103
|
+
|
|
104
|
+
* All 3,807 coded values of the official codebook (counts and weighted totals) reproduce.
|
|
105
|
+
* 24 figures from Tables 1 and 3 of FHWA's *Summary of Travel Trends: 2017 NHTS*, with margins
|
|
106
|
+
of error: 20 reproduce completely, 2 reproduce the estimate but not the margin of error,
|
|
107
|
+
2 do not reproduce. See [docs/validation.md](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/validation.md).
|
|
108
|
+
|
|
109
|
+
## Documentation
|
|
110
|
+
|
|
111
|
+
[Methods](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/methods.md) · [Data notes](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/data_notes.md) · [Validation](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/validation.md)
|
|
112
|
+
· [Roadmap](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/roadmap.md) · [Contributing](https://github.com/sabernaseralavi-60/pynhts/blob/main/CONTRIBUTING.md) · [Changelog](https://github.com/sabernaseralavi-60/pynhts/blob/main/CHANGELOG.md)
|
|
113
|
+
|
|
114
|
+
## Data
|
|
115
|
+
|
|
116
|
+
The NHTS microdata are public data of the US Federal Highway Administration
|
|
117
|
+
(<https://nhts.ornl.gov>). They are **never** bundled with or committed to this repository.
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
uv venv && uv pip install -e ".[dev]"
|
|
123
|
+
pytest # unit tests, no data needed
|
|
124
|
+
pytest -m data # real-data tests (files registered in the pynhts cache)
|
|
125
|
+
pytest -m network -o addopts=-ra # live ORNL download, manual only
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Citation
|
|
129
|
+
|
|
130
|
+
See [CITATION.cff](https://github.com/sabernaseralavi-60/pynhts/blob/main/CITATION.cff). License: BSD-3-Clause.
|
pynhts-0.1.1/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# pynhts
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sabernaseralavi-60/pynhts/actions/workflows/ci.yml)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Typed, survey-weight-aware Python access to US National Household Travel Survey (NHTS)
|
|
9
|
+
microdata.
|
|
10
|
+
|
|
11
|
+
> **Scope and status (0.1.0, alpha).** Only the **2017** wave is supported; other waves come
|
|
12
|
+
> after this one is solid. The API may still change. Results are validated
|
|
13
|
+
> against FHWA's published tables: 20 of 24 figures reproduce completely and 4 gaps are reported
|
|
14
|
+
> openly in [docs/validation.md](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/validation.md).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
pip install pynhts
|
|
20
|
+
pynhts fetch --replicates # download, verify (SHA-256) and convert; about 0.4 GB of Parquet, plus 1.8 GB of raw CSV that you may delete afterwards
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Already have the NHTS 2017 CSV files? `pynhts register <folder>` adopts them after checking
|
|
24
|
+
their hashes.
|
|
25
|
+
|
|
26
|
+
## What it does
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import pynhts
|
|
30
|
+
data = pynhts.load(2017)
|
|
31
|
+
|
|
32
|
+
# lazy, validated tables: columns are joined from the table that owns them
|
|
33
|
+
trips = data.trips(["TRPMILES", "TRPTRANS", "HHFAMINC", "R_AGE"])
|
|
34
|
+
|
|
35
|
+
# weighted statistics with jackknife (JKn) standard errors and 95 % margins of error
|
|
36
|
+
s = data.survey("person", ["R_AGE_IMP", "DRIVER"])
|
|
37
|
+
s.total(domain=s.rows["DRIVER"] == "01") # 223,277 thousand drivers, MOE 827 thousand
|
|
38
|
+
data.survey("trip", ["TRPMILES"]).mean("TRPMILES") # 10.70 miles, MOE 0.40
|
|
39
|
+
|
|
40
|
+
# replicate a published table and see every gap
|
|
41
|
+
from pynhts import recipes
|
|
42
|
+
print(recipes.to_markdown(recipes.run("stt2017", data)))
|
|
43
|
+
|
|
44
|
+
# machine-learning bridge
|
|
45
|
+
from pynhts import ml
|
|
46
|
+
arr = ml.to_arrays(trips, ["TRPMILES", "R_AGE"], target="TRPTRANS", weight="WTTRDFIN")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Module | Purpose |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `pynhts.io` | verified download, offline mode, deterministic Parquet cache |
|
|
52
|
+
| `pynhts.core` | codebook-derived schema, `NHTSData` loader, missing-code handling |
|
|
53
|
+
| `pynhts.stats` | JKn totals, means, proportions, ratios; margins of error |
|
|
54
|
+
| `pynhts.recipes` | replication of published tables with honest status per figure |
|
|
55
|
+
| `pynhts.ml` | NumPy / PyTorch / graph export (PyTorch optional) |
|
|
56
|
+
|
|
57
|
+
## Why a library
|
|
58
|
+
|
|
59
|
+
Correct NHTS analysis has many silent traps (a data server that returns HTTP 200 for missing
|
|
60
|
+
files, string codes with leading zeros, `-1` skips that are not missing data, 107 copied
|
|
61
|
+
parent columns in the child tables, a vehicle `PERSONID` that is not the person key, ...).
|
|
62
|
+
They are documented in [docs/data_notes.md](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/data_notes.md) and each is covered by a test.
|
|
63
|
+
|
|
64
|
+
## Validation
|
|
65
|
+
|
|
66
|
+
* All 3,807 coded values of the official codebook (counts and weighted totals) reproduce.
|
|
67
|
+
* 24 figures from Tables 1 and 3 of FHWA's *Summary of Travel Trends: 2017 NHTS*, with margins
|
|
68
|
+
of error: 20 reproduce completely, 2 reproduce the estimate but not the margin of error,
|
|
69
|
+
2 do not reproduce. See [docs/validation.md](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/validation.md).
|
|
70
|
+
|
|
71
|
+
## Documentation
|
|
72
|
+
|
|
73
|
+
[Methods](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/methods.md) · [Data notes](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/data_notes.md) · [Validation](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/validation.md)
|
|
74
|
+
· [Roadmap](https://github.com/sabernaseralavi-60/pynhts/blob/main/docs/roadmap.md) · [Contributing](https://github.com/sabernaseralavi-60/pynhts/blob/main/CONTRIBUTING.md) · [Changelog](https://github.com/sabernaseralavi-60/pynhts/blob/main/CHANGELOG.md)
|
|
75
|
+
|
|
76
|
+
## Data
|
|
77
|
+
|
|
78
|
+
The NHTS microdata are public data of the US Federal Highway Administration
|
|
79
|
+
(<https://nhts.ornl.gov>). They are **never** bundled with or committed to this repository.
|
|
80
|
+
|
|
81
|
+
## Development
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
uv venv && uv pip install -e ".[dev]"
|
|
85
|
+
pytest # unit tests, no data needed
|
|
86
|
+
pytest -m data # real-data tests (files registered in the pynhts cache)
|
|
87
|
+
pytest -m network -o addopts=-ra # live ORNL download, manual only
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Citation
|
|
91
|
+
|
|
92
|
+
See [CITATION.cff](https://github.com/sabernaseralavi-60/pynhts/blob/main/CITATION.cff). License: BSD-3-Clause.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# NHTS 2017: pitfalls found while building pynhts
|
|
2
|
+
|
|
3
|
+
Every item below was checked against the files or the official documents, and each is covered
|
|
4
|
+
by a test. They are the reason the library exists.
|
|
5
|
+
|
|
6
|
+
## Download and files
|
|
7
|
+
|
|
8
|
+
* **The ORNL server answers HTTP 200 with an HTML page for files that do not exist.** A status
|
|
9
|
+
check proves nothing. pynhts pins SHA-256 hashes for both archives and every member
|
|
10
|
+
(`src/pynhts/_registry/2017.yaml`).
|
|
11
|
+
* The files dated 2019 and the archive currently served (modified 2023) are byte-identical:
|
|
12
|
+
all four core CSV hashes match.
|
|
13
|
+
* There is **no trip-level replicate file**. `perwgt.csv` holds two blocks, the person weight
|
|
14
|
+
(`WTPERFIN`, `WTPERFIN1..98`) and the travel-day weight (`WTTRDFIN`, `WTTRDFIN1..98`); trips
|
|
15
|
+
get replicates by joining on `HOUSEID` + `PERSONID`.
|
|
16
|
+
|
|
17
|
+
## Codes
|
|
18
|
+
|
|
19
|
+
* Coded columns are **strings with leading zeros** (`"01"`). Letting a reader infer types
|
|
20
|
+
silently turns them into integers. pynhts types every column from the codebook.
|
|
21
|
+
* Four different kinds of non-answer exist: `-9` not ascertained, `-8` don't know, `-7`
|
|
22
|
+
refused, and `-1` **appropriate skip** (the question did not apply; not missing). `-88`/`-77`
|
|
23
|
+
are "don't know"/"prefer not to answer" in numeric fields where -8/-7 could be real values.
|
|
24
|
+
* The codebook counts `-88` together with the `-8` rows (and `-77` with `-7`) in `YEARMILE` and
|
|
25
|
+
`OD_READ`: 149 and 144 rows hold -8/-7 although only -88/-77 are listed. Masking only the
|
|
26
|
+
listed codes would leave them in as negative numbers. pynhts masks them too.
|
|
27
|
+
* The codebook lists only the codes **observed in each table**, so the same variable can
|
|
28
|
+
declare different codes in the household and trip files. pynhts masks per column from the
|
|
29
|
+
schema of the table the column is read from, and a test proves that the real data contain no
|
|
30
|
+
undeclared missing or skip code.
|
|
31
|
+
|
|
32
|
+
## Tables and keys
|
|
33
|
+
|
|
34
|
+
* **Child files carry copies of parent columns** (107 of them: 35 in the person file, 35 in
|
|
35
|
+
the vehicle file, 37 in the trip file). A naive merge creates `_x`/`_y` columns or picks the
|
|
36
|
+
wrong weight. The loader reads copies from the child table and joins only genuinely missing
|
|
37
|
+
columns, with a validated many-to-one join. A test proves that every copy equals its parent.
|
|
38
|
+
* **The same name can be a different variable.** In the vehicle file `PERSONID` is the person
|
|
39
|
+
who mainly drives the vehicle, not the person key, and `VEHID` is the vehicle's own key.
|
|
40
|
+
Ownership is therefore decided by name *and* label, and only toward ancestors.
|
|
41
|
+
* Vehicles use the **household** weight `WTHHFIN`.
|
|
42
|
+
|
|
43
|
+
## Weights and variance
|
|
44
|
+
|
|
45
|
+
* The 2017 weighting report builds 98 jackknife replicates (14 variance strata of 7 units), so
|
|
46
|
+
Var = 6/7 * sum of squared replicate deviations. FHWA's margin of error equals the standard
|
|
47
|
+
error times t(0.975, 98) = 1.984 (checked against the published margins of error).
|
|
48
|
+
* `WTTRDFIN` equals 365 x `WTPERFIN`, in the full weights and in every replicate (to 1e-7,
|
|
49
|
+
float32 storage). All trips of one person share one weight, which lets trip estimates run on
|
|
50
|
+
264,234 person-days instead of a 923,572 x 98 matrix.
|
|
51
|
+
* Replicates are stored as float32; they are cast to float64 before any squaring.
|
|
52
|
+
* Rates *per household* published by FHWA are person-weighted totals divided by
|
|
53
|
+
household-weighted households. They are ratios across two weight systems, which
|
|
54
|
+
`pynhts.stats.ratio_across` supports.
|
|
55
|
+
|
|
56
|
+
## Variables
|
|
57
|
+
|
|
58
|
+
* `R_AGE` leaves 496 persons without an age; the report's age bands use the imputed age
|
|
59
|
+
`R_AGE_IMP`.
|
|
60
|
+
* The public person file starts at age 5. The report's printed total of persons (321,419
|
|
61
|
+
thousand) exceeds the sum of its own age bands (301,599) by 19,820 thousand; the weights
|
|
62
|
+
reproduce the sum of the bands exactly. The cause is not established.
|
|
63
|
+
* 656 trips have `TRPMILES = -9`. They count as trips but add no miles.
|
|
64
|
+
* The 2017 survey changed how trip distance is obtained (shortest route between geocoded
|
|
65
|
+
points instead of self-report). The report therefore prints "original" and "adjusted"
|
|
66
|
+
2017 values; pynhts reproduces the **original** ones, which are what the public variable
|
|
67
|
+
`TRPMILES` contains.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# pynhts
|
|
2
|
+
|
|
3
|
+
Typed, survey-weight-aware Python access to US National Household Travel Survey (NHTS)
|
|
4
|
+
microdata. Phase 1 covers the **2017** wave.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
pip install pynhts
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Five-minute tour
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import pynhts
|
|
16
|
+
|
|
17
|
+
data = pynhts.load(2017) # downloads, verifies (SHA-256) and converts on first use
|
|
18
|
+
|
|
19
|
+
# 1. Lazy, validated tables; columns are joined from the table that owns them
|
|
20
|
+
trips = data.trips(["TRPMILES", "TRPTRANS", "HHFAMINC", "R_AGE"])
|
|
21
|
+
|
|
22
|
+
# 2. Correct weighted statistics with margins of error (jackknife, 98 replicate weights)
|
|
23
|
+
s = data.survey("person", ["R_AGE_IMP", "DRIVER"])
|
|
24
|
+
s.total(domain=s.rows["DRIVER"] == "01") # 223,277 thousand drivers, MOE 827 thousand
|
|
25
|
+
|
|
26
|
+
t = data.survey("trip", ["TRPMILES"])
|
|
27
|
+
t.mean("TRPMILES") # 10.70 miles, MOE 0.40
|
|
28
|
+
|
|
29
|
+
# 3. Replicate published tables and see every gap
|
|
30
|
+
from pynhts import recipes
|
|
31
|
+
print(recipes.to_markdown(recipes.run("stt2017", data)))
|
|
32
|
+
|
|
33
|
+
# 4. Hand data to machine-learning code
|
|
34
|
+
from pynhts import ml
|
|
35
|
+
arr = ml.to_arrays(trips, ["TRPMILES", "R_AGE"], target="TRPTRANS", weight="WTTRDFIN")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Command line: `pynhts fetch --replicates`, `pynhts register <folder-with-your-CSVs>`,
|
|
39
|
+
`pynhts info`, `pynhts validate`.
|
|
40
|
+
|
|
41
|
+
## Where the data live
|
|
42
|
+
|
|
43
|
+
Raw CSV and Parquet files go to `$PYNHTS_CACHE_DIR` or the user cache directory. They are
|
|
44
|
+
never part of the package or of the git repository. Files you already have can be adopted
|
|
45
|
+
with `pynhts register`, after their hashes are checked. After conversion the raw CSVs can be
|
|
46
|
+
deleted: the Parquet files are enough.
|
|
47
|
+
|
|
48
|
+
## Pages
|
|
49
|
+
|
|
50
|
+
* [Validation against published tables](validation.md): what reproduces and what does not.
|
|
51
|
+
* [Methods](methods.md): weights, jackknife variance, margins of error.
|
|
52
|
+
* [Data notes](data_notes.md): the NHTS 2017 pitfalls found while building the library.
|
|
53
|
+
* [Roadmap](roadmap.md)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Methods
|
|
2
|
+
|
|
3
|
+
## Estimators
|
|
4
|
+
|
|
5
|
+
`data.survey(table, columns)` returns a `Survey`: the rows of a table bound to the 98 replicate
|
|
6
|
+
weights of the unit that carries the weight.
|
|
7
|
+
|
|
8
|
+
| Table | Weight | Replicate unit |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| household, vehicle | `WTHHFIN` | household |
|
|
11
|
+
| person | `WTPERFIN` | person |
|
|
12
|
+
| trip | `WTTRDFIN` | person-day |
|
|
13
|
+
|
|
14
|
+
Supported estimators, each with optional `domain=` (subpopulation) and `by=` (groups):
|
|
15
|
+
|
|
16
|
+
* `total(y)`: weighted total (`y=None` counts rows),
|
|
17
|
+
* `mean(y)`: ratio of the weighted total of `y` to the weighted count of rows with a value,
|
|
18
|
+
* `proportion(condition)`: mean of a boolean,
|
|
19
|
+
* `ratio(numerator, denominator)`: ratio of two weighted totals,
|
|
20
|
+
* `ratio_across(survey_a, y_a, survey_b, y_b)`: ratio between two surveys that share the
|
|
21
|
+
replicate index (person-weighted trips per household-weighted household).
|
|
22
|
+
|
|
23
|
+
Every result row has `estimate`, `se`, `moe`, `ci_low`, `ci_high`, `cv` and `n` (rows
|
|
24
|
+
contributing).
|
|
25
|
+
|
|
26
|
+
## Variance
|
|
27
|
+
|
|
28
|
+
For an estimate `theta` with replicate estimates `theta_1 ... theta_98`:
|
|
29
|
+
|
|
30
|
+
Var(theta) = (6/7) * sum_r (theta_r - theta)^2 (stratified jackknife, JKn)
|
|
31
|
+
MOE = t(0.975, 98) * sqrt(Var) (95 %)
|
|
32
|
+
|
|
33
|
+
The factor 6/7 = (n_h - 1)/n_h comes from the 14 variance strata of 7 variance units described
|
|
34
|
+
in the 2017 NHTS weighting report. For ratios, the ratio is recomputed under every replicate
|
|
35
|
+
weight, so the variance reflects the variability of numerator and denominator jointly.
|
|
36
|
+
`level=` changes the confidence level; the degrees of freedom default to 98 as in FHWA's
|
|
37
|
+
tables.
|
|
38
|
+
|
|
39
|
+
## Rows and units
|
|
40
|
+
|
|
41
|
+
All rows of one unit share one weight, so `sum_rows w_row * y_row = sum_units W_u * (sum of y
|
|
42
|
+
in unit u)`. The engine aggregates row values to the unit once (`numpy.bincount`) and works
|
|
43
|
+
on a matrix of at most 264,234 x 98 doubles, even for 923,572 trips.
|
|
44
|
+
|
|
45
|
+
## Missing values
|
|
46
|
+
|
|
47
|
+
A row whose analysis variable is NA is dropped from numerator and denominator (complete cases
|
|
48
|
+
inside the domain), like `na.rm = TRUE` in R's `survey` package. Load tables with
|
|
49
|
+
`missing="nan"` (default) so that survey codes become NA. This is a statement about the
|
|
50
|
+
estimator, not about nonresponse bias: item nonresponse can bias an estimate and pynhts does not
|
|
51
|
+
adjust for it.
|
|
52
|
+
|
|
53
|
+
## What is not provided
|
|
54
|
+
|
|
55
|
+
Variance for regression coefficients and other model-based estimates is not implemented yet;
|
|
56
|
+
the replicate weights are available (`data.replicate_weights`) for users who refit a model once
|
|
57
|
+
per replicate. Only the 2017 wave is supported.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
## Done (version 0.1.0, NHTS 2017)
|
|
4
|
+
|
|
5
|
+
* Hash-pinned download, offline mode, adoption of local files (`pynhts.io`).
|
|
6
|
+
* Codebook-derived typed schema, deterministic idempotent Parquet cache with float32 replicate
|
|
7
|
+
weights (`pynhts.io`, `pynhts.core.schema`).
|
|
8
|
+
* `NHTSData` loader with lazy columns, validated joins and per-column missing-code masking
|
|
9
|
+
(`pynhts.core`).
|
|
10
|
+
* Jackknife estimators with margins of error and a cross-survey ratio (`pynhts.stats`).
|
|
11
|
+
* Recipe that reproduces 24 figures of FHWA's *Summary of Travel Trends: 2017 NHTS*
|
|
12
|
+
(`pynhts.recipes`); 20 reproduce completely, 4 gaps are documented.
|
|
13
|
+
* Arrays, PyTorch tensors and household graph edges (`pynhts.ml`); command line interface.
|
|
14
|
+
|
|
15
|
+
## Not done yet
|
|
16
|
+
|
|
17
|
+
* **Other waves** (2022 NextGen, 2009, 2001, 1995). Deliberately postponed until the 2017
|
|
18
|
+
schema, merge logic and weighting were validated. The registry and schema layers are
|
|
19
|
+
per-wave, so a new wave adds files, not code paths.
|
|
20
|
+
* Variance of regression coefficients (refit once per replicate weight).
|
|
21
|
+
* More recipes: other tables of the Summary of Travel Trends and seminal NHTS papers.
|
|
22
|
+
* A hosted documentation site and a PyPI release.
|
|
23
|
+
* Explaining the four documented gaps (see [validation](validation.md)); reports from users
|
|
24
|
+
who can are welcome.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Validation against published NHTS 2017 tables
|
|
2
|
+
|
|
3
|
+
*Generated by `tools/make_validation_report.py` on 2026-10-17 with pynhts 0.1.1. Do not edit by hand.*
|
|
4
|
+
|
|
5
|
+
**Source:** McGuckin, N. and Fucci, A. (2018). Summary of Travel Trends: 2017 National Household Travel Survey. Federal Highway Administration.
|
|
6
|
+
|
|
7
|
+
**Scorecard (24 published figures):** 20 reproduced, 2 estimate reproduced but margin of error differs, 2 not reproduced.
|
|
8
|
+
|
|
9
|
+
Status rule: the estimate must match the printed figure to half a unit of its last printed digit; the 95 % margin of error (MOE = t(0.975, 98) x JKn standard error) must match to the same rounding or 0.1 %, whichever is larger. Figures are in the report's units (thousands for Table 1, daily rates for Table 3). The *MOE diff* column is (ours - published) / published.
|
|
10
|
+
|
|
11
|
+
| Table | Quantity | Published | MOE pub. | Estimate | MOE | MOE diff | Status |
|
|
12
|
+
|---|---|---:|---:|---:|---:|---:|---|
|
|
13
|
+
| 1a | Households, all (thousands) | 118,208 | | 118,208 | 0 | | reproduced |
|
|
14
|
+
| 1a | Households, 1 person (thousands) | 32,952 | | 32,952 | 0 | | reproduced |
|
|
15
|
+
| 1a | Households, 2 persons (thousands) | 40,056 | | 40,056 | 0 | | reproduced |
|
|
16
|
+
| 1a | Households, 3 persons (thousands) | 18,521 | 97 | 18,521 | 97 | +0.4% | reproduced |
|
|
17
|
+
| 1a | Households, 4+ persons (thousands) | 26,679 | 97 | 26,679 | 97 | +0.4% | reproduced |
|
|
18
|
+
| 1b | Persons, all (thousands) | 321,419 | 0 | 301,599 | 0 | | not_reproduced |
|
|
19
|
+
| 1b | Persons under 16 (thousands) | 45,498 | 756 | 45,498 | 756 | +0.0% | reproduced |
|
|
20
|
+
| 1b | Persons 16-19 (thousands) | 17,755 | 945 | 17,755 | 945 | +0.0% | reproduced |
|
|
21
|
+
| 1b | Persons 20-34 (thousands) | 64,339 | 954 | 64,339 | 954 | -0.0% | reproduced |
|
|
22
|
+
| 1b | Persons 35-64 (thousands) | 126,350 | 985 | 126,350 | 986 | +0.1% | reproduced |
|
|
23
|
+
| 1b | Persons 65+ (thousands) | 47,657 | 0 | 47,657 | 0 | | reproduced |
|
|
24
|
+
| 1b | Persons 16+ (thousands) | 256,101 | 756 | 256,101 | 756 | +0.0% | reproduced |
|
|
25
|
+
| 1c | Drivers (thousands) | 223,277 | 827 | 223,277 | 827 | +0.0% | reproduced |
|
|
26
|
+
| 1c | Workers (thousands) | 156,988 | 1,012 | 156,988 | 1,012 | -0.0% | reproduced |
|
|
27
|
+
| 3a | Daily person trips per household | 8.60 | 0.10 | 8.60 | 0.10 | +1.9% | reproduced |
|
|
28
|
+
| 3a | Daily person miles per household | 92.02 | 3.50 | 92.02 | 3.50 | -0.1% | reproduced |
|
|
29
|
+
| 3a | Daily vehicle trips per household | 5.11 | 0.06 | 5.11 | 0.06 | -1.1% | reproduced |
|
|
30
|
+
| 3a | Daily vehicle miles per household | 48.81 | 2.04 | 48.81 | 2.04 | +0.1% | reproduced |
|
|
31
|
+
| 3b | Daily person trips per person | 3.37 | 0.04 | 3.37 | 0.04 | -0.2% | reproduced |
|
|
32
|
+
| 3b | Daily person miles per person | 36.07 | 1.47 | 36.07 | 1.37 | -6.8% | estimate_only |
|
|
33
|
+
| 3b | Average person trip length (miles) | 10.70 | 0.40 | 10.70 | 0.40 | -0.6% | reproduced |
|
|
34
|
+
| 3b | Daily vehicle trips per driver | 2.70 | 0.03 | 2.71 | 0.03 | -1.6% | not_reproduced |
|
|
35
|
+
| 3b | Daily vehicle miles per driver | 25.84 | 1.04 | 25.84 | 1.06 | +1.4% | estimate_only |
|
|
36
|
+
| 3b | Average vehicle trip length (miles) | 9.55 | 0.37 | 9.55 | 0.37 | +1.1% | reproduced |
|
|
37
|
+
|
|
38
|
+
## Known gaps
|
|
39
|
+
|
|
40
|
+
* **Persons, all (thousands)** (`persons_all`, Table 1b, status `not_reproduced`): The printed total exceeds the sum of the report's own five age bands (301,599) by 19,820 thousand; the weights reproduce that sum exactly and the public file has no persons under age 5. Cause not established (a population-control baseline is a plausible but unverified explanation).
|
|
41
|
+
|
|
42
|
+
* **Daily person miles per person** (`pmt_per_person`, Table 3b, status `estimate_only`): Estimate reproduces (36.07) but the JKn margin of error is 1.37, 6.7 percent below the printed 1.47, while the MOE of person miles per household (3.50) and of trip length (0.40) reproduce. WTTRDFIN equals 365 x WTPERFIN in the full weights and in all 98 replicates (relative difference below 1.1e-7), so the weight system is not the cause. Not established.
|
|
43
|
+
|
|
44
|
+
* **Daily vehicle trips per driver** (`vtrips_per_driver`, Table 3b, status `not_reproduced`): We obtain 2.7064, which rounds to 2.71 against the printed 2.70 (0.2 percent). The MOE (0.0295) matches. Vehicle trips are defined here as trips on which the respondent drove (DRVR_FLG = 01); a slightly different definition in the report is the likely cause but is not established.
|
|
45
|
+
|
|
46
|
+
* **Daily vehicle miles per driver** (`vmt_per_driver`, Table 3b, status `estimate_only`): Estimate reproduces (25.84); JKn MOE is 1.055 against the printed 1.04 (1.4 percent).
|
|
47
|
+
|
|
48
|
+
These gaps are reported, not tuned away: no rule in the library was changed to make a figure match. If you can explain one, please open an issue.
|