Taxes & Transfers Objects and Modifications of the Policy Environment#
GETTSIM’s design allows you to go beyond the depiction of the current (or historical) tax and transfer system. Analyzing counterfactual reform scenarios, ranging from small changes of certain parameters of the tax and transfer system, to the introduction of large-scale reforms, is a common use case.
This tutorial showcases how to modify the calculation of taxes and transfers when using GETTSIM.
Here, we focus mainly on small reforms to the calculation of income taxes. We pick this example because it is a fairly complex system that uses (almost) the entire range of objects TTSIM offers.
Status Quo#
Before modifying taxes and transfers, it’s important to understand how GETTSIM represents the current tax and transfer system. The core of GETTSIM’s implementation is the policy environment.
What is a Policy Environment?#
A policy environment is a nested dictionary that holds all the parameters and functions needed to calculate taxes and transfers for a given policy date. Think of it as a complete snapshot of the tax and transfer system at a particular point in time.
The Three Types of Objects#
The policy environment contains three main categories of objects:
Column Objects (
ColumnObjects
): These work with data columns - either input data you provide or results computed by previous functions. They handle the actual calculations and data processing.Parameter Objects (
ParamObjects
): These store the parameters and constants used in calculations, such as tax rates, benefit amounts, or thresholds.Parameter Functions (
ParamFunctions
): These process parameters which cannot be used directly by the column objects.
Getting Started#
After the standard imports, the first step in modifying taxes and transfers is to create the base policy environment for the date you want to work with.
[1]:
from __future__ import annotations
import numpy as np
from gettsim import InputData, MainTarget, copy_environment, main
[2]:
status_quo_environment = main(
main_target=MainTarget.policy_environment,
policy_date_str="2025-01-01",
backend="numpy",
)
We also create some input data in order to verify how our modifications affect the output. The following input data is required to compute the amount of income tax when assuming parental leave benefits (i.e. ('elterngeld', 'betrag_m')
), pensions (i.e. ('sozialversicherung', 'rente', 'altersrente', 'betrag_m')
, ('sozialversicherung', 'rente', 'erwerbsminderung', 'betrag_m')
) and unemployment benefits (i.e. ('sozialversicherung', 'arbeitslosen', 'betrag_m')
) are fixed at some
value.
[3]:
INPUT_DATA_TREE = {
"alter": np.array([40, 40, 5]),
"arbeitsstunden_w": np.array([40, 40, 0]),
"behinderungsgrad": np.array([0, 60, 0]),
"einnahmen": {
"bruttolohn_m": np.array([6000.0, 4000.0, 0.0]),
"kapitalerträge_m": np.array([0.0, 0.0, 0.0]),
"renten": {
"betriebliche_altersvorsorge_m": np.array([0.0, 0.0, 0.0]),
"geförderte_private_vorsorge_m": np.array([0.0, 0.0, 0.0]),
"sonstige_private_vorsorge_m": np.array([0.0, 0.0, 0.0]),
},
},
"einkommensteuer": {
"abzüge": {
"beitrag_private_rentenversicherung_m": np.array([120, 120, 0]),
"kinderbetreuungskosten_m": np.array([0.0, 0.0, 120.0]),
"p_id_kinderbetreuungskostenträger": np.array([-1, -1, 0]),
},
"einkünfte": {
"aus_forst_und_landwirtschaft": {"betrag_m": np.array([0.0, 0.0, 0.0])},
"aus_gewerbebetrieb": {"betrag_m": np.array([0.0, 0.0, 0.0])},
"aus_selbstständiger_arbeit": {"betrag_m": np.array([0.0, 0.0, 0.0])},
"aus_vermietung_und_verpachtung": {"betrag_m": np.array([0.0, 0.0, 0.0])},
"ist_hauptberuflich_selbstständig": np.array([False, False, False]),
"sonstige": {
"alle_weiteren_m": np.array([0.0, 0.0, 0.0]),
},
},
"gemeinsam_veranlagt": np.array([True, True, False]),
},
"bürgergeld": {
"p_id_einstandspartner": np.array([1, 0, -1])
}, # not required for ESt, but we need it later
"familie": {
"alleinerziehend": np.array([False, False, False]),
"p_id_ehepartner": np.array([1, 0, -1]),
"p_id_elternteil_1": np.array([-1, -1, 0]),
"p_id_elternteil_2": np.array([-1, -1, 1]),
},
"geburtsjahr": np.array([1985, 1985, 2020]),
"kindergeld": {
"in_ausbildung": np.array([False, False, False]),
"p_id_empfänger": np.array([-1, -1, 0]),
},
"p_id": np.array([0, 1, 2]),
"hh_id": np.array([0, 0, 0]),
"sozialversicherung": {
"kranken": {"beitrag": {"privat_versichert": np.array([False, False, False])}},
"pflege": {"beitrag": {"hat_kinder": np.array([True, True, False])}},
"rente": {
"altersrente": {"betrag_m": np.array([0.0, 0.0, 0.0])},
"erwerbsminderung": {"betrag_m": np.array([0.0, 0.0, 0.0])},
"jahr_renteneintritt": np.array([2060, 2060, 2090]),
},
},
}
The status quo is the following:
[4]:
main(
main_target=MainTarget.results.df_with_nested_columns,
policy_date_str="2025-01-01",
input_data=InputData.tree(INPUT_DATA_TREE),
tt_targets={"tree": {"einkommensteuer": {"betrag_y_sn": None}}},
include_warn_nodes=False,
)
[4]:
einkommensteuer | |
---|---|
betrag_y_sn | |
p_id | |
0 | 17821.0 |
1 | 17821.0 |
2 | 0.0 |
Modifying Parameters#
GETTSIM’s parameters are stored in different objects depending on the form they are specified in the law. If you modify the parameters in the policy_environment
, you will encounter the following objects:
ScalarParam: A scalar parameter, i.e. a parameter that is a single number.
DictParam: A parameter that is a flat dictionary with homogeneous keys and values (i.e. all keys and values are of the same type).
ConsecutiveIntLookupTableParam: A lookup table that stores values and assigns a consecutive integer index to each value.
PiecewisePolynomialParam: A piecewise polynomial parameter, i.e. a parameter that describes a piecewise polynomial function.
RawParam: A parameter that does not fit into the other categories. For these parameters, we need
ParamFunction
s to process them (see next section).
All of those parameter classes have the following attributes:
leaf_name
: The leaf name of the parameter in GETTSIM’s policy environment.start_date
: The date from which the parameter is valid (if applicable).end_date
: The date until which the parameter is valid (if applicable).unit
: The unit of the parameter (if applicable).reference_period
: The period over which the parameter is valid (if applicable).name
: The name of the parameter.description
: A more elaborate description of the parameter.value
: The value of the parameter.note
: Some notes (if applicable).reference
: A legal reference.
When modifying parameters, you will mostly care about the value
attribute.
Scalar Parameters#
Scalar parameters are the simplest type of parameters. They are represented by the ScalarParam
class.
Let’s take a look at the arbeitnehmerpauschbetrag
parameter, a flat deduction applied to income from regular employment.
As you can see the arbeitnehmerpauschbetrag
parameter is a ScalarParam
object and its value is 1230€ in the status quo.
[5]:
status_quo_environment["einkommensteuer"]["einkünfte"][
"aus_nichtselbstständiger_arbeit"
]["arbeitnehmerpauschbetrag"]
[5]:
ScalarParam(start_date=datetime.date(2023, 1, 1), end_date=datetime.date(2099, 12, 31), unit='Euros', reference_period='Year', name={'de': 'Arbeitnehmerpauschbetrag bei nichtselbstständiger Arbeit.', 'en': 'Lump-sum deduction for employment income.'}, description={'de': '§ 9a Nr. 1a) EStG', 'en': 'This is the minimum amount deducted from any employment income.'}, value=1230, note=None, reference=None)
[6]:
status_quo_environment["einkommensteuer"]["einkünfte"][
"aus_nichtselbstständiger_arbeit"
]["arbeitnehmerpauschbetrag"].value
[6]:
1230
Let’s increase the parameter.
Step 1: Create a copy of the status quo policy environment.#
This is good practice to avoid in-place modifications of the original policy environment.
[7]:
higher_arbeitnehmerpauschbetrag_pe = copy_environment(status_quo_environment)
Step 2: Create the new parameter.#
Create a new ScalarParam
object. To do this, we first import the ScalarParam
class from GETTSIM and then instantiate it with the new value.
Note: You don’t have to specify all attributes of the ScalarParam
class. Only the value attribute is required.
[8]:
from gettsim.tt import ScalarParam
higher_arbeitnehmerpauschbetrag = ScalarParam(value=1600)
Step 3: Replace the old parameter with the new one in the new policy environment#
[9]:
higher_arbeitnehmerpauschbetrag_pe["einkommensteuer"]["einkünfte"][
"aus_nichtselbstständiger_arbeit"
]["arbeitnehmerpauschbetrag"] = higher_arbeitnehmerpauschbetrag
Let’s call GETTSIM with the modified policy environment.
[10]:
main(
main_target=MainTarget.results.df_with_nested_columns,
policy_date_str="2025-01-01",
input_data=InputData.tree(INPUT_DATA_TREE),
tt_targets={"tree": {"einkommensteuer": {"betrag_y_sn": None}}},
policy_environment=higher_arbeitnehmerpauschbetrag_pe,
include_warn_nodes=False,
)
[10]:
einkommensteuer | |
---|---|
betrag_y_sn | |
p_id | |
0 | 17585.0 |
1 | 17585.0 |
2 | 0.0 |
Dict Parameters#
Dict parameters are parameters that are a flat dictionary with homogeneous keys and values. They are represented by the DictParam
class. They are stored as a flat dictionary in the policy_environment
.
Let’s take a look at the parameter_kinderfreibetrag
parameter. This parameter contains tax deductions for parents of young children.
As you can see the parameter_kinderfreibetrag
parameter is a DictParam
object and its value is a dictionary.
[11]:
status_quo_environment["einkommensteuer"]["parameter_kinderfreibetrag"]
[11]:
DictParam(start_date=datetime.date(2025, 1, 1), end_date=datetime.date(2025, 12, 31), unit='Euros', reference_period='Year', name={'de': 'Kinderfreibetrag', 'en': 'Basic Income Tax Allowance for children'}, description={'de': 'sächliches Existenzminimum des Kindes, seit 2000 auch der Freibetrag für Betreuungs-, Erziehungs- oder Ausbildungsbedarf. Wird verdoppelt für gemeinsam veranlagte Paare. §32 (6) EStG.', 'en': None}, value={'betreuung_erziehung_ausbildung': 1464, 'sächliches_existenzminimum': 3336}, note=None, reference=None)
[12]:
status_quo_environment["einkommensteuer"]["parameter_kinderfreibetrag"].value
[12]:
{'betreuung_erziehung_ausbildung': 1464, 'sächliches_existenzminimum': 3336}
Let’s modify the parameter by increasing the “sächliches Existenzminimum” for children. We follow the same steps as in the previous section.
[13]:
from gettsim.tt import DictParam
# Step 1: Create a copy of the status quo policy environment.
higher_kinderfreibetrag_pe = copy_environment(status_quo_environment)
# Step 2: Create a new parameter `parameter_kinderfreibetrag`
higher_kinderfreibetrag = DictParam(
value={
"betreuung_erziehung_ausbildung": 1464,
"sächliches_existenzminimum": 4000,
},
)
# Step 3: Insert the new parameter into the copied policy environment
higher_kinderfreibetrag_pe["einkommensteuer"]["parameter_kinderfreibetrag"] = (
higher_kinderfreibetrag
)
main(
main_target=MainTarget.results.df_with_nested_columns,
policy_date_str="2025-01-01",
input_data=InputData.tree(INPUT_DATA_TREE),
tt_targets={"tree": {"einkommensteuer": {"betrag_y_sn": None}}},
policy_environment=higher_kinderfreibetrag_pe,
include_warn_nodes=False,
)
[13]:
einkommensteuer | |
---|---|
betrag_y_sn | |
p_id | |
0 | 17398.0 |
1 | 17398.0 |
2 | 0.0 |
Consecutive Int Lookup Table Parameters#
In many cases, we need to look up values in a table, for which we tend to use dictionaries. E.g., the increase in the normal retirement age for the 1940s cohort would look like this:
{
1940: 65,
1941: 65,
1942: 65,
1943: 65,
1944: 65,
1945: 65,
1946: 65,
1947: 65.08333333,
1948: 65.16666667,
1949: 65.25,
}
For all its efficiency, our alternative computing engine Jax does not support such lookup tables. We thus construct our own version, which only works for consecutive integer values (i.e., the dictionary keys could not be red
, blue
, green
, or 1
, 7
, 9
, etc.).
Let’s look at the implementation.
[14]:
status_quo_environment["sozialversicherung"]["rente"]["altersrente"][
"regelaltersrente"
]["altersgrenze_gestaffelt"]
[14]:
ConsecutiveIntLookupTableParam(start_date=datetime.date(2007, 4, 20), end_date=datetime.date(2030, 12, 31), unit='Years', reference_period=None, name={'de': 'Gestaffeltes Eintrittsalter für Regelaltersrente nach Geburtsjahr', 'en': 'Staggered normal retirement age (NRA) for Regelaltersrente by birth year'}, description={'de': '§ 35 Satz 2 SGB VI Regelaltersgrenze ab der Renteneintritt möglich ist. Wenn früher oder später in Rente gegangen wird, wird der Zugangsfaktor und damit der Rentenanspruch höher oder niedriger, sofern keine Sonderregelungen gelten.', 'en': '§ 35 Satz 2 SGB VI Normal retirement age from which pension can be received. If retirement benefits are claimed earlier or later, the Zugangsfaktor and thus the pension entitlement is higher or lower unless special regulations apply.'}, value=<ttsim.tt.param_objects.ConsecutiveIntLookupTableParamValue object at 0x794b653525c0>, note=None, reference=None)
The value
attribute of ConsecutiveIntLookupTableParam
stores a ConsecutiveIntLookupTableParamValue
object:
[15]:
status_quo_environment["sozialversicherung"]["rente"]["altersrente"][
"regelaltersrente"
]["altersgrenze_gestaffelt"].value
[15]:
<ttsim.tt.param_objects.ConsecutiveIntLookupTableParamValue at 0x794b653525c0>
This object has a look_up
method, which returns the value for the given key.
[16]:
for i in range(1940, 1950):
print(
i,
status_quo_environment["sozialversicherung"]["rente"]["altersrente"][
"regelaltersrente"
]["altersgrenze_gestaffelt"].value.look_up(i),
)
1940 [65.]
1941 [65.]
1942 [65.]
1943 [65.]
1944 [65.]
1945 [65.]
1946 [65.]
1947 [65.08333333]
1948 [65.16666667]
1949 [65.25]
Let’s create a modified version with a steeper increase in the normal retirement age.
In order to instantiate a ConsecutiveIntLookupTableParamValue
, we have a function get_consecutive_int_lookup_table_param_value
that takes a dictionary as above:
[17]:
from gettsim.tt import (
ConsecutiveIntLookupTableParam,
get_consecutive_int_lookup_table_param_value,
)
# Step 1: Create a copy of the status quo policy environment.
increased_nra_by_birth_year_pe = copy_environment(status_quo_environment)
# Step 2: Create a new parameter `altersgrenze_gestaffelt`
increased_nra_by_birth_year = ConsecutiveIntLookupTableParam(
value=get_consecutive_int_lookup_table_param_value(
raw={
1940: 65,
1941: 65,
1942: 65,
1943: 65,
1944: 65,
1945: 65.5,
1946: 66,
1947: 66.5,
1948: 67,
1949: 67,
},
xnp=np,
),
)
# Step 3: Insert the new parameter into the copied policy environment
increased_nra_by_birth_year_pe["sozialversicherung"]["rente"]["altersrente"][
"regelaltersrente"
]["altersgrenze_gestaffelt"] = increased_nra_by_birth_year
main(
main_target=MainTarget.results.df_with_nested_columns,
policy_date_str="2025-01-01",
input_data=InputData.tree(
{
"geburtsjahr": np.array([1944, 1945, 1946, 1947, 1948]),
"p_id": np.array([0, 1, 2, 3, 4]),
}
),
tt_targets={
"tree": {
"geburtsjahr": None,
"sozialversicherung": {
"rente": {"altersrente": {"regelaltersrente": {"altersgrenze": None}}}
},
}
},
policy_environment=increased_nra_by_birth_year_pe,
include_warn_nodes=False,
)
[17]:
sozialversicherung | geburtsjahr | |
---|---|---|
rente | NaN | |
altersrente | NaN | |
regelaltersrente | NaN | |
altersgrenze | NaN | |
p_id | ||
0 | 65.0 | 1944 |
1 | 65.5 | 1945 |
2 | 66.0 | 1946 |
3 | 66.5 | 1947 |
4 | 67.0 | 1948 |
Piecewise Polynomial Parameters#
Piecewise polynomial parameters specify a continuous polynomial (first to third degree) on the real line. GETTSIM uses them whenever a parameter is a function of a continuous variable (like income, age, etc.).
Let’s take a look at parameter_behindertenpauschbetrag
, a tax deduction for disabled individuals. The deduction is a function of the degree of disability (between 0 and 100).
[18]:
status_quo_environment["einkommensteuer"]["abzüge"]["parameter_behindertenpauschbetrag"]
[18]:
PiecewisePolynomialParam(start_date=datetime.date(2021, 1, 1), end_date=datetime.date(2099, 12, 31), unit='Euros', reference_period='Year', name={'de': 'Behindertenpauschbetrag in Abhängigkeit des Behinderungsgrads.', 'en': 'Tax Credit for disabled, depending on degree of disability.'}, description={'de': '§ 33b (3) EStG.', 'en': None}, value=PiecewisePolynomialParamValue(thresholds=array([-inf, 20., 30., 40., 50., 60., 70., 80., 90., 100., inf]), intercepts=array([ 0., 384., 620., 860., 1140., 1140., 1780., 2120., 2460.,
2840.]), rates=array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])), note=None, reference=None)
The value
attribute of PiecewisePolynomialParam
stores a PiecewisePolynomialParamValue
object:
[19]:
status_quo_environment["einkommensteuer"]["abzüge"][
"parameter_behindertenpauschbetrag"
].value
[19]:
PiecewisePolynomialParamValue(thresholds=array([-inf, 20., 30., 40., 50., 60., 70., 80., 90., 100., inf]), intercepts=array([ 0., 384., 620., 860., 1140., 1140., 1780., 2120., 2460.,
2840.]), rates=array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]))
Let’s change the schedule such that any disability degree above the level of 50% gets a flat deduction of 3000€.
In order to instantiate a PiecewisePolynomialParamValue
, we have a function get_piecewise_parameters
that takes a configuration dictionary and processes it. Here, we define a piecewise_constant
function on the real line with two pieces:
0
:-inf
to0
with0
as intercept1
:0
toinf
with3000
as intercept
Note: Piecewise polynomial parameters must always be defined on the real line. Note: It can be complex to build the parameter_dict
for get_piecewise_parameters
. For more complex schedules, take a look at the tutorial on piecewise polynomial functions [not available yet, add link eventually; in the mean time, look at the implementation in GETTSIM’s parameter yamls (or ask for help)].
[20]:
from gettsim.tt import PiecewisePolynomialParam, get_piecewise_parameters
increased_behindertenpauschbetrag = PiecewisePolynomialParam(
value=get_piecewise_parameters(
func_type="piecewise_constant",
parameter_dict={
0: {
"lower_threshold": "-inf",
"intercept_at_lower_threshold": 0,
},
1: {
"lower_threshold": 0.5,
"upper_threshold": "inf",
"intercept_at_lower_threshold": 3000.0,
},
},
leaf_name="parameter_behindertenpauschbetrag",
xnp=np,
)
)
increased_behindertenpauschbetrag_pe = copy_environment(status_quo_environment)
increased_behindertenpauschbetrag_pe["einkommensteuer"]["abzüge"][
"parameter_behindertenpauschbetrag"
] = increased_behindertenpauschbetrag
main(
main_target=MainTarget.results.df_with_nested_columns,
policy_date_str="2025-01-01",
input_data=InputData.tree(INPUT_DATA_TREE),
tt_targets={"tree": {"einkommensteuer": {"betrag_y_sn": None}}},
policy_environment=increased_behindertenpauschbetrag_pe,
include_warn_nodes=False,
)
[20]:
einkommensteuer | |
---|---|
betrag_y_sn | |
p_id | |
0 | 17229.0 |
1 | 17229.0 |
2 | 0.0 |
Raw params#
Raw parameters are parameters that are stored in a way that doesn’t fit into the other categories. RawParam
s need to be processed via a ParamFunction
to make them usable for standard ColumnFunction
s (see next section).
Because of this, RawParam
s may come in very different shapes. One example is the income tax tariff. GETTSIM stores it as it is reported in the law. To receive the shape we need to wrap it in a PiecewisePolynomialParam
, some post-processing is needed to convert it to the shape we need.
Let’s take a look at the raw_parameter_einkommensteuertarif
parameter.
[21]:
status_quo_environment["einkommensteuer"]["raw_parameter_einkommensteuertarif"]
[21]:
RawParam(start_date=datetime.date(2025, 1, 1), end_date=datetime.date(2025, 12, 31), unit='Euros', reference_period='Year', name={'de': 'Parameter des Einkommensteuertarifs', 'en': 'Parameters of the income tax schedule'}, description={'de': '§32a EStG Der quadratische Anstieg in den mittleren Intervallen wird nach der Formel des Progressionsfaktors berechnet. Einzelheiten sind im docstring der Funktion add_progressionsfaktor beschrieben.', 'en': '§32a EStG The quadratic rate is calculated by gettsim with the formula of the Progressionsfaktor. For details see the docstring of add_progressionsfaktor.'}, value={0: {'lower_threshold': '-inf', 'upper_threshold': 12096, 'rate_linear': 0, 'rate_quadratic': 0, 'intercept_at_lower_threshold': 0}, 1: {'upper_threshold': 17443, 'rate_linear': 0.14}, 2: {'upper_threshold': 68480, 'rate_linear': 0.2397}, 3: {'upper_threshold': 277825, 'rate_linear': 0.42, 'rate_quadratic': 0}, 4: {'upper_threshold': 'inf', 'rate_linear': 0.45, 'rate_quadratic': 0}}, note=None, reference=None)
[22]:
status_quo_environment["einkommensteuer"]["raw_parameter_einkommensteuertarif"].value
[22]:
{0: {'lower_threshold': '-inf',
'upper_threshold': 12096,
'rate_linear': 0,
'rate_quadratic': 0,
'intercept_at_lower_threshold': 0},
1: {'upper_threshold': 17443, 'rate_linear': 0.14},
2: {'upper_threshold': 68480, 'rate_linear': 0.2397},
3: {'upper_threshold': 277825, 'rate_linear': 0.42, 'rate_quadratic': 0},
4: {'upper_threshold': 'inf', 'rate_linear': 0.45, 'rate_quadratic': 0}}
We modify it by increasing the income exempt from any income tax (upper threshold of the first bracket) from 12096€ to 14000€.
[23]:
from gettsim.tt import RawParam
# Step 1: Create a copy of the status quo policy environment.
increased_tax_exemption_pe = copy_environment(status_quo_environment)
# Step 2: Create the new parameter.
increased_tax_exemption = RawParam(
value={
0: {
"lower_threshold": "-inf",
"upper_threshold": 14000,
"rate_linear": 0,
"rate_quadratic": 0,
"intercept_at_lower_threshold": 0,
},
1: {"upper_threshold": 17443, "rate_linear": 0.14},
2: {"upper_threshold": 68480, "rate_linear": 0.2397},
3: {"upper_threshold": 277825, "rate_linear": 0.42, "rate_quadratic": 0},
4: {"upper_threshold": "inf", "rate_linear": 0.45, "rate_quadratic": 0},
}
)
# Step 3: Insert the new parameter into the copied policy environment
increased_tax_exemption_pe["einkommensteuer"]["raw_parameter_einkommensteuertarif"] = (
increased_tax_exemption
)
main(
main_target=MainTarget.results.df_with_nested_columns,
policy_date_str="2025-01-01",
input_data=InputData.tree(INPUT_DATA_TREE),
tt_targets={"tree": {"einkommensteuer": {"betrag_y_sn": None}}},
policy_environment=increased_tax_exemption_pe,
include_warn_nodes=False,
)
[23]:
einkommensteuer | |
---|---|
betrag_y_sn | |
p_id | |
0 | 17098.0 |
1 | 17098.0 |
2 | 0.0 |
Modifying Parameter Functions#
Parameter functions process parameters. Often, they process RawParam
s to make them usable for standard ColumnFunction
s, but they can also be used to modify the standard parameters described above. Parameter functions must not depend on ColumnFunction
s.
Let’s take a look at the kinderfreibetrag_pro_kind_y
parameter. It is created via a ParamFunction
by summing up the values of the parameter_kinderfreibetrag
parameter dictionary.
@param_function()
def kinderfreibetrag_pro_kind_y(parameter_kinderfreibetrag: dict[str, float]) -> float:
return sum(parameter_kinderfreibetrag.values())
[24]:
status_quo_environment["einkommensteuer"]["kinderfreibetrag_pro_kind_y"]
[24]:
ParamFunction(leaf_name='kinderfreibetrag_pro_kind_y', start_date=datetime.date(1900, 1, 1), end_date=datetime.date(2099, 12, 31), function=<function kinderfreibetrag_pro_kind_y at 0x794b65643f60>, description='None', warn_msg_if_included=None, fail_msg_if_included=None)
Let’s look at a reform that modifies the tax deduction for children by adding the “Bildung und Teilhabe” bonus of the “Kinderzuschlag” to it. This effectively increases the tax deduction for parents with children by 348€ per child.
Instead of rewriting the underlying parameters (strictly speaking, the parameters themselves are not affected by this reform), we can use a ParamFunction
to modify the tax deduction for children only.
The original function looks like this:
@param_function()
def kinderfreibetrag_pro_kind_y(
parameter_kinderfreibetrag: dict[str, float],
) -> float:
return sum(parameter_kinderfreibetrag.values())
This is our modified version:
[25]:
from gettsim.tt import param_function
@param_function()
def kinderfreibetrag_pro_kind_y(
parameter_kinderfreibetrag: dict[str, float],
kinderzuschlag__parameter_existenzminimum: dict[str, float],
) -> float:
return (
sum(parameter_kinderfreibetrag.values())
+ kinderzuschlag__parameter_existenzminimum["bildung_und_teilhabe"]["kind"]
)
As before, we create a copy of the status quo environment and replace the old function with the new one.
[26]:
bildung_und_teilhabe_tax_deductible_pe = copy_environment(status_quo_environment)
bildung_und_teilhabe_tax_deductible_pe["einkommensteuer"][
"kinderfreibetrag_pro_kind_y"
] = kinderfreibetrag_pro_kind_y
main(
main_target=MainTarget.results.df_with_nested_columns,
policy_date_str="2025-01-01",
input_data=InputData.tree(INPUT_DATA_TREE),
tt_targets={"tree": {"einkommensteuer": {"betrag_y_sn": None}}},
policy_environment=bildung_und_teilhabe_tax_deductible_pe,
include_warn_nodes=False,
)
[26]:
einkommensteuer | |
---|---|
betrag_y_sn | |
p_id | |
0 | 17599.0 |
1 | 17599.0 |
2 | 0.0 |
Modifying Column Objects#
ColumnObject
s are objects that operate on columns of data. The policy environment contains three types of ColumnObject
s:
PolicyFunction
s: The most common type ofColumnObject
. They perform the standard tax and transfer calculations on the individual (or group) level.GroupCreationFunction
s: These are used to create grouping IDs for individuals (e.g. Bedarfsgemeinschaften or Ehegemeinschaften).AggByGroupFunction
s: These functions aggregate the results ofPolicyFunction
s by group (e.g. aggregate a transfer from the individual level to the level of a Bedarfsgemeinschaft).AggByPIDFunction
s: These functions aggregate the results ofPolicyFunction
s by person (e.g. aggregate a transfer from the individual level to the level of one specific parent).PolicyInput
s: These are placeholders for the basic inputs of the tax and transfer system, delivering information about the input type. If you add new inputs when modifying the policy environemnt and want to create input data templates (viaMainTarget.templates.input_data_dtypes
), you should add aPolicyInput
for each new input variable. Else, you can safely ignore them.
Here, we look at how to modify PolicyFunction
s. If you want to specify aggregation functions, take a look at GEP 2.
Policy Functions#
PolicyFunction
s are usually written to operate on rows of the input data. They can take any parameter or other ColumnObject
as inputs.
Note: Some PolicyFunction
s operate on columns of input data. Their decorator will contain the term vectorization_strategy="not_required"
.
Let’s take a look at the tax deduction for children again. We look at a reform that abolishes the tax deduction for families with less than 2 children.
This is the original function:
@policy_function()
def kinderfreibetrag_y(
anzahl_kinderfreibeträge: int,
kinderfreibetrag_pro_kind_y: float,
) -> float:
"""Individual child allowance."""
return kinderfreibetrag_pro_kind_y * anzahl_kinderfreibeträge
To do this, we do, as above, the following steps:
Create a copy of the status quo environment
Create a new parameter
min_anzahl_kinder_für_kinderfreibetrag
Create a new
PolicyFunction
that modifies the tax deduction for children based on the new parameterInsert the new parameter and function into the copy of the status quo environment
[27]:
# Step 1: Create a copy of the status quo environment
tax_deduction_only_for_at_least_two_children_pe = copy_environment(
status_quo_environment
)
# Step 2: Create a new parameter `min_anzahl_kinder_für_kinderfreibetrag`
from gettsim.tt import ScalarParam
min_anzahl_kinder_für_kinderfreibetrag = ScalarParam(value=2)
# Step 3: Create a new `PolicyFunction` that modifies the tax deduction for children
# based on the new parameter
from gettsim.tt import policy_function
@policy_function()
def kinderfreibetrag_y(
anzahl_kinderfreibeträge: int,
kinderfreibetrag_pro_kind_y: float,
familie__anzahl_kinder_fg: int,
min_anzahl_kinder_für_kinderfreibetrag: int,
) -> float:
"""Individual child allowance."""
if familie__anzahl_kinder_fg >= min_anzahl_kinder_für_kinderfreibetrag:
out = kinderfreibetrag_pro_kind_y * anzahl_kinderfreibeträge
else:
out = 0.0
return out
# Step 4: Add the new parameter and function to the policy environment
tax_deduction_only_for_at_least_two_children_pe["einkommensteuer"][
"kinderfreibetrag_y"
] = kinderfreibetrag_y
tax_deduction_only_for_at_least_two_children_pe["einkommensteuer"][
"min_anzahl_kinder_für_kinderfreibetrag"
] = min_anzahl_kinder_für_kinderfreibetrag
main(
main_target=MainTarget.results.df_with_nested_columns,
policy_date_str="2025-01-01",
input_data=InputData.tree(INPUT_DATA_TREE),
tt_targets={"tree": {"einkommensteuer": {"betrag_y_sn": None}}},
policy_environment=tax_deduction_only_for_at_least_two_children_pe,
include_warn_nodes=False,
)
[27]:
einkommensteuer | |
---|---|
betrag_y_sn | |
p_id | |
0 | 17915.0 |
1 | 17915.0 |
2 | 0.0 |