Repository navigation
Description
State child SPM rates in Microcosm's US files differ from Census P60-290 Table 18 by 4.8 points on average on the default file, and by 3.19 on the ACS-local file that policyengine.py uses for state regions. We build these files to correct CPS errors, so we expect part of that difference and want to keep it. #1445 holds the trace, with the decomposition, scripts, state-level outputs and receipts. Nothing below adds a poverty target: state child rates stay out of the targets.
What the trace found
| Measure, 51 jurisdictions | Default, published | Dense, initial | Dense, published | ACS-local (10/17) |
|---|---|---|---|---|
| Mean gap | +3.24 | 0.00 | +3.62 | +0.89 |
| Mean absolute gap | 4.80 | 1.78 | 4.33 | 3.19 |
| Correlation with Census | .52 | .78 | .65 | .54 |
| Outside Census's margin | 38 | 16 | 38 | 29 |
| Median state standard error | 4.04 | 1.07 | 1.86 | 2.98 |
| State-specific standard deviation τ | 2.73 | 1.50 | 3.07 | 2.04 |
| τ, allowing for shared households | 2.90 | 1.88 | 3.20 | not run |
- On the dense pool, calibration creates most of the systematic shift, and most of the added state variation lands on ASEC rows. Dense initial weights carry no net shift. Published weights add +3.62, and state-specific variation rises about 1.7 times once we allow for households the pool shares with Table 18. A test with no shared households keeps the direction: dense 2022 records against Census's 2024 to 2025 average go from a mean absolute gap of 2.47 to 6.08. Shared households still pull the initial-weight columns toward Census.
- This result stops at the dense pool. On the default file, record selection puts the shift in place before calibration starts, and the ACS-local file stores no initial weights. The dense zero nets +2.20 on ASEC rows against -2.20 on PUF rows, and initial weights miss the administrative targets, so this shows where the shift enters and does not recommend skipping calibration.
- Noise accounts for most of the default file's state scatter. Three fits put it at 60%, 71% and 80%, and the first carries a 95% interval of about 10% to 89%.
- Some state variation predates calibration, and we cannot yet pin it on particular states. State gaps exceed sampling noise at dense initial weights. Louisiana has the largest joint gap with ACS rows, but a gap that large on both turns up by chance about one time in five (p = .19).
- The files we checked carry rent on the wrong households. Of records that should carry rent, 89% do not hold their own. Place each ACS rent draw on its own household's head #1444 fixed the code, and no published file carries the fix yet. At published weights, the fix nearly doubles modelled housing subsidies and barely moves the state gaps.
- On the ACS-local file, 23,407 donor households hold 68% of child weight. ACS rows alone reach a mean absolute gap of 2.46 with a median standard error of 1.06, and still leave 22 jurisdictions outside the margin.
Next steps, in order
We want Microcosm's files to differ from Census where the CPS errs, so a smaller gap alone does not make a better file, and we judge each step by what it corrects. We order the steps by judgement: prerequisites first, then the change measured on the state path, then changes measured elsewhere or not at all. The measured effects come from different files, so they cannot rank the steps, and aging reaches as far on the default file as the ACS share does on ACS-local.
Aging and better benefit receipt should lower rates, and neither has a test on the ACS-local path. That path already reads +0.89, and -0.45 at share 0.9, or -0.78 with corrected rent, so measure steps 6 and 7 there before adopting a size from the default file.
1. Rebuild with the rent fix
#1444 fixed the assignment at 9434c50. Rebuild from frames that carry no rent: with_us_housing_inputs returns a frame unchanged when its signal gate passes, and the gate passes rent that moved from one renter household's head to another's, so an existing base can keep it. The rebuild itself needs a release decision. Corrected-rent runs in #1445 hold published weights fixed, so calibration can move results further.
The 10/17 ACS-local file leaves donors' parent links blank, and #1445 fills them with 0, which policyengine-us reads as unknown. The current build writes each ASEC person's co-resident parent's id, or 0 when the ASEC names none, and remaps those ids onto each PUF clone (#884). A -1 fill instead moves California's rate 0.56 points and South Dakota's reform effect 1.33, each through one ASEC unit, so real links can move state figures too.
Done when the rebuilt base has zero positive_rent_nonrenter, the rebuilt ACS-local file stores parent ids rather than blanks on donors, and scripts/ in the evidence folder reproduces its tables on the new file.
2. Fail the build on rent outside renter households
#1529 landed this at 3f7cfc1. us_housing_inputs_signal_gate now fails on positive pre_subsidy_rent outside renter households, and impute_us_pre_subsidy_rent sets the CPS draw to zero there, so the draw leaves no such rent at any seed.
3. Zero owners' rent after the ACS transfer
build_optional_acs_multispine passes the base to transfer_acs_inputs as its donor, and pre_subsidy_rent sits among the housing transfer targets with head and tenure as mandatory predictors. Published ACS rows learned from misplaced rent: 39.6% of ACS renters carry rent, and so do 30% of ACS owners. Step 1 should restore renters' link with no new code.
Owners need new code whether or not step 1 restores the link. transfer_acs_inputs fits its own QRF on the donor and draws with the same zero-or-positive classifier, so a rebuilt donor can still leave rent on ACS owners, and any such rent now stops a fiscal refresh built from --pool-manifest at the housing gate. Set it to zero in _apply_post_transfer_structure, and declare the step under post_transfer_structure in acs_transfer_execution_contract_identity, which moves the contract identity.
The same gate requires household tenure to carry the three ASEC categories and no others. acs_inputs.py maps ACS tenure code 2 to OWNED_OUTRIGHT, so ACS rows that keep that value already fail it on main, and this step needs to settle that mapping too.
acs_transfer.py keeps RNTP out of this path on purpose, with the comment "RNTP/GRNTP are not pre-subsidy rent", so any native-rent route has to handle subsidised tenants.
Done when ACS renters carry rent at the donor rate, ACS owners carry none, and a refresh from --pool-manifest passes the housing gate.
4. Raise the ACS share of state mass
acs_share in packages/microcosm-build/src/microcosm/build/us_runtime/acs_multispine.py defaults to 0.5. The solves from #1078, evaluated on all 51 states:
| Solve | Mean absolute gap | Correlation | Outside margin | Median state SE | Median reform SE |
|---|---|---|---|---|---|
| Share 0.5, release-like | 2.79 | .56 | 27 | 2.82 | 1.30 |
| Share 0.7, λ = .03 | 2.40 | .64 | 23 | 1.86 | 0.88 |
| Share 0.9, λ = .03 | 2.16 | .75 | 22 | 1.09 | 0.56 |
| Share 0.964, λ = .03 | 2.14 | .77 | 22 | 0.87 | 0.47 |
With corrected rent and these weights, shares 0.5 and 0.9 read 2.88 and 2.23, with 26 and 23 outside the margin.
Standard errors fall and gaps remain: at share 0.9, DC reads 24.47 against Census's 20.3. #1078 reports 36% and 43% more held-out target error on its two folds at share 0.9 with no penalty, mostly SOI, so the default needs a methodology decision. Run the sweep on the rebuilt base, including the λ = .03 rows' held-out error, and report SOI fit, child state Kish and reform standard error together.
Done when a table with those columns sits beside the recommendation, and the default changes only on that decision.
5. Test a calibration anchor on the dense pool
Dense initial weights carry a mean absolute gap of 1.78, against 4.21 on the default file, whose selected records also double the noise (median standard error 2.20 against 1.07). A chi-square anchor to default initial weights lowered noise and overshot on level: from the same-pipeline re-solve (mean gap 2.93, correlation .42), the mean gap reached 5.18 at λ = .1, above the 3.27 at initial weights, while correlation rose to .68. We do not know why it overshoots, so find that first. No one has tested an anchor on the dense pool, or measured ACS-local at its initial weights, which the published snapshot does not store.
Done when a dense solve with a chi-square anchor reports state gaps, τ and target fit against the published dense solve, the overshoot has an explanation, and the ACS-local build stores its initial weights.
6. Close benefit receipt before calibration
On the dense file, summed family cells start far short: SNAP 37.8%, Medicaid 26.2%, EITC 50.8%, ACA 43.9%, against AGI at 4.2%. Census's own flag rises from 13.03 to 16.31 as calibration closes them. The rent fix does not close SNAP's shortfall: it lifts SNAP at initial weights from $57.58bn to $58.86bn. Better take-up and receipt inputs should reduce the tilt, though no one has tested that, and each input needs its own issue with a before-and-after on this table. Run the ablations on the dense pool too, since those in #1445 ran on Build P.
Done when the Census-flag rate at final weights moves toward its value at initial weights while target fit holds.
7. Age pooled incomes
#944 tracks this. A single-factor run on 2022 and 2023 ASEC records lowers the default file's mean gap from 3.24 to 2.45 and its mean absolute gap from 4.80 to 4.05. A real fix ages each income source, and should test a 2023 to 2025 pool, which matches Table 18's window.
Done when aged records enter the build and dense_steps.csv shows the thresholds step falling.
8. Close the gap between ASEC and PUF rows
At dense initial weights, ASEC rows read 16.38 and PUF rows 11.34, and the pool's zero depends on that offset.
Start with cash income. At published weights, moving to PolicyEngine's cash income adds 0.75 points on ASEC rows and removes 2.06 on PUF rows, while housing adds 1.65 and 1.16 (dense_shapley9_asec.csv, dense_shapley9_puf_tax_detail.csv).
Two known fixes should widen the gap, so make them for their own reasons:
impute_us_housing_assistance_to_puf_supportreplaces each PUF-half person's receipt with a QRF draw. Unweighted, 2.05% of PUF-half units receive assistance against 3.73% on the ASEC half, and 9.6% of ASEC recipients keep the flag on their clone. Carrying the ASEC flag to its clone fixes that receipt and adds subsidy to PUF rows only.- Partnership and S-corp income stays out of SPM resources (SPM resources leave out partnership/S-corp income (and other gross-income sources), while SPM taxes include the tax on them policyengine-us#9919; Count the same cash payments in household and SPM income policyengine-us#10573 partly addresses it). Adding it takes PUF rows from 13.63 to 11.27 and leaves ASEC rows at 20.52 in 2024, at published weights, by arithmetic on simulation output.
Done when the two channels' child rates differ by no more than their sampling error, or the difference has a documented source.
9. Reconcile 2025 resources near the line
PolicyEngine's child rate rises 1.48 points from 2024 to 2025 while Census's holds at 13.4. Between the 10th and 30th percentiles, child SPM resources grew 3.6% to 5.7% in Census's data, against 1.6% to 2.8% on the default file and 2.5% to 3.2% on dense. Compare growth by income source, starting with the uprating series for low earners.
Done when a table of child resource growth by source, for PolicyEngine and Census, names the sources behind the difference.
10. Check the state gaps that persist
Rank states by the smaller of their two z-scores at dense initial weights and on ACS rows, counting only gaps in the same direction: Louisiana 2.80, Delaware 1.91, Ohio 1.40, Illinois 1.39, Mississippi 1.38 and Washington 1.31.
These stay leads. Chance alone gives 0.7 states past 1.96 (we see one, p = .53), 1.4 past 1.645 (two, p = .41) and 3.0 past 1.28 (six, p = .05). Both gaps also subtract the same Census estimate, which accounts for about .16 of the observed .30 correlation.
Start with Louisiana and Delaware. Check state tax and benefit rules and Census's state estimates. Any rule bug belongs in the country model and the matching rulespec repository.
Done when each named state has a documented cause, or a finding that no cause beyond noise holds up.
11. Report state child rates as a standing holdout
Add the 51-state comparison, with standard errors and Census's margin, to each US release's receipts. Poverty stays a comparison and never a target.
Done when a release receipt carries the table, and CI checks that it exists rather than what it says.
Related
- Diagnose the child-vs-total SPM composition anomaly (poverty stays a permanent holdout) #646 covers the child-versus-total composition anomaly.
- Move the backend onto policyengine.py 6.11.1 and its state dataset child-poverty-impact-dashboard#127 moves the dashboard to the 10/17 file. Its files should carry the rent bug until step 1 ships. On published weights and stand-in ACS rent, the fix moves a state's 2021 CTC effect by a median of 0.29 points across all 51 states and by more than a point in nine. Florida moves furthest, from -3.53 to -6.27, and in each of the nine, five source households hold most of the change. At the same model, Build P's baselines sit closer to Census than Build O's (mean absolute gap 2.78 against 3.13).
- Model version moves state reform effects further than the file does: the same 10/17 file at policyengine-us 1.825.2 and 2.30.1 differs by 1.11 points on a state's 2021 CTC effect on average, and Arizona goes from -11.92 to -6.37. Trace how calibration, sampling noise and rent imputation move state child SPM poverty rates #1445 does not explain that movement.
Activity
MaxGhenis commentedon Nov 2, 2026
The ACS-local figures in this issue come from runs that filled blank parent links with -1. PolicyEngine-US reads 0 as unknown, so those figures carry a small error, largest in California (19.81 against 20.37). #1445 has the details and a pending rerun. Don't cite this issue's ACS-local figures until that rerun lands; I'll update them here then.
Say which misplaced rent the housing gate catches
MaxGhenis commentedon Nov 6, 2026
#1529 covers step 2: the housing gate now fails on positive rent outside renter households. It also sets the CPS rent draw to zero there, because the shared QRF's zero-or-positive classifier gives every owner and no-cash-rent head a small chance of positive rent. On the Route A pre-clone frame, seeds 1 to 7 each put rent on one to four such heads, and seed 0 on none.
For step 3, zero owners' rent after the transfer whether or not the rebuild restores the link.
transfer_acs_inputsfits its own QRF on the donor and drawspre_subsidy_rentfor ACS rows with the same zero-or-positive classifier, so a rebuilt donor can still leave rent on ACS owners. Once #1529 lands, any such rent stops a fiscal refresh built from--pool-manifestat the housing gate. The same gate requires household tenure to carry the three ASEC categories and no others.acs_inputs.pymaps ACS tenure code 2 toOWNED_OUTRIGHT, so ACS rows that keep that value already fail it on main. A refresh that reads its base through--base-h5checks only that base, which holds no ACS rows.MaxGhenis commentedon Nov 6, 2026
#1531 adds Census state age targets to the ACS-local calibration, and we project that they leave the concentration on donor households about where it stands. We balanced each state slice to Census's 15 age groups while holding 16 of the slice's own aggregates, and simulated the results under policyengine-us 1.825.2. We ran the production file (Build P,
592ae5d6) and the October 17 candidate (Build O,766b7c7). These runs change onlyhousehold_weightin a copy of each slice, so the parent-link fill above doesn't affect them.Donor households make up 3.6% of each file's households and account for 73.6% of Build P's lift and 78.4% of Build O's. For example, we estimate the $2,000 lifts 12,417 Indiana children on Build P, with one household accounting for 81.8% of them, and 2,356 on Build O, where the top household accounts for 21.1%. On Build P, the balance's rise from 21 states to 24 comes from states lifting fewer than 1,000 children. Among the 36 states where Build P lifts at least 1,000 children, Build O's lift falls below half of Build P's or exceeds double in 6. The balance moves Build P's by more than a fifth in 10 of them.
For step 4, I'd add the top household's share of a small reform's state lift beside child state Kish and reform standard error, with a floor on the lift. The District of Columbia's share reads 100% on Build P with a net lift under 0.001 children.
_lift_statsincensus_age_reweight_modal.pycomputes it, and the by-state files for Build P and Build O hold these runs' values. PolicyEngine/child-poverty-impact-dashboard#129 has the analysis, and PolicyEngine/child-poverty-impact-dashboard#130 covers the dashboard's side.🤖 Generated with Claude Code
[-]State child SPM rates miss Census: next steps from the November diagnosis[/-][+]State child SPM rates: next steps from the November trace[/+]Merge main (3db6515) into store-years-since-us-entry