Skip to content

Commit

Permalink
Merge pull request #17 from masterismail/povertyImpact
Browse files Browse the repository at this point in the history
Added Regular Poverty Impact
  • Loading branch information
nikhilwoodruff authored Jul 9, 2024
2 parents 845dc4a + be7d3e7 commit fbe6efd
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 0 deletions.
54 changes: 54 additions & 0 deletions policyengine/economic_impact/base_metric_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from policyengine_core.reforms import Reform
from typing import Dict, Union

class BaseMetricCalculator:
"""
Base class for calculating metrics based on baseline and reformed data.
Attributes:
baseline (object): Object representing baseline data.
reformed (object): Object representing reformed data.
"""
def __init__(self, baseline: object, reformed: object, default_period: int = 2024) -> None:
"""
Initialize with baseline and reformed data objects.
Args:
baseline (object): Object representing baseline data.
reformed (object): Object representing reformed data.
"""
self.baseline = baseline
self.reformed = reformed
self.default_period = default_period
self.baseline.default_calculation_period = default_period
self.reformed.default_calculation_period = default_period

def calculate_baseline(self, variable: str, period: int = None) -> Union[float, int]:
"""
Calculate baseline metric value.
Args:
variable (str): Variable to calculate.
period (int): Year or period for calculation (default: 2024).
Returns:
Union[float, int]: Calculated metric value.
"""
if period is None:
period = self.default_period
return self.baseline.calculate(variable, period=period)

def calculate_reformed(self, variable: str, period: int = None) -> Union[float, int]:
"""
Calculate reformed metric value.
Args:
variable (str): Variable to calculate.
period (int): Year or period for calculation (default: 2024).
Returns:
Union[float, int]: Calculated metric value.
"""
if period is None:
period = self.default_period
return self.reformed.calculate(variable, period=period)
18 changes: 18 additions & 0 deletions policyengine/economic_impact/economic_impact.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
from policyengine_core.reforms import Reform
from .inequality_impact.inequality_impact import GiniCalculator, Top10PctShareCalculator, Top1PctShareCalculator
from .poverty_impact.regular_poverty.by_age.by_age import (
ChildPoverty as RegularChildPoverty,
AdultPoverty as RegularAdultPoverty,
SeniorPoverty as RegularSeniorPoverty,
AllPoverty as RegularAgeAllPoverty
)
from .poverty_impact.regular_poverty.by_gender.by_gender import (
MalePoverty as RegularMalePoverty,
FemalePoverty as RegularFemalePoverty,
AllPoverty as RegularGenderAllPoverty
)
from typing import Dict

class EconomicImpact:
Expand Down Expand Up @@ -36,6 +47,13 @@ def __init__(self, reform: dict, country: str) -> None:
"inequality/gini": GiniCalculator(self.baseline, self.reformed),
"inequality/top_1_pct_share": Top1PctShareCalculator(self.baseline, self.reformed),
"inequality/top_10_pct_share": Top10PctShareCalculator(self.baseline, self.reformed),
"poverty/regular/child": RegularChildPoverty(self.baseline, self.reformed),
"poverty/regular/adult": RegularAdultPoverty(self.baseline, self.reformed),
"poverty/regular/senior": RegularSeniorPoverty(self.baseline, self.reformed),
"poverty/regular/age/all": RegularAgeAllPoverty(self.baseline, self.reformed),
"poverty/regular/male": RegularMalePoverty(self.baseline, self.reformed),
"poverty/regular/female": RegularFemalePoverty(self.baseline, self.reformed),
"poverty/regular/gender/all": RegularGenderAllPoverty(self.baseline, self.reformed),
}

def _get_simulation_class(self) -> type:
Expand Down
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator
from policyengine_uk import Microsimulation

class ChildPoverty(BaseMetricCalculator):
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
super().__init__(baseline, reformed, default_period)
self.baseline = baseline
self.reformed = reformed

def calculate(self):
age = self.baseline.calculate("age")

baseline_poverty = self.baseline.calculate("in_poverty", map_to="person")
reform_poverty = self.reformed.calculate("in_poverty", map_to="person")

baseline = float(baseline_poverty[age < 18].mean())
reform = float(reform_poverty[age < 18].mean())
change = ((reform - baseline) / baseline) * 100

return {
"baseline": round(baseline,2),
"reform": round(reform,2),
"change": round(change,1)
}

class AdultPoverty(BaseMetricCalculator):
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
super().__init__(baseline, reformed, default_period)
self.baseline = baseline
self.reformed = reformed

def calculate(self):
age = self.baseline.calculate("age")

baseline_poverty = self.baseline.calculate("in_poverty", map_to="person")
reform_poverty = self.reformed.calculate("in_poverty", map_to="person")

baseline = float(baseline_poverty[(age >= 18) & (age < 65)].mean())
reform = float(reform_poverty[(age >= 18) & (age < 65)].mean())
change = ((reform - baseline) / baseline) * 100

return {
"baseline": round(baseline,2),
"reform": round(reform,2),
"change": round(change,1)
}

class SeniorPoverty(BaseMetricCalculator):
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
super().__init__(baseline, reformed, default_period)
self.baseline = baseline
self.reformed = reformed

def calculate(self):
age = self.baseline.calculate("age")

baseline_poverty = self.baseline.calculate("in_poverty", map_to="person")
reform_poverty = self.reformed.calculate("in_poverty", map_to="person")

baseline = float(baseline_poverty[age >= 65].mean())
reform = float(reform_poverty[age >= 65].mean())
change = ((reform - baseline) / baseline) * 100

return {
"baseline": round(baseline,2),
"reform": round(reform,2),
"change": round(change,1)
}

class AllPoverty(BaseMetricCalculator):
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
super().__init__(baseline, reformed, default_period)
self.baseline = baseline
self.reformed = reformed

def calculate(self):

baseline_poverty = self.baseline.calculate("in_poverty", map_to="person")
reform_poverty = self.reformed.calculate("in_poverty", map_to="person")

baseline = float(baseline_poverty.mean())
reform = float(reform_poverty.mean())
change = ((reform - baseline) / baseline) * 100

return {
"baseline": round(baseline,2),
"reform": round(reform,2),
"change": round(change,1)
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from policyengine.economic_impact.inequality_impact.inequality_impact import BaseMetricCalculator
from policyengine_uk import Microsimulation

class MalePoverty(BaseMetricCalculator):
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
super().__init__(baseline, reformed, default_period)
self.baseline = baseline
self.reformed = reformed

def calculate(self):
is_male = self.baseline.calculate("is_male")

baseline_poverty = self.baseline.calculate("in_poverty", map_to="person")
reform_poverty = self.reformed.calculate("in_poverty", map_to="person")

baseline = float(baseline_poverty[is_male].mean())
reform = float(reform_poverty[is_male].mean())
change = ((reform - baseline) / baseline) * 100

return {
"baseline": round(baseline,2),
"reform": round(reform,2),
"change": round(change,1)
}

class FemalePoverty(BaseMetricCalculator):
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
super().__init__(baseline, reformed, default_period)
self.baseline = baseline
self.reformed = reformed

def calculate(self):
is_male = self.baseline.calculate("is_male")

baseline_poverty = self.baseline.calculate("in_poverty", map_to="person")
reform_poverty = self.reformed.calculate("in_poverty", map_to="person")

baseline = float(baseline_poverty[~is_male].mean())
reform = float(reform_poverty[~is_male].mean())
change = ((reform - baseline) / baseline) * 100

return {
"baseline": round(baseline,2),
"reform": round(reform,2),
"change": round(change,1)
}

class AllPoverty(BaseMetricCalculator):
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
super().__init__(baseline, reformed, default_period)
self.baseline = baseline
self.reformed = reformed

def calculate(self):

baseline_poverty = self.baseline.calculate("in_poverty", map_to="person")
reform_poverty = self.reformed.calculate("in_poverty", map_to="person")

baseline = float(baseline_poverty.mean())
reform = float(reform_poverty.mean())
change = ((reform - baseline) / baseline) * 100

return {
"baseline": round(baseline,2),
"reform": round(reform,2),
"change": round(change,1)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Regular poverty by age
- test_child_poverty:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
baseline: 0.32
reform: 0.36
change: 10.1

- test_adult_poverty:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
baseline: 0.17
reform: 0.19
change: 8.4

- test_senior_poverty:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
baseline: 0.13
reform: 0.17
change: 30.5

- test_all_poverty:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
baseline: 0.2
reform: 0.22
change: 11.7
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest
import yaml
import os
from policyengine import EconomicImpact

def assert_dict_approx_equal(actual, expected, tolerance=1e-4):
for key in expected:
assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}"


yaml_file_path = "policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/by_age.yaml"

# Check if the file exists
if not os.path.exists(yaml_file_path):
raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}")

with open(yaml_file_path, 'r') as file:
test_cases = yaml.safe_load(file)

@pytest.mark.parametrize("test_case", test_cases)
def test_economic_impact(test_case):
test_name = list(test_case.keys())[0]
test_data = test_case[test_name]

economic_impact = EconomicImpact(test_data['reform'], test_data['country'])

if 'child' in test_name:
result = economic_impact.calculate("poverty/regular/child")
elif 'adult' in test_name:
result = economic_impact.calculate("poverty/regular/adult")
elif 'senior' in test_name:
result = economic_impact.calculate("poverty/regular/senior")
elif 'all' in test_name:
result = economic_impact.calculate("poverty/regular/age/all")
else:
pytest.fail(f"Unknown test case: {test_name}")

assert_dict_approx_equal(result, test_data['expected'])

if __name__ == "__main__":
pytest.main([__file__])
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Regular poverty by age
- male_poverty_test:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
baseline: 0.18
reform: 0.21
change: 12.9

- female_poverty_test:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
baseline: 0.21
reform: 0.23
change: 10.7

- all_poverty_test:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
baseline: 0.2
reform: 0.22
change: 11.7
Loading

0 comments on commit fbe6efd

Please sign in to comment.