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/macro/comparison/poverty/calculate_child_poverty_impacts.py b/policyengine/outputs/macro/comparison/poverty/calculate_child_poverty_impacts.py new file mode 100644 index 00000000..290d0732 --- /dev/null +++ b/policyengine/outputs/macro/comparison/poverty/calculate_child_poverty_impacts.py @@ -0,0 +1,83 @@ +import pandas as pd +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( + 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: + 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. + """ + + 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 = [] + baseline_child_poverty = [] + reform_child_poverty = [] + child_poverty_change = [] + + for year in range(start_year, end_year): + baseline_cp = _get_child_povert_chnage( + baseline_simulation, year + ).mean() + reform_cp = _get_child_povert_chnage( + 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..415bb13d --- /dev/null +++ b/tests/country/test_child_poverty.py @@ -0,0 +1,17 @@ +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).all() + assert (child_poverty.baseline_child_poverty > 0).all()