Skip to content

Commit

Permalink
Merge pull request #23 from masterismail/DistributionalImpact
Browse files Browse the repository at this point in the history
[1] - added Distributional impact by income
  • Loading branch information
nikhilwoodruff authored Aug 13, 2024
2 parents d5021d3 + a119b78 commit 9f404ec
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 1 deletion.
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator
from policyengine_uk import Microsimulation
from microdf import MicroDataFrame, MicroSeries


class Average(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_income = MicroSeries(self.baseline.calculate("household_net_income"), weights = self.baseline.calculate("household_weight"))
reform_income = MicroSeries(self.reformed.calculate("household_net_income") , weights = baseline_income.weights)

decile = self.baseline.calculate("household_income_decile")
income_change = reform_income - baseline_income


avg_income_change_by_decile = (
income_change.groupby(decile).sum()
/ baseline_income.groupby(decile).count()
)


avg_decile_dict = avg_income_change_by_decile.to_dict()
result = dict(

average={int(k): v for k, v in avg_decile_dict.items() if k > 0},
)


return {
"average": result["average"],

}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator
from policyengine_uk import Microsimulation
from microdf import MicroDataFrame, MicroSeries


class Relative(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_income = MicroSeries(self.baseline.calculate("household_net_income"), weights = self.baseline.calculate("household_weight"))
reform_income = MicroSeries(self.reformed.calculate("household_net_income") , weights = baseline_income.weights)

decile = self.baseline.calculate("household_income_decile")
income_change = reform_income - baseline_income


rel_income_change_by_decile = (
income_change.groupby(decile).sum()
/ baseline_income.groupby(decile).sum()
)


rel_decile_dict = rel_income_change_by_decile.to_dict()

result = dict(
relative={int(k): v for k, v in rel_decile_dict.items() if k > 0}

)


return {
"relative": result["relative"],

}
6 changes: 5 additions & 1 deletion policyengine/economic_impact/economic_impact.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
PensionCredit
)

from .distributional_impact.by_income_decile.average.average import Average
from .distributional_impact.by_income_decile.relative.relative import Relative

from .budgetary_impact.overall.overall import (
BudgetaryImpact,
BenefitSpendingImpact,
Expand Down Expand Up @@ -108,9 +111,10 @@ def __init__(self, reform: dict, country: str) -> None:
"poverty/deep/male": DeepMalePoverty(self.baseline, self.reformed),
"poverty/deep/female": DeepFemalePoverty(self.baseline, self.reformed),
"poverty/deep/gender/all": DeepGenderAllPoverty(self.baseline, self.reformed),
"distributional/by_income/average": Average(self.baseline, self.reformed),
"distributional/by_income/relative": Relative(self.baseline, self.reformed),
"winners_and_losers/by_income_decile": ByIncomeDecile(self.baseline, self.reformed),
"winners_and_losers/by_wealth_decile": ByWealthDecile(self.baseline, self.reformed),

}

def _get_simulation_class(self) -> type:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Regular poverty by age
- test_by_income_average:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
average:
1: -288.4419422028393
2: -936.7849231542535
3: -1655.8066942572789
4: -3040.3849413280304
5: -5082.727697456715
6: -8483.137124053015
7: -11510.447500088267
8: -14567.684605663524
9: -17960.33204429504
10: -22070.767989875396
11: -13195.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pytest
import yaml
import os
from policyengine import EconomicImpact

def assert_dict_approx_equal(actual, expected, tolerance=1e-4):
assert set(actual.keys()) == set(expected.keys()), f"Keys don't match. Actual: {actual.keys()}, Expected: {expected.keys()}"
for key in expected:
if isinstance(expected[key], dict):
assert_dict_approx_equal(actual[key], expected[key], tolerance)
else:
assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}"

yaml_file_path = "policyengine/tests/economic_impact/distributional_impact/by_income/average/average.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 'average' in test_name:
result = economic_impact.calculate("distributional/by_income/average")
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,19 @@
# Regular poverty by age
- test_by_income_average:
reform:
gov.hmrc.income_tax.rates.uk[0].rate:
"2024-01-01.2100-12-31": 0.55
country: uk
expected:
relative:
1: -0.026265868982450032
2: -0.05321454070727573
3: -0.07293061898874185
4: -0.1046825029478255
5: -0.1407466605531786
6: -0.19129333726780962
7: -0.2185085314786923
8: -0.22920465056330844
9: -0.21991139800089637
10: -0.12790076021888797
11: -0.007019584080998393
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pytest
import yaml
import os
from policyengine import EconomicImpact

def assert_dict_approx_equal(actual, expected, tolerance=1e-4):
assert set(actual.keys()) == set(expected.keys()), f"Keys don't match. Actual: {actual.keys()}, Expected: {expected.keys()}"
for key in expected:
if isinstance(expected[key], dict):
assert_dict_approx_equal(actual[key], expected[key], tolerance)
else:
assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}"

yaml_file_path = "policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.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 'average' in test_name:
result = economic_impact.calculate("distributional/by_income/relative")
else:
pytest.fail(f"Unknown test case: {test_name}")

assert_dict_approx_equal(result, test_data['expected'])

if __name__ == "__main__":
pytest.main([__file__])

0 comments on commit 9f404ec

Please sign in to comment.