def headline_metrics(baseline: Microsimulation, reformed: Microsimulation) -> dict: """Compute headline society-wide metrics. :param baseline: Baseline simulation. :type baseline: Microsimulation :param reformed: Reform simulation. :type reformed: Microsimulation :return: Dictionary with net_cost, poverty_change, winner_share, loser_share, and gini_change. :rtype: dict """ new_income = reformed.calc("equiv_household_net_income", map_to="person") old_income = baseline.calc("equiv_household_net_income", map_to="person") gain = new_income - old_income net_cost = (reformed.calc("net_income").sum() - baseline.calc("net_income").sum()) poverty_change = pct_change( baseline.calc("in_poverty_bhc", map_to="person").mean(), reformed.calc("in_poverty_bhc", map_to="person").mean(), ) winner_share = (gain > 1).mean() loser_share = (gain < -1).mean() gini_change = pct_change(old_income.gini(), new_income.gini()) return dict( net_cost=gbp(net_cost), net_cost_numeric=(net_cost), poverty_change=float(poverty_change), winner_share=float(winner_share), loser_share=float(loser_share), gini_change=float(gini_change), )
def compute_reform(): params = request.json reform = Microsimulation(create_reform(params)) new_income = reform.calc("equiv_household_net_income", map_to="person") old_income = baseline.calc("equiv_household_net_income", map_to="person") gain = new_income - old_income net_cost = reform.calc("net_income").sum() - baseline.calc("net_income").sum() decile_plot = px.bar(gain.groupby(old_income.percentile_rank()).mean()).update_layout( title="Income effect by percentile", xaxis_title="Equivalised disposable income percentile", yaxis_title="Average income effect", yaxis_tickprefix="£", width=800, height=600, template="plotly_white", showlegend=False ).to_json() top_1_pct_share_effect = gain[old_income.percentile_rank() == 100].mean() top_10_pct_share_effect = gain[old_income.decile_rank() == 10].mean() median_effect = new_income.median() - old_income.median() return {"net_cost": gbp(net_cost), "decile_plot": json.loads(decile_plot), "1pct": top_1_pct_share_effect, "10pct": top_10_pct_share_effect, "median": median_effect}
def hover_label(component: str, amount: float, is_pop: bool) -> str: """Create a label for an individual point in a waterfall hovercard. :param component: Name of the component, e.g. "Tax revenues". :type component: str :param amount: Name of the component, e.g. "Tax revenues". :type amount: float :param is_pop: Whether the component is population- vs. household-level. :type is_pop: bool :return: Label for hovercard. :rtype: str """ # Reset household net income label to match the headline. if component == "Your net income": component = "Your annual net income" # Net impact bars should match the title. res = component # Flip the amount for labeling population benefits and household taxes. if component in ["Benefit outlays", "Your taxes"]: amount *= -1 # Round population estimates, not individual. abs_amount = round(abs(amount)) abs_amount_display = gbp(abs_amount) if is_pop else f"£{abs_amount:,}" # Branch logic, starting with no change. # More special handling of the net impact to match the title. if amount == 0: if component == "Net impact": return "Reform has no budgetary impact" return res + " would not change" if amount > 0: if component == "Net impact": # Population. return "Reform produces " + abs_amount_display + " net surplus" return res + " would rise by " + abs_amount_display if amount < 0: if component == "Net impact": # Population. return "Reform produces " + abs_amount_display + " net cost" return res + " would fall by " + abs_amount_display
def test_rounding(): from rdbl import gbp assert gbp(231e9) == "£231bn"