From d8550c5b2ace3cabccf11f8d2a688575e655ee91 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Wed, 17 Jul 2024 15:09:50 +0530 Subject: [PATCH 01/11] added basic files --- policyengine/economic_impact/distributional_impact/__init__.py | 0 .../distributional_impact/by_income_decile/__init__.py | 0 .../distributional_impact/by_income_decile/average/__init__.py | 0 .../distributional_impact/by_income_decile/relative/__init__.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 policyengine/economic_impact/distributional_impact/__init__.py create mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/__init__.py create mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/average/__init__.py create mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/relative/__init__.py diff --git a/policyengine/economic_impact/distributional_impact/__init__.py b/policyengine/economic_impact/distributional_impact/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/__init__.py b/policyengine/economic_impact/distributional_impact/by_income_decile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/average/__init__.py b/policyengine/economic_impact/distributional_impact/by_income_decile/average/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/relative/__init__.py b/policyengine/economic_impact/distributional_impact/by_income_decile/relative/__init__.py new file mode 100644 index 0000000..e69de29 From 2c3e6260a9b92d232fc4d7a9788f6e3130b50b35 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Wed, 17 Jul 2024 15:10:27 +0530 Subject: [PATCH 02/11] added code for, by income --- .../by_income_decile/average/average.py | 37 ++++++++++++++++++ .../by_income_decile/relative/relative.py | 38 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/average/average.py create mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/relative/relative.py diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/average/average.py b/policyengine/economic_impact/distributional_impact/by_income_decile/average/average.py new file mode 100644 index 0000000..de64b2c --- /dev/null +++ b/policyengine/economic_impact/distributional_impact/by_income_decile/average/average.py @@ -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"], + + } \ No newline at end of file diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/relative/relative.py b/policyengine/economic_impact/distributional_impact/by_income_decile/relative/relative.py new file mode 100644 index 0000000..33f6098 --- /dev/null +++ b/policyengine/economic_impact/distributional_impact/by_income_decile/relative/relative.py @@ -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"], + + } \ No newline at end of file From e2656d90c8ee472d7d59451db873d15c56cb2177 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Wed, 17 Jul 2024 15:10:49 +0530 Subject: [PATCH 03/11] updated main class --- policyengine/economic_impact/economic_impact.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/policyengine/economic_impact/economic_impact.py b/policyengine/economic_impact/economic_impact.py index 417483a..04c062b 100644 --- a/policyengine/economic_impact/economic_impact.py +++ b/policyengine/economic_impact/economic_impact.py @@ -36,6 +36,10 @@ PensionCredit ) +from .distributional_impact.by_income_decile.average.average import Average +from .distributional_impact.by_income_decile.relative.relative import Relative + + from typing import Dict class EconomicImpact: @@ -96,7 +100,8 @@ 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), } def _get_simulation_class(self) -> type: From ad9b1bc9fb28e4b3f7ce446bc024030df6cf9a00 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Wed, 17 Jul 2024 15:11:12 +0530 Subject: [PATCH 04/11] added tests --- .../by_income/average/average.yaml | 19 ++++++++++ .../by_income/average/test_average.py | 38 +++++++++++++++++++ .../by_income/relative/relative.yaml | 19 ++++++++++ .../by_income/relative/test_relative.py | 38 +++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml create mode 100644 policyengine/tests/economic_impact/distributional_impact/by_income/average/test_average.py create mode 100644 policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml create mode 100644 policyengine/tests/economic_impact/distributional_impact/by_income/relative/test_relative.py diff --git a/policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml b/policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml new file mode 100644 index 0000000..0b2e8ac --- /dev/null +++ b/policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml @@ -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 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_income/average/test_average.py b/policyengine/tests/economic_impact/distributional_impact/by_income/average/test_average.py new file mode 100644 index 0000000..7ee0bdd --- /dev/null +++ b/policyengine/tests/economic_impact/distributional_impact/by_income/average/test_average.py @@ -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__]) \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml b/policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml new file mode 100644 index 0000000..8ab7b48 --- /dev/null +++ b/policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml @@ -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 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_income/relative/test_relative.py b/policyengine/tests/economic_impact/distributional_impact/by_income/relative/test_relative.py new file mode 100644 index 0000000..bbe6fa5 --- /dev/null +++ b/policyengine/tests/economic_impact/distributional_impact/by_income/relative/test_relative.py @@ -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__]) \ No newline at end of file From e048e63d5be1244c69b0e898903e66d552873436 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Wed, 17 Jul 2024 15:34:04 +0530 Subject: [PATCH 05/11] added basic files --- .../distributional_impact/by_wealth_decile/__init__.py | 0 .../distributional_impact/by_wealth_decile/average/__init__.py | 0 .../distributional_impact/by_wealth_decile/relative/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/__init__.py create mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/average/__init__.py create mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/__init__.py diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/__init__.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/__init__.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/__init__.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/__init__.py new file mode 100644 index 0000000..e69de29 From 71e15a6bc28065384f50e49c6ab57a5298c8cbee Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Wed, 17 Jul 2024 15:34:22 +0530 Subject: [PATCH 06/11] added main logic --- .../by_wealth_decile/average/average.py | 37 ++++++++++++++++++ .../by_wealth_decile/relative/relative.py | 38 +++++++++++++++++++ .../economic_impact/economic_impact.py | 12 ++++-- 3 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/average/average.py create mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/relative.py diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/average.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/average.py new file mode 100644 index 0000000..faac486 --- /dev/null +++ b/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/average.py @@ -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_wealth_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"], + + } \ No newline at end of file diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/relative.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/relative.py new file mode 100644 index 0000000..fdba948 --- /dev/null +++ b/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/relative.py @@ -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_wealth_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"], + + } \ No newline at end of file diff --git a/policyengine/economic_impact/economic_impact.py b/policyengine/economic_impact/economic_impact.py index 04c062b..9b33dc3 100644 --- a/policyengine/economic_impact/economic_impact.py +++ b/policyengine/economic_impact/economic_impact.py @@ -36,9 +36,11 @@ PensionCredit ) -from .distributional_impact.by_income_decile.average.average import Average -from .distributional_impact.by_income_decile.relative.relative import Relative +from .distributional_impact.by_income_decile.average.average import Average as AverageByIncome +from .distributional_impact.by_income_decile.relative.relative import Relative as RelativeByIncome +from .distributional_impact.by_wealth_decile.average.average import Average as AverageByWealth +from .distributional_impact.by_wealth_decile.relative.relative import Relative as RelativeByWealth from typing import Dict @@ -100,8 +102,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), + "distributional/by_income/average": AverageByIncome(self.baseline, self.reformed), + "distributional/by_income/relative": RelativeByIncome(self.baseline, self.reformed), + "distributional/by_wealth/average": AverageByWealth(self.baseline, self.reformed), + "distributional/by_wealth/relative": RelativeByWealth(self.baseline, self.reformed), } def _get_simulation_class(self) -> type: From ad0c5091adac444aae90fab52c3c53e4582237dc Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Wed, 17 Jul 2024 15:34:38 +0530 Subject: [PATCH 07/11] added tests --- .../by_wealth/average/average.yaml | 18 +++++++++ .../by_wealth/average/test_average.py | 38 +++++++++++++++++++ .../by_wealth/relative/relative.yaml | 18 +++++++++ .../by_wealth/relative/test_relative.py | 38 +++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml create mode 100644 policyengine/tests/economic_impact/distributional_impact/by_wealth/average/test_average.py create mode 100644 policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml create mode 100644 policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/test_relative.py diff --git a/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml b/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml new file mode 100644 index 0000000..b7fa61a --- /dev/null +++ b/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml @@ -0,0 +1,18 @@ +- test_by_wealth_average: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + average: + 1: -1542.5277425455492 + 2: -1982.534041798336 + 3: -4556.8445264034835 + 4: -5370.201917927786 + 5: -7216.048585689702 + 6: -7591.304204543115 + 7: -9211.025138324332 + 8: -9648.62199574695 + 9: -11506.889823053707 + 10: -14378.295708381549 + 11: -13195.0 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/test_average.py b/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/test_average.py new file mode 100644 index 0000000..b0c8fe9 --- /dev/null +++ b/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/test_average.py @@ -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_wealth/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_wealth/average") + else: + pytest.fail(f"Unknown test case: {test_name}") + + assert_dict_approx_equal(result, test_data['expected']) + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml b/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml new file mode 100644 index 0000000..36bda21 --- /dev/null +++ b/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml @@ -0,0 +1,18 @@ +- test_by_wealth_relative: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + relative: + 1: -0.07043473659333963 + 2: -0.08091752038892668 + 3: -0.14646910570509714 + 4: -0.15592888462331828 + 5: -0.17838073974119345 + 6: -0.18231409463546466 + 7: -0.19855480132208575 + 8: -0.18318290061281753 + 9: -0.19595437624444986 + 10: -0.1391547762431559 + 11: -0.028110349900091886 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/test_relative.py b/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/test_relative.py new file mode 100644 index 0000000..c95e311 --- /dev/null +++ b/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/test_relative.py @@ -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_wealth/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 'relative' in test_name: + result = economic_impact.calculate("distributional/by_wealth/relative") + else: + pytest.fail(f"Unknown test case: {test_name}") + + assert_dict_approx_equal(result, test_data['expected']) + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file From 74bec3ee25d2d1f355a58cf63cc07325c0a8f752 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Thu, 18 Jul 2024 21:06:28 +0530 Subject: [PATCH 08/11] added winners and losers metrics --- .../economic_impact/economic_impact.py | 5 ++ .../by_income_decile/__init__.py | 0 .../by_income_decile/by_income_decile.py | 55 +++++++++++++++++++ .../by_wealth_decile/__init__.py | 0 .../by_wealth_decile/by_wealth_decile.py | 55 +++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 policyengine/economic_impact/winners_and_losers/by_income_decile/__init__.py create mode 100644 policyengine/economic_impact/winners_and_losers/by_income_decile/by_income_decile.py create mode 100644 policyengine/economic_impact/winners_and_losers/by_wealth_decile/__init__.py create mode 100644 policyengine/economic_impact/winners_and_losers/by_wealth_decile/by_wealth_decile.py diff --git a/policyengine/economic_impact/economic_impact.py b/policyengine/economic_impact/economic_impact.py index a539eff..b1ddd89 100644 --- a/policyengine/economic_impact/economic_impact.py +++ b/policyengine/economic_impact/economic_impact.py @@ -42,6 +42,9 @@ TaxRevenueImpact ) +from .winners_and_losers.by_income_decile.by_income_decile import ByIncomeDecile +from .winners_and_losers.by_wealth_decile.by_wealth_decile import ByWealthDecile + from typing import Dict class EconomicImpact: @@ -105,6 +108,8 @@ 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), + "winners_and_losers/by_income_decile": ByIncomeDecile(self.baseline, self.reformed), + "winners_and_losers/by_wealth_decile": ByWealthDecile(self.baseline, self.reformed), } diff --git a/policyengine/economic_impact/winners_and_losers/by_income_decile/__init__.py b/policyengine/economic_impact/winners_and_losers/by_income_decile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/winners_and_losers/by_income_decile/by_income_decile.py b/policyengine/economic_impact/winners_and_losers/by_income_decile/by_income_decile.py new file mode 100644 index 0000000..fd8928b --- /dev/null +++ b/policyengine/economic_impact/winners_and_losers/by_income_decile/by_income_decile.py @@ -0,0 +1,55 @@ +from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator +from policyengine_uk import Microsimulation +from microdf import MicroDataFrame, MicroSeries +import numpy as np + +class ByIncomeDecile(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 + ) + people = MicroSeries( + self.baseline.calculate("household_count_people"), weights=baseline_income.weights + ) + decile = MicroSeries(self.baseline.calculate("household_income_decile")).values + absolute_change = (reform_income - baseline_income).values + capped_baseline_income = np.maximum(baseline_income.values, 1) + capped_reform_income = ( + np.maximum(reform_income.values, 1) + absolute_change + ) + income_change = ( + capped_reform_income - capped_baseline_income + ) / capped_baseline_income + + outcome_groups = {} + all_outcomes = {} + BOUNDS = [-np.inf, -0.05, -1e-3, 1e-3, 0.05, np.inf] + LABELS = [ + "Lose more than 5%", + "Lose less than 5%", + "No change", + "Gain less than 5%", + "Gain more than 5%", + ] + for lower, upper, label in zip(BOUNDS[:-1], BOUNDS[1:], LABELS): + outcome_groups[label] = [] + for i in range(1, 11): + in_decile = decile == i + in_group = (income_change > lower) & (income_change <= upper) + in_both = in_decile & in_group + outcome_groups[label].append( + round(float(people[in_both].sum() / people[in_decile].sum()) * 100, 1) + ) + all_outcomes[label] = round(sum(outcome_groups[label]) / 10, 1) + + return { + "result": dict(deciles=outcome_groups, all=all_outcomes) + } \ No newline at end of file diff --git a/policyengine/economic_impact/winners_and_losers/by_wealth_decile/__init__.py b/policyengine/economic_impact/winners_and_losers/by_wealth_decile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/economic_impact/winners_and_losers/by_wealth_decile/by_wealth_decile.py b/policyengine/economic_impact/winners_and_losers/by_wealth_decile/by_wealth_decile.py new file mode 100644 index 0000000..9ee890b --- /dev/null +++ b/policyengine/economic_impact/winners_and_losers/by_wealth_decile/by_wealth_decile.py @@ -0,0 +1,55 @@ +from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator +from policyengine_uk import Microsimulation +from microdf import MicroDataFrame, MicroSeries +import numpy as np + +class ByWealthDecile(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 + ) + people = MicroSeries( + self.baseline.calculate("household_count_people"), weights=baseline_income.weights + ) + decile = MicroSeries(self.baseline.calculate("household_wealth_decile")).values + absolute_change = (reform_income - baseline_income).values + capped_baseline_income = np.maximum(baseline_income.values, 1) + capped_reform_income = ( + np.maximum(reform_income.values, 1) + absolute_change + ) + income_change = ( + capped_reform_income - capped_baseline_income + ) / capped_baseline_income + + outcome_groups = {} + all_outcomes = {} + BOUNDS = [-np.inf, -0.05, -1e-3, 1e-3, 0.05, np.inf] + LABELS = [ + "Lose more than 5%", + "Lose less than 5%", + "No change", + "Gain less than 5%", + "Gain more than 5%", + ] + for lower, upper, label in zip(BOUNDS[:-1], BOUNDS[1:], LABELS): + outcome_groups[label] = [] + for i in range(1, 11): + in_decile = decile == i + in_group = (income_change > lower) & (income_change <= upper) + in_both = in_decile & in_group + outcome_groups[label].append( + round(float(people[in_both].sum() / people[in_decile].sum()) * 100, 1) + ) + all_outcomes[label] = round(sum(outcome_groups[label]) / 10, 1) + + return { + "result": dict(deciles=outcome_groups, all=all_outcomes) + } \ No newline at end of file From 6b184b9ec2c287023df0fba3bf6537980a0d7681 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Tue, 13 Aug 2024 09:41:33 +0530 Subject: [PATCH 09/11] updated docs for Distributional Impact --- docs/Distributional/ByIncomeDecile/average.md | 52 +++++++++++++++++++ docs/Distributional/ByIncomeDecile/index.md | 1 + .../Distributional/ByIncomeDecile/relative.md | 49 +++++++++++++++++ docs/Distributional/ByWealthDecile/average.md | 48 +++++++++++++++++ docs/Distributional/ByWealthDecile/index.md | 1 + .../Distributional/ByWealthDecile/relative.md | 48 +++++++++++++++++ docs/Distributional/index.md | 1 + docs/_toc.yml | 12 ++++- 8 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 docs/Distributional/ByIncomeDecile/average.md create mode 100644 docs/Distributional/ByIncomeDecile/index.md create mode 100644 docs/Distributional/ByIncomeDecile/relative.md create mode 100644 docs/Distributional/ByWealthDecile/average.md create mode 100644 docs/Distributional/ByWealthDecile/index.md create mode 100644 docs/Distributional/ByWealthDecile/relative.md create mode 100644 docs/Distributional/index.md diff --git a/docs/Distributional/ByIncomeDecile/average.md b/docs/Distributional/ByIncomeDecile/average.md new file mode 100644 index 0000000..222554d --- /dev/null +++ b/docs/Distributional/ByIncomeDecile/average.md @@ -0,0 +1,52 @@ +# Income Distribution by Average + +## Overview + +The income distribution by average metrics provide insights into how a policy reform affects the average income across different income deciles. These metrics are part of the `EconomicImpact` class and measure the average change in household net income due to the reform. + +## Available Metrics + +1. `distributional/by_income/average`: Calculates the average change in household net income by income decile. + +## Metric Structure + +Each metric returns a dictionary with the following keys: +- `average`: A dictionary where the key is the income decile, and the value is the average change in income for that decile. + +The values represent the change in income due to the policy reform, rounded to several decimal places. + +## Example Usage + +```python +from policyengine import EconomicImpact + +# Initialize the EconomicImpact class with a reform +impact = EconomicImpact(reform={ + "gov.hmrc.income_tax.rates.uk[0].rate": { + "2024-01-01.2100-12-31": 0.55 + }}, country="uk") + +# Calculate average income change by income decile +average_income_change = impact.calculate("distributional/by_income/average") + +# Print the result +print(f"Average income change: {average_income_change}") +``` + +## Output + +``` +Average income change: {'average': {1: -1542.53, 2: -1982.53, 3: -4556.84, 4: -5370.20, 5: -7216.05, 6: -7591.30, 7: -9211.03, 8: -9648.62, 9: -11506.89, 10: -14378.30, 11: -13195.00}} +``` + +## Interpretation + +In this example: + +- The average income change is negative across all deciles, indicating a reduction in household net income due to the policy reform. +- The change is most significant in the higher deciles, with the highest decrease observed in the top decile (11), at -13,195.00. +- The impact is progressively less severe in lower deciles, with the smallest decrease observed in the lowest decile (1), at -1,542.53. + +--- + +Let me know if you need any further adjustments! \ No newline at end of file diff --git a/docs/Distributional/ByIncomeDecile/index.md b/docs/Distributional/ByIncomeDecile/index.md new file mode 100644 index 0000000..f9eeaf8 --- /dev/null +++ b/docs/Distributional/ByIncomeDecile/index.md @@ -0,0 +1 @@ +# By income \ No newline at end of file diff --git a/docs/Distributional/ByIncomeDecile/relative.md b/docs/Distributional/ByIncomeDecile/relative.md new file mode 100644 index 0000000..1d550b7 --- /dev/null +++ b/docs/Distributional/ByIncomeDecile/relative.md @@ -0,0 +1,49 @@ +# Income Decile by Relative Change + +## Overview + +The income decile by relative change metrics provide insights into how a policy reform affects household net income across different income deciles in relative terms. These metrics are part of the `EconomicImpact` class and measure the relative change in income due to the reform, expressed as a percentage of the baseline income. + +## Available Metrics + +1. `distributional/by_income/relative`: Calculates the relative change in household net income by income decile. + +## Metric Structure + +Each metric returns a dictionary with the following keys: +- `relative`: A dictionary where the key is the income decile, and the value is the relative change in income for that decile. The relative change is expressed as a percentage of the baseline income. + +The values are rounded to several decimal places. + +## Example Usage + +```python +from policyengine import EconomicImpact + +# Initialize the EconomicImpact class with a reform +impact = EconomicImpact(reform={ + "gov.hmrc.income_tax.rates.uk[0].rate": { + "2024-01-01.2100-12-31": 0.55 + }}, country="uk") + +# Calculate relative income change by income decile +relative_income_change = impact.calculate("distributional/by_income/relative") + +# Print the result +print(f"Relative income change: {relative_income_change}") +``` + +## Output + +``` +Relative income change: {'relative': {1: -0.0704, 2: -0.0809, 3: -0.1465, 4: -0.1559, 5: -0.1784, 6: -0.1823, 7: -0.1986, 8: -0.1832, 9: -0.1960, 10: -0.1392, 11: -0.0281}} +``` + +## Interpretation + +In this example: + +- The relative income change is negative across all deciles, indicating a reduction in household net income as a percentage of the baseline. +- The highest relative decrease is observed in the 7th decile, with a reduction of approximately 19.86%. +- The smallest relative decrease is in the 11th decile, at about 2.81%. +- The relative changes vary by decile, reflecting the impact of the policy reform on different income groups. diff --git a/docs/Distributional/ByWealthDecile/average.md b/docs/Distributional/ByWealthDecile/average.md new file mode 100644 index 0000000..b8d72a3 --- /dev/null +++ b/docs/Distributional/ByWealthDecile/average.md @@ -0,0 +1,48 @@ +# Average + +## Overview + +The wealth distribution by average metrics provide insights into how a policy reform affects the average wealth across different wealth deciles. These metrics are part of the `EconomicImpact` class and measure the average change in household net wealth due to the reform. + +## Available Metrics + +1. `distributional/by_wealth/average`: Calculates the average change in household net wealth by wealth decile. + +## Metric Structure + +Each metric returns a dictionary with the following keys: +- `average`: A dictionary where the key is the wealth decile, and the value is the average change in wealth for that decile. + +The values represent the change in wealth due to the policy reform, rounded to several decimal places. + +## Example Usage + +```python +from policyengine import EconomicImpact + +# Initialize the EconomicImpact class with a reform +impact = EconomicImpact(reform={ + "gov.hmrc.income_tax.rates.uk[0].rate": { + "2024-01-01.2100-12-31": 0.55 + }}, country="uk") + +# Calculate average wealth change by wealth decile +average_wealth_change = impact.calculate("distributional/by_wealth/average") + +# Print the result +print(f"Average wealth change: {average_wealth_change}") +``` + +## Output + +``` +Average wealth change: {'average': {1: -1542.53, 2: -1982.53, 3: -4556.84, 4: -5370.20, 5: -7216.05, 6: -7591.30, 7: -9211.03, 8: -9648.62, 9: -11506.89, 10: -14378.30, 11: -13195.00}} +``` + +## Interpretation + +In this example: + +- The average wealth change is negative across all deciles, indicating a reduction in household net wealth due to the policy reform. +- The impact is most severe in the highest deciles, with the highest decrease observed in the top decile (11), at -13,195.00. +- The decrease in wealth is progressively less severe in lower deciles, with the smallest decrease observed in the lowest decile (1), at -1,542.53. diff --git a/docs/Distributional/ByWealthDecile/index.md b/docs/Distributional/ByWealthDecile/index.md new file mode 100644 index 0000000..fef9dfb --- /dev/null +++ b/docs/Distributional/ByWealthDecile/index.md @@ -0,0 +1 @@ +# by wealth \ No newline at end of file diff --git a/docs/Distributional/ByWealthDecile/relative.md b/docs/Distributional/ByWealthDecile/relative.md new file mode 100644 index 0000000..da554ab --- /dev/null +++ b/docs/Distributional/ByWealthDecile/relative.md @@ -0,0 +1,48 @@ +# Relative + +## Overview + +The wealth distribution by relative change metrics provide insights into how a policy reform affects the relative change in household net income across different wealth deciles. These metrics are part of the `EconomicImpact` class and measure the proportional change in income due to the reform. + +## Available Metrics + +1. `distributional/by_wealth/relative`: Calculates the relative change in household net income by wealth decile. + +## Metric Structure + +Each metric returns a dictionary with the following keys: +- `relative`: A dictionary where the key is the wealth decile, and the value is the relative change in income for that decile. + +The relative change is calculated as the ratio of the change in income to the baseline income, providing a measure of how income changes proportionally within each decile. + +## Example Usage + +```python +from policyengine import EconomicImpact + +# Initialize the EconomicImpact class with a reform +impact = EconomicImpact(reform={ + "gov.hmrc.income_tax.rates.uk[0].rate": { + "2024-01-01.2100-12-31": 0.55 + }}, country="uk") + +# Calculate relative income change by wealth decile +relative_income_change = impact.calculate("distributional/by_wealth/relative") + +# Print the result +print(f"Relative income change: {relative_income_change}") +``` + +## Output + +``` +Relative income change: {'relative': {1: -0.0704, 2: -0.0809, 3: -0.1465, 4: -0.1559, 5: -0.1784, 6: -0.1823, 7: -0.1986, 8: -0.1832, 9: -0.1960, 10: -0.1392, 11: -0.0281}} +``` + +## Interpretation + +In this example: + +- The relative income change is negative across all wealth deciles, indicating a proportional decrease in household net income due to the policy reform. +- The relative change is highest in the higher deciles, with the most significant decrease observed in the top decile (11), at -0.0281 (2.81% decrease). +- The impact is also noticeable in the lower deciles, with the smallest relative decrease observed in the lowest decile (1), at -0.0704 (7.04% decrease). diff --git a/docs/Distributional/index.md b/docs/Distributional/index.md new file mode 100644 index 0000000..38456ce --- /dev/null +++ b/docs/Distributional/index.md @@ -0,0 +1 @@ +# Distributional \ No newline at end of file diff --git a/docs/_toc.yml b/docs/_toc.yml index c590a63..9cae0d3 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -20,4 +20,14 @@ parts: - file: Budgetary/index sections: - file: Budgetary/overall - - file: Budgetary/byprogram \ No newline at end of file + - file: Budgetary/byprogram + - file: Distributional/index + sections: + - file: Distributional/ByIncomeDecile/index + sections: + - file: Distributional/ByIncomeDecile/average + - file: Distributional/ByIncomeDecile/relative + - file: Distributional/ByWealthDecile/index + sections: + - file: Distributional/ByWealthDecile/average + - file: Distributional/ByWealthDecile/relative \ No newline at end of file From 23f1f6d45bb850ea9c90994669298d52926c2141 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Tue, 13 Aug 2024 10:19:43 +0530 Subject: [PATCH 10/11] added docs for winners and losers --- docs/WinnersAndLosers/byincomedecile.md | 78 +++++++++++++++++++++++++ docs/WinnersAndLosers/bywealthdecile.md | 74 +++++++++++++++++++++++ docs/WinnersAndLosers/index.md | 1 + docs/_toc.yml | 7 ++- 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 docs/WinnersAndLosers/byincomedecile.md create mode 100644 docs/WinnersAndLosers/bywealthdecile.md create mode 100644 docs/WinnersAndLosers/index.md diff --git a/docs/WinnersAndLosers/byincomedecile.md b/docs/WinnersAndLosers/byincomedecile.md new file mode 100644 index 0000000..c2bc19e --- /dev/null +++ b/docs/WinnersAndLosers/byincomedecile.md @@ -0,0 +1,78 @@ +# by Income Decile + +## Overview + +The winners and losers by income decile metrics provide insights into how a policy reform affects income changes across different income deciles. These metrics help to understand the distribution of income gains and losses among various income groups. The analysis is categorized based on the percentage change in income and provides a breakdown for each decile. + +## Available Metrics + +1. `winners_and_losers/by_income_decile`: Evaluates the income changes across income deciles and categorizes the population based on the magnitude of income gain or loss. + +## Metric Structure + +Each metric returns a dictionary with the following keys: +- `result`: Contains two sub-dictionaries: + - `deciles`: Breakdown of the population within each decile into categories based on income change. + - `all`: Summary statistics showing the average percentage of the population in each category across all deciles. + +### Income Change Categories + +The categories used for income change are: +- **Lose more than 5%**: Percentage of the population experiencing an income loss greater than 5%. +- **Lose less than 5%**: Percentage of the population experiencing an income loss less than 5%. +- **No change**: Percentage of the population experiencing no change in income. +- **Gain less than 5%**: Percentage of the population experiencing an income gain less than 5%. +- **Gain more than 5%**: Percentage of the population experiencing an income gain greater than 5%. + +## Example Usage + +```python +from policyengine import EconomicImpact + +# Initialize the EconomicImpact class with a reform +impact = EconomicImpact(reform={ + "gov.hmrc.income_tax.rates.uk[0].rate": { + "2024-01-01.2100-12-31": 0.55 + }}, country="uk") + +# Calculate income change metrics by income decile +by_income = impact.calculate("winners_and_losers/by_income_decile") +by_wealth = impact.calculate("winners_and_losers/by_wealth_decile") + +# Print the results +print(f"By Income Decile: {by_income}") +print(f"By Wealth Decile: {by_wealth}") +``` + +## Output + +### By Income Decile + +```python +{ + 'result': { + 'deciles': { + 'Lose more than 5%': [16.8, 21.8, 28.9, 52.2, 72.4, 85.4, 91.9, 93.0, 98.0, 95.4], + 'Lose less than 5%': [2.7, 4.8, 5.8, 9.0, 5.2, 0.3, 0.6, 0.1, 0.0, 1.4], + 'No change': [80.5, 73.4, 65.3, 38.8, 22.4, 14.3, 7.5, 6.9, 1.9, 3.1], + 'Gain less than 5%': [0.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + 'Gain more than 5%': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + }, + 'all': { + 'Lose more than 5%': 65.6, + 'Lose less than 5%': 3.0, + 'No change': 31.4, + 'Gain less than 5%': 0.0, + 'Gain more than 5%': 0.0 + } + } +} +``` + +## Interpretation + +In this example: + +- For income deciles, the percentage of people losing more than 5% increases from the lower deciles to the higher deciles, with a substantial concentration of losses in the top deciles. The majority of the population experiences no change in income, with very few gaining or losing within small ranges. +- For wealth deciles, a similar pattern is observed, with a significant proportion of the population in higher deciles experiencing losses greater than 5%, while most people in lower deciles experience no change. + diff --git a/docs/WinnersAndLosers/bywealthdecile.md b/docs/WinnersAndLosers/bywealthdecile.md new file mode 100644 index 0000000..95e6827 --- /dev/null +++ b/docs/WinnersAndLosers/bywealthdecile.md @@ -0,0 +1,74 @@ +# by Wealth Decile + +## Overview + +The winners and losers by wealth decile metrics provide insights into how a policy reform impacts different segments of the population based on their wealth. These metrics help understand the distributional effects of policy changes across various wealth deciles. + +## Available Metrics + +1. `winners_and_losers/by_wealth_decile`: Calculates the proportion of people in each wealth decile who experience a gain or loss of income due to the reform, categorized into different ranges of percentage change. + +## Metric Structure + +The `winners_and_losers/by_wealth_decile` metric returns a dictionary with the following keys: + +- `deciles`: A dictionary where each key represents a wealth decile, and the value is a list showing the proportion of people in that decile who fall into different categories based on their income change: + - `Lose more than 5%` + - `Lose less than 5%` + - `No change` + - `Gain less than 5%` + - `Gain more than 5%` + +- `all`: A dictionary showing the overall proportion of people across all wealth deciles who fall into each income change category. + +All proportions are expressed as percentages and rounded to 1 decimal place. + +## Example Usage + +```python +from policyengine import EconomicImpact + +# Initialize the EconomicImpact class with a reform +impact = EconomicImpact(reform={ + "gov.hmrc.income_tax.rates.uk[0].rate": { + "2024-01-01.2100-12-31": 0.55 + } +}, country="uk") + +# Calculate winners and losers by wealth decile +by_wealth_decile = impact.calculate("winners_and_losers/by_wealth_decile") + +# Print the results +print(f"By wealth decile: {by_wealth_decile}") +``` + +## Output + +```python +{ + "result": { + "deciles": { + "Lose more than 5%": [21.0, 31.4, 58.9, 64.5, 68.7, 79.3, 81.1, 82.6, 84.5, 86.4], + "Lose less than 5%": [6.6, 9.9, 2.3, 1.9, 3.4, 2.0, 0.4, 2.6, 0.2, 0.6], + "No change": [72.3, 58.6, 38.8, 33.6, 27.9, 18.7, 18.4, 14.8, 15.3, 13.0], + "Gain less than 5%": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + "Gain more than 5%": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + }, + "all": { + "Lose more than 5%": 65.8, + "Lose less than 5%": 3.0, + "No change": 31.1, + "Gain less than 5%": 0.0, + "Gain more than 5%": 0.0 + } + } +} +``` + +## Interpretation + +In this example: + +- The proportion of people in the lowest wealth decile who lose more than 5% of their income is 21.0%, while those who gain more than 5% is 0.0%. +- The proportion of people in the highest wealth decile who lose more than 5% is 86.4%, with no gain more than 5%. +- Overall, 65.8% of the population experiences a loss of more than 5% in income, with no significant gains. diff --git a/docs/WinnersAndLosers/index.md b/docs/WinnersAndLosers/index.md new file mode 100644 index 0000000..9840ddf --- /dev/null +++ b/docs/WinnersAndLosers/index.md @@ -0,0 +1 @@ +# Winners and Losers \ No newline at end of file diff --git a/docs/_toc.yml b/docs/_toc.yml index 9cae0d3..f66b33d 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -30,4 +30,9 @@ parts: - file: Distributional/ByWealthDecile/index sections: - file: Distributional/ByWealthDecile/average - - file: Distributional/ByWealthDecile/relative \ No newline at end of file + - file: Distributional/ByWealthDecile/relative + - file: WinnersAndLosers/index + sections: + - file: WinnersAndLosers/byincomedecile + - file: WinnersAndLosers/bywealthdecile + \ No newline at end of file From ad3898f95278986e867ca1745afc88d198f7e5e4 Mon Sep 17 00:00:00 2001 From: Mohammed Ismail Date: Tue, 13 Aug 2024 18:33:26 +0530 Subject: [PATCH 11/11] Inequality docs --- docs/Inequality/index.md | 1 + docs/Inequality/inequality.md | 63 +++++++++++++++++++++++++++++++++++ docs/_toc.yml | 5 +++ 3 files changed, 69 insertions(+) create mode 100644 docs/Inequality/index.md create mode 100644 docs/Inequality/inequality.md diff --git a/docs/Inequality/index.md b/docs/Inequality/index.md new file mode 100644 index 0000000..efa73c0 --- /dev/null +++ b/docs/Inequality/index.md @@ -0,0 +1 @@ +# Inequality \ No newline at end of file diff --git a/docs/Inequality/inequality.md b/docs/Inequality/inequality.md new file mode 100644 index 0000000..76893ae --- /dev/null +++ b/docs/Inequality/inequality.md @@ -0,0 +1,63 @@ +# Inequality Impact + +## Overview + +The inequality impact metrics provide insights into how a policy reform affects income distribution. These metrics are part of the `EconomicImpact` class and include measures of income inequality and concentration of income among the top earners. + +## Available Metrics + +1. **`inequality/gini`**: Calculates the Gini coefficient, which measures income inequality. +2. **`inequality/top_1_pct_share`**: Calculates the income share of the top 1% of earners. +3. **`inequality/top_10_pct_share`**: Calculates the income share of the top 10% of earners. + +## Metric Structure + +Each metric returns a dictionary with the following keys: +- **`baseline`**: The metric value before the reform. +- **`reform`**: The metric value after the reform. +- **`change`**: The absolute change in the metric value due to the reform. +- **`change_percentage`**: The percentage change in the metric value due to the reform (only for Gini coefficient and top 1% share). + +All values are rounded to 2 decimal places for baseline, reform, change, and change percentage. + +## Example Usage + +```python +from policyengine import EconomicImpact + +# Initialize the EconomicImpact class with a reform +impact = EconomicImpact( + reform={"gov.hmrc.income_tax.rates.uk[0].rate": {"2024-01-01": 0.25}}, + country="uk" +) + +# Calculate Gini coefficient impact +gini_impact = impact.calculate("inequality/gini") + +# Calculate top 1% income share impact +top_1_pct_share = impact.calculate("inequality/top_1_pct_share") + +# Calculate top 10% income share impact +top_10_pct_share = impact.calculate("inequality/top_10_pct_share") + +# Print the results +print(f"Gini coefficient impact: {gini_impact}") +print(f"Top 1% income share impact: {top_1_pct_share}") +print(f"Top 10% income share impact: {top_10_pct_share}") +``` + +## Output + +``` +Gini coefficient impact: {'baseline': 0.43, 'reform': 0.43, 'change': -0.00, 'change_percentage': -0.39} +Top 1% income share impact: {'baseline': 0.09, 'reform': 0.09, 'change': 0.00, 'change_percentage': 1.96} +Top 10% income share impact: {'baseline': 0.31, 'reform': 0.31, 'change': 0.00, 'change_percentage': 0.64} +``` + +## Interpretation + +In this example: + +- The Gini coefficient decreases slightly from 0.43 to 0.43, indicating a small reduction in income inequality. +- The income share of the top 1% increases from 9.12% to 9.30%, reflecting a slight rise in concentration among the highest earners. +- The income share of the top 10% increases from 31.02% to 31.21%, showing a minor rise in the share of income held by the top 10% of earners. diff --git a/docs/_toc.yml b/docs/_toc.yml index f66b33d..adc99be 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -35,4 +35,9 @@ parts: sections: - file: WinnersAndLosers/byincomedecile - file: WinnersAndLosers/bywealthdecile + - file: Inequality/index + sections: + - file: Inequality/inequality + + \ No newline at end of file