From dcf30e91b2ddf4e074b2de2b2ffc413f4feef9cf Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 14 Feb 2025 14:46:18 +0100 Subject: [PATCH 1/5] Add child poverty impact extension Fixes #99 --- .../calculate_child_poverty_impacts.py | 38 +++++++++++++++++++ tests/country/test_child_poverty.py | 18 +++++++++ 2 files changed, 56 insertions(+) create mode 100644 policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py create mode 100644 tests/country/test_child_poverty.py diff --git a/policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py b/policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py new file mode 100644 index 00000000..fa73652e --- /dev/null +++ b/policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py @@ -0,0 +1,38 @@ +import pandas as pd +from policyengine import Simulation +from policyengine_core.simulations import Microsimulation + + +def calculate_child_poverty_impacts(simulation: Simulation, count_years: int = 10) -> pd.DataFrame: + """The change in mean child poverty under baseline vs reform. + Args: + simulation: A Simulation object containing baseline and reform scenarios. + is_child(bool): person level + in_poverty(bool): spm_unit level + Returns: + The change in mean child poverty under baseline vs reform. + """ + start_year = simulation.options.time_period + end_year = start_year + count_years + + years = [] + baseline_child_poverty = [] + reform_child_poverty = [] + child_poverty_change = [] + + for year in range(start_year, end_year): + baseline_cp = _get_child_povert_chnage(simulation.baseline_simulation, year).mean() + reform_cp = _get_child_povert_chnage(simulation.reform_simulation, year).mean() + years.append(year) + baseline_child_poverty.append(baseline_cp) + reform_child_poverty.append(reform_cp) + child_poverty_change.append(reform_cp - baseline_cp) + + return pd.DataFrame({"year": years, "baseline_child_poverty": baseline_child_poverty, "reform_child_poverty": reform_child_poverty, "child_poverty_change": child_poverty_change}) + + +def _get_child_povert_chnage(sim: Microsimulation, + year: int,): + is_child = sim.calculate("is_child", period=year) + in_poverty = sim.calculate("in_poverty", map_to="person", period=year) + return in_poverty[is_child].mean() diff --git a/tests/country/test_child_poverty.py b/tests/country/test_child_poverty.py new file mode 100644 index 00000000..16f01013 --- /dev/null +++ b/tests/country/test_child_poverty.py @@ -0,0 +1,18 @@ +def test_child_poverty(): + from policyengine import Simulation + + sim = Simulation( + country="us", + scope="macro", + reform={ + "gov.irs.credits.ctc.refundable.fully_refundable": True, + }, + ) + + child_poverty = sim.calculate_child_poverty_impacts(count_years=3) + + assert len(child_poverty) == 3 + + assert (child_poverty.child_poverty_change < 0).all() + assert child_poverty.reform_child_poverty > 0 + assert child_poverty.baseline_child_poverty > 0 From 5ff58f1086f7d40296c75238acbd36b5ca387bf2 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Sat, 15 Feb 2025 16:17:22 +0100 Subject: [PATCH 2/5] lint --- .../calculate_child_poverty_impacts.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py b/policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py index fa73652e..eb63940e 100644 --- a/policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py +++ b/policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py @@ -3,7 +3,9 @@ from policyengine_core.simulations import Microsimulation -def calculate_child_poverty_impacts(simulation: Simulation, count_years: int = 10) -> pd.DataFrame: +def calculate_child_poverty_impacts( + simulation: Simulation, count_years: int = 10 +) -> pd.DataFrame: """The change in mean child poverty under baseline vs reform. Args: simulation: A Simulation object containing baseline and reform scenarios. @@ -21,18 +23,31 @@ def calculate_child_poverty_impacts(simulation: Simulation, count_years: int = 1 child_poverty_change = [] for year in range(start_year, end_year): - baseline_cp = _get_child_povert_chnage(simulation.baseline_simulation, year).mean() - reform_cp = _get_child_povert_chnage(simulation.reform_simulation, year).mean() + baseline_cp = _get_child_povert_chnage( + simulation.baseline_simulation, year + ).mean() + reform_cp = _get_child_povert_chnage( + simulation.reform_simulation, year + ).mean() years.append(year) baseline_child_poverty.append(baseline_cp) reform_child_poverty.append(reform_cp) child_poverty_change.append(reform_cp - baseline_cp) - - return pd.DataFrame({"year": years, "baseline_child_poverty": baseline_child_poverty, "reform_child_poverty": reform_child_poverty, "child_poverty_change": child_poverty_change}) + return pd.DataFrame( + { + "year": years, + "baseline_child_poverty": baseline_child_poverty, + "reform_child_poverty": reform_child_poverty, + "child_poverty_change": child_poverty_change, + } + ) -def _get_child_povert_chnage(sim: Microsimulation, - year: int,): + +def _get_child_povert_chnage( + sim: Microsimulation, + year: int, +): is_child = sim.calculate("is_child", period=year) in_poverty = sim.calculate("in_poverty", map_to="person", period=year) return in_poverty[is_child].mean() From a6776ad08a36accc95a741b254a050dcea3881b4 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Sat, 15 Feb 2025 16:46:22 +0100 Subject: [PATCH 3/5] test fix --- tests/country/test_child_poverty.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/country/test_child_poverty.py b/tests/country/test_child_poverty.py index 16f01013..415bb13d 100644 --- a/tests/country/test_child_poverty.py +++ b/tests/country/test_child_poverty.py @@ -12,7 +12,6 @@ def test_child_poverty(): child_poverty = sim.calculate_child_poverty_impacts(count_years=3) assert len(child_poverty) == 3 - assert (child_poverty.child_poverty_change < 0).all() - assert child_poverty.reform_child_poverty > 0 - assert child_poverty.baseline_child_poverty > 0 + assert (child_poverty.reform_child_poverty > 0).all() + assert (child_poverty.baseline_child_poverty > 0).all() From 2c3ddab51287ec91693898e3743d0a1c636ee76b Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Sat, 15 Feb 2025 17:51:53 +0100 Subject: [PATCH 4/5] changelog and markdown --- changelog_entry.yaml | 4 ++++ docs/outputs/calculate_child_poverty_impacts.md | 7 +++++++ .../comparison/poverty}/calculate_child_poverty_impacts.py | 0 3 files changed, 11 insertions(+) create mode 100644 docs/outputs/calculate_child_poverty_impacts.md rename policyengine/outputs/{household/comparison => macro/comparison/poverty}/calculate_child_poverty_impacts.py (100%) diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29b..1ec30f1e 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Child poverty impact function. diff --git a/docs/outputs/calculate_child_poverty_impacts.md b/docs/outputs/calculate_child_poverty_impacts.md new file mode 100644 index 00000000..6fbcede5 --- /dev/null +++ b/docs/outputs/calculate_child_poverty_impacts.md @@ -0,0 +1,7 @@ +# Child poverty impacts + +This extension calculates the change in child poverty. + +```{eval-rst} +.. autofunction:: policyengine.outputs.macro.comparison.poverty.calculate_child_poverty_impacts.calculate_child_poverty_impacts +``` \ No newline at end of file diff --git a/policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py b/policyengine/outputs/macro/comparison/poverty/calculate_child_poverty_impacts.py similarity index 100% rename from policyengine/outputs/household/comparison/calculate_child_poverty_impacts.py rename to policyengine/outputs/macro/comparison/poverty/calculate_child_poverty_impacts.py From 9d394234f07f8436a04d8c577356f27a143e6622 Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Mon, 17 Feb 2025 13:23:43 +0000 Subject: [PATCH 5/5] Add changes to interface proposed after chat --- .../calculate_child_poverty_impacts.py | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/policyengine/outputs/macro/comparison/poverty/calculate_child_poverty_impacts.py b/policyengine/outputs/macro/comparison/poverty/calculate_child_poverty_impacts.py index eb63940e..290d0732 100644 --- a/policyengine/outputs/macro/comparison/poverty/calculate_child_poverty_impacts.py +++ b/policyengine/outputs/macro/comparison/poverty/calculate_child_poverty_impacts.py @@ -1,20 +1,50 @@ import pandas as pd -from policyengine import Simulation -from policyengine_core.simulations import Microsimulation +from policyengine import Simulation as PolicyEngine # Rename Simulation to PolicyEngine to avoid name conflict +from policyengine_core.simulations import Simulation +from pydantic import BaseModel +class ChildPovertyImpactParameters(BaseModel): # Ask extension developers to validate inputs to their functions + count_years: int + start_year: int + country: str + reform: dict def calculate_child_poverty_impacts( - simulation: Simulation, count_years: int = 10 + engine: PolicyEngine, + country: str, + reform: dict, + count_years: int = 10, + start_year: int = 2024, + ) -> pd.DataFrame: """The change in mean child poverty under baseline vs reform. Args: - simulation: A Simulation object containing baseline and reform scenarios. + engine: A PolicyEngine instance. is_child(bool): person level in_poverty(bool): spm_unit level Returns: The change in mean child poverty under baseline vs reform. """ - start_year = simulation.options.time_period + + ChildPovertyImpactParameters.model_validate( + count_years=count_years, + start_year=start_year, + ) + + baseline_simulation: Simulation = engine.build_simulation( + name="baseline", # Optionally, we could cache named simulations so we're not rerunning baseline each time. Or not! + country=country, + policy={}, + scope="macro", + ) + + reform_simulation: Simulation = engine.build_simulation( + name="reform", + country=country, + policy=reform, + scope="macro", + ) + end_year = start_year + count_years years = [] @@ -24,10 +54,10 @@ def calculate_child_poverty_impacts( for year in range(start_year, end_year): baseline_cp = _get_child_povert_chnage( - simulation.baseline_simulation, year + baseline_simulation, year ).mean() reform_cp = _get_child_povert_chnage( - simulation.reform_simulation, year + reform_simulation, year ).mean() years.append(year) baseline_child_poverty.append(baseline_cp)