# Was sind R und RStudio?
R ist eine **Programmiersprache**, die es seit 1992 gibt und speziell für die statistische Analyse entwickelt wurde.
Sie wird jedoch nicht nur in der Statistik, sondern in fast jedem Forschungsgebiet eingesetzt: Von den Sozialwissenschaften über Naturwissenschaften und Ingenierwissenschaften bis hin zur Linguistik. Auch von Unternehmen wird die Programmiersprache gerne genutzt.
Sie ist so beliebt, da sie eine riesige Auswahl an Paketen (Erweiterungen) für nahezu jede Art von **Datenanalyse** und auch zur **Präsentation** dieser Analysen bietet.
Ich habe die App zum Beispiel nicht nur für statistische Analysen genutzt, sondern auch um Reports zu erstellen, PDFs und Bilder zu konvertieren und zu bearbeiten, Grafiken und Karten zu erstellen, Präsentationen zu erstellen und sogar kleine Apps zu bauen.
RStudio ist eine **integrierte Entwicklungsumgebung (IDE)** für R. Stell dir vor, R ist das Auto und RStudio ist das Armaturenbrett mit allen wichtigen Anzeigen und Bedienelementen. RStudio erleichtert die Arbeit mit R erheblich, indem es eine übersichtliche Benutzeroberfläche bereitstellt, die den Code-Editor, die Konsole, eine Anzeige für die erstellten Grafiken und eine Übersicht über die Variablen in deinem Arbeitsbereich in einem einzigen Fenster vereint. Es macht die Programmierung in R intuitiver und effizienter.
# Welche Ressourcen sind gut, um R für einfache Analysen mit sozialwissenschaftlichen Datensätzen zu lernen?
## Über RStudio selbst
- Oben: Help - Cheat Sheets
- `help(package = "tidyverse")`für Hilfe zu einem gesamten Paket (wie hier tidyverse)
- `help("select")`für eine bestimmte Funktion (wie hier select)
## Bücher
1. [Data insight foundations. Step-by-step data analysis with R](https://search.ub.tum.de/vufind/Record/ZDB-30-ORH-115924442?sid=19363896)
2. [Quantitative social science. An introduction in tidyverse](https://search.ub.tum.de/vufind/Record/DE-604.BV048304745?sid=19363920)
3. auf Deutsch: ["Statistik mit "R" für Nicht-Mathematiker. Praktische Tipps für die quantitativ-empirische Bachelor-, Master- und Doktorarbeit"](https://search.ub.tum.de/vufind/Record/DE-604.BV047271015?sid=19364010)
## Online-Ressourcen
Es gibt unüberschaubar viele Online-Ressourcen zu R. Einsteigerkurse, Anleitung zu bestimmten Themen und Online-Foren sind die ersten Anlaufstellen:
Hier eine kleine Auswahl:
1. [Eine Übersicht über alle möglichen R-Ressourcen](https://gkhajduk.github.io/R-resources/#general-resources)
2. [Wie man R und RStudio mit GitHub verbindet - und warum das so hilfreich ist](https://happygitwithr.com)
3. [Die Reddit-Seite Rstats](https://www.reddit.com/r/rstats/)
4. [Stackoverflow, ein Forum zu Hilfe bei Coding-Problemen und Fragen, u.a. zu R. Eine der Hauptquellen für KI-Training](https://stackoverflow.com/)
## KI
Die großen, bekannten LLMs eignen sich alle sehr gut, um Hilfe beim Code mit R zu geben. Sie wurden auf allen möglichen R-Dokumentationen und Büchern dazu trainiert und vereinen damit Wissen aus vielen verschiedenen Quellen.
### Vorteile von KI
Man kann so gut wie alles fragen: z.B. "Ich habe diesen hier reinkopierten Code bis jetzt. Was muss ich tun, um den Einfluss von x auf y zu untersuchen?" oder "Kannst du mir diesen hier reinkopierten R-Code Schritt für Schritt erkären?" oder "Ich habe diese Fehlermeldung bekommen. Was habe ich falsch gemacht?"
### Nachteile von KI
Da die KI auf jede mögliche Dokumentation trainiert wurde, gibt sie oft Code, der funktioniert, aber ein etwas komischer Mix aus verschiedenen Coding-Stilen ist - oft eine Mischung aus Base-R und tidy-R. Hier kann es z.B. helfen, das LLM direkt nach Code "im tidyverse-Stil" zu fragen. Für komplexe oder unklare Anweisungen kann die KI zudem Schwierigkeiten haben, den Kontext richtig zu interpretieren.
Ein letzter Nachteil ist, dass das Auswendiglernen des richtigen Codes eher langsamer verläuft, wenn man KI nutzt, als wenn man es immer selbst schreibt.
Oft kopiert man sich jedoch auch Code aus seinen eigenen vergangenen Skripten zusammen.
Wichtig ist, den Sinn hinter dem Code zu verstehen.
# R und Rstudio herunterladen
1. Diese Website besuchen: https://posit.co/download/rstudio-desktop/
2. Beide Buttons nacheinander anklicken, um R und RStudio zu installieren. Auf der Website wird normalerweise schon die für das eigene Betriebssystem richtige Version von RStudio angezeigt. ![[installation.png]]
# Einen Projektordner anlegen
An einem selbst gewählten Ort auf dem PC sollte nun ein neuer Ordner angelegt werden. In diesen Ordner sollten die folgenden Dateien vorhanden sein:
- `besserlesen_long.xlsx` und/oder `besserlesen_long.csv`. Dies ist der vollständige Datensatz mit Wellen 1 und 2
- `sf_w1_itemlabels.pdf` und `ef_itemlabels.pdf`: Die Itemlabels für die Schüler- und Elternfragebögen in Welle 1
- `sf_iv_w2_itemlabels.pdf` und `sf_kg_w2_itemlabels.pdf`: Die Itemlabels für Schülerfragebögen in den Interventions- sowie der Kontrollgruppe in Welle 2
- `levumi-raw.csv`Dieser Datensatz ist der aus Levumi exportierte Rohdatensatz, mit dam man genauere Untersuchungen nur zu den Lesetests anstellen kann (zum Beispiel für jedes Pseudowort einzeln)
# RStudio öffnen und ein neues Projekt anlegen
## Schritt 1: Oben links in den Einstellungen - New Project
![[new_project_1.png]]
## Schritt 2: Da wir schon einen Ordner erstellt haben - Exisiting Directory auswählen
![[new_project_2.png]]
## Schritt 3: Mit Browse den Ort auswählen, an dem sich unser Projektordner auf unserem PC befindet.
![[new_project_3.png]]
## Schritt 4: Wenn das geklappt hat, steht der Pfad zum Projektordner in der Zeile und für können auf Create Project klicken, um das R-Projekt zu erstellen
![[new_project_4.png]]
# R- Skript öffnen
![[open_r_script_1.png]]
Ein neues R-Skript lässt sich wieder links oben über das Optionsmenü öffnen.
R-Skripte sind aus mehreren Gründen vorteilhaft gegenüber Befehlen, die man einzeln per Menü oder in der Konsole eingibt.
## Reproduzierbarkeit
Der größte Vorteil ist die **Reproduzierbarkeit**. Ein Skript speichert alle deine Schritte in einer Datei. Wenn du später genau die gleiche Analyse noch einmal durchführen möchtest, musst du nur das Skript erneut ausführen. Bei der manuellen Eingabe in der Konsole kann es leicht passieren, dass du einen Schritt vergisst oder einen Wert falsch eintippst. R-Skripte garantieren, dass deine Ergebnisse immer die gleichen sind.
## Dokumentation und Organisation
Skripte dienen auch als Dokumentation deiner Arbeit. Du kannst Kommentare (`#`) hinzufügen, um zu erklären, warum du bestimmte Schritte durchgeführt hast. Das macht es viel einfacher für dich selbst oder für andere, die Logik deiner Analyse zu verstehen. Bei der Konsoleneingabe gibt es keine einfache Möglichkeit, deine Arbeit so zu organisieren und zu kommentieren.
## Effizienz
Für sich wiederholende Aufgaben sind Skripte viel effizienter. Anstatt zehnmal den gleichen Befehl mit leicht veränderten Parametern einzugeben, kannst du eine Schleife in einem Skript schreiben, die die Arbeit automatisch erledigt. Mit einem Skript kannst du auch lange, komplexe Analysen in einem Rutsch ausführen, anstatt jeden Befehl einzeln eingeben zu müssen.
# Erweiterungspakete für R laden
Erweiterungspakete (`packages`) sind der Grund, warum R so mächtig und vielseitig ist. Sie sind Sammlungen von zusätzlichen Funktionen, Daten und Dokumentationen, die von der R-Community entwickelt wurden. Stell dir R als ein Auto vor, das nur mit dem Nötigsten ausgestattet ist – es kann fahren, aber nicht viel mehr. Auch für die Analyse des BesserLesen-Datensatzes greifen wir auf einige Zusatzpakete zurück, darunter vor allem Pakete aus dem [[tidyverse]].
Mit dem folgenden Befehl lassen sich alle Pakete, die aus meiner Sicht relevant für die Analyse des BesserLesen-Datensatzes sind, installieren. Diese sollten einmalig nach der Installation von R installiert werden.
```
install.packages(c("readxl", "tidyverse", "flextable", "janitor", "labelled", "broom", "svglite", "psych"))
```
Nachdem du diesen Befehl also einmal ausgeführt hast, kann er wieder gelöscht werden.
Allerdings alle diese Zusatzpakete auch nach der Installation noch in jeder neuen R-Session einmal neu geladen werden. Dies ist mit den folgenden Befehlen möglich:
```
library(readxl)
library(tidyverse)
library(flextable)
library(janitor)
library(labelled)
library(broom)
library(svglite)
library(psych)
```
Diese sollten also zu Beginn eines R-Skripts stehen, wenn die Inhalte des Pakets genutzt werden.
# Der BesserLesen-Datensatz
Wir werden mit dem Datensatz **BesserLesen** arbeiten. Dies ist ein Längsschnitt-Datensatz mit fehlenden Werten, der vor der Analyse bereinigt werden muss. Er ist sowohl als Excel- als auch als CSV-Datei verfügbar. Es spielt keine Rolle, welche davon Sie für die Analyse mit RStudio verwenden.
# Laden des Datensatzes
## Die Funktion `read_csv()`
Mit der Funktion `read_csv()` kann man CSV-Dateien einlesen.
```
besserlesen <- read_csv("besserlesen.csv")
```
`read_csv()` erkennt die Spaltentypen (z.B. Zahl, Zeichenkette, Datum) automatisch und gibt eine hilfreiche Nachricht darüber aus, was es gefunden hat. Dies hilft Ihnen, schnell zu bestätigen, dass die Daten wie erwartet geladen wurden.
## Laden einer Excel-Datei mit `readxl`
`read_excel()` ähnelt `read_csv()`, hat aber ein zusätzliches Argument: `sheet`. Das ist wichtig, da eine Excel-Datei mehrere Arbeitsblätter enthalten kann. Der besserlesen-Datenssatz ist im Sheet1 abgespeichert.
```
excel_data <- read_excel("besserlesen.xlsx", sheet = "Sheet1")
```
# Ein erster Blick in den Datensatz mit glimpse()
`glimpse()` ist ein Befehl aus dem `dplyr` Paket, der dir einen schnellen Überblick über deine Daten gibt.
Der Befehl zeigt dir die Struktur deines Datensatzes in einem kompakten, menschenlesbaren Format an. Das Coole daran ist, dass `glimpse()` die Informationen vertikal anordnet. Das ist besonders praktisch, wenn dein Datensatz viele Spalten hat, da du die Namen, Datentypen und die ersten paar Werte jeder Spalte auf einen Blick sehen kannst, ohne horizontal scrollen zu müssen.
**Glimpse zeigt dir folgendes an:**
- **`Rows`** und **`Columns`**: Die Anzahl der Zeilen und Spalten deines Datensatzes.
- **Spaltennamen**: Die Namen aller Spalten.
- **Datentypen**: Den [[Datentypen|Datentyp]] jeder Spalte, wie `int` (ganze Zahl), `chr` (Text) oder `dbl` (Dezimalzahl).
- **Erste Werte**: Eine kurze Vorschau der ersten Werte in jeder Spalte.
Eine Variable, die im Output ins Auge springen sollte, ist die erstgenannte Variable, `ID`. Diese Variable ist in wissenschaftlichen Datensätzen oft die wichtigste Variable, da sie dazu dient, Beobachtungen zu organisieren. Daher wollen wir sie uns im Folgenden genauer anschauen.
# Was hat es mit den IDs auf sich?
## Wie viele einizgarte IDs gibt es in dem Datensatz?
Um diese Frage zu beantworten, nutzen wir den folgenden Befehl:
```
num_distinct_ids <- n_distinct(besserlesen$ID)
```
Er zählt, wie viele verschiedene oder **eindeutige** Werte in einer Spalte vorhanden sind.
### Aufbau des Befehls
Der Befehl ist in drei Teile unterteilt:
1. **`n_distinct()`**: Dies ist die Funktion, die die Zählung durchführt. Sie stammt aus dem `dplyr`-Paket und ist darauf spezialisiert, effizient die Anzahl der eindeutigen Werte zu zählen.
2. **`besserlesen$ID`**: Dies ist das Argument, das der Funktion sagt, **was** sie zählen soll.
- `besserlesen` ist der Name deines Datensatzes.
- Der **`
-Operator** wird verwendet, um eine bestimmte Spalte innerhalb des Datensatzes auszuwählen.
- `ID` ist der Name der Spalte, in der die eindeutigen Werte gezählt werden sollen.
3. **`<-` und `num_distinct_ids`**: Das `<-`-Zeichen ist der **Zuweisungsoperator**. Es nimmt das Ergebnis der Funktion `n_distinct()` und speichert es in einer neuen Variable namens `num_distinct_ids`.
### Ergebnis in der Konsole auszugeben
Um das in der Variable gespeicherte Ergebnis auszugeben, braucht man nun nur noch den Befehl:
```
print(num_distinct_ids)
```
## Wie oft kommen die IDs im Datensatz vor?
```
id_counts <- besserlesen |>
count(ID) |>
count(n, name = "Anzahl_der_IDs")
print(id_counts)
```
Der Code zählt, wie oft jede eindeutige **ID** in unserem Datensatz `besserlesen` vorkommt. Das Ergebnis wird dann in einem neuen Datensatz namens `id_counts` gespeichert.
- `besserlesen |> count(ID)`: Zuerst wird der Datensatz `besserlesen` genommen. Der `|>`-Operator (genannt "Pipe-Operator") leitet das Ergebnis an den nächsten Befehl weiter.
- Der `count(ID)`-Befehl zählt, wie oft jeder eindeutige Wert in der Spalte **ID** vorkommt. Das Ergebnis ist eine Tabelle mit zwei Spalten: `ID` und `n`, wobei `n` die Anzahl der Vorkommen ist.
- `count(n, name = "Anzahl_der_IDs")`: Der Befehl zählt erneut. Diesmal zählt er die Werte in der `n`-Spalte aus dem vorherigen Schritt. Mit dem Zusatz `name = "Anzahl_der_IDs"` benennst du die neue Zählspalte um.
Der Befehl zählt also nicht nur, wie oft jede ID vorkommt, sondern auch, wie oft eine bestimmte Anzahl von IDs vorkommt. Wenn zum Beispiel fünf IDs jeweils dreimal vorkommen, dann wird in der Spalte `Anzahl_der_IDs` der Wert 5 stehen, und in der Spalte `n` der Wert 3.
Das Endergebnis wird der Variable `id_counts` zugewiesen, die du mit dem `print()`-Befehl anzeigen lassen kannst.
## Was unterscheidet zwei Beobachtungen derselben ID voneinander?
Mit Hintergrundwissen zum BesserLesen-Projekt liegt die Antwort auf diese Frage nahe, denn wir wissen, dass wir bis jetzt zwei Testungen pro Schulkind gemacht haben und dass wir diesen Kindern IDs aus Levumi zugewiesen haben. Doch auch ohne Kenntniss über den BesserLesen-Datensatz können wir herausfinden, was zwei Beobachtungen derselben ID voneinander unterscheidet. Dazu finden wir zunächst heraus, welche Variablen für beide Beobachtungen konstant, also gleich sind:
```
constant_variables <- besserlesen |>
group_by(ID) |>
summarise(
across(everything(), ~ n_distinct(.) == 1)
) |>
select_if(all) |>
names()
print(constant_variables)
```
[[Konstante Variablen|Hier]] findest du eine genaue Erklärung der Befehlkette.
Nun müssen wir nur noch das Gegenteil herausfinden, also welche Variablen nicht konstant sind:
```
all_variables <- names(besserlesen)
changing_variables <- setdiff(all_variables, constant_variables)
print(changing_variables)
```
1. **`all_variables <- names(besserlesen)`**
- **`names(besserlesen)`**: Diese Funktion nimmt den Datenrahmen **`besserlesen`** und gibt einen Vektor mit den Namen aller Spalten (Variablen) zurück.
- **`all_variables <- ...`**: Dieser Vektor mit allen Variablennamen wird dann der neuen Variable **`all_variables`** zugewiesen.
2. **`changing_variables <- setdiff(all_variables, constant_variables)`**
- **`setdiff(x, y)`**: Dies ist eine Mengenfunktion in R. Sie findet alle Elemente, die in Vektor `x` enthalten sind, aber nicht in Vektor `y`.
- In diesem Fall vergleicht die Funktion den Vektor **`all_variables`** (alle Variablennamen) mit dem Vektor **`constant_variables`** (die Variablennamen, die du im vorherigen Schritt als konstant identifiziert hast).
- **`changing_variables <- ...`**: Das Ergebnis – also die Namen der Variablen, die in `all_variables` sind, aber nicht in `constant_variables` – wird der neuen Variable **`changing_variables`** zugewiesen.
3. **`print(changing_variables)`**
- Dieser Befehl gibt den Inhalt des Vektors **`changing_variables`** auf der Konsole aus, sodass du die Namen der nicht-konstanten Variablen sehen kannst.
Wir wissen also nun, welche Variablen im besserlesen-Datensatz konstant sind und welche nicht.
## Wie bringen wir die IDs in eine logische Reihenfolge?
Datensätze mit mehreren IDs sind nicht immer unbedingt Längsschnittdatensätze! Ein Längsschnittdatensatz verfolgt dieselben Individuen (oder IDs) mehrmals zu **verschiedenen Zeitpunkten**, um Veränderungen oder Entwicklungen im Zeitverlauf zu untersuchen.
Ein Datensatz kann allerdings auch mehrere Beobachtungen pro ID enthalten, ohne dass es einen Zeitbezug gibt. Beispielsweise könnte man die Ergebnisse von mehreren Tests für dieselbe Person (ID) in einer einzigen Tabelle zusammenfassen, ohne dass diese Tests zu unterschiedlichen Zeitpunkten stattgefunden haben. In solchen Fällen spricht man nicht von Längsschnittdaten.
Um zu wissen, wie wir die IDs in eine logische Reihenfolge bringen können, müssen wir also auf Hintergrundwissen zum BesserLesen-Datensatz zurückgreifen. Wir wissen, dass das BesserLesen-Projekt eine Längsschnittstudie ist und können daher vermuten, dass es sich bei dem vorliegenden Datensatz um Längsschnittdaten handelt. Ein guter Anhaltspunkt, um dies zu überprüfen, ist die Suche nach einer Variablen, die ein **Datum** enthält.
Die Arbeit mit zeitbezogenen Variablen ist in der Data Science dafür bekannt, eher kompliziert zu sein. Das liegt daran, dass sie in Rohdatensatz häufig in ganz unterschiedlichen Formatierungen vorhanden sind.
R bietet eine breite Palette an Funktionen und Paketen, die das **"Parsen"** (also das Auslesen und Interpretieren) dieser Variablen erheblich vereinfachen.
- **Standardfunktionen**: R verfügt über eingebaute Funktionen wie **`as.Date()`** und **`as.POSIXct()`**, die in der Lage sind, eine Vielzahl von Datums- und Zeitformaten automatisch zu erkennen und in ein standardisiertes Format zu konvertieren. Dadurch können die Daten einfach verglichen, sortiert und für Analysen genutzt werden.
- **Spezialisierte Pakete**: Für komplexere Fälle gibt es spezialisierte Pakete wie **`lubridate`**. Dieses Paket macht die Arbeit mit Datums- und Zeitvariablen geradezu intuitiv. Anstatt sich mit kryptischen Formatcodes (`%d`, `%m`, `%Y`) herumzuschlagen, können Sie mit `lubridate` Funktionen wie **`dmy()`** (Tag-Monat-Jahr) oder **`ymd_hms()`** (Jahr-Monat-Tag-Stunde-Minute-Sekunde) verwenden, um unstrukturierte Strings mühelos in das korrekte Zeitformat zu überführen. Das beschleunigt den Datenbereinigungsprozess massiv und verringert das Fehlerrisiko.
Die Möglichkeit, Datums- und Zeitdaten zuverlässig zu standardisieren, ist die Grundlage dafür, dass wir die Beobachtungen für jede ID in der richtigen chronologischen Reihenfolge analysieren und so die tatsächlichen Veränderungen über die Zeit hinweg untersuchen können.
Wir werden uns also nun auf die Suche nach einer Variablen im besserlesen-Datensatz machen, die das Datum der einzelnen Beobachtungen angibt. Eine Möglichkeit wäre, innerhalb der Variablen, die uns der Befehl `print(changing_variables)`angezeigt hast, nach einer Variable mit dem Namen "Datum" oder einer ähnlich benannten Variable zu suchen.
Eine andere Möglichkeit wäre, nach den [[Datentypen]] `dttm` oder `date`zu suchen, die in R zur Angabe von zeitbezogenen Variablen genutzt werden. Dies können wir mit der folgenden Befehlskette bewerkstelligen:
```
is_date <- function(x) {is.Date(x) | is.POSIXct(x)}
besserlesen_date <- besserlesen |>
select(where(is_date)) |>
names()
print(besserlesen_date)
```
Hier ist eine kurze Aufschlüsselung der einzelnen Schritte:
- `is_date <- function(x) {is.Date(x) | is.POSIXct(x)}`: Diese Zeile definiert eine neue Funktion namens `is_date`. Sie prüft, ob ein übergebenes Objekt entweder vom Typ `Date` oder `POSIXct` ist, also ein Datums- oder Zeitformat hat.
- `besserlesen_date <- besserlesen |> select(where(is_date)) |> names()`: Dies ist der Hauptteil der Kette.
- `besserlesen |>`: Der Pipe-Operator (`|>`) leitet den Datensatz **besserlesen** an den nächsten Befehl weiter.
- `select(where(is_date))`: Dieser Befehl wählt aus dem Datensatz **besserlesen** nur die Spalten aus, für die die zuvor definierte Funktion `is_date` den Wert `TRUE` zurückgibt.
- `names()`: Dieser Befehl extrahiert die **Namen** der ausgewählten Spalten.
- `print(besserlesen_date)`: Dieser Befehl gibt die Namen der Datums- und Zeitspalten in der Konsole aus.
Wir wissen nun, dass mit `Testdatum`eine Variable vorhanden ist, mit der wir unsere Beobachtungen potenziell in eine logische Reihenfolge bringen können.
Wir können nun als nächsten Schritt schauen, welches der maximale und welches der minimale Wert für die Variable Testdatum ist. Außerdem können wir uns anzeigen lassen, wie viele verschiedene Ausprägungen es für Testdatum gibt:
```
besserlesen_testdate_minmax_n <- besserlesen |>
summarise(
mindate = min(Testdatum, na.rm = TRUE),
maxdate = max(Testdatum, na.rm = TRUE),
n_obs = n_distinct(Testdatum)
)
print(besserlesen_testdate_minmax_n)
```
1. `besserlesen |>`: Dies ist die Pipe-Operator (`|>`) in R, die den Datenrahmen `besserlesen` als Eingabe für den nächsten Schritt verwendet.
2. `summarise(...)`: Diese Funktion fasst die Daten zusammen, indem sie neue Variablen erstellt.
- `mindate = min(Testdatum, na.rm = TRUE)`: Erstellt eine Variable namens `mindate`, die das **minimale**(früheste) Datum in der Spalte `Testdatum` speichert. `na.rm = TRUE` stellt sicher, dass fehlende Werte ignoriert werden.
- `maxdate = max(Testdatum, na.rm = TRUE)`: Erstellt eine Variable namens `maxdate`, die das **maximale**(späteste) Datum in der Spalte `Testdatum` speichert.
- `n_obs = n_distinct(Testdatum)`: Erstellt eine Variable namens `n_obs`, die die **Anzahl der eindeutigen**(unique) Daten in der Spalte `Testdatum` zählt.
3. `print(besserlesen_testdate_minmax_n)`: Gibt das Ergebnis der Zusammenfassung aus.
Zusammenfassend lässt sich sagen, dass der Code das **Anfangs- und Enddatum** sowie die **Anzahl der der eindeutigen Daten** aus den Daten ermittelt.
Da die Anzahl der eindeutigen Daten überschaubar ist, können wir nun die Anzahl der Beobachtungen für jedes Testdatum anzeigen, zusammen mit der Anzahl der einzigartigen IDs pro Testdatum.
```
obs_by_testdate <- besserlesen |>
group_by(Testdatum) |>
summarise(
n_obs = n(),
distinct_ids = n_distinct(ID)
) |>
ungroup()
print(obs_by_testdate)
```
- `besserlesen |>`: Der Code startet mit dem `besserlesen` Datensatz. Der Pipe-Operator (`|>`) leitet die Daten zum nächsten Schritt weiter.
- `group_by(Testdatum)`: Dieser Befehl **gruppiert** die Daten nach dem `Testdatum`. Alle nachfolgenden Berechnungen werden separat für jedes einzelne Datum durchgeführt.
- `summarise(...)`: Innerhalb jeder Datums-Gruppe werden zusammenfassende Statistiken berechnet:
- `n_obs = n()`: Zählt die **Anzahl der Zeilen** (Beobachtungen) für das jeweilige Datum.
- `distinct_ids = n_distinct(ID)`: Zählt die **Anzahl der einzigartigen IDs** (Teilnehmer) für das jeweilige Datum.
- `ungroup()`: Dieser Befehl hebt die Gruppierung wieder auf. Das ist eine gute Praxis, um sicherzustellen, dass spätere Operationen nicht fälschlicherweise auf den Gruppen ausgeführt werden.
- `print(obs_by_testdate)`: Gibt die erstellte Tabelle mit den berechneten Werten für jedes Testdatum aus.
Im Output sehen wir, dass die Anzahl der einigartigen IDs bei jedem Testdatum genauso hoch ist wie die Anzahl der Beobachtungen. Das heißt, dass kein Kind an einem Tag doppelt getestet wurde. Außerdem sehen wir, dass sich die Testdaten grob in zwei Gruppen aufteilen lassen, zwischen denen circa 8 Monate liegen.
Um das Bild zu vervollständigen, können wir nun schauen, wie viel Zeit für jede ID zwischen den beiden Testdaten liegt:
```
besserlesen <- besserlesen |>
group_by(ID) |>
arrange(Testdatum) |>
mutate(
time_between_obs = difftime(Testdatum, lag(Testdatum), units = "days")
) |>
ungroup()
tabyl(besserlesen$time_between_obs)
```
Wir sehen, dass es keine großen Unterschiede darin gibt, wie viel Zeit zwischen den beiden Beobachtungen von Testdatum für jede ID liegt. Im Zusammenhang mit dem vorherigen Code können wir außerdem mit Sicherheit sagen, dass jede ID einmal im November 2024 und einmal im Juli 2025 getestet wurde.
Um die folgenden Analysen zu vereinfachen, können wir die Testdaten zu zwei "Erhebungswellen" zusammenfassen: ^k739ha
```
besserlesen <- besserlesen |>
mutate(
wave = case_when(
Testdatum >= as.Date("2024-11-01") & Testdatum <= as.Date("2024-11-30") ~ 1,
Testdatum >= as.Date("2025-07-01") & Testdatum <= as.Date("2025-07-31") ~ 2,
TRUE ~ NA_real_
)
)
```
# Fehlende Daten verstehen
## Panel Verluste
Aus dem vorherigen Abschnitt wissen wir noch, dass manche IDs zwei mal vorkommen, während einige nur einmal vorkommen. Wir haben außerdem die Variable `Testdatum`in zwei Erhebungswellen aufgeteilt.
Wir können nun untersuchen, in welcher "Erhebungswelle" diejenigen IDs, die nur eine Beobachtung haben, gefehlt haben:
```
wave1_ids <- besserlesen |>
filter(wave == 1) |>
pull(ID) |>
unique()
wave2_ids <- besserlesen |>
filter(wave == 2) |>
pull(ID) |>
unique()
missing_in_wave1 <- setdiff(wave2_ids, wave1_ids)
missing_in_wave2 <- setdiff(wave1_ids, wave2_ids)
missing_ids_alt <- tibble(
ID = c(missing_in_wave1, missing_in_wave2),
wave = c(rep(1, length(missing_in_wave1)), rep(2, length(missing_in_wave2)))
)
print(missing_ids_alt, n=Inf)
```
Nun wissen wir, wie viele Kinder in der zweiten Erhebungswelle nicht mitgemacht haben. Je nach Stärke der sogenannten Panel Attrition könnten unterschiedliche Strategien notwendig sein, vor allem wenn die Wahrscheinlichkeit dafür, in der zweiten Erhebungswelle nicht anwesen zu sein, mit Variablen zusammenhängt, die auch mit unserer Forschungsfrage zusammenhängen. Ein möglicher Zusammenhang wäre:
Kinder aus bildungsfernen Haushalten fehlen häufiger in der Schule -> gleichzeitig entwickelt sich ihre Leseleistung langsamer -> Daher überschätzen wir aufgrund von Panel Attrition die Entwicklung der Leseleistung
Da in unserem Datensatz allerdings nur ein paar Kinder in der zweiten Erhebungswelle gefehlt haben, verzichte ich in dieser Einführung auf tiefergehende Analysen zur Panel Attrition und konzentriere mich im Folgenden auf item missingness.
## Item Missingness
Um eine schnelle Übersicht über die Anzahl der fehlenden Werte pro Item zu bekommen, ist dieser Code sehr hilfreich:
```
missingvalues <- besserlesen |>
summarise(across(everything(), ~ sum(is.na(.)))) |>
pivot_longer(
cols = everything(),
names_to = "variable",
values_to = "missing_count"
)
print(missingvalues, n = Inf)
```
Dieser zeigt allerdings weder die Ursache noch die Folgen von fehlenden Werten an.
# Labeling Variables
Das Labelling in R, also das Zuweisen von Labels (Etiketten) zu Variablen und ihren Werten, ist aus [[Gründe für das Labeln in R|mehreren Gründen]] wichtig, vor allem im Kontext der **Datenanalyse und Kommunikation**.
In unserem Datensatz gibt es einige Variablen, bei der die Schulkinder oder die Eltern ihre Zustimmung auf einer vierstufigen [Lickert-Skala](https://datatab.de/tutorial/likert-skala) angeben sollten. Damit wir den Code, um eine Variable zu belabeln, nicht für jede Variable neu schreiben müssen, hilft hier wieder eine Funktion:
```
apply_my_labels <- function(data, ...) {
labels <- c(
"stimme überhaupt nicht zu" = 1,
"stimme wenig zu" = 2,
"stimme einigermaßen zu" = 3,
"stimme stark zu" = 4
)
data <- data %>%
mutate(
across(
c(...),
~ set_value_labels(., labels)
)
)
return(data)
}
```
Diese Funktion wenden wir in dem folgenden Beispiel auf zwei Variablen an:
```
besserlesen <- apply_my_labels(
besserlesen,
lesen_unterhaltung,
lesen_geschenk
)
```
# Beispiel-Forschungsfragen
## Weisen Kinder aus Haushalten mit geringer Bücheranzahl eine niedrigere Lesekompetenz auf als Kinder aus Haushalten mit hoher Bücheranzahl?
Oberflächlich geht es hier also erstmal nur um zwei Variablen: Die Anzahl der Bücher im Haushalt und die Lesekompetenz - Ein Blick in die Datendokumentation (Itemlabels) zeigt, dass die Anzahl der Bücher nur durch die Elternfragebögen zu Beginn der Erhebung (Welle 1) erhoben wurde, während die Lesekompetenz zu zwei Punkten erhoben wurde. Da die Forschungsfrage eine querschnittliche Untersuchung nahelegt, macht es hier Sinn, den Messzeitpunkt der Lesekompetenz zu nehmen, der zeitlich am nächsten an der Erhebung der Anzahl der Bücher liegt.
Um unseren Längsschnittdatensatz zu einem Querschnittsdatensatz zu machen, können wir auf unsere Variable `wave`, die wir [[Einführung in den BesserLesen-Datensatz mit R#^k739ha|hier]] erstellt haben, zurückgreifen.
```
besserlesen_wave1 <- besserlesen |>
filter(wave == 1)
```
Dann müssen wir die beiden relevanten Variablen `anzahl_bücher`und `total_correct_words` raussuchen und genauer anschauen.
Da `anzahl_bücher` eine kategoriale Variable ist, können wir uns sie wieder mit `tabyl`anzeigen lassen:
```
tabyl_anzahl_bücher <- besserlesen_wave1 |>
tabyl(anzahl_bücher)
print(tabyl_anzahl_bücher)
```
Dann können wir die Variable `anzahl_bücher` in einem nächsten Schritt und mit Blick in die Itemlabels wieder labeln:
```
besserlesen_wave1$anzahl_bücher <- labelled(besserlesen_wave1$anzahl_bücher,
labels = c(
"Keine oder nur sehr wenige (0 – 10 Bücher)" = 1,
"Genug, um ein Regalbrett zu füllen (11 – 25 Bücher)" = 2,
"Genug, um ein Regal zu füllen (26 – 100 Bücher)" = 3,
"Genug, um zwei Regale zu füllen (101 – 200 Bücher)" = 4,
"Genug, um drei oder mehr Regale zu füllen (über 200)" = 5
))
```
Diese Labels können wir nun zum Beispiel mit `tabyl`wieder nutzen. Dazu müssen wir die Variable `anzahl_bücher`, die R bis jetzt als numerische Variable abgespeichert hat, in eine `factor`, also in einer kategoriale Variable, umwandeln:
```
besserlesen_wave1$anzahl_bücher_factor <- as_factor(besserlesen_wave1$anzahl_bücher)
```
Dann wieder in alter Methode anzeigen lassen:
```
tabyl_anzahl_bücher_factor <- besserlesen_wave1 |>
tabyl(anzahl_bücher_factor)
print(tabyl_anzahl_bücher_factor)
```
Da `total_correct_words`eine kontinuerliche Variable ist, brauchen wir für sie eine andere Methode für einen schnellen Überblick:
```
besserlesen_wave1 |>
summarise(
Mittelwert = mean(total_correct_words, na.rm = TRUE),
Median = median(total_correct_words, na.rm = TRUE),
Standardabweichung = sd(total_correct_words, na.rm = TRUE),
Minimum = min(total_correct_words, na.rm = TRUE),
Maximum = max(total_correct_words, na.rm = TRUE),
Anzahl_NA = sum(is.na(total_correct_words))
)
```
Wir wissen jetzt, dass es keinen statistisch signifikanten Zusammenhang zwischen der Anzahl der Bücher und der Leistung im Lesetest in Welle 1 in unserem Datensatz gibt.
## Besteht zwischen dem Leseselbstkonzept und der Lesekompetenz ein positiver Zusammenhang?
Dazu müssen wir zunächst eine Leseselbstkonzept-Skala erstellen. Hier ist wieder Wissen über die Daten notwendig (zum Beispiel, dass wir eine aus IGLU adaptierte Leseselbstkonzept-Skala benutzen). Darüber hinaus müssen wir uns wieder entscheiden, welchen Zeitpunkt wir als Grundlage nehmen. Da auch die Frage eher querschnittlich ist, können wir hier der Einfachheit halber bei unserem Datensatz `besserlesen_wave1`bleiben.
Als ersten Schritt müssen wir prüfen, ob die verschiedenen Items der Skala in dieselbe Richtung zeigen (in diesem Fall also: Höherer Wert = höheres Leseselbstkonzept). Ein Blick in die Itemlabels zeigt, dass dies bei dem Item vor_klasse_lesen nicht der Fall ist.
```
besserlesen_wave1$vor_klasse_lesen <- 5 - besserlesen_wave1$vor_klasse_lesen
```
## Weisen Kinder, deren Eltern über einen niedrigen Bildungsabschluss verfügen, ein schwächer ausgeprägtes Leseselbstkonzept auf als Kinder mit höher gebildeten Eltern?
Hierzu können wir die Variablen `abschluss_vater`und `abschluss_mutter`oder die Variablen
`bildung_vater`und `bildung_mutter`heranziehen. In beiden Fällen müssen wir aus zwei kategorialen Variablen nun eine Variable erstellen. Hier ein Beispiel, wie man eine kategoriale Variable mit den Ausprägungen `Beide Elternteile haben Abitur`, `Ein Elternteil hat Abitur`und `Kein Elternteil hat Abitur`erstellt:
```
besserlesen_wave1 <- besserlesen_wave1 |>
mutate(
bildung_mutter = na_if(bildung_mutter, 11),
bildung_vater = na_if(bildung_vater, 11)
) |>
mutate(
abitur_status = case_when(
bildung_mutter == 1 & bildung_vater == 1 ~ "Beide Elternteile haben Abitur",
bildung_mutter == 1 | bildung_vater == 1 ~ "Ein Elternteil hat Abitur",
TRUE ~ "Kein Elternteil hat Abitur"
)
)
```
Diese Variable können wir als kategoriale Variable (wie vorhin die Anzahl der Bücher) wieder in ein lineares Regressionsmodell aufnehmen:
```
modell_abitur <- lm(total_correct_words ~ abitur_status, data = besserlesen_wave1)
summary(modell_abitur)
```
# Mögliche nächste Schritte mit R
1. Versionierung mit Git und Github
2. Grafiken erstellen
3. Reproduzierbarer Code mit renv