Repository navigation
Conversation
…alues subsample exported to_input_dataframe(include_computed_variables=True), every stored value, and loaded it back as inputs of the rebuilt population. The baseline arm of a reform simulation is rebuilt from that population, so anything calculated under the reform before subsampling became an input of the baseline arm too: after calculating taxable_income on a policyengine-us reform simulation, the baseline arm read the reform's taxable income. subsample now rebuilds from _to_subsample_dataframe: for each variable, the periods Holder.get_input_periods reports as inputs (the storage's own input/derived mark, so a stale _user_input_keys entry cannot pass a calculated value off as an input), including inputs of formula-backed variables such as policyengine-us's dataset-supplied person_id and is_spm_independent_minor_role. A formula-backed id, membership or role variable that has no input is calculated at the dataset period, as the rebuild needs it. Tests: examples and a Hypothesis property (history independence, baseline arm equals an unreformed subsample, restriction, export depends only on inputs). Also carries the tests from #573, which this supersedes, and which pass here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 7, 2026
Contributor
Author
|
US + core hub: closing as superseded.
Nothing further is needed from this branch. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Simulation.subsamplerebuilt the population fromto_input_dataframe(include_computed_variables=True): every value the simulation had stored, calculated ones included, reloaded as inputs. It then rebuilds a reform simulation's baseline arm withself.get_branch("baseline")from that population. Anything calculated under the reform before subsampling therefore became an input of the baseline arm too, and the baseline read the reform's values.Observed on policyengine-us main (two earners at $50,000 and $80,000,
spm={"geography_kind": "national"}, reform:gov.irs.deductions.standard.amount.SINGLE= 0 for 2024,taxable_incomecalculated for 2024, thensubsample(2, seed=0)):taxable_income2024The rule
subsamplenow rebuilds from_to_subsample_dataframe():Holder.get_input_periods(self.branch_name)reports. That is the storage's input/derived mark, whichput_in_cache(..., derived=True)sets for every calculated, carried, uprated or default value.person_id(formulanp.arange(len(age))) andis_spm_independent_minor_role(DATASET_SOURCE_INPUTSinspm.py). Both are the reason 56c8b82 switched to the full export._user_input_keys. That record can outlive the value it names. policyengine-ussystem.pymovesemployment_incomeintoemployment_income_before_lsrwithHolder.delete_arraysand leaves theemployment_incomekeys behind. A calculatedemployment_income(which includes the labour-supply response) at such a key would otherwise pass as an input.build_from_datasetreads to place people in entities: the person id, each group's id, and each person's membership id and role in it. If one has a formula but no input, it is calculated at the dataset period and kept. Before this PR it was exported only if something had calculated it earlier.to_input_dataframeand itsinclude_computed_variablesflag are unchanged.Invariants
Stated for every population, reform (none, parametric, structural formula, changed default, added variable), set of calculations on either arm, sample size and seed. Hypothesis tests in
tests/core/test_subsample_computed_values_property.py, 200 examples plus 5 explicit ones:_to_subsample_dataframe()unchanged.From #573 (
tests/core/test_subsample_inputs_only_property.py, carried here unchanged): whatever was calculated before subsampling, with auto-carry-over on or off, the subsampled simulation stores exactly what one subsampled straight after loading stores, and every later request agrees.Tests
test_subsample_computed_values.pyincome_tax,disposable_income) calculated before subsample; a default the reform changes; a value calculated where an input was deleted (stale record); a dataset value for a formula-backed variable is kept; a formula-backedhousehold_idthe dataset lacks, calculated first or nottest_subsample_computed_values_property.pytest_subsample_inputs_only.py,..._property.py,fixtures/subsample_inputs.pyExisting subsample tests (formula-backed IDs, fast cache, baseline branch) pass.
Relation to other PRs
household_idcase, both with and without calculating it first. Rebuild a subsampled simulation from its inputs, not its calculated values #573 reads_user_input_keysand has no structural fallback; it would rely on Keep the record of set_input values in step with storage #561 for the stale record._user_input_keysin step with storage. It is still needed for_invalidate_all_caches, which this PR leaves unchanged. On policyengine-us synthetic data,_invalidate_all_cachesturns a calculatedemployment_incomeat a stale key into an input, and it then ignores a newemployment_income_before_lsr. This PR doesn't read the record, so it is correct with or without Keep the record of set_input values in step with storage #561.Impact
subsamplecalled straight after construction samples the same households with the same inputs and weights as before.gh search code) and read each call site. No caller ofSimulation.subsampleon a default branch calculates anything before subsampling:build-parameter-dependency-map.py, axiom-oraclespopulace_us.py, policyengine-us docs and microsimulation tests, and core's docs and tests all subsample straight after construction.subsample. On policyengine-uk main 9dd771a05,Microsimulation(...).subsample(2)raisesTypeError: Simulation.build_from_dataset() missing 1 required positional argument: 'dataset'. Core'ssubsamplecallsself.build_from_dataset()with no argument (on master and here), UK overrides it asbuild_from_dataset(self, dataset), and the UK baseline is a separateSimulation, not a branch. This PR changes nothing there.person_weight, later years'household_weight) is recalculated from the rescaled inputs, as it would be after subsampling a fresh simulation.Real data. I used a 500-household, 1,421-person slice of the default policyengine-us dataset (
default_population_sample(500)frompolicyengine_us/tests/microsimulation/populace_fixture.py), on policyengine-us main 19c240d0ff.income_taxandhousehold_net_incomewere calculated for 2026, thensubsample(200, seed=0)ran. "Master" means this branch withsubsample's export restored to master's, the only behavioural change. The script is below.subsample(200), 2026income_tax, weighted sumhousehold_net_income, weighted sumThe true impact is the reform subsample minus an unreformed subsample with the same seed.
income_tax) and $9,765.94 (household_net_income).Script (run from a policyengine-us checkout with this core; argument
fixormaster)Checks run
ruff format --check .andruff check ..-n 2).test_spm_system.py -k subsampl(test_subsampling_records_the_counties_each_arm_then_reads), 1 passed.Documentation review
subsample's docstring now states what the rebuild keeps;docs/python_apiis generated from docstrings by autodoc. No other docs describe the export. Impact: medium (public method's behaviour after calculations). Confidence: high.axiom: n/a: core engine, no policy encoding
🤖 Generated with Claude Code