diff --git a/policyengine/charts/poverty/__init__.py b/policyengine/charts/poverty/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/charts/poverty/deep/__init__.py b/policyengine/charts/poverty/deep/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/charts/poverty/deep/by_age.py b/policyengine/charts/poverty/deep/by_age.py new file mode 100644 index 0000000..fabe5c6 --- /dev/null +++ b/policyengine/charts/poverty/deep/by_age.py @@ -0,0 +1,107 @@ +import plotly.graph_objects as go +from policyengine_core.charts.formatting import * + +class DeepPovertyByAgeChart: + def __init__(self,country:str, data=None): + if data is None: + raise ValueError("Data must be provided") + + self.data = data + + def _get_color(self, value): + # All bars should be gray + return GRAY + + def _get_change_direction(self, value): + if value > 0: + return "increase" + elif value < 0: + return "decrease" + else: + return "no change" + + def ordinal_suffix(self, n): + """Return the ordinal suffix for an integer.""" + if 10 <= n % 100 <= 20: + suffix = 'th' + else: + suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') + return suffix + + def generate_chart_data(self): + categories = list(self.data.keys()) + values = [self.data[cat]['change'] for cat in categories] + baselines = [self.data[cat]['baseline'] * 100 for cat in categories] + reforms = [self.data[cat]['reform'] * 100 for cat in categories] + + # Generate hover texts with baseline, reform, and percentage change + hover_texts = [ + f"This reform would {self._get_change_direction(val)} the percentage of {category.lower()} in poverty by {abs(val):.1f}% from {baseline:.1f}% to {reform:.1f}%" + for category, val, baseline, reform in zip(categories, values, baselines, reforms) + ] + + fig = go.Figure() + + values_in_pct = values # Use percentage values + colors = [self._get_color(value) for value in values] + + # Add bar chart with percentage values + fig.add_trace(go.Bar( + x=categories, + y=values_in_pct, + marker=dict(color=colors, line=dict(width=1)), + width=0.6, + text=[f"{abs(value):.1f}%" for value in values], # Display values as percentages + textposition='outside', + hovertemplate="%{x}

%{customdata}", # Hover shows category + customdata=hover_texts + )) + + # Update layout to reflect percentage values on y-axis + fig.update_layout( + yaxis=dict( + tickformat=",.1f%%", # Format y-axis as percentages with one decimal place + title="Percentage Change in Poverty" + ), + xaxis=dict( + title="Category" + ), + hoverlabel=dict( + bgcolor="white", + font=dict(color="black", size=16) + ), + title="Change in Poverty Percentage by Category" + ) + + format_fig(fig) # Keep the formatting logic from policyengine_core + return fig + + +# # Example data +# data = { +# 'Child': { +# 'Baseline': 0.32427219591395395, +# 'Reform': 0.33392168532001054, +# 'Change': 3.0 +# }, +# 'Adult': { +# 'Baseline': 0.17427822561729264, +# 'Reform': 0.17757158627182623, +# 'Change': 1.9 +# }, +# 'Senior': { +# 'Baseline': 0.12817646500651358, +# 'Reform': 0.1370685860340031, +# 'Change': 6.9 +# }, +# 'All': { +# 'Baseline': 0.19913534734369268, +# 'Reform': 0.20487670454940832, +# 'Change': 2.9 +# } +# } + +# # Generate chart for all categories +# chart = OverallChart(data=data) +# fig = chart.generate_chart_data() +# fig.show() diff --git a/policyengine/charts/poverty/deep/by_gender.py b/policyengine/charts/poverty/deep/by_gender.py new file mode 100644 index 0000000..40e1a61 --- /dev/null +++ b/policyengine/charts/poverty/deep/by_gender.py @@ -0,0 +1,103 @@ +import plotly.graph_objects as go +from policyengine_core.charts.formatting import * + +class DeepPovertyByGenderChart: + def __init__(self, country:str,data=None): + if data is None: + raise ValueError("Data must be provided") + + self.data = data + + def _get_color(self, value): + # All bars should be gray + return GRAY + + def _get_change_direction(self, value): + if value > 0: + return "increase" + elif value < 0: + return "decrease" + else: + return "no change" + + def ordinal_suffix(self, n): + """Return the ordinal suffix for an integer.""" + if 10 <= n % 100 <= 20: + suffix = 'th' + else: + suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') + return suffix + + def generate_chart_data(self): + categories = list(self.data.keys()) + values = [self.data[cat]['change'] for cat in categories] + baselines = [self.data[cat]['baseline'] * 100 for cat in categories] + reforms = [self.data[cat]['reform'] * 100 for cat in categories] + + # Generate hover texts with baseline, reform, and percentage change + hover_texts = [ + f"This reform would {self._get_change_direction(val)} the percentage of {category.lower()} in poverty by {abs(val):.1f}% from {baseline:.1f}% to {reform:.1f}%" + for category, val, baseline, reform in zip(categories, values, baselines, reforms) + ] + + fig = go.Figure() + + values_in_pct = values # Use percentage values + colors = [self._get_color(value) for value in values] + + # Add bar chart with percentage values + fig.add_trace(go.Bar( + x=categories, + y=values_in_pct, + marker=dict(color=colors, line=dict(width=1)), + width=0.6, + text=[f"{abs(value):.1f}%" for value in values], # Display values as percentages + textposition='outside', + hovertemplate="Category %{x}

%{customdata}", # Hover shows category + customdata=hover_texts + )) + + # Update layout to reflect percentage values on y-axis + fig.update_layout( + yaxis=dict( + tickformat=",.1f%%", + ticksuffix = "%", # Format y-axis as percentages with one decimal place + title="Percentage Change in Poverty" + ), + xaxis=dict( + title="Category" + ), + hoverlabel=dict( + bgcolor="white", + font=dict(color="black", size=16) + ), + title="Change in Poverty Percentage by Category" + ) + + format_fig(fig) # Keep the formatting logic from policyengine_core + return fig + + +# # Example data +# data = { +# 'Male': { +# 'Baseline': 0.18412623468617267, +# 'Reform': 0.18932591339284738, +# 'Change': 2.8 +# }, +# 'Female': { +# 'Baseline': 0.21377616483057263, +# 'Reform': 0.22004590877186603, +# 'Change': 2.9 +# }, +# 'All': { +# 'Baseline': 0.19913534734369268, +# 'Reform': 0.20487670454940832, +# 'Change': 2.9 +# } +# } + +# # Generate chart for all categories +# chart = OverallChart(data=data) +# fig = chart.generate_chart_data() +# fig.show() diff --git a/policyengine/charts/poverty/regular/__init__.py b/policyengine/charts/poverty/regular/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/policyengine/charts/poverty/regular/by_age.py b/policyengine/charts/poverty/regular/by_age.py new file mode 100644 index 0000000..e60333b --- /dev/null +++ b/policyengine/charts/poverty/regular/by_age.py @@ -0,0 +1,107 @@ +import plotly.graph_objects as go +from policyengine_core.charts.formatting import * + +class RegularPovertyByAgeChart: + def __init__(self,country:str, data=None): + if data is None: + raise ValueError("Data must be provided") + + self.data = data + + def _get_color(self, value): + # All bars should be gray + return GRAY + + def _get_change_direction(self, value): + if value > 0: + return "increase" + elif value < 0: + return "decrease" + else: + return "no change" + + def ordinal_suffix(self, n): + """Return the ordinal suffix for an integer.""" + if 10 <= n % 100 <= 20: + suffix = 'th' + else: + suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') + return suffix + + def generate_chart_data(self): + categories = list(self.data.keys()) + values = [self.data[cat]['change'] for cat in categories] + baselines = [self.data[cat]['baseline'] * 100 for cat in categories] + reforms = [self.data[cat]['reform'] * 100 for cat in categories] + + # Generate hover texts with baseline, reform, and percentage change + hover_texts = [ + f"This reform would {self._get_change_direction(val)} the percentage of {category.lower()} in poverty by {abs(val):.1f}% from {baseline:.1f}% to {reform:.1f}%" + for category, val, baseline, reform in zip(categories, values, baselines, reforms) + ] + + fig = go.Figure() + + values_in_pct = values # Use percentage values + colors = [self._get_color(value) for value in values] + + # Add bar chart with percentage values + fig.add_trace(go.Bar( + x=categories, + y=values_in_pct, + marker=dict(color=colors, line=dict(width=1)), + width=0.6, + text=[f"{abs(value):.1f}%" for value in values], # Display values as percentages + textposition='outside', + hovertemplate="%{x}

%{customdata}", # Hover shows category + customdata=hover_texts + )) + + # Update layout to reflect percentage values on y-axis + fig.update_layout( + yaxis=dict( + tickformat=",.1f%%", # Format y-axis as percentages with one decimal place + title="Percentage Change in Poverty" + ), + xaxis=dict( + title="Category" + ), + hoverlabel=dict( + bgcolor="white", + font=dict(color="black", size=16) + ), + title="Change in Poverty Percentage by Category" + ) + + format_fig(fig) # Keep the formatting logic from policyengine_core + return fig + + +# # Example data +# data = { +# 'Child': { +# 'Baseline': 0.32427219591395395, +# 'Reform': 0.33392168532001054, +# 'Change': 3.0 +# }, +# 'Adult': { +# 'Baseline': 0.17427822561729264, +# 'Reform': 0.17757158627182623, +# 'Change': 1.9 +# }, +# 'Senior': { +# 'Baseline': 0.12817646500651358, +# 'Reform': 0.1370685860340031, +# 'Change': 6.9 +# }, +# 'All': { +# 'Baseline': 0.19913534734369268, +# 'Reform': 0.20487670454940832, +# 'Change': 2.9 +# } +# } + +# # Generate chart for all categories +# chart = OverallChart(data=data) +# fig = chart.generate_chart_data() +# fig.show() diff --git a/policyengine/charts/poverty/regular/by_gender.py b/policyengine/charts/poverty/regular/by_gender.py new file mode 100644 index 0000000..a393c25 --- /dev/null +++ b/policyengine/charts/poverty/regular/by_gender.py @@ -0,0 +1,103 @@ +import plotly.graph_objects as go +from policyengine_core.charts.formatting import * + +class RegularPovertyByGenderChart: + def __init__(self, country:str,data=None): + if data is None: + raise ValueError("Data must be provided") + + self.data = data + + def _get_color(self, value): + # All bars should be gray + return GRAY + + def _get_change_direction(self, value): + if value > 0: + return "increase" + elif value < 0: + return "decrease" + else: + return "no change" + + def ordinal_suffix(self, n): + """Return the ordinal suffix for an integer.""" + if 10 <= n % 100 <= 20: + suffix = 'th' + else: + suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') + return suffix + + def generate_chart_data(self): + categories = list(self.data.keys()) + values = [self.data[cat]['change'] for cat in categories] + baselines = [self.data[cat]['baseline'] * 100 for cat in categories] + reforms = [self.data[cat]['reform'] * 100 for cat in categories] + + # Generate hover texts with baseline, reform, and percentage change + hover_texts = [ + f"This reform would {self._get_change_direction(val)} the percentage of {category.lower()} in poverty by {abs(val):.1f}% from {baseline:.1f}% to {reform:.1f}%" + for category, val, baseline, reform in zip(categories, values, baselines, reforms) + ] + + fig = go.Figure() + + values_in_pct = values # Use percentage values + colors = [self._get_color(value) for value in values] + + # Add bar chart with percentage values + fig.add_trace(go.Bar( + x=categories, + y=values_in_pct, + marker=dict(color=colors, line=dict(width=1)), + width=0.6, + text=[f"{abs(value):.1f}%" for value in values], # Display values as percentages + textposition='outside', + hovertemplate="Category %{x}

%{customdata}", # Hover shows category + customdata=hover_texts + )) + + # Update layout to reflect percentage values on y-axis + fig.update_layout( + yaxis=dict( + tickformat=",.1f%%", + ticksuffix = "%", # Format y-axis as percentages with one decimal place + title="Percentage Change in Poverty" + ), + xaxis=dict( + title="Category" + ), + hoverlabel=dict( + bgcolor="white", + font=dict(color="black", size=16) + ), + title="Change in Poverty Percentage by Category" + ) + + format_fig(fig) # Keep the formatting logic from policyengine_core + return fig + + +# # Example data +# data = { +# 'Male': { +# 'Baseline': 0.18412623468617267, +# 'Reform': 0.18932591339284738, +# 'Change': 2.8 +# }, +# 'Female': { +# 'Baseline': 0.21377616483057263, +# 'Reform': 0.22004590877186603, +# 'Change': 2.9 +# }, +# 'All': { +# 'Baseline': 0.19913534734369268, +# 'Reform': 0.20487670454940832, +# 'Change': 2.9 +# } +# } + +# # Generate chart for all categories +# chart = OverallChart(data=data) +# fig = chart.generate_chart_data() +# fig.show() diff --git a/policyengine/economic_impact/economic_impact.py b/policyengine/economic_impact/economic_impact.py index c913e96..49b914b 100644 --- a/policyengine/economic_impact/economic_impact.py +++ b/policyengine/economic_impact/economic_impact.py @@ -79,6 +79,10 @@ from typing import Dict, Type, Union from policyengine.charts.inequality import InequalityImpactChart +from policyengine.charts.poverty.regular.by_age import RegularPovertyByAgeChart +from policyengine.charts.poverty.deep.by_age import DeepPovertyByAgeChart +from policyengine.charts.poverty.regular.by_gender import RegularPovertyByGenderChart +from policyengine.charts.poverty.deep.by_gender import DeepPovertyByGenderChart class EconomicImpact: @@ -176,6 +180,10 @@ def __init__(self, reform: dict, country: str, dataset: str = None) -> None: self.chart_generators: Dict[str, Type] = { "inequality": InequalityImpactChart, + "poverty/regular/by_age": RegularPovertyByAgeChart, + "poverty/regular/by_gender": RegularPovertyByGenderChart, + "poverty/deep/by_age": DeepPovertyByAgeChart, + "poverty/deep/by_gender": DeepPovertyByGenderChart, } self.composite_metrics: Dict[str, Dict[str, str]] = { @@ -183,6 +191,28 @@ def __init__(self, reform: dict, country: str, dataset: str = None) -> None: "Gini index": "inequality/gini", "Top 1% share": "inequality/top_1_pct_share", "Top 10% share": "inequality/top_10_pct_share", + }, + "poverty/regular/by_age": { + "Child": "poverty/regular/child", + "Adult": "poverty/regular/adult", + "Senior":"poverty/regular/senior", + "All": "poverty/regular/age/all" + }, + "poverty/regular/by_gender": { + "Male": "poverty/regular/male", + "Female": "poverty/regular/female", + "All": "poverty/regular/gender/all" + }, + "poverty/deep/by_age": { + "Child": "poverty/deep/child", + "Adult": "poverty/deep/adult", + "Senior":"poverty/deep/senior", + "All": "poverty/deep/age/all" + }, + "poverty/deep/by_gender": { + "Male": "poverty/deep/male", + "Female": "poverty/deep/female", + "All": "poverty/deep/gender/all" } } @@ -250,7 +280,7 @@ def chart(self, metric: str) -> dict: if not chart_generator: raise ValueError(f"No chart generator found for metric: {metric}") - return chart_generator(data=data).generate_chart_data() + return chart_generator(self.country,data=data).generate_chart_data() def add_metric(self, metric: str, calculator: object, chart_generator: Type = None): self.metric_calculators[metric] = calculator