Die for-Schleife in R

Schleifen mit einer Zählvariable werden eingesetzt, wenn bekannt ist, wie oft ein gewisser Vorgang wiederholt werden muss (wobei die Anweisungen nicht exakt identisch sind, sondern meist vom Wert der Zählvariable abhängen). Die Syntax der for-Schleife sowie einige mit ihr verbundene Spitzfindigkeiten werden erklärt. Zusätzliche Kontrolle über den Ablauf einer Schleife erhält man durch break (vorzeitiges Verlassen der Schleife) und next (sofortiger Übergang zum nächsten Wert der Zählvariable).

Einordnung des Artikels

Einführung

Arten von Schleifen

Nach der Alternative ist die Schleife (oder Iteration) die wichtigste Kontrollstruktur, die den Ablauf von Programmen regelt. Mit einer Schleife ist gemeint, dass etwas wie eine Liste abgearbeitet werden muss, wobei für jeden Listen-Eintrag dieselben Befehle ausgeführt werden müssen In Pseudocode und formale Sprachen im Abschnitt Schleife (Iteration) wurden

im Pseudocode vorgestellt. Letztere wird in R mit der for-Anweisung realisiert; und da dies diejenige Schleife ist, die am klarsten strukturiert ist, wird sie zuerst besprochen.

Für eine ausführliche Einführung in Schleifen – unabhängig von einer Programmiersprache – ist oben zitierter Abschnitt zu empfehlen.

Die Schleife mit Zählvariable (for-Schleife)

Das Paradebeispiel einer Schleife, das in Pseudocode und formale Sprachen im Abschnitt Schleife (Iteration) diskutiert wird, ist:

Es sollen die Personalien aller sich in einem Raum befindlichen Personen aufgenommen werden.

Das Problem wird zunächst in 2 Teilprobleme zerlegt:

  1. Was ist zu tun, um von einer Person die Personalien aufzunehmen?
  2. Wie sorgt man dafür, dass alle Personen durchlaufen werden?

Im Pseudocode lautet die Lösung des Problems etwa

Für i = 1 bis N Schrittweite 1
    PersonalienAufnehmen(P(i))

Dabei wird vorausgesetzt:

  1. Es ist bekannt, dass N Personen im Raum sind.
  2. Die Personen sind durchnumeriert und können mit P(i), i = 1, 2, ..., N aufgerufen werden.
  3. Das erste Teilproblem oben ist bereits gelöst: Der Aufruf des Unterprogramms PersonalienAufnehmen(P(i)) nimmt die Personalien der Person i auf.

Man erkennt dann, wie durch Zeile 1 das zweite Teilproblem gelöst wird:

Es wird eine Zählvariable i eingeführt, über die die Personen aufgerufen werden können. Die Zählvariable läuft von 1 bis N bei einer Schrittweite von 1. Dies garantiert, dass jede Person aufgerufen wird.

Man kann die Schleife auch als Abkürzung für die Folge von N Anweisungen sehen:

PersonalienAufnehmen(P(1))
PersonalienAufnehmen(P(2))
...
PersonalienAufnehmen(P(N))

Da R auf Vektoren basiert, wird auch die Schleife mit Zählvariable über einen Vektor konfiguriert: Man gibt sich einen Vektor seq vor, dessen Komponenten nacheinander von der Zählvariable var angenommen werden. Der Ausdruck expr ist ein beliebiger Block von Anweisungen, der mit jedem Wert von var ausgeführt wird.

for(var in seq) expr

Darum dass nach einem Abarbeiten von expr der nächste Wert von var gesetzt wird und wiederum expr abgearbeitet wird, muss sich der Programmierer nicht kümmern – dies ist gerade die Ausführung der Schleife. (Ebenso, dass die Schleife verlassen wird, wenn var den letzten Wert aus seq angenommen hat und die zugehörigen Anweisungen abgearbeitet wurden.)

Bezeichnungen: Schleife, Schleifenkörper, Schleifen-Durchlauf

Für das Beispiel, das oben im Pseudocode formuliert wurde, nämlich

Für i = 1 bis N Schrittweite 1
    PersonalienAufnehmen(P(i))

sollen nun die Bezeichnungen vereinbart werden, die im Folgenden verwendet werden.

Angenommen es befinden sich 7 Personen im Raum, dann sagt man auch gerne, dass die Schleife 7-mal durchlaufen werden muss. Diese Sprechweise ist etwas irreführend und soll nun präzisiert werden.

Die Anweisung PersonalienAufnehmen(P(i)) wird als der Schleifenkörper bezeichnet; weiter unten wird erklärt, dass hier auch mehrere Anweisungen stehen können. Die Anweisungen im Schleifenkörper hängen vom aktuellen Wert der Zählvariable i ab.

Werden jetzt von allen 7 Personen die Personalien aufgenommen, so sollte man besser sagen: Der Schleifenkörper wird 7-mal durchlaufen. Das Aufnehmen der Personalien einer Person wird auch als ein Schleifen-Durchlauf bezeichnet.

Die eigentliche Schleife besteht aus sämtlichen Schleifen-Durchläufen. Wenn die Zählvariable ihren letzten Wert angenommen hat und alle Personen abgearbeitet wurden, wird die Schleife verlassen; es folgen Anweisungen, die nicht mehr zur Schleife gehören.

In salopper Sprechweise wird oft der Begriff Schleife sowohl für die Gesamtheit aller Schleifen-Durchläufe als auch für einen einzigen Schleifen-Durchlauf verwendet. Achten Sie beim Lesen der einschlägigen Literatur an dieser Stelle darauf, wie der Begriff Schleife eingesetzt wird.

Oft – und gleichwertig – wird die Schleife auch als Iteration bezeichnet. Ein Schleifen-Durchlauf ist dann ein Iterationsschritt. Den Schleifenkörper nennt man dann den Körper (oder den Rumpf) der Iteration.

Auswahl der Beispiele

Die Beispiele, mit denen im Folgenden Schleifen erläutert werden, lassen sich in R auch deutlich einfacher realisieren – weil entsprechende Funktionen bereits implementiert sind. Die einfacheren Lösungen werden dann nur verwendet, um eine Kontrollrechnung durchzuführen.

Ziel diese Kapitels ist es natürlich, eine Einführung in Schleifen zu geben und nicht die vorgestellten Aufgaben möglichst einfach zu lösen.

Gerade im Zusammenhang mit Schleifen muss darauf hingewiesen werden, dass es in R die sogenannte Familie von apply()-Funktionen gibt, mit der sich viele Schleifen vermeiden lassen. Diese Familie wird aber erst später besprochen, wenn erklärt wurde, wie man selber Funktionen definiert.

Die Syntax der for-Schleife

Der einfachste Fall

Wie in R eine Schleife allgemein aussieht, wurde oben bereits gezeigt. Die allgemeine Syntax soll jetzt an einem einfachen Beispiel erklärt werden: Die Zahlen von 1 bis 100 werden mit einer for-Schleife addiert.

idx <- (1:100)
summe <- 0

for(i in idx){
  summe <- summe + i
}

cat("Summe: ", summe, "\n")
# Summe:  5050 

# Kontrolle:
sum(idx)
# [1] 5050

Zeile 1: Es wird der Vektor der Zahlen vorbereitet, die addiert werden sollen.

Zeile 2: Es wird eine Variable summe vorbereitet, die die Zwischensummen berechnet; am Ende der Schleifen-Durchläufe muss sie die Gesamtsumme beinhalten. Diese Hilfsvariable muss natürlich am Anfang den Wert 0 besitzen.

Zeile 4 bis 6: Die eigentliche for-Schleife. Nach dem Schlüsselwort for folgt in runden Klammern die Konfiguration der Schleife:

Zeile 8 und 9: Nach Beendigung der Schleife wird die Variable summe ausgegeben; sie liefert den Wert 5050.

Zeile 11 und 12. Die Kontrollrechnung mit der Funktion sum() liefert den selben Wert.

Was gehört vor die Schleife, in die Schleife und nach die Schleife?

Für das Verständnis des Ablaufs einer Schleife ist es wichtig, sich zu fragen:

Man kann dazu obiges Beispiel verwenden und die Anweisungen verschieben.

Zuerst wird die Anweisung summe <- 0 in den Schleifenkörper verschoben:

idx <- (1:100)

for(i in idx){
  summe <- 0
  summe <- summe + i
}

cat("Summe: ", summe, "\n")
# Summe:  100 

# Kontrolle:
sum(idx)
# [1] 5050

Wie kann man das Ergebnis 100 erklären?

Wird summe bei jedem Durchlauf der Schleife auf 0 zurückgesetzt, stimmt nach dem Durchlauf einer Schleife summe genau mit dem aktuellen Wert von i überein. Nach dem letzten Durchlauf ist somit summe mit 100 belegt.

Was passiert, wenn die Ausgabe cat("Summe: ", summe, "\n") in den Schleifenkörper verschoben wird?

idx <- (1:100)
summe <- 0

for(i in idx){ 
  summe <- summe + i
  cat("Summe: ", summe, "\n")
}

# Kontrolle:
sum(idx)
# [1] 5050

Jetzt wird bei jedem Schleifen-Durchlauf eine Ausgabe erzeugt, nämlich mit dem aktuellen Wert von summe. Dies ergibt 100 Ausgaben mit den Werten: 1, 3, 6, 10, ..., 5050.

Diese Zwischensummen sind hier irrelevant, gefragt war nur der letzte Zahlenwert von summe. Um die Zwischensummen zu unterdrücken, muss die Ausgabe nach der Schleife erfolgen.

Man kann versuchen, die Antwort auf die Frage "was gehört vor, in und nach die Schleife?" etws allgemeiner zu formulieren:

  1. Vor die Schleife gehören Anweisungen, die die Schleife vorbereiten, also etwa die Definition von Variablen, die sich auf die Gesamtheit der Schleifen-Durchläufe beziehen. Dies sind meist einmalige Anweisungen, die nicht wiederholt werden.
  2. In den Schleifenkörper gehören Anweisungen, die bei jedem Durchlauf ausgeführt werden müssen (also so oft wiederholt werden wie es Schleifen-Durchläufe gibt).
  3. Nach der Schleife stehen Anweisungen, die sich wieder auf die Gesamtheit der Schleifen-Durchläufe beziehen, also etwa die Weiterverarbeitung von Variablen, die sich auf die Gesamtheit der Schleifen beziehen.

Einige Spitzfindigkeiten

Die geschweiften Klammern

In der Dokumentation wird die for-Schleife erklärt durch:

for(var in seq) expr

Im Beispiel oben lautete die Schleife (hier wird idx nicht ausdrücklich definiert):

for(i in (1:100)){
  summe <- summe + i
}

Es stellen sich sofort zwei Fragen:

Zunächst zur Bedeutung der geschweiften Klammern: Die Anweisungen innerhalb der geschweiften Klammern bilden einen Block, der insgesamt den Schleifenkörper ausmacht. Besteht der Schleifenkörper aus mehreren Anweisungen, so müssen geschweifte Klammern gesetzt werden.

Besteht dagegen der Schleifenkörper nur aus einer einzigen Anweisung, so kann man die geschweiften Klammern weglassen. Es gibt jetzt zwei Möglichkeiten:

  1. Der Schleifenkörper steht in der Zeile mit for.
  2. Der Schleifenkörper steht in einer eigenen Zeile.

Die folgenden Skripte zeigen diese zwei Möglichkeiten:

summe <- 0

for(i in (1:100))
  summe <- summe + i

cat("Summe: ", summe, "\n")
# Summe:  5050
summe <- 0

for(i in (1:100))       summe <- summe + i

cat("Summe: ", summe, "\n")
# Summe:  5050

Die beiden Skripte sind identisch.

Setzt man keine geschweiften Klammern für den Schleifenkörper, dann wird der Befehl, der nach der for-Anweisung kommt, – und nur dieser Befehl – als der Schleifenkörper interpretiert; im Beispiel ist dies jeweils der Befehl summe <- summe + i . Der nächste Befehl ist bereits der Befehl nach der Schleife.

Empfohlen wird aber die Schreibweise, die selbst für einen einzigen Befehl im Schleifenkörper geschweifte Klammern verwendet. Die Gründe dafür sind einfach:

  1. Der Schleifenkörper ist kar als solcher zu erkennen und von den Befehlen nach der Schleife zu unterscheiden.
  2. Sollen später Befehle im Schleifenkörper ergänzt werden, muss man nicht umständlich Klammern hinzufügen (gerade zum Testen muss man oft Ausgaben in den Schleifenkörper einbauen).

Die Unveränderlichkeit des Vektors, über den iteriert wird

In den bisherigen Beispielen wurde meist vor der Schleife ein Vektor idx definiert, über den in der Schleife iteriert wird. Man kann hier natürlich auf die Idee kommen, diesen Vektor idx in der Schleife zu verändern. Lässt sich dadurch das Verhalten der Schleife, die ja eigentlich durch for(i in idx) kontrolliert wird, nachträglich anpassen? Die Antwort ist nein und wird durch das folgende Beispiel bestätigt:

idx <- (1:100)
summe <- 0

for(i in idx){
  summe <- summe + i
  if(i > 50){
    idx <- append(idx, i)
  }
}

cat("Summe: ", summe, "\n")
Summe:  5050 

length(idx)
# [1] 150

sum(idx)
# [1] 8825

Zeile 1, 2, 4 und 5: Dies sind die bekannten Anweisungen zur Berechnung der Summe der Zahlen von 1 bis 100.

Zeile 6 bis 8: Neu ist die if-Anweisung, in der für i = 51, 52, ..., 100 der Vektor idx um die entsprechende Zahl erweitert wird.

Wenn der veränderte Vektor in der Iteration verwendet wird, ergibt sich eine Endlos-Schleife, da immer wieder die Zahlen 51, 52, ..., 100 an idx angehängt werden. Die Ausgaben zeigen aber Folgendes:

Iterierbare Objekte

Andere Vektoren als (1:n)

Bisher wurde in allen Beispielen über den Vektor (1:100) iteriert. Dies könnte den Eindruck erwecken, dass nur Vektoren dieser Art als Wertemenge für die Zählvariable zulässig sind. Die Dokumentation ist dagegen so zu verstehen, dass ein beliebiger Vektor eingesetzt werden kann. Im folgenden Beispiel wird der Vektor gewählt, der aus den ungeraden Zahlen 1, 3, 5, ..., 99 besteht, und über diesen iteriert:

idx <- seq(from = 1, to = 99, by = 2)

summe <- 0

for(i in idx){
  summe <- summe + i
}

cat("Summe: ", summe, "\n")
# Summe:  2500

# Zur Kontrolle:
sum(idx)
# [1] 2500

Die Abarbeitung der Schleife kann man sich so vorstellen:

Damt sollte klar sein, dass man anstelle von idx jeden beliebigen Vektor einsetzen kann. Der Vektor muss auch nicht aus ganzen Zahlen bestehten wie das folgende Beispiel zeigt:

for(i in c(TRUE, FALSE, TRUE)){
    cat("i: ", i, "\n")
}
# i:  TRUE 
# i:  FALSE 
# i:  TRUE

Andere Objekte als Vektoren

Zur Iteration können nicht nur Vektoren eingesetzt werden, sondern jedes Objekt, das in einen Vektor verwandelt werden kann, zum Beispiel:

Das folgende Beispiel definiert zunächst eine Liste und iteriert in der for-Schleife über ihre Komponenten:

book <- list(author = "R-Expert", title = "Introduction to R", year = 2019)

for(i in book){
  cat("i: ", i, "||", storage.mode(i), "\n")
  storage.mode(i)
}
# i:  R-Expert || character 
# i:  Introduction to R || character 
# i:  2019 || double

Selbst eine Matrix kann verwendet werden, da eine Matrix intern als Vektor gespeichert wird. Iteration über eine Matrix bedeutet dann, dass der zugrunde liegende Vektor verwendet wird. Man muss dabei eventuell beachten, in welcher Reihenfolge der Vektor die Matrix-Elemente liefert.

Das folgende Skript zeigt ein einfaches Beispiel, bei dem ene Matrix definiert wird und anschließend wird die Summe der Komponenten bestimmt; an der Ausgabe erkennt man, in welcher Reihenfolge die Komponenten angearbeitet werden:

v <- (1:6)
m <- matrix(data = v, nrow = 2, byrow = TRUE)
m
#    [,1] [,2] [,3]
# [1,]    1    2    3
# [2,]    4    5    6

summe <- 0

for(i in m){
  cat("i: ", i, "\n")
  summe <- summe + i
}

# i:  1 
# i:  4 
# i:  2 
# i:  5 
# i:  3 
# i:  6 

cat("Summe: ", summe, "\n")
# Summe:  21

Nicht ganz klar ist, wie sich Faktoren verhalten, wenn über sie iteriert wird. Denn Faktoren aus character-Vektoren werden intern als integer-Vektoren abgespeichert.

sample <- c("A", "A", "E", "C", "B", "A", "E", "C", "A")

length(sample)    # 9

sample
# [1] "A" "A" "E" "C" "B" "A" "E" "C" "A"

mode(sample)            # [1] "character"

sf <- factor(x = sample)
sf
# [1] A A E C B A E C A
# Levels: A B C E

for(i in sample){
  cat("i: ", i, "\n")
}
# i:  A 
# i:  A 
# i:  E 
# i:  C 
# i:  B 
# i:  A 
# i:  E 
# i:  C 
# i:  A 

for(i in sf){
  cat("i: ", i, "||", storage.mode(i), "\n")
}
# i:  A || character 
# i:  A || character 
# i:  E || character 
# i:  C || character 
# i:  B || character 
# i:  A || character 
# i:  E || character 
# i:  C || character 
# i:  A || character

Zeile 1 bis 13: Es wird ein character-Vektor sample definiert und untersucht. Aus dem Vektor wird ein Faktor sf erzeugt und ausgegeben.

Zeile 15 bis 26: Wird über den character-Vektor sample iteriert, erkennt man in den Ausgaben die Zeichen, die in den Komponenten von sample abgespeichert sind.

Zeile 28 bis 40: Wird über den Faktor sf iteriert, so sind die Komponenten ebenfalls Zeichen; man erkennt dies ausdrücklich an der Ausgabe von storage.mode(i) .

Die Schlüsselwörter break und next

So wie die Schleifen bisher behandelt wurden, kann man sie noch nicht sehr flexibel einsetzen: Allein der in for(i in seq) definierte Vektor seq bestimmt den Ablauf der Schleife:

Manchmal ist es aber wünschenswert, dass

Diese beiden Möglichkeiten werden durch die Schlüsselwörter break und next realisiert:

Es folgen zwei Beispiele, die den Einsatz von break und next demonstrieren.

1. Beispiel: Der Einsatz von break

Es sollen wieder die Zahlen von 1 bis 100 addiert werden, aber die Berechnung soll vorzeitig abbrechen, wenn die Summe größer als 1000 ist. In diesem Fall sollen der aktuelle Wert der Zählvariable und die Summe ausgegeben werden.

Das folgende Skript zeigt eine mögliche Realisierung:

idx <- (1:100)
summe <- 0

for(i in idx){
  summe <- summe + i
  if(summe > 1000){
    cat("Index: ", i, " || Summe: ", summe, "\n")
    break
  }
}

# Index:  45  || Summe:  1035 

cat("Summe: ", summe, "\n")
# Summe:  1035 

# Kontrolle:
sum(1:45)
# [1] 1035

Zeile 6 bis 9: Innerhalb des Schleifenkörpers wird abgefragt, ob der aktuelle Wert von summe 1000 überschreitet. Falls ja, werden der aktuelle Wert der Zählvariable und der Summe ausgegeben. Anschließend sorgt in Zeile 8 der Befehl break dafür, dass die Schleife verlassen wird.

Zeile 12: Die Ausgabe aus der cat()-Anweisung im if-Zweig.

Zeile 14 und 15: Die Ausgabe von summe nach der Schleife zeigt, dass die Schleife tatsächlich abgebrochen wurde.

Zeile 18 und 19: Zur Kontrolle wird die Summe der Zahlen von 1 bis 45 berechnet.

2. Beispiel: Der Einsatz von next

Es sollen wieder die Zahlen von 1 bis 100 addiert werden, aber Zahlen, die durch 7 teilbar sind, sollen nicht eingerechnet werden.

Das folgende Skript zeigt eine mögliche Realisierung:

idx <- (1:100)
summe <- 0

for(i in idx){
  if(i %% 7 == 0){
    next
  }
  summe <- summe + i
}

cat("Summe: ", summe, "\n")
# Summe:  4315 

# Kontrolle
5050 - sum(7*(1:14))
# [1] 4315

Zeile 5 bis 7: Falls i durch 7 teilbar ist, wird next aufgerufen und dadurch der nächste Iterationsschritt angestoßen; der Befehl summe <- summe + i wird für durch 7 teilbare i nicht ausgeführt.

Zeile 11 bis 7: Die Summe ergibt jetzt nur noch 4315. Zur Kontrolle wird von 5050 die Summe der durch 7 teilbaren Zahlen (also 7, 14, ..., 98) abgezogen.

Verschachtelte Schleifen

Oben wurde erklärt, dass der Schleifenkörper in geschweifte Klammern eingeschlossen werden soll (falls er nur aus einer einzigen Anweisung besteht, könnte man die Klammern weglassen). Da man den Schleifenkörper mit beliebigen Anweisungen füllen kann, ist es möglich dort wiederum eine Schleife einzubauen; man spricht dann von verschachtelten Schleifen.

Eine typische Anwendung dafür ist, dass man die Werte der Komponenten einer Matrix durch zwei Iterationen setzt:

Im Kapitel Matrizen in R: der Datentyp matrix wurde gezeigt, wie man spezielle Matrizen erzeugen kann. Manchmal ist es einfacher, die Werte der Komponenten einzeln zu berechnen; das folgende Beispiel zeigt, wie man dies mit zwei Schleifen erledigen kann:

v <- (1:6)
S2 <- matrix(data = 0, nrow = 6, ncol = 6)

for(i in v){      # i: Zeilenindex
  for(j in v){    # j: Spaltenindex
    S2[i, j] <- i + j
  }
}

S2
#      [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]    2    3    4    5    6    7
# [2,]    3    4    5    6    7    8
# [3,]    4    5    6    7    8    9
# [4,]    5    6    7    8    9   10
# [5,]    6    7    8    9   10   11
# [6,]    7    8    9   10   11   12

outer(X = v, Y = v, FUN = "+")
#      [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]    2    3    4    5    6    7
# [2,]    3    4    5    6    7    8
# [3,]    4    5    6    7    8    9
# [4,]    5    6    7    8    9   10
# [5,]    6    7    8    9   10   11
# [6,]    7    8    9   10   11   12

In diesem Beispiel wird die Augensumme beim zweimaligen Würfeln berechnet. Dazu wird der Vektor v der möglichen Ergebnisse bei einem Wurf vorbereitet (Zeile 1) sowie eine Matrix S2 mit geeigneten Dimensionen (Zeile 2).

In den verschachtelten Schleifen (Zeile 4 bis 8) werden die Komponenten durch die Summe von Zeilen- und Spalten-Index gesetzt. Dabei ist i der Zeilen-Index und j der Spalten-Index.

Die Ausgabe von S2 zeigt die Matrix der Augensummen (Zeile 10 bis 17). Man kann diese Matrix auch einfacher mit Hilfe der Funktion outer() erzeugen (Zeile 19).

Zusammenfassung

Bei einer for-Schleife durchläuft die Zählvariable var die Werte der Komponenten des Vektors seq. Für jede dieser Komponenten wird die Anweisung expr ausgeführt, die üblicherweise von var abhängig ist.

for(var in seq) expr

Weitere Informationen über for-Schleifen findet man in der Dokumentation unter ?Control im Paket base.