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.

Economic impact analysis

The economic_impact_analysis() function is the canonical way to compare a baseline simulation against a reform simulation. It produces a comprehensive PolicyReformAnalysis containing decile impacts, programme-by-programme statistics, poverty rates, and inequality metrics in a single call.

Overview

There are two approaches to comparing simulations:

ApproachUse case
ChangeAggregateSingle-metric queries: “What is the total tax revenue change?”
economic_impact_analysis()Full analysis: decile impacts, programme stats, poverty, inequality

ChangeAggregate gives you one number per call. economic_impact_analysis() runs ~30+ aggregate computations and returns a structured result containing everything.

Full analysis workflow

US example

import datetime
from policyengine.core import Parameter, ParameterValue, Policy, Simulation
from policyengine.tax_benefit_models.us import (
    economic_impact_analysis,
    ensure_datasets,
    us_latest,
)

# 1. Load data
datasets = ensure_datasets(
    datasets=["hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5"],
    years=[2026],
    data_folder="./data",
)
dataset = datasets["enhanced_cps_2024_2026"]

# 2. Define reform
param = Parameter(
    name="gov.irs.deductions.standard.amount.SINGLE",
    tax_benefit_model_version=us_latest,
)
reform = Policy(
    name="Double standard deduction (single)",
    parameter_values=[
        ParameterValue(
            parameter=param,
            start_date=datetime.date(2026, 1, 1),
            end_date=datetime.date(2026, 12, 31),
            value=30_950,
        ),
    ],
)

# 3. Create simulations (no need to call .run() — ensure() is called internally)
baseline_sim = Simulation(
    dataset=dataset,
    tax_benefit_model_version=us_latest,
)
reform_sim = Simulation(
    dataset=dataset,
    tax_benefit_model_version=us_latest,
    policy=reform,
)

# 4. Run full analysis
analysis = economic_impact_analysis(baseline_sim, reform_sim)

UK example

import datetime
from policyengine.core import Parameter, ParameterValue, Policy, Simulation
from policyengine.tax_benefit_models.uk import (
    economic_impact_analysis,
    ensure_datasets,
    uk_latest,
)

datasets = ensure_datasets(
    datasets=["hf://policyengine/policyengine-uk-data/enhanced_frs_2023_24.h5"],
    years=[2026],
    data_folder="./data",
)
dataset = datasets["enhanced_frs_2023_24_2026"]

param = Parameter(
    name="gov.hmrc.income_tax.allowances.personal_allowance.amount",
    tax_benefit_model_version=uk_latest,
)
reform = Policy(
    name="Zero personal allowance",
    parameter_values=[
        ParameterValue(
            parameter=param,
            start_date=datetime.date(2026, 1, 1),
            end_date=datetime.date(2026, 12, 31),
            value=0,
        ),
    ],
)

baseline_sim = Simulation(
    dataset=dataset,
    tax_benefit_model_version=uk_latest,
)
reform_sim = Simulation(
    dataset=dataset,
    tax_benefit_model_version=uk_latest,
    policy=reform,
)

analysis = economic_impact_analysis(baseline_sim, reform_sim)

What economic_impact_analysis() computes

The function calls ensure() on both simulations (run + cache if not already computed), then produces:

Decile impacts

Mean income changes by income decile (1-10), with counts of people better off, worse off, and unchanged.

for d in analysis.decile_impacts.outputs:
    print(f"Decile {d.decile}: avg change={d.absolute_change:+.0f}, "
          f"relative={d.relative_change:+.2f}%")

Fields on each DecileImpact:

Programme/program statistics

Per-programme totals, changes, and winner/loser counts.

US programs analysed: income_tax, payroll_tax, state_income_tax, snap, tanf, ssi, social_security, medicare, medicaid, eitc, ctc

UK programmes analysed: income_tax, national_insurance, vat, council_tax, universal_credit, child_benefit, pension_credit, income_support, working_tax_credit, child_tax_credit

for p in analysis.program_statistics.outputs:  # US
    print(f"{p.program_name}: baseline=${p.baseline_total/1e9:.1f}B, "
          f"reform=${p.reform_total/1e9:.1f}B, change=${p.change/1e9:+.1f}B")

Fields on each ProgramStatistics / ProgrammeStatistics:

Poverty rates

Poverty headcount and rates for both baseline and reform simulations.

US poverty types: SPM poverty, deep SPM poverty

UK poverty types: Absolute BHC, absolute AHC, relative BHC, relative AHC

for bp, rp in zip(analysis.baseline_poverty.outputs,
                  analysis.reform_poverty.outputs):
    print(f"{bp.poverty_type}: baseline={bp.rate:.4f}, reform={rp.rate:.4f}")

Inequality metrics

Gini coefficient and income share metrics for both simulations.

bi = analysis.baseline_inequality
ri = analysis.reform_inequality
print(f"Gini: baseline={bi.gini:.4f}, reform={ri.gini:.4f}")
print(f"Top 10% share: baseline={bi.top_10_share:.4f}, reform={ri.top_10_share:.4f}")
print(f"Top 1% share: baseline={bi.top_1_share:.4f}, reform={ri.top_1_share:.4f}")
print(f"Bottom 50% share: baseline={bi.bottom_50_share:.4f}, reform={ri.bottom_50_share:.4f}")

The PolicyReformAnalysis return type

class PolicyReformAnalysis(BaseModel):
    decile_impacts: OutputCollection[DecileImpact]
    program_statistics: OutputCollection[ProgramStatistics]       # US
    # programme_statistics: OutputCollection[ProgrammeStatistics]  # UK
    baseline_poverty: OutputCollection[Poverty]
    reform_poverty: OutputCollection[Poverty]
    baseline_inequality: Inequality
    reform_inequality: Inequality

Each OutputCollection contains:

Using ChangeAggregate for targeted queries

When you only need a single metric, ChangeAggregate is more direct than the full analysis pipeline. It requires that both simulations have already been run (or ensure’d).

Tax revenue change

from policyengine.outputs.change_aggregate import ChangeAggregate, ChangeAggregateType

baseline_sim.run()
reform_sim.run()

revenue = ChangeAggregate(
    baseline_simulation=baseline_sim,
    reform_simulation=reform_sim,
    variable="household_tax",
    aggregate_type=ChangeAggregateType.SUM,
)
revenue.run()
print(f"Revenue change: ${revenue.result / 1e9:.1f}B")

Winners and losers

winners = ChangeAggregate(
    baseline_simulation=baseline_sim,
    reform_simulation=reform_sim,
    variable="household_net_income",
    aggregate_type=ChangeAggregateType.COUNT,
    change_geq=1,  # Gained at least $1
)
winners.run()

losers = ChangeAggregate(
    baseline_simulation=baseline_sim,
    reform_simulation=reform_sim,
    variable="household_net_income",
    aggregate_type=ChangeAggregateType.COUNT,
    change_leq=-1,  # Lost at least $1
)
losers.run()

Filtering by income decile

# Average loss in the 3rd income decile
avg_loss = ChangeAggregate(
    baseline_simulation=baseline_sim,
    reform_simulation=reform_sim,
    variable="household_net_income",
    aggregate_type=ChangeAggregateType.MEAN,
    filter_variable="household_net_income",
    quantile=10,
    quantile_eq=3,
)
avg_loss.run()

Filter options reference

Absolute change filters:

Relative change filters:

Variable filters:

Quantile filters:

Examples