Data Preparation
Data-preparation toolbox for the weighting workflow.
This sub-package does not orchestrate the weighting pipeline itself. Instead, it provides the reusable building blocks that the weighting pipeline uses to prepare geography, PUMS inputs, control totals, and survey seed data.
Modules
- Census geography (
census_geo) -- download and cache TIGER PUMA and block shapefiles via pygris, including block-level population inputs. - Geography crosswalk (
crosswalk) -- construct a population-weighted PUMA-to-target-zone allocation table and assign/allocate records across project geographies. - PUMS data (
pums_data) -- fetch ACS 1-year PUMS microdata from the Census API or load local extracts, with chunking and caching helpers. - Control data (
control_data) -- recode PUMS variables into weighting control categories and aggregate them into zone-level control totals. - Seed data (
seed_data) -- recode canonical survey variables into the same control categories used by the PUMS-based controls.
Together these modules provide the shared data-preparation utilities used
by WeightingPipeline
before balancing begins.
crosswalk
PUMA-specific crosswalk wrapper.
PumaCrosswalk fetches Census PUMA and block geographies and
delegates the heavy lifting to build_crosswalk.
See docs/pipeline_steps/weighting/crosswalk.md for the detailed
crosswalk explanation, diagrams, and worked example.
- Load target zone polygons; auto-discover overlapping PUMAs.
- Download/cache TIGER PUMA and block shapefiles (via
census_geo). - Rasterize block population into a density grid.
- Rasterize PUMA IDs into a categorical label grid.
exactextract: computesum(population)per target zone, grouped by PUMA label.- Normalise:
allocation_weight = pop(puma, target) / pop(puma)per PUMA.
Resolution only affects within-block population distribution granularity —
boundary accuracy is exact at any resolution due to exactextract's
analytical sub-cell coverage.
Outputs:
crosswalk_df:puma_id,study_geoid,population,allocation_weight.puma_ids: list of PUMAs overlapping the study area.
Example
xw = PumaCrosswalk(geo_cfg, state_fips="06", pums_year=2023)
households = xw.assign_households(households)
pums_hh, pums_per = xw.allocate_pums_weights(pums_hh, pums_per)
census_geo
Census TIGER geography loading.
PUMA and block geometries share the same decennial vintage (2010 for pre-2022 PUMS, 2020 for 2022+) so that block GEOIDs match the sample plan. Block population is fetched from the Census decennial API (PL 94-171 for 2020, SF1 for 2010) and joined by GEOID — the TIGER shapefiles themselves do not always carry population columns.
Processed GeoDataFrames are cached as GeoParquet under
<pipeline-cache-dir>/weighting/census_geo/ for fast repeat loads.
pums_data
PUMS microdata I/O.
Downloads ACS PUMS 1-year microdata from the Census Bureau API (through
pums_api) or loads it from local CSV / Parquet files, checks it, and casts
the API's string responses to proper numeric dtypes. Downloads are cached as
Parquet at <cache_dir>/pums/{state}_{year}_{hh|person}.parquet.
Transformation (recoding, aggregation) lives in control_data.
control_data
PUMS control-data transformation.
Recodes raw PUMS variables into the shared control categories defined in
controls.py, then aggregates weighted totals by geography (PUMA by
default).
All mapping logic lives in controls.py. This module only orchestrates
recoding (via ctrl.pums_expr()) and aggregation.
Approach:
- Load PUMS data; join persons to households to carry PUMA geography.
- Join crosswalk; multiply PUMS weight by
allocation_weightto distribute into custom zones. - For each control: apply filter, recode variable into bins/groups,
aggregate weighted sum by
(ctrl_geoid, category).
Control Variable YAML Configuration Example::
controls:
# Simple marginal — household size
- name: h_size
table: households
variable: NP
bins:
"1": [1, 1]
"2": [2, 2]
"3": [3, 3]
"4+": [4, 99]
# Grouped marginal — commute mode
- name: commute_mode
table: persons
variable: JWTRNS
groups:
drove_alone: [1]
carpool: [2, 3]
transit: [4, 5, 6, 7, 8, 9]
other: [10, 11, 12]
filter: "ESR in [1,2,4,5]" # employed persons only
seed_data
Survey seed-data preparation for weighting.
Uses ControlTarget.survey_expr() to recode canonical survey data into
control-category ints via native Polars expressions (vectorised, no
map_elements). Every control is handled uniformly.
Survey field mapping is hardcoded in each
ControlTarget
subclass — the survey_fields class attribute declares which canonical
survey columns the control reads, and survey_expr() returns the Polars
expression that maps those values to control-category ints. For example:
HHIncomeControl.survey_fields = ("income_bin",)— reads the already-binnedincome_bincolumn viaidentity_expr.GenderControl.survey_fields = ("gender",)— mapsGenderenum values toGenderCategoryints viareplace_strict.HHSizeControl.survey_fields = ("_n_persons",)— clips the person-count aggregate column to [1, 10].
No YAML config is involved; the mapping lives entirely in the control
class definitions in processing.weighting.controls.
incidence
Incidence-table construction and control-total aggregation.
Unified operations that apply identically to both survey and PUMS datasets after recoding:
- Pivot helpers — :func:
pivot_hh_controlsand :func:pivot_person_controlsconvert encoded control columns into the{ctrl}__{member}incidence layout at HH and person levels. - Incidence table — :func:
build_incidence_tableorchestrates the helpers into a single household-level table for the balancer. - Aggregate — :func:
aggregate_control_totalssums the pivoted PUMS incidence into per-zone target totals.
Category merging lives in :mod:processing.weighting.data_prep.merges.