Chapter 6 Schleifen und Funktionen

In diesem Kapitel möchte ich euch eine Möglichkeit vorstellen, effizienter zu arbeiten. R ist eine Programmiersprache für statistische Analysen, enthält aber auch klassische Elemente der Programmierung. Zwei grundlegende Operationen sind Schleifen und Funktionen. Wir können damit Aufgaben automatisieren – und je früher ihr das lernt, desto schneller versteht ihr die Logik von R. Das Ziel dieses Kapitels ist, euch mit Funktionen und Schleifen vertraut zu machen, damit ihr sie erkennt, wenn ihr auf sie trefft.

6.1 Schleifen

Zum Beispiel könnt ihr eine Schleife verwenden, um eine Liste von Zahlen zu durchlaufen und für jede Zahl Berechnungen durchzuführen, oder um die Zeilen eines Datensatzes zu durchlaufen und auf jede Zeile bestimmte Operationen anzuwenden. Schleifen bieten eine Möglichkeit, den Code zu vereinfachen und das manuelle Schreiben von sich wiederholenden Anweisungen zu vermeiden.

Es gibt verschiedene Arten von Schleifen, aber in diesem Kurs konzentrieren wir uns ausschließlich auf for-Schleifen, da ihr diesen Typ auch im QM-Tutorium begegnen werdet.

6.1.1 For-Schleifen

Erinnert ihr euch an mein Notenbeispiel aus dem ersten Kapitel?

note <- 4.0

if (note == 1.0) {
  print("Hervorragend") 
} else if (note > 1.0 & note <= 2.0) {
  print("Gut gemacht")
} else if (note > 2.0 & note <= 3.0) {
  print("OK")
} else if (note > 3.0 & note <= 4.0) {
  print("Das Leben geht weiter") 
}
## [1] "Das Leben geht weiter"
note <- 3.3

if (note == 1.0) {
  print("Hervorragend") 
} else if (note > 1.0 & note <= 2.0) {
  print("Gut gemacht")
} else if (note > 2.0 & note <= 3.0) {
  print("OK")
} else if (note > 3.0 & note <= 4.0) {
  print("Das Leben geht weiter") 
}
## [1] "Das Leben geht weiter"
note <- 4.0

if (note == 1.0) {
  print("Hervorragend") 
} else if (note > 1.0 & note <= 2.0) {
  print("Gut gemacht")
} else if (note > 2.0 & note <= 3.0) {
  print("OK")
} else if (note > 3.0 & note <= 4.0) {
  print("Das Leben geht weiter") 
}
## [1] "Das Leben geht weiter"
note <- 2.3

if (note == 1.0) {
  print("Hervorragend") 
} else if (note > 1.0 & note <= 2.0) {
  print("Gut gemacht")
} else if (note > 2.0 & note <= 3.0) {
  print("OK")
} else if (note > 3.0 & note <= 4.0) {
  print("Das Leben geht weiter") 
}
## [1] "OK"
note <- 1.7

if (note == 1.0) {
  print("Hervorragend") 
} else if (note > 1.0 & note <= 2.0) {
  print("Gut gemacht")
} else if (note > 2.0 & note <= 3.0) {
  print("OK")
} else if (note > 3.0 & note <= 4.0) {
  print("Das Leben geht weiter") 
}
## [1] "Gut gemacht"

Ich könnte nun alle meine Noten aufschreiben und sie einzeln zuweisen, wie im ersten Kapitel – aber es gibt eine Möglichkeit, diesen Prozess zu automatisieren. Dazu verwende ich die for-Schleife.

Zunächst erstellen wir einen Vektor mit Noten:

noten <- c(1.7, 3.3, 4.0, 2.3, 1.0)

Jetzt können wir direkt in die Schleife einsteigen.

  • Schreibe for und definiere in den Klammern den Schleifeniterator – das ist das i in der Schleife. Dann legst du fest, durch welches Objekt du iterieren möchtest. In unserem Fall soll die Operation durch den Notenvektor iterieren. Ich könnte auch die Zahl 5 schreiben, aber es ist konventionell, ein Objekt zu definieren. Warum?

  • Nach dem Schließen der Klammern öffnest du geschweifte Klammern und schreibst deine Funktion, wie du es normalerweise tätest – aber diesmal musst du festlegen, wie der Iterator verwendet wird. Da ich die Zahlen in grades verwende, muss mein Iterator in eckigen Klammern nach dem Namen grades stehen. Warum?

  • Und das war es im Grunde.

for (i in 1:length(noten)) {
  if (noten[i] == 1.0) {
  print("Hervorragend") 
} else if (noten[i] > 1.0 & noten[i] <= 2.0) {
  print("Gut gemacht")
} else if (noten[i] > 2.0 & noten[i] <= 3.0) {
  print("OK")
} else if (noten[i] > 3.0 & noten[i] <= 4.0) {
  print("Das Leben geht weiter") 
}
}
## [1] "Gut gemacht"
## [1] "Das Leben geht weiter"
## [1] "Das Leben geht weiter"
## [1] "OK"
## [1] "Hervorragend"

Schleifen können unterschiedlich aussehen:

In diesem Beispiel habe ich einen Zahlenvektor und lasse die Konsole einen Satz ausgeben, bei dem sich die Zahl und damit der Satz bei jedem Schleifendurchlauf ändert:

# Zahlenvektor erstellen
num <- c(1, 2, 3, 4, 5, 249)

# Durch den Vektor iterieren
for (i in num) { 
  print(stringr::str_c("Das ist die ", i, ". Iteration")) 
}
## [1] "Das ist die 1. Iteration"
## [1] "Das ist die 2. Iteration"
## [1] "Das ist die 3. Iteration"
## [1] "Das ist die 4. Iteration"
## [1] "Das ist die 5. Iteration"
## [1] "Das ist die 249. Iteration"

6.1.2 Verschachtelte Schleifen

Da ihr ihnen früher oder später begegnen werdet, zeige ich euch kurz verschachtelte Schleifen:

Spielen wir zunächst eine Runde Tic-Tac-Toe:

# Matrix definieren
ttt <- matrix(c("X", "O", "X",
                "O", "X", "O",
                "O", "X", "O"), nrow = 3, ncol = 3, byrow = TRUE)

Wir definieren eine Schleife mit dem Iterator i für die Zeilen der Matrix und eine weitere mit dem Iterator j für die Spalten.

Anschließend bauen wir den Schleifenkörper auf, der Informationen über die Matrix und ihren Inhalt liefert. Der ausgegebene Satz zeigt, welche Zeilen und Spalten welche Werte enthalten.

for (i in 1:nrow(ttt)) {
  for (j in 1:ncol(ttt)) {
    print(paste("In Zeile", i, "und Spalte", 
                j, "enthält das Spielfeld", ttt[i,j]))
  }
}
## [1] "In Zeile 1 und Spalte 1 enthält das Spielfeld X"
## [1] "In Zeile 1 und Spalte 2 enthält das Spielfeld O"
## [1] "In Zeile 1 und Spalte 3 enthält das Spielfeld X"
## [1] "In Zeile 2 und Spalte 1 enthält das Spielfeld O"
## [1] "In Zeile 2 und Spalte 2 enthält das Spielfeld X"
## [1] "In Zeile 2 und Spalte 3 enthält das Spielfeld O"
## [1] "In Zeile 3 und Spalte 1 enthält das Spielfeld O"
## [1] "In Zeile 3 und Spalte 2 enthält das Spielfeld X"
## [1] "In Zeile 3 und Spalte 3 enthält das Spielfeld O"

6.2 Die apply()-Funktionsfamilie

6.2.1 apply()

apply() nimmt einen Dataframe oder eine Matrix als Eingabe und gibt das Ergebnis als Vektor, Liste oder Array zurück. Die apply()-Funktion in R wird hauptsächlich verwendet, um explizite Schleifenkonstrukte zu vermeiden. Die Idee ist, eine Funktion wiederholt auf eine Matrix oder einen Dataframe anzuwenden: apply(X, MARGIN, FUNCTION).

# Eine Matrix mit zufälligen Zahlen erstellen
mat <- matrix(1:10, nrow = 5, ncol = 6)

# Überprüfung
head(mat)
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]    1    6    1    6    1    6
## [2,]    2    7    2    7    2    7
## [3,]    3    8    3    8    3    8
## [4,]    4    9    4    9    4    9
## [5,]    5   10    5   10    5   10

Nehmen wir an, ihr möchtet den Mittelwert jeder Spalte berechnen:

apply(mat, 2, mean) # Mittelwert berechnen
## [1] 3 8 3 8 3 8
apply(mat, 2, sum)  # Summe berechnen
## [1] 15 40 15 40 15 40
apply(mat, 2, sd)   # Standardabweichung berechnen
## [1] 1.581139 1.581139 1.581139 1.581139 1.581139 1.581139
# Die entsprechende Schleife würde so aussehen:
for (i in 1:ncol(mat)) {
  durchschnitt_spalte <- mean(mat[, i])
  print(durchschnitt_spalte)
}
## [1] 3
## [1] 8
## [1] 3
## [1] 8
## [1] 3
## [1] 8

Die Funktion apply() ist besonders nützlich, wenn man mit mehrdimensionalen Strukturen arbeitet und Berechnungen durchführen möchte. Sie kann jedoch mit der Flexibilität von Schleifen nicht mithalten – das sollte man im Hinterkopf behalten.

6.2.2 Hinweise

Schleifen und die apply()-Funktion werden in der Programmierung häufig eingesetzt. Dies ist jedoch kein Programmierkurs, sondern eine Einführung – jetzt habt ihr eine Vorstellung davon, was passiert, wenn ihr diese beiden Konstrukte in Skripten seht. Wenn ihr euch für das Thema interessiert, empfehle ich euch, euch in while-Schleifen und repeat-Schleifen einzulesen. Die apply()-Funktion ist Teil einer Familie: sapply(), lapply() und tapply() gehören ebenfalls dazu.

6.3 Eigene Funktionen schreiben

Wir können erneut viel Zeit sparen und effizienter arbeiten, indem wir eigene Funktionen schreiben.

  • Zunächst muss man der Funktion einen Namen geben.
  • Dann schreibt man den Befehl mit dem function()-Befehl. In die Klammern kommen die Variablen – später folgt der eigentliche Input.
  • Nach den geschweiften Klammern definiert man die Operation mit den zuvor festgelegten Variablen.
  • Abschließend lässt man die Funktion die gewünschte Größe zurückgeben (return()) und schließt die geschweiften Klammern.
  • Danach ist die Funktion gespeichert und kann verwendet werden.
# Meine Funktion berechnet einfach eine Summe

add <- function(x, y) { 
  
  ergebnis <- x + y
  return(ergebnis)
}

add(2, 7) # Jetzt kann ich meine Funktion verwenden
## [1] 9

Berechnen wir den Flächeninhalt eines Kreises:

aoc <- function(radius) {
  pi <- 3.14159
  
  fläche <- pi * radius^2
  
  return(fläche)
}

aoc(5)
## [1] 78.53975

Verknüpfen wir nun, was wir in diesem Kapitel gelernt haben, mit einem Beispiel aus dem Klassenzimmer. Den folgenden Code müsst ihr nicht vollständig verstehen – ich möchte ihn auch nicht im Detail erklären. Es geht darum zu zeigen, was Schleifen und Funktionen leisten können. Alle Pakete basieren schließlich auf Funktionen und Schleifen, daher ist es hilfreich, einen Überblick zu bekommen, wie das aussehen kann.

In dieser Schleife lassen wir R ausgeben, wo Personen in einem Klassenzimmer sitzen. Die Sitzordnung ist dabei nichts anderes als eine Matrix – mit Zeilen und Spalten. Die Schleife identifiziert, in welcher Zeile (i) und in welcher Spalte (j) eine Person sitzt. Die Werte in der Matrix sind Studierenden-IDs, und die Schleife kennt diese IDs und gibt aus, welche Person wo sitzt. Das ist eine unnötige Schleife für diesen kleinen Datensatz – aber ein gutes Beispiel. Stellt euch vor, die Klasse hätte 1000 Personen: Dann wäre diese Schleife notwendig. Wie gesagt: Ihr müsst das nicht vollständig verstehen. Es soll illustrieren, was in R möglich ist.

# Die Funktion
klassenzimmer <- function(x) {
  for (i in 1:length(x)) {        # Äußere Schleife iteriert über Zeilen
    for (j in 1:length(x[[i]])) { # Innere Schleife iteriert über Spalten
      schüler <- x[[i]][j]
      if (schüler == 1) {
        comment <- "Alice"
      } else if (schüler == 2) {
        comment <- "Bob"
      } else if (schüler == 3) {
        comment <- "Cathy"
      } else if (schüler == 4) {
        comment <- "David"
      } else if (schüler == 5) {
        comment <- "Eva"
      } else {
        comment <- paste("Unbekannter Student", schüler, "macht etwas Interessantes.")
      }
      cat("In Zeile", i, "Spalte", j, ":", comment, "\n")
    }
  }
}

# Beispielverwendung
sitzordnung <- list(
  c(1, 5, 2),
  c(4, 3, 7)
)

# Ausgabe
klassenzimmer(sitzordnung)
## In Zeile 1 Spalte 1 : Alice 
## In Zeile 1 Spalte 2 : Eva 
## In Zeile 1 Spalte 3 : Bob 
## In Zeile 2 Spalte 1 : David 
## In Zeile 2 Spalte 2 : Cathy 
## In Zeile 2 Spalte 3 : Unbekannter Student 7 macht etwas Interessantes.

6.4 Ausblick

Dieser Abschnitt war eine kurze Einführung in die Automatisierung von Arbeit und das Schreiben von effizientem Code: Schleifen und Funktionen. Da beide Konzepte sehr schnell komplex werden können, werdet ihr am Anfang eurer R-Reise seltener auf Schleifen und Funktionen zurückgreifen. Irgendwann werdet ihr jedoch auf sie treffen – und je früher ihr sie kennt, desto besser.

  • Für weitere Informationen zur Programmierung in R empfehle ich „Hands-On Programming with R” von Garret Grolemund. In diesem Buch zeigt der Autor Projekte, in denen er Funktionen und Schleifen einsetzt – eine schöne Veranschaulichung ihrer Verwendung.

6.5 Übungsaufgaben

6.5.1 Übung 1: Eine Schleife schreiben

Schreibe eine for-Schleife, die das Quadrat jeder Zahl von 1 bis 10 ausgibt.

# Objekt für einen besseren Arbeitsablauf zuweisen
nummer <- 10

# Die Schleife

6.5.2 Übung 2: Eine Funktion schreiben

Schreibe eine Funktion, die den Input x nimmt und ihn quadriert:

# Eine Funktion zum Quadrieren definieren

sq <- function(x) {
  
  
  
}

# Einen Vektor von 1 bis 10 definieren
nummern <- c(1:10) 

# Die Funktion anwenden
sq(nummern)

6.5.3 Übung 3: Die Mitternachtsformel

Das ist die Mitternachtsformel, aufgeteilt in zwei Gleichungen:

\(x_{1,2} = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}\)

Schreibe eine Funktion für die Mitternachtsformel, sodass die Ausgabe \(x_1\) und \(x_2\) sind. Teste sie mit a = 2, b = -6, c = -8.

Hinweis: Du musst die Formel in zwei Gleichungen mit zwei Ausgaben aufteilen.

mnf <- 


mnf(2, 6, 8)