Charts#

PolicyEngine Core provides a set of chart utils to speed up data visualisation for PolicyEngine model-powered analyses. These use the PolicyEngine styling by default. The examples below use the PolicyEngine UK microsimulation model.

Bar chart#

The bar_chart function creates a bar chart from a dataframe.

# Reform code generated from the PolicyEngine export function.

from policyengine_uk import Microsimulation
from policyengine_core.reforms import Reform
from policyengine_core.periods import instant


def modify_parameters(parameters):
    parameters.gov.hmrc.income_tax.rates.uk[0].rate.update(
        start=instant("2023-01-01"), stop=instant("2028-12-31"), value=0.25
    )
    return parameters


class reform(Reform):
    def apply(self):
        self.modify_parameters(modify_parameters)


baseline = Microsimulation()
reformed = Microsimulation(reform=reform)

baseline_income = baseline.calculate("household_net_income", 2023)
reformed_income = reformed.calculate("household_net_income", 2023)
gain = reformed_income - baseline_income
decile = baseline.calculate("household_income_decile", 2023)
decile_impacts = (
    gain.groupby(decile).sum() / baseline_income.groupby(decile).sum()
)
decile_impacts = decile_impacts[decile_impacts.index != 0]

from policyengine_core.charts import *

display_fig(
    bar_chart(
        decile_impacts,
        title="Change in net income by decile",
        xaxis_title="Decile",
        yaxis_title="Change in net income",
        xaxis_tickvals=list(range(1, 11)),
        yaxis_tickformat=".0%",
        text_format=".1%",
        hover_text_function=lambda x, y: f"The {cardinal(x)} decile sees a {y:+.1%} in net income",
    )
)

Cross-section bar chart#

The cross-section bar chart is useful for showing the distribution of outcomes along different breakdowns.

lower_age_group = baseline.calculate("age", 2023) // 10
personal_gain = reformed.calculate(
    "household_net_income", 2023, map_to="person"
) - baseline.calculate("household_net_income", 2023, map_to="person")
personal_gain = personal_gain[lower_age_group < 8]
lower_age_group = lower_age_group[lower_age_group < 8] + 1

display_fig(
    cross_section_bar_chart(
        personal_gain,
        lower_age_group,
        slices=[-0.1, -0.01, 0.01, 0.1],
        xaxis_tickformat=".0%",
        category_names=[
            "Lose more than 10%",
            "Lose between 1% and 10%",
            "Experience less than 1% change",
            "Gain between 1% and 10%",
            "Gain more than 10%",
        ],
        yaxis_ticktext=[
            "Under 10",
            "10 to 19",
            "20 to 29",
            "30 to 39",
            "40 to 49",
            "50 to 59",
            "60 to 69",
            "70 to 79",
        ],
        color_discrete_map={
            "Lose more than 10%": DARK_GRAY,
            "Lose between 1% and 10%": MEDIUM_DARK_GRAY,
            "Experience less than 1% change": GRAY,
            "Gain between 1% and 10%": LIGHT_GRAY,
            "Gain more than 10%": BLUE,
        },
        legend_orientation="h",
        legend_y=-0.2,
        title="Gain by age",
        hover_text_function=lambda age, outcome, percent: f"{percent:.1%} of {age * 10:.0f} to {(age + 1) * 10:.0f} year olds {outcome.lower()} of their income",
    )
)