From 2e4c618afb299a926a91e74b4462489020ea8e98 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Sat, 6 Jul 2024 22:48:15 +0530 Subject: [PATCH 1/4] added basic init files --- .../economic_impact/poverty_impact/deep_poverty/__init__.py | 0 .../poverty_impact/deep_poverty/by_age/__init__.py | 0 .../poverty_impact/deep_poverty/by_gender/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/__init__.py create mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/by_age/__init__.py create mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/__init__.py diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/__init__.py b/policyengine/economic_impact/poverty_impact/deep_poverty/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/__init__.py b/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/__init__.py b/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/__init__.py new file mode 100644 index 0000000..e69de29 From a6e7211d35ebb31b0669c4d1065491e84131c693 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Sat, 6 Jul 2024 22:49:00 +0530 Subject: [PATCH 2/4] added main classes for deep poverty --- .../deep_poverty/by_age/by_age.py | 89 +++++++++++++++++++ .../deep_poverty/by_gender/by_gender.py | 67 ++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/by_age/by_age.py create mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.py diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/by_age.py b/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/by_age.py new file mode 100644 index 0000000..79bcb4f --- /dev/null +++ b/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/by_age.py @@ -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_deep_poverty", map_to="person") + reform_poverty = self.reformed.calculate("in_deep_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*100,2), + "reform": round(reform*100,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_deep_poverty", map_to="person") + reform_poverty = self.reformed.calculate("in_deep_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*100,2), + "reform": round(reform*100,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_deep_poverty", map_to="person") + reform_poverty = self.reformed.calculate("in_deep_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*100,2), + "reform": round(reform*100,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_deep_poverty", map_to="person") + reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") + + baseline = float(baseline_poverty.mean()) + reform = float(reform_poverty.mean()) + change = ((reform - baseline) / baseline) * 100 + + return { + "baseline": round(baseline*100,2), + "reform": round(reform*100,2), + "change": round(change,1) + } \ No newline at end of file diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.py b/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.py new file mode 100644 index 0000000..05ce1f1 --- /dev/null +++ b/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.py @@ -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_deep_poverty", map_to="person") + reform_poverty = self.reformed.calculate("in_deep_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*100,2), + "reform": round(reform*100,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_deep_poverty", map_to="person") + reform_poverty = self.reformed.calculate("in_deep_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*100,2), + "reform": round(reform*100,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_deep_poverty", map_to="person") + reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") + + baseline = float(baseline_poverty.mean()) + reform = float(reform_poverty.mean()) + change = ((reform - baseline) / baseline) * 100 + + return { + "baseline": round(baseline*100,2), + "reform": round(reform*100,2), + "change": round(change,1) + } From 46662aa7b03960988c6ae3c737a7e519260018c1 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Sat, 6 Jul 2024 22:49:24 +0530 Subject: [PATCH 3/4] edited the main class according --- .../economic_impact/economic_impact.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/policyengine/economic_impact/economic_impact.py b/policyengine/economic_impact/economic_impact.py index f75225d..ff57ebd 100644 --- a/policyengine/economic_impact/economic_impact.py +++ b/policyengine/economic_impact/economic_impact.py @@ -11,6 +11,17 @@ FemalePoverty as RegularFemalePoverty, AllPoverty as RegularGenderAllPoverty ) +from .poverty_impact.deep_poverty.by_age.by_age import ( + ChildPoverty as DeepChildPoverty, + AdultPoverty as DeepAdultPoverty, + SeniorPoverty as DeepSeniorPoverty, + AllPoverty as DeepAgeAllPoverty +) +from .poverty_impact.deep_poverty.by_gender.by_gender import ( + MalePoverty as DeepMalePoverty, + FemalePoverty as DeepFemalePoverty, + AllPoverty as DeepGenderAllPoverty +) from typing import Dict class EconomicImpact: @@ -54,6 +65,14 @@ def __init__(self, reform: dict, country: str) -> None: "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), + "poverty/deep/child": DeepChildPoverty(self.baseline, self.reformed), + "poverty/deep/adult": DeepAdultPoverty(self.baseline, self.reformed), + "poverty/deep/senior": DeepSeniorPoverty(self.baseline, self.reformed), + "poverty/deep/age/all": DeepAgeAllPoverty(self.baseline, self.reformed), + "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), + } def _get_simulation_class(self) -> type: From 4f8890e424c4fece72a03edc5d25fce76a039e37 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Sat, 6 Jul 2024 22:49:54 +0530 Subject: [PATCH 4/4] added tests for deep poverty --- .../deep_poverty/by_age/by_age.yaml | 40 ++++++++++++++++++ .../deep_poverty/by_age/test_deep_by_age.py | 41 +++++++++++++++++++ .../deep_poverty/by_gender/by_gender.yaml | 30 ++++++++++++++ .../by_gender/test_deep_by_gender.py | 41 +++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml create mode 100644 policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/test_deep_by_age.py create mode 100644 policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.yaml create mode 100644 policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/test_deep_by_gender.py diff --git a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml new file mode 100644 index 0000000..65db7db --- /dev/null +++ b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml @@ -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: 2.44 + reform: 2.45 + change: 0.7 + +- test_adult_poverty: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + baseline: 2.6 + reform: 2.7 + change: 3.9 + +- test_senior_poverty: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + baseline: 1.76 + reform: 1.76 + change: 0.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: 2.41 + reform: 2.47 + change: 2.7 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/test_deep_by_age.py b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/test_deep_by_age.py new file mode 100644 index 0000000..2dc34f0 --- /dev/null +++ b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/test_deep_by_age.py @@ -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/deep_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/deep/child") + elif 'adult' in test_name: + result = economic_impact.calculate("poverty/deep/adult") + elif 'senior' in test_name: + result = economic_impact.calculate("poverty/deep/senior") + elif 'all' in test_name: + result = economic_impact.calculate("poverty/deep/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__]) diff --git a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.yaml b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.yaml new file mode 100644 index 0000000..4187504 --- /dev/null +++ b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.yaml @@ -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: 2.66 + reform: 2.73 + change: 2.5 + +- female_poverty_test: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + baseline: 2.16 + reform: 2.23 + change: 2.9 + +- all_poverty_test: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + baseline: 2.41 + reform: 2.47 + change: 2.7 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/test_deep_by_gender.py b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/test_deep_by_gender.py new file mode 100644 index 0000000..2dc34f0 --- /dev/null +++ b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/test_deep_by_gender.py @@ -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/deep_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/deep/child") + elif 'adult' in test_name: + result = economic_impact.calculate("poverty/deep/adult") + elif 'senior' in test_name: + result = economic_impact.calculate("poverty/deep/senior") + elif 'all' in test_name: + result = economic_impact.calculate("poverty/deep/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__])