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.

UK tax-benefit model

The UK tax-benefit model implements the United Kingdom’s tax and benefit system using PolicyEngine UK as the underlying calculation engine.

Entity structure

The UK model uses three entity levels:

household
    └── benunit (benefit unit)
            └── person

Person

Individual people with demographic and income characteristics.

Key variables:

Benunit (benefit unit)

The unit for benefit assessment. Usually a single person or a couple with dependent children.

Key variables:

Important flags:

Household

The residence unit, typically sharing accommodation.

Key variables:

Required fields:

Using the UK model

Loading representative data

from policyengine.tax_benefit_models.uk import PolicyEngineUKDataset

dataset = PolicyEngineUKDataset(
    name="FRS 2023-24",
    description="Family Resources Survey microdata",
    filepath="./data/frs_2023_24_year_2026.h5",
    year=2026,
)

print(f"People: {len(dataset.data.person):,}")
print(f"Benefit units: {len(dataset.data.benunit):,}")
print(f"Households: {len(dataset.data.household):,}")

Creating custom scenarios

import pandas as pd
from microdf import MicroDataFrame
from policyengine.tax_benefit_models.uk import UKYearData

# Single parent with 2 children
person_df = MicroDataFrame(
    pd.DataFrame({
        "person_id": [0, 1, 2],
        "person_benunit_id": [0, 0, 0],
        "person_household_id": [0, 0, 0],
        "age": [35, 8, 5],
        "employment_income": [25000, 0, 0],
        "person_weight": [1.0, 1.0, 1.0],
        "is_disabled_for_benefits": [False, False, False],
        "uc_limited_capability_for_WRA": [False, False, False],
    }),
    weights="person_weight"
)

benunit_df = MicroDataFrame(
    pd.DataFrame({
        "benunit_id": [0],
        "benunit_weight": [1.0],
        "would_claim_uc": [True],
        "would_claim_child_benefit": [True],
        "would_claim_WTC": [True],
        "would_claim_CTC": [True],
    }),
    weights="benunit_weight"
)

household_df = MicroDataFrame(
    pd.DataFrame({
        "household_id": [0],
        "household_weight": [1.0],
        "region": ["LONDON"],
        "rent": [15000],  # £1,250/month
        "council_tax": [2000],
        "tenure_type": ["RENT_PRIVATELY"],
    }),
    weights="household_weight"
)

dataset = PolicyEngineUKDataset(
    name="Single parent scenario",
    description="One adult, two children",
    filepath="./single_parent.h5",
    year=2026,
    data=UKYearData(
        person=person_df,
        benunit=benunit_df,
        household=household_df,
    )
)

Running a simulation

from policyengine.core import Simulation
from policyengine.tax_benefit_models.uk import uk_latest

simulation = Simulation(
    dataset=dataset,
    tax_benefit_model_version=uk_latest,
)
simulation.run()

# Check results
output = simulation.output_dataset.data
print(output.household[["household_net_income", "household_benefits", "household_tax"]])

Key parameters

Income tax

National insurance

Universal credit

Child benefit

Common policy reforms

Increasing personal allowance

from policyengine.core import Policy, Parameter, ParameterValue
import datetime

parameter = Parameter(
    name="gov.hmrc.income_tax.allowances.personal_allowance.amount",
    tax_benefit_model_version=uk_latest,
    description="Personal allowance",
    data_type=float,
)

policy = Policy(
    name="Increase personal allowance to £15,000",
    description="Raises personal allowance from £12,570 to £15,000",
    parameter_values=[
        ParameterValue(
            parameter=parameter,
            start_date=datetime.date(2026, 1, 1),
            end_date=datetime.date(2026, 12, 31),
            value=15000,
        )
    ],
)

Adjusting UC taper rate

parameter = Parameter(
    name="gov.dwp.universal_credit.means_test.reduction_rate",
    tax_benefit_model_version=uk_latest,
    description="UC taper rate",
    data_type=float,
)

policy = Policy(
    name="Reduce UC taper to 50%",
    description="Lowers taper rate from 55% to 50%",
    parameter_values=[
        ParameterValue(
            parameter=parameter,
            start_date=datetime.date(2026, 1, 1),
            end_date=datetime.date(2026, 12, 31),
            value=0.50,  # 50%
        )
    ],
)

Abolishing two-child limit

# Set subsequent child element equal to first child
parameter = Parameter(
    name="gov.dwp.universal_credit.elements.child.subsequent_child",
    tax_benefit_model_version=uk_latest,
    description="UC subsequent child element",
    data_type=float,
)

policy = Policy(
    name="Abolish two-child limit",
    description="Sets subsequent child element equal to first child",
    parameter_values=[
        ParameterValue(
            parameter=parameter,
            start_date=datetime.date(2026, 1, 1),
            end_date=datetime.date(2026, 12, 31),
            value=333.33,  # Match first child rate
        )
    ],
)

Regional variations

The UK model accounts for regional differences:

Regions

Valid region values:

Entity mapping

The UK model has a simpler entity structure than the US, with three levels: person → benunit → household.

Direct entity mapping

You can map data between entities using the map_to_entity method:

# Map person income to benunit level
benunit_income = dataset.data.map_to_entity(
    source_entity="person",
    target_entity="benunit",
    columns=["employment_income"],
    how="sum"
)

# Split household rent equally among persons
person_rent_share = dataset.data.map_to_entity(
    source_entity="household",
    target_entity="person",
    columns=["rent"],
    how="divide"
)

# Map benunit UC to household level
household_uc = dataset.data.map_to_entity(
    source_entity="benunit",
    target_entity="household",
    columns=["universal_credit"],
    how="sum"
)

See the Entity mapping section in Core Concepts for full documentation on aggregation methods.

Data sources

The UK model can use several data sources:

  1. Family Resources Survey (FRS): Official UK household survey

    • ~19,000 households

    • Detailed income and benefit receipt

    • Published annually

  2. Enhanced FRS: Uprated and enhanced version

    • Calibrated to population totals

    • Additional imputed variables

    • Multiple projection years

  3. Custom datasets: User-created scenarios

    • Full control over household composition

    • Exact income levels

    • Specific benefit claiming patterns

Validation

When creating custom datasets, validate:

  1. Would claim flags: All set to True

  2. Disability flags: Set explicitly (not random)

  3. Join keys: Person data links to benunits and households

  4. Required fields: Region, tenure_type set correctly

  5. Weights: Sum to expected values

  6. Income ranges: Realistic values

Examples

See working examples in the examples/ directory:

References