from copy import deepcopy
from pathlib import Path

import numpy as np
import pandas as pd
from gettsim import (
    compute_taxes_and_transfers,
    set_up_policy_environment,
)

DATA = Path("/tmp/Daten_FSP/")  # noqa: S108

Abschätzen der Kosten und Nutzen von Reformen#

Heute setzen wir unsere Arbeit an den SOEP Daten fort. Unser Ziel ist es die zu erwartenden Kosten einer Reform, der Kindergrundsicherung, mittels realer Daten abzuschätzen. Dabei beschäftigen wir uns auch mit den Problemen einer solchen Schätzung. Zuletzt betrachten wir die Veränderung des äquivalisierten Nettohaushaltseinkommens durch die Reform näher und schätzen so den Nutzen der untersuchten Reform ab.

Achtung: Um realitätsnahe Vorhersagen zu Kosten und Nutzen der Kindergrundsicherung zu erhalten, bedarf es einer wesentlich komplexeren Datenaufbereitung als das hier der Fall ist. Sie sollten daher Ihre eigene Haltung zur Kindergrundsicherung nicht auf unseren quantitativen Ergebnissen beruhen lassen.

Datenerstellung und Aufbereitung#

Zunächst laden wir den gesäuberten SOEP Datensatz und vollziehen einige letzte Schritte zur Datensäuberung. Die folgenden Zellen unterscheiden sich nicht von denen der letzten Präsenzphase.

Wir laden die Daten und setzen einen korrekten Index:

data_cleaned = pd.read_pickle(DATA / "gettsim_data_soep_2019.pkl")  # noqa: S301
data_cleaned["hh_id"] = data_cleaned["hh_id"].astype(int)
data_cleaned["tu_id"] = data_cleaned["tu_id"].astype(int)
data_cleaned = data_cleaned.set_index(["hh_id", "tu_id", "p_id"]).sort_index()

data_cleaned

Wir verwenden das Steuer- und Transfersystem von 2019, da die Daten aus dem Jahr 2019 stammen.

params_dict, policy_func_dict = set_up_policy_environment(2019)

Einige Spalten werden recodiert, einige Spalten erhalten einen neuen Typ.

data_cleaned = data_cleaned.reset_index()
data_cleaned = data_cleaned.drop(["anz_kinder_hh"], axis=1)

# GETTSIM unterstützt nur eine tax units pro Haushalt.
data_cleaned["hh_id"] = data_cleaned["tu_id"]

# Fehlende Werte auf 0 setzen.
for c in [
    "kindergeld_hh",
    "kinderzuschlag_hh",
    "grundr_zeiten",
    "grundr_bew_zeiten",
    "bruttolohn_vorj_m",
]:
    data_cleaned[c] = data_cleaned[c].fillna(0)

# Jahresangaben runden
for c in [
    "jahr_renteneintr",
    "immobilie_baujahr_hh",
    "grundr_zeiten",
    "grundr_bew_zeiten",
]:
    data_cleaned[c] = data_cleaned[c].round(0)

# Boolean Datentyp
for c in [
    "weiblich",
    "wohnort_ost",
    "anwartschaftszeit",
]:
    data_cleaned[c] = data_cleaned[c].astype(bool)

# Integer Datentyp
for c in [
    "alter",
    "geburtsmonat",
    "geburtsjahr",
    "jahr_renteneintr",
    "m_elterngeld",
    "behinderungsgrad",
    "immobilie_baujahr_hh",
    "grundr_zeiten",
    "grundr_bew_zeiten",
]:
    data_cleaned[c] = data_cleaned[c].astype(int)

# Float Datentyp
for c in ["sozialv_pflicht_5j"]:
    data_cleaned[c] = data_cleaned[c].astype(float)

# Unbekannte Werte zu 0
for c in [
    "m_pflichtbeitrag",
    "m_freiw_beitrag",
    "m_mutterschutz",
    "m_arbeitsunfähig",
    "m_krank_ab_16_bis_24",
    "m_arbeitslos",
    "m_ausbild_suche",
    "m_schul_ausbild",
    "m_geringf_beschäft",
    "m_alg1_übergang",
    "m_ersatzzeit",
    "m_kind_berücks_zeit",
    "m_pfleg_berücks_zeit",
    "y_pflichtbeitr_ab_40",
    "kind_unterh_anspr_m",
]:
    data_cleaned[c] = 0.0

data_cleaned["bruttolohn_vorj_m"] = data_cleaned["bruttolohn_vorj_m"].replace(np.inf, 0)

data_cleaned = data_cleaned.set_index(["hh_id", "tu_id", "p_id"]).sort_index()

Wir berechnen Steuern und Transfers im Status Quo.

ergebnisse_personen = compute_taxes_and_transfers(
    data=data_cleaned.reset_index(),
    functions=policy_func_dict,
    params=params_dict,
    targets=[
        "kindergeld_m_hh",
        "sozialv_beitr_m",
        "arbeitsl_geld_m",
        "arbeitsl_geld_2_m_hh",
        "kindergeld_m_tu",
        "wohngeld_m_hh",
        "kinderzuschl_m_hh",
        "eink_st_tu",
        "soli_st_tu",
    ],
)

ergebnisse_personen = ergebnisse_personen.join(
    data_cleaned.reset_index()[["bruttolohn_m", "hh_id"]],
)

ergebnisse_personen

Zuletzt berechnen wir die Einkommensteuer auf monatlicher Basis und aggregieren einige Variablen.

ergebnisse_status_quo = (
    ergebnisse_personen.reset_index()
    .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_m_hh": lambda x: x.max(),
            "arbeitsl_geld_m": lambda x: x.sum(),
            "wohngeld_m_hh": lambda x: x.max(),
            "kinderzuschl_m_hh": lambda x: x.max(),
        },
    )
)

# Variablen korrekt benennen
ergebnisse_status_quo = ergebnisse_status_quo.rename(
    columns={
        "eink_st_tu": "einkommensteuer_m_tu",
        "soli_st_tu": "soli_m_tu",
        "bruttolohn_m": "bruttolohn_m_hh",
        "sozialv_beitr_m": "sozialv_beitr_m_hh",
        "arbeitsl_geld_m": "arbeitsl_geld_m_hh",
    },
)

ergebnisse_status_quo

Aufgabe 1: Verfügbares Einkommen laut GETTSIM (15 Minuten)#

Berechnen Sie das verfügbare Einkommen eines jeden Haushalts und bennennen Sie die Spalte Verfügbares Einkommen.

Implementierung Kindergrundsicherung#

Wir implementieren dieselbe Version einer Kindergrundsicherung von vor 3 Wochen.

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


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

params_dict_mit_kindergrundsicherung = 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

Hier berechnen wir Steuern und Transfers im Reformszenario.

ergebnisse_personen_kindergrundsicherung = compute_taxes_and_transfers(
    data=data_cleaned.reset_index(),
    functions=policy_func_dict_mit_kindergrundsicherung,
    params=params_dict_mit_kindergrundsicherung,
    targets=[
        "kindergrundsicherung_m_hh",
        "sozialv_beitr_m",
        "arbeitsl_geld_m",
        "arbeitsl_geld_2_m_hh",
        "kindergeld_m_tu",
        "wohngeld_m_hh",
        "kinderzuschl_m_hh",
        "eink_st_tu",
        "soli_st_tu",
    ],
    rounding=False,
)

ergebnisse_personen_kindergrundsicherung = (
    ergebnisse_personen_kindergrundsicherung.join(
        data_cleaned.reset_index()[["bruttolohn_m", "hh_id"]],
    )
)

ergebnisse_kindergrundsicherung = (
    ergebnisse_personen_kindergrundsicherung.reset_index()
    .groupby("hh_id")
    .agg(
        {
            "bruttolohn_m": lambda x: x.sum(),
            "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_m_hh": lambda x: x.max(),
            "arbeitsl_geld_m": lambda x: x.sum(),
            "wohngeld_m_hh": lambda x: x.max(),
            "kinderzuschl_m_hh": lambda x: x.max(),
            "kindergrundsicherung_m_hh": lambda x: x.max(),
        },
    )
)

# Variablen korrekt benennen
ergebnisse_kindergrundsicherung = ergebnisse_kindergrundsicherung.rename(
    columns={
        "eink_st_tu": "einkommensteuer_m_tu",
        "soli_st_tu": "soli_m_tu",
        "bruttolohn_m": "bruttolohn_m_hh",
        "sozialv_beitr_m": "sozialv_beitr_m_hh",
        "arbeitsl_geld_m": "arbeitsl_geld_m_hh",
    },
)

ergebnisse_kindergrundsicherung

Aufgabe 2: Verfügbares Einkommen nach Reform (10 Minuten)#

Berechnen Sie das verfügbare Einkommen eines jeden Haushalts nach der Reform und bennennen Sie die Spalte Verfügbares Einkommen.

Vergleich vor und nach Reform#

Um den Effekt der Reform zu ermitteln fügen wir die beiden DataFrames mit den Ergebnissen zusammen.

ergebnisse = pd.concat(
    [ergebnisse_status_quo, ergebnisse_kindergrundsicherung],
    keys=["vor Reform", "nach Reform"],
    names=["Szenario", "hh_id"],
)

ergebnisse

Aufgabe 3: Differenz des verfügbaren Einkommens (10 Minuten)#

Berechnen Sie die Differenz des verfügbaren Einkommens für jeden Haushalt.

Aufgabe 4: Kosten der Reform (20 Minuten)#

Wie teuer wäre die Reform ungefähr pro Haushalt?

Warum würden die Kosten in der Realität vermutlich anders ausfallen? Warum sind die Kosten vermutlich geringer?

Aufgabe 5: Unterschiede im äquivalisierten Nettoeinkommen (insgesamt 55 Minuten)#

Nun bestimmen wir die Wirkung der Reform auf das Nettoäquivalenzeinkommen der Haushalte.

Aufgabe 5.1: Bestimmen der Gewichte (10 Minuten)#

Zunächst berechnen wir für Sie die Anzahl der Erwachsenen, Jugendliche ab 14 Jahren und Kinder im Haushalt.

ergebnisse = ergebnisse.reset_index().set_index("hh_id")

anz_kinder_bis_13 = (
    data_cleaned.query("alter < 14 & kind == True").groupby("hh_id").size()
).to_frame("anz_kinder_bis_13")

anz_kinder_ab_14 = (
    data_cleaned.query("alter >= 14 & kind == True").groupby("hh_id").size()
).to_frame("anz_kinder_ab_14")

anz_erwachsene = (data_cleaned.query("kind == False").groupby("hh_id").size()).to_frame(
    "anz_erwachsene",
)

ergebnisse = ergebnisse.join(
    [
        anz_kinder_bis_13,
        anz_kinder_ab_14,
        anz_erwachsene,
    ],
)

ergebnisse["anz_kinder_bis_13"] = ergebnisse["anz_kinder_bis_13"].fillna(0).astype(int)
ergebnisse["anz_kinder_ab_14"] = ergebnisse["anz_kinder_ab_14"].fillna(0).astype(int)

ergebnisse = ergebnisse.reset_index().set_index(["Szenario", "hh_id"]).sort_index()

ergebnisse

Wir werden zwei Äquivalenzskalen verwenden. Die Wurzelfunktionsskala haben wir für Sie in der folgenden Code Zeile definiert. Definieren Sie nach diesem Beispiel eine Funktion, die die OECD Äquivalenzskala implementiert.

def wurzel_skala(data):
    """Berechne Gewichtungsfaktor nach Wurzelfunktion.

    Args:
        data (pd.DataFrame): Datensatz mit Spalten für Anzahl Erwachsene und Kinder.

    Returns:
        pd.Series: Gewichtungsfaktor für OECD-Äquivalenzskala.
    """
    return (
        data["anz_erwachsene"] + data["anz_kinder_ab_14"] + data["anz_kinder_bis_13"]
    ) ** (1 / 2)


ergebnisse["Gewichtungsfaktor Wurzel"] = wurzel_skala(ergebnisse)
ergebnisse

In der folgenden Zelle wird der Gewichtungsfaktor laut OECD berechnet.

ergebnisse["Gewichtungsfaktor OECD"] = oecd_skala(ergebnisse)
ergebnisse

Aufgabe 5.2: Bestimmen des Äquivalenzeinkommens (5 Minuten)#

Bestimmen Sie das verfügbare Äquivalenzeinkommen für beide Gewichtungsarten. Nennen Sie die Spalte des verfügbaren Äquivalenzeinkommens nach Wurzelfunktion Verfügbares Äquivalenzeinkommen Wurzel. Nennen Sie die entsprechende OECD Spalte Verfügbares Äquivalenzeinkommen OECD.

Aufgabe 5.3: Differenz des Äquivalenzeinkommens (10 Minuten)#

Im Folgenden bestimmen wir die Differenz des Äquivalenzeinkommens laut Wurzelfunktion. Bestimmen Sie die Differenz des Äquivalenzeinkommens laut OECD Skala und joinen Sie ihr Ergebnis an den differenz_äquivalenzeinkommen DataFrame.

Tipp: Bevor Sie joinen, können Sie der neuen Spalte einen besseren Namen geben. Hier haben wir das beispielhaft für eine pandas Series namens differenz_oecd getan.

differenz_oecd.name = "Differenz Verfügbares Äquivalenzeinkommen OECD"
differenz_äquivalenzeinkommen = (
    ergebnisse.loc["nach Reform", "Verfügbares Äquivalenzeinkommen Wurzel"]
    - ergebnisse.loc["vor Reform", "Verfügbares Äquivalenzeinkommen Wurzel"]
).to_frame("Differenz Verfügbares Äquivalenzeinkommen Wurzel")

differenz_äquivalenzeinkommen
differenz_äquivalenzeinkommen = differenz_äquivalenzeinkommen.join(
    ergebnisse[["anz_erwachsene", "anz_kinder_ab_14", "anz_kinder_bis_13"]],
)

differenz_äquivalenzeinkommen

Aufgabe 5.4: Vergleich der Differenzen (15 Minuten)#

Erstellen Sie eine Spalte anz_kinder im DataFrame differenz_äquivalenzeinkommen, welche die Anzahl der Kinder in den beiden Altersstufen aufsummiert.

Gruppieren Sie danach Haushalte nach anz_erwachsene und anz_kinder. Lassen Sie sich den Mittelwert der Differenz des verfügbaren Äquivalenzeinkommens für jede Gruppe und für beide Skalen anzeigen.

Aufgabe 5.5: Interpretation (15 Minuten)#

Diskutieren Sie folgende Fragen:

  1. Für welche Haushalte verändert sich das Äquivalenzeinkommen durch die Reform am stärksten?

  2. Warum verändert es sich auch für Single-Haushalte ohne Kinder?

  3. Weshalb profitieren einige Haushalte stäker als andere?

Aufgabe 6: Pentabilities (10min)#

Bitte bewerten Sie die Beiträge zur Gruppenarbeit in der Pentabilities-App. Bitte vergeben Sie über alle 5 Dimensionen hinweg Punkte für die Verhaltensweisen, welche Sie heute beobachten konnten. Denken Sie bitte daran, sowohl die Beiträge der Kommiliton:innen Ihrer Gruppe als auch Ihre eigenen zu bewerten.