15 Repetitionsübung 8
16 Hinweis
Spätestens ab jetzt solltet ihr das Quarto Markdown file “Vorlage Quarto Uebungen.qmd” (siehe OLAT -> Materialien (Tutorate)) für die Lösung der Übungsblätter verwenden. Mit diesem File werdet ihr auch die Prüfung schreiben.
# Wichtig: Ladet eure packages über den folgenden Befehl. Ladet keine weiteren packages.
pacman::p_load("sjPlot", "texreg", "haven", "readr", "readxl", "fixest", "MASS","glm.predict", "scales", "tidyverse")17 Teil 1: Eure Fragen aus dem Forum und den Tutoraten
Frage 1: Wie kann man von der Aufgabenstellung herauslesen, welche Funktionen man verwenden soll?
- Achte auf Signalausdrücke wie “Variable auswählen” (select), “Beobachtungen auswählen” (filter) “Variable erstellen/verändern” (mutate)“,”Berechne und interpretiere lineare Regression” (lm), “Berechne und visualisiere Regression” (ggplot + geom_abline)
- Erstellt einen “Spick” mit den wichtigsten R-Befehlen aus dem Tutorat
- Google nutzen, falls nötig
- Tidyverse Cheatsheets anschauen, bspw. https://nyu-cdsc.github.io/learningr/assets/data-transformation.pdf und https://rstudio.github.io/cheatsheets/data-visualization.pdf
- Übung macht den Meister!
Frage 2: In welcher Situation brauche ich eine Filter-Funktion die ein %in% beinhaltet und wann würde ich einfach ein == verwenden?
df_selects19 <- haven::read_dta("Selects2019.dta")
df_selects19[df_selects19 == ""] <- NA # NA's korrekt kodieren
# == bedeutet in r "ist gleich". Wir können damit einen Wert mit genau einem anderen Wert vergleichen
df_zug <- df_selects19 %>%
filter(Kanton == "Zug")
table(df_zug$Kanton)
# %in% bedeutet in r "ist Element von". Wir können damit prüfen ob ein Wert in einem Set an Möglichkeiten enthalten ist
df_zentralschweiz <- df_selects19 %>%
filter(Kanton %in% c("Luzern", "Uri", "Schwyz", "Obwalden", "Nidwalden", "Zug"))
table(df_zentralschweiz$Kanton)
# Eine äquivalente Schreibweise wäre folgende:
df_zentralschweiz2 <- df_selects19 %>%
filter(Kanton == "Luzern" |
Kanton == "Uri" |
Kanton == "Schwyz" |
Kanton == "Obwalden" |
Kanton == "Nidwalden" |
Kanton == "Zug")Frage 3: Was sind nochmal Faktoren und wozu sind sie gut?
# Wir schauen uns nochmal die Aufgabe an, bei der wir die links-rechts-Selbsteinschätzung von Personen erklären wollten
## Repetition Recoding leftright
df_selects19 <- df_selects19 %>%
mutate(
leftright = as.numeric(case_when(
leftright == "0 - Left" ~ "0",
leftright == "10 - Right" ~ "10",
T ~ leftright))
)
# Eigene Arbeit nach Recoding immer kontrollieren!
table(df_selects19$leftright, useNA="ifany")
# Und was sind jetzt Faktoren?
# Faktoren sind Kategoriale Variable mit definierten Stufen (Levels)
# Faktoren speichern Kategorien intern als Zahlen, zeigen aber Labels an.
## Beispiel 1: Links-Rechts-Einschätzung nach Geschlecht
class(df_selects19$gender)
table(df_selects19$gender)
lm(leftright ~ gender, data = df_selects19)
# Wir wollen "Male" als Referenzkategorie
df_selects19 <- df_selects19 %>%
mutate(gender = factor(gender, # Variable, die wir konvertieren wollen
levels = c("Male", "Female"))) # Mit levels = geben wir an, welche Ausprägungen in welcher Reihenfolge gespeichert werden sollen
lm(leftright ~ gender, data = df_selects19)
## Beispiel 2: Links-Rechts-Einschätzung nach Religionszugehörigkeit
class(df_selects19$religion)
table(df_selects19$religion)
lm(leftright ~ religion, data = df_selects19) # R konvertiert religion zu einem Faktor und macht Catholic zur Referenzkategorie
# Variable vereinfachen und zu einem Faktor recodieren
df_selects19 <- df_selects19 %>%
mutate(religion = factor(case_when(
religion %in% c("Protestant", "Catholic", "Other Christian religion") ~ "Christian",
TRUE ~ religion),
levels = c("None", "Christian", "Muslim", "Jewish", "Other religion")))
levels(df_selects19$religion) # Ausprägungen der Faktorvariable
lm(leftright ~ religion, data = df_selects19)
# Wofür sind Faktoren also gut? Kontrolle der Reihenfolge von Kategorien einer VariableFrage 4: Wie verwendet man pivot-wider nochmal genau? + Wann braucht man group_by?
# Recoding
df_selects19 <- df_selects19 %>%
mutate(alter_cat = case_when(alter <= 34 ~ "18-34",
alter > 34 & alter < 55 ~ "35-54",
alter > 54 & alter < 75 ~ "55-74",
alter >= 75 ~ "75+"))
agemeans <- df_selects19 %>%
group_by(alter_cat, gender) %>%
summarize(mu = mean(leftright, na.rm = TRUE))
# Datensatz "breiter" machen
# Wir wollen eine Reihe pro Altersgruppe
agemeans_wider <- agemeans %>%
pivot_wider(names_from = gender, # Spaltennamen basieren auf der "gender"-Variable
values_from = mu) # Werte für die neuen Spalten kommen aus der "mu"-Variable
# Datensatz "länger" machen
# Wir wollen eine Reihe pro Altersgruppen-Geschlechts Kombination
agemeans_longer <- agemeans_wider %>%
pivot_longer(
cols = c("Female", "Male"), # Welche Spalten sollen in eine einzige Spalte umgewandelt werden?
names_to = "gender", # Spaltennamen ("Female"/"Male") kommen in die neue Spalte "gender"
values_to = "mu" # Werte aus den Spalten kommen in die neue Spalte "mu"
)Frage 5: Wann sollte man NA-Werte entfernen und wann behalten? In welchen Situationen muss man R explizit mitteilen, dass NA-Werte behalten werden sollen? Wann erkennt R dies automatisch?
- Observationen mit NA’s müssen in der Regel nicht entfernt werden. Falls doch
na.omitverwenden
df_selects19 <- readr::read_csv("Selects2019.csv")
df_selects19_nona <- df_selects19 %>%
na.omit() #drop_na <- macht dasselbe wie na.omit()- In Tabellen könnt ihr NA’s eigentlich immer darstellen lassen (ausser es wird explizit angegeben, dass ihr das nicht müsst)
table(df_selects19$partei, df_selects19$religion, useNA="ifany")
table(df_selects19_nona$partei, df_selects19_nona$religion, useNA="ifany")- Lm() entfernt NA’s automatisch
- Ansonsten gut auf die Aufgabenstellung achten. Daraus lässt sich ablesen, ob ein Recoding von NA’s verlangt wird.
18 Teil 2: Ganze Aufgaben aus Übungsblättern
18.1 Übung 2, Aufgabe 3
- Erstelle einen Datensatz
df_verworfen, welcher für jede Parteizugehörigkeit die Wahrscheinlichkeit einen Fall zu verwerfen enthält. Nehme vereinfachend an, dass ein Fall entweder verworfen oder bestätigt werden kann. Für die Lösung dieser Aufgabe kannst du unter anderem die Funktionenfilter(),group_by(), undcount()verwenden.
# Daten einlesen
df_asylentscheide <- read_csv("bvg_dataset.csv")
df_verworfen <- df_asylentscheide %>%
# Zunächst berechnen wir die absolute Anzahl verworfener und bestätigter Fälle je Partei
filter(case_decision %in% c("dismissed", "upheld")) %>%
group_by(judge_party) %>%
count(case_decision) %>%
# Dann berechnen wir die Wahrscheinlichkeit/den Anteil, dass ein Fall verworfen wird je Partei
mutate(Prverworfen = round(n / sum(n), 2)) %>%
# Und filtern nach verworfenen Fällen
filter(case_decision == "dismissed")- Stelle nun die, in deinem Datensatz gespeicherten, Wahrscheinlichkeiten einen Fall zu verwerfen nach Partei als Balkendiagramm dar.
df_verworfen %>%
ggplot(aes(x = Prverworfen * 100, y = judge_party, fill = judge_party)) +
geom_col(width = 0.5) +
scale_fill_manual(values = c("BDP" = "#E6C820", "FDP" = "#3872B5",
"GLP" = "#C4C43D", "GPS" = "#84B547",
"Mitte" = "#D6862B", "SP" = "#F0554D",
"SVP" = "#4B8A3E")) +
theme_bw() +
theme(legend.position = "none") +
labs(x = "Anteil verworfener Fälle", y = "Parteizugehörigkeit") +
xlim(0,100)18.2 Übung 2, Aufgabe 5
Sei \(D_i\in\{0,1\}\) ein Indikator für den Treatment-Status einer Untersuchungseinheit \(i\). Für alle Einheiten in der Treatment-Gruppe gilt \(D_i=1\), während \(D_i=0\) für alle Kontrolleinheiten. Ferner sei \(Y_i(0)\) das potentielle Ergebnis unter Kontrollstatus, während \(Y_i(1)\) das potentielle Ergebnis unter Treatmentstatus darstellt.
- Simuliere die beobachteten Werte \(Y\) einer randomisierten kontrollierten Studie (engl. randomized controlled trial RCT) mit 1000 Teilnehmenden. Der Treatment-Effekt betrage 2.55 für sämtliche Individuen. Nehme an, dass die potentiellen Ergebnisse aus folgendem Datengenerierungsprozess stammen:
\[\begin{align*} Y(0) &= 1 + \varepsilon, & \varepsilon\sim\mathcal{N}(0,1) \\ Y(1) &= 1 + \text{TE} + \varepsilon, & \varepsilon\sim\mathcal{N}(0,1) \end{align*}\]
# TIDYVERSE VARIANTE
# Seed setzen für Nachvollziehbarkeit
set.seed(32)
# Treatment randomisieren, Fehlerterm und Treatment-Effekt generieren
exp <- tibble(
treatment = rbinom(n = 1000, size = 1, prob = 0.5), # Randomisierung, wer Treatment erhält
e = rnorm(n= 1000, mean = 0, sd = 1), # Individueller Fehler, Basisabweichung
effect = 2.55 # Treatment-Effekt, hier für alle gleich, in der Realität aber häufig unterschiedlich (für diese Übung irrelevant)
)
# Alles in Formel Datengenerierungsprozesse einsetzen und potentielle Ergebnisse Y(1) und Y(0) generieren
exp <- exp %>%
mutate(
y_0 = 1 + e,
y_1 = 1 + effect + e
)
# Beobachtete Werte Y generieren
# Wenn treatment = 1, dann y_1, sonst y_0
exp <- exp %>%
mutate(
y = ifelse(treatment == 1, y_1, y_0)
)- Berechne nun sowohl den Mittelwert der beobachteten Werte für die Kontroll- als auch die Treatment-Gruppe und bilde die Differenz beider Werte. Wiederhole dies für beide potentiellen Ergebnisse. Vergleiche die beiden erhaltenen Differenzen.
# Beobachtete Werte (y)
# Tidyverse
mean_treated <- exp %>%
filter(treatment == 1) %>%
summarize(mean_y = mean(y))
mean_control <- exp %>%
filter(treatment == 0) %>%
summarize(mean_y = mean(y))
mean_treated - mean_control
# Mit base r
mean(exp$y[exp$treatment == 1]) - mean(exp$y[exp$treatment == 0])
# Potential outcomes (y_0 und y_1)
mean(exp$y_1 - exp$y_0)Wir können erkennen, dass \(\mathbb{E}[Y|D=1]-\mathbb{E}[Y|D=0]\approx\mathbb{E}[Y(1)-Y(0)]\). In Worten ausgedrückt, sehen wir, dass der Unterschied in den beobachteten Mittelwerten approximativ dem durchschnittlichen Treatment-Effekt (engl. average treatment effect ATE) entspricht. Dies gilt, da das Treatment zufällig zugewiesen wurde, wodurch sich die potenziellen Outcomes in beiden Gruppen im Erwartungswert nicht unterscheiden.
Der beobachtete Mittelwert entspricht nicht genau dem Treatment-Effekt, da es aufgrund der randomisierten Treatment-Zuweisung in dieser spezifischen Iteration der Zuweisung mal diese, mal jene Observation ‘trifft’. Über viele Iterationen und viele Zuweisungen würde der Mittelwert der beobachteten Mittelwerte genau dem Treatment-Effekt entsprechen.
18.3 Übung 3, Aufgabe 3c (+ Repetition left_join) #wichtig!
- Importiere den Datensatz
mean_turnout.csvund führe ihn mit dem bestehenden DatensatzCO2_Gesetz.csvzusammen.
#| message: false
# Importiere Datensätze
df_turn <- read_csv("mean_turnout.csv")
df_co2 <- read_csv("CO2_Gesetz.csv")
df_co2 <- df_co2 %>%
left_join(df_turn, by = c("canton_id", "canton_name", "mun_id", "mun_name"))
# Verbinden von Datensätzen über Schlüsselspalten
# left_join(): Alle Zeilen von links, passende von rechts
# right_join(): Alle Zeilen von rechts, passende von links
# inner_join(): Nur Zeilen, die in beiden vorkommen
# full_join(): Alle Zeilen aus beiden Tabellen
# Datensätze stumpf zusammenkleben
# bind_rows(): Tabellen untereinander stapeln (gleiche Spalten)
# bind_cols(): Tabellen nebeneinander kleben (gleiche Zeilenanzahl)
#saveRDS <- zum Daten speichern!!
#immer zuerst dataframe, dann wie es gespeichert werden soll in ""- (herausfordernd) Möglicherweise fragst du dich, ob das \(\text{CO}_2\)-Gesetz angenommen worden wäre wenn die Stimmbeteiligung in den ländlichen Gemeinden am 13. Juni nicht so aussergewöhnlich hoch gewesen wäre. Berechne die absolute Anzahl an Ja-Stimmen für jede Gemeinde in diesem kontrafaktischen Szenario anhand der durchschnittlichen Stimmbeteiligung der sechs vorangehenden Abstimmungsterminen (
mean_turnout_pct). Nehme an, dass alle anderen Faktoren gleich bleiben (Anzahl Stimmberechtigte, Verhältnis von Ja- zu Nein-Stimmen etc.).
# Erstelle neuen Data Frame für kontrafaktisches Szenario
df_co2_kf <- df_co2 %>%
select(canton_id, canton_name, mun_id, mun_name, anzahlStimmberechtigte, mean_turnout_pct, jaStimmenInProzent) %>%
mutate(stimmbeteiligung = mean_turnout_pct / 100,
AnteilJaStimmen = jaStimmenInProzent / 100,
# Berechne gültige Stimmen und absolute Anzahl Ja-Stimmen
gueltigeStimmen = stimmbeteiligung * anzahlStimmberechtigte,
jaStimmenAbsolut = round(AnteilJaStimmen * gueltigeStimmen)
)- Wäre das \(\text{CO}_2\)-Gesetz in obigem Szenario angenommen worden?
# Tatsächlicher Anteil Ja-Stimmen
sum(df_co2$jaStimmenAbsolut) / sum(df_co2$gueltigeStimmen) * 100
# Anteil Ja-Stimmen in kontrafaktischem Szenario
sum(df_co2_kf$jaStimmenAbsolut) / sum(df_co2_kf$gueltigeStimmen) * 10018.4 Übung 3, Aufgabe 4c
df_co2_exklZH <- df_co2 %>% filter(mun_name != "Zürich")
co2_fit3 <- lm(stimmbeteiligungInProzent ~ anzahlStimmberechtigte, data = df_co2)
co2_fit4 <- lm(stimmbeteiligungInProzent ~ anzahlStimmberechtigte,
data = df_co2_exklZH)- Basierend auf dem in der vorherigen Teilaufgabe geschätzten Modell ohne die Stadt Zürich: Was ist die vorhergesagte Stimmbeteiligung für die Stadt Lugano?
Um die vorhergesagte Stimmbeteiligung zu erhalten setzen wir von Hand die geschätzten Koeffizienten und den x-Wert für Lugano in die Regressionsgleichung ein:
\[ \widehat{\text{stimmbeteiligungInProzent}}_i = \hat{\beta}_0 + \hat{\beta}_1 \cdot \text{anzahlStimmberechtigte}_i \]
# Von Hand ausrechnen
beta0 <- coef(co2_fit4)[1] # Achsenabschnitt
beta1 <- coef(co2_fit4)[2] # Steigung
x_lugano <- df_co2_exklZH %>%
filter(mun_name == "Lugano") %>%
pull(anzahlStimmberechtigte)
# In Formel einsetzen
y_hat_lugano <- beta0 + beta1*x_lugano
y_hat_luganoOder wir lassen R mithilfe von predict diese Aufgabe für uns übernehmen.
predict(co2_fit4, newdata = data.frame(anzahlStimmberechtigte = x_lugano))In beiden Fällen sehen wir, dass die vorhergesagte Stimmbeteiligung für die Stadt Lugano 51.2% beträgt.
19 Teil 3: Gibt es weitere offene Fragen/Probleme mit R?
20 Teil 4: Grundlagen von R
20.1 Funktionen #bei nachbearbeitung lesen <- nur passiv können
Eine Funktion in R ist ein wiederverwendbarer Codeblock, der eine bestimmte Aufgabe ausführt. Mit Funktionen können wir also ganze Abläufe bzw. Algorithmen für eine spätere Verwendung abspeichern. Die meisten Funktionen, die wir verwenden, haben andere Personen für uns geschrieben. Wir können aber auch selbst Funktionen definieren.
# Aufbau einer Funktion
name <- function(argumente) {
# Code der ausgeführt werden soll
}
# BEISPIEL: Würfel in r
sample(1:6, size = 1, replace = TRUE)
sample(1:6, size = 1, replace = TRUE)
sample(1:6, size = 2, replace = TRUE) # Zwei Würfe
sample(1:12, size = 2, replace = TRUE) # Zwei Würfe mit einem 12er-Würfel
# Geht das auch einfacher?
game <- function() {
sample(1:6, size = 1, replace = TRUE)
}
game()
# Parameter definieren
game <- function(type, size) {
sample(type, size = size, replace = TRUE)
}
game(type = 1:6, size = 2)
# game(size = 1, replace = TRUE) # replace kann nicht angepasst werden, da kein entsprechender Parameter definiert wurde
# Default Werte für Parameter setzen
game <- function(type = 1:6, size) {
sample(type, size = size, replace = TRUE)
}
game(size = 1)
game(1:12, 500)20.2 For-Schleifen
For-Schleifen wiederholen einen Codeblock für eine definierte Anzahl von Durchläufen.
# BEISPIEL 1
# Nehmen wir an, wir möchten jede Zahl von 1 bis 10 mit 2 multiplizieren
1*2
2*2
3*2
4*2
5*2
6*2
7*2
8*2
9*2
10*2
# Schnellere Variante
for (i in 1:10) {
x <- i*2 # im ersten Durchgang ist i = 1, dann i = 2, dann i = 3, usw.
print(x)
}
# i ist nur ein platzhalter!
for (zahl in 1:10) {
x <- zahl*2
print(x)
}20.3 Aufgabe mit For-Schleife
Wie viele biologische Merkmale ist die Körpergrösse approximativ normalverteilt. In der Schweiz beträgt die durchschnittliche Körpergrösse für Frauen ca. 165 cm. Generiere jeweils 4 X 1000 Zufallswerte für die Körpergrösse von Frauen, wobei du vier verschiedene Werte für die Standardabweichung verwendest: 3 cm, 5 cm, 8 cm, und 10 cm. Verwende dazu einen for Loop und einen seed von 42. Erstelle anschliessend mit ggplot eine Grafik, welche die vier Verteilungen jeweils als Histogramm darstellt.
# Setze Seed für Reproduzierbarkeit des Zufallsprozesses
set.seed(42)
# Erstelle leeren Dataframe als Container
df_plot <- tibble()
for (i in c(3,5,8,10)) {
df_temp <- tibble(
grösse = rnorm(n = 1000, mean = 165, sd = i), std = i
)
df_plot <- bind_rows(df_plot, df_temp)
}
# Plot erstellen
df_plot %>%
ggplot(aes(x = grösse)) +
geom_histogram(binwidth = 1.5, fill = "#0028A5", color = "white") +
facet_wrap(~std) +
theme_bw() +
labs(x = "Körpergrösse", y = "Anzahl")20.4 Aufgabe mit Funktion und For-Schleife
Du wirfst 1000 Mal je zwei faire (6er)-Würfel und addierst jeweils die Augenzahlen. Welche Augenzahlsumme wirst du am häufigsten als Resultat eines Würfelwurfs erhalten? Definiere eine Funktion game, welche dieses einfache Szenario simuliert. Führe das Szenario 10000 Mal durch und erstelle mit ggplot ein Säulendiagramm, welches die Ergebnisse darstellt. Verwende wiederum einen seed von 52. Entspricht die Grafik deinen Erwartungen?
game <- function(type = 1:6) {
roll <- sample(type, size = 2, replace = TRUE)
sum(roll)
}
diceresults <- rep(NA, 10000)
set.seed(52)
for (i in 1:10000) {
diceresults[i] <- game()
}
diceresults <- diceresults %>% as.tibble() # ggplot erwartet einen dataframe
diceresults %>%
ggplot(aes(x = value)) +
geom_bar(fill = "#0028A5") +
theme_bw() +
labs(x = "Augenzahlsumme", y = "Anzahl") +
scale_x_continuous(breaks = c(1:12))
# Anteile der Spielresultate
prop.table(table(diceresults))Wie erwartet ist die häufigste Augensumme 7. Die Anzahl möglicher Outcomes bei einem zweifachen Würfelwurf beträgt 6^2=36. Die Menge der Kombinationen von Augenzahlen mit einer Summe von 7 ist {(4,3),(3,4),(5,2),(2,5),(6,1),(1,6)}. Daher ist Pr(S=7)= 6/36 = 0.17. Für alle anderen Augenzahlsummen ist die Menge der möglichen Kombinationen kleiner.
Hinweis für die Prüfung: In Bezug auf das Schreiben eigener Funktionen und For-Schleifen, genügen passive Kenntnisse.
21 Teil 5: Häufige Fehlermeldungen/Probleme in R und Debugging
21.1 Markdown-Fehler
21.2 Code wird nicht ausgeführt
mean(c(1,2,3)21.3 Unerwartetes Symbol
sum(df_selects19$leftright na.rm = T)21.4 Datensatz nicht gefunden
read_csv("daten.csv")21.5 Objekt nicht gefunden
mean(x)
read.csv(meine_datei.csv)
df_zug <- df_selects19 %>%
filter(Kanton == Zug)21.6 Funktion nicht gefunden
plott(1:10)21.7 Berechnung deskriptiver Statistiken gibt NA aus
median(df_selects19$leftright)
mean(df_selects19$leftright)
sum(df_selects19$leftright)21.8 Falscher Datentyp I
x <- "fünf"
x + 2
mean(df_selects19$partei, na.rm = T)21.9 Falscher Datentyp II
df_selects19 <- df_selects19 %>%
mutate(
leftright = as.numeric(case_when(
leftright == "0 - Left" ~ "0",
leftright == "10 - Right" ~ 10,
T ~ leftright))
)21.10 Objekt nicht gefunden
df_selects19 %>% class(leftright)21.11 Allgemeine Hinweise zum Lösen von Fehlern in R
1.) Syntaxfehler ausschliessen: Fehlt irgendwo eine Klammer, ein Komma, etc.? 2.) Rechtsschreibung prüfen: Wurden die richtigen Variablen/-Objektnamen verwendet? Wurde eine Funktion falsch geschrieben? 3.) Isolieren des Fehlers: Versuche einzugrenzen wo genau der Fehler auftritt. Gehe den Code Schritt für Schritt, von oben nach unten durch. Welcher Schritt, welche Funktion löst den Fehler aus? 4.) Dokumentation konsultieren: Falls der Fehler durch eine Funktion ausgelöst wird mit ?funktionsname das Helpfile konsultieren 5.) Online Ressourcen verwenden: Googeln, Online-Foren durchsuchen, bspw. Stack Overflow oder Reddit. An der Prüfung: Keine LLM’s! 6.) Packages/R updaten: Falls sich der Fehler nicht beheben lässt. Achtung: Updates nicht während der Prüfung machen, sondern vorher!
Weiterführender Blog-Post zu Debugging: https://statsandr.com/blog/top-10-errors-in-r/