import numpy as np
import pandas as pd

pd.options.plotting.backend = "plotly"

from gettsim import (
    compute_taxes_and_transfers,
    create_synthetic_data,
    set_up_policy_environment,
)

Kinder & Bürgergeld / Kindergrundsicherung#

In diesem Notebook beschäftigen wir uns damit, wie Kinder das Bürgergeld und die resultierenden Arbeitsanreize beeinflussen.

Von der Struktur her ist es fast dasselbe wie das Notebook in der letzten Präsenzphase. Wir erstellen 5 Typen von Haushalten:

  • 1 Erwachsener

  • 1 Erwachsener, 1 Kind

  • 2 Erwachsene

  • 2 Erwachsene, 1 Kind

  • 2 Erwachsene, 2 Kinder

Zunächst beschränken wir uns auf die Transfers und Transferentzugsraten. Dieser Teil ist vorprogrammiert. Bitte gehen Sie wie üblich langsam durch und versuchen Sie, die einzelnen Schritte zu verstehen.

Anschließend bringen wir Transfers und Steuern zusammen und Sie berechnen selbständig die Gesamtbelastung.

Erstellung des Steuer- und Transfersystems und der Daten#

Wir setzen zunächst wieder die Parameter des Steuer- und Transfersystems auf die am Anfang des Jahres 2023 fest.

params_dict, policy_func_dict = set_up_policy_environment("2023-01-01")

Nun erstellen wir die benötigten Datensätze. Ab dem zweiten Datensatz passen wir auch die ID jeder Person und jedes Haushalts an, damit diese eindeutig sind.

min_einkommen = 0
max_einkommen = 10_000
anzahl_schritte = 1_001
# Ein Erwachsener, keine Kinder

df_1e = create_synthetic_data(
    n_adults=1,
    n_children=0,
    specs_heterogeneous={
        "bruttolohn_m": [
            [i] for i in np.linspace(min_einkommen, max_einkommen, anzahl_schritte)
        ],
    },
)

df_1e
# Ein Erwachsener, ein Kind

df_1e_1k = create_synthetic_data(
    n_adults=1,
    n_children=1,
    specs_heterogeneous={
        "bruttolohn_m": [
            [i, 0] for i in np.linspace(min_einkommen, max_einkommen, anzahl_schritte)
        ],
    },
)

# IDs anpassen
for var in ["hh_id", "tu_id", "p_id"]:
    df_1e_1k[var] += 1 + df_1e[var].max()

df_1e_1k
# Zwei Erwachsene, keine Kinder

df_2e = create_synthetic_data(
    n_adults=2,
    n_children=0,
    specs_heterogeneous={
        "bruttolohn_m": [
            [i / 2, i / 2]
            for i in np.linspace(min_einkommen, max_einkommen, anzahl_schritte)
        ],
    },
)

# IDs anpassen
for var in ["hh_id", "tu_id", "p_id"]:
    df_2e[var] += 1 + df_1e_1k[var].max()

df_2e
# Zwei Erwachsene, ein Kind

df_2e_1k = create_synthetic_data(
    n_adults=2,
    n_children=1,
    specs_heterogeneous={
        "bruttolohn_m": [
            [i / 2, i / 2, 0]
            for i in np.linspace(min_einkommen, max_einkommen, anzahl_schritte)
        ],
    },
)

# IDs anpassen
for var in ["hh_id", "tu_id", "p_id"]:
    df_2e_1k[var] += 1 + df_2e[var].max()

df_2e_1k
# Zwei Erwachsene, zwei Kinder

df_2e_2k = create_synthetic_data(
    n_adults=2,
    n_children=2,
    specs_heterogeneous={
        "bruttolohn_m": [
            [i / 2, i / 2, 0, 0]
            for i in np.linspace(min_einkommen, max_einkommen, anzahl_schritte)
        ],
    },
)

for var in ["hh_id", "tu_id", "p_id"]:
    df_2e_2k[var] += 1 + df_2e_1k[var].max()

df_2e_2k

Nun erstellen wir einen großen Datensatz, der die eben erstellten enthält.

inputs_personen = pd.concat(
    [df_1e, df_1e_1k, df_2e, df_2e_1k, df_2e_2k],
    names=["Haushaltstyp"],
    keys=[
        "1 Erwachsener",
        "1 Erwachsener, 1 Kind",
        "2 Erwachsene",
        "2 Erwachsene, 1 Kind",
        "2 Erwachsene, 2 Kinder",
    ],
)

inputs_personen

Zusätzlich bestimmen wir das zu versteuernde Einkommen und setzen die Heiz- und Mietkosten.

inputs_personen["_zu_verst_eink_ohne_kinderfreib_tu"] = (
    inputs_personen.groupby("tu_id")["bruttolohn_m"].transform("sum") * 12
)
inputs_personen["heizkosten_m_hh"] = 50.0
inputs_personen["bruttokaltmiete_m_hh"] = 350.0

inputs_personen

Nun können wir die Steuern und Transfers der Haushalte genau wie in den letzten Wochen mittels GETTSIM bestimmen. Wir berechnen zusätzlich den Unterhaltsvorschuss (unterhaltsvors_m). Dies ist eine Transferzahlung, falls eine alleinerziehende Person keine (oder zu geringe) Unterhaltszahlungen erhält. Wir nehmen in diesem Notebook an, dass Kinder von Alleinerziehenden keine Unterhaltszahlungen erhalten und deshalb den Unterhaltsvorschuss als Transfer erhalten.

Bei der Berechnung der Steuerlast vernachlässigen wir die gesetzlich vorgeschriebenen Rundungen, indem wir rounding=False als Argument in die Funktion aufnehmen. Dies sorgt dafür, dass die später zu berechnenden Grenzbelastungen keine (kleinen) Sprünge durch Rundungen aufweisen.

ergebnisse_personen = compute_taxes_and_transfers(
    data=inputs_personen,
    functions=policy_func_dict,
    params=params_dict,
    columns_overriding_functions=[
        "_zu_verst_eink_ohne_kinderfreib_tu",
    ],
    targets=[
        "kindergeld_m_hh",
        "eink_st_tu",
        "soli_st_tu",
        "sozialv_beitr_m",
        "arbeitsl_geld_2_vor_vorrang_m_hh",
        "unterhaltsvors_m",
    ],
    rounding=False,
)

ergebnisse_personen.index.name = "p_id"
ergebnisse_personen
# Mithilfe der ID den Bruttolohn an Ergebnis anhängen

ergebnisse_personen = ergebnisse_personen.join(
    inputs_personen.reset_index().set_index("p_id")[["hh_id", "bruttolohn_m"]],
)

Nun aggregieren wir die Steuer- und Transferzahlungen auf Haushaltsebene.

ergebnisse = ergebnisse_personen.groupby("hh_id").agg(
    {
        "bruttolohn_m": lambda x: x.sum(),
        "kindergeld_m_hh": lambda x: x.max(),
        "eink_st_tu": lambda x: x.max() / 12,
        "soli_st_tu": lambda x: x.max() / 12,
        "sozialv_beitr_m": lambda x: x.sum(),
        "arbeitsl_geld_2_vor_vorrang_m_hh": lambda x: x.max(),
        "unterhaltsvors_m": lambda x: x.sum(),
    },
)

Da wir nun Variablen auf Haushaltsebene haben, benennen wir diese noch korrekt mittels .rename(). Diese Methode ordnet einem alten Variablennahmen (bspw. bruttolohn_m) einen neuen Namen zu (bspw. bruttolohn_hh_m).

ergebnisse = ergebnisse.rename(
    columns={
        "bruttolohn_m": "bruttolohn_m_hh",
        "sozialv_beitr_m": "sozialv_beitr_m_hh",
        "unterhaltsvors_m": "unterhaltsvors_m_hh",
    },
)

ergebnisse

Zuletzt fügen wir den Haushaltstyp unserem DataFrame mit den Ergebnissen hinzu und setzen den Index korrekt.

ergebnisse = ergebnisse.join(
    inputs_personen.reset_index().set_index("hh_id")["Haushaltstyp"],
).drop_duplicates()

ergebnisse = ergebnisse.set_index(["Haushaltstyp", "bruttolohn_m_hh"])
ergebnisse

Transfers und Transferentzugsrate#

Nun errechnen wir die gesamten Transfers und die Entzugsraten.

ergebnisse["transfers"] = (
    ergebnisse["kindergeld_m_hh"]
    + ergebnisse["arbeitsl_geld_2_vor_vorrang_m_hh"]
    + ergebnisse["unterhaltsvors_m_hh"]
)

ergebnisse["transferentzugsrate"] = (
    ergebnisse["transfers"] - ergebnisse.groupby("Haushaltstyp")["transfers"].shift(-1)
) / 10

display(ergebnisse.head())
display(
    ergebnisse.query("Haushaltstyp == '1 Erwachsener, 1 Kind'").head(),
)
display(ergebnisse.tail())

Wir untersuchen erstmal nur Haushalte mit bruttolohn_m_hh < 3000.

ergebnisse_klein = ergebnisse.query("bruttolohn_m_hh < 3000")
ergebnisse_klein

Wir schauen uns zunächst die Transfers an.

ergebnisse_1e = ergebnisse_klein.query(
    "Haushaltstyp == '1 Erwachsener' | Haushaltstyp == '1 Erwachsener, 1 Kind'",
)

ergebnisse_1e.reset_index().plot.line(
    x="bruttolohn_m_hh",
    y="transfers",
    color="Haushaltstyp",
    title="Transfers bei 1 Erwachsenen",
)

Hinweis: Die Zeichenketten (strings), die das Argument der folgenden .query()-Methode bilden, werden von Python automatisch aneinandergehängt. So können wir die Abfrage lesbarer gestalten.

ergebnisse_2e = ergebnisse_klein.query(
    "Haushaltstyp == '2 Erwachsene' | "
    "Haushaltstyp == '2 Erwachsene, 1 Kind' | "
    "Haushaltstyp == '2 Erwachsene, 2 Kinder'",
)

ergebnisse_2e.reset_index().plot.line(
    x="bruttolohn_m_hh",
    y="transfers",
    color="Haushaltstyp",
    title="Transfers bei 2 Erwachsenen",
)

Und nun die Transferentzugsraten.

Tipp zur besseren Übersicht: Sie können einzelne Haushaltstypen ausblenden indem Sie in der Legende des Plots diesen abwählen.

ergebnisse_1e.reset_index().plot.line(
    x="bruttolohn_m_hh",
    y="transferentzugsrate",
    color="Haushaltstyp",
    title="Transferentzugsrate",
)
ergebnisse_2e.reset_index().plot.line(
    x="bruttolohn_m_hh",
    y="transferentzugsrate",
    color="Haushaltstyp",
    title="Transferentzugsrate",
)

Aufgabe 1: Interpretation der Graphiken#

Erklären Sie die wichtigsten Sprünge in den Linien für die Transferentzugsraten!

Hinweis: Bei Alleinerziehenden gibt es Leistungen, die wir nicht im Kurs besprochen haben.

Hier Platz für Ihre Antwort

Aufgabe 2: Haushaltseinkommen und Grenzbelastung#

Wiederholen Sie die Analyse ab “Transfers und Transferentzugsrate” für das verfügbare Haushaltseinkommen und die gesamte Grenzbelastung aus Transferentzug und Steuern!

Nutzen Sie für die Berechnung des verfügbaren Einkommens wieder das DataFrame ergebnisse und nicht ergebnisse_klein. Plotten Sie dann aber das DataFrame ergebnisse_klein, welches in der mittleren Zelle erstellt wird.

ergebnisse = ergebnisse.reset_index()

ergebnisse["verfügbares einkommen"] = (
    ergebnisse["bruttolohn_m_hh"]
    + ergebnisse["transfers"]
    - ergebnisse["eink_st_tu"]
    - ergebnisse["soli_st_tu"]
    - ergebnisse["sozialv_beitr_m_hh"]
)
ergebnisse["grenzbelastung gesamt"] = (
    ergebnisse["verfügbares einkommen"]
    - ergebnisse.groupby("Haushaltstyp")["verfügbares einkommen"].shift(-1)
) / 10 + 1

ergebnisse = ergebnisse.set_index(["Haushaltstyp", "bruttolohn_m_hh"])
ergebnisse_klein = ergebnisse.query("bruttolohn_m_hh < 3000")
ergebnisse_1e = ergebnisse_klein.query(
    "Haushaltstyp == '1 Erwachsener' | Haushaltstyp == '1 Erwachsener, 1 Kind'",
)

for var in ["verfügbares einkommen", "grenzbelastung gesamt"]:
    ergebnisse_1e.reset_index().plot.line(
        x="bruttolohn_m_hh",
        y=var,
        color="Haushaltstyp",
        title=var,
    ).show()
ergebnisse_2e = ergebnisse_klein.query(
    "Haushaltstyp == '2 Erwachsene' | "
    "Haushaltstyp == '2 Erwachsene, 1 Kind' | "
    "Haushaltstyp == '2 Erwachsene, 2 Kinder'",
)

for var in ["verfügbares einkommen", "grenzbelastung gesamt"]:
    ergebnisse_2e.reset_index().plot.line(
        x="bruttolohn_m_hh",
        y=var,
        color="Haushaltstyp",
        title=var,
    ).show()

Aufgabe 3: Interpretation der Unterschiede#

Was treibt die Unterschiede zwischen Haushalten mit Kindern und ohne Kinder? Verursachen diese Unterschiede unterschiedliche Arbeitsanreize?

Hier Platz für Ihre Antwort

Aufgabe 4#

Sehen Sie sich im folgenden die Bürgergeld-Transfers für Familien mit 2 Erwachsenen und 2 Erwachsenen mit einem Kind an. Die Kinder in unserem Datensatz sind 8 Jahre alt.

Warum entspricht der Unterschied zwischen dem Bürgergeldanspruch einer Familie mit und ohne Kind nicht genau dem Regelsatz eines 8-jährigen Kindes?

display(
    ergebnisse.query("Haushaltstyp == '2 Erwachsene'").head(),
)
display(
    ergebnisse.query("Haushaltstyp == '2 Erwachsene, 1 Kind'").head(),
)

Reformvorschlag Kindergrundsicherung#

In dieser Aufgabe untersuchen wir die quantitativen Effekte des Reformvorschlags des Bündnis Kindergrundsicherung auf die Budgets von Haushalten. Hierfür müssen wir eine neue Funktion zu GETTSIM hinzufügen, und Parameter des Status quo abändern.

NB: Der Reformvorschlag des Bündnis Kindergrundsicherung hat wenig bis gar nichts mit dem zu tun, was sich aktuell unter der Überschrift “Kindergrundsicherung” im Gesetzgebungsverfahren befindet.

Funktion erstellen (5 Minuten)#

Wir erstellen eine neue Funktion für die Kindergrundsicherung. Die meisten der Funktionen in GETTSIM können Sie sich hier anschauen. In der Regel finden Sie dort gute Vorlagen.

Das Kindergeld ist ähnlich zur Kindergrundsicherung, deshalb nehem wir die Funktion als Grundlage. Die Funktion findet sich hier. Da das Kindergeld sich über die Jahre verändert hat und nicht trivial ist, ist es dort ein wenig komplexer.

Wir können das Interface direkt übernehmen.

Wir nennen die neue Funktion kindergrundsicherung_m_hh wobei

  • _m anzeigt, dass der Wert monatlich zu interpretieren ist

  • _hh anzeigt, dass der Wert auf Haushaltsebene berechnet wird

def kindergrundsicherung_m_hh(
    kindergeld_anspruch_hh,
    kindergrundsicherung_params,
):
    """Give Kindergrundsicherung to all children eligible to Kindergeld.

    Parameters
    ----------
    kindergeld_anspruch_hh
    kindergeld_params

    Returns:
    -------
    Kindergrundsicherung

    """
    # Kindergrundsicherung is for all children eligible for kindergeld.
    return float(kindergeld_anspruch_hh) * kindergrundsicherung_params

Kindergrundsicherung zum Steuer- und Transfersystem hinzufügen (5 Minuten)#

Diese neue Funktion wollen wir jetzt zu GETTSIM hinzufügen.

  • Zunächst kopieren wir alle Funkionen, die GETTSIM im dictionary policy_func_dict bereitstellt.

  • Dann fügen wir unsere neue Funktion hinzu.

# Ausnahmsweise importieren wir an dieser Stelle ein Modul.
import copy

policy_func_dict_mit_kindergrundsicherung = copy.deepcopy(policy_func_dict)
policy_func_dict_mit_kindergrundsicherung["kindergrundsicherung_m_hh"] = (
    kindergrundsicherung_m_hh
)

Prüfen Sie, ob Ihre neue Funktion gelistet ist:

print(*policy_func_dict_mit_kindergrundsicherung.keys(), sep="\n")

Änderungen der Parameter (5 Minuten)#

Abgesehen von den Funktionen ändern wir jetzt auch die Parameter entsprechend des Reformvorschlags. Zunächst kopieren wir wieder alle Parameter, die GETTSIM bereitstellt.

params_dict_mit_kindergrundsicherung = copy.deepcopy(params_dict)
params_dict_mit_kindergrundsicherung["kindergrundsicherung"] = 746
params_dict_mit_kindergrundsicherung["eink_st_abzuege"]["kinderfreib"][
    "beitr_erz_ausb"
] = 0
params_dict_mit_kindergrundsicherung["eink_st_abzuege"]["kinderfreib"][
    "sächl_existenzmin"
] = 0
for stufe in (3, 4, 5, 6):
    params_dict_mit_kindergrundsicherung["arbeitsl_geld_2"]["regelsatz"][stufe] = 0
for kind in params_dict_mit_kindergrundsicherung["kindergeld"]["kindergeld"]:
    params_dict_mit_kindergrundsicherung["kindergeld"]["kindergeld"][kind] = 0

Aufgabe 5: Interpretation Code (5 Minuten)#

In der vorherigen Zelle wird folgender Code ausgeführt:

params_dict_mit_kindergrundsicherung["kindergrundsicherung"] = 746
for kind in params_dict_mit_kindergrundsicherung["kindergeld"]["kindergeld"]:
    params_dict_mit_kindergrundsicherung["kindergeld"]["kindergeld"][kind] = 0

Was bewirkt der Code? Und warum ist das notwendig um den Reformvorschlag des Bündnis Kindergrundsicherung zu implementieren? Auch die anderen Codezeilen sollten Sie nachvollziehen können.

Steuern und Transfers berechnen und auf Haushaltsebene aggregieren (7 Minuten)#

ergebnisse_personen_kindergrundsicherung = compute_taxes_and_transfers(
    data=inputs_personen,
    functions=policy_func_dict_mit_kindergrundsicherung,
    params=params_dict_mit_kindergrundsicherung,
    columns_overriding_functions=[
        "_zu_verst_eink_ohne_kinderfreib_tu",
    ],
    targets=[
        "kindergrundsicherung_m_hh",
        "sozialv_beitr_m",
        "arbeitsl_geld_2_vor_vorrang_m_hh",
        "eink_st_tu",
        "soli_st_tu",
        "unterhaltsvors_m_hh",
    ],
    rounding=False,
)
ergebnisse_personen_kindergrundsicherung
# Mithilfe der p_id wieder die hh_id und den Bruttolohn an das DataFrame joinen

ergebnisse_personen_kindergrundsicherung = (
    ergebnisse_personen_kindergrundsicherung.join(
        inputs_personen.reset_index().set_index("p_id")[["hh_id", "bruttolohn_m"]],
    )
)
# Zahlen auf Haushaltsebene aggregieren.

ergebnisse_kindergrundsicherung = ergebnisse_personen_kindergrundsicherung.groupby(
    "hh_id",
).agg(
    {
        "bruttolohn_m": lambda x: x.sum(),
        "kindergrundsicherung_m_hh": lambda x: x.max(),
        "sozialv_beitr_m": lambda x: x.sum(),
        "arbeitsl_geld_2_vor_vorrang_m_hh": lambda x: x.max(),
        "eink_st_tu": lambda x: x.max() / 12,
        "soli_st_tu": lambda x: x.max() / 12,
        "unterhaltsvors_m_hh": lambda x: x.max(),
    },
)
# Kenntlich machen, dass es sich um Zahlen auf Haushaltsebene handelt.

ergebnisse_kindergrundsicherung = ergebnisse_kindergrundsicherung.rename(
    columns={
        "bruttolohn_m": "bruttolohn_m_hh",
        "sozialv_beitr_m": "sozialv_beitr_m_hh",
    },
)
# Mithilfe der hh_id den Haushaltstyp dem DataFrame hinzufügen.

ergebnisse_kindergrundsicherung = ergebnisse_kindergrundsicherung.join(
    inputs_personen.reset_index().set_index("hh_id")["Haushaltstyp"],
).drop_duplicates()

ergebnisse_kindergrundsicherung = ergebnisse_kindergrundsicherung.set_index(
    ["Haushaltstyp", "bruttolohn_m_hh"],
)

ergebnisse_kindergrundsicherung

Aufgabe 6: Plot Kindergrundsicherung (2 Minuten)#

Plotten Sie analog zu Aufgabe 2 die Kindergrundsicherung.

Aufgabe 7: Verfügbares Einkommen (5 Minuten)#

Berechnen Sie analog zu Aufgabe 2 das verfügbare Einkommen und plotten Sie für die verschiedenen Haushaltstypen das verfügbare Einkommen.

Zusammenführung mit Ergebnissen vor Reform (5 Minuten)#

Um die Effekte der Reform besser untersuchen zu können, wollen wir die Differenz im verfügbaren Einkommen vor und nach der Reform berechnen. Dazu ändern wir im Ergebnis-DataFrame von vor und nach der Reform die Spaltennamen und fügen sie dann zu einem neuen DataFrame vergleich zusammen.

ergebnisse = ergebnisse.rename(
    columns={"verfügbares einkommen": "Verfügbares Einkommen, vor Reform"},
)
ergebnisse_kindergrundsicherung = ergebnisse_kindergrundsicherung.rename(
    columns={"verfügbares einkommen": "Verfügbares Einkommen, nach Reform"},
)
# Anmerkung: data_frame["column_name"] ist eine Series und kann nicht für
# join genutzt werden. data_frame[["column_name"]] bleibt ein DataFrame.
vergleich = ergebnisse_kindergrundsicherung[
    ["Verfügbares Einkommen, nach Reform"]
].join(ergebnisse[["Verfügbares Einkommen, vor Reform"]])

Aufgabe 8: Differenz im verfügbaren Einkommen (5 Minuten)#

Berechnen Sie die Differenz im verfügbaren Einkommen und plotten Sie die Differenz analog zu Aufgabe 2.

Aufgabe 9: Interpretation der Ergebnisse (10 Minuten)#

Wodurch ergeben sich die Unterschiede im Vergleich vor und nach der Reform?