Die einfache for-Schleife
Eine for-Schleife führt in R einen Codeblock einmal pro Element eines Vektors oder einer Liste aus. Die Schleifenvariable nimmt nacheinander jeden Wert an:
Drei Elemente, drei Durchläufe. Es gibt keinen Zähler aufzusetzen und keine Bedingung zu pflegen - for (fruit in fruits) liest sich als "für jedes fruit in fruits", und genau das tut es. Dieselbe Form iteriert über die Elemente einer Liste, eine Zahlenfolge (for (i in 1:10)) oder über die Namen von allem via names().
Ausgabe in einer Schleife braucht print() oder cat()
An der Konsole gibt das Tippen von x seinen Wert aus. In einem Schleifenrumpf nicht - Auto-Printing passiert nur auf oberster Ebene, und ein nackter Ausdruck in einer Schleife wird ausgewertet und weggeworfen:
Wenn eine Schleife also "nichts tut", ist das das Erste, was du prüfst. Nutze print() für einen schnellen Blick auf einen Wert oder cat(), wenn du eine Ausgabezeile selbst zusammenstellst (denk an das "\n" - cat() fügt keines hinzu).
Über Indizes iterieren mit seq_along()
Manchmal brauchst du die Position zusätzlich zum Wert - um am selben Index in einen anderen Vektor zu schreiben oder eine nummerierte Liste auszugeben. Iteriere mit seq_along() über Indizes:
In älterem Code siehst du for (i in 1:length(prices)), und darin steckt ein echter Bug. Ist der Vektor leer, ist length() gleich 0, und 1:0 bedeutet nicht "keine Iterationen" - der Doppelpunkt zählt abwärts:
Eine Schleife über 1:length(empty) läuft zweimal und indiziert Elemente, die nicht existieren (empty[1] ist NA, und Zuweisungen erzeugen Phantomeinträge). seq_along() gibt für einen leeren Vektor eine leere Sequenz zurück, sodass der Schleifenrumpf schlicht nie läuft. Mach seq_along() zur Gewohnheit; sein Geschwister seq_len(n) erledigt dieselbe Aufgabe, wenn du eine Anzahl statt eines Vektors hast.
Ergebnisse sammeln: vorbelegen, nicht wachsen lassen
Der natürliche erste Impuls beim Aufbauen von Ergebnissen sieht so aus:
squares <- c()
for (i in 1:10000) {
squares <- c(squares, i^2) # copies the ENTIRE vector every pass
}
Es funktioniert, aber jedes c(squares, ...) legt einen brandneuen Vektor an und kopiert alle alten Elemente hinein. Am Ende hast du rund 50 Millionen Zahlen kopiert, um 10.000 zu erzeugen. Dieses Wachsende-Vektor-Antimuster ist der mit Abstand größte Grund, warum Leute R-Schleifen für langsam halten.
Die Lösung: das Ergebnis zuerst in voller Größe anlegen, dann per Index zuweisen:
Für Ergebnisse, die keine Zahlen sind, gilt dieselbe Idee mit character(n), logical(n) oder vector("list", n) für eine Liste beliebiger Dinge. Vorbelegte Schleifen sind völlig schnell.
next und break
Zwei Schlüsselwörter steuern eine Schleife von innen: next bricht den aktuellen Durchlauf ab und springt zum nächsten Element; break verlässt die Schleife vollständig.
Das gibt 2 4 6 8 aus: Ungerade Werte werden von next übersprungen, und wenn 10 kommt, beendet break die Schleife vor der Ausgabe. Nutze next, um Fälle früh auszusieben (das hält den Hauptrumpf uneingerückt), und break, wenn eine Schleife gefunden hat, wonach sie suchte, und keinen Grund zum Weitermachen hat.
Verschachtelte Schleifen
Ein Schleifenrumpf kann eine weitere Schleife enthalten. Die innere Schleife läuft für jeden einzelnen Durchlauf der äußeren komplett durch - die klassische Demonstration ist eine Multiplikationstabelle:
Für jede Zeile i läuft die innere Schleife alle Spalten j ab, und der Zeilenumbruch wird nach Abschluss der inneren Schleife ausgegeben. Verschachtelte Schleifen sind auf zwei Ebenen in Ordnung; bei drei oder mehr will der Rumpf meist eine Funktion werden, oder die ganze Berechnung will ein outer() oder eine Matrixoperation sein.
Verschachtelung deckt auch das Durchlaufen einer Liste von Vektoren ab - äußere Schleife über die Liste, innere Schleife (oder besser ein vektorisierter Aufruf) über jedes Element:
Beachte, dass es am Ende gar keine innere Schleife gibt - mean() erledigt den ganzen inneren Vektor in einem Aufruf. Diese Beobachtung verallgemeinert sich, was uns zum ehrlichen Teil bringt.
Wann man nicht schleifen sollte
R ist eine vektorisierte Sprache: Seine Grundoperationen arbeiten bereits auf ganzen Vektoren gleichzeitig. Eine Schleife, die jedes Element einzeln transformiert, ist oft der längere Weg, einen Einzeiler zu schreiben:
Bevorzuge die vektorisierte Form, wann immer es eine gibt - sie ist kürzer, schwerer falsch zu machen und schneller. Um eine Funktion über jedes Element einer Liste anzuwenden, spielt die apply-Familie (sapply, lapply, vapply) dieselbe Rolle.
Aber mach daraus kein Dogma "Schleifen sind schlecht". Eine Schleife ist das richtige Werkzeug, wenn Iterationen von vorherigen abhängen (laufender Zustand, Simulationen), wenn du Seiteneffekte wie das Schreiben von Dateien ausführst, oder schlicht, wenn die Schleife die Fassung ist, die du und deine Leser auf einen Blick verstehen. Eine klare Schleife schlägt einen cleveren Einzeiler, den niemand entziffern kann.
Was du mitnimmst
for (x in v) { ... }führt den Rumpf einmal pro Element aus - keine Zählerbuchhaltung.- Gib in einer Schleife explizit mit
print()odercat()aus; nackte Ausdrücke werden verworfen. - Iteriere über Positionen mit
seq_along(v), nie mit1:length(v)- Letzteres läuft bei einem leeren Vektor zweimal. - Belege Ergebnisse mit
numeric(n)/vector("list", n)vor und weise per Index zu; Wachsen mitc()kopiert bei jedem Durchlauf alles. nextüberspringt einen Durchlauf,breaksteigt aus; bevorzuge vektorisierte Operationen, wo es sie gibt, aber scheu dich nicht vor einer lesbaren Schleife.
Als Nächstes: Schleifen, die laufen, bis etwas passiert, statt einmal pro Element - while und repeat.
Häufig gestellte Fragen
Wie schreibt man eine for-Schleife in R?
for (x in v) { ... } - die Schleifenvariable x nimmt nacheinander jedes Element des Vektors (oder der Liste) v an, und der Rumpf läuft einmal pro Element. Um über Positionen statt Werte zu iterieren, nutze for (i in seq_along(v)) und indiziere mit v[i].
Warum seq_along statt 1:length(v) in R?
Ist v leer, ist length(v) gleich 0, sodass 1:length(v) zu 1:0 wird - dem zweielementigen Vektor c(1, 0) - und der Schleifenrumpf läuft zweimal gegen nicht existierende Elemente. seq_along(v) gibt für einen leeren Vektor eine leere Sequenz zurück, sodass die Schleife korrekt null Mal läuft.
Warum gibt meine for-Schleife in R nichts aus?
R's Auto-Printing greift nur bei Ausdrücken, die an der Konsole auf oberster Ebene getippt werden. In einem Schleifenrumpf wird ein nackter Ausdruck wie x ausgewertet und verworfen. Pack ihn in print(x) oder nutze cat(...), wenn du die Ausgabe selbst formatieren willst.
Sind for-Schleifen in R langsam?
Die Schleife selbst ist in Ordnung - langsam ist es, darin einen Vektor mit result <- c(result, ...) wachsen zu lassen, was den ganzen Vektor bei jedem Durchlauf kopiert. Belege ihn mit numeric(n) oder vector("list", n) vor und weise per Index zu, dann läuft eine Schleife völlig gut. Wenn allerdings eine vektorisierte Operation oder ein Aufruf aus der apply-Familie dieselbe Idee ausdrückt, ist das meist schneller und klarer.