from pathlib import Path

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

pd.options.plotting.backend = "plotly"

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

Anwendung von GETTSIM auf das SOEP#

Das SOEP ist eine Langzeitstudie zur Erforschung des sozialen und wirtschaftlichen Wandels in Deutschland. Es handelt sich dabei um eine repräsentative Wiederholungsbefragung von Privathaushalten, bei der regelmäßig Informationen zu den Lebensbedingungen, Einkommen, Bildung, Beschäftigung, Gesundheit und vielen anderen Aspekten des Lebens der Befragten erhoben werden.

Das SOEP wurde 1984 ins Leben gerufen und ist seitdem eine der wichtigsten Datenquellen für die sozialwissenschaftliche Forschung in Deutschland. Es ermöglicht Analysen zu langfristigen sozialen und wirtschaftlichen Entwicklungen, sozialer Mobilität, Armuts- und Ungleichheitsforschung, Arbeitsmarktverhalten, Bildungsverläufen und vielen anderen Themen.

Hier verwenden wir einen Lehrdatensatz des SOEP. Dabei handelt es sich um eine kleinere Version des originalen Datensatzes.

Im folgenden möchten wir die Budgetwirkung einer möglichen Einführung der Kindergrundsicherung mit realen Daten analysieren. Die Analyse in dieser Vorlesung ist naturgemäß sehr grob. Insbesondere würde eine realistische Abschätzung der Budgetwirkung eine wesentlich detailreichere Datenaufbereitung verlangen. Bitte verwechseln Sie daher nicht die Ergebnisse dieser Vorlesung mit tatsächlichen quantitativen Vorhersagen zur Wirkung der Reform.

SOEP Daten#

Aufgabe 1: Datenaufbereitung (45 Minuten insgesamt)#

Zunächst müssen wir die Rohdaten aufbereiten. Wir tun dies hier beispielhaft für die spätere bruttolohn_m Variable und stellen Ihnen danach einen fertigen Datensatz zur Verfügung, der bereits alle benötigten Variablen enthält.

Wir starten wie in der Selbstlernphase mit dem pgen Datensatz.

pgen = pd.read_pickle(DATA / "pgen_2019.pkl")  # noqa: S301
pgen = pgen.rename(columns={"pid": "p_id", "hid": "hh_id"}).set_index(
    "p_id",
)

Aufgabe 1.1: Arbeitseinkommen (30 Minuten)#

Wir möchten uns das Arbeitseinkommen genauer anschauen. Suchen Sie wie in der Selbstlernphase innerhalb des pgen Datensatzes nach der Variable, die das monatliche Brutto-Arbeitseinkommen enthält. Nennen Sie es bruttoerwerbseink_m.

Einen Überblick über die Variablen finden Sie hier.

Schauen Sie sich hier die Daten zum Bruttoerwerbseinkommen an. Was fällt Ihnen auf?

pgen[["bruttoerwerbseink_m"]]

Ersetzen Sie die Werte, sodass Sie die Spalte in den Datentyp float umwandeln können. Dafür möchten wir die Ausprägung "[-2] trifft nicht zu" in 0 umwandeln. Das betrifft beispielsweise Arbeitslose oder Personen in Ausbildung. Die Ausprägung [-5] in Fragebogenversion nicht enthalten wandeln Sie bitte zu np.nan um.

Tipp: Dazu brauchen Sie die .replace() Methode aus der Selbstlernphase.

pgen[["bruttoerwerbseink_m"]].describe()

Welche Zahlungen des Arbeitgebers sind nicht enthalten? Schauen Sie in der Beschreibung der Bruttoerwerbseinkommensvariable nach, welche Zahlungen des Arbeitsgeber nicht enthalten sind.

Um den fehlenden Betrag schätzungsweise zu ergänzen, berechnen wir das gesamte Brutto-Arbeitseinkommen als bruttoerwerbseink * 1.07. Wir erhöhen das Erwerbseinkommen also pauschal um 7%.

pgen["arbeitseink_m"] = 1.07 * pgen["bruttoerwerbseink_m"]
pgen = pgen.reset_index().set_index(["p_id", "hh_id"])

Berechnen Sie jetzt das monatliche gesamte Brutto-Arbeitseinkommen auf Haushaltsebene. Nennen Sie die Variable _arbeitseink_m_hh.

Zuletzt fügen Sie bitte die Variable _arbeitseink_m_hh wieder an den pgen Datensatz an.

Tipp: Wie üblich können Sie einen .join() Befehl nur einmal erfolgreich ausführen. Starten Sie das Notebook neu wenn Sie die Zelle erneut ausführen möchten.

Aufgabe 1.2: Haushaltsdaten (15 Minuten)#

Wie Sie wissen, benötigen wir für gettsim auch die Variablen Haushaltstyp, Miete und Heizkosten. Für einen Plausibilitätscheck möchten wir auch eine Variable haben, die angibt wer Eigentümer und wer Mieter ist. Suchen Sie die entsprechenden Variablen im Datensatz hgen. Die Variablenübersicht finden Sie hier.

Anmerkung: Fragen Sie nach, wenn Sie die Variable für Heizkosten nicht finden.

hgen = pd.read_pickle(DATA / "hgen_2019.pkl")  # noqa: S301

Notieren Sie hier die Namen der benötigten Variablen.

Benennen Sie nun die Variablen um in ihre bisher üblichen Namen: hh_id, Haushaltstyp, heizkosten_m_hh, bruttokaltmiete_m_hh, rented_or_owned.

Für die folgenden Berechnungen benötigen wir ausschließlich die eben genannten Variablen:

hgen = (
    hgen[
        [
            "hh_id",
            "Haushaltstyp",
            "heizkosten_m_hh",
            "bruttokaltmiete_m_hh",
            "rented_or_owned",
        ]
    ]
    .reset_index()
    .set_index(["hh_id"])
)

hgen

Nun fügen wir die beiden Datensätze zusammen.

pgen = pgen.reset_index().set_index("hh_id")
data_merged = pgen.join(hgen)

Erneut müssen wir einige Variablen umcodieren um sie verwenden zu können. Das haben wir hier bereits für Sie getan:

data_merged[["bruttokaltmiete_m_hh", "heizkosten_m_hh"]] = (
    data_merged[["bruttokaltmiete_m_hh", "heizkosten_m_hh"]]
    .replace(
        {
            "[-1] keine Angabe": np.nan,
            "[-2] trifft nicht zu": np.nan,
            "[-5] In Fragebogenversion nicht enthalten": np.nan,
        },
    )
    .astype("float")
)

Außerdem ersetzen wir die Beschreibungen der Haushaltstypen.

data_merged["Haushaltstyp"] = (
    data_merged["Haushaltstyp"]
    .replace(
        {
            "[1] 1 1-Pers.-HH": "Einpersonenhaushalt",
            "[2] 2 (Ehe-)Paar ohne K.": "(Ehe-)Paar ohne Kinder",
            "[3] 3 Alleinerziehende": "Alleinerziehende",
            "[4] 4 Paar + K. LE 16": "Paar + Kind(er) bis 16",
            "[5] 5 Paar + K. GT 16": "Paar + Kind(er) ab 17",
            "[6] 6 Paar + K. LE und GT 16": "Paar + Kinder bis 16 und ab 17",
            "[7] 7 Mehr-Generationen-HH": "Mehrgenerationenhaushalt",
            "[8] 8 Sonst. Kombination": "Sonstige Kombination",
        },
    )
    .astype(str)
)

Aufgabe 2: Plausibilitätschecks (30 Minuten insgesamt)#

Zur Aufbereitung der Daten gehört auch das Prüfen der Plausibilität der Daten. Eventuell haben wir bei der Aufbereitung Fehler gemacht oder die Daten beschreiben andere Sachverhalte als wir zunächst vermutet haben.

Aufgabe 2.1: Plausibilität des Bruttoarbeitseinkommens (15 Minuten)#

Erstellen Sie ein Scatter-Plot mit dem Haushalts-Arbeitseinkommen auf der x-Achse und der Bruttokaltmiete auf der y-Achse für zwei Haushaltstypen Ihrer Wahl.

Wie können Sie sich teilweise hohe Mieten bei keinem Arbeitseinkommen erklären? Welche Variablen bräuchten Sie, um zu prüfen, ob diese Angaben plausibel sind oder es sich um Messfehler handelt? (Sie brauchen keine Variablen zu suchen)

Aufgabe 2.2: Plausibilität der Miete (15 Minuten)#

Jetzt wollen wir prüfen, ob bei Eigentümern keine Miete eingetragen ist.

data_merged["rented_or_owned"].value_counts()
pd.crosstab(
    data_merged["bruttokaltmiete_m_hh"],
    data_merged["rented_or_owned"],
    margins=True,
)

Wie Sie sehen, sehen Sie nichts: Eigentümer sind in der obigen Tabelle nicht eingetragen, da die Variable bruttokaltmiete_m_hh für diese stets NaN ist. Setzen Sie die bruttokaltmiete_m_hh für Eigentümer auf 0.

Schauen Sie sich erneut die Tabelle zur Bruttomiete von Eigentümern und Nicht-Eigentümern an.

Aufgabe 3: Messfehler im Einkommen (20 Minuten)#

Denken Sie nochmals über die Datenerhebung und Aufbereitung nach. Sind die Daten plausibel? Gibt es Messfehler? Wenn ja, wodurch könnten diese zustande kommen?

GETTSIM mit SOEP-Daten#

Um die Datenaufbereitung abzukürzen stellen wir Ihnen nun einen bereits aufbereiteten Datensatz zur Verfügung.

Dabei wurde bspw. die Einkommensvariable exakt so aufbereitet wie oben. Im Prozess der Datenaufbereitung wurden auch andere simplifizierende Annahmen getroffen. Beispielsweise ist die Mietstufe aller Haushalte gleich, da wir die PLZ nicht beobachten.

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(["p_id", "hh_id", "tu_id"])
data_cleaned

Aufgabe 4: Histogramm Bruttoeinkommen (15 Minuten)#

Plotten Sie einen Histogramm des Bruttoeinkommens aus dem SOEP.

Plotten Sie ein Histogram in dem Sie nur Personen mit monatlichem Bruttolohn über 0 und unter 20.000€ betrachten.

Aufgabe 5: Berechnung von Steuern und Transfers (10min)#

Nun sind wir fast so weit, dass wir die Steuer- und Transferzahlungen berechnen können. Im Prinzip ändert sich nichts zu den Musterhaushalten, die wir im bisherigen Kursverlauf betrachtet haben.

Da es sich um Daten aus dem Jahr 2019 handelt, verwenden wir das Steuer- und Transfersystem aus 2019.

params_dict, policy_func_dict = set_up_policy_environment(2019)

Der Datensatz data_cleaned enthält bereits alle Variablen die wir für GETTSIM benötigen. Um die Steuern und Transfers zu berechnen, müssen wir noch einige Datentypen ändern und NaNs in 0 umcodieren.

In den Daten bestehen manche Haushalte aus mehreren “tax units”, das heißt, dass Individuen zwar in einem Haushalt wohnen aber steuerrechtlich getrennt voneinander zu betrachten sind. Wir treffen hier die simplifizierende Annahme, dass diese Individuen in getrennten Haushalten leben. Zukünftige Versionen von GETTSIM werden diese Restriktion nicht benötigen.

Desweiteren setzen wir unbekannte Größen, die im Zusammenhang mit der Rentenberechnung stehen (bspw. die Kinderberücksichtigungszeit), auf 0.

Die nächste Zelle müssen Sie nicht im Detail nachvollziehen.

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(["p_id", "hh_id", "tu_id"])

Berechnen wir die Steuern und Transfers:

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

Darauf basierend berechnen wir die Daten auf Haushaltsebene und passen die Variablennamen an.

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 6: Wohngeld (15 Minuten)#

Nun wollen wir die tatsächliche Inanspruchnahme abschätzen. Berechnen Sie zuerst, wie viele Haushalte laut GETTSIM Anspruch auf Wohngeld haben.

Berechnen Sie danach wie viele Haushalte laut SOEP tatsächlich Wohngeld in Anspruch nehmen. Nutzen Sie dazu die drop_duplicates() Methode um nur eine Beobachtung pro Haushalt zu behalten. Die Wohngeld Variable im data_cleaned Datensatz heißt housing_benefit_hh.

Nennen Sie mindestens zwei Gründe, warum der Quotient aus den beiden Zahlen von der echten Inanspruchnahmequote abweichen dürfte.

Aufgabe 7: 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.