Getting started#

PolicyEngine has two main use-cases:

  • I want to simulate policy over specific households.

  • I want to run microsimulation analyses over large datasets.

Anyone can do the former in a few minutes of setup, but the UK’s large household surveys are only available to academics, researchers and nonprofits, so getting set up takes a bit longer (and you should get in touch so we can make it as fast as possible).

If you can, please use Google Colab. It’s free, and more importantly, it enables everyone to use the same computing environment, saving all the trouble of fiddling around with bad Python installations, etc. It means that the below snippet is all you need to use the full microsimulation model:

!export POLICYENGINE_GITHUB_MICRODATA_AUTH_TOKEN=your_token_if_running_microsimulations
!pip install policyengine-uk

Household-level analysis#

The PolicyEngine UK Python package can be installed just like any other. Here’s an example below.

First, we need to install the package (please make sure you’re using Python >=3.7):

pip install policyengine-uk

Simulating current law#

This example shows how to define a situation (you need to define the people, benefit units and households, and their variables in specific time periods) and simulate the current tax-benefit system.

# The `Simulation` class is the most important class in PolicyEngine- it runs the actual simulation.

from policyengine_uk import Simulation

situation = {
    "people": {
        "person": {
            "age": {2023: 30},
            "employment_income": {2023: 30_000},
        },
    },
    "benunits": {
        "benunit": {
            "members": ["person"],
        },
    },
    "households": {
        "household": {
            "members": ["person"],
        }
    },
}

simulation = Simulation(situation=situation)

simulation.calculate("income_tax", 2023)
array([3486.], dtype=float32)

Simulating a policy reform#

Now, let’s simulate a policy reform that changes a policy parameter. The OpenFisca documentation has some excellent documentation on the syntax here.

from policyengine_core.model_api import *


def modify_parameters(parameters: ParameterNode) -> ParameterNode:
    parameters.gov.hmrc.income_tax.rates.uk[0].rate.update(
        value=0.25,
        period="year:2023:1",
    )
    return parameters


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


baseline = Simulation(situation=situation)
reformed = Simulation(situation=situation, reform=increase_basic_rate)

baseline_income_tax = baseline.calculate("income_tax", 2023)[0]
reformed_income_tax = reformed.calculate("income_tax", 2023)[0]

print(
    f"Raising the basic rate to 25% would increase this person's income tax by £{reformed_income_tax - baseline_income_tax:.2f}"
)
Raising the basic rate to 25% would increase this person's income tax by £871.50

Microsimulation analysis#

PolicyEngine UK has all the code needed to actually generate the microsimulation datasets from the raw dataset files if you have them, but it’s easiest to download our final datasets. To do this, you’ll need to do the following:

  • Make sure you’ve got a GitHub account.

  • Have PolicyEngine grant you permissions to our data storage.

  • Download a classic Personal Access Token (GitHub settings > developer settings).

  • Set it as the environment variable POLICYENGINE_GITHUB_MICRODATA_AUTH_TOKEN

For example, in a bash shell:

export POLICYENGINE_GITHUB_MICRODATA_AUTH_TOKEN=your_token_here

Getting set up#

Now the datasets will be downloaded automatically as you select them. Here’s an example:

from policyengine_uk import Microsimulation

sim = Microsimulation(dataset="enhanced_frs")

sim.calculate("universal_credit", 2023).sum() / 1e9
40.10304435685574

We passed a string to indicate that we’d like to use the enhanced FRS (PolicyEngine’s most extensive dataset, with consumption, wealth and VAT imputations and full calibration to external statistics). But here are the other options we could have used:

import pandas as pd

names = [dataset.name for dataset in Microsimulation.datasets]
labels = [dataset.label for dataset in Microsimulation.datasets]

pd.DataFrame({"Name": names, "Label": labels}).set_index(["Name", "Label"])
Name Label
frs_2018 FRS 2018-19
frs_2019 FRS 2019-20
frs_2020 FRS 2020-21
frs_2021 FRS 2021-22
frs_2022 FRS 2022-23
raw_frs_2021 FRS 2021-22
pooled_frs_2019_21 FRS 2019-21
spi_enhanced_pooled_frs_2019_21 SPI-enhanced FRS 2019-21
calibrated_spi_enhanced_pooled_frs_2019_21 Calibrated SPI-enhanced FRS 2019-21
enhanced_frs Enhanced FRS
spi_2020_21 SPI 2020-21

Running reform analyses#

Reforms work in exactly the same way as in the household-level analysis above. Here’s the same example reform:

baseline = Microsimulation()  # Enhanced FRS 2022 by default
reformed = Microsimulation(reform=increase_basic_rate)

revenue = (
    -(
        reformed.calculate("household_net_income", 2023)
        - baseline.calculate("household_net_income", 2023)
    ).sum()
    / 1e9
)

print(
    f"Raising the basic rate to 25% would raise the UK £{revenue:.2f}bn per year"
)
Raising the basic rate to 25% would raise the UK £23.39bn per year

The PolicyEngine web app actually has a helpful tool for this: if you can generate a household or reform on the app, scroll down in the bottom left to the Reproduce in Python section, and you’ll see an automatically-generated code snippet to reproduce the same analysis in Python.