Reforms#
Reforms allow you to modify a tax-benefit system’s parameters or variable logic to simulate policy changes. PolicyEngine Core provides two main approaches to creating reforms:
Reform.from_dict()- Create reforms programmatically from a dictionary (recommended for most use cases)Reform subclass - Create a custom class for complex reforms requiring variable logic changes
Creating reforms with Reform.from_dict()#
The Reform.from_dict() method is the simplest way to create parameter-based reforms. It takes a dictionary mapping parameter paths to their new values.
Method signature#
Reform.from_dict(
parameter_values: dict, # Parameter path -> period -> value mappings
country_id: str = None, # Optional: country code for API integration
name: str = None, # Optional: human-readable name for the reform
) -> Reform
Dictionary format#
The parameter_values dictionary uses parameter paths as keys and period-value mappings as values:
{
'path.to.parameter': {
'YYYY-MM-DD.YYYY-MM-DD': value, # Period format: start.end
},
'path.to.another.parameter': {
'2024-01-01.2100-12-31': 1000, # Example: effective 2024 onwards
},
}
Period formats:
'YYYY-MM-DD.YYYY-MM-DD'- Date range (start date, end date separated by.)'year:YYYY:N'- N years starting from YYYY (e.g.,'year:2024:10'for 2024-2033)Simple value (no dict) - Applies for 100 years from 2000 (convenience shorthand)
Basic example#
Let’s create a simple reform that increases the basic income amount in the country template.
from policyengine_core.country_template import Microsimulation
from policyengine_core.reforms import Reform
# Create a reform that increases basic income from 600 to 1000
basic_income_reform = Reform.from_dict(
{
"benefits.basic_income": {
"2020-01-01.2100-12-31": 1000,
}
}
)
# Run simulations
baseline = Microsimulation()
reformed = Microsimulation(reform=basic_income_reform)
# Compare results
print("Baseline basic income:")
print(baseline.calculate("basic_income", "2022-01"))
print("\nReformed basic income:")
print(reformed.calculate("basic_income", "2022-01"))
Multiple parameters#
You can modify multiple parameters in a single reform.
# Reform modifying multiple parameters
multi_param_reform = Reform.from_dict(
{
"benefits.basic_income": {
"2020-01-01.2100-12-31": 800,
},
"taxes.income_tax_rate": {
"2020-01-01.2100-12-31": 0.20, # Increase from 0.15 to 0.20
},
}
)
reformed_multi = Microsimulation(reform=multi_param_reform)
print(
"Reformed basic income:",
reformed_multi.calculate("basic_income", "2022-01"),
)
print("Reformed income tax:", reformed_multi.calculate("income_tax", "2022-01"))
Bracket parameters#
For parameters with brackets (like tax scales), use array index notation: parameter.brackets[index].rate or parameter.brackets[index].threshold.
# Reform modifying a bracket parameter (social security contribution scale)
bracket_reform = Reform.from_dict(
{
# Modify the first bracket's rate
"taxes.social_security_contribution.brackets[0].rate": {
"2020-01-01.2100-12-31": 0.05,
},
# Modify the second bracket's threshold
"taxes.social_security_contribution.brackets[1].threshold": {
"2020-01-01.2100-12-31": 15000,
},
}
)
reformed_bracket = Microsimulation(reform=bracket_reform)
print(
"Reformed social security contribution:",
reformed_bracket.calculate("social_security_contribution", "2022-01"),
)
Shorthand for permanent changes#
If you want a parameter change to apply indefinitely, you can omit the period dictionary and just provide the value directly.
# Shorthand: value applies from year 2000 for 100 years
simple_reform = Reform.from_dict(
{
"benefits.basic_income": 1200, # No period dict needed
"taxes.income_tax_rate": 0.18,
}
)
reformed_simple = Microsimulation(reform=simple_reform)
print("Basic income:", reformed_simple.calculate("basic_income", "2022-01"))
Named reforms for API integration#
When working with the PolicyEngine API, you can provide a country_id and name for better integration.
# Named reform with country ID (useful for API integration)
named_reform = Reform.from_dict(
{
"benefits.basic_income": {
"2024-01-01.2100-12-31": 1500,
},
},
country_id="country_template",
name="Increased Basic Income Reform",
)
# The reform class now has these attributes set
print(f"Reform name: {named_reform.name}")
print(f"Country ID: {named_reform.country_id}")
Creating reforms with a Reform subclass#
For more complex reforms that need to modify variable logic or simulation data, define a class inheriting from Reform with an apply(self) method. Inside it, self is the tax-benefit system attached to the simulation with loaded data via self.simulation: Simulation.
from policyengine_core.model_api import *
baseline = Microsimulation()
class custom_reform(Reform):
def apply(self):
simulation = self.simulation
# Modify parameters
simulation.tax_benefit_system.parameters.taxes.housing_tax.rate.update(20)
# Modify simulation data
salary = simulation.calculate("salary", "2022-01")
new_salary = salary * 1.1
simulation.set_input("salary", "2022-01", new_salary)
reformed = Microsimulation(reform=custom_reform)
(
reformed.calculate("salary", "2022-01"),
baseline.calculate("salary", "2022-01"),
)
(
reformed.calculate("housing_tax", 2022),
baseline.calculate("housing_tax", 2022),
)
Comparing baseline and reformed simulations#
A common workflow is to compare results between baseline and reformed scenarios.
# Create reform
reform = Reform.from_dict({"benefits.basic_income": {"2020-01-01.2100-12-31": 1000}})
# Run both simulations
baseline = Microsimulation()
reformed = Microsimulation(reform=reform)
# Calculate and compare
baseline_income = baseline.calculate("basic_income", "2022-01")
reformed_income = reformed.calculate("basic_income", "2022-01")
print("Baseline basic income:")
print(baseline_income)
print("\nReformed basic income:")
print(reformed_income)
print(
f"\nDifference: {reformed_income['value'].sum() - baseline_income['value'].sum():.2f}"
)