import numpy as np
import pandas as pd
pd.options.plotting.backend = "plotly"
GETTSIM - Einführung: Einkommensteuer und Sozialversicherungsbeiträge#
Die Selbstlernphase besteht diese Woche aus 3 Teilen:
Zur Wiederholung werden Sie sich noch einmal kurz mit der Einkommensteuer und den Sozialversicherungsbeiträgen beschäftigen.
Anschließend werden Sie GETTSIM verwenden und damit ebenfalls Einkommensteuer und Sozialversicherungsbeiträge errechnen.
In einem letzten Schritt überprüfen Sie dann, inwieweit die Ergebnisse unserer Formeln aus dem Kurs mit GETTSIM übereinstimmen.
Aufgabe 1: Bekannte Funktionen#
Aufgabe 1.1: Aufwärmen (5 Minuten)#
Verschaffen Sie sich ein Überblick über die Funktionen, mit denen wir auch diese Woche in der Präsenzphase gearbeitet haben.
def einkommensteuer_2023(zu_versteuerndes_einkommen):
"""Berechne Einkommensteuer 2023.
Args:
zu_versteuerndes_einkommen (float): Zu versteuerndes Einkommen
Output:
Steuerlast(float): Steuerlast 2023
"""
x = np.floor(zu_versteuerndes_einkommen)
y = (x - 10_908) / 10_000
z = (x - 15_999) / 10_000
if x <= 10_908:
return 0
elif x <= 15_999:
return np.floor((979.18 * y + 1_400) * y)
elif x <= 62_809:
return np.floor((192.59 * z + 2_397) * z + 966.53)
elif x <= 277_825:
return np.floor(0.42 * x - 9_972.98)
else:
return np.floor(0.45 * x - 18_307.73)
def soli(einkommensteuer, freigrenze, soli_rate_max):
"""Berechne Solidaritätszuschlag.
Args:
einkommensteuer (float): Einkommensteuerlast
freigrenze (float): Solidaritätszuschlagsfreigrenze für die Einkommensteuerlast
soli_rate_max (float): Maximaler Grenzsteuersatz des Solidaritätszuschlags
Output:
soli (float): Steuerlast aus dem Solidaritätszuschlag
"""
soli = np.minimum(
0.055 * einkommensteuer,
np.maximum(soli_rate_max * (einkommensteuer - freigrenze), 0),
)
return soli
def soz_vers_beitrag(
bruttolohn_m,
arbeitnehmersatz,
bemessungsgrenze_m,
):
"""Berechne Beitrag zu einer der Sozialversicherungen.
Inputs:
bruttolohn_m (float): monatliches Bruttoarbeitsentgelt
arbeitnehmersatz (float): Beitragssatz, der vom Arbeitnehmer zu zahlen ist
bemessungsgrenze_m (float): Monatliche Beitragsbemessungsgrenze
Output:
arbeitnehmerbeitrag (float): Der Beitrag in Euro zur Sozialversicherung.
"""
e_bis_bbg = np.minimum(bruttolohn_m, bemessungsgrenze_m)
arbeitnehmerbeitrag = e_bis_bbg * arbeitnehmersatz
return np.round(arbeitnehmerbeitrag, 2)
def soli_einkommensteuer_m(einkommen_m):
"""Berechne Einkommenssteuer + Soli auf den Monat runtergerechnet.
Annahme: Einkommen bleibt konstant über das Jahr.
Args:
einkommen_m (float): monatliches Einkommen
Return:
float: Summe der Einkommensteuer und des Solidaritätszuschlag pro Monat
"""
steuer = einkommensteuer_2023(zu_versteuerndes_einkommen=einkommen_m * 12)
soli_betrag = soli(einkommensteuer=steuer, freigrenze=16_956, soli_rate_max=0.119)
return (steuer + soli_betrag) / 12
def gesamt_sozialversicherung(bruttolohn_m):
"""Berechne die gesamten Sozialabgaben eines Erwachsenen ohne Kinder.
Args:
bruttolohn_m (float): monatliches Bruttoarbeitsentgelt
Return:
float: monatliche Sozialversicherungsbeiträge
"""
beitrag = (
soz_vers_beitrag(bruttolohn_m, arbeitnehmersatz=0.093, bemessungsgrenze_m=7_300)
+ soz_vers_beitrag(
bruttolohn_m,
arbeitnehmersatz=0.013,
bemessungsgrenze_m=7_300,
)
+ soz_vers_beitrag(
bruttolohn_m,
arbeitnehmersatz=0.073,
bemessungsgrenze_m=4_987.50,
)
+ soz_vers_beitrag(
bruttolohn_m,
arbeitnehmersatz=0.01525 + 0.0035,
bemessungsgrenze_m=4_987.50,
)
)
return beitrag
Zunächst erstellen wir ein DataFrame, welches die Grundlage für die folgenden Berechnungen darstellt.
min_einkommen = 100
max_einkommen = 10000
anzahl_schritte = 100
eigene_berechnung = pd.DataFrame(columns=["bruttolohn_m"])
eigene_berechnung["bruttolohn_m"] = np.linspace(
min_einkommen,
max_einkommen,
anzahl_schritte,
)
eigene_berechnung
Aufgabe 1.2: Gesamtbelastung (5 Minuten)#
Zunächst berechnen wir die Beiträge zur Sozialversicherung, Einkommensteuerzahlungen und den Soli. Wir wählen dabei die Variablennamen so, dass sie konsistent mit GETTSIM sind. eink_st_tu
ist die zu zahlende Einkommensteuer auf das Jahreseinkommen auf Haushaltsebene (tax unit).
eigene_berechnung["eink_st_tu"] = (eigene_berechnung["bruttolohn_m"] * 12).apply(
einkommensteuer_2023,
)
eigene_berechnung["soli_st_tu"] = soli(eigene_berechnung["eink_st_tu"], 16_956, 0.119)
eigene_berechnung["einkommensteuer_inkl_soli_m"] = (
eigene_berechnung["eink_st_tu"] + eigene_berechnung["soli_st_tu"]
) / 12
eigene_berechnung["ges_rentenv_beitr_m"] = eigene_berechnung["bruttolohn_m"].apply(
soz_vers_beitrag,
arbeitnehmersatz=0.093,
bemessungsgrenze_m=7_300,
)
eigene_berechnung["arbeitsl_v_beitr_m"] = eigene_berechnung["bruttolohn_m"].apply(
soz_vers_beitrag,
arbeitnehmersatz=0.013,
bemessungsgrenze_m=7_300,
)
eigene_berechnung["ges_krankenv_beitr_m"] = eigene_berechnung["bruttolohn_m"].apply(
soz_vers_beitrag,
arbeitnehmersatz=0.073,
bemessungsgrenze_m=4_987.50,
)
eigene_berechnung["ges_pflegev_beitr_m"] = eigene_berechnung["bruttolohn_m"].apply(
soz_vers_beitrag,
arbeitnehmersatz=0.01525 + 0.0035,
bemessungsgrenze_m=4_987.50,
)
eigene_berechnung["gesamtbelastung_m"] = eigene_berechnung[
[
"einkommensteuer_inkl_soli_m",
"ges_rentenv_beitr_m",
"arbeitsl_v_beitr_m",
"ges_krankenv_beitr_m",
"ges_pflegev_beitr_m",
]
].sum(axis=1)
eigene_berechnung = eigene_berechnung.set_index("bruttolohn_m")
Plotten Sie in Abhängigkeit vom monatlichen Bruttolohn die monatliche Gesamtbelastung in einem Graphen.
Aufgabe 1.3: Grenzbelastung (5 Minuten)#
Wir wollen uns noch einmal die Grenzbelastung ansehen. Hierfür nutzen wir wie in der Vorlesung die Funktion shift
.
# Erzeugen einer Spalte, in der die Gesamtbelastung um eine Zeile verschoben ist.
eigene_berechnung["gesamtbelastung_m_shifted"] = eigene_berechnung[
"gesamtbelastung_m"
].shift(-1)
eigene_berechnung["grenzbelastung"] = (
eigene_berechnung["gesamtbelastung_m_shifted"]
- eigene_berechnung["gesamtbelastung_m"]
)
eigene_berechnung
Plotten Sie die Grenzbelastung in Abhängigkeit vom monatlichen Einkommen.
Aufgabe 1.4: Interpretation der Grenzbelastung (5 Minuten)#
Welche Mechanik bei Steuern und Abgaben führt dazu, dass die Grenzbelastung für höhere Einkommen abnimmt? Wieso spricht man in Deutschland trotzdem von einem progressiven Steuersystem?
Ihre Antwort in Stichpunkten hier
Aufgabe 2: Erste Schritte mit GETTSIM#
Aufgabe 2.1: Einführung in GETTSIM (20 Minuten)#
Wir importieren zunächst die drei GETTSIM-Funktionen, mit denen wir arbeiten werden.
Hinweis: Normalerweise importiert man Bibliotheken und Funktionen im Skript ganz oben. Aus didaktischen Gründen weichen wir hier davon ab.
from gettsim import (
compute_taxes_and_transfers,
create_synthetic_data,
set_up_policy_environment,
)
In einem ersten Schritt wollen wir uns die Steuerlast in Abhängigkeit vom Einkommen von GETTSIM berechnen lassen.
GETTSIM kann fast das gesamte deutsche Steuer- und Sozialsystem für natürliche Personen abbilden. Insbesondere die Sozialleistungen hängen von sehr vielen individuellen Informationen ab, beispielsweise der Miethöhe, den Heizkosten oder dem Behinderungsgrad. Insgesamt hat GETTSIM über 60 Inputvariablen. Da uns zunächst nur Einkommensteuer und Sozialversicherungsbeiträge interessieren, können wir uns auf die benötigte Teilmenge beschränken.
Dies sind die folgenden Inputs:
data = pd.DataFrame(
{
"p_id": 1,
"tu_id": 1,
"hh_id": 1,
"hat_kinder": False,
"bruttolohn_m": 2000.0,
"_zu_verst_eink_ohne_kinderfreib_tu": 24000.0,
"abgelt_st_tu": 0.0,
"zu_verst_kapital_eink_tu": 0.0,
"wohnort_ost": False,
"kind": False,
"alter": 35,
"selbstständig": False,
"eink_selbst_m": 0.0,
"ges_rente_m": 0.0,
"priv_rente_m": 0.0,
"in_priv_krankenv": False,
"arbeitsstunden_w": 40.0,
"in_ausbildung": False,
},
index=[0],
)
# Transponiere für bessere Lesbarkeit
data.T
Anmerkungen zu den Inputs#
GETTSIM berechnet Werte auf individueller Ebene, der einer Steuergemeinschaft (idR eine Ehe) und auf Haushaltsebene (gekennzeichnet durch hh). Entsprechend haben wir die Identifikationsvariablen:
p_id
für eine Persontu_id
für eine Steuergemeinschaft (tu für tax unit)hh_id
für einen Haushalt
Diese Identifikationsvariablen ermöglichen es, verschiedene Personen einer Steuergemeinschaft oder einem Haushalt zuzuordnen. Das Namensschema findet sich auch in den Spaltennamen wieder. Variablen ohne Kennzeichnung beziehen sich immer auf Individuen. Spaltennamen, die ein
_tu
bzw_hh
beinhalten, beziehen sich auf die die Ebenen von Steuergemeinschaft bzw. Haushalt.Monatlich definierte Variablen werden mit _m gekennzeichnet, bei Variablen ohne Zeitangabe, aber mit Bezug auf einen Zeitraum (z.B.
abgelt_st_tu
), ist immer das Jahr gemeint._zu_verst_eink_ohne_kinderfreib_tu
ist für diese Aufgabe äquivalent zu dem, was wir bisher zu versteuerndes Einkommen bezeichnet haben. Dementsprechend ist_zu_verst_eink_ohne_kinderfreib_tu
das Zwölffache vonbruttolohn_m
. Aus technischen Gründen müssen in der vereinfachten Variante, mit der wir arbeiten, beide Variablen angegeben werden.Damit sind die restlichen Variablennamen vermutlich selbsterklärend, ansonsten werfen Sie bitte einen Blick in die entsprechende Seite der Dokumentation. Wir werden in der Selbstlernphase ausschließlich das Einkommen variieren, Sie müssen sich also nicht im Detail mit den Variablen beschäftigen.
Nichtsdestotrotz ist es sinnvoll, wenn Sie sich mit den Kenntnissen, die Sie in den letzten Wochen erworben haben, überlegen, wie verschiende Variablenwerte die Berechnung von Steuern und Sozialversicherungsbeiträgen beeinflussen.
Schritt 1: Festsetzen der Parameter des Steuer- und Transfersystems#
GETTSIM kann momentan bis ungefähr ins Jahr 2010 zurück die Steuer- und Sozialpolitik
korrekt abbilden. Wir wollen uns das Jahr 2023 anschauen, deshalb übergeben wir der
Funktion set_up_policy_environment
den Wert 2023
.
params_dict, policy_func_dict = set_up_policy_environment(2023)
Die Funktion gibt zwei Werte zurück: policy_func_dict
enthält die notwendigen
Funktionen für das Jahr 2023 (also beispielsweise eine Funktion für die
Sozialversicherungsbeiträge mit Beitragssatz und Beitragsbemessungsgrenze), und
params_dict
die zugehörigen Parameter.
Schritt 2: Übergabe zusätzlicher Variablen#
Wie oben erklärt, wollen wir uns in der Selbstlernphase nur einen Ausschnitt von GETTSIM ansehen. Dazu übergeben wir GETTSIM direkt drei Variablen als zusätzliche, die sonst auf Grundlage vieler anderer Inputs errechnet würden. Diese überschreiben Funktionen von GETTSIM (Schritt 4), der Konsistenz halber wählen wir den Variablennamen entsprechend des Schlüsselwortes in der Funktion von GETTSIM.
columns_overriding_functions = [
"abgelt_st_tu",
"_zu_verst_eink_ohne_kinderfreib_tu",
"ges_rente_m",
]
Schritt 3: Festsetzung der zurückzugebenden Variablen#
Standardmäßig würde GETTSIM versuchen, eine lange Liste der ihm bekannten Elemente des Steuer- und Transfersystems zurückzugeben. Den gesamten Bereich der bedarfsgeprüften Transfers und weiteres ignorieren wir jedoch zunächst.
Stattdessen berechnen wir nur die Einkommensteuer, den Soli und die vier
Sozialversicherungsbeiträge. Diese legen wir deshalb in den targets
fest:
targets = [
"eink_st_tu",
"soli_st_tu",
"ges_krankenv_beitr_m",
"ges_rentenv_beitr_m",
"arbeitsl_v_beitr_m",
"ges_pflegev_beitr_m",
]
Schritt 4: Berechnung der Steuern und Sozialversicherungsbeiträge#
Die Funktion compute_taxes_and_transfers
errechnet nun die Zielvariablen. Schauen
Sie sich die Inputs der Funktion genauer an, Sie sollten alle Elemente wiedererkennen.
ergebnis = compute_taxes_and_transfers(
data=data,
functions=policy_func_dict,
columns_overriding_functions=columns_overriding_functions,
params=params_dict,
targets=targets,
rounding=True,
)
ergebnis
Sie haben ein DataFrame mit den berechneten Targets erhalten. Zur besseren Lesbarkeit können Sie das jetzt noch an das DataFrame mit den Inputs zusammenfügen:
komplett = data.join(ergebnis.round(2))
komplett.T
Direkter Vergleich mit unseren selbst berechneten Ergebnissen#
Zunächst wählen wir das Element aus der eigenen Berechnung mit Bruttolohn 2000 Euro und setzen einen passenden Index.
eigene_berechnung_2000 = (
eigene_berechnung.query("bruttolohn_m == 2000")[targets]
.round(
2,
)
.set_index(pd.Index(data=["Eigene Berechnung"], name="Methode"))
)
eigene_berechnung_2000
Für die GETTSIM-Berechnung setzen wir den analogen Index direkt innerhalb der
concat
-Funktion, verändern also die Variable ergebnis
nicht. Für die selbst
berechneten Ergebnisse haben wir das bereits oben getan. Schlussendlich
transponieren wir den resultierenden DataFrame zwecks besserer Lesbarkeit.
pd.concat(
[
ergebnis.set_index(pd.Index(data=["GETTSIM"], name="Methode")),
eigene_berechnung_2000,
],
).T
Das Ergebnis der Berechnung der Krankenversicherungsbeiträge weicht ab. Haben Sie eine Idee, woran das liegen könnte? Bitte nur beantworten, wenn Ihnen spontan etwas einfällt, keine Zeit darauf verwenden!
Aufgabe 2.2: DataFrame data_groß erzeugen (15 Minuten)#
Nun wollen wir Steuern und Abgaben für verschiedene Einkommenslevel bzw. verschiedene
Haushalte berechnen. Um einen entsprechenden Datensatz zu erstellen, können wir eine
Funktion der GETTSIM Bibliothek nutzen. Dieser Datensatz soll das Gegenstück zum
eigene_berechnung
Datensatz von oben sein.
Hinweis: Wir hätten create_synthetic_data
bereits in Aufgabe 2.1 verwenden können,
die explizite Erstellung der Variablen haben wir aus didaktischen Gründen gewählt.
data_groß = 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)
],
},
)
Zuletzt definieren wir, wie oben, diejenigen Variablen, welche GETTSIM nicht selbst berechnen soll:
data_groß["abgelt_st_tu"] = 0.0
data_groß["ges_rente_m"] = 0.0
data_groß["_zu_verst_eink_ohne_kinderfreib_tu"] = data_groß["bruttolohn_m"] * 12
GETTSIM lässt sich nun genauso anwenden wie zuvor:
ergebnis_groß = compute_taxes_and_transfers(
data=data_groß,
functions=policy_func_dict,
columns_overriding_functions=columns_overriding_functions,
params=params_dict,
targets=targets,
rounding=True,
)
ergebnis_groß.round(2)
Fügen Sie die DataFrames data_groß
und ergebnis_groß
zusammen (.join
),
nennen Sie den neuen Datensatz komplett_groß
.
Plotten Sie die monatliche Gesamtbelastung.
Tipp: Dafür müssen Sie zunächst die Einkommensteuer und Soli Belastungen auf das Monatslevel herunter rechnen und dann die Gesamtbelastung (inklusive Sozialversicherungsabgaben) ermitteln. Dann können Sie die plot Funktion von oben verwenden und die Variablennamen anpassen.
Aufgabe 2.3: Grenzbelastung plotten (10 Minuten)#
Errechnen Sie die Grenzbelastung.
Tipp: Bis auf die Variablennamen können Sie den gesamten Code aus 1.3 kopieren!
Bonusaufgabe 3: Vergleich von GETTSIM mit den selbstgeschriebenen Funktionen (10 Minuten)#
Stapeln Sie die beiden DataFrames eigene_berechnung
und komplett_groß
.
Tipp: Verwenden Sie die pd.concat
-Funktion mit ganz genau so, wie beim Zusammenfügen
von DataFrames in der vergangenen Präsenzphase (Notebook: ehegattensplitting). Um den Index korrekt zu setzen können Sie hier folgende keys
und names
in der pd.concat
Funktion verwenden.
keys=["Eigene Berechnung", "GETTSIM"],
names=["Berechnungsmethode", "bruttolohn_m"],
Plotten Sie in einem Graphen die Grenzbelastung aus unseres eigenen Berechnung und aus GETTSIM.
Haben Sie eine Erklärung für die Unterschiede unterhalb von einem monatlichen Bruttolohn von 2000 Euro?
Tipp: mit .query("bruttolohn_m <= 2000")
können Sie den DataFrame auf Bruttolöhne
bis 2000 Euro beschränken.
Plotten Sie in einem Graphen die Gesamtbelastung aus unseres eigenen Berechnung und aus GETTSIM.