Changing Parameters of the Taxes and Transfers System#

This tutorial focuses on the policy_params of GETTSIM, one of the two objects returned by the function set_up_policy_environment. It is a dictionary that contains all the date-specific parameters (e.g. level of unemployment benefit) that are necessary to compute the target variables we are interested in. GETTSIM not only provides current and past policy environments, but also allows users to alter policies for instance by changing parameters.

[1]:
import copy

import numpy as np
import pandas as pd
import plotly.express as px
from gettsim import (
    compute_taxes_and_transfers,
    create_synthetic_data,
    set_up_policy_environment,
)

Example: Increasing Child Benefit#

In this tutorial, we will implement such a policy change: a raise of the child benefit by 20 € for each child.

Small Recap: Kindergeld is a child benefit that can be claimed by parents in Germany. Mainly lower-income families benefit from Kindergeld. For higher-income families, the tax credits for children are more advantageous, so that they do not receive Kindergeld. The child benefit and how they are implemented in GETTSIM is explained in more detail in the advanced usage tutorial.

To implement the raise in GETTSIM, we will change relevant parameters in the existing system by editing the policy parameters that shape the policy environment.

Finding the Relevant Parameter#

Firstly, we can load the existing policy environment for the year 2020 using the function set_up_policy_environment.

[2]:
policy_params, policy_functions = set_up_policy_environment("2020")

We can open the dictionary with all the parameters by typing policy_params. The parameters are saved to a nested dictionary, meaning that the dictionary consists of multiple further dictionaries. This can make displaying the parameter values look confusing at first sight.

To get an idea of the structure of the dictionary, we will work us through it step by step. First we take a look at the keys of the dict. The first layer of keys represent different policy groups.

[3]:
print(*policy_params.keys(), sep="\n")
eink_st
eink_st_abzuege
soli_st
arbeitsl_geld
sozialv_beitr
unterhalt
unterhaltsvors
abgelt_st
wohngeld
kinderzuschl
kinderzuschl_eink
kindergeld
elterngeld
ges_rente
erwerbsm_rente
arbeitsl_geld_2
grunds_im_alter
lohnst
erziehungsgeld

Since we are interested in altering child benefits, we will select the key “kindergeld” to inspect the according parameters further. The parameters are saved to a sub-dictionary which can be selected using policy_params["kindergeld"]. It contains the keys to different parameter groups that affect child benefits in the German system:

[4]:
print(*policy_params["kindergeld"].keys(), sep="\n")
altersgrenze
kindergeld
einkommensgrenze
stundengrenze
kinderbonus
datum
  • kindergeld_altersgrenze is the maximum age of a child that is entitled to Kindergeld.

  • kindergeld is the amount of money that parents receive for their children.

  • kindergeld_einkommensgrenze is the maximum annual income of a child that is still entitled to Kindergeld.

  • kindergeld_stundengrenze is the maximum number of weekly working hours of a child that is entitled to Kindergeld.

  • datum specifies the date to which the parameters apply in the German taxes and transfers system.

The reform we are simulating influences the amount of money that parents receive for their children. Let us take a look at the value of this parameter:

[5]:
policy_params["kindergeld"]["kindergeld"]
[5]:
{1: 204, 2: 204, 3: 210, 4: 235}

For the first and second child, the monthly Kindergeld is 204€. For the third child, monthly Kindergeld is 210€. For each additional child, Kindergeld is 235€.

Changing the Parameter#

To implement the bonus, we create a new parameter dictionary by creating a copy of the original which we can then alter.

[6]:
policy_params_new = copy.deepcopy(policy_params)

Using a loop, we add the 100€ to each entry of our new policy_params_with_bonus["kindergeld"]["kindergeld"].

[7]:
# Loop through policy paramaters to add the special child bonus.
for n in policy_params_new["kindergeld"]["kindergeld"]:
    policy_params_new["kindergeld"]["kindergeld"][n] += 20

Now we can check if the ["kindergeld"]["kindergeld"] entries in the new parameter dictionary (policy_params_new) are as expected:

[8]:
policy_params_new["kindergeld"]["kindergeld"]
[8]:
{1: 224, 2: 224, 3: 230, 4: 255}

It worked, we raised all values by 20!

Applying the Edited Parameter Dictionary to Simulated Households#

We use simulated data to illustrate the impact of the bonus we added to the Kindergeld parameters. For simplicity, we will only look at households with two children and two parents.

[9]:
data = create_synthetic_data(
    n_adults=2,
    n_children=2,
    specs_heterogeneous={
        "bruttolohn_m": [[i, 0, 0, 0] for i in np.linspace(1000, 8000, 701)]
    },
)

# Compute number of children in household and add it to data.
children = compute_taxes_and_transfers(
    data=data,
    params=policy_params,
    targets="anz_kinder_bis_17_hh",
    functions=policy_functions,
)
# Compute sum of pension contributions in household and add it to data.
sum_ges_rente_priv_rente_m = compute_taxes_and_transfers(
    data=data,
    params=policy_params,
    targets="sum_ges_rente_priv_rente_m",
    functions=policy_functions,
)
data["anz_kinder_bis_17_hh"] = children["anz_kinder_bis_17_hh"]
data["sum_ges_rente_priv_rente_m"] = sum_ges_rente_priv_rente_m[
    "sum_ges_rente_priv_rente_m"
]
data.head()
[9]:
p_id hh_id hh_typ hat_kinder alleinerz anz_eig_kind_bis_24 weiblich alter kind in_ausbildung ... m_durchg_alg1_bezug sozialv_pflicht_5j kind_unterh_anspr_m kind_unterh_erhalt_m steuerklasse budgetsatz_erzieh voll_erwerbsgemind teilw_erwerbsgemind anz_kinder_bis_17_hh sum_ges_rente_priv_rente_m
0 0 0 couple_2_children True False 2 False 35 False False ... 0.0 0.0 0.0 0.0 0 False False False 2 0.0
1 1 0 couple_2_children True False 2 True 35 False False ... 0.0 0.0 0.0 0.0 0 False False False 2 0.0
2 2 0 couple_2_children False False 0 False 8 True True ... 0.0 0.0 0.0 0.0 0 False False False 2 0.0
3 3 0 couple_2_children False False 0 True 5 True True ... 0.0 0.0 0.0 0.0 0 False False False 2 0.0
4 4 1 couple_2_children True False 2 False 35 False False ... 0.0 0.0 0.0 0.0 0 False False False 2 0.0

5 rows × 86 columns

This simulated data set shows 701 households of two adults and their two children. The households only vary in their income.

First, we compute the Kindergeld with the original parameters:

[10]:
kindergeld_status_quo = compute_taxes_and_transfers(
    data=data,
    params=policy_params,
    functions=policy_functions,
    targets="kindergeld_m_hh",
)

kindergeld_status_quo[["kindergeld_m_hh"]]
/tmp/ipykernel_3872/879626904.py:1: FunctionsAndColumnsOverlapWarning: Your data provides the columns:

[
    "sum_ges_rente_priv_rente_m",
    "anz_kinder_bis_17_hh",
]

These are already present among the hard-coded functions of the taxes and
transfers system. If you want a data column to be used instead of calculating
it within GETTSIM you do not need to do anything. If you want data columns to
be calculated by hard-coded functions, remove them from the *data* you pass to
GETTSIM. You need to pick one option for each column that appears in the list
above.
If you want to ignore this warning, add the following code to your script
before calling GETTSIM:

import warnings from gettsim import FunctionsAndColumnsOverlapWarning

warnings.filterwarnings(     "ignore",
category=FunctionsAndColumnsOverlapWarning )
  kindergeld_status_quo = compute_taxes_and_transfers(
[10]:
kindergeld_m_hh
0 408
1 408
2 408
3 408
4 408
... ...
2799 408
2800 408
2801 408
2802 408
2803 408

2804 rows × 1 columns

Now we do exactly the same, but using the policy_params_new.

[11]:
kindergeld_new = compute_taxes_and_transfers(
    data=data,
    functions=policy_functions,
    params=policy_params_new,
    targets="kindergeld_m_hh",
)
kindergeld_new[["kindergeld_m_hh"]]
/tmp/ipykernel_3872/3015463161.py:1: FunctionsAndColumnsOverlapWarning: Your data provides the columns:

[
    "sum_ges_rente_priv_rente_m",
    "anz_kinder_bis_17_hh",
]

These are already present among the hard-coded functions of the taxes and
transfers system. If you want a data column to be used instead of calculating
it within GETTSIM you do not need to do anything. If you want data columns to
be calculated by hard-coded functions, remove them from the *data* you pass to
GETTSIM. You need to pick one option for each column that appears in the list
above.
If you want to ignore this warning, add the following code to your script
before calling GETTSIM:

import warnings from gettsim import FunctionsAndColumnsOverlapWarning

warnings.filterwarnings(     "ignore",
category=FunctionsAndColumnsOverlapWarning )
  kindergeld_new = compute_taxes_and_transfers(
[11]:
kindergeld_m_hh
0 448
1 448
2 448
3 448
4 448
... ...
2799 448
2800 448
2801 448
2802 448
2803 448

2804 rows × 1 columns

Compare Results#

Using the results shown above, we can now easily visualize and compare the amount of Kindergeld for different income groups in the two scenarios. Therefore, we reorganize our data. We create a DataFrame with the total monthly income of each household and the Kindergeld they receive with the policy_params_new and the policy_params:

[12]:
# Group data by household id and sum the gross monthly income.
total_income_m_hh = data.groupby("hh_id")["bruttolohn_m"].sum()
total_income_m_hh.tail(10)
[12]:
hh_id
691    7910.0
692    7920.0
693    7930.0
694    7940.0
695    7950.0
696    7960.0
697    7970.0
698    7980.0
699    7990.0
700    8000.0
Name: bruttolohn_m, dtype: float64
[13]:
# Create DataFrame with relevant columns for plotting.
df = pd.DataFrame()
df["Status Quo"] = kindergeld_status_quo["kindergeld_m_hh"]
df["After raise"] = kindergeld_new["kindergeld_m_hh"]
df["hh_id"] = data["hh_id"]
df = df.drop_duplicates("hh_id").set_index("hh_id")
df["Income (per household)"] = total_income_m_hh

We can illustrate the effect of the special child bonus of 2020 by plotting the monthly Kindergeld against the income on household level.

[14]:
fig = px.line(
    data_frame=df,
    x="Income (per household)",
    y=["Status Quo", "After raise"],
    title="Effects of hypothetical raise of Kindergeld",
)
fig.update_layout(
    xaxis_title="Monthly gross income in € (per household)",
    yaxis_title="Kindergeld in € per month",
)

Unsurprisingly, the policy reform increases the amount of money that households receive if they are entitled to Kindergeld.

Of course this is a very basic visualization and analysis. Have a look at the policy functions tutorial to see a more sophisticated approach for this example taking into account the interaction with ALG-II. At the same time you will learn learn how to change and add functions to a policy environment!