{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Taxes & Transfers Objects and Modifications of the Policy Environment\n", "\n", "GETTSIM's design allows you to go beyond the depiction of the current (or historical)\n", "tax and transfer system. Analyzing counterfactual reform scenarios, ranging from small\n", "changes of certain parameters of the tax and transfer system, to the introduction of\n", "large-scale reforms, is a common use case.\n", "\n", "This tutorial showcases how to modify the calculation of taxes and transfers when using\n", "GETTSIM.\n", "\n", "Here, we focus mainly on small reforms to the calculation of income taxes. We pick this\n", "example because it is a fairly complex system that uses (almost) the entire range of\n", "objects TTSIM offers." ] }, { "cell_type": "markdown", "id": "1", "metadata": {}, "source": [ "## Status Quo\n", "\n", "Before modifying taxes and transfers, it's important to understand how GETTSIM\n", "represents the current tax and transfer system. The core of GETTSIM's implementation is\n", "the **policy environment**.\n", "\n", "### What is a Policy Environment?\n", "\n", "A policy environment is a nested dictionary that holds all the parameters and functions\n", "needed to calculate taxes and transfers for a given policy date. Think of it as a\n", "complete snapshot of the tax and transfer system at a particular point in time.\n", "\n", "### The Three Types of Objects\n", "\n", "The policy environment contains three main categories of objects:\n", "\n", "1. **Column Objects** (`ColumnObjects`): These work with data columns - either input\n", " data you provide or results computed by previous functions. They handle the actual\n", " calculations and data processing.\n", "\n", "2. **Parameter Objects** (`ParamObjects`): These store the parameters and constants\n", " used in calculations, such as tax rates, benefit amounts, or thresholds.\n", "\n", "3. **Parameter Functions** (`ParamFunctions`): These process parameters which cannot be\n", " used directly by the column objects.\n", "\n", "### Getting Started\n", "\n", "After the standard imports, the first step in modifying taxes and transfers is to create\n", "the base policy environment for the date you want to work with." ] }, { "cell_type": "code", "execution_count": null, "id": "2", "metadata": {}, "outputs": [], "source": [ "from __future__ import annotations\n", "\n", "import numpy as np\n", "\n", "from gettsim import InputData, MainTarget, copy_environment, main" ] }, { "cell_type": "code", "execution_count": null, "id": "3", "metadata": {}, "outputs": [], "source": [ "status_quo_environment = main(\n", " main_target=MainTarget.policy_environment,\n", " policy_date_str=\"2025-01-01\",\n", " backend=\"numpy\",\n", ")" ] }, { "cell_type": "markdown", "id": "4", "metadata": {}, "source": [ "We also create some input data in order to verify how our modifications affect the\n", "output. The following input data is required to compute the amount of income tax when\n", "assuming parental leave benefits (i.e. `('elterngeld', 'betrag_m')`), pensions (i.e.\n", "`('sozialversicherung', 'rente', 'altersrente', 'betrag_m')`, `('sozialversicherung',\n", "'rente', 'erwerbsminderung', 'betrag_m')`) and unemployment benefits (i.e.\n", "`('sozialversicherung', 'arbeitslosen', 'betrag_m')`) are fixed at some value." ] }, { "cell_type": "code", "execution_count": null, "id": "5", "metadata": {}, "outputs": [], "source": [ "INPUT_DATA_TREE = {\n", " \"alter\": np.array([40, 40, 5]),\n", " \"arbeitsstunden_w\": np.array([40, 40, 0]),\n", " \"behinderungsgrad\": np.array([0, 60, 0]),\n", " \"einnahmen\": {\n", " \"bruttolohn_m\": np.array([6000.0, 4000.0, 0.0]),\n", " \"kapitalerträge_m\": np.array([0.0, 0.0, 0.0]),\n", " \"renten\": {\n", " \"betriebliche_altersvorsorge_m\": np.array([0.0, 0.0, 0.0]),\n", " \"geförderte_private_vorsorge_m\": np.array([0.0, 0.0, 0.0]),\n", " \"sonstige_private_vorsorge_m\": np.array([0.0, 0.0, 0.0]),\n", " },\n", " },\n", " \"einkommensteuer\": {\n", " \"abzüge\": {\n", " \"beitrag_private_rentenversicherung_m\": np.array([120, 120, 0]),\n", " \"kinderbetreuungskosten_m\": np.array([0.0, 0.0, 120.0]),\n", " \"p_id_kinderbetreuungskostenträger\": np.array([-1, -1, 0]),\n", " },\n", " \"einkünfte\": {\n", " \"aus_forst_und_landwirtschaft\": {\"betrag_m\": np.array([0.0, 0.0, 0.0])},\n", " \"aus_gewerbebetrieb\": {\"betrag_m\": np.array([0.0, 0.0, 0.0])},\n", " \"aus_selbstständiger_arbeit\": {\"betrag_m\": np.array([0.0, 0.0, 0.0])},\n", " \"aus_vermietung_und_verpachtung\": {\"betrag_m\": np.array([0.0, 0.0, 0.0])},\n", " \"ist_hauptberuflich_selbstständig\": np.array([False, False, False]),\n", " \"sonstige\": {\n", " \"alle_weiteren_m\": np.array([0.0, 0.0, 0.0]),\n", " },\n", " },\n", " \"gemeinsam_veranlagt\": np.array([True, True, False]),\n", " },\n", " \"bürgergeld\": {\n", " \"p_id_einstandspartner\": np.array([1, 0, -1])\n", " }, # not required for ESt, but we need it later\n", " \"familie\": {\n", " \"alleinerziehend\": np.array([False, False, False]),\n", " \"p_id_ehepartner\": np.array([1, 0, -1]),\n", " \"p_id_elternteil_1\": np.array([-1, -1, 0]),\n", " \"p_id_elternteil_2\": np.array([-1, -1, 1]),\n", " },\n", " \"geburtsjahr\": np.array([1985, 1985, 2020]),\n", " \"kindergeld\": {\n", " \"in_ausbildung\": np.array([False, False, False]),\n", " \"p_id_empfänger\": np.array([-1, -1, 0]),\n", " },\n", " \"p_id\": np.array([0, 1, 2]),\n", " \"hh_id\": np.array([0, 0, 0]),\n", " \"sozialversicherung\": {\n", " \"kranken\": {\"beitrag\": {\"privat_versichert\": np.array([False, False, False])}},\n", " \"pflege\": {\"beitrag\": {\"hat_kinder\": np.array([True, True, False])}},\n", " \"rente\": {\n", " \"altersrente\": {\"betrag_m\": np.array([0.0, 0.0, 0.0])},\n", " \"erwerbsminderung\": {\"betrag_m\": np.array([0.0, 0.0, 0.0])},\n", " \"jahr_renteneintritt\": np.array([2060, 2060, 2090]),\n", " },\n", " },\n", "}" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "The status quo is the following:" ] }, { "cell_type": "code", "execution_count": null, "id": "7", "metadata": {}, "outputs": [], "source": [ "main(\n", " main_target=MainTarget.results.df_with_nested_columns,\n", " policy_date_str=\"2025-01-01\",\n", " input_data=InputData.tree(INPUT_DATA_TREE),\n", " tt_targets={\"tree\": {\"einkommensteuer\": {\"betrag_y_sn\": None}}},\n", " include_warn_nodes=False,\n", ")" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "## Modifying Parameters\n", "\n", "GETTSIM's parameters are stored in different objects depending on the form they are\n", "specified in the law. If you modify the parameters in the `policy_environment`, you will\n", "encounter the following objects:\n", "\n", "1. **ScalarParam**: A scalar parameter, i.e. a parameter that is a single number.\n", "2. **DictParam**: A parameter that is a flat dictionary with homogeneous keys and values\n", " (i.e. all keys and values are of the same type).\n", "3. **ConsecutiveIntLookupTableParam**: A lookup table that stores values and assigns a\n", " consecutive integer index to each value.\n", "4. **PiecewisePolynomialParam**: A piecewise polynomial parameter, i.e. a parameter that\n", " describes a piecewise polynomial function.\n", "5. **RawParam**: A parameter that does not fit into the other categories. For these\n", " parameters, we need `ParamFunction`s to process them (see next section).\n", "\n", "All of those parameter classes have the following attributes:\n", "- `leaf_name`: The leaf name of the parameter in GETTSIM's policy environment.\n", "- `start_date`: The date from which the parameter is valid (if applicable).\n", "- `end_date`: The date until which the parameter is valid (if applicable).\n", "- `unit`: The unit of the parameter (if applicable).\n", "- `reference_period`: The period over which the parameter is valid (if applicable).\n", "- `name`: The name of the parameter.\n", "- `description`: A more elaborate description of the parameter.\n", "- `value`: The value of the parameter.\n", "- `note`: Some notes (if applicable).\n", "- `reference`: A legal reference.\n", "\n", "When modifying parameters, you will mostly care about the `value` attribute.\n", "\n", "### Scalar Parameters\n", "\n", "Scalar parameters are the simplest type of parameters. They are represented by the\n", "`ScalarParam` class.\n", "\n", "Let's take a look at the `arbeitnehmerpauschbetrag` parameter, a flat deduction applied\n", "to income from regular employment.\n", "\n", "As you can see the `arbeitnehmerpauschbetrag` parameter is a `ScalarParam` object and\n", "its value is 1230€ in the status quo." ] }, { "cell_type": "code", "execution_count": null, "id": "9", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"einkünfte\"][\n", " \"aus_nichtselbstständiger_arbeit\"\n", "][\"arbeitnehmerpauschbetrag\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "10", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"einkünfte\"][\n", " \"aus_nichtselbstständiger_arbeit\"\n", "][\"arbeitnehmerpauschbetrag\"].value" ] }, { "cell_type": "markdown", "id": "11", "metadata": {}, "source": [ "Let's increase the parameter.\n", "\n", "#### Step 1: Create a copy of the status quo policy environment. \n", "\n", "This is good practice to avoid in-place modifications of the original policy\n", "environment." ] }, { "cell_type": "code", "execution_count": null, "id": "12", "metadata": {}, "outputs": [], "source": [ "higher_arbeitnehmerpauschbetrag_pe = copy_environment(status_quo_environment)" ] }, { "cell_type": "markdown", "id": "13", "metadata": {}, "source": [ "#### Step 2: Create the new parameter.\n", "\n", "Create a new `ScalarParam` object. To do this, we first import the `ScalarParam` class\n", "from GETTSIM and then instantiate it with the new value.\n", "\n", "**Note**: You don't have to specify all attributes of the `ScalarParam` class. Only the\n", "value attribute is required." ] }, { "cell_type": "code", "execution_count": null, "id": "14", "metadata": {}, "outputs": [], "source": [ "from gettsim.tt import ScalarParam\n", "\n", "higher_arbeitnehmerpauschbetrag = ScalarParam(value=1600)" ] }, { "cell_type": "markdown", "id": "15", "metadata": {}, "source": [ "#### Step 3: Replace the old parameter with the new one in the new policy environment" ] }, { "cell_type": "code", "execution_count": null, "id": "16", "metadata": {}, "outputs": [], "source": [ "higher_arbeitnehmerpauschbetrag_pe[\"einkommensteuer\"][\"einkünfte\"][\n", " \"aus_nichtselbstständiger_arbeit\"\n", "][\"arbeitnehmerpauschbetrag\"] = higher_arbeitnehmerpauschbetrag" ] }, { "cell_type": "markdown", "id": "17", "metadata": {}, "source": [ "Let's call GETTSIM with the modified policy environment." ] }, { "cell_type": "code", "execution_count": null, "id": "18", "metadata": {}, "outputs": [], "source": [ "main(\n", " main_target=MainTarget.results.df_with_nested_columns,\n", " policy_date_str=\"2025-01-01\",\n", " input_data=InputData.tree(INPUT_DATA_TREE),\n", " tt_targets={\"tree\": {\"einkommensteuer\": {\"betrag_y_sn\": None}}},\n", " policy_environment=higher_arbeitnehmerpauschbetrag_pe,\n", " include_warn_nodes=False,\n", ")" ] }, { "cell_type": "markdown", "id": "19", "metadata": {}, "source": [ "### Dict Parameters\n", "\n", "Dict parameters are parameters that are a flat dictionary with homogeneous keys and\n", "values. They are represented by the `DictParam` class. They are stored as a flat\n", "dictionary in the `policy_environment`.\n", "\n", "Let's take a look at the `parameter_kinderfreibetrag` parameter. This parameter\n", "contains tax deductions for parents of young children.\n", "\n", "As you can see the `parameter_kinderfreibetrag` parameter is a `DictParam` object and\n", "its value is a dictionary." ] }, { "cell_type": "code", "execution_count": null, "id": "20", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"parameter_kinderfreibetrag\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "21", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"parameter_kinderfreibetrag\"].value" ] }, { "cell_type": "markdown", "id": "22", "metadata": {}, "source": [ "Let's modify the parameter by increasing the \"sächliches Existenzminimum\" for children.\n", "We follow the same steps as in the previous section." ] }, { "cell_type": "code", "execution_count": null, "id": "23", "metadata": {}, "outputs": [], "source": [ "from gettsim.tt import DictParam\n", "\n", "# Step 1: Create a copy of the status quo policy environment.\n", "higher_kinderfreibetrag_pe = copy_environment(status_quo_environment)\n", "\n", "# Step 2: Create a new parameter `parameter_kinderfreibetrag`\n", "higher_kinderfreibetrag = DictParam(\n", " value={\n", " \"betreuung_erziehung_ausbildung\": 1464,\n", " \"sächliches_existenzminimum\": 4000,\n", " },\n", ")\n", "\n", "# Step 3: Insert the new parameter into the copied policy environment\n", "higher_kinderfreibetrag_pe[\"einkommensteuer\"][\"parameter_kinderfreibetrag\"] = (\n", " higher_kinderfreibetrag\n", ")\n", "\n", "main(\n", " main_target=MainTarget.results.df_with_nested_columns,\n", " policy_date_str=\"2025-01-01\",\n", " input_data=InputData.tree(INPUT_DATA_TREE),\n", " tt_targets={\"tree\": {\"einkommensteuer\": {\"betrag_y_sn\": None}}},\n", " policy_environment=higher_kinderfreibetrag_pe,\n", " include_warn_nodes=False,\n", ")" ] }, { "cell_type": "markdown", "id": "24", "metadata": {}, "source": [ "### Consecutive Int Lookup Table Parameters\n", "\n", "In many cases, we need to look up values in a table, for which we tend to use\n", "dictionaries. E.g., the increase in the normal retirement age for the 1940s cohort would\n", "look like this:\n", "\n", "```python\n", "{\n", " 1940: 65,\n", " 1941: 65,\n", " 1942: 65,\n", " 1943: 65,\n", " 1944: 65,\n", " 1945: 65,\n", " 1946: 65,\n", " 1947: 65.08333333,\n", " 1948: 65.16666667,\n", " 1949: 65.25,\n", "}\n", "``` \n", "\n", "For all its efficiency, our alternative computing engine Jax does not support such lookup tables. \n", "We thus construct our own version, which only works for consecutive integer values\n", "(i.e., the dictionary keys could not be `red`, `blue`, `green`, or `1`, `7`, `9`, etc.).\n", "\n", "Let's look at the implementation." ] }, { "cell_type": "code", "execution_count": null, "id": "25", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"sozialversicherung\"][\"rente\"][\"altersrente\"][\n", " \"regelaltersrente\"\n", "][\"altersgrenze_gestaffelt\"]" ] }, { "cell_type": "markdown", "id": "26", "metadata": {}, "source": [ "The `value` attribute of `ConsecutiveIntLookupTableParam` stores a `ConsecutiveIntLookupTableParamValue` object:" ] }, { "cell_type": "code", "execution_count": null, "id": "27", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"sozialversicherung\"][\"rente\"][\"altersrente\"][\n", " \"regelaltersrente\"\n", "][\"altersgrenze_gestaffelt\"].value" ] }, { "cell_type": "markdown", "id": "28", "metadata": {}, "source": [ "This object has a `look_up` method, which returns the value for the given key." ] }, { "cell_type": "code", "execution_count": null, "id": "29", "metadata": {}, "outputs": [], "source": [ "for i in range(1940, 1950):\n", " print(\n", " i,\n", " status_quo_environment[\"sozialversicherung\"][\"rente\"][\"altersrente\"][\n", " \"regelaltersrente\"\n", " ][\"altersgrenze_gestaffelt\"].value.look_up(i),\n", " )" ] }, { "cell_type": "markdown", "id": "30", "metadata": {}, "source": [ "Let's create a modified version with a steeper increase in the normal retirement age.\n", "\n", "In order to instantiate a `ConsecutiveIntLookupTableParamValue`, we have a function\n", "`get_consecutive_int_lookup_table_param_value` that takes a dictionary as above:" ] }, { "cell_type": "code", "execution_count": null, "id": "31", "metadata": {}, "outputs": [], "source": [ "from gettsim.tt import (\n", " ConsecutiveIntLookupTableParam,\n", " get_consecutive_int_lookup_table_param_value,\n", ")\n", "\n", "# Step 1: Create a copy of the status quo policy environment.\n", "increased_nra_by_birth_year_pe = copy_environment(status_quo_environment)\n", "\n", "# Step 2: Create a new parameter `altersgrenze_gestaffelt`\n", "increased_nra_by_birth_year = ConsecutiveIntLookupTableParam(\n", " value=get_consecutive_int_lookup_table_param_value(\n", " raw={\n", " 1940: 65,\n", " 1941: 65,\n", " 1942: 65,\n", " 1943: 65,\n", " 1944: 65,\n", " 1945: 65.5,\n", " 1946: 66,\n", " 1947: 66.5,\n", " 1948: 67,\n", " 1949: 67,\n", " },\n", " xnp=np,\n", " ),\n", ")\n", "\n", "# Step 3: Insert the new parameter into the copied policy environment\n", "increased_nra_by_birth_year_pe[\"sozialversicherung\"][\"rente\"][\"altersrente\"][\n", " \"regelaltersrente\"\n", "][\"altersgrenze_gestaffelt\"] = increased_nra_by_birth_year\n", "\n", "main(\n", " main_target=MainTarget.results.df_with_nested_columns,\n", " policy_date_str=\"2025-01-01\",\n", " input_data=InputData.tree(\n", " {\n", " \"geburtsjahr\": np.array([1944, 1945, 1946, 1947, 1948]),\n", " \"p_id\": np.array([0, 1, 2, 3, 4]),\n", " }\n", " ),\n", " tt_targets={\n", " \"tree\": {\n", " \"geburtsjahr\": None,\n", " \"sozialversicherung\": {\n", " \"rente\": {\"altersrente\": {\"regelaltersrente\": {\"altersgrenze\": None}}}\n", " },\n", " }\n", " },\n", " policy_environment=increased_nra_by_birth_year_pe,\n", " include_warn_nodes=False,\n", ")" ] }, { "cell_type": "markdown", "id": "32", "metadata": {}, "source": [ "### Piecewise Polynomial Parameters\n", "\n", "Piecewise polynomial parameters specify a continuous polynomial (first to third degree)\n", "on the real line. GETTSIM uses them whenever a parameter is a function of a continuous\n", "variable (like income, age, etc.).\n", "\n", "Let's take a look at `parameter_behindertenpauschbetrag`, a tax deduction for disabled\n", "individuals. The deduction is a function of the degree of disability (between 0 and\n", "100)." ] }, { "cell_type": "code", "execution_count": null, "id": "33", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"abzüge\"][\"parameter_behindertenpauschbetrag\"]" ] }, { "cell_type": "markdown", "id": "34", "metadata": {}, "source": [ "The `value` attribute of `PiecewisePolynomialParam` stores a `PiecewisePolynomialParamValue` object:" ] }, { "cell_type": "code", "execution_count": null, "id": "35", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"abzüge\"][\n", " \"parameter_behindertenpauschbetrag\"\n", "].value" ] }, { "cell_type": "markdown", "id": "36", "metadata": {}, "source": [ "Let's change the schedule such that any disability degree above the level of 50% gets a\n", "flat deduction of 3000€.\n", "\n", "In order to instantiate a `PiecewisePolynomialParamValue`, we have a function\n", "`get_piecewise_parameters` that takes a configuration dictionary and processes it. Here,\n", "we define a `piecewise_constant` function on the real line with two pieces:\n", "\n", "- `0`: `-inf` to `0` with `0` as intercept\n", "- `1`: `0` to `inf` with `3000` as intercept\n", "\n", "**Note:** Piecewise polynomial parameters must **always** be defined on the real line.\n", "**Note:** It can be complex to build the `parameter_dict` for\n", "`get_piecewise_parameters`. For more complex schedules, take a look at the tutorial on\n", "piecewise polynomial functions [not available yet, add link eventually; in the mean\n", "time, look at the implementation in GETTSIM's parameter yamls (or ask for help)]." ] }, { "cell_type": "code", "execution_count": null, "id": "37", "metadata": {}, "outputs": [], "source": [ "from gettsim.tt import PiecewisePolynomialParam, get_piecewise_parameters\n", "\n", "increased_behindertenpauschbetrag = PiecewisePolynomialParam(\n", " value=get_piecewise_parameters(\n", " func_type=\"piecewise_constant\",\n", " parameter_dict={\n", " 0: {\n", " \"lower_threshold\": \"-inf\",\n", " \"intercept_at_lower_threshold\": 0,\n", " },\n", " 1: {\n", " \"lower_threshold\": 0.5,\n", " \"upper_threshold\": \"inf\",\n", " \"intercept_at_lower_threshold\": 3000.0,\n", " },\n", " },\n", " leaf_name=\"parameter_behindertenpauschbetrag\",\n", " xnp=np,\n", " )\n", ")\n", "\n", "increased_behindertenpauschbetrag_pe = copy_environment(status_quo_environment)\n", "increased_behindertenpauschbetrag_pe[\"einkommensteuer\"][\"abzüge\"][\n", " \"parameter_behindertenpauschbetrag\"\n", "] = increased_behindertenpauschbetrag\n", "\n", "main(\n", " main_target=MainTarget.results.df_with_nested_columns,\n", " policy_date_str=\"2025-01-01\",\n", " input_data=InputData.tree(INPUT_DATA_TREE),\n", " tt_targets={\"tree\": {\"einkommensteuer\": {\"betrag_y_sn\": None}}},\n", " policy_environment=increased_behindertenpauschbetrag_pe,\n", " include_warn_nodes=False,\n", ")" ] }, { "cell_type": "markdown", "id": "38", "metadata": {}, "source": [ "### Raw params\n", "\n", "Raw parameters are parameters that are stored in a way that doesn't fit into the other\n", "categories. `RawParam`s need to be processed via a `ParamFunction` to make them usable\n", "for standard `ColumnFunction`s (see next section).\n", "\n", "Because of this, `RawParam`s may come in very different shapes. One example is the\n", "income tax tariff. GETTSIM stores it as it is reported in the law. To receive the shape\n", "we need to wrap it in a `PiecewisePolynomialParam`, some post-processing is needed to\n", "convert it to the shape we need.\n", "\n", "Let's take a look at the `raw_parameter_einkommensteuertarif` parameter." ] }, { "cell_type": "code", "execution_count": null, "id": "39", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"raw_parameter_einkommensteuertarif\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "40", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"raw_parameter_einkommensteuertarif\"].value" ] }, { "cell_type": "markdown", "id": "41", "metadata": {}, "source": [ "We modify it by increasing the income exempt from any income tax (upper threshold of the\n", "first bracket) from 12096€ to 14000€." ] }, { "cell_type": "code", "execution_count": null, "id": "42", "metadata": {}, "outputs": [], "source": [ "from gettsim.tt import RawParam\n", "\n", "# Step 1: Create a copy of the status quo policy environment.\n", "increased_tax_exemption_pe = copy_environment(status_quo_environment)\n", "\n", "# Step 2: Create the new parameter.\n", "increased_tax_exemption = RawParam(\n", " value={\n", " 0: {\n", " \"lower_threshold\": \"-inf\",\n", " \"upper_threshold\": 14000,\n", " \"rate_linear\": 0,\n", " \"rate_quadratic\": 0,\n", " \"intercept_at_lower_threshold\": 0,\n", " },\n", " 1: {\"upper_threshold\": 17443, \"rate_linear\": 0.14},\n", " 2: {\"upper_threshold\": 68480, \"rate_linear\": 0.2397},\n", " 3: {\"upper_threshold\": 277825, \"rate_linear\": 0.42, \"rate_quadratic\": 0},\n", " 4: {\"upper_threshold\": \"inf\", \"rate_linear\": 0.45, \"rate_quadratic\": 0},\n", " }\n", ")\n", "\n", "# Step 3: Insert the new parameter into the copied policy environment\n", "increased_tax_exemption_pe[\"einkommensteuer\"][\"raw_parameter_einkommensteuertarif\"] = (\n", " increased_tax_exemption\n", ")\n", "\n", "main(\n", " main_target=MainTarget.results.df_with_nested_columns,\n", " policy_date_str=\"2025-01-01\",\n", " input_data=InputData.tree(INPUT_DATA_TREE),\n", " tt_targets={\"tree\": {\"einkommensteuer\": {\"betrag_y_sn\": None}}},\n", " policy_environment=increased_tax_exemption_pe,\n", " include_warn_nodes=False,\n", ")" ] }, { "cell_type": "markdown", "id": "43", "metadata": {}, "source": [ "## Modifying Parameter Functions\n", "\n", "Parameter functions process parameters. Often, they process `RawParam`s to make them\n", "usable for standard `ColumnFunction`s, but they can also be used to modify the standard\n", "parameters described above. Parameter functions must not depend on `ColumnFunction`s.\n", "\n", "Let's take a look at the `kinderfreibetrag_pro_kind_y` parameter. It is created via a\n", "`ParamFunction` by summing up the values of the `parameter_kinderfreibetrag` parameter\n", "dictionary.\n", "\n", "```python\n", "@param_function()\n", "def kinderfreibetrag_pro_kind_y(parameter_kinderfreibetrag: dict[str, float]) -> float:\n", " return sum(parameter_kinderfreibetrag.values())\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "44", "metadata": {}, "outputs": [], "source": [ "status_quo_environment[\"einkommensteuer\"][\"kinderfreibetrag_pro_kind_y\"]" ] }, { "cell_type": "markdown", "id": "45", "metadata": {}, "source": [ "Let's look at a reform that modifies the tax deduction for children by adding the\n", "\"Bildung und Teilhabe\" bonus of the \"Kinderzuschlag\" to it. This effectively increases\n", "the tax deduction for parents with children by 348€ per child. \n", "\n", "Instead of rewriting the underlying parameters (strictly speaking, the parameters\n", "themselves are not affected by this reform), we can use a `ParamFunction` to modify the\n", "tax deduction for children only.\n", "\n", "The original function looks like this:\n", "\n", "```python\n", "@param_function()\n", "def kinderfreibetrag_pro_kind_y(\n", " parameter_kinderfreibetrag: dict[str, float],\n", ") -> float:\n", " return sum(parameter_kinderfreibetrag.values())\n", "```\n", "\n", "This is our modified version:" ] }, { "cell_type": "code", "execution_count": null, "id": "46", "metadata": {}, "outputs": [], "source": [ "from gettsim.tt import param_function\n", "\n", "\n", "@param_function()\n", "def kinderfreibetrag_pro_kind_y(\n", " parameter_kinderfreibetrag: dict[str, float],\n", " kinderzuschlag__parameter_existenzminimum: dict[str, float],\n", ") -> float:\n", " return (\n", " sum(parameter_kinderfreibetrag.values())\n", " + kinderzuschlag__parameter_existenzminimum[\"bildung_und_teilhabe\"][\"kind\"]\n", " )" ] }, { "cell_type": "markdown", "id": "47", "metadata": {}, "source": [ "As before, we create a copy of the status quo environment and replace the old function\n", "with the new one." ] }, { "cell_type": "code", "execution_count": null, "id": "48", "metadata": {}, "outputs": [], "source": [ "bildung_und_teilhabe_tax_deductible_pe = copy_environment(status_quo_environment)\n", "bildung_und_teilhabe_tax_deductible_pe[\"einkommensteuer\"][\n", " \"kinderfreibetrag_pro_kind_y\"\n", "] = kinderfreibetrag_pro_kind_y\n", "\n", "main(\n", " main_target=MainTarget.results.df_with_nested_columns,\n", " policy_date_str=\"2025-01-01\",\n", " input_data=InputData.tree(INPUT_DATA_TREE),\n", " tt_targets={\"tree\": {\"einkommensteuer\": {\"betrag_y_sn\": None}}},\n", " policy_environment=bildung_und_teilhabe_tax_deductible_pe,\n", " include_warn_nodes=False,\n", ")" ] }, { "cell_type": "markdown", "id": "49", "metadata": {}, "source": [ "## Modifying Column Objects\n", "\n", "`ColumnObject`s are objects that operate on columns of data. The policy environment\n", "contains three types of `ColumnObject`s:\n", "\n", "- `PolicyFunction`s: The most common type of `ColumnObject`. They perform the standard\n", " tax and transfer calculations on the individual (or group) level.\n", "- `GroupCreationFunction`s: These are used to create grouping IDs for individuals (e.g.\n", " Bedarfsgemeinschaften or Ehegemeinschaften).\n", "- `AggByGroupFunction`s: These functions aggregate the results of `PolicyFunction`s\n", " by group (e.g. aggregate a transfer from the individual level to the level of a\n", " Bedarfsgemeinschaft).\n", "- `AggByPIDFunction`s: These functions aggregate the results of `PolicyFunction`s by\n", " person (e.g. aggregate a transfer from the individual level to the level of one\n", " specific parent).\n", "- `PolicyInput`s: These are placeholders for the basic inputs of the tax and transfer\n", " system, delivering information about the input type. If you add new inputs when\n", " modifying the policy environemnt **and** want to create input data templates (via\n", " `MainTarget.templates.input_data_dtypes`), you should add a `PolicyInput` for each new\n", " input variable. Else, you can safely ignore them.\n", "\n", "\n", "Here, we look at how to modify `PolicyFunction`s. If you want to specify aggregation\n", "functions, take a look at [GEP\n", "2](https://gettsim.readthedocs.io/en/stable/geps/gep-04.html).\n", "\n", "### Policy Functions\n", "\n", "`PolicyFunction`s are usually written to operate on rows of the input data. They can\n", "take any parameter or other `ColumnObject` as inputs.\n", "\n", "**Note**: Some `PolicyFunction`s operate on columns of input data. Their decorator will\n", "contain the term `vectorization_strategy=\"not_required\"`.\n", "\n", "Let's take a look at the tax deduction for children again. We look at a reform that\n", "abolishes the tax deduction for families with less than 2 children.\n", "\n", "This is the original function:\n", "\n", "```python\n", "@policy_function()\n", "def kinderfreibetrag_y(\n", " anzahl_kinderfreibeträge: int,\n", " kinderfreibetrag_pro_kind_y: float,\n", ") -> float:\n", " \"\"\"Individual child allowance.\"\"\"\n", " return kinderfreibetrag_pro_kind_y * anzahl_kinderfreibeträge\n", "```\n", "\n", "To do this, we do, as above, the following steps:\n", "1. Create a copy of the status quo environment\n", "2. Create a new parameter `min_anzahl_kinder_für_kinderfreibetrag`\n", "3. Create a new `PolicyFunction` that modifies the tax deduction for children based on\n", " the new parameter\n", "4. Insert the new parameter and function into the copy of the status quo environment" ] }, { "cell_type": "code", "execution_count": null, "id": "50", "metadata": {}, "outputs": [], "source": [ "# Step 1: Create a copy of the status quo environment\n", "tax_deduction_only_for_at_least_two_children_pe = copy_environment(\n", " status_quo_environment\n", ")\n", "\n", "# Step 2: Create a new parameter `min_anzahl_kinder_für_kinderfreibetrag`\n", "from gettsim.tt import ScalarParam\n", "\n", "min_anzahl_kinder_für_kinderfreibetrag = ScalarParam(value=2)\n", "\n", "# Step 3: Create a new `PolicyFunction` that modifies the tax deduction for children\n", "# based on the new parameter\n", "from gettsim.tt import policy_function\n", "\n", "\n", "@policy_function()\n", "def kinderfreibetrag_y(\n", " anzahl_kinderfreibeträge: int,\n", " kinderfreibetrag_pro_kind_y: float,\n", " familie__anzahl_kinder_fg: int,\n", " min_anzahl_kinder_für_kinderfreibetrag: int,\n", ") -> float:\n", " \"\"\"Individual child allowance.\"\"\"\n", " if familie__anzahl_kinder_fg >= min_anzahl_kinder_für_kinderfreibetrag:\n", " out = kinderfreibetrag_pro_kind_y * anzahl_kinderfreibeträge\n", " else:\n", " out = 0.0\n", " return out\n", "\n", "\n", "# Step 4: Add the new parameter and function to the policy environment\n", "tax_deduction_only_for_at_least_two_children_pe[\"einkommensteuer\"][\n", " \"kinderfreibetrag_y\"\n", "] = kinderfreibetrag_y\n", "tax_deduction_only_for_at_least_two_children_pe[\"einkommensteuer\"][\n", " \"min_anzahl_kinder_für_kinderfreibetrag\"\n", "] = min_anzahl_kinder_für_kinderfreibetrag\n", "\n", "\n", "main(\n", " main_target=MainTarget.results.df_with_nested_columns,\n", " policy_date_str=\"2025-01-01\",\n", " input_data=InputData.tree(INPUT_DATA_TREE),\n", " tt_targets={\"tree\": {\"einkommensteuer\": {\"betrag_y_sn\": None}}},\n", " policy_environment=tax_deduction_only_for_at_least_two_children_pe,\n", " include_warn_nodes=False,\n", ")" ] }, { "cell_type": "markdown", "id": "51", "metadata": {}, "source": [] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 }