From 3bd7b4d67311fd07c5165f466dd16a8a9089eb9a Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Fri, 14 Mar 2025 10:33:42 +0000 Subject: [PATCH 1/7] Add single economy and start comparison --- .../calculate_economy_comparison.py | 781 ++++++++++++++++-- .../calculate_economy_comparison.py | 696 ++++++++++++++++ .../macro/single/calculate_single_economy.py | 414 +++++++++- 3 files changed, 1790 insertions(+), 101 deletions(-) create mode 100644 policyengine/outputs/macro/comparison/legacy_api/calculate_economy_comparison.py diff --git a/policyengine/outputs/macro/comparison/calculate_economy_comparison.py b/policyengine/outputs/macro/comparison/calculate_economy_comparison.py index 41398eee..8e51e87f 100644 --- a/policyengine/outputs/macro/comparison/calculate_economy_comparison.py +++ b/policyengine/outputs/macro/comparison/calculate_economy_comparison.py @@ -1,110 +1,733 @@ """Calculate comparison statistics between two economic scenarios.""" +from microdf import MicroSeries +import numpy as np +from policyengine_core.tools.hugging_face import download_huggingface_dataset +import pandas as pd +import h5py +from pydantic import BaseModel +from policyengine import Simulation +from policyengine.outputs.macro.single.calculate_single_economy import SingleEconomy -import typing +class EconomyComparison(BaseModel): + budget: None + detailed_budget: None + decile: None + inequality: None + poverty: None + poverty_by_gender: None + poverty_by_race: None + intra_decile: None + wealth_decile: None + intra_wealth_decile: None + labor_supply_response: None + constituency_impact: None -from policyengine import Simulation -from pydantic import BaseModel -from policyengine.utils.calculations import get_change +def calculate_economy_comparison( + simulation: Simulation, +) -> EconomyComparison: + """Calculate comparison statistics between two economic scenarios.""" + if not simulation.is_comparison: + raise ValueError("Simulation must be a comparison simulation.") -from policyengine.outputs.macro.single import ( - _calculate_government_balance, - FiscalSummary, - _calculate_inequality, - InequalitySummary, -) + baseline = simulation.baseline_simulation + reform = simulation.reform_simulation + options = simulation.options -from .decile import calculate_decile_impacts, DecileImpacts +class BudgetaryImpact(BaseModel): + budgetary_impact: float + tax_revenue_impact: float + state_tax_revenue_impact: float + benefit_spending_impact: float + households: int + baseline_net_income: float -from typing import Literal, List +def budgetary_impact(baseline: SingleEconomy, reform: SingleEconomy) -> BudgetaryImpact: + tax_revenue_impact = reform["total_tax"] - baseline["total_tax"] + state_tax_revenue_impact = ( + reform["total_state_tax"] - baseline["total_state_tax"] + ) + benefit_spending_impact = ( + reform["total_benefits"] - baseline["total_benefits"] + ) + budgetary_impact = tax_revenue_impact - benefit_spending_impact + return BudgetaryImpact( + budgetary_impact=budgetary_impact, + tax_revenue_impact=tax_revenue_impact, + state_tax_revenue_impact=state_tax_revenue_impact, + benefit_spending_impact=benefit_spending_impact, + households=sum(baseline["household_weight"]), + baseline_net_income=baseline["total_net_income"], + ) -class FiscalComparison(BaseModel): - baseline: FiscalSummary - reform: FiscalSummary - change: FiscalSummary - relative_change: FiscalSummary +def labor_supply_response(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + substitution_lsr = ( + reform["substitution_lsr"] - baseline["substitution_lsr"] + ) + income_lsr = reform["income_lsr"] - baseline["income_lsr"] + total_change = substitution_lsr + income_lsr + revenue_change = ( + reform["budgetary_impact_lsr"] - baseline["budgetary_impact_lsr"] + ) -class InequalityComparison(BaseModel): - baseline: InequalitySummary - reform: InequalitySummary - change: InequalitySummary - relative_change: InequalitySummary + substitution_lsr_hh = np.array(reform["substitution_lsr_hh"]) - np.array( + baseline["substitution_lsr_hh"] + ) + income_lsr_hh = np.array(reform["income_lsr_hh"]) - np.array( + baseline["income_lsr_hh"] + ) + decile = np.array(baseline["household_income_decile"]) + household_weight = baseline["household_weight"] + total_lsr_hh = substitution_lsr_hh + income_lsr_hh -class Headlines(BaseModel): - budgetary_impact: float - """The change in the (federal) government budget balance.""" - winner_share: float - """The share of people that are better off in the reform scenario.""" + emp_income = MicroSeries( + baseline["employment_income_hh"], weights=household_weight + ) + self_emp_income = MicroSeries( + baseline["self_employment_income_hh"], weights=household_weight + ) + earnings = emp_income + self_emp_income + original_earnings = earnings - total_lsr_hh + substitution_lsr_hh = MicroSeries( + substitution_lsr_hh, weights=household_weight + ) + income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) + decile_avg = dict( + income=income_lsr_hh.groupby(decile).mean().to_dict(), + substitution=substitution_lsr_hh.groupby(decile).mean().to_dict(), + ) + decile_rel = dict( + income=( + income_lsr_hh.groupby(decile).sum() + / original_earnings.groupby(decile).sum() + ).to_dict(), + substitution=( + substitution_lsr_hh.groupby(decile).sum() + / original_earnings.groupby(decile).sum() + ).to_dict(), + ) -class EconomyComparison(BaseModel): - headlines: Headlines - """Headline statistics for the comparison.""" - fiscal: FiscalComparison - """Government budgets and other top-level fiscal statistics.""" - inequality: InequalityComparison - """Inequality statistics for the household sector.""" - distributional: DecileImpacts - """Distributional impacts of the reform.""" + relative_lsr = dict( + income=(income_lsr_hh.sum() / original_earnings.sum()), + substitution=(substitution_lsr_hh.sum() / original_earnings.sum()), + ) + decile_rel["income"] = { + int(k): v for k, v in decile_rel["income"].items() if k > 0 + } + decile_rel["substitution"] = { + int(k): v for k, v in decile_rel["substitution"].items() if k > 0 + } -def calculate_economy_comparison( - simulation: Simulation, -) -> EconomyComparison: - """Calculate comparison statistics between two economic scenarios.""" - if not simulation.is_comparison: - raise ValueError("Simulation must be a comparison simulation.") + hours = dict( + baseline=baseline["weekly_hours"], + reform=reform["weekly_hours"], + change=reform["weekly_hours"] - baseline["weekly_hours"], + income_effect=reform["weekly_hours_income_effect"] + - baseline["weekly_hours_income_effect"], + substitution_effect=reform["weekly_hours_substitution_effect"] + - baseline["weekly_hours_substitution_effect"], + ) - baseline = simulation.baseline_simulation - reform = simulation.reform_simulation - options = simulation.options + return dict( + substitution_lsr=substitution_lsr, + income_lsr=income_lsr, + relative_lsr=relative_lsr, + total_change=total_change, + revenue_change=revenue_change, + decile=dict( + average=decile_avg, + relative=decile_rel, + ), + hours=hours, + ) + + +def detailed_budgetary_impact( + baseline: SingleEconomy, reform: SingleEconomy, country_id: str +) -> dict: + result = {} + if country_id == "uk": + for program in baseline["programs"]: + # baseline[programs][program] = total budgetary impact of program + result[program] = dict( + baseline=baseline["programs"][program], + reform=reform["programs"][program], + difference=reform["programs"][program] + - baseline["programs"][program], + ) + return result + + +def decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + """ + Compare the impact of a reform on the deciles of the population. - baseline_balance = _calculate_government_balance(baseline, options) - reform_balance = _calculate_government_balance(reform, options) - balance_change = get_change( - baseline_balance, reform_balance, relative=False + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on the deciles of the population. + """ + baseline_income = MicroSeries( + baseline["household_net_income"], weights=baseline["household_weight"] ) - balance_rel_change = get_change( - baseline_balance, reform_balance, relative=True + reform_income = MicroSeries( + reform["household_net_income"], weights=baseline_income.weights ) - fiscal_comparison = FiscalComparison( - baseline=baseline_balance, - reform=reform_balance, - change=balance_change, - relative_change=balance_rel_change, + + # Filter out negative decile values + decile = MicroSeries(baseline["household_income_decile"]) + baseline_income_filtered = baseline_income[decile >= 0] + reform_income_filtered = reform_income[decile >= 0] + + income_change = reform_income_filtered - baseline_income_filtered + rel_income_change_by_decile = ( + income_change.groupby(decile).sum() + / baseline_income_filtered.groupby(decile).sum() ) - baseline_inequality = _calculate_inequality(baseline) - reform_inequality = _calculate_inequality(reform) - inequality_change = get_change( - baseline_inequality, reform_inequality, relative=False + avg_income_change_by_decile = ( + income_change.groupby(decile).sum() + / baseline_income_filtered.groupby(decile).count() ) - inequality_rel_change = get_change( - baseline_inequality, reform_inequality, relative=True + rel_decile_dict = rel_income_change_by_decile.to_dict() + avg_decile_dict = avg_income_change_by_decile.to_dict() + result = dict( + relative={int(k): v for k, v in rel_decile_dict.items()}, + average={int(k): v for k, v in avg_decile_dict.items()}, ) - inequality_comparison = InequalityComparison( - baseline=baseline_inequality, - reform=reform_inequality, - change=inequality_change, - relative_change=inequality_rel_change, + return result + + +def wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + """ + Compare the impact of a reform on the deciles of the population. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on the deciles of the population. + """ + baseline_income = MicroSeries( + baseline["household_net_income"], weights=baseline["household_weight"] + ) + reform_income = MicroSeries( + reform["household_net_income"], weights=baseline_income.weights ) - decile_impacts = calculate_decile_impacts(baseline, reform, options) + # Filter out negative decile values + decile = MicroSeries(baseline["household_wealth_decile"]) + baseline_income_filtered = baseline_income[decile >= 0] + reform_income_filtered = reform_income[decile >= 0] - # Headlines - budgetary_impact = fiscal_comparison.change.federal_balance - winner_share = decile_impacts.income.winners_and_losers.all.gain_share - headlines = Headlines( - budgetary_impact=budgetary_impact, - winner_share=winner_share, + income_change = reform_income_filtered - baseline_income_filtered + rel_income_change_by_decile = ( + income_change.groupby(decile).sum() + / baseline_income_filtered.groupby(decile).sum() + ) + avg_income_change_by_decile = ( + income_change.groupby(decile).sum() + / baseline_income_filtered.groupby(decile).count() + ) + rel_decile_dict = rel_income_change_by_decile.to_dict() + avg_decile_dict = avg_income_change_by_decile.to_dict() + result = dict( + relative={int(k): v for k, v in rel_decile_dict.items()}, + average={int(k): v for k, v in avg_decile_dict.items()}, + ) + return result + + +def inequality_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + """ + Compare the impact of a reform on inequality. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on inequality. + """ + + return dict( + gini=dict( + baseline=baseline["gini"], + reform=reform["gini"], + ), + top_10_pct_share=dict( + baseline=baseline["top_10_percent_share"], + reform=reform["top_10_percent_share"], + ), + top_1_pct_share=dict( + baseline=baseline["top_1_percent_share"], + reform=reform["top_1_percent_share"], + ), + ) + + +def poverty_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + """ + Compare the impact of a reform on poverty. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on poverty. + """ + baseline_poverty = MicroSeries( + baseline["person_in_poverty"], weights=baseline["person_weight"] + ) + baseline_deep_poverty = MicroSeries( + baseline["person_in_deep_poverty"], weights=baseline["person_weight"] + ) + reform_poverty = MicroSeries( + reform["person_in_poverty"], weights=baseline_poverty.weights + ) + reform_deep_poverty = MicroSeries( + reform["person_in_deep_poverty"], weights=baseline_poverty.weights + ) + age = MicroSeries(baseline["age"]) + + poverty = dict( + child=dict( + baseline=float(baseline_poverty[age < 18].mean()), + reform=float(reform_poverty[age < 18].mean()), + ), + adult=dict( + baseline=float(baseline_poverty[(age >= 18) & (age < 65)].mean()), + reform=float(reform_poverty[(age >= 18) & (age < 65)].mean()), + ), + senior=dict( + baseline=float(baseline_poverty[age >= 65].mean()), + reform=float(reform_poverty[age >= 65].mean()), + ), + all=dict( + baseline=float(baseline_poverty.mean()), + reform=float(reform_poverty.mean()), + ), ) - return EconomyComparison( - headlines=headlines, - fiscal=fiscal_comparison, - inequality=inequality_comparison, - distributional=decile_impacts, + deep_poverty = dict( + child=dict( + baseline=float(baseline_deep_poverty[age < 18].mean()), + reform=float(reform_deep_poverty[age < 18].mean()), + ), + adult=dict( + baseline=float( + baseline_deep_poverty[(age >= 18) & (age < 65)].mean() + ), + reform=float(reform_deep_poverty[(age >= 18) & (age < 65)].mean()), + ), + senior=dict( + baseline=float(baseline_deep_poverty[age >= 65].mean()), + reform=float(reform_deep_poverty[age >= 65].mean()), + ), + all=dict( + baseline=float(baseline_deep_poverty.mean()), + reform=float(reform_deep_poverty.mean()), + ), ) + + return dict( + poverty=poverty, + deep_poverty=deep_poverty, + ) + + +def intra_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + baseline_income = MicroSeries( + baseline["household_net_income"], weights=baseline["household_weight"] + ) + reform_income = MicroSeries( + reform["household_net_income"], weights=baseline_income.weights + ) + people = MicroSeries( + baseline["household_count_people"], weights=baseline_income.weights + ) + decile = MicroSeries(baseline["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 + + # Within each decile, calculate the percentage of people who: + # 1. Gained more than 5% of their income + # 2. Gained between 0% and 5% of their income + # 3. Had no change in income + # 3. Lost between 0% and 5% of their income + # 4. Lost more than 5% of their 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: bool = decile == i + in_group: bool = (income_change > lower) & (income_change <= upper) + in_both: bool = in_decile & in_group + + people_in_both: np.float64 = people[in_both].sum() + people_in_decile: np.float64 = people[in_decile].sum() + + # np.float64 does not raise ZeroDivisionError, instead returns NaN + if people_in_decile == 0 and people_in_both == 0: + people_in_proportion: float = 0.0 + else: + people_in_proportion: float = float( + people_in_both / people_in_decile + ) + + outcome_groups[label].append(people_in_proportion) + + all_outcomes[label] = sum(outcome_groups[label]) / 10 + return dict(deciles=outcome_groups, all=all_outcomes) + + +def intra_wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + baseline_income = MicroSeries( + baseline["household_net_income"], weights=baseline["household_weight"] + ) + reform_income = MicroSeries( + reform["household_net_income"], weights=baseline_income.weights + ) + people = MicroSeries( + baseline["household_count_people"], weights=baseline_income.weights + ) + decile = MicroSeries(baseline["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 + + # Within each decile, calculate the percentage of people who: + # 1. Gained more than 5% of their income + # 2. Gained between 0% and 5% of their income + # 3. Had no change in income + # 3. Lost between 0% and 5% of their income + # 4. Lost more than 5% of their 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: bool = decile == i + in_group: bool = (income_change > lower) & (income_change <= upper) + in_both: bool = in_decile & in_group + + people_in_both: np.float64 = people[in_both].sum() + people_in_decile: np.float64 = people[in_decile].sum() + + # np.float64 does not raise ZeroDivisionError, instead returns NaN + if people_in_decile == 0 and people_in_both == 0: + people_in_proportion = 0 + else: + people_in_proportion: float = float( + people_in_both / people_in_decile + ) + + outcome_groups[label].append(people_in_proportion) + + all_outcomes[label] = sum(outcome_groups[label]) / 10 + return dict(deciles=outcome_groups, all=all_outcomes) + + +def poverty_gender_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + """ + Compare the impact of a reform on poverty. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on poverty. + """ + if baseline["is_male"] is None: + return {} + baseline_poverty = MicroSeries( + baseline["person_in_poverty"], weights=baseline["person_weight"] + ) + baseline_deep_poverty = MicroSeries( + baseline["person_in_deep_poverty"], weights=baseline["person_weight"] + ) + reform_poverty = MicroSeries( + reform["person_in_poverty"], weights=baseline_poverty.weights + ) + reform_deep_poverty = MicroSeries( + reform["person_in_deep_poverty"], weights=baseline_poverty.weights + ) + is_male = MicroSeries(baseline["is_male"]) + + poverty = dict( + male=dict( + baseline=float(baseline_poverty[is_male].mean()), + reform=float(reform_poverty[is_male].mean()), + ), + female=dict( + baseline=float(baseline_poverty[~is_male].mean()), + reform=float(reform_poverty[~is_male].mean()), + ), + ) + + deep_poverty = dict( + male=dict( + baseline=float(baseline_deep_poverty[is_male].mean()), + reform=float(reform_deep_poverty[is_male].mean()), + ), + female=dict( + baseline=float(baseline_deep_poverty[~is_male].mean()), + reform=float(reform_deep_poverty[~is_male].mean()), + ), + ) + + return dict( + poverty=poverty, + deep_poverty=deep_poverty, + ) + + +def poverty_racial_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + """ + Compare the impact of a reform on poverty. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on poverty. + """ + if baseline["race"] is None: + return {} + baseline_poverty = MicroSeries( + baseline["person_in_poverty"], weights=baseline["person_weight"] + ) + reform_poverty = MicroSeries( + reform["person_in_poverty"], weights=baseline_poverty.weights + ) + race = MicroSeries( + baseline["race"] + ) # Can be WHITE, BLACK, HISPANIC, or OTHER. + + poverty = dict( + white=dict( + baseline=float(baseline_poverty[race == "WHITE"].mean()), + reform=float(reform_poverty[race == "WHITE"].mean()), + ), + black=dict( + baseline=float(baseline_poverty[race == "BLACK"].mean()), + reform=float(reform_poverty[race == "BLACK"].mean()), + ), + hispanic=dict( + baseline=float(baseline_poverty[race == "HISPANIC"].mean()), + reform=float(reform_poverty[race == "HISPANIC"].mean()), + ), + other=dict( + baseline=float(baseline_poverty[race == "OTHER"].mean()), + reform=float(reform_poverty[race == "OTHER"].mean()), + ), + ) + + return dict( + poverty=poverty, + ) + + +class UKConstituencyBreakdownByConstituency(BaseModel): + average_household_income_change: float + relative_household_income_change: float + x: int + y: int + + +class UKConstituencyBreakdown(BaseModel): + by_constituency: dict[str, UKConstituencyBreakdownByConstituency] + outcomes_by_region: dict[str, dict[str, int]] + + +def uk_constituency_breakdown( + baseline: SingleEconomy, reform: SingleEconomy, country_id: str +) -> UKConstituencyBreakdown | None: + if country_id != "uk": + return None + + output = { + "by_constituency": {}, + "outcomes_by_region": {}, + } + for region in ["uk", "england", "scotland", "wales", "northern_ireland"]: + output["outcomes_by_region"][region] = { + "Gain more than 5%": 0, + "Gain less than 5%": 0, + "No change": 0, + "Lose less than 5%": 0, + "Lose more than 5%": 0, + } + baseline_hnet = baseline["household_net_income"] + reform_hnet = reform["household_net_income"] + + constituency_weights_path = download_huggingface_dataset( + repo="policyengine/policyengine-uk-data", + repo_filename="parliamentary_constituency_weights.h5", + ) + with h5py.File(constituency_weights_path, "r") as f: + weights = f["2025"][ + ... + ] # {2025: array(650, 100180) where cell i, j is the weight of household record i in constituency j} + + constituency_names_path = download_huggingface_dataset( + repo="policyengine/policyengine-uk-data", + repo_filename="constituencies_2024.csv", + ) + constituency_names = pd.read_csv( + constituency_names_path + ) # columns code (constituency code), name (constituency name), x, y (geographic position) + + for i in range(len(constituency_names)): + name: str = constituency_names.iloc[i]["name"] + code: str = constituency_names.iloc[i]["code"] + weight: np.ndarray = weights[i] + baseline_income = MicroSeries(baseline_hnet, weights=weight) + reform_income = MicroSeries(reform_hnet, weights=weight) + average_household_income_change: float = ( + reform_income.sum() - baseline_income.sum() + ) / baseline_income.count() + percent_household_income_change: float = ( + reform_income.sum() / baseline_income.sum() - 1 + ) + output["by_constituency"][name] = { + "average_household_income_change": average_household_income_change, + "relative_household_income_change": percent_household_income_change, + "x": int(constituency_names.iloc[i]["x"]), # Geographic positions + "y": int(constituency_names.iloc[i]["y"]), + } + + regions = ["uk"] + if "E" in code: + regions.append("england") + elif "S" in code: + regions.append("scotland") + elif "W" in code: + regions.append("wales") + elif "N" in code: + regions.append("northern_ireland") + + if percent_household_income_change > 0.05: + bucket = "Gain more than 5%" + elif percent_household_income_change > 1e-3: + bucket = "Gain less than 5%" + elif percent_household_income_change > -1e-3: + bucket = "No change" + elif percent_household_income_change > -0.05: + bucket = "Lose less than 5%" + else: + bucket = "Lose more than 5%" + + for region_ in regions: + output["outcomes_by_region"][region_][bucket] += 1 + + return UKConstituencyBreakdown(**output) + + +def compare_economic_outputs( + baseline: SingleEconomy, reform: SingleEconomy, country_id: str = None +) -> dict: + """ + Compare the economic outputs of two economies. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The comparison of the two economies. + """ + if baseline.get("type") == "general": + budgetary_impact_data = budgetary_impact(baseline, reform) + detailed_budgetary_impact_data = detailed_budgetary_impact( + baseline, reform, country_id + ) + decile_impact_data = decile_impact(baseline, reform) + inequality_impact_data = inequality_impact(baseline, reform) + poverty_impact_data = poverty_impact(baseline, reform) + poverty_by_gender_data = poverty_gender_breakdown(baseline, reform) + poverty_by_race_data = poverty_racial_breakdown(baseline, reform) + intra_decile_impact_data = intra_decile_impact(baseline, reform) + labor_supply_response_data = labor_supply_response(baseline, reform) + constituency_impact_data: UKConstituencyBreakdown | None = ( + uk_constituency_breakdown(baseline, reform, country_id) + ) + if constituency_impact_data is not None: + constituency_impact_data = constituency_impact_data.model_dump() + try: + wealth_decile_impact_data = wealth_decile_impact(baseline, reform) + intra_wealth_decile_impact_data = intra_wealth_decile_impact( + baseline, reform + ) + except: + wealth_decile_impact_data = {} + intra_wealth_decile_impact_data = {} + + return dict( + budget=budgetary_impact_data, + detailed_budget=detailed_budgetary_impact_data, + decile=decile_impact_data, + inequality=inequality_impact_data, + poverty=poverty_impact_data, + poverty_by_gender=poverty_by_gender_data, + poverty_by_race=poverty_by_race_data, + intra_decile=intra_decile_impact_data, + wealth_decile=wealth_decile_impact_data, + intra_wealth_decile=intra_wealth_decile_impact_data, + labor_supply_response=labor_supply_response_data, + constituency_impact=constituency_impact_data, + ) + elif baseline.get("type") == "cliff": + return dict( + baseline=dict( + cliff_gap=baseline["cliff_gap"], + cliff_share=baseline["cliff_share"], + ), + reform=dict( + cliff_gap=reform["cliff_gap"], + cliff_share=reform["cliff_share"], + ), + ) diff --git a/policyengine/outputs/macro/comparison/legacy_api/calculate_economy_comparison.py b/policyengine/outputs/macro/comparison/legacy_api/calculate_economy_comparison.py new file mode 100644 index 00000000..cd54ff57 --- /dev/null +++ b/policyengine/outputs/macro/comparison/legacy_api/calculate_economy_comparison.py @@ -0,0 +1,696 @@ +from microdf import MicroSeries +import numpy as np +from policyengine_core.tools.hugging_face import download_huggingface_dataset +import pandas as pd +import h5py +from pydantic import BaseModel + + +def budgetary_impact(baseline: dict, reform: dict) -> dict: + tax_revenue_impact = reform["total_tax"] - baseline["total_tax"] + state_tax_revenue_impact = ( + reform["total_state_tax"] - baseline["total_state_tax"] + ) + benefit_spending_impact = ( + reform["total_benefits"] - baseline["total_benefits"] + ) + budgetary_impact = tax_revenue_impact - benefit_spending_impact + return dict( + budgetary_impact=budgetary_impact, + tax_revenue_impact=tax_revenue_impact, + state_tax_revenue_impact=state_tax_revenue_impact, + benefit_spending_impact=benefit_spending_impact, + households=sum(baseline["household_weight"]), + baseline_net_income=baseline["total_net_income"], + ) + + +def labor_supply_response(baseline: dict, reform: dict) -> dict: + substitution_lsr = ( + reform["substitution_lsr"] - baseline["substitution_lsr"] + ) + income_lsr = reform["income_lsr"] - baseline["income_lsr"] + total_change = substitution_lsr + income_lsr + revenue_change = ( + reform["budgetary_impact_lsr"] - baseline["budgetary_impact_lsr"] + ) + + substitution_lsr_hh = np.array(reform["substitution_lsr_hh"]) - np.array( + baseline["substitution_lsr_hh"] + ) + income_lsr_hh = np.array(reform["income_lsr_hh"]) - np.array( + baseline["income_lsr_hh"] + ) + decile = np.array(baseline["household_income_decile"]) + household_weight = baseline["household_weight"] + + total_lsr_hh = substitution_lsr_hh + income_lsr_hh + + emp_income = MicroSeries( + baseline["employment_income_hh"], weights=household_weight + ) + self_emp_income = MicroSeries( + baseline["self_employment_income_hh"], weights=household_weight + ) + earnings = emp_income + self_emp_income + original_earnings = earnings - total_lsr_hh + substitution_lsr_hh = MicroSeries( + substitution_lsr_hh, weights=household_weight + ) + income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) + + decile_avg = dict( + income=income_lsr_hh.groupby(decile).mean().to_dict(), + substitution=substitution_lsr_hh.groupby(decile).mean().to_dict(), + ) + decile_rel = dict( + income=( + income_lsr_hh.groupby(decile).sum() + / original_earnings.groupby(decile).sum() + ).to_dict(), + substitution=( + substitution_lsr_hh.groupby(decile).sum() + / original_earnings.groupby(decile).sum() + ).to_dict(), + ) + + relative_lsr = dict( + income=(income_lsr_hh.sum() / original_earnings.sum()), + substitution=(substitution_lsr_hh.sum() / original_earnings.sum()), + ) + + decile_rel["income"] = { + int(k): v for k, v in decile_rel["income"].items() if k > 0 + } + decile_rel["substitution"] = { + int(k): v for k, v in decile_rel["substitution"].items() if k > 0 + } + + hours = dict( + baseline=baseline["weekly_hours"], + reform=reform["weekly_hours"], + change=reform["weekly_hours"] - baseline["weekly_hours"], + income_effect=reform["weekly_hours_income_effect"] + - baseline["weekly_hours_income_effect"], + substitution_effect=reform["weekly_hours_substitution_effect"] + - baseline["weekly_hours_substitution_effect"], + ) + + return dict( + substitution_lsr=substitution_lsr, + income_lsr=income_lsr, + relative_lsr=relative_lsr, + total_change=total_change, + revenue_change=revenue_change, + decile=dict( + average=decile_avg, + relative=decile_rel, + ), + hours=hours, + ) + + +def detailed_budgetary_impact( + baseline: dict, reform: dict, country_id: str +) -> dict: + result = {} + if country_id == "uk": + for program in baseline["programs"]: + # baseline[programs][program] = total budgetary impact of program + result[program] = dict( + baseline=baseline["programs"][program], + reform=reform["programs"][program], + difference=reform["programs"][program] + - baseline["programs"][program], + ) + return result + + +def decile_impact(baseline: dict, reform: dict) -> dict: + """ + Compare the impact of a reform on the deciles of the population. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on the deciles of the population. + """ + baseline_income = MicroSeries( + baseline["household_net_income"], weights=baseline["household_weight"] + ) + reform_income = MicroSeries( + reform["household_net_income"], weights=baseline_income.weights + ) + + # Filter out negative decile values + decile = MicroSeries(baseline["household_income_decile"]) + baseline_income_filtered = baseline_income[decile >= 0] + reform_income_filtered = reform_income[decile >= 0] + + income_change = reform_income_filtered - baseline_income_filtered + rel_income_change_by_decile = ( + income_change.groupby(decile).sum() + / baseline_income_filtered.groupby(decile).sum() + ) + + avg_income_change_by_decile = ( + income_change.groupby(decile).sum() + / baseline_income_filtered.groupby(decile).count() + ) + rel_decile_dict = rel_income_change_by_decile.to_dict() + avg_decile_dict = avg_income_change_by_decile.to_dict() + result = dict( + relative={int(k): v for k, v in rel_decile_dict.items()}, + average={int(k): v for k, v in avg_decile_dict.items()}, + ) + return result + + +def wealth_decile_impact(baseline: dict, reform: dict) -> dict: + """ + Compare the impact of a reform on the deciles of the population. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on the deciles of the population. + """ + baseline_income = MicroSeries( + baseline["household_net_income"], weights=baseline["household_weight"] + ) + reform_income = MicroSeries( + reform["household_net_income"], weights=baseline_income.weights + ) + + # Filter out negative decile values + decile = MicroSeries(baseline["household_wealth_decile"]) + baseline_income_filtered = baseline_income[decile >= 0] + reform_income_filtered = reform_income[decile >= 0] + + income_change = reform_income_filtered - baseline_income_filtered + rel_income_change_by_decile = ( + income_change.groupby(decile).sum() + / baseline_income_filtered.groupby(decile).sum() + ) + avg_income_change_by_decile = ( + income_change.groupby(decile).sum() + / baseline_income_filtered.groupby(decile).count() + ) + rel_decile_dict = rel_income_change_by_decile.to_dict() + avg_decile_dict = avg_income_change_by_decile.to_dict() + result = dict( + relative={int(k): v for k, v in rel_decile_dict.items()}, + average={int(k): v for k, v in avg_decile_dict.items()}, + ) + return result + + +def inequality_impact(baseline: dict, reform: dict) -> dict: + """ + Compare the impact of a reform on inequality. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on inequality. + """ + + return dict( + gini=dict( + baseline=baseline["gini"], + reform=reform["gini"], + ), + top_10_pct_share=dict( + baseline=baseline["top_10_percent_share"], + reform=reform["top_10_percent_share"], + ), + top_1_pct_share=dict( + baseline=baseline["top_1_percent_share"], + reform=reform["top_1_percent_share"], + ), + ) + + +def poverty_impact(baseline: dict, reform: dict) -> dict: + """ + Compare the impact of a reform on poverty. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on poverty. + """ + baseline_poverty = MicroSeries( + baseline["person_in_poverty"], weights=baseline["person_weight"] + ) + baseline_deep_poverty = MicroSeries( + baseline["person_in_deep_poverty"], weights=baseline["person_weight"] + ) + reform_poverty = MicroSeries( + reform["person_in_poverty"], weights=baseline_poverty.weights + ) + reform_deep_poverty = MicroSeries( + reform["person_in_deep_poverty"], weights=baseline_poverty.weights + ) + age = MicroSeries(baseline["age"]) + + poverty = dict( + child=dict( + baseline=float(baseline_poverty[age < 18].mean()), + reform=float(reform_poverty[age < 18].mean()), + ), + adult=dict( + baseline=float(baseline_poverty[(age >= 18) & (age < 65)].mean()), + reform=float(reform_poverty[(age >= 18) & (age < 65)].mean()), + ), + senior=dict( + baseline=float(baseline_poverty[age >= 65].mean()), + reform=float(reform_poverty[age >= 65].mean()), + ), + all=dict( + baseline=float(baseline_poverty.mean()), + reform=float(reform_poverty.mean()), + ), + ) + + deep_poverty = dict( + child=dict( + baseline=float(baseline_deep_poverty[age < 18].mean()), + reform=float(reform_deep_poverty[age < 18].mean()), + ), + adult=dict( + baseline=float( + baseline_deep_poverty[(age >= 18) & (age < 65)].mean() + ), + reform=float(reform_deep_poverty[(age >= 18) & (age < 65)].mean()), + ), + senior=dict( + baseline=float(baseline_deep_poverty[age >= 65].mean()), + reform=float(reform_deep_poverty[age >= 65].mean()), + ), + all=dict( + baseline=float(baseline_deep_poverty.mean()), + reform=float(reform_deep_poverty.mean()), + ), + ) + + return dict( + poverty=poverty, + deep_poverty=deep_poverty, + ) + + +def intra_decile_impact(baseline: dict, reform: dict) -> dict: + baseline_income = MicroSeries( + baseline["household_net_income"], weights=baseline["household_weight"] + ) + reform_income = MicroSeries( + reform["household_net_income"], weights=baseline_income.weights + ) + people = MicroSeries( + baseline["household_count_people"], weights=baseline_income.weights + ) + decile = MicroSeries(baseline["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 + + # Within each decile, calculate the percentage of people who: + # 1. Gained more than 5% of their income + # 2. Gained between 0% and 5% of their income + # 3. Had no change in income + # 3. Lost between 0% and 5% of their income + # 4. Lost more than 5% of their 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: bool = decile == i + in_group: bool = (income_change > lower) & (income_change <= upper) + in_both: bool = in_decile & in_group + + people_in_both: np.float64 = people[in_both].sum() + people_in_decile: np.float64 = people[in_decile].sum() + + # np.float64 does not raise ZeroDivisionError, instead returns NaN + if people_in_decile == 0 and people_in_both == 0: + people_in_proportion: float = 0.0 + else: + people_in_proportion: float = float( + people_in_both / people_in_decile + ) + + outcome_groups[label].append(people_in_proportion) + + all_outcomes[label] = sum(outcome_groups[label]) / 10 + return dict(deciles=outcome_groups, all=all_outcomes) + + +def intra_wealth_decile_impact(baseline: dict, reform: dict) -> dict: + baseline_income = MicroSeries( + baseline["household_net_income"], weights=baseline["household_weight"] + ) + reform_income = MicroSeries( + reform["household_net_income"], weights=baseline_income.weights + ) + people = MicroSeries( + baseline["household_count_people"], weights=baseline_income.weights + ) + decile = MicroSeries(baseline["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 + + # Within each decile, calculate the percentage of people who: + # 1. Gained more than 5% of their income + # 2. Gained between 0% and 5% of their income + # 3. Had no change in income + # 3. Lost between 0% and 5% of their income + # 4. Lost more than 5% of their 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: bool = decile == i + in_group: bool = (income_change > lower) & (income_change <= upper) + in_both: bool = in_decile & in_group + + people_in_both: np.float64 = people[in_both].sum() + people_in_decile: np.float64 = people[in_decile].sum() + + # np.float64 does not raise ZeroDivisionError, instead returns NaN + if people_in_decile == 0 and people_in_both == 0: + people_in_proportion = 0 + else: + people_in_proportion: float = float( + people_in_both / people_in_decile + ) + + outcome_groups[label].append(people_in_proportion) + + all_outcomes[label] = sum(outcome_groups[label]) / 10 + return dict(deciles=outcome_groups, all=all_outcomes) + + +def poverty_gender_breakdown(baseline: dict, reform: dict) -> dict: + """ + Compare the impact of a reform on poverty. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on poverty. + """ + if baseline["is_male"] is None: + return {} + baseline_poverty = MicroSeries( + baseline["person_in_poverty"], weights=baseline["person_weight"] + ) + baseline_deep_poverty = MicroSeries( + baseline["person_in_deep_poverty"], weights=baseline["person_weight"] + ) + reform_poverty = MicroSeries( + reform["person_in_poverty"], weights=baseline_poverty.weights + ) + reform_deep_poverty = MicroSeries( + reform["person_in_deep_poverty"], weights=baseline_poverty.weights + ) + is_male = MicroSeries(baseline["is_male"]) + + poverty = dict( + male=dict( + baseline=float(baseline_poverty[is_male].mean()), + reform=float(reform_poverty[is_male].mean()), + ), + female=dict( + baseline=float(baseline_poverty[~is_male].mean()), + reform=float(reform_poverty[~is_male].mean()), + ), + ) + + deep_poverty = dict( + male=dict( + baseline=float(baseline_deep_poverty[is_male].mean()), + reform=float(reform_deep_poverty[is_male].mean()), + ), + female=dict( + baseline=float(baseline_deep_poverty[~is_male].mean()), + reform=float(reform_deep_poverty[~is_male].mean()), + ), + ) + + return dict( + poverty=poverty, + deep_poverty=deep_poverty, + ) + + +def poverty_racial_breakdown(baseline: dict, reform: dict) -> dict: + """ + Compare the impact of a reform on poverty. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The impact of the reform on poverty. + """ + if baseline["race"] is None: + return {} + baseline_poverty = MicroSeries( + baseline["person_in_poverty"], weights=baseline["person_weight"] + ) + reform_poverty = MicroSeries( + reform["person_in_poverty"], weights=baseline_poverty.weights + ) + race = MicroSeries( + baseline["race"] + ) # Can be WHITE, BLACK, HISPANIC, or OTHER. + + poverty = dict( + white=dict( + baseline=float(baseline_poverty[race == "WHITE"].mean()), + reform=float(reform_poverty[race == "WHITE"].mean()), + ), + black=dict( + baseline=float(baseline_poverty[race == "BLACK"].mean()), + reform=float(reform_poverty[race == "BLACK"].mean()), + ), + hispanic=dict( + baseline=float(baseline_poverty[race == "HISPANIC"].mean()), + reform=float(reform_poverty[race == "HISPANIC"].mean()), + ), + other=dict( + baseline=float(baseline_poverty[race == "OTHER"].mean()), + reform=float(reform_poverty[race == "OTHER"].mean()), + ), + ) + + return dict( + poverty=poverty, + ) + + +class UKConstituencyBreakdownByConstituency(BaseModel): + average_household_income_change: float + relative_household_income_change: float + x: int + y: int + + +class UKConstituencyBreakdown(BaseModel): + by_constituency: dict[str, UKConstituencyBreakdownByConstituency] + outcomes_by_region: dict[str, dict[str, int]] + + +def uk_constituency_breakdown( + baseline: dict, reform: dict, country_id: str +) -> UKConstituencyBreakdown | None: + if country_id != "uk": + return None + + output = { + "by_constituency": {}, + "outcomes_by_region": {}, + } + for region in ["uk", "england", "scotland", "wales", "northern_ireland"]: + output["outcomes_by_region"][region] = { + "Gain more than 5%": 0, + "Gain less than 5%": 0, + "No change": 0, + "Lose less than 5%": 0, + "Lose more than 5%": 0, + } + baseline_hnet = baseline["household_net_income"] + reform_hnet = reform["household_net_income"] + + constituency_weights_path = download_huggingface_dataset( + repo="policyengine/policyengine-uk-data", + repo_filename="parliamentary_constituency_weights.h5", + ) + with h5py.File(constituency_weights_path, "r") as f: + weights = f["2025"][ + ... + ] # {2025: array(650, 100180) where cell i, j is the weight of household record i in constituency j} + + constituency_names_path = download_huggingface_dataset( + repo="policyengine/policyengine-uk-data", + repo_filename="constituencies_2024.csv", + ) + constituency_names = pd.read_csv( + constituency_names_path + ) # columns code (constituency code), name (constituency name), x, y (geographic position) + + for i in range(len(constituency_names)): + name: str = constituency_names.iloc[i]["name"] + code: str = constituency_names.iloc[i]["code"] + weight: np.ndarray = weights[i] + baseline_income = MicroSeries(baseline_hnet, weights=weight) + reform_income = MicroSeries(reform_hnet, weights=weight) + average_household_income_change: float = ( + reform_income.sum() - baseline_income.sum() + ) / baseline_income.count() + percent_household_income_change: float = ( + reform_income.sum() / baseline_income.sum() - 1 + ) + output["by_constituency"][name] = { + "average_household_income_change": average_household_income_change, + "relative_household_income_change": percent_household_income_change, + "x": int(constituency_names.iloc[i]["x"]), # Geographic positions + "y": int(constituency_names.iloc[i]["y"]), + } + + regions = ["uk"] + if "E" in code: + regions.append("england") + elif "S" in code: + regions.append("scotland") + elif "W" in code: + regions.append("wales") + elif "N" in code: + regions.append("northern_ireland") + + if percent_household_income_change > 0.05: + bucket = "Gain more than 5%" + elif percent_household_income_change > 1e-3: + bucket = "Gain less than 5%" + elif percent_household_income_change > -1e-3: + bucket = "No change" + elif percent_household_income_change > -0.05: + bucket = "Lose less than 5%" + else: + bucket = "Lose more than 5%" + + for region_ in regions: + output["outcomes_by_region"][region_][bucket] += 1 + + return UKConstituencyBreakdown(**output) + + +def compare_economic_outputs( + baseline: dict, reform: dict, country_id: str = None +) -> dict: + """ + Compare the economic outputs of two economies. + + Args: + baseline (dict): The baseline economy. + reform (dict): The reform economy. + + Returns: + dict: The comparison of the two economies. + """ + if baseline.get("type") == "general": + budgetary_impact_data = budgetary_impact(baseline, reform) + detailed_budgetary_impact_data = detailed_budgetary_impact( + baseline, reform, country_id + ) + decile_impact_data = decile_impact(baseline, reform) + inequality_impact_data = inequality_impact(baseline, reform) + poverty_impact_data = poverty_impact(baseline, reform) + poverty_by_gender_data = poverty_gender_breakdown(baseline, reform) + poverty_by_race_data = poverty_racial_breakdown(baseline, reform) + intra_decile_impact_data = intra_decile_impact(baseline, reform) + labor_supply_response_data = labor_supply_response(baseline, reform) + constituency_impact_data: UKConstituencyBreakdown | None = ( + uk_constituency_breakdown(baseline, reform, country_id) + ) + if constituency_impact_data is not None: + constituency_impact_data = constituency_impact_data.model_dump() + try: + wealth_decile_impact_data = wealth_decile_impact(baseline, reform) + intra_wealth_decile_impact_data = intra_wealth_decile_impact( + baseline, reform + ) + except: + wealth_decile_impact_data = {} + intra_wealth_decile_impact_data = {} + + return dict( + budget=budgetary_impact_data, + detailed_budget=detailed_budgetary_impact_data, + decile=decile_impact_data, + inequality=inequality_impact_data, + poverty=poverty_impact_data, + poverty_by_gender=poverty_by_gender_data, + poverty_by_race=poverty_by_race_data, + intra_decile=intra_decile_impact_data, + wealth_decile=wealth_decile_impact_data, + intra_wealth_decile=intra_wealth_decile_impact_data, + labor_supply_response=labor_supply_response_data, + constituency_impact=constituency_impact_data, + ) + elif baseline.get("type") == "cliff": + return dict( + baseline=dict( + cliff_gap=baseline["cliff_gap"], + cliff_share=baseline["cliff_share"], + ), + reform=dict( + cliff_gap=reform["cliff_gap"], + cliff_share=reform["cliff_share"], + ), + ) diff --git a/policyengine/outputs/macro/single/calculate_single_economy.py b/policyengine/outputs/macro/single/calculate_single_economy.py index 206fd489..0a8d37ec 100644 --- a/policyengine/outputs/macro/single/calculate_single_economy.py +++ b/policyengine/outputs/macro/single/calculate_single_economy.py @@ -5,35 +5,405 @@ from policyengine import Simulation from pydantic import BaseModel - -from .budget import FiscalSummary, _calculate_government_balance -from .inequality import InequalitySummary, _calculate_inequality from typing import List +from policyengine_core.simulations import Microsimulation +from typing import Dict +from dataclasses import dataclass + class SingleEconomy(BaseModel): - fiscal: FiscalSummary - """Government budgets and other top-level fiscal statistics.""" - inequality: InequalitySummary - """Inequality statistics for the household sector.""" + total_net_income: float + employment_income_hh: List[float] + self_employment_income_hh: List[float] + total_tax: float + total_state_tax: float + total_benefits: float + household_net_income: List[float] + equiv_household_net_income: List[float] + household_income_decile: List[int] + household_market_income: List[float] + household_wealth_decile: List[int] + household_wealth: List[float] + in_poverty: List[bool] + person_in_poverty: List[bool] + person_in_deep_poverty: List[bool] + poverty_gap: float + deep_poverty_gap: float + person_weight: List[float] + household_weight: List[float] + household_count_people: List[int] + gini: float + top_10_percent_share: float + top_1_percent_share: float + is_male: List[bool] + race: List[str] | None + age: List[int] + substitution_lsr: float + income_lsr: float + budgetary_impact_lsr: float + income_lsr_hh: List[float] + substitution_lsr_hh: List[float] + weekly_hours: float | None + weekly_hours_income_effect: float | None + weekly_hours_substitution_effect: float | None + type: str + programs: Dict[str, float] | None + + +@dataclass +class UKProgram: + name: str + is_positive: bool -def calculate_single_economy( - simulation: Simulation, -) -> SingleEconomy: - """Calculate economy statistics for a single economic scenario.""" - options = simulation.options - if simulation.is_comparison: - raise ValueError( - "This function is for single economy simulations only." + +class UKPrograms: + PROGRAMS = [ + UKProgram("income_tax", True), + UKProgram("national_insurance", True), + UKProgram("vat", True), + UKProgram("council_tax", True), + UKProgram("fuel_duty", True), + UKProgram("tax_credits", False), + UKProgram("universal_credit", False), + UKProgram("child_benefit", False), + UKProgram("state_pension", False), + UKProgram("pension_credit", False), + UKProgram("ni_employer", True), + ] + +class GeneralEconomyTask: + def __init__(self, simulation: Microsimulation, country_id: str): + self.simulation = simulation + self.country_id = country_id + self.household_count_people = self.simulation.calculate( + "household_count_people" ) - fiscal = _calculate_government_balance( - simulation.baseline_simulation, options - ) - inequality = _calculate_inequality(simulation.baseline_simulation) + def calculate_tax_and_spending(self): + if self.country_id == "uk": + total_tax = self.simulation.calculate("gov_tax").sum() + total_spending = self.simulation.calculate("gov_spending").sum() + else: + total_tax = self.simulation.calculate("household_tax").sum() + total_spending = self.simulation.calculate( + "household_benefits" + ).sum() + return total_tax, total_spending + + def calculate_inequality_metrics(self): + personal_hh_equiv_income = self._get_weighted_household_income() + + try: + gini = personal_hh_equiv_income.gini() + except Exception as e: + print( + "WARNING: Gini index calculations resulted in an error: returning no change, but this is inaccurate." + ) + print("Error: ", e) + gini = 0.4 + + in_top_10_pct = personal_hh_equiv_income.decile_rank() == 10 + in_top_1_pct = personal_hh_equiv_income.percentile_rank() == 100 + + personal_hh_equiv_income.weights /= self.household_count_people + + top_10_share = ( + personal_hh_equiv_income[in_top_10_pct].sum() + / personal_hh_equiv_income.sum() + ) + top_1_share = ( + personal_hh_equiv_income[in_top_1_pct].sum() + / personal_hh_equiv_income.sum() + ) + + return gini, top_10_share, top_1_share + + def _get_weighted_household_income(self): + income = self.simulation.calculate("equiv_household_net_income") + income[income < 0] = 0 + income.weights *= self.household_count_people + return income + + def calculate_income_breakdown_metrics(self): + total_net_income = self.simulation.calculate( + "household_net_income" + ).sum() + employment_income_hh = ( + self.simulation.calculate("employment_income", map_to="household") + .astype(float) + .tolist() + ) + self_employment_income_hh = ( + self.simulation.calculate( + "self_employment_income", map_to="household" + ) + .astype(float) + .tolist() + ) + + return ( + total_net_income, + employment_income_hh, + self_employment_income_hh, + ) + + def calculate_household_income_metrics(self): + household_net_income = ( + self.simulation.calculate("household_net_income") + .astype(float) + .tolist() + ) + equiv_household_net_income = ( + self.simulation.calculate("equiv_household_net_income") + .astype(float) + .tolist() + ) + household_income_decile = ( + self.simulation.calculate("household_income_decile") + .astype(int) + .tolist() + ) + household_market_income = ( + self.simulation.calculate("household_market_income") + .astype(float) + .tolist() + ) + + return ( + household_net_income, + equiv_household_net_income, + household_income_decile, + household_market_income, + ) + + def calculate_wealth_metrics(self): + try: + wealth = self.simulation.calculate("total_wealth") + wealth.weights *= self.household_count_people + wealth_decile = ( + wealth.decile_rank().clip(1, 10).astype(int).tolist() + ) + wealth = wealth.astype(float).tolist() + except Exception as e: + wealth = None + wealth_decile = None + return wealth, wealth_decile + + def calculate_demographic_metrics(self): + try: + is_male = ( + self.simulation.calculate("is_male").astype(bool).tolist() + ) + except Exception: + is_male = None - return SingleEconomy( - fiscal=fiscal, - inequality=inequality, + try: + race = self.simulation.calculate("race").astype(str).tolist() + except Exception: + race = None + + age = self.simulation.calculate("age").astype(int).tolist() + + return is_male, race, age + + def calculate_poverty_metrics(self): + in_poverty = ( + self.simulation.calculate("in_poverty").astype(bool).tolist() + ) + person_in_poverty = ( + self.simulation.calculate("in_poverty", map_to="person") + .astype(bool) + .tolist() + ) + person_in_deep_poverty = ( + self.simulation.calculate("in_deep_poverty", map_to="person") + .astype(bool) + .tolist() + ) + poverty_gap = self.simulation.calculate("poverty_gap").sum() + deep_poverty_gap = self.simulation.calculate("deep_poverty_gap").sum() + return ( + in_poverty, + person_in_poverty, + person_in_deep_poverty, + poverty_gap, + deep_poverty_gap, + ) + + def calculate_weights(self): + person_weight = ( + self.simulation.calculate("person_weight").astype(float).tolist() + ) + household_weight = ( + self.simulation.calculate("household_weight") + .astype(float) + .tolist() + ) + + return person_weight, household_weight + + def calculate_labor_supply_responses(self): + result = { + "substitution_lsr": 0, + "income_lsr": 0, + "budgetary_impact_lsr": 0, + "income_lsr_hh": (self.household_count_people * 0) + .astype(float) + .tolist(), + "substitution_lsr_hh": (self.household_count_people * 0) + .astype(float) + .tolist(), + } + + if not self._has_behavioral_response(): + return result + + result.update( + { + "substitution_lsr": self.simulation.calculate( + "substitution_elasticity_lsr" + ).sum(), + "income_lsr": self.simulation.calculate( + "income_elasticity_lsr" + ).sum(), + "income_lsr_hh": self.simulation.calculate( + "income_elasticity_lsr", map_to="household" + ) + .astype(float) + .tolist(), + "substitution_lsr_hh": self.simulation.calculate( + "substitution_elasticity_lsr", map_to="household" + ) + .astype(float) + .tolist(), + } + ) + + return result + + def _has_behavioral_response(self) -> bool: + return ( + "employment_income_behavioral_response" + in self.simulation.tax_benefit_system.variables + and any( + self.simulation.calculate( + "employment_income_behavioral_response" + ) + != 0 + ) + ) + + def calculate_lsr_working_hours(self): + if self.country_id != "us": + return { + "weekly_hours": 0, + "weekly_hours_income_effect": 0, + "weekly_hours_substitution_effect": 0, + } + + return { + "weekly_hours": self.simulation.calculate( + "weekly_hours_worked" + ).sum(), + "weekly_hours_income_effect": self.simulation.calculate( + "weekly_hours_worked_behavioural_response_income_elasticity" + ).sum(), + "weekly_hours_substitution_effect": self.simulation.calculate( + "weekly_hours_worked_behavioural_response_substitution_elasticity" + ).sum(), + } + + def calculate_uk_programs(self) -> Dict[str, float]: + if self.country_id != "uk": + return {} + + return { + program.name: self.simulation.calculate( + program.name, map_to="household" + ).sum() + * (1 if program.is_positive else -1) + for program in UKPrograms.PROGRAMS + } + + +def calculate_single_economy(simulation: Simulation, reform: bool = False) -> Dict: + task_manager = GeneralEconomyTask(simulation.baseline_simulation if not reform else simulation.reform_simulation, simulation.options.country) + country_id = simulation.options.country + + total_tax, total_spending = task_manager.calculate_tax_and_spending() + gini, top_10_share, top_1_share = ( + task_manager.calculate_inequality_metrics() ) + wealth, wealth_decile = task_manager.calculate_wealth_metrics() + is_male, race, age = task_manager.calculate_demographic_metrics() + labor_supply_responses = task_manager.calculate_labor_supply_responses() + lsr_working_hours = task_manager.calculate_lsr_working_hours() + ( + in_poverty, + person_in_poverty, + person_in_deep_poverty, + poverty_gap, + deep_poverty_gap, + ) = task_manager.calculate_poverty_metrics() + total_net_income, employment_income_hh, self_employment_income_hh = ( + task_manager.calculate_income_breakdown_metrics() + ) + ( + household_net_income, + equiv_household_net_income, + household_income_decile, + household_market_income, + ) = task_manager.calculate_household_income_metrics() + person_weight, household_weight = task_manager.calculate_weights() + + if country_id == "uk": + uk_programs = task_manager.calculate_uk_programs() + else: + uk_programs = None + + total_state_tax = 0 + + if country_id == "us": + try: + total_state_tax = simulation.calculate( + "household_state_income_tax" + ).sum() + except: + total_state_tax = 0 + + return SingleEconomy(**{ + "total_net_income": total_net_income, + "employment_income_hh": employment_income_hh, + "self_employment_income_hh": self_employment_income_hh, + "total_tax": total_tax, + "total_state_tax": total_state_tax, + "total_benefits": total_spending, + "household_net_income": household_net_income, + "equiv_household_net_income": equiv_household_net_income, + "household_income_decile": household_income_decile, + "household_market_income": household_market_income, + "household_wealth_decile": wealth_decile, + "household_wealth": wealth, + "in_poverty": in_poverty, + "person_in_poverty": person_in_poverty, + "person_in_deep_poverty": person_in_deep_poverty, + "poverty_gap": poverty_gap, + "deep_poverty_gap": deep_poverty_gap, + "person_weight": person_weight, + "household_weight": household_weight, + "household_count_people": task_manager.household_count_people.astype( + int + ).tolist(), + "gini": float(gini), + "top_10_percent_share": float(top_10_share), + "top_1_percent_share": float(top_1_share), + "is_male": is_male, + "race": race, + "age": age, + **labor_supply_responses, + **lsr_working_hours, + "type": "general", + "programs": uk_programs, + }) From 8e3ffe5851d3d08646a036867c8a99cbb5179a5e Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Fri, 14 Mar 2025 11:19:59 +0000 Subject: [PATCH 2/7] Add rest of APIv1 compatible code --- .../calculate_economy_comparison.py | 356 +++++---- .../calculate_economy_comparison.py | 696 ------------------ .../macro/single/calculate_single_economy.py | 4 +- 3 files changed, 214 insertions(+), 842 deletions(-) delete mode 100644 policyengine/outputs/macro/comparison/legacy_api/calculate_economy_comparison.py diff --git a/policyengine/outputs/macro/comparison/calculate_economy_comparison.py b/policyengine/outputs/macro/comparison/calculate_economy_comparison.py index 8e51e87f..0c4f37e7 100644 --- a/policyengine/outputs/macro/comparison/calculate_economy_comparison.py +++ b/policyengine/outputs/macro/comparison/calculate_economy_comparison.py @@ -7,49 +7,25 @@ from pydantic import BaseModel from policyengine import Simulation from policyengine.outputs.macro.single.calculate_single_economy import SingleEconomy +from typing import List, Dict -class EconomyComparison(BaseModel): - budget: None - detailed_budget: None - decile: None - inequality: None - poverty: None - poverty_by_gender: None - poverty_by_race: None - intra_decile: None - wealth_decile: None - intra_wealth_decile: None - labor_supply_response: None - constituency_impact: None - - -def calculate_economy_comparison( - simulation: Simulation, -) -> EconomyComparison: - """Calculate comparison statistics between two economic scenarios.""" - if not simulation.is_comparison: - raise ValueError("Simulation must be a comparison simulation.") - - baseline = simulation.baseline_simulation - reform = simulation.reform_simulation - options = simulation.options class BudgetaryImpact(BaseModel): budgetary_impact: float tax_revenue_impact: float state_tax_revenue_impact: float benefit_spending_impact: float - households: int + households: float baseline_net_income: float def budgetary_impact(baseline: SingleEconomy, reform: SingleEconomy) -> BudgetaryImpact: - tax_revenue_impact = reform["total_tax"] - baseline["total_tax"] + tax_revenue_impact = reform.total_tax - baseline.total_tax state_tax_revenue_impact = ( - reform["total_state_tax"] - baseline["total_state_tax"] + reform.total_state_tax - baseline.total_state_tax ) benefit_spending_impact = ( - reform["total_benefits"] - baseline["total_benefits"] + reform.total_benefits - baseline.total_benefits ) budgetary_impact = tax_revenue_impact - benefit_spending_impact return BudgetaryImpact( @@ -57,37 +33,55 @@ def budgetary_impact(baseline: SingleEconomy, reform: SingleEconomy) -> Budgetar tax_revenue_impact=tax_revenue_impact, state_tax_revenue_impact=state_tax_revenue_impact, benefit_spending_impact=benefit_spending_impact, - households=sum(baseline["household_weight"]), - baseline_net_income=baseline["total_net_income"], + households=sum(baseline.household_weight), + baseline_net_income=baseline.total_net_income, ) +DecileValues = Dict[int, float] + +class HoursResponse(BaseModel): + baseline: float + reform: float + change: float + income_effect: float + substitution_effect: float + +class LaborSupplyResponse(BaseModel): + substitution_lsr: float + income_lsr: float + relative_lsr: dict + total_change: float + revenue_change: float + decile: Dict[str, Dict[str, DecileValues]] + hours: HoursResponse + -def labor_supply_response(baseline: SingleEconomy, reform: SingleEconomy) -> dict: +def labor_supply_response(baseline: SingleEconomy, reform: SingleEconomy) -> LaborSupplyResponse: substitution_lsr = ( - reform["substitution_lsr"] - baseline["substitution_lsr"] + reform.substitution_lsr - baseline.substitution_lsr ) - income_lsr = reform["income_lsr"] - baseline["income_lsr"] + income_lsr = reform.income_lsr - baseline.income_lsr total_change = substitution_lsr + income_lsr revenue_change = ( - reform["budgetary_impact_lsr"] - baseline["budgetary_impact_lsr"] + reform.budgetary_impact_lsr - baseline.budgetary_impact_lsr ) - substitution_lsr_hh = np.array(reform["substitution_lsr_hh"]) - np.array( - baseline["substitution_lsr_hh"] + substitution_lsr_hh = np.array(reform.substitution_lsr_hh) - np.array( + baseline.substitution_lsr_hh ) - income_lsr_hh = np.array(reform["income_lsr_hh"]) - np.array( - baseline["income_lsr_hh"] + income_lsr_hh = np.array(reform.income_lsr_hh) - np.array( + baseline.income_lsr_hh ) - decile = np.array(baseline["household_income_decile"]) - household_weight = baseline["household_weight"] + decile = np.array(baseline.household_income_decile) + household_weight = baseline.household_weight total_lsr_hh = substitution_lsr_hh + income_lsr_hh emp_income = MicroSeries( - baseline["employment_income_hh"], weights=household_weight + baseline.employment_income_hh, weights=household_weight ) self_emp_income = MicroSeries( - baseline["self_employment_income_hh"], weights=household_weight + baseline.self_employment_income_hh, weights=household_weight ) earnings = emp_income + self_emp_income original_earnings = earnings - total_lsr_hh @@ -124,16 +118,16 @@ def labor_supply_response(baseline: SingleEconomy, reform: SingleEconomy) -> dic } hours = dict( - baseline=baseline["weekly_hours"], - reform=reform["weekly_hours"], - change=reform["weekly_hours"] - baseline["weekly_hours"], - income_effect=reform["weekly_hours_income_effect"] - - baseline["weekly_hours_income_effect"], - substitution_effect=reform["weekly_hours_substitution_effect"] - - baseline["weekly_hours_substitution_effect"], + baseline=baseline.weekly_hours, + reform=reform.weekly_hours, + change=reform.weekly_hours - baseline.weekly_hours, + income_effect=reform.weekly_hours_income_effect + - baseline.weekly_hours_income_effect, + substitution_effect=reform.weekly_hours_substitution_effect + - baseline.weekly_hours_substitution_effect, ) - return dict( + return LaborSupplyResponse( substitution_lsr=substitution_lsr, income_lsr=income_lsr, relative_lsr=relative_lsr, @@ -146,24 +140,35 @@ def labor_supply_response(baseline: SingleEconomy, reform: SingleEconomy) -> dic hours=hours, ) +class ProgramSpecificImpact(BaseModel): + baseline: float + reform: float + difference: float + +DetailedBudgetaryImpact = Dict[str, ProgramSpecificImpact] | None + def detailed_budgetary_impact( baseline: SingleEconomy, reform: SingleEconomy, country_id: str -) -> dict: +) -> DetailedBudgetaryImpact: result = {} if country_id == "uk": - for program in baseline["programs"]: + for program in baseline.programs: # baseline[programs][program] = total budgetary impact of program result[program] = dict( - baseline=baseline["programs"][program], - reform=reform["programs"][program], - difference=reform["programs"][program] - - baseline["programs"][program], + baseline=baseline.programs[program], + reform=reform.programs[program], + difference=reform.programs[program] + - baseline.programs[program], ) return result +class DecileImpact(BaseModel): + relative: DecileValues + average: DecileValues -def decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: + +def decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> DecileImpact: """ Compare the impact of a reform on the deciles of the population. @@ -175,14 +180,14 @@ def decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: dict: The impact of the reform on the deciles of the population. """ baseline_income = MicroSeries( - baseline["household_net_income"], weights=baseline["household_weight"] + baseline.household_net_income, weights=baseline.household_weight ) reform_income = MicroSeries( - reform["household_net_income"], weights=baseline_income.weights + reform.household_net_income, weights=baseline_income.weights ) # Filter out negative decile values - decile = MicroSeries(baseline["household_income_decile"]) + decile = MicroSeries(baseline.household_income_decile) baseline_income_filtered = baseline_income[decile >= 0] reform_income_filtered = reform_income[decile >= 0] @@ -198,14 +203,19 @@ def decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: ) rel_decile_dict = rel_income_change_by_decile.to_dict() avg_decile_dict = avg_income_change_by_decile.to_dict() - result = dict( + return DecileImpact( relative={int(k): v for k, v in rel_decile_dict.items()}, average={int(k): v for k, v in avg_decile_dict.items()}, ) - return result +class WealthDecileImpactWithValues(BaseModel): + relative: DecileValues + average: DecileValues -def wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: +WealthDecileImpact = WealthDecileImpactWithValues | None + + +def wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy, country_id: str) -> WealthDecileImpact: """ Compare the impact of a reform on the deciles of the population. @@ -216,15 +226,17 @@ def wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict Returns: dict: The impact of the reform on the deciles of the population. """ + if country_id != "uk": + return None baseline_income = MicroSeries( - baseline["household_net_income"], weights=baseline["household_weight"] + baseline.household_net_income, weights=baseline.household_weight ) reform_income = MicroSeries( - reform["household_net_income"], weights=baseline_income.weights + reform.household_net_income, weights=baseline_income.weights ) # Filter out negative decile values - decile = MicroSeries(baseline["household_wealth_decile"]) + decile = MicroSeries(baseline.household_wealth_decile) baseline_income_filtered = baseline_income[decile >= 0] reform_income_filtered = reform_income[decile >= 0] @@ -239,14 +251,22 @@ def wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict ) rel_decile_dict = rel_income_change_by_decile.to_dict() avg_decile_dict = avg_income_change_by_decile.to_dict() - result = dict( + return WealthDecileImpactWithValues( relative={int(k): v for k, v in rel_decile_dict.items()}, average={int(k): v for k, v in avg_decile_dict.items()}, ) - return result + +class BaselineReformValues(BaseModel): + baseline: float + reform: float + +class InequalityImpact(BaseModel): + gini: BaselineReformValues + top_10_pct_share: BaselineReformValues + top_1_pct_share: BaselineReformValues -def inequality_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: +def inequality_impact(baseline: SingleEconomy, reform: SingleEconomy) -> InequalityImpact: """ Compare the impact of a reform on inequality. @@ -258,21 +278,32 @@ def inequality_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: dict: The impact of the reform on inequality. """ - return dict( + values = dict( gini=dict( - baseline=baseline["gini"], - reform=reform["gini"], + baseline=baseline.gini, + reform=reform.gini, ), top_10_pct_share=dict( - baseline=baseline["top_10_percent_share"], - reform=reform["top_10_percent_share"], + baseline=baseline.top_10_percent_share, + reform=reform.top_10_percent_share, ), top_1_pct_share=dict( - baseline=baseline["top_1_percent_share"], - reform=reform["top_1_percent_share"], + baseline=baseline.top_1_percent_share, + reform=reform.top_1_percent_share, ), ) + return InequalityImpact(**values) + +class AgeGroupBaselineReformValues(BaseModel): + child: BaselineReformValues + adult: BaselineReformValues + senior: BaselineReformValues + all: BaselineReformValues + +class PovertyImpact(BaseModel): + poverty: AgeGroupBaselineReformValues + deep_poverty: AgeGroupBaselineReformValues def poverty_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: """ @@ -286,18 +317,18 @@ def poverty_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: dict: The impact of the reform on poverty. """ baseline_poverty = MicroSeries( - baseline["person_in_poverty"], weights=baseline["person_weight"] + baseline.person_in_poverty, weights=baseline.person_weight ) baseline_deep_poverty = MicroSeries( - baseline["person_in_deep_poverty"], weights=baseline["person_weight"] + baseline.person_in_deep_poverty, weights=baseline.person_weight ) reform_poverty = MicroSeries( - reform["person_in_poverty"], weights=baseline_poverty.weights + reform.person_in_poverty, weights=baseline_poverty.weights ) reform_deep_poverty = MicroSeries( - reform["person_in_deep_poverty"], weights=baseline_poverty.weights + reform.person_in_deep_poverty, weights=baseline_poverty.weights ) - age = MicroSeries(baseline["age"]) + age = MicroSeries(baseline.age) poverty = dict( child=dict( @@ -339,23 +370,27 @@ def poverty_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: ), ) - return dict( + return PovertyImpact( poverty=poverty, deep_poverty=deep_poverty, ) +class IntraDecileImpact(BaseModel): + deciles: Dict[str, List[float]] + all: Dict[str, float] + -def intra_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: +def intra_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> IntraDecileImpact: baseline_income = MicroSeries( - baseline["household_net_income"], weights=baseline["household_weight"] + baseline.household_net_income, weights=baseline.household_weight ) reform_income = MicroSeries( - reform["household_net_income"], weights=baseline_income.weights + reform.household_net_income, weights=baseline_income.weights ) people = MicroSeries( - baseline["household_count_people"], weights=baseline_income.weights + baseline.household_count_people, weights=baseline_income.weights ) - decile = MicroSeries(baseline["household_income_decile"]).values + decile = MicroSeries(baseline.household_income_decile).values absolute_change = (reform_income - baseline_income).values capped_baseline_income = np.maximum(baseline_income.values, 1) capped_reform_income = ( @@ -404,20 +439,27 @@ def intra_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: outcome_groups[label].append(people_in_proportion) all_outcomes[label] = sum(outcome_groups[label]) / 10 - return dict(deciles=outcome_groups, all=all_outcomes) + return IntraDecileImpact(deciles=outcome_groups, all=all_outcomes) +class IntraWealthDecileImpactWithValues(BaseModel): + deciles: Dict[str, List[float]] + all: Dict[str, float] -def intra_wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: +IntraWealthDecileImpact = IntraWealthDecileImpactWithValues | None + +def intra_wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy, country_id: str) -> IntraWealthDecileImpact: + if country_id != "uk": + return None baseline_income = MicroSeries( - baseline["household_net_income"], weights=baseline["household_weight"] + baseline.household_net_income, weights=baseline.household_weight ) reform_income = MicroSeries( - reform["household_net_income"], weights=baseline_income.weights + reform.household_net_income, weights=baseline_income.weights ) people = MicroSeries( - baseline["household_count_people"], weights=baseline_income.weights + baseline.household_count_people, weights=baseline_income.weights ) - decile = MicroSeries(baseline["household_wealth_decile"]).values + decile = MicroSeries(baseline.household_wealth_decile).values absolute_change = (reform_income - baseline_income).values capped_baseline_income = np.maximum(baseline_income.values, 1) capped_reform_income = ( @@ -466,10 +508,17 @@ def intra_wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) - outcome_groups[label].append(people_in_proportion) all_outcomes[label] = sum(outcome_groups[label]) / 10 - return dict(deciles=outcome_groups, all=all_outcomes) + return IntraWealthDecileImpactWithValues(deciles=outcome_groups, all=all_outcomes) + +class GenderBaselineReformValues(BaseModel): + male: BaselineReformValues + female: BaselineReformValues +class PovertyGenderBreakdown(BaseModel): + poverty: GenderBaselineReformValues + deep_poverty: GenderBaselineReformValues -def poverty_gender_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> dict: +def poverty_gender_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> PovertyGenderBreakdown: """ Compare the impact of a reform on poverty. @@ -480,21 +529,21 @@ def poverty_gender_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> Returns: dict: The impact of the reform on poverty. """ - if baseline["is_male"] is None: + if baseline.is_male is None: return {} baseline_poverty = MicroSeries( - baseline["person_in_poverty"], weights=baseline["person_weight"] + baseline.person_in_poverty, weights=baseline.person_weight ) baseline_deep_poverty = MicroSeries( - baseline["person_in_deep_poverty"], weights=baseline["person_weight"] + baseline.person_in_deep_poverty, weights=baseline.person_weight ) reform_poverty = MicroSeries( - reform["person_in_poverty"], weights=baseline_poverty.weights + reform.person_in_poverty, weights=baseline_poverty.weights ) reform_deep_poverty = MicroSeries( - reform["person_in_deep_poverty"], weights=baseline_poverty.weights + reform.person_in_deep_poverty, weights=baseline_poverty.weights ) - is_male = MicroSeries(baseline["is_male"]) + is_male = MicroSeries(baseline.is_male) poverty = dict( male=dict( @@ -518,13 +567,23 @@ def poverty_gender_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> ), ) - return dict( + return PovertyGenderBreakdown( poverty=poverty, deep_poverty=deep_poverty, ) +class RacialBaselineReformValues(BaseModel): + white: BaselineReformValues + black: BaselineReformValues + hispanic: BaselineReformValues + other: BaselineReformValues -def poverty_racial_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> dict: +class PovertyRacialBreakdownWithValues(BaseModel): + poverty: RacialBaselineReformValues + +PovertyRacialBreakdown = PovertyRacialBreakdownWithValues | None + +def poverty_racial_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> PovertyRacialBreakdown: """ Compare the impact of a reform on poverty. @@ -535,16 +594,16 @@ def poverty_racial_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> Returns: dict: The impact of the reform on poverty. """ - if baseline["race"] is None: - return {} + if baseline.race is None: + return None baseline_poverty = MicroSeries( - baseline["person_in_poverty"], weights=baseline["person_weight"] + baseline.person_in_poverty, weights=baseline.person_weight ) reform_poverty = MicroSeries( - reform["person_in_poverty"], weights=baseline_poverty.weights + reform.person_in_poverty, weights=baseline_poverty.weights ) race = MicroSeries( - baseline["race"] + baseline.race ) # Can be WHITE, BLACK, HISPANIC, or OTHER. poverty = dict( @@ -566,7 +625,7 @@ def poverty_racial_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> ), ) - return dict( + return PovertyRacialBreakdownWithValues( poverty=poverty, ) @@ -578,14 +637,16 @@ class UKConstituencyBreakdownByConstituency(BaseModel): y: int -class UKConstituencyBreakdown(BaseModel): +class UKConstituencyBreakdownWithValues(BaseModel): by_constituency: dict[str, UKConstituencyBreakdownByConstituency] outcomes_by_region: dict[str, dict[str, int]] +UKConstituencyBreakdown = UKConstituencyBreakdownWithValues | None + def uk_constituency_breakdown( baseline: SingleEconomy, reform: SingleEconomy, country_id: str -) -> UKConstituencyBreakdown | None: +) -> UKConstituencyBreakdown: if country_id != "uk": return None @@ -601,8 +662,8 @@ def uk_constituency_breakdown( "Lose less than 5%": 0, "Lose more than 5%": 0, } - baseline_hnet = baseline["household_net_income"] - reform_hnet = reform["household_net_income"] + baseline_hnet = baseline.household_net_income + reform_hnet = reform.household_net_income constituency_weights_path = download_huggingface_dataset( repo="policyengine/policyengine-uk-data", @@ -664,23 +725,36 @@ def uk_constituency_breakdown( for region_ in regions: output["outcomes_by_region"][region_][bucket] += 1 - return UKConstituencyBreakdown(**output) + return UKConstituencyBreakdownWithValues(**output) -def compare_economic_outputs( - baseline: SingleEconomy, reform: SingleEconomy, country_id: str = None -) -> dict: - """ - Compare the economic outputs of two economies. +class EconomyComparison(BaseModel): + budget: BudgetaryImpact + detailed_budget: DetailedBudgetaryImpact + decile: DecileImpact + inequality: InequalityImpact + poverty: PovertyImpact + poverty_by_gender: PovertyGenderBreakdown + poverty_by_race: PovertyRacialBreakdown + intra_decile: IntraDecileImpact + wealth_decile: WealthDecileImpact + intra_wealth_decile: IntraWealthDecileImpact + labor_supply_response: LaborSupplyResponse + constituency_impact: UKConstituencyBreakdown - Args: - baseline (dict): The baseline economy. - reform (dict): The reform economy. - Returns: - dict: The comparison of the two economies. - """ - if baseline.get("type") == "general": +def calculate_economy_comparison( + simulation: Simulation, +) -> EconomyComparison: + """Calculate comparison statistics between two economic scenarios.""" + if not simulation.is_comparison: + raise ValueError("Simulation must be a comparison simulation.") + + baseline: SingleEconomy = simulation.calculate_single_economy(reform=False) + reform: SingleEconomy = simulation.calculate_single_economy(reform=True) + options = simulation.options + country_id = options.country + if baseline.type == "general": budgetary_impact_data = budgetary_impact(baseline, reform) detailed_budgetary_impact_data = detailed_budgetary_impact( baseline, reform, country_id @@ -692,21 +766,15 @@ def compare_economic_outputs( poverty_by_race_data = poverty_racial_breakdown(baseline, reform) intra_decile_impact_data = intra_decile_impact(baseline, reform) labor_supply_response_data = labor_supply_response(baseline, reform) - constituency_impact_data: UKConstituencyBreakdown | None = ( + constituency_impact_data: UKConstituencyBreakdown = ( uk_constituency_breakdown(baseline, reform, country_id) ) - if constituency_impact_data is not None: - constituency_impact_data = constituency_impact_data.model_dump() - try: - wealth_decile_impact_data = wealth_decile_impact(baseline, reform) - intra_wealth_decile_impact_data = intra_wealth_decile_impact( - baseline, reform - ) - except: - wealth_decile_impact_data = {} - intra_wealth_decile_impact_data = {} + wealth_decile_impact_data = wealth_decile_impact(baseline, reform, country_id) + intra_wealth_decile_impact_data = intra_wealth_decile_impact( + baseline, reform, country_id + ) - return dict( + return EconomyComparison( budget=budgetary_impact_data, detailed_budget=detailed_budgetary_impact_data, decile=decile_impact_data, @@ -720,14 +788,14 @@ def compare_economic_outputs( labor_supply_response=labor_supply_response_data, constituency_impact=constituency_impact_data, ) - elif baseline.get("type") == "cliff": + elif baseline.type == "cliff": return dict( baseline=dict( - cliff_gap=baseline["cliff_gap"], - cliff_share=baseline["cliff_share"], + cliff_gap=baseline.cliff_gap, + cliff_share=baseline.cliff_share, ), reform=dict( - cliff_gap=reform["cliff_gap"], - cliff_share=reform["cliff_share"], + cliff_gap=reform.cliff_gap, + cliff_share=reform.cliff_share, ), ) diff --git a/policyengine/outputs/macro/comparison/legacy_api/calculate_economy_comparison.py b/policyengine/outputs/macro/comparison/legacy_api/calculate_economy_comparison.py deleted file mode 100644 index cd54ff57..00000000 --- a/policyengine/outputs/macro/comparison/legacy_api/calculate_economy_comparison.py +++ /dev/null @@ -1,696 +0,0 @@ -from microdf import MicroSeries -import numpy as np -from policyengine_core.tools.hugging_face import download_huggingface_dataset -import pandas as pd -import h5py -from pydantic import BaseModel - - -def budgetary_impact(baseline: dict, reform: dict) -> dict: - tax_revenue_impact = reform["total_tax"] - baseline["total_tax"] - state_tax_revenue_impact = ( - reform["total_state_tax"] - baseline["total_state_tax"] - ) - benefit_spending_impact = ( - reform["total_benefits"] - baseline["total_benefits"] - ) - budgetary_impact = tax_revenue_impact - benefit_spending_impact - return dict( - budgetary_impact=budgetary_impact, - tax_revenue_impact=tax_revenue_impact, - state_tax_revenue_impact=state_tax_revenue_impact, - benefit_spending_impact=benefit_spending_impact, - households=sum(baseline["household_weight"]), - baseline_net_income=baseline["total_net_income"], - ) - - -def labor_supply_response(baseline: dict, reform: dict) -> dict: - substitution_lsr = ( - reform["substitution_lsr"] - baseline["substitution_lsr"] - ) - income_lsr = reform["income_lsr"] - baseline["income_lsr"] - total_change = substitution_lsr + income_lsr - revenue_change = ( - reform["budgetary_impact_lsr"] - baseline["budgetary_impact_lsr"] - ) - - substitution_lsr_hh = np.array(reform["substitution_lsr_hh"]) - np.array( - baseline["substitution_lsr_hh"] - ) - income_lsr_hh = np.array(reform["income_lsr_hh"]) - np.array( - baseline["income_lsr_hh"] - ) - decile = np.array(baseline["household_income_decile"]) - household_weight = baseline["household_weight"] - - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - emp_income = MicroSeries( - baseline["employment_income_hh"], weights=household_weight - ) - self_emp_income = MicroSeries( - baseline["self_employment_income_hh"], weights=household_weight - ) - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - substitution_lsr_hh = MicroSeries( - substitution_lsr_hh, weights=household_weight - ) - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - - decile_avg = dict( - income=income_lsr_hh.groupby(decile).mean().to_dict(), - substitution=substitution_lsr_hh.groupby(decile).mean().to_dict(), - ) - decile_rel = dict( - income=( - income_lsr_hh.groupby(decile).sum() - / original_earnings.groupby(decile).sum() - ).to_dict(), - substitution=( - substitution_lsr_hh.groupby(decile).sum() - / original_earnings.groupby(decile).sum() - ).to_dict(), - ) - - relative_lsr = dict( - income=(income_lsr_hh.sum() / original_earnings.sum()), - substitution=(substitution_lsr_hh.sum() / original_earnings.sum()), - ) - - decile_rel["income"] = { - int(k): v for k, v in decile_rel["income"].items() if k > 0 - } - decile_rel["substitution"] = { - int(k): v for k, v in decile_rel["substitution"].items() if k > 0 - } - - hours = dict( - baseline=baseline["weekly_hours"], - reform=reform["weekly_hours"], - change=reform["weekly_hours"] - baseline["weekly_hours"], - income_effect=reform["weekly_hours_income_effect"] - - baseline["weekly_hours_income_effect"], - substitution_effect=reform["weekly_hours_substitution_effect"] - - baseline["weekly_hours_substitution_effect"], - ) - - return dict( - substitution_lsr=substitution_lsr, - income_lsr=income_lsr, - relative_lsr=relative_lsr, - total_change=total_change, - revenue_change=revenue_change, - decile=dict( - average=decile_avg, - relative=decile_rel, - ), - hours=hours, - ) - - -def detailed_budgetary_impact( - baseline: dict, reform: dict, country_id: str -) -> dict: - result = {} - if country_id == "uk": - for program in baseline["programs"]: - # baseline[programs][program] = total budgetary impact of program - result[program] = dict( - baseline=baseline["programs"][program], - reform=reform["programs"][program], - difference=reform["programs"][program] - - baseline["programs"][program], - ) - return result - - -def decile_impact(baseline: dict, reform: dict) -> dict: - """ - Compare the impact of a reform on the deciles of the population. - - Args: - baseline (dict): The baseline economy. - reform (dict): The reform economy. - - Returns: - dict: The impact of the reform on the deciles of the population. - """ - baseline_income = MicroSeries( - baseline["household_net_income"], weights=baseline["household_weight"] - ) - reform_income = MicroSeries( - reform["household_net_income"], weights=baseline_income.weights - ) - - # Filter out negative decile values - decile = MicroSeries(baseline["household_income_decile"]) - baseline_income_filtered = baseline_income[decile >= 0] - reform_income_filtered = reform_income[decile >= 0] - - income_change = reform_income_filtered - baseline_income_filtered - rel_income_change_by_decile = ( - income_change.groupby(decile).sum() - / baseline_income_filtered.groupby(decile).sum() - ) - - avg_income_change_by_decile = ( - income_change.groupby(decile).sum() - / baseline_income_filtered.groupby(decile).count() - ) - rel_decile_dict = rel_income_change_by_decile.to_dict() - avg_decile_dict = avg_income_change_by_decile.to_dict() - result = dict( - relative={int(k): v for k, v in rel_decile_dict.items()}, - average={int(k): v for k, v in avg_decile_dict.items()}, - ) - return result - - -def wealth_decile_impact(baseline: dict, reform: dict) -> dict: - """ - Compare the impact of a reform on the deciles of the population. - - Args: - baseline (dict): The baseline economy. - reform (dict): The reform economy. - - Returns: - dict: The impact of the reform on the deciles of the population. - """ - baseline_income = MicroSeries( - baseline["household_net_income"], weights=baseline["household_weight"] - ) - reform_income = MicroSeries( - reform["household_net_income"], weights=baseline_income.weights - ) - - # Filter out negative decile values - decile = MicroSeries(baseline["household_wealth_decile"]) - baseline_income_filtered = baseline_income[decile >= 0] - reform_income_filtered = reform_income[decile >= 0] - - income_change = reform_income_filtered - baseline_income_filtered - rel_income_change_by_decile = ( - income_change.groupby(decile).sum() - / baseline_income_filtered.groupby(decile).sum() - ) - avg_income_change_by_decile = ( - income_change.groupby(decile).sum() - / baseline_income_filtered.groupby(decile).count() - ) - rel_decile_dict = rel_income_change_by_decile.to_dict() - avg_decile_dict = avg_income_change_by_decile.to_dict() - result = dict( - relative={int(k): v for k, v in rel_decile_dict.items()}, - average={int(k): v for k, v in avg_decile_dict.items()}, - ) - return result - - -def inequality_impact(baseline: dict, reform: dict) -> dict: - """ - Compare the impact of a reform on inequality. - - Args: - baseline (dict): The baseline economy. - reform (dict): The reform economy. - - Returns: - dict: The impact of the reform on inequality. - """ - - return dict( - gini=dict( - baseline=baseline["gini"], - reform=reform["gini"], - ), - top_10_pct_share=dict( - baseline=baseline["top_10_percent_share"], - reform=reform["top_10_percent_share"], - ), - top_1_pct_share=dict( - baseline=baseline["top_1_percent_share"], - reform=reform["top_1_percent_share"], - ), - ) - - -def poverty_impact(baseline: dict, reform: dict) -> dict: - """ - Compare the impact of a reform on poverty. - - Args: - baseline (dict): The baseline economy. - reform (dict): The reform economy. - - Returns: - dict: The impact of the reform on poverty. - """ - baseline_poverty = MicroSeries( - baseline["person_in_poverty"], weights=baseline["person_weight"] - ) - baseline_deep_poverty = MicroSeries( - baseline["person_in_deep_poverty"], weights=baseline["person_weight"] - ) - reform_poverty = MicroSeries( - reform["person_in_poverty"], weights=baseline_poverty.weights - ) - reform_deep_poverty = MicroSeries( - reform["person_in_deep_poverty"], weights=baseline_poverty.weights - ) - age = MicroSeries(baseline["age"]) - - poverty = dict( - child=dict( - baseline=float(baseline_poverty[age < 18].mean()), - reform=float(reform_poverty[age < 18].mean()), - ), - adult=dict( - baseline=float(baseline_poverty[(age >= 18) & (age < 65)].mean()), - reform=float(reform_poverty[(age >= 18) & (age < 65)].mean()), - ), - senior=dict( - baseline=float(baseline_poverty[age >= 65].mean()), - reform=float(reform_poverty[age >= 65].mean()), - ), - all=dict( - baseline=float(baseline_poverty.mean()), - reform=float(reform_poverty.mean()), - ), - ) - - deep_poverty = dict( - child=dict( - baseline=float(baseline_deep_poverty[age < 18].mean()), - reform=float(reform_deep_poverty[age < 18].mean()), - ), - adult=dict( - baseline=float( - baseline_deep_poverty[(age >= 18) & (age < 65)].mean() - ), - reform=float(reform_deep_poverty[(age >= 18) & (age < 65)].mean()), - ), - senior=dict( - baseline=float(baseline_deep_poverty[age >= 65].mean()), - reform=float(reform_deep_poverty[age >= 65].mean()), - ), - all=dict( - baseline=float(baseline_deep_poverty.mean()), - reform=float(reform_deep_poverty.mean()), - ), - ) - - return dict( - poverty=poverty, - deep_poverty=deep_poverty, - ) - - -def intra_decile_impact(baseline: dict, reform: dict) -> dict: - baseline_income = MicroSeries( - baseline["household_net_income"], weights=baseline["household_weight"] - ) - reform_income = MicroSeries( - reform["household_net_income"], weights=baseline_income.weights - ) - people = MicroSeries( - baseline["household_count_people"], weights=baseline_income.weights - ) - decile = MicroSeries(baseline["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 - - # Within each decile, calculate the percentage of people who: - # 1. Gained more than 5% of their income - # 2. Gained between 0% and 5% of their income - # 3. Had no change in income - # 3. Lost between 0% and 5% of their income - # 4. Lost more than 5% of their 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: bool = decile == i - in_group: bool = (income_change > lower) & (income_change <= upper) - in_both: bool = in_decile & in_group - - people_in_both: np.float64 = people[in_both].sum() - people_in_decile: np.float64 = people[in_decile].sum() - - # np.float64 does not raise ZeroDivisionError, instead returns NaN - if people_in_decile == 0 and people_in_both == 0: - people_in_proportion: float = 0.0 - else: - people_in_proportion: float = float( - people_in_both / people_in_decile - ) - - outcome_groups[label].append(people_in_proportion) - - all_outcomes[label] = sum(outcome_groups[label]) / 10 - return dict(deciles=outcome_groups, all=all_outcomes) - - -def intra_wealth_decile_impact(baseline: dict, reform: dict) -> dict: - baseline_income = MicroSeries( - baseline["household_net_income"], weights=baseline["household_weight"] - ) - reform_income = MicroSeries( - reform["household_net_income"], weights=baseline_income.weights - ) - people = MicroSeries( - baseline["household_count_people"], weights=baseline_income.weights - ) - decile = MicroSeries(baseline["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 - - # Within each decile, calculate the percentage of people who: - # 1. Gained more than 5% of their income - # 2. Gained between 0% and 5% of their income - # 3. Had no change in income - # 3. Lost between 0% and 5% of their income - # 4. Lost more than 5% of their 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: bool = decile == i - in_group: bool = (income_change > lower) & (income_change <= upper) - in_both: bool = in_decile & in_group - - people_in_both: np.float64 = people[in_both].sum() - people_in_decile: np.float64 = people[in_decile].sum() - - # np.float64 does not raise ZeroDivisionError, instead returns NaN - if people_in_decile == 0 and people_in_both == 0: - people_in_proportion = 0 - else: - people_in_proportion: float = float( - people_in_both / people_in_decile - ) - - outcome_groups[label].append(people_in_proportion) - - all_outcomes[label] = sum(outcome_groups[label]) / 10 - return dict(deciles=outcome_groups, all=all_outcomes) - - -def poverty_gender_breakdown(baseline: dict, reform: dict) -> dict: - """ - Compare the impact of a reform on poverty. - - Args: - baseline (dict): The baseline economy. - reform (dict): The reform economy. - - Returns: - dict: The impact of the reform on poverty. - """ - if baseline["is_male"] is None: - return {} - baseline_poverty = MicroSeries( - baseline["person_in_poverty"], weights=baseline["person_weight"] - ) - baseline_deep_poverty = MicroSeries( - baseline["person_in_deep_poverty"], weights=baseline["person_weight"] - ) - reform_poverty = MicroSeries( - reform["person_in_poverty"], weights=baseline_poverty.weights - ) - reform_deep_poverty = MicroSeries( - reform["person_in_deep_poverty"], weights=baseline_poverty.weights - ) - is_male = MicroSeries(baseline["is_male"]) - - poverty = dict( - male=dict( - baseline=float(baseline_poverty[is_male].mean()), - reform=float(reform_poverty[is_male].mean()), - ), - female=dict( - baseline=float(baseline_poverty[~is_male].mean()), - reform=float(reform_poverty[~is_male].mean()), - ), - ) - - deep_poverty = dict( - male=dict( - baseline=float(baseline_deep_poverty[is_male].mean()), - reform=float(reform_deep_poverty[is_male].mean()), - ), - female=dict( - baseline=float(baseline_deep_poverty[~is_male].mean()), - reform=float(reform_deep_poverty[~is_male].mean()), - ), - ) - - return dict( - poverty=poverty, - deep_poverty=deep_poverty, - ) - - -def poverty_racial_breakdown(baseline: dict, reform: dict) -> dict: - """ - Compare the impact of a reform on poverty. - - Args: - baseline (dict): The baseline economy. - reform (dict): The reform economy. - - Returns: - dict: The impact of the reform on poverty. - """ - if baseline["race"] is None: - return {} - baseline_poverty = MicroSeries( - baseline["person_in_poverty"], weights=baseline["person_weight"] - ) - reform_poverty = MicroSeries( - reform["person_in_poverty"], weights=baseline_poverty.weights - ) - race = MicroSeries( - baseline["race"] - ) # Can be WHITE, BLACK, HISPANIC, or OTHER. - - poverty = dict( - white=dict( - baseline=float(baseline_poverty[race == "WHITE"].mean()), - reform=float(reform_poverty[race == "WHITE"].mean()), - ), - black=dict( - baseline=float(baseline_poverty[race == "BLACK"].mean()), - reform=float(reform_poverty[race == "BLACK"].mean()), - ), - hispanic=dict( - baseline=float(baseline_poverty[race == "HISPANIC"].mean()), - reform=float(reform_poverty[race == "HISPANIC"].mean()), - ), - other=dict( - baseline=float(baseline_poverty[race == "OTHER"].mean()), - reform=float(reform_poverty[race == "OTHER"].mean()), - ), - ) - - return dict( - poverty=poverty, - ) - - -class UKConstituencyBreakdownByConstituency(BaseModel): - average_household_income_change: float - relative_household_income_change: float - x: int - y: int - - -class UKConstituencyBreakdown(BaseModel): - by_constituency: dict[str, UKConstituencyBreakdownByConstituency] - outcomes_by_region: dict[str, dict[str, int]] - - -def uk_constituency_breakdown( - baseline: dict, reform: dict, country_id: str -) -> UKConstituencyBreakdown | None: - if country_id != "uk": - return None - - output = { - "by_constituency": {}, - "outcomes_by_region": {}, - } - for region in ["uk", "england", "scotland", "wales", "northern_ireland"]: - output["outcomes_by_region"][region] = { - "Gain more than 5%": 0, - "Gain less than 5%": 0, - "No change": 0, - "Lose less than 5%": 0, - "Lose more than 5%": 0, - } - baseline_hnet = baseline["household_net_income"] - reform_hnet = reform["household_net_income"] - - constituency_weights_path = download_huggingface_dataset( - repo="policyengine/policyengine-uk-data", - repo_filename="parliamentary_constituency_weights.h5", - ) - with h5py.File(constituency_weights_path, "r") as f: - weights = f["2025"][ - ... - ] # {2025: array(650, 100180) where cell i, j is the weight of household record i in constituency j} - - constituency_names_path = download_huggingface_dataset( - repo="policyengine/policyengine-uk-data", - repo_filename="constituencies_2024.csv", - ) - constituency_names = pd.read_csv( - constituency_names_path - ) # columns code (constituency code), name (constituency name), x, y (geographic position) - - for i in range(len(constituency_names)): - name: str = constituency_names.iloc[i]["name"] - code: str = constituency_names.iloc[i]["code"] - weight: np.ndarray = weights[i] - baseline_income = MicroSeries(baseline_hnet, weights=weight) - reform_income = MicroSeries(reform_hnet, weights=weight) - average_household_income_change: float = ( - reform_income.sum() - baseline_income.sum() - ) / baseline_income.count() - percent_household_income_change: float = ( - reform_income.sum() / baseline_income.sum() - 1 - ) - output["by_constituency"][name] = { - "average_household_income_change": average_household_income_change, - "relative_household_income_change": percent_household_income_change, - "x": int(constituency_names.iloc[i]["x"]), # Geographic positions - "y": int(constituency_names.iloc[i]["y"]), - } - - regions = ["uk"] - if "E" in code: - regions.append("england") - elif "S" in code: - regions.append("scotland") - elif "W" in code: - regions.append("wales") - elif "N" in code: - regions.append("northern_ireland") - - if percent_household_income_change > 0.05: - bucket = "Gain more than 5%" - elif percent_household_income_change > 1e-3: - bucket = "Gain less than 5%" - elif percent_household_income_change > -1e-3: - bucket = "No change" - elif percent_household_income_change > -0.05: - bucket = "Lose less than 5%" - else: - bucket = "Lose more than 5%" - - for region_ in regions: - output["outcomes_by_region"][region_][bucket] += 1 - - return UKConstituencyBreakdown(**output) - - -def compare_economic_outputs( - baseline: dict, reform: dict, country_id: str = None -) -> dict: - """ - Compare the economic outputs of two economies. - - Args: - baseline (dict): The baseline economy. - reform (dict): The reform economy. - - Returns: - dict: The comparison of the two economies. - """ - if baseline.get("type") == "general": - budgetary_impact_data = budgetary_impact(baseline, reform) - detailed_budgetary_impact_data = detailed_budgetary_impact( - baseline, reform, country_id - ) - decile_impact_data = decile_impact(baseline, reform) - inequality_impact_data = inequality_impact(baseline, reform) - poverty_impact_data = poverty_impact(baseline, reform) - poverty_by_gender_data = poverty_gender_breakdown(baseline, reform) - poverty_by_race_data = poverty_racial_breakdown(baseline, reform) - intra_decile_impact_data = intra_decile_impact(baseline, reform) - labor_supply_response_data = labor_supply_response(baseline, reform) - constituency_impact_data: UKConstituencyBreakdown | None = ( - uk_constituency_breakdown(baseline, reform, country_id) - ) - if constituency_impact_data is not None: - constituency_impact_data = constituency_impact_data.model_dump() - try: - wealth_decile_impact_data = wealth_decile_impact(baseline, reform) - intra_wealth_decile_impact_data = intra_wealth_decile_impact( - baseline, reform - ) - except: - wealth_decile_impact_data = {} - intra_wealth_decile_impact_data = {} - - return dict( - budget=budgetary_impact_data, - detailed_budget=detailed_budgetary_impact_data, - decile=decile_impact_data, - inequality=inequality_impact_data, - poverty=poverty_impact_data, - poverty_by_gender=poverty_by_gender_data, - poverty_by_race=poverty_by_race_data, - intra_decile=intra_decile_impact_data, - wealth_decile=wealth_decile_impact_data, - intra_wealth_decile=intra_wealth_decile_impact_data, - labor_supply_response=labor_supply_response_data, - constituency_impact=constituency_impact_data, - ) - elif baseline.get("type") == "cliff": - return dict( - baseline=dict( - cliff_gap=baseline["cliff_gap"], - cliff_share=baseline["cliff_share"], - ), - reform=dict( - cliff_gap=reform["cliff_gap"], - cliff_share=reform["cliff_share"], - ), - ) diff --git a/policyengine/outputs/macro/single/calculate_single_economy.py b/policyengine/outputs/macro/single/calculate_single_economy.py index 0a8d37ec..61853998 100644 --- a/policyengine/outputs/macro/single/calculate_single_economy.py +++ b/policyengine/outputs/macro/single/calculate_single_economy.py @@ -23,8 +23,8 @@ class SingleEconomy(BaseModel): equiv_household_net_income: List[float] household_income_decile: List[int] household_market_income: List[float] - household_wealth_decile: List[int] - household_wealth: List[float] + household_wealth_decile: List[int] | None + household_wealth: List[float] | None in_poverty: List[bool] person_in_poverty: List[bool] person_in_deep_poverty: List[bool] From 5dc1021e3fb73bf7dfa64ea88efd2d072fff673a Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Fri, 14 Mar 2025 11:21:24 +0000 Subject: [PATCH 3/7] Format --- policyengine/__init__.py | 2 +- .../calculate_economy_comparison.py | 82 ++++++++++++++---- .../macro/single/calculate_single_economy.py | 85 +++++++++++-------- 3 files changed, 113 insertions(+), 56 deletions(-) diff --git a/policyengine/__init__.py b/policyengine/__init__.py index 26fe276e..4bb8a90c 100644 --- a/policyengine/__init__.py +++ b/policyengine/__init__.py @@ -1,3 +1,3 @@ from .simulation import Simulation, SimulationOptions -__version__ = "0.1.1" \ No newline at end of file +__version__ = "0.1.1" diff --git a/policyengine/outputs/macro/comparison/calculate_economy_comparison.py b/policyengine/outputs/macro/comparison/calculate_economy_comparison.py index 0c4f37e7..4cb4ecda 100644 --- a/policyengine/outputs/macro/comparison/calculate_economy_comparison.py +++ b/policyengine/outputs/macro/comparison/calculate_economy_comparison.py @@ -1,4 +1,5 @@ """Calculate comparison statistics between two economic scenarios.""" + from microdf import MicroSeries import numpy as np from policyengine_core.tools.hugging_face import download_huggingface_dataset @@ -6,7 +7,9 @@ import h5py from pydantic import BaseModel from policyengine import Simulation -from policyengine.outputs.macro.single.calculate_single_economy import SingleEconomy +from policyengine.outputs.macro.single.calculate_single_economy import ( + SingleEconomy, +) from typing import List, Dict @@ -19,14 +22,14 @@ class BudgetaryImpact(BaseModel): baseline_net_income: float -def budgetary_impact(baseline: SingleEconomy, reform: SingleEconomy) -> BudgetaryImpact: +def budgetary_impact( + baseline: SingleEconomy, reform: SingleEconomy +) -> BudgetaryImpact: tax_revenue_impact = reform.total_tax - baseline.total_tax state_tax_revenue_impact = ( reform.total_state_tax - baseline.total_state_tax ) - benefit_spending_impact = ( - reform.total_benefits - baseline.total_benefits - ) + benefit_spending_impact = reform.total_benefits - baseline.total_benefits budgetary_impact = tax_revenue_impact - benefit_spending_impact return BudgetaryImpact( budgetary_impact=budgetary_impact, @@ -37,8 +40,10 @@ def budgetary_impact(baseline: SingleEconomy, reform: SingleEconomy) -> Budgetar baseline_net_income=baseline.total_net_income, ) + DecileValues = Dict[int, float] + class HoursResponse(BaseModel): baseline: float reform: float @@ -46,6 +51,7 @@ class HoursResponse(BaseModel): income_effect: float substitution_effect: float + class LaborSupplyResponse(BaseModel): substitution_lsr: float income_lsr: float @@ -56,10 +62,10 @@ class LaborSupplyResponse(BaseModel): hours: HoursResponse -def labor_supply_response(baseline: SingleEconomy, reform: SingleEconomy) -> LaborSupplyResponse: - substitution_lsr = ( - reform.substitution_lsr - baseline.substitution_lsr - ) +def labor_supply_response( + baseline: SingleEconomy, reform: SingleEconomy +) -> LaborSupplyResponse: + substitution_lsr = reform.substitution_lsr - baseline.substitution_lsr income_lsr = reform.income_lsr - baseline.income_lsr total_change = substitution_lsr + income_lsr revenue_change = ( @@ -140,11 +146,13 @@ def labor_supply_response(baseline: SingleEconomy, reform: SingleEconomy) -> Lab hours=hours, ) + class ProgramSpecificImpact(BaseModel): baseline: float reform: float difference: float + DetailedBudgetaryImpact = Dict[str, ProgramSpecificImpact] | None @@ -163,12 +171,15 @@ def detailed_budgetary_impact( ) return result + class DecileImpact(BaseModel): relative: DecileValues average: DecileValues -def decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> DecileImpact: +def decile_impact( + baseline: SingleEconomy, reform: SingleEconomy +) -> DecileImpact: """ Compare the impact of a reform on the deciles of the population. @@ -208,14 +219,18 @@ def decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> DecileImpac average={int(k): v for k, v in avg_decile_dict.items()}, ) + class WealthDecileImpactWithValues(BaseModel): relative: DecileValues average: DecileValues + WealthDecileImpact = WealthDecileImpactWithValues | None -def wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy, country_id: str) -> WealthDecileImpact: +def wealth_decile_impact( + baseline: SingleEconomy, reform: SingleEconomy, country_id: str +) -> WealthDecileImpact: """ Compare the impact of a reform on the deciles of the population. @@ -256,17 +271,21 @@ def wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy, country average={int(k): v for k, v in avg_decile_dict.items()}, ) + class BaselineReformValues(BaseModel): baseline: float reform: float + class InequalityImpact(BaseModel): gini: BaselineReformValues top_10_pct_share: BaselineReformValues top_1_pct_share: BaselineReformValues -def inequality_impact(baseline: SingleEconomy, reform: SingleEconomy) -> InequalityImpact: +def inequality_impact( + baseline: SingleEconomy, reform: SingleEconomy +) -> InequalityImpact: """ Compare the impact of a reform on inequality. @@ -295,16 +314,19 @@ def inequality_impact(baseline: SingleEconomy, reform: SingleEconomy) -> Inequal return InequalityImpact(**values) + class AgeGroupBaselineReformValues(BaseModel): child: BaselineReformValues adult: BaselineReformValues senior: BaselineReformValues all: BaselineReformValues + class PovertyImpact(BaseModel): poverty: AgeGroupBaselineReformValues deep_poverty: AgeGroupBaselineReformValues + def poverty_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: """ Compare the impact of a reform on poverty. @@ -375,12 +397,15 @@ def poverty_impact(baseline: SingleEconomy, reform: SingleEconomy) -> dict: deep_poverty=deep_poverty, ) + class IntraDecileImpact(BaseModel): deciles: Dict[str, List[float]] all: Dict[str, float] -def intra_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> IntraDecileImpact: +def intra_decile_impact( + baseline: SingleEconomy, reform: SingleEconomy +) -> IntraDecileImpact: baseline_income = MicroSeries( baseline.household_net_income, weights=baseline.household_weight ) @@ -441,13 +466,18 @@ def intra_decile_impact(baseline: SingleEconomy, reform: SingleEconomy) -> Intra all_outcomes[label] = sum(outcome_groups[label]) / 10 return IntraDecileImpact(deciles=outcome_groups, all=all_outcomes) + class IntraWealthDecileImpactWithValues(BaseModel): deciles: Dict[str, List[float]] all: Dict[str, float] + IntraWealthDecileImpact = IntraWealthDecileImpactWithValues | None -def intra_wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy, country_id: str) -> IntraWealthDecileImpact: + +def intra_wealth_decile_impact( + baseline: SingleEconomy, reform: SingleEconomy, country_id: str +) -> IntraWealthDecileImpact: if country_id != "uk": return None baseline_income = MicroSeries( @@ -508,17 +538,24 @@ def intra_wealth_decile_impact(baseline: SingleEconomy, reform: SingleEconomy, c outcome_groups[label].append(people_in_proportion) all_outcomes[label] = sum(outcome_groups[label]) / 10 - return IntraWealthDecileImpactWithValues(deciles=outcome_groups, all=all_outcomes) + return IntraWealthDecileImpactWithValues( + deciles=outcome_groups, all=all_outcomes + ) + class GenderBaselineReformValues(BaseModel): male: BaselineReformValues female: BaselineReformValues + class PovertyGenderBreakdown(BaseModel): poverty: GenderBaselineReformValues deep_poverty: GenderBaselineReformValues -def poverty_gender_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> PovertyGenderBreakdown: + +def poverty_gender_breakdown( + baseline: SingleEconomy, reform: SingleEconomy +) -> PovertyGenderBreakdown: """ Compare the impact of a reform on poverty. @@ -572,18 +609,24 @@ def poverty_gender_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> deep_poverty=deep_poverty, ) + class RacialBaselineReformValues(BaseModel): white: BaselineReformValues black: BaselineReformValues hispanic: BaselineReformValues other: BaselineReformValues + class PovertyRacialBreakdownWithValues(BaseModel): poverty: RacialBaselineReformValues + PovertyRacialBreakdown = PovertyRacialBreakdownWithValues | None -def poverty_racial_breakdown(baseline: SingleEconomy, reform: SingleEconomy) -> PovertyRacialBreakdown: + +def poverty_racial_breakdown( + baseline: SingleEconomy, reform: SingleEconomy +) -> PovertyRacialBreakdown: """ Compare the impact of a reform on poverty. @@ -641,6 +684,7 @@ class UKConstituencyBreakdownWithValues(BaseModel): by_constituency: dict[str, UKConstituencyBreakdownByConstituency] outcomes_by_region: dict[str, dict[str, int]] + UKConstituencyBreakdown = UKConstituencyBreakdownWithValues | None @@ -769,7 +813,9 @@ def calculate_economy_comparison( constituency_impact_data: UKConstituencyBreakdown = ( uk_constituency_breakdown(baseline, reform, country_id) ) - wealth_decile_impact_data = wealth_decile_impact(baseline, reform, country_id) + wealth_decile_impact_data = wealth_decile_impact( + baseline, reform, country_id + ) intra_wealth_decile_impact_data = intra_wealth_decile_impact( baseline, reform, country_id ) diff --git a/policyengine/outputs/macro/single/calculate_single_economy.py b/policyengine/outputs/macro/single/calculate_single_economy.py index 61853998..7a21133a 100644 --- a/policyengine/outputs/macro/single/calculate_single_economy.py +++ b/policyengine/outputs/macro/single/calculate_single_economy.py @@ -51,7 +51,6 @@ class SingleEconomy(BaseModel): programs: Dict[str, float] | None - @dataclass class UKProgram: name: str @@ -73,6 +72,7 @@ class UKPrograms: UKProgram("ni_employer", True), ] + class GeneralEconomyTask: def __init__(self, simulation: Microsimulation, country_id: str): self.simulation = simulation @@ -328,8 +328,17 @@ def calculate_uk_programs(self) -> Dict[str, float]: } -def calculate_single_economy(simulation: Simulation, reform: bool = False) -> Dict: - task_manager = GeneralEconomyTask(simulation.baseline_simulation if not reform else simulation.reform_simulation, simulation.options.country) +def calculate_single_economy( + simulation: Simulation, reform: bool = False +) -> Dict: + task_manager = GeneralEconomyTask( + ( + simulation.baseline_simulation + if not reform + else simulation.reform_simulation + ), + simulation.options.country, + ) country_id = simulation.options.country total_tax, total_spending = task_manager.calculate_tax_and_spending() @@ -373,37 +382,39 @@ def calculate_single_economy(simulation: Simulation, reform: bool = False) -> Di except: total_state_tax = 0 - return SingleEconomy(**{ - "total_net_income": total_net_income, - "employment_income_hh": employment_income_hh, - "self_employment_income_hh": self_employment_income_hh, - "total_tax": total_tax, - "total_state_tax": total_state_tax, - "total_benefits": total_spending, - "household_net_income": household_net_income, - "equiv_household_net_income": equiv_household_net_income, - "household_income_decile": household_income_decile, - "household_market_income": household_market_income, - "household_wealth_decile": wealth_decile, - "household_wealth": wealth, - "in_poverty": in_poverty, - "person_in_poverty": person_in_poverty, - "person_in_deep_poverty": person_in_deep_poverty, - "poverty_gap": poverty_gap, - "deep_poverty_gap": deep_poverty_gap, - "person_weight": person_weight, - "household_weight": household_weight, - "household_count_people": task_manager.household_count_people.astype( - int - ).tolist(), - "gini": float(gini), - "top_10_percent_share": float(top_10_share), - "top_1_percent_share": float(top_1_share), - "is_male": is_male, - "race": race, - "age": age, - **labor_supply_responses, - **lsr_working_hours, - "type": "general", - "programs": uk_programs, - }) + return SingleEconomy( + **{ + "total_net_income": total_net_income, + "employment_income_hh": employment_income_hh, + "self_employment_income_hh": self_employment_income_hh, + "total_tax": total_tax, + "total_state_tax": total_state_tax, + "total_benefits": total_spending, + "household_net_income": household_net_income, + "equiv_household_net_income": equiv_household_net_income, + "household_income_decile": household_income_decile, + "household_market_income": household_market_income, + "household_wealth_decile": wealth_decile, + "household_wealth": wealth, + "in_poverty": in_poverty, + "person_in_poverty": person_in_poverty, + "person_in_deep_poverty": person_in_deep_poverty, + "poverty_gap": poverty_gap, + "deep_poverty_gap": deep_poverty_gap, + "person_weight": person_weight, + "household_weight": household_weight, + "household_count_people": task_manager.household_count_people.astype( + int + ).tolist(), + "gini": float(gini), + "top_10_percent_share": float(top_10_share), + "top_1_percent_share": float(top_1_share), + "is_male": is_male, + "race": race, + "age": age, + **labor_supply_responses, + **lsr_working_hours, + "type": "general", + "programs": uk_programs, + } + ) From 018fe7e437e051902af537c51593a782f171a268 Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Fri, 14 Mar 2025 11:46:22 +0000 Subject: [PATCH 4/7] Remove simulationadjustment code --- policyengine/simulation.py | 13 ++----------- policyengine/utils/reforms.py | 6 ------ 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/policyengine/simulation.py b/policyengine/simulation.py index bfadd921..869520ef 100644 --- a/policyengine/simulation.py +++ b/policyengine/simulation.py @@ -7,7 +7,7 @@ from policyengine_core.simulations import ( Microsimulation as CountryMicrosimulation, ) -from .utils.reforms import ParametricReform, SimulationAdjustment +from .utils.reforms import ParametricReform from policyengine_core.reforms import Reform as StructuralReform from policyengine_core.data import Dataset from .utils.huggingface import download @@ -34,7 +34,7 @@ ) # Needs stricter typing. Any==policyengine_core.data.Dataset, but pydantic refuses for some reason. TimePeriodType = int ReformType = ( - ParametricReform | SimulationAdjustment | Type[StructuralReform] | None + ParametricReform | Type[StructuralReform] | None ) RegionType = str | None SubsampleType = int | None @@ -172,12 +172,6 @@ def _initialise_simulation( if isinstance(reform, ParametricReform): reform = reform.model_dump() - simulation_editing_reform = None - - if isinstance(reform, SimulationAdjustment): - simulation_editing_reform = reform.root - reform = None - simulation: CountrySimulation = _simulation_type( dataset=data if macro else None, situation=data if not macro else None, @@ -199,9 +193,6 @@ def _initialise_simulation( if subsample is not None: simulation = simulation.subsample(subsample) - if simulation_editing_reform is not None: - simulation_editing_reform(simulation) - return simulation def _apply_region_to_simulation( diff --git a/policyengine/utils/reforms.py b/policyengine/utils/reforms.py index ab8225bb..6d681f57 100644 --- a/policyengine/utils/reforms.py +++ b/policyengine/utils/reforms.py @@ -11,9 +11,3 @@ class ParametricReform(RootModel): """A reform that just changes parameter values.""" root: Dict[str, Dict | float | bool] - - -class SimulationAdjustment(RootModel): - """A reform that changes the simulation in some way.""" - - root: object # Python callable function that takes a Simulation object and returns nothing. Not JSON serialisable. Needs fixing. From a85d65accd84a75c9c9ff8c195f0aa3e548744b2 Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Fri, 14 Mar 2025 12:14:56 +0000 Subject: [PATCH 5/7] Bump US version --- docs/basic/calculate_economy_comparison.ipynb | 24 ++++++------------- docs/basic/calculate_single_economy.ipynb | 2 +- pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/docs/basic/calculate_economy_comparison.ipynb b/docs/basic/calculate_economy_comparison.ipynb index c9d08f0b..09f9f931 100644 --- a/docs/basic/calculate_economy_comparison.ipynb +++ b/docs/basic/calculate_economy_comparison.ipynb @@ -11,26 +11,16 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "WARNING: Gini calculation failed. Setting to 0.4.\n", - "WARNING: Gini calculation failed. Setting to 0.4.\n" + "/Users/nikhilwoodruff/policyengine/policyengine.py/.venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" ] - }, - { - "data": { - "text/plain": [ - "EconomyComparison(headlines=Headlines(budgetary_impact=12057201822.641846, winner_share=3.096710278402974e-05), fiscal=FiscalComparison(baseline=FiscalSummary(tax_revenue=3013608593152.1895, federal_tax=2583108898331.884, federal_balance=1160152745100.683, state_tax=430499694820.3057, government_spending=1422956153231.2007, tax_benefit_programs={}, household_net_income=12826524419636.805), reform=FiscalSummary(tax_revenue=3013602235736.6367, federal_tax=2583108898330.5815, federal_balance=1172209946923.325, state_tax=430493337406.05536, government_spending=1410898951407.2566, tax_benefit_programs={}, household_net_income=12814482607439.363), change=FiscalSummary(tax_revenue=-6357415.552734375, federal_tax=-1.30224609375, federal_balance=12057201822.641846, state_tax=-6357414.250366211, government_spending=-12057201823.944092, tax_benefit_programs={}, household_net_income=-12041812197.441406), relative_change=FiscalSummary(tax_revenue=-2.1095690950644036e-06, federal_tax=-5.041390607228997e-13, federal_balance=0.01039277101533339, state_tax=-1.476752324532971e-05, government_spending=-0.008473347401861264, tax_benefit_programs={}, household_net_income=-0.0009388211337286318)), inequality=InequalityComparison(baseline=InequalitySummary(gini=0.4, top_10_share=0.3056184716000026, top_1_share=0.07133480442157591), reform=InequalitySummary(gini=0.4, top_10_share=0.30585587458075175, top_1_share=0.0713913687702228), change=InequalitySummary(gini=0.0, top_10_share=0.00023740298074914623, top_1_share=5.65643486468842e-05), relative_change=InequalitySummary(gini=0.0, top_10_share=0.000776795262100068, top_1_share=0.0007929418059745287)), distributional=DecileImpacts(income=IncomeMeasureSpecificDecileImpacts(income_change=IncomeMeasureSpecificDecileIncomeChange(relative={1: -0.003725182447367451, 2: -0.004777925629293106, 3: -0.004427406664007123, 4: -0.003033546615233723, 5: -0.0012295913621786522, 6: -0.0003957445652524665, 7: -9.39759257002033e-05, 8: -3.0593991125471323e-05, 9: -1.4266187310034442e-05, 10: -1.1781514896665692e-05}, average={1: -63.89275812122564, 2: -174.0337390913802, 3: -224.9955222412714, 4: -193.3288209208671, 5: -96.35100483351104, 6: -37.64443533520402, 7: -10.856160889113756, 8: -4.3236716100441095, 9: -2.6063802446197073, 10: -3.9748627643461294}), winners_and_losers=IncomeMeasureSpecificDecileWinnersLosers(deciles={1: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.0657153256192896, lose_less_than_5_percent_share=0.05318331952206108, lose_share=0.11889864514135068, no_change_share=0.8807898379100747, gain_share=0.00031151694857464756, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.00031151694857464756), 2: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.15194453295736532, lose_less_than_5_percent_share=0.07711620163220688, lose_share=0.22906073458957218, no_change_share=0.7709392654104278, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0), 3: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.17114447859504486, lose_less_than_5_percent_share=0.059347324242194015, lose_share=0.23049180283723886, no_change_share=0.7695081971627611, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0), 4: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.12989846703096578, lose_less_than_5_percent_share=0.04485983299864166, lose_share=0.17475830002960743, no_change_share=0.8252416999703925, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0), 5: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.051068199514449644, lose_less_than_5_percent_share=0.024111392231720524, lose_share=0.07517959174617017, no_change_share=0.9248204082538298, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0), 6: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.015369267341425434, lose_less_than_5_percent_share=0.018481961006501027, lose_share=0.03385122834792646, no_change_share=0.9661487716520736, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0), 7: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.0029113126526266477, lose_less_than_5_percent_share=0.00904746763596632, lose_share=0.011958780288592966, no_change_share=0.988041219711407, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0), 8: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.0005094937950111613, lose_less_than_5_percent_share=0.0031321041000860993, lose_share=0.0036415978950972605, no_change_share=0.9963584021049028, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0), 9: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.00029487893089546547, lose_less_than_5_percent_share=0.0035164203584642836, lose_share=0.003811299289359749, no_change_share=0.9961887007106403, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0), 10: IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.0002281227488112428, lose_less_than_5_percent_share=0.003779501887019752, lose_share=0.004007624635830995, no_change_share=0.9867296828146133, gain_share=0.0, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=0.0)}, all=IncomeMeasureSpecificDecileWinnersLosersGroupOutcomes(lose_more_than_5_percent_share=0.05886871339782029, lose_less_than_5_percent_share=0.02962594712536097, lose_share=0.08849466052318125, no_change_share=0.9105479297614693, gain_share=3.096710278402974e-05, gain_less_than_5_percent_share=0.0, gain_more_than_5_percent_share=3.096710278402974e-05))), wealth=None))" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -61,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -255,7 +245,7 @@ ], "metadata": { "kernelspec": { - "display_name": "base", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -269,7 +259,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.9" + "version": "3.11.11" } }, "nbformat": 4, diff --git a/docs/basic/calculate_single_economy.ipynb b/docs/basic/calculate_single_economy.ipynb index 6e330de3..2a7de6e1 100644 --- a/docs/basic/calculate_single_economy.ipynb +++ b/docs/basic/calculate_single_economy.ipynb @@ -34,7 +34,7 @@ " time_period=2025,\n", ")\n", "\n", - "sim.calculate_single_economy()" + "result = sim.calculate_single_economy()" ] }, { diff --git a/pyproject.toml b/pyproject.toml index 82c45fc6..c9d75b71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ requires-python = ">=3.6" dependencies = [ "policyengine_core>=3.10", "policyengine-uk", - "policyengine-us", + "policyengine-us>=1.213.1", "microdf_python", "getpass4", "pydantic", From f7a8945693b52d5543e37c83348951c736e79783 Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Fri, 14 Mar 2025 12:25:37 +0000 Subject: [PATCH 6/7] Temp remove charts --- docs/_toc.yml | 1 - docs/basic/create_charts.ipynb | 5191 ----------------- .../macro/comparison/charts/__init__.py | 5 - .../outputs/macro/comparison/charts/budget.py | 90 - .../comparison/charts/budget_by_program.py | 96 - .../outputs/macro/comparison/charts/decile.py | 89 - .../macro/comparison/charts/inequality.py | 77 - .../macro/comparison/charts/winners_losers.py | 148 - 8 files changed, 5697 deletions(-) delete mode 100644 docs/basic/create_charts.ipynb delete mode 100644 policyengine/outputs/macro/comparison/charts/__init__.py delete mode 100644 policyengine/outputs/macro/comparison/charts/budget.py delete mode 100644 policyengine/outputs/macro/comparison/charts/budget_by_program.py delete mode 100644 policyengine/outputs/macro/comparison/charts/decile.py delete mode 100644 policyengine/outputs/macro/comparison/charts/inequality.py delete mode 100644 policyengine/outputs/macro/comparison/charts/winners_losers.py diff --git a/docs/_toc.yml b/docs/_toc.yml index e060ef57..65fd941b 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -7,7 +7,6 @@ parts: - file: basic/calculate_household_comparison - file: basic/calculate_single_economy - file: basic/calculate_economy_comparison - - file: basic/create_charts - caption: Reference chapters: - file: reference/parameters_us diff --git a/docs/basic/create_charts.ipynb b/docs/basic/create_charts.ipynb deleted file mode 100644 index c2a8d65a..00000000 --- a/docs/basic/create_charts.ipynb +++ /dev/null @@ -1,5191 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Create charts for an economic comparison\n", - "\n", - "This package also comes with utilities that allow you to create charts from the economy comparison operation. In this notebook, we'll walk through them." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# First, set up the simulation\n", - "\n", - "from policyengine import Simulation\n", - "from policyengine.outputs.macro.comparison.charts import *\n", - "\n", - "sim = Simulation(\n", - " country=\"uk\",\n", - " scope=\"macro\",\n", - " reform={\n", - " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": 10_000,\n", - " },\n", - " title=\"Lowering the personal allowance to £10,000\" # Required for charts\n", - ")\n", - "\n", - "from policyengine.utils.charts import add_fonts\n", - "\n", - "add_fonts() # Load the right font (Roboto Serif) in this notebook" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Budget\n", - "\n", - "The budget chart shows high-level budget changes under the reform." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "decreasing": { - "marker": { - "color": "#616161" - } - }, - "increasing": { - "marker": { - "color": "#2C6496" - } - }, - "measure": [ - "relative", - "relative", - "total" - ], - "text": [ - "£24.2bn", - "£0.7bn", - "£23.5bn" - ], - "textposition": "inside", - "totals": { - "marker": { - "color": "#2C6496" - } - }, - "type": "waterfall", - "x": [ - "Tax revenues", - "Government spending", - "Public sector net worth" - ], - "y": [ - 24.18759785404004, - 0.7363357242798462, - 23.45126212976019 - ] - } - ], - "layout": { - "annotations": [ - { - "showarrow": false, - "text": "Source: PolicyEngine UK tax-benefit microsimulation model (version 2.18.0)", - "x": 0, - "xanchor": "left", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "font": { - "color": "black", - "family": "Roboto Serif" - }, - "height": 600, - "images": [ - { - "sizex": 0.15, - "sizey": 0.15, - "source": "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png", - "x": 1.1, - "xanchor": "right", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "margin": { - "b": 120, - "l": 120, - "r": 120, - "t": 120 - }, - "modebar": { - "activecolor": "#F4F4F4", - "bgcolor": "#F4F4F4", - "color": "#F4F4F4" - }, - "paper_bgcolor": "#F4F4F4", - "plot_bgcolor": "#F4F4F4", - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "white", - "showlakes": true, - "showland": true, - "subunitcolor": "#C8D4E3" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "white", - "polar": { - "angularaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - }, - "bgcolor": "white", - "radialaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "yaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "zaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "baxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "bgcolor": "white", - "caxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - } - } - }, - "title": { - "text": "Lowering the personal allowance to £10,000 would raise £23.5bn" - }, - "uniformtext": { - "minsize": 12, - "mode": "hide" - }, - "width": 800, - "xaxis": { - "gridcolor": "#F4F4F4", - "ticksuffix": "", - "title": { - "text": "" - }, - "zerolinecolor": "#F4F4F4" - }, - "yaxis": { - "gridcolor": "#F4F4F4", - "ticksuffix": "", - "title": { - "text": "Budgetary impact (£bn)" - }, - "zerolinecolor": "#616161" - } - } - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "create_budget_comparison_chart(sim)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is also the budget by program chart, which splits out the budgetary impact by program." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "decreasing": { - "marker": { - "color": "#616161" - } - }, - "increasing": { - "marker": { - "color": "#2C6496" - } - }, - "measure": [ - "relative", - "relative", - "relative", - "relative", - "total" - ], - "text": [ - "£24.2bn", - "-£0.4bn", - "-£0.2bn", - "-£1.0bn", - "£23.0bn" - ], - "textposition": "inside", - "totals": { - "marker": { - "color": "#2C6496" - } - }, - "type": "waterfall", - "x": [ - "Income Tax", - "Universal Credit", - "Pension Credit", - "Other", - "Combined" - ], - "y": [ - 24.2, - -0.4, - -0.2, - -1, - 23 - ] - } - ], - "layout": { - "annotations": [ - { - "showarrow": false, - "text": "Source: PolicyEngine UK tax-benefit microsimulation model (version 2.18.0)", - "x": 0, - "xanchor": "left", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "font": { - "color": "black", - "family": "Roboto Serif" - }, - "height": 600, - "images": [ - { - "sizex": 0.15, - "sizey": 0.15, - "source": "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png", - "x": 1.1, - "xanchor": "right", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "margin": { - "b": 120, - "l": 120, - "r": 120, - "t": 120 - }, - "modebar": { - "activecolor": "#F4F4F4", - "bgcolor": "#F4F4F4", - "color": "#F4F4F4" - }, - "paper_bgcolor": "#F4F4F4", - "plot_bgcolor": "#F4F4F4", - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "white", - "showlakes": true, - "showland": true, - "subunitcolor": "#C8D4E3" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "white", - "polar": { - "angularaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - }, - "bgcolor": "white", - "radialaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "yaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "zaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "baxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "bgcolor": "white", - "caxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - } - } - }, - "title": { - "text": "Lowering the personal allowance to £10,000 would raise £23bn" - }, - "uniformtext": { - "minsize": 12, - "mode": "hide" - }, - "width": 800, - "xaxis": { - "gridcolor": "#F4F4F4", - "ticksuffix": "", - "title": { - "text": "" - }, - "zerolinecolor": "#F4F4F4" - }, - "yaxis": { - "gridcolor": "#F4F4F4", - "ticksuffix": "", - "title": { - "text": "Budgetary impact (bn)" - }, - "zerolinecolor": "#616161" - } - } - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "create_budget_program_comparison_chart(sim)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Decile\n", - "\n", - "The decile chart shows the impact of the reform on each decile of the income distribution. You can specify whether this is over income deciles, or wealth deciles, in the UK." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "marker": { - "color": [ - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161" - ] - }, - "text": [ - "-1.1%", - "-1.5%", - "-1.7%", - "-1.8%", - "-1.5%", - "-1.9%", - "-2.0%", - "-1.9%", - "-1.8%", - "-0.8%" - ], - "type": "bar", - "x": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10 - ], - "y": [ - -0.010774457133013601, - -0.01452619007920295, - -0.017437944033923014, - -0.018396321126487758, - -0.015306613953115554, - -0.019266000449108846, - -0.019665841659677847, - -0.018543594207703178, - -0.018167225868890278, - -0.007966273529744838 - ] - } - ], - "layout": { - "annotations": [ - { - "showarrow": false, - "text": "Source: PolicyEngine UK tax-benefit microsimulation model (version 2.18.0)", - "x": 0, - "xanchor": "left", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "font": { - "color": "black", - "family": "Roboto Serif" - }, - "height": 600, - "images": [ - { - "sizex": 0.15, - "sizey": 0.15, - "source": "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png", - "x": 1.1, - "xanchor": "right", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "margin": { - "b": 120, - "l": 120, - "r": 120, - "t": 120 - }, - "modebar": { - "activecolor": "#F4F4F4", - "bgcolor": "#F4F4F4", - "color": "#F4F4F4" - }, - "paper_bgcolor": "#F4F4F4", - "plot_bgcolor": "#F4F4F4", - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "white", - "showlakes": true, - "showland": true, - "subunitcolor": "#C8D4E3" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "white", - "polar": { - "angularaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - }, - "bgcolor": "white", - "radialaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "yaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "zaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "baxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "bgcolor": "white", - "caxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - } - } - }, - "title": { - "text": "Lowering the personal allowance to £10,000 would decrease the net income of
households by 1.6% " - }, - "uniformtext": { - "minsize": 12, - "mode": "hide" - }, - "width": 800, - "xaxis": { - "gridcolor": "#F4F4F4", - "ticksuffix": "", - "tickvals": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10 - ], - "title": { - "text": "Income decile" - }, - "zerolinecolor": "#F4F4F4" - }, - "yaxis": { - "gridcolor": "#F4F4F4", - "tickformat": ".0%", - "ticksuffix": "", - "title": { - "text": "Average change to net income (%)" - }, - "zerolinecolor": "#616161" - } - } - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "create_decile_chart(sim, decile_variable=\"income\", relative=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Winners and losers\n", - "\n", - "The winners and losers chart shows in each decile (and overall) the share of people who come out ahead or behind under the reform. Again, you can specify whether the deciles are income or wealth in the UK." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "marker": { - "color": [ - "#2C6496", - "#D8E6F3", - "#F2F2F2", - "#BDBDBD", - "#616161" - ] - }, - "name": "All deciles", - "orientation": "h", - "showlegend": false, - "text": [ - "0.0%", - "0.1%", - "13.1%", - "74.3%", - "12.5%" - ], - "type": "bar", - "x": [ - 0, - 0.0012506888662903927, - 0.13111063617035676, - 0.7427831391111167, - 0.12485553585223622 - ], - "xaxis": "x", - "y": [ - "All", - "All", - "All", - "All", - "All" - ], - "yaxis": "y" - }, - { - "customdata": [ - "Gain more than 5%, 1: 0.0%", - "Gain more than 5%, 2: 0.0%", - "Gain more than 5%, 3: 0.0%", - "Gain more than 5%, 4: 0.0%", - "Gain more than 5%, 5: 0.0%", - "Gain more than 5%, 6: 0.0%", - "Gain more than 5%, 7: 0.0%", - "Gain more than 5%, 8: 0.0%", - "Gain more than 5%, 9: 0.0%", - "Gain more than 5%, 10: 0.0%", - "Gain less than 5%, 1: 0.0%", - "Gain less than 5%, 2: 0.8%", - "Gain less than 5%, 3: 0.4%", - "Gain less than 5%, 4: 0.0%", - "Gain less than 5%, 5: 0.0%", - "Gain less than 5%, 6: 0.0%", - "Gain less than 5%, 7: 0.0%", - "Gain less than 5%, 8: 0.0%", - "Gain less than 5%, 9: 0.0%", - "Gain less than 5%, 10: 0.0%", - "No change, 1: 62.5%", - "No change, 2: 27.6%", - "No change, 3: 17.2%", - "No change, 4: 10.3%", - "No change, 5: 2.6%", - "No change, 6: 2.9%", - "No change, 7: 1.0%", - "No change, 8: 0.8%", - "No change, 9: 1.5%", - "No change, 10: 9.0%", - "Lose less than 5%, 1: 20.3%", - "Lose less than 5%, 2: 54.3%", - "Lose less than 5%, 3: 45.3%", - "Lose less than 5%, 4: 74.6%", - "Lose less than 5%, 5: 95.7%", - "Lose less than 5%, 6: 78.5%", - "Lose less than 5%, 7: 80.9%", - "Lose less than 5%, 8: 94.4%", - "Lose less than 5%, 9: 97.6%", - "Lose less than 5%, 10: 91.0%", - "Lose more than 5%, 1: 17.1%", - "Lose more than 5%, 2: 17.2%", - "Lose more than 5%, 3: 37.0%", - "Lose more than 5%, 4: 15.1%", - "Lose more than 5%, 5: 1.7%", - "Lose more than 5%, 6: 18.6%", - "Lose more than 5%, 7: 18.1%", - "Lose more than 5%, 8: 4.8%", - "Lose more than 5%, 9: 1.0%", - "Lose more than 5%, 10: 0.0%" - ], - "hovertemplate": "%{customdata}", - "marker": { - "color": [ - "#2C6496", - "#2C6496", - "#2C6496", - "#2C6496", - "#2C6496", - "#2C6496", - "#2C6496", - "#2C6496", - "#2C6496", - "#2C6496", - "#D8E6F3", - "#D8E6F3", - "#D8E6F3", - "#D8E6F3", - "#D8E6F3", - "#D8E6F3", - "#D8E6F3", - "#D8E6F3", - "#D8E6F3", - "#D8E6F3", - "#F2F2F2", - "#F2F2F2", - "#F2F2F2", - "#F2F2F2", - "#F2F2F2", - "#F2F2F2", - "#F2F2F2", - "#F2F2F2", - "#F2F2F2", - "#F2F2F2", - "#BDBDBD", - "#BDBDBD", - "#BDBDBD", - "#BDBDBD", - "#BDBDBD", - "#BDBDBD", - "#BDBDBD", - "#BDBDBD", - "#BDBDBD", - "#BDBDBD", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161", - "#616161" - ] - }, - "name": "Deciles", - "orientation": "h", - "text": [ - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.8%", - "0.4%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "0.0%", - "62.5%", - "27.6%", - "17.2%", - "10.3%", - "2.6%", - "2.9%", - "1.0%", - "0.8%", - "1.5%", - "9.0%", - "20.3%", - "54.3%", - "45.3%", - "74.6%", - "95.7%", - "78.5%", - "80.9%", - "94.4%", - "97.6%", - "91.0%", - "17.1%", - "17.2%", - "37.0%", - "15.1%", - "1.7%", - "18.6%", - "18.1%", - "4.8%", - "1.0%", - "0.0%" - ], - "textposition": "inside", - "type": "bar", - "x": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0.00003002075376702071, - 0.008309730310565712, - 0.0042514917572317205, - 0, - 0, - 0, - 5.104037721866914e-7, - 1.224587452954663e-7, - 0, - 0, - 0.6251904617648583, - 0.2761051112600827, - 0.17241658338662263, - 0.10326230200712576, - 0.02634261147112231, - 0.029121268727080388, - 0.009779479442778611, - 0.007915780728303014, - 0.014550917092224268, - 0.09022786691788508, - 0.20347253259186693, - 0.5434876317349786, - 0.4532449619985432, - 0.7461001383808286, - 0.9570849978504702, - 0.7850737490397103, - 0.8087431337210494, - 0.9444749389980199, - 0.9758083400649616, - 0.9097721330821149, - 0.17130698488950774, - 0.17209752669437295, - 0.37008696285760245, - 0.15063755961204564, - 0.01657239067840755, - 0.18580498223320932, - 0.1814768764323998, - 0.047609157814931725, - 0.009640742842814045, - 0 - ], - "xaxis": "x2", - "y": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10 - ], - "yaxis": "y2" - } - ], - "layout": { - "annotations": [ - { - "showarrow": false, - "text": "Source: PolicyEngine UK tax-benefit microsimulation model (version 2.18.0)", - "x": 0, - "xanchor": "left", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "barmode": "stack", - "font": { - "color": "black", - "family": "Roboto Serif" - }, - "grid": { - "columns": 1, - "rows": 2 - }, - "height": 600, - "images": [ - { - "sizex": 0.15, - "sizey": 0.15, - "source": "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png", - "x": 1.1, - "xanchor": "right", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "margin": { - "b": 120, - "l": 120, - "r": 120, - "t": 120 - }, - "modebar": { - "activecolor": "#F4F4F4", - "bgcolor": "#F4F4F4", - "color": "#F4F4F4" - }, - "paper_bgcolor": "#F4F4F4", - "plot_bgcolor": "#F4F4F4", - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "white", - "showlakes": true, - "showland": true, - "subunitcolor": "#C8D4E3" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "white", - "polar": { - "angularaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - }, - "bgcolor": "white", - "radialaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "yaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "zaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "baxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "bgcolor": "white", - "caxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - } - } - }, - "title": { - "text": "Lowering the personal allowance to £10,000 would raise the net income of 0.1% of
people " - }, - "uniformtext": { - "minsize": 12, - "mode": "hide" - }, - "width": 800, - "xaxis": { - "anchor": "y", - "fixedrange": true, - "gridcolor": "#F4F4F4", - "matches": "x2", - "showgrid": false, - "showticklabels": false, - "tickformat": ".0%", - "ticksuffix": "", - "title": { - "text": "" - }, - "zerolinecolor": "#F4F4F4" - }, - "xaxis2": { - "anchor": "y2", - "fixedrange": true, - "tickformat": ".0%", - "title": { - "text": "Population share" - } - }, - "yaxis": { - "domain": [ - 0.91, - 1 - ], - "gridcolor": "#F4F4F4", - "ticksuffix": "", - "tickvals": [ - "All" - ], - "title": { - "text": "" - }, - "zerolinecolor": "#F4F4F4" - }, - "yaxis2": { - "anchor": "x2", - "domain": [ - 0, - 0.85 - ], - "tickvals": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10 - ], - "title": { - "text": "Population share" - } - } - } - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "create_winners_losers_chart(sim, decile_variable=\"income\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Inequality\n", - "\n", - "The inequality chart shows the impact of the reform on various inequality measures: the Gini coefficient, the share of income held by the top 10% of households (by income) and the top 1% share." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "marker": { - "color": [ - "#616161", - "#616161", - "#616161" - ] - }, - "text": [ - "0.2%", - "0.6%", - "1.4%" - ], - "type": "bar", - "x": [ - "Gini index", - "Top 10% share", - "Top 1% share" - ], - "y": [ - 0.002379218659187675, - 0.006010878001505188, - 0.014167378755875062 - ] - } - ], - "layout": { - "annotations": [ - { - "showarrow": false, - "text": "Source: PolicyEngine UK tax-benefit microsimulation model (version 2.18.0)", - "x": 0, - "xanchor": "left", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "font": { - "color": "black", - "family": "Roboto Serif" - }, - "height": 600, - "images": [ - { - "sizex": 0.15, - "sizey": 0.15, - "source": "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png", - "x": 1.1, - "xanchor": "right", - "xref": "paper", - "y": -0.2, - "yanchor": "bottom", - "yref": "paper" - } - ], - "margin": { - "b": 120, - "l": 120, - "r": 120, - "t": 120 - }, - "modebar": { - "activecolor": "#F4F4F4", - "bgcolor": "#F4F4F4", - "color": "#F4F4F4" - }, - "paper_bgcolor": "#F4F4F4", - "plot_bgcolor": "#F4F4F4", - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "white", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "#C8D4E3", - "linecolor": "#C8D4E3", - "minorgridcolor": "#C8D4E3", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "white", - "showlakes": true, - "showland": true, - "subunitcolor": "#C8D4E3" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "white", - "polar": { - "angularaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - }, - "bgcolor": "white", - "radialaxis": { - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "yaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - }, - "zaxis": { - "backgroundcolor": "white", - "gridcolor": "#DFE8F3", - "gridwidth": 2, - "linecolor": "#EBF0F8", - "showbackground": true, - "ticks": "", - "zerolinecolor": "#EBF0F8" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "baxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - }, - "bgcolor": "white", - "caxis": { - "gridcolor": "#DFE8F3", - "linecolor": "#A2B1C6", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "#EBF0F8", - "linecolor": "#EBF0F8", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "#EBF0F8", - "zerolinewidth": 2 - } - } - }, - "title": { - "text": "Lowering the personal allowance to £10,000 would raise inequality" - }, - "uniformtext": { - "minsize": 12, - "mode": "hide" - }, - "width": 800, - "xaxis": { - "gridcolor": "#F4F4F4", - "ticksuffix": "", - "title": { - "text": "" - }, - "zerolinecolor": "#F4F4F4" - }, - "yaxis": { - "gridcolor": "#F4F4F4", - "tickformat": ".0%", - "ticksuffix": "", - "title": { - "text": "Change (%)" - }, - "zerolinecolor": "#616161" - } - } - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "create_inequality_chart(sim, relative=True)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.14" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/policyengine/outputs/macro/comparison/charts/__init__.py b/policyengine/outputs/macro/comparison/charts/__init__.py deleted file mode 100644 index 9db0bf9c..00000000 --- a/policyengine/outputs/macro/comparison/charts/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .budget import create_budget_comparison_chart -from .budget_by_program import create_budget_program_comparison_chart -from .decile import create_decile_chart -from .winners_losers import create_winners_losers_chart -from .inequality import create_inequality_chart diff --git a/policyengine/outputs/macro/comparison/charts/budget.py b/policyengine/outputs/macro/comparison/charts/budget.py deleted file mode 100644 index f7e453fd..00000000 --- a/policyengine/outputs/macro/comparison/charts/budget.py +++ /dev/null @@ -1,90 +0,0 @@ -import plotly.express as px -import plotly.graph_objects as go -import typing - -from policyengine import Simulation - -from pydantic import BaseModel -from policyengine.utils.charts import * - - -def create_budget_comparison_chart( - simulation: Simulation, -) -> go.Figure: - """Create a budget comparison chart.""" - if not simulation.is_comparison: - raise ValueError("Simulation must be a comparison simulation.") - - economy = simulation.calculate_economy_comparison() - - if simulation.options.country == "uk": - x_values = [ - "Tax revenues", - "Government spending", - "Public sector net worth", - ] - y_values = [ - economy.fiscal.change.federal_tax, - -economy.fiscal.change.government_spending, - economy.fiscal.change.federal_balance, - ] - else: - x_values = [ - "Federal tax revenues", - "State tax revenues", - "Federal government spending", - ] - y_values = [ - economy.fiscal.change.federal_tax, - economy.fiscal.change.state_tax, - -economy.fiscal.change.government_spending, - ] - - y_values = [value / 1e9 for value in y_values] - - net_change = round(economy.fiscal.change.federal_balance / 1e9, 1) - - if net_change > 0: - description = f"raise ${net_change}bn" - elif net_change < 0: - description = f"cost ${-net_change}bn" - else: - description = "have no effect on government finances" - - chart = go.Figure( - data=[ - go.Waterfall( - x=x_values, - y=y_values, - measure=["relative"] * (len(x_values) - 1) + ["total"], - textposition="inside", - text=[f"${value:.1f}bn" for value in y_values], - increasing=dict( - marker=dict( - color=BLUE, - ) - ), - decreasing=dict( - marker=dict( - color=DARK_GRAY, - ) - ), - totals=dict( - marker=dict( - color=BLUE if net_change > 0 else DARK_GRAY, - ) - ), - ), - ] - ).update_layout( - title=f"{simulation.options.title} would {description}", - yaxis_title="Budgetary impact ($bn)", - uniformtext=dict( - mode="hide", - minsize=12, - ), - ) - - return format_fig( - chart, country=simulation.options.country, add_zero_line=True - ) diff --git a/policyengine/outputs/macro/comparison/charts/budget_by_program.py b/policyengine/outputs/macro/comparison/charts/budget_by_program.py deleted file mode 100644 index 382cdfcf..00000000 --- a/policyengine/outputs/macro/comparison/charts/budget_by_program.py +++ /dev/null @@ -1,96 +0,0 @@ -import plotly.express as px -import plotly.graph_objects as go -import typing - -from policyengine import Simulation - -from pydantic import BaseModel -from policyengine.utils.charts import * - - -def create_budget_program_comparison_chart( - simulation: Simulation, -) -> go.Figure: - """Create a budget comparison chart.""" - if not simulation.is_comparison: - raise ValueError("Simulation must be a comparison simulation.") - - if not simulation.options.country == "uk": - raise ValueError("This chart is only available for the UK.") - - economy = simulation.calculate_economy_comparison() - - change_programs = economy.fiscal.change.tax_benefit_programs - - change_programs = { - program: change_programs[program] - for program in change_programs - if round(change_programs[program] / 1e9, 1) != 0 - } - - labels = [ - simulation.baseline_simulation.tax_benefit_system.variables.get( - program - ).label - for program in change_programs - ] - - x_values = labels - y_values = [ - round(change_programs[program] / 1e9, 1) for program in change_programs - ] - - total_from_programs = round(sum(y_values)) - total_overall = round(economy.fiscal.change.federal_balance / 1e9) - - if total_from_programs != total_overall: - x_values.append("Other") - y_values.append(total_overall - total_from_programs) - - x_values.append("Combined") - y_values.append(total_overall) - - if total_overall > 0: - description = f"raise ${total_overall}bn" - elif total_overall < 0: - description = f"cost ${-total_overall}bn" - else: - description = "have no effect on government finances" - - chart = go.Figure( - data=[ - go.Waterfall( - x=x_values, - y=y_values, - measure=["relative"] * (len(x_values) - 1) + ["total"], - textposition="inside", - text=[f"${value:.1f}bn" for value in y_values], - increasing=dict( - marker=dict( - color=BLUE, - ) - ), - decreasing=dict( - marker=dict( - color=DARK_GRAY, - ) - ), - totals=dict( - marker=dict( - color=BLUE if total_overall > 0 else DARK_GRAY, - ) - ), - ), - ] - ).update_layout( - title=f"{simulation.options.title} would {description}", - yaxis_title="Budgetary impact (bn)", - uniformtext=dict( - mode="hide", - minsize=12, - ), - ) - - return format_fig( - chart, country=simulation.options.country, add_zero_line=True - ) diff --git a/policyengine/outputs/macro/comparison/charts/decile.py b/policyengine/outputs/macro/comparison/charts/decile.py deleted file mode 100644 index bd2c69e7..00000000 --- a/policyengine/outputs/macro/comparison/charts/decile.py +++ /dev/null @@ -1,89 +0,0 @@ -import plotly.express as px -import plotly.graph_objects as go -import typing - -from policyengine import Simulation - -from pydantic import BaseModel -from policyengine.utils.charts import * -from typing import Literal - - -def create_decile_chart( - simulation: Simulation, - decile_variable: Literal["income", "wealth"], - relative: bool, -) -> go.Figure: - """Create a budget comparison chart.""" - if not simulation.is_comparison: - raise ValueError("Simulation must be a comparison simulation.") - - economy = simulation.calculate_economy_comparison() - - if decile_variable == "income": - data = economy.distributional.income.income_change - else: - data = economy.distributional.wealth.income_change - - if relative: - data = data.relative - else: - data = data.average - - avg_change = sum(data.values()) / len(data) - - if relative: - text = [f"{value:.1%}" for value in data.values()] - avg_change = round(avg_change, 3) - else: - text = [f"${value:,.0f}" for value in data.values()] - avg_change = round(avg_change) - - avg_change_str = ( - f"${abs(avg_change):,.0f}" - if not relative - else f"{abs(avg_change):.1%}" - ) - - if avg_change > 0: - description = ( - f"increase the net income of households by {avg_change_str}" - ) - elif avg_change < 0: - description = ( - f"decrease the net income of households by {avg_change_str}" - ) - else: - description = "have no effect on household net income" - - chart = go.Figure( - data=[ - go.Bar( - x=list(data.keys()), - y=list(data.values()), - text=text, - marker=dict( - color=[ - BLUE if value > 0 else DARK_GRAY - for value in data.values() - ] - ), - ) - ] - ).update_layout( - title=f"{simulation.options.title} would {description}", - yaxis_title=f"Average change to net income ({'%' if relative else '$'})", - yaxis_tickformat="$,.0f" if not relative else ".0%", - xaxis_title=( - "Income decile" if decile_variable == "income" else "Wealth decile" - ), - uniformtext=dict( - mode="hide", - minsize=12, - ), - xaxis_tickvals=list(data.keys()), - ) - - return format_fig( - chart, country=simulation.options.country, add_zero_line=True - ) diff --git a/policyengine/outputs/macro/comparison/charts/inequality.py b/policyengine/outputs/macro/comparison/charts/inequality.py deleted file mode 100644 index e0db5c29..00000000 --- a/policyengine/outputs/macro/comparison/charts/inequality.py +++ /dev/null @@ -1,77 +0,0 @@ -import plotly.express as px -import plotly.graph_objects as go -import typing - -from policyengine import Simulation - -from pydantic import BaseModel -from policyengine.utils.charts import * - - -def create_inequality_chart( - simulation: Simulation, - relative: bool, -) -> go.Figure: - """Create a budget comparison chart.""" - if not simulation.is_comparison: - raise ValueError("Simulation must be a comparison simulation.") - - economy = simulation.calculate_economy_comparison() - - x_values = [ - "Gini index", - "Top 10% share", - "Top 1% share", - ] - - if relative: - data = economy.inequality.relative_change - else: - data = economy.inequality.change - - y_values = [ - data.gini, - data.top_10_share, - data.top_1_share, - ] - - if all(value < 0 for value in y_values): - description = f"lower inequality" - elif all(value > 0 for value in y_values): - description = f"raise inequality" - else: - description = "have an ambiguous effect on inequality" - - if not relative: - y_values = [value * 100 for value in y_values] - - chart = go.Figure( - data=[ - go.Bar( - x=x_values, - y=y_values, - text=[ - f"{value:.1%}" if relative else f"{value:.1f}pp" - for value in y_values - ], - marker=dict( - color=[ - BLUE if value < 0 else DARK_GRAY for value in y_values - ] - ), - ), - ] - ).update_layout( - title=f"{simulation.options.title} would {description}", - yaxis_title="Change" + (" (%)" if relative else ""), - yaxis_ticksuffix="pp" if not relative else "", - yaxis_tickformat=".0%" if relative else ".1f", - uniformtext=dict( - mode="hide", - minsize=12, - ), - ) - - return format_fig( - chart, country=simulation.options.country, add_zero_line=True - ) diff --git a/policyengine/outputs/macro/comparison/charts/winners_losers.py b/policyengine/outputs/macro/comparison/charts/winners_losers.py deleted file mode 100644 index db21e18d..00000000 --- a/policyengine/outputs/macro/comparison/charts/winners_losers.py +++ /dev/null @@ -1,148 +0,0 @@ -import plotly.express as px -import plotly.graph_objects as go -import typing - -from policyengine import Simulation - -from pydantic import BaseModel -from policyengine.utils.charts import * -from typing import Literal, Dict - - -COLOR_MAP = { - "Gain more than 5%": BLUE, - "Gain less than 5%": BLUE_95, - "No change": LIGHT_GRAY, - "Lose less than 5%": MEDIUM_LIGHT_GRAY, - "Lose more than 5%": DARK_GRAY, -} - -FORMATTED_KEYS = { - "gain_more_than_5_percent_share": "Gain more than 5%", - "gain_less_than_5_percent_share": "Gain less than 5%", - "no_change_share": "No change", - "lose_less_than_5_percent_share": "Lose less than 5%", - "lose_more_than_5_percent_share": "Lose more than 5%", -} - - -def create_winners_losers_chart( - simulation: Simulation, - decile_variable: Literal["income", "wealth"], -) -> go.Figure: - """Create a budget comparison chart.""" - if not simulation.is_comparison: - raise ValueError("Simulation must be a comparison simulation.") - - economy = simulation.calculate_economy_comparison() - - if decile_variable == "income": - data = economy.distributional.income.winners_and_losers - else: - data = economy.distributional.wealth.winners_and_losers - - all_decile_data = {} - for key in FORMATTED_KEYS.keys(): - all_decile_data[FORMATTED_KEYS[key]] = data.all.model_dump()[key] - - all_decile_chart = go.Bar( - x=list(all_decile_data.values()), - y=["All"] * len(all_decile_data), - name="All deciles", - orientation="h", - yaxis="y", - xaxis="x", - showlegend=False, - text=[f"{value:.1%}" for value in all_decile_data.values()], - marker=dict(color=[COLOR_MAP[key] for key in all_decile_data.keys()]), - ) - - x_values = [] - y_values = [] - color_values = [] - text = [] - hover_text = [] - for outcome_type in FORMATTED_KEYS.keys(): - for decile in range(1, 11): - value = data.deciles[decile].model_dump()[outcome_type] - x_values.append(value) - y_values.append(decile) - color_values.append(COLOR_MAP[FORMATTED_KEYS[outcome_type]]) - text.append(f"{value:.1%}") - hover_text.append( - f"{FORMATTED_KEYS[outcome_type]}, {decile}: {value:.1%}" - ) - - decile_chart = go.Bar( - x=x_values, - y=y_values, - name="Deciles", - orientation="h", - yaxis="y2", - xaxis="x2", - text=text, - textposition="inside", - marker=dict( - color=color_values, - ), - customdata=hover_text, - hovertemplate="%{customdata}", - # Need to sort out showlegend, currently fiddly. - ) - - fig = go.Figure( - data=[ - all_decile_chart, - decile_chart, - ] - ) - - winner_share = round( - economy.distributional.income.winners_and_losers.all.gain_share, 3 - ) - - if winner_share > 0: - description = f"raise the net income of {winner_share:.1%} of people" - elif winner_share < 0: - description = ( - f"decrease the net income of {-winner_share:.1%} of people" - ) - else: - description = "have no effect on household net income" - - fig.update_layout( - barmode="stack", - grid=dict( - rows=2, - columns=1, - ), - yaxis=dict( - title="", - tickvals=["All"], - domain=[0.91, 1], - ), - xaxis=dict( - title="", - tickformat=".0%", - anchor="y", - matches="x2", - showgrid=False, - showticklabels=False, - fixedrange=True, - ), - xaxis2=dict( - title="Population share", - tickformat=".0%", - anchor="y2", - fixedrange=True, - ), - yaxis2=dict( - title="Population share", - tickvals=list(range(1, 11)), - anchor="x2", - domain=[0, 0.85], - ), - title=f"{simulation.options.title} would {description}", - ) - - return format_fig(fig, country=simulation.options.country) From 029206c7ebe6a11977202486534909affd8323da Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Fri, 14 Mar 2025 12:27:41 +0000 Subject: [PATCH 7/7] Format --- policyengine/simulation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/policyengine/simulation.py b/policyengine/simulation.py index 869520ef..abec5e2b 100644 --- a/policyengine/simulation.py +++ b/policyengine/simulation.py @@ -33,9 +33,7 @@ str | dict | Any | None ) # Needs stricter typing. Any==policyengine_core.data.Dataset, but pydantic refuses for some reason. TimePeriodType = int -ReformType = ( - ParametricReform | Type[StructuralReform] | None -) +ReformType = ParametricReform | Type[StructuralReform] | None RegionType = str | None SubsampleType = int | None