Skip to content

Commit

Permalink
Add LSR prototypes (#852)
Browse files Browse the repository at this point in the history
* Add LSRs

* Fix import

* Fix metadata

* Adjust test

* Fix test
  • Loading branch information
nikhilwoodruff authored May 2, 2024
1 parent e2e55c4 commit aba1069
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 1 deletion.
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: minor
changes:
added:
- U.S. progress on labour supply responses.
1 change: 1 addition & 0 deletions policyengine_uk/parameters/gov/simulation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Simulation
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Bounds
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Effective wage rate changes larger than this will be capped at this value.
values:
0000-01-01: 1
metadata:
unit: /1
label: Effective wage rate change LSR bound
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Net income changes larger than this will be capped at this value.
values:
0000-01-01: 1
metadata:
unit: /1
label: Income change LSR bound
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Percent change in labor supply given a 1% change in disposable income. This applies only to employment income.
values:
2020-01-01: 0
metadata:
unit: /1
label: Income elasticity of labor supply
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Percent change in labor supply given a 1% change in the effective marginal wage. This applies only to employment income.
values:
2020-01-01: 0
metadata:
unit: /1
label: Substitution elasticity of labor supply
16 changes: 16 additions & 0 deletions policyengine_uk/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ def __init__(self, *args, **kwargs):
if reform is not None:
self.apply_reform(reform)

# Labor supply responses

employment_income = self.get_holder("employment_income")
for known_period in employment_income.get_known_periods():
array = employment_income.get_array(known_period)
self.set_input("employment_income_before_lsr", known_period, array)
employment_income.delete_arrays(known_period)


class Microsimulation(CoreMicrosimulation):
default_tax_benefit_system = CountryTaxBenefitSystem
Expand All @@ -82,6 +90,14 @@ def __init__(self, *args, **kwargs):
if reform is not None:
self.apply_reform(reform)

# Labor supply responses

employment_income = self.get_holder("employment_income")
for known_period in employment_income.get_known_periods():
array = employment_income.get_array(known_period)
self.set_input("employment_income_before_lsr", known_period, array)
employment_income.delete_arrays(known_period)


class IndividualSim(CoreIndividualSim): # Deprecated
tax_benefit_system = CountryTaxBenefitSystem
Expand Down
5 changes: 5 additions & 0 deletions policyengine_uk/tests/microsimulation/test_validity.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
@pytest.mark.parametrize("year", YEARS)
def test_not_nan(year):
for variable in baseline.tax_benefit_system.variables:
requires_computation_after = baseline.tax_benefit_system.variables[
variable
].requires_computation_after
if requires_computation_after:
continue
if (
baseline.tax_benefit_system.variables[variable].definition_period
== "year"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from policyengine_uk.model_api import *


class relative_income_change(Variable):
value_type = float
entity = Person
label = "relative income change"
unit = "/1"
definition_period = YEAR
requires_computation_after = "employment_income_behavioral_response"

def formula(person, period, parameters):
simulation = person.simulation
measurement_branch = simulation.get_branch("lsr_measurement")
baseline_branch = simulation.get_branch("baseline").get_branch(
"baseline_lsr_measurement"
)
measurement_person = measurement_branch.populations["person"]
baseline_person = baseline_branch.populations["person"]
baseline_net_income = baseline_person.household(
"household_net_income", period
)
net_income = measurement_person.household(
"household_net_income", period
)
income_change_bound = parameters(
period
).gov.simulation.labor_supply_responses.bounds.income_change
# _c suffix for "clipped"
baseline_net_income_c = np.clip(baseline_net_income, 1, None)
net_income_c = np.clip(net_income, 1, None)
relative_change = (
net_income_c - baseline_net_income_c
) / baseline_net_income_c
return np.clip(
relative_change, -income_change_bound, income_change_bound
)


class relative_wage_change(Variable):
value_type = float
entity = Person
label = "relative wage change"
unit = "/1"
definition_period = YEAR
requires_computation_after = "employment_income_behavioral_response"

def formula(person, period, parameters):
simulation = person.simulation
measurement_branch = simulation.get_branch("lsr_measurement")
baseline_branch = simulation.get_branch("baseline").get_branch(
"baseline_lsr_measurement"
)
measurement_person = measurement_branch.populations["person"]
baseline_person = baseline_branch.populations["person"]
baseline_mtr = baseline_person("marginal_tax_rate", period)
baseline_wage = 1 - baseline_mtr
mtr = measurement_person("marginal_tax_rate", period)
wage_rate = 1 - mtr
# _c suffix for "clipped"
baseline_wage_c = np.where(baseline_wage == 0, 0.01, baseline_wage)
wage_rate_c = np.where(wage_rate == 0, 0.01, wage_rate)
relative_change = (wage_rate_c - baseline_wage_c) / baseline_wage_c
wage_change_bound = parameters(
period
).gov.simulation.labor_supply_responses.bounds.effective_wage_rate_change
return np.clip(relative_change, -wage_change_bound, wage_change_bound)


class income_elasticity_lsr(Variable):
value_type = float
entity = Person
label = "income elasticity of labor supply response"
unit = GBP
definition_period = YEAR
requires_computation_after = "employment_income_behavioral_response"

def formula(person, period, parameters):
lsr = parameters(period).gov.simulation.labor_supply_responses
employment_income = person("employment_income_before_lsr", period)
income_change = person("relative_income_change", period)

return employment_income * income_change * lsr.income_elasticity


class substitution_elasticity_lsr(Variable):
value_type = float
entity = Person
label = "substitution elasticity of labor supply response"
unit = GBP
definition_period = YEAR
requires_computation_after = "employment_income_behavioral_response"

def formula(person, period, parameters):
lsr = parameters(period).gov.simulation.labor_supply_responses
employment_income = person("employment_income_before_lsr", period)
wage_change = person("relative_wage_change", period)

return employment_income * wage_change * lsr.substitution_elasticity


class employment_income_behavioral_response(Variable):
value_type = float
entity = Person
label = "income-related labor supply change"
unit = GBP
definition_period = YEAR

def formula(person, period, parameters):
lsr = parameters(period).gov.simulation.labor_supply_responses
simulation = person.simulation
if simulation.baseline is None:
return 0 # No reform, no impact
if lsr.income_elasticity == 0 and lsr.substitution_elasticity == 0:
return 0

measurement_branch = simulation.get_branch(
"lsr_measurement", clone_system=True
) # A branch without LSRs
baseline_branch = simulation.get_branch("baseline").get_branch(
"baseline_lsr_measurement", clone_system=True
) # Already created by default

# (system with LSRs) <- (system without LSRs used to calculate LSRs)
# |
# * -(baseline system without LSRs used to calculate LSRs)

for branch in [measurement_branch, baseline_branch]:
branch.tax_benefit_system.neutralize_variable(
"employment_income_behavioral_response"
)
branch.set_input(
"employment_income_before_lsr",
period,
person("employment_income_before_lsr", period),
)

return add(
person,
period,
[
"income_elasticity_lsr",
"substitution_elasticity_lsr",
],
)
13 changes: 12 additions & 1 deletion policyengine_uk/variables/input/income.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ class employment_income(Variable):
unit = GBP
reference = "Income Tax (Earnings and Pensions) Act 2003 s. 1(1)(a)"
quantity_type = FLOW
category = INCOME
adds = [
"employment_income_before_lsr",
"employment_income_behavioral_response",
]


class employment_income_before_lsr(Variable):
value_type = float
entity = Person
label = "employment income before labor supply responses"
unit = GBP
definition_period = YEAR
uprating = "calibration.programs.employment_income.budgetary_impact.UNITED_KINGDOM"


Expand Down

0 comments on commit aba1069

Please sign in to comment.