Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Advanced outputs

Beyond Aggregate and ChangeAggregate (covered in Core concepts), the package provides specialised output types for distributional analysis, poverty measurement, and inequality metrics.

All output types follow the same pattern: create an instance, call .run(), read the result fields. Convenience functions are provided for common use cases.

OutputCollection

Many convenience functions return an OutputCollection[T], a container holding both the individual output objects and a pandas DataFrame:

from policyengine.core import OutputCollection

# Returned by calculate_decile_impacts(), calculate_us_poverty_rates(), etc.
collection = calculate_us_poverty_rates(simulation)

# Access individual objects
for poverty in collection.outputs:
    print(f"{poverty.poverty_type}: {poverty.rate:.4f}")

# Access as DataFrame
print(collection.dataframe)

DecileImpact

Calculates the impact of a policy reform on a single income decile: baseline and reform mean income, absolute and relative change, and counts of people better off, worse off, and unchanged.

Using the convenience function

from policyengine.outputs.decile_impact import calculate_decile_impacts

decile_impacts = calculate_decile_impacts(
    dataset=dataset,
    tax_benefit_model_version=us_latest,
    baseline_policy=None,           # Current law
    reform_policy=reform,
    income_variable="household_net_income",  # Default for US
)

for d in decile_impacts.outputs:
    print(f"Decile {d.decile}: "
          f"baseline={d.baseline_mean:,.0f}, "
          f"reform={d.reform_mean:,.0f}, "
          f"change={d.absolute_change:+,.0f} "
          f"({d.relative_change:+.2f}%)")

Using directly

from policyengine.outputs.decile_impact import DecileImpact

impact = DecileImpact(
    baseline_simulation=baseline_sim,
    reform_simulation=reform_sim,
    income_variable="household_net_income",
    decile=5,  # 5th decile
)
impact.run()

print(f"Count better off: {impact.count_better_off:,.0f}")
print(f"Count worse off: {impact.count_worse_off:,.0f}")

Parameters

ParameterDefaultDescription
income_variableequiv_hbai_household_net_incomeIncome variable to group by and measure changes
decile_variableNoneUse a pre-computed grouping variable instead of qcut
entityAuto-detectedEntity level for the income variable
quantiles10Number of quantile groups (10 = deciles, 5 = quintiles)

For US simulations, use income_variable="household_net_income". The UK default (equiv_hbai_household_net_income) is the equivalised HBAI measure.

IntraDecileImpact

Classifies people within each decile into five income change categories:

CategoryThreshold
Lose more than 5%change <= -5%
Lose less than 5%-5% < change <= -0.1%
No change-0.1% < change <= 0.1%
Gain less than 5%0.1% < change <= 5%
Gain more than 5%change > 5%

Proportions are people-weighted (using household_count_people * household_weight).

Using the convenience function

from policyengine.outputs.intra_decile_impact import compute_intra_decile_impacts

intra = compute_intra_decile_impacts(
    baseline_simulation=baseline_sim,
    reform_simulation=reform_sim,
    income_variable="household_net_income",
)

for row in intra.outputs:
    if row.decile == 0:
        label = "Overall"
    else:
        label = f"Decile {row.decile}"
    print(f"{label}: "
          f"lose>5%={row.lose_more_than_5pct:.2%}, "
          f"lose<5%={row.lose_less_than_5pct:.2%}, "
          f"no change={row.no_change:.2%}, "
          f"gain<5%={row.gain_less_than_5pct:.2%}, "
          f"gain>5%={row.gain_more_than_5pct:.2%}")

The function returns deciles 1-10 plus an overall average at decile=0.

Poverty

Calculates poverty headcount and rates for a single simulation, with optional demographic filtering.

Poverty types

UK (4 measures):

US (2 measures):

Calculating all poverty rates

from policyengine.outputs.poverty import (
    calculate_uk_poverty_rates,
    calculate_us_poverty_rates,
)

# US
us_poverty = calculate_us_poverty_rates(simulation)
for p in us_poverty.outputs:
    print(f"{p.poverty_type}: headcount={p.headcount:,.0f}, rate={p.rate:.4f}")

# UK
uk_poverty = calculate_uk_poverty_rates(simulation)
for p in uk_poverty.outputs:
    print(f"{p.poverty_type}: headcount={p.headcount:,.0f}, rate={p.rate:.4f}")

Poverty by demographic group

from policyengine.outputs.poverty import (
    calculate_us_poverty_by_age,
    calculate_us_poverty_by_gender,
    calculate_us_poverty_by_race,
    calculate_uk_poverty_by_age,
    calculate_uk_poverty_by_gender,
)

# By age group (child <18, adult 18-64, senior 65+)
by_age = calculate_us_poverty_by_age(simulation)
for p in by_age.outputs:
    print(f"{p.filter_group} {p.poverty_type}: {p.rate:.4f}")

# By gender
by_gender = calculate_us_poverty_by_gender(simulation)

# By race (US only: WHITE, BLACK, HISPANIC, OTHER)
by_race = calculate_us_poverty_by_race(simulation)

Custom filters

from policyengine.outputs.poverty import Poverty

# Child poverty only
child_poverty = Poverty(
    simulation=simulation,
    poverty_variable="spm_unit_is_in_spm_poverty",
    entity="person",
    filter_variable="age",
    filter_variable_leq=17,
)
child_poverty.run()
print(f"Child SPM poverty rate: {child_poverty.rate:.4f}")

Result fields

FieldDescription
headcountWeighted count of people in poverty
total_populationWeighted total population (after filters)
rateheadcount / total_population
filter_groupGroup label set by demographic convenience functions

Inequality

Calculates weighted inequality metrics for a single simulation: Gini coefficient and income share measures.

Using convenience functions

from policyengine.outputs.inequality import (
    calculate_uk_inequality,
    calculate_us_inequality,
)

# US (uses household_net_income by default)
ineq = calculate_us_inequality(simulation)
print(f"Gini: {ineq.gini:.4f}")
print(f"Top 10% share: {ineq.top_10_share:.4f}")
print(f"Top 1% share: {ineq.top_1_share:.4f}")
print(f"Bottom 50% share: {ineq.bottom_50_share:.4f}")

# UK (uses equiv_hbai_household_net_income by default)
ineq = calculate_uk_inequality(simulation)

With demographic filters

# Inequality among working-age adults only
ineq = calculate_us_inequality(
    simulation,
    filter_variable="age",
    filter_variable_geq=18,
    filter_variable_leq=64,
)

Using directly

from policyengine.outputs.inequality import Inequality

ineq = Inequality(
    simulation=simulation,
    income_variable="household_net_income",
    entity="household",
)
ineq.run()

Result fields

FieldDescription
giniWeighted Gini coefficient (0 = perfect equality, 1 = perfect inequality)
top_10_shareShare of total income held by top 10%
top_1_shareShare of total income held by top 1%
bottom_50_shareShare of total income held by bottom 50%

Comparing baseline and reform

Poverty and inequality are single-simulation outputs. To compare baseline and reform, compute both and take the difference:

baseline_poverty = calculate_us_poverty_rates(baseline_sim)
reform_poverty = calculate_us_poverty_rates(reform_sim)

for bp, rp in zip(baseline_poverty.outputs, reform_poverty.outputs):
    change = rp.rate - bp.rate
    print(f"{bp.poverty_type}: {bp.rate:.4f} -> {rp.rate:.4f} ({change:+.4f})")

baseline_ineq = calculate_us_inequality(baseline_sim)
reform_ineq = calculate_us_inequality(reform_sim)
print(f"Gini change: {reform_ineq.gini - baseline_ineq.gini:+.4f}")

The economic_impact_analysis() function does this automatically and returns both baseline and reform poverty/inequality in the PolicyReformAnalysis result. See Economic impact analysis.