Skip to content
This repository was archived by the owner on Jul 2, 2026. It is now read-only.

Commit 7f8503b

Browse files
authored
Use microimpute target filters for source imputations (#1103)
* Use microimpute target filters for source imputations * Add imputation changelog fragment * Address source imputation review findings * Require raw ACS allocation flags * Anchor QRF Social Security split by age * Bump SSI disability imputation cache * Require SIPP tip allocation flags * Fix source imputation cache and sampling guards
1 parent 7c567f2 commit 7f8503b

21 files changed

Lines changed: 1291 additions & 163 deletions

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ read `docs/engineering/skills/pipeline_operations.md`.
2323
When adding, changing, or reviewing calibration target definitions, read
2424
`docs/engineering/skills/calibration_targets.md`.
2525

26+
When adding, changing, or reviewing donor-survey imputations, read
27+
`docs/engineering/skills/imputation.md`.
28+
2629
## Calibration targets
2730

2831
Manually sourced national or local-file calibration targets must be registered

changelog.d/1103.changed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use target-specific source-quality filters for ACS and SIPP imputations.

docs/engineering/skills/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Current skills:
1414
notes.
1515
- `github-prs.md`: same-repository PR workflow, PR head verification, and title
1616
conventions.
17+
- `imputation.md`: donor-survey imputation provenance rules, including
18+
target-level exclusion of allocated source values.
1719
- `pipeline_docs.md`: decorator-backed pipeline map maintenance and generated
1820
pydoc-style artifacts.
1921
- `pipeline_operations.md`: model-neutral workflow for diagnosing deployed Modal
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Imputation
2+
3+
Use this guide when adding, changing, or reviewing donor-survey imputations.
4+
5+
## Source Provenance
6+
7+
Do not train an imputation target on donor rows whose source value for that
8+
target is itself allocated, hot-decked, edited, or imputed by the source survey.
9+
Wire source-survey allocation or quality flags into the training frame whenever
10+
the donor file exposes them.
11+
12+
Apply this rule at the target-variable level, not the donor-row level. A donor
13+
row with observed tip income but allocated bank-account assets can train
14+
`tip_income`; the same row must be excluded from the `bank_account_assets`
15+
training target. Use `policyengine_us_data.utils.source_quality` to build
16+
target masks, then pass them to `microimpute` through `target_filters` or
17+
`row_filter` so the filtering logic lives in the imputation library rather than
18+
in one-off model wrappers.
19+
20+
Do not drop final CPS, ECPS, or calibration records solely because a donor
21+
survey target was excluded from training. The exclusion applies to donor
22+
training rows only; recipient datasets should remain complete.
23+
24+
When a donor source lacks target-level quality flags, document that limitation
25+
near the imputation code and keep the training surface structured so flags can
26+
be added later.
27+
28+
## Tests
29+
30+
Add focused regression tests when adding a donor imputation or a source-quality
31+
flag:
32+
33+
- allocation flags are read from the donor source,
34+
- allocated source values are excluded for the affected target,
35+
- unrelated observed targets from the same row can still train, and
36+
- legacy and current imputation surfaces use the same target provenance rule.

policyengine_us_data/calibration/puf_impute.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,29 @@ def _qrf_ss_shares(
325325
for sub in shares:
326326
shares[sub] = np.where(total > 0, shares[sub] / total, 0.0)
327327

328+
if (
329+
"age" in data
330+
and "social_security_retirement" in shares
331+
and "social_security_disability" in shares
332+
):
333+
# Preserve QRF survivor/dependent predictions, but anchor the
334+
# retirement-vs-disability split to the same age rule as the fallback.
335+
age = data["age"][time_period][:n_cps][puf_has_ss]
336+
is_old = age >= MINIMUM_RETIREMENT_AGE
337+
retirement_or_disability = (
338+
shares["social_security_retirement"] + shares["social_security_disability"]
339+
)
340+
shares["social_security_retirement"] = np.where(
341+
is_old,
342+
retirement_or_disability,
343+
0.0,
344+
)
345+
shares["social_security_disability"] = np.where(
346+
is_old,
347+
0.0,
348+
retirement_or_disability,
349+
)
350+
328351
del fitted, predictions
329352
gc.collect()
330353

0 commit comments

Comments
 (0)