3  Programmiersprache R

3.1 Worum es geht

In Kap. 1 hast du RStudio eingerichtet, in Kap. 2 dein erstes Quarto-Dokument gebaut. Jetzt geht es an die Sprache, in der du in diesem Buch alles formulierst: R. Variablen, Funktionen, Datentypen und der Pipe-Operator sind die Bausteine, mit denen du in den folgenden Kapiteln Datensätze einliest, transformierst, analysierst und visualisierst.

Anders als die SPSS-Syntax — die im Kern eine Befehlssprache mit festem Vokabular ist — schreibst du in R selbst Funktionen, baust Werte zu Vektoren, kombinierst sie zu Datensätzen. Diese kleine Hürde am Anfang zahlt sich später aus: dieselben Bausteine tragen dich vom Einstieg bis zur Bachelorarbeit.

Hinweis

Forschungsjournal: Wo stehst du in deiner Mini-Studie? Du hast in Kapitel 1 die IDE eingerichtet und in Kapitel 2 den Berichts-Workflow verstanden. Jetzt lernst du die Sprachelemente, mit denen du gleich Survey-Items rekodierst, Skalen baust und Modelle rechnest. Beispiele in diesem Kapitel sind bewusst sozial- und politikwissenschaftlich gewählt — du wirst dieselben Operationen ab Kapitel 4 auf echte ALLBUS-Variablen anwenden.

Was du nach diesem Kapitel kannst

  • Variablen anlegen, benennen und Werte zuweisen.
  • Code mit #-Kommentaren für dein zukünftiges Ich verständlich halten.
  • Funktionen mit Argumenten aufrufen und verstehen, was sie zurückgeben.
  • Arithmetische, Vergleichs- und Logik-Operatoren (==, &, %in%, …) einsetzen.
  • Die wichtigsten R-Datentypen unterscheiden: Vektor, Faktor, Datensatz (Tibble), Liste.
  • Mit fehlenden Werten (NA) umgehen und sie aus Berechnungen ausschließen.
  • Den nativen Pipe-Operator |> lesen und schreiben.
  • Den Unterschied zwischen |> und dem älteren %>% aus magrittr einordnen.
  • Code mit Pipes lesbar und linear strukturieren.

3.2 Variablen definieren

Eine Variable speichert einen Wert. In R weist du Werte über den Zuweisungspfeil <- zu:

var1 <- 2 * 4
var1
[1] 8

var1 (links) ist der Variablenname; 2 * 4 (rechts) der Ausdruck, dessen Ergebnis (8) gespeichert wird.

Wenn du die Zuweisung in runde Klammern setzt, wird das Ergebnis gleichzeitig in der Konsole angezeigt:

(var2 <- 2 * 4)
[1] 8
Warnung

Vorsicht: <- statt =. Aus anderen Programmiersprachen kennst du = als Zuweisungsoperator. R akzeptiert es in vielen Kontexten, der idiomatische Operator ist aber <-. Grund: = wird in R innerhalb von Funktionsaufrufen für Argumente verwendet (mean(x = data)). Wenn du <- für Zuweisung und = für Argumente strikt trennst, vermeidest du subtile Fehler.

Tastenkürzel für <-: option + - (macOS) bzw. alt + - (Windows/Linux).

3.2.1 Variablennamen

Eine Variable muss mit einem Buchstaben beginnen und darf danach Buchstaben, Zahlen, Punkte (.) und Unterstriche (_) enthalten. Leerzeichen sind nicht erlaubt.

Für lesbaren Code halten sich Sozialwissenschaftler:innen meist an eine von zwei Konventionen:

  1. snake_case — alles klein, Worte mit Unterstrich verbunden: mittelwert_alter. Tidyverse-Standard.
  2. camelCase — erstes Wort klein, weitere groß beginnend: mittelwertAlter. Verbreitet im Stata- und JavaScript-Umfeld.

Wähle eine und bleibe konsequent. In diesem Buch nutzen wir snake_case.

# Gut
mittelwert_alter
faktor_skala
islamophobie_skala

# Schlecht (inkonsistent)
Mittelwert.alter
mittelwertAlter_neu

# Verboten (Leerzeichen)
mittelwert alter

3.2.2 Kommentare

Alles, was hinter einem # steht, wird von R ignoriert — du nutzt das, um deinen Code zu erklären:

# Stichprobengröße der Mini-Studie
n_befragte <- 5246      # ALLBUS 2023, gesamt

Kommentare sind das, was deinem zukünftigen Ich (oder Reviewer:innen) den Code in drei Wochen wieder verständlich macht. Schreibe sie für die Frage Warum? — nicht für das, was der Code ohnehin schon sagt.

3.3 Funktionen aufrufen

„Everything that exists is an object. Everything that happens is a function call.” — John Chambers

Eine Funktion ist ein benanntes Werkzeug, dem du Argumente übergibst und das ein Ergebnis zurückgibt. Eine generische Funktion sieht so aus:

funktionsname(arg1 = wert1, arg2 = wert2, ...)
  • funktionsname — was die Funktion macht.
  • arg1, arg2 — wie sie ihre Arbeit konfiguriert.
  • Werte können explizit benannt (arg1 = wert1) oder positional übergeben werden.

Einige Argumente haben Default-Werte: sie müssen nicht angegeben werden, weil der häufigste Wert voreingestellt ist.

seq(from = 1, to = 10, by = 2)   # explizit
[1] 1 3 5 7 9
seq(1, 10, 2)                    # positional, identisch
[1] 1 3 5 7 9
Tipp

Tipp: ?funktion. Wenn du die Argumente einer Funktion vergessen hast, gibt ?funktion (z. B. ?seq) die Hilfe in RStudio aus. Innerhalb der Klammern öffnet Tab ein Dropdown mit allen Argumenten.

3.3.1 Grundfunktionen für den Einstieg

Funktion Wirkung
c(...) erzeugt einen Vektor aus den Argumenten (combine)
seq(from, to, by) erzeugt eine Sequenz
1:5 reguläre Sequenz (1, 2, 3, 4, 5)
rep(x, times, each) wiederholt x
head(x, n) die ersten n Elemente
tail(x, n) die letzten n Elemente
length(x) Anzahl der Elemente
round(x, digits) rundet Zahlen
zahlen <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
head(zahlen, n = 3)
[1] 1 2 3
seq(from = 1, to = 10, by = 2)
[1] 1 3 5 7 9
rep(x = 0, times = 5)
[1] 0 0 0 0 0
round(c(1.222, 2.345, 3.987), digits = 1)
[1] 1.2 2.3 4.0

3.4 Operatoren

Drei Gruppen von Operatoren brauchst du immer wieder.

Arithmetisch.

Operator Wirkung
+ Addition
- Subtraktion
* Multiplikation
/ Division
^ Potenzieren
%% Modulo (Rest)

Vergleich. Geben einen logischen Wert TRUE/FALSE zurück.

Operator Wirkung
== gleich
!= ungleich
<, <= kleiner (gleich)
>, >= größer (gleich)
%in% enthalten in Menge
Warnung

Vorsicht: == ist nicht =. Für Vergleiche verwendest du immer == (doppeltes Gleichheitszeichen). Einzelnes = ist für Funktionsargumente reserviert — mean(x = data) setzt x, vergleicht aber nichts.

Logisch. Verknüpfen Wahrheitswerte.

Operator Wirkung
& UND
| ODER
! NICHT

Ein kleines Beispiel:

alter <- c(22, 45, 67, 18, 33)
alter > 30
[1] FALSE  TRUE  TRUE FALSE  TRUE
alter >= 18 & alter <= 65
[1]  TRUE  TRUE FALSE  TRUE  TRUE
alter %in% c(22, 33, 44)
[1]  TRUE FALSE FALSE FALSE  TRUE

Diese Operatoren wirst du in Kap. 4 ständig brauchen, wenn du Fälle mit filter() auswählst.

3.5 Objekte und Datentypen

In R ist alles ein Objekt — ein Wert, ein Datensatz, ein statistisches Modell, sogar eine Funktion. Die wichtigsten Datentypen für sozialwissenschaftliche Arbeit:

3.5.1 Vektoren

Ein Vektor ist eine eindimensionale Sammlung gleicher Datentypen. Drei Typen wirst du am häufigsten sehen:

zahlen <- c(1, 2, 3, 4, 5)              # numeric
namen  <- c("Anna", "Boris", "Carla")   # character
flags  <- c(TRUE, FALSE, NA)            # logical
Warnung

Vorsicht: TRUE/FALSE schreiben, nicht T/F. Die Kurzschreibweisen funktionieren — sind aber überschreibbar, deshalb in der Praxis fehleranfällig. Schreibe immer TRUE und FALSE aus.

Mit Indizes greifst du auf einzelne Elemente zu:

zahlen[1]      # erstes Element
[1] 1
zahlen[2:4]    # zweites bis viertes
[1] 2 3 4
zahlen[-1]     # alle ausser dem ersten
[1] 2 3 4 5

3.5.2 Faktoren

Ein Faktor ist ein Vektor mit definierten Ausprägungen (levels) — für kategoriale Variablen wie Geschlecht oder Bildung.

geschlecht <- factor(c("Mann", "Frau", "Mann", "Divers"),
                     levels = c("Mann", "Frau", "Divers"))
geschlecht
[1] Mann   Frau   Mann   Divers
Levels: Mann Frau Divers
levels(geschlecht)
[1] "Mann"   "Frau"   "Divers"

Faktoren sind wichtig für Regressionsmodelle — die erste Ausprägung wird automatisch zur Referenzkategorie. Mit relevel() (Base R) oder forcats::fct_relevel() (tidyverse) änderst du die Reihenfolge.

Hinweis

Hintergrund: haven_labelled vs. Faktor. Wenn du Daten aus SPSS einliest (Kap. 4), bekommst du Spalten vom Typ <haven_labelled>. Das ist kein Faktor — sondern ein numerischer Vektor mit Labels als Begleitinformation. Mit to_label() (mariposa) wandelst du sie bei Bedarf in einen klassischen Faktor.

3.5.3 Datensätze (Tibbles)

Ein Tibble ist die moderne Form des Data Frames im tidyverse — eine Tabelle mit Zeilen (Fälle) und Spalten (Variablen). Du baust es mit tibble():

befragte <- tibble(
  geschlecht = factor(c("Mann", "Frau", "Mann", "Divers")),
  alter      = c(34, 41, 28, 52)
)
befragte
# A tibble: 4 × 2
  geschlecht alter
  <fct>      <dbl>
1 Mann          34
2 Frau          41
3 Mann          28
4 Divers        52

Auf eine einzelne Spalte greifst du mit $ oder [[]] zu:

befragte$alter
[1] 34 41 28 52
befragte[["alter"]]
[1] 34 41 28 52
befragte[1, "alter"]   # erste Zeile, Spalte "alter"
# A tibble: 1 × 1
  alter
  <dbl>
1    34

In der Praxis liest du Datensätze fertig aus SPSS-, Stata- oder Excel-Dateien ein (siehe Kap. 4). Selbst-gebaute Tibbles brauchst du fast nur für kleine Demonstrationen.

3.5.4 Listen

Eine Liste kann verschiedene Objekttypen unterschiedlicher Länge sammeln. Ergebnisse statistischer Analysen sind in R meistens Listen — ein Regressionsergebnis enthält Koeffizienten, Residuen, Modellgüte usw. Du musst sie selten selbst erstellen, aber regelmäßig lesen können:

ergebnis <- list(
  koeffizient = 0.42,
  p_wert      = 0.001,
  konfidenz   = c(0.31, 0.53),
  modell      = "lm"
)
ergebnis$koeffizient
[1] 0.42
ergebnis$konfidenz[1]
[1] 0.31

3.6 Fehlende Werte: NA

In echten Datensätzen fehlen oft einzelne Werte — eine Befragte hat eine Frage übersprungen, ein Messwert war ungültig, eine Auskunft wurde verweigert. R kodiert das mit einem speziellen Wert: NA (Not Available).

x <- c(1, 2, NA, 4, 5)
x
[1]  1  2 NA  4  5
is.na(x)              # welche Elemente sind NA?
[1] FALSE FALSE  TRUE FALSE FALSE

Rechnungen mit NA ergeben NA. Das ist Absicht: der Mittelwert von „1, 2, ?, 4, 5” ist eigentlich unbekannt.

NA + 1
[1] NA
mean(x)
[1] NA

Damit Funktionen wie mean(), sd() & Co. trotzdem rechnen, übergibst du na.rm = TRUE:

mean(x, na.rm = TRUE)
[1] 3
sum(x, na.rm = TRUE)
[1] 12
Hinweis

Hintergrund: Mehrere Arten von Missing. SPSS unterscheidet system-missing (leeres Feld) von user-missing (z. B. „keine Angabe” als Code -9). In R gibt es per Default nur ein generisches NA — beim SPSS-Import behält mariposa die user-missing-Codes aber als tagged NA, sodass du den Grund des Fehlens jederzeit nachschauen kannst. Mehr dazu in Kap. 4.

3.7 Pipe-Operatoren

R erlaubt es, Funktionen ineinander zu verschachteln — der Output einer Funktion wird zum Input der nächsten:

head(round(c(1.11, 1.22, 1.33, 1.44, 1.55), digits = 1), n = 3)

Das funktioniert, aber je mehr Schritte du verschachtelst, desto schwerer wird der Code zu lesen — du musst ihn von innen nach außen lesen, was unserer natürlichen Leserichtung widerspricht. Für genau dieses Problem gibt es eine elegante Lösung: den Pipe-Operator. Ein Pipe verkettet Funktionsaufrufe linear, statt sie zu verschachteln. R kennt zwei Varianten — wir nutzen die native Pipe |>.

3.7.1 Native Pipe |>

Mit der nativen Pipe schreibst du dieselbe Pipeline linear — von links nach rechts statt von innen nach außen:

zahlen <- c(1.11, 1.22, 1.33, 1.44, 1.55)

zahlen |>
  round(digits = 1) |>
  head(n = 3)
[1] 1.1 1.2 1.3

Lies das wie einen Satz: „Nimm zahlen, dann runde auf 1 Nachkommastelle, dann zeige die ersten 3.”

Tastenkürzel: cmd/strg + shift + M. Stelle in RStudio unter ToolsGlobal OptionsCodeUse native pipe operator die Option ein, damit das Shortcut |> statt %>% einfügt.

Hinweis

Hintergrund: Wert nicht ans erste Argument? Standardmäßig übergibt |> den Wert als erstes Argument der nächsten Funktion. Wenn er an eine andere Stelle gehen soll, nutzt du seit R 4.2 den Platzhalter _ — er muss als benanntes Argument stehen:

allbus |>
  lm(islamophobie ~ age, data = _)

Bei den meisten Tidyverse-Funktionen brauchst du das nicht — sie nehmen den Datensatz ohnehin als erstes Argument.

3.7.2 Das ältere %>% aus magrittr

Du wirst in der R-Welt überall den Pipe %>% (aus dem tidyverse-Paket magrittr) sehen — in Tutorials, in Stack-Overflow-Antworten, in vielen R-Paketen intern. %>% und |> sind funktional fast identisch:

zahlen %>%
  round(digits = 1) %>%
  head(n = 3)

Beide funktionieren in tidyverse-Code, beide liefern dieselben Ergebnisse. Wir nutzen in diesem Buch durchgängig |>, weil er Teil der Sprache selbst ist (kein Paket nötig) und ein klein wenig schneller — aber wenn du in fremdem Code %>% siehst, ist das kein Problem.

Hinweis

Hintergrund: Wo %>% etwas anders ist. Der magrittr-Pipe hat einen Platzhalter ., der überall positionierbar ist: x %>% lm(y ~ ., data = .). Der native _ muss benannt sein. Bei den meisten Tidyverse-Funktionen brauchst du den Platzhalter nicht — das erste Argument ist immer der Datensatz.

Warnung

Verschwundene Operatoren. %$%, %T>%, %<>% aus magrittr waren früher gelegentlich nötig (z. B. um Pipes durch Plotting-Funktionen zu führen). In diesem Buch brauchen wir sie nirgends — mariposa-Funktionen sind durchgehend data-first und pipe-freundlich.

3.7.3 Pipes in der Praxis

Hier eine echte Pipeline aus Kap. 4, die zeigt, wofür Pipes wirklich nützlich sind:

allbus <- read_spss("ALLBUS2023.sav") |>
  rec(mm02, rules = "rev", suffix = "r") |>
  rec(mm05, rules = "rev", suffix = "r") |>
  mutate(
    islamophobie = row_means(pick(mm01, mm02r, mm03, mm04, mm05r, mm06),
                             min_valid = 5)
  )

Du liest: Datensatz einlesen → mm02 umpolen → mm05 umpolen → Skala bilden. Ohne Pipe wäre das ein verschachteltes Etwas, das du nur mit Mühe rückwärts lesen könntest.

Übungen

HinweisÜbung 1 · Variablen und Vektoren · Reproduktion

Erstelle einen Vektor mit den Zahlen 10 bis 20, runde jede Zahl auf eine Dezimalstelle (auch wenn schon ganzzahlig), berechne den Mittelwert. Schreibe das einmal verschachtelt und einmal mit Pipe.

# Verschachtelt
mean(round(10:20, digits = 1))

# Mit Pipe
10:20 |>
  round(digits = 1) |>
  mean()
HinweisÜbung 2 · Tibble basteln · Reproduktion

Erzeuge ein Tibble mit zwei Variablen: id (1 bis 5) und wert (zufällige Zahlen — runif(5, 0, 10) ist eine Möglichkeit). Greife danach auf die dritte Zeile zu.

demo <- tibble(
  id   = 1:5,
  wert = runif(5, 0, 10)
)
demo
demo[3, ]
HinweisÜbung 3 · Faktor und Referenzkategorie · Reproduktion

Baue einen Faktor bildung mit den Ausprägungen "niedrig", "mittel", "hoch". Setze "mittel" als Referenzkategorie. Tipp: levels = setzt die Reihenfolge bei factor().

bildung <- factor(c("hoch", "niedrig", "mittel", "mittel", "hoch"),
                  levels = c("mittel", "niedrig", "hoch"))
levels(bildung)
HinweisÜbung 4 · Operatoren und NA mit Survey-typischen Daten · Transfer

Stell dir vor, du hast eine kleine Umfrage zu Vertrauen in den Bundestag (0 = kein Vertrauen, 10 = volles Vertrauen) bei sieben Befragten gemacht: vertrauen <- c(3, 8, NA, 7, 5, NA, 9). (NA = „keine Angabe”.) Bestimme (a) den Mittelwert ohne fehlende Werte, (b) wie viele Befragte hohes Vertrauen (> 5) angegeben haben, (c) ob mindestens eine Person die Frage übersprungen hat.

vertrauen <- c(3, 8, NA, 7, 5, NA, 9)
mean(vertrauen, na.rm = TRUE)            # (a)
sum(vertrauen > 5, na.rm = TRUE)         # (b) TRUE zählt als 1
any(is.na(vertrauen))                    # (c)
HinweisÜbung 5 · Pipe-Pipeline auf Survey-Vektor · Mini-Forschungsfrage

Du hast in der Vertrauens-Übung gerade einzelne Kennwerte berechnet. Schreibe nun in einer Pipeline: filtere alle NA-Werte heraus, behalte nur Werte ≥ 5 (hohes Vertrauen), berechne den Mittelwert. Schreibe die Lösung sowohl verschachtelt als auch mit Pipe — welche Version findest du lesbarer?

Stelle dir vor, du würdest später dieselbe Pipeline auf den ALLBUS-Vektor pt03 (Vertrauen in den Bundestag, ~5.000 Fälle) anwenden — der Code wäre derselbe.

vertrauen <- c(3, 8, NA, 7, 5, NA, 9)

# Verschachtelt
mean(vertrauen[!is.na(vertrauen) & vertrauen >= 5])

# Mit Pipe (lesbarer)
vertrauen[!is.na(vertrauen) & vertrauen >= 5] |>
  mean()

Anschluss: In den folgenden Kapiteln rufst du genau diese Operationen mit tidyverse-Verben (filter(), summarise()) auf — die |>-Pipe bleibt dieselbe.

Was du jetzt weißt

  • Du weist Werte mit <- Variablen zu und kennst die Konvention snake_case.
  • Du kommentierst Code mit # für dein zukünftiges Ich und Reviewer:innen.
  • Du rufst Funktionen mit benannten Argumenten auf und schlägst Hilfe mit ? nach.
  • Du nutzt arithmetische, Vergleichs- und Logik-Operatoren — inklusive %in%.
  • Du erkennst und erstellst Vektoren, Faktoren, Tibbles und Listen.
  • Du gehst sicher mit NA um und nutzt na.rm = TRUE in Berechnungsfunktionen.
  • Du nutzt den nativen Pipe |> für lesbaren, linearen Code.
  • Du verstehst den Unterschied zu %>% und weißt, dass |> der heutige Standard ist.

Im nächsten Kapitel arbeiten wir mit echten Daten: dem ALLBUS 2023.

Weiterführend