Bedingung und Alternative in R: if else und ifelse

Die Bedingungspr├╝fung mit if und die Alternative mit if else sind die wohl am h├Ąufigsten eingesetzten Kontrollstrukturen, durch die sich der Ablauf eines Programmes steuern l├Ąsst – sie sorgen daf├╝r, dass gewisse Programm-Teile nur ausgef├╝hrt werden, wenn eine bestimmte Bedingung erf├╝llt ist. In R gibt es zus├Ątzlich eine vektorisierte Variante der Alternative mit ifelse().
Noch keine Stimmen abgegeben
Noch keine Kommentare

Einordnung des Artikels

Vorausgesetzt werden hier insbesondere Kenntnisse ├╝ber logische Werte und Operationen (siehe Einf├╝hrung in R: Logische Werte) und der Zusammenhang zwischen logischen Werten und Vektoren (siehe Vektoren in R: Anwendungen).

Einf├╝hrung

Ohne Kontrollstrukturen k├Ânnte man Algorithmen (oder Programme) nur in der Form schreiben, die in Abbildung 1 links zu sehen ist: Ein Programm ist ein Abfolge von Anweisungen, die der Reihe nach abgearbeitet werden. F├╝r einfache Programme mag dies ausreichen, um komplexe Probleme zu l├Âsen, ben├Âtigt man Kontrollstrukturen, die den Ablauf eines Programmes steuern und eine andere als die sequentielle Befehlsabarbeitung erm├Âglichen. Von diesen Kontrollstrukturen werden hier die Bedingungspr├╝fung und die Alternative besprochen (siehe Abbildung 1 mitte und rechts).

Abbildung 1: Links: Abfolge (oder Sequenz) von Befehlen, die nacheinander ausgef├╝hrt werden. Mitte: Die Bedingungspr├╝fung sorgt daf├╝r, dass Anweisung 2 nur dann ausgef├╝hrt wird, wenn die Bedingung erf├╝llt ist (Ja-Zweig). Rechts: Bei der Alternative wird nach der Bedingungspr├╝fung einer der beiden Zweige durchlaufen. Ist die Bedingung erf├╝llt, wird Anweisung 2 ausgef├╝hrt (Ja-Zweig); ist die Bedingung nicht erf├╝llt, wird Anweisung 3 ausgef├╝hrt (Nein-Zweig).Abbildung 1: Links: Abfolge (oder Sequenz) von Befehlen, die nacheinander ausgef├╝hrt werden. Mitte: Die Bedingungspr├╝fung sorgt daf├╝r, dass Anweisung 2 nur dann ausgef├╝hrt wird, wenn die Bedingung erf├╝llt ist (Ja-Zweig). Rechts: Bei der Alternative wird nach der Bedingungspr├╝fung einer der beiden Zweige durchlaufen. Ist die Bedingung erf├╝llt, wird Anweisung 2 ausgef├╝hrt (Ja-Zweig); ist die Bedingung nicht erf├╝llt, wird Anweisung 3 ausgef├╝hrt (Nein-Zweig).

Beispiele:

1. Bedingungspr├╝fung: Ein Nutzer soll eine Zahl x eingeben, mit der sp├Ąter weitergerechnet werden soll, was aber bei negativen x-Werten zu einem Fehler f├╝hren soll. Daher wird die Nutzer-Eingabe zun├Ąchst ├╝berpr├╝ft.

Falls x < 0, wird x = -x gesetzt.

Andernfalls werden die beabsichtigten Berechnungen sofort durchgef├╝hrt.

Im Vergleich mit Abbildung 1 mitte ist dann:

Anweisung 1: Eingabe x

Bedingungspr├╝fung: x < 0?
    Falls JA:
    Anweisung 2: x = -x

Anweisung 3: weitere Berechnungen

Man erkennt (sowohl in der Abbildung als auch im Pseudocode):

  • Die Anweisung 2 wird nur ausgef├╝hrt, wenn die Bedingung x < 0 erf├╝llt ist.
  • Die Anweisung 3 wird immer ausgef├╝hrt (egal ob die Bedingung x < 0 erf├╝llt war oder nicht).

2. Alternative: Wieder soll der Nutzer eine Zahl x eingeben. Es soll gepr├╝ft werden, ob x gerade oder ungerade ist.

  • Falls x gerade ist, soll ausgegeben werden: "x ist gerade".
  • Falls x ungerade ist, soll ausgegeben werden: "x ist ungerade".

Anschlie├čend sollen weitere Berechnungen folgen.

Vergleicht man dies mit Abbildung 1 rechts, so ist

Anweisung 1: Eingabe x

Bedingungspr├╝fung: x gerade?

    Falls JA:
    Anweisung 2: Ausgabe "x ist gerade"
    
    Falls NEIN:
    Anweisung 3: Ausgabe "x ist ungerade"

Anweisung 4: weitere Berechnungen

Man erkennt (sowohl in der Abbildung als auch im Pseudocode):

  • Anweisung 1 wird immer ausgef├╝hrt.
  • Es wird entweder Anweisung 2 oder Anweisung 3 ausgef├╝hrt; es kann nicht vorkommen, dass beide Anwendungen ausgef├╝hrt werden. (Genau das dr├╝ckt der Begriff Alternative aus: es gibt 2 M├Âglichkeiten, die sich gegenseitig ausschlie├čen.)
  • Anweisung 4 wird immer ausgef├╝hrt.

ÔÖŽ ÔÖŽ ÔÖŽ ÔÖŽ ÔÖŽ

Es sollte klar sein, dass dort, wo in den Abbildungen einzelne Anweisungen stehen, auch ganze Bl├Âcke von Anweisungen stehen k├Ânnten. Und dass diese Bl├Âcke wiederum Bedingungspr├╝fungen und Alternativen enthalten k├Ânnen, so dass sie beliebig ineinander verschachtelt werden k├Ânnen.

In den folgenden Abschnitten wird gezeigt, wie Bedingungspr├╝fung und Alternative mit R realisiert wird. Die Schl├╝sselworte dazu sind if und else.

Da es in R eigentlich keine elementaren Datentypen sondern nur Vektoren gibt, muss man bei der Bedingungspr├╝fung unterscheiden, ob eine Bedingung gepr├╝ft wird oder mehrere Bedingungen. Ersteres hei├čt, dass ein logischer Ausdruck gleich TRUE ist (genauer: ein logischer Vektor der L├Ąnge 1 ist gleich TRUE); Letzteres soll hei├čen, dass ein logischer Vektor komponentenweise ├╝berpr├╝ft wird, und f├╝r jede Komponente entschieden wird, welche Anweisung ausgef├╝hrt wird. F├╝r Ersteres wird in R if eingesetzt, f├╝r Letzteres ifelse.

Die Bedingungspr├╝fung mit if()

Die Syntax der Bedingungspr├╝fung

Nach den Erkl├Ąrungen in der Einf├╝hrung zu Bedingungspr├╝fung muss man folgende Anweisungen unterscheiden (den ersten Punkt kann man auch weglassen):

  1. Die Anweisungen vor der Bedingungspr├╝fung.
  2. Die eigentliche Bedingungspr├╝fung.
  3. Die Anweisungen, die nur ausgef├╝hrt werden, wenn die Bedingung erf├╝llt ist.
  4. Die Anweisungen nach der Bedingungspr├╝fung (die immer ausgef├╝hrt werden).

Das Beispiel aus der Einf├╝hrung zur Bedingungspr├╝fung wird jetzt in der Syntax von R gezeigt, wobei der erste und vierte Punkt der Liste nur im Pseudocode dargestellt werden ÔÇô sie sind f├╝r das Verst├Ąndnis der Syntax einer Bedingungspr├╝fung unerheblich:

# Eingabe x

if(x < 0){
    x <- -x
}

# weitere Berechnungen

Zeile 3: Die Bedingungspr├╝fung wird mit dem Schl├╝sselwort if eingeleitet. Die Bedingungspr├╝fung findet in runden Klammern statt. Hier muss ein logischer Ausdruck stehen, der also zu TRUE oder FALSE ausgewertet werden kann.

Zeile 3 bis 5: In den geschweiften Klammern stehen alle Anweisungen, die ausgef├╝hrt werden, wenn die Bedingungspr├╝fung TRUE ergeben hat. Da es hier nur eine Anweisung gibt, k├Ânnte man die geschweiften Klammern auch weglassen; dies wird unten sofort erkl├Ąrt.

Zeile 7: Die Anweisungen nach dem Schlie├čen der geschweiften Klammern werden immer ausgef├╝hrt.

Es sollte sofort klar sein, wie das Skript abzu├Ąndern ist, wenn mehrere Anweisungen im if-Zweig ausgef├╝hrt werden sollen. Der if-Zweig ist durch die geschweiften Klammern vom Rest des Programmes abgegrenzt, daher werden zus├Ątzliche Anweisungen in die geschweiften Klammern mit aufgenommen.

Nicht ganz klar ist: Ben├Âtigt man f├╝r eine Anweisung tats├Ąchlich die geschweiften Klammern?

Nein sie sind nicht n├Âtig und k├Ânnen weggelassen werden. Es wird aber empfohlen, die Klammern trotzdem wie im Beispiel oben zu setzen: Der Quelltext ist leichter lesbar, da man auf einen Blick den if-Zweig erkennt und ihn klar von den Anweisungen nach dem if-Zweig unterscheiden kann. Ein weiterer Grund ist, dass man oftmals ein Programm erweitert und wom├Âglich neue Anweisungen zum if-Zweig hinzuf├╝gt ÔÇô in diesem Fall sind die Klammern schon vorhanden, wenn man sich angew├Âhnt, sie immer zu setzen.

Die folgenden 2 Skripte sind gleichwertig zum Skript oben und verzichten auf die geschweiften Klammern:

# Eingabe x

if(x < 0)   x <- -x

# weitere Berechnungen
# Eingabe x

if(x < 0)
    x <- -x

# weitere Berechnungen

Es sollte klar sein: Wird die Leerzeile nach dem if-Zweig nicht gesetzt, kann man leicht das Ende des if-Zweiges ├╝bersehen und als Leser den Quelltext falsch interpretieren. (Wenn Sie also auf die geschweiften Klammern verzichten, setzen Sie zumindest die Leerzeile.) Syntaktisch korrekt sind alle drei Versionen.

Die Bedingung in if()

Oben wurde schon gesagt, dass in den runden Klammern ein logischer Ausdruck stehen muss, der zu TRUE oder FALSE ausgewertet werden kann. Das hei├čt aber, dass hier auch beliebige logische Verkn├╝pfungen eingesetzt werden k├Ânnen. Man sollte dabei bedenken, dass verschachtelte logische Ausdr├╝cke schwer nachvollziehbar sind und sich dort leicht Fehler einschleichen. Fehler nicht im Sinne der Syntax sondern semantische Fehler: Der logische Ausdruck liefert ein anderes als das beabsichtigte Ergebnis.

Als Faustregel kann man verwenden, dass logische Ausdr├╝cke mit mehr als zwei Verkn├╝pfungen schwer nachvollziehbar sind und geeignet vorbereitet werden sollten. Oder wer kann in 10 Sekunden sagen, welche Bedingung hier beschrieben wird und ob sie ├╝berhaupt erf├╝llt werden kann:

if(x > y && x > 5 && y > 2 && x < 2*y){
    # doSomething(x, y)
}

Wenn sich derartige Ausdr├╝cke nicht vermeiden lassen, sollte man sie zumindest ausreichend kommentieren.

Muss man Bedingungen ├╝ber Vektoren formulieren, sind oft Funktionen wie all() oder any() hilfreich, die in Vektoren in R: Anwendungen im Abschnitt Die Funktionen all() und any() beschrieben wurden.

Derartige Funktionen ÔÇô oder allgemein die Verarbeitung von Vektoren ÔÇô werfen sofort ein Problem auf: Was passiert, wenn in der Bedingung nicht ein logischer Ausdruck, sondern ein logischer Vektor steht?

Das folgende Skript untersucht zwei Vektoren auf Gleichheit:

v <- c(1, 1, 1)
w <- c(1, 1, 0)

if(v == w){
  cat(v, w)
}
# 1 1 1 1 1 0
# Warning message:
#   In if (v == w) { :
#       the condition has length > 1 and only the first element will be used

Zeile 1 und 2: Es werden zwei Vektoren der L├Ąnge 3 definiert.

Zeile 4: Die beiden Vektoren werden mit dem Vergleichs-Operator == verglichen. Dieser Vergleich liefert einen logischen Vektor der L├Ąnge 3, da jede der 3 Komponenten auf Gleichheit gepr├╝ft wird. Mehr zum Vergleichs-Operator findet man in Vektoren in R: Anwendungen im Abschnitt Der Vergleichsoperator und die Funktion identical().

Zeile 8 bis 10: An der Warnung erkennt man, wie R reagiert:

  • Es wird darauf hingewiesen, dass die Bedingung eine L├Ąnge gr├Â├čer als 1 hat.
  • Von den drei logischen Werten, die bei der Bedingungspr├╝fung entstehen, wird der erste Wert verwendet, das hei├čt es werden die ersten Komponenten der Vektoren v und w verglichen. Dieser Vergleich liefert TRUE und daher erfolgt die cat()-Ausgabe aus dem if-Zweig (Zeile 5).

Da man derartige Warnungen bei l├Ąngeren Skripten leicht ├╝bersehen kann, ist es ratsam, niemals den Vergleichs-Operator == in der Bedingungspr├╝fung einzusetzen (oder nur, wenn kein unerw├╝nschtes Verhalten auftreten kann). Durch den recycling-Mechanismus werden in R oft Vektoren angelegt, wo man eigentlich nur Zahlen erwartet. Zudem kann beim Vergleich mit == unerw├╝nschtes Verhalten eintreten, wenn ein Wert gleich NA ist.

M├Âchte man tats├Ąchlich mehrere Komponenten miteinander vergleichen und mit einem logischen Vektor in der Bedingungspr├╝fung arbeiten, gibt es ifelse, was sp├Ąter besprochen wird.

Die Alternative mit if() und else

Bei der Alternative findet wieder zuerst eine Bedingungspr├╝fung statt. Liefert sie TRUE, wird der if-Zweig durchlaufen, bei FALSE der else-Zweig. Es kann nicht vorkommen, dass beide Zweige durchlaufen werden.

Das folgende Skript nimmt das Beispiel aus der Einf├╝hrung auf (Pr├╝fung einer Zahl, ob sie gerade oder ungerade ist) und zeigt, wie man eine Alternative syntaktisch korrekt formuliert:

# Eingabe x

if(x %% 2 == 0){
    cat("x ist gerade\n")
} else {
    cat("x ist ungerade\n")
    }

# weitere Berechnungen

Man erkennt:

Zeile 3: Die Bedingungspr├╝fung sieht aus wie bei der reinen Bedingungspr├╝fung. Auch hier gilt, dass in den runden Klammern nur ein logischer Wert stehen darf.

Zeile 3 bis 5: Der if-Zweig befindet sich wie bei der Bedingungspr├╝fung in den geschweiften Klammern.

Zeile 5: Neu ist das Schl├╝sselwort else, das den Alternativ-Zweig oder kurz else-Zweig einleitet. Man beachte die Schreibweise:

  • Zuerst geht die geschweifte Klammer des if-Zweiges zu,
  • dann folgt das Schl├╝sselwort else,
  • danach ├Âffnet sich die geschweifte Klammer f├╝r den else-Zweig.

Zeile 5 bis 7: Der else-Zweig ist in geschweifte Klammern eingeschlossen.

Zeile 5 sieht unscheinbar aus, es ist aber wichtig, diese Konvention einzuhalten. Das folgende Skript scheint identisch zum vorhergehenden Skript zu sein, liefert aber einen Syntax-Fehler ÔÇô wer das Skript zum ersten Mal sieht, wird hier keinen Fehler vermuten:

# Eingabe x

if(x %% 2 == 0){
    cat("x ist gerade\n")
}
else {              # SYNTAX-FEHLER!
    cat("x ist ungerade\n")
}

# weitere Berechnungen

Im Vergleich zu oben wurde nur Zeile 5 in zwei Zeilen zerlegt; else steht jetzt in einer eigenen Zeile, wodurch der Quelltext sogar ├╝bersichtlicher wirkt. Aber er ist syntaktisch falsch: In der Entwicklungsumgebung sollte in Zeile 6 angezeigt werden:

unexpected token 'else'

Der Grund f├╝r den Fehler ist nicht so schwer aufzudecken: Vergleicht man das Skript mit einer echten Bedingungspr├╝fung, so erkennt man, dass Zeile 3 bis 5 eine vollst├Ąndige Bedingungspr├╝fung bilden. Und diese ist mit der geschweiften Klammer in Zeile 5 zu Ende.

Zeile 6 wird jetzt so interpretiert, dass ein neuer Befehl mit else startet ÔÇô aber das ist syntaktisch nicht korrekt. Das Schl├╝sselwort else kann nur innerhalb einer Alternative stehen und muss dann wie im Skript oben in der selben Zeile stehen wie die geschlossene geschweifte Klammer des if-Zweiges.

Tip:

Schreiben Sie die Alternative immer so wie im Skript oben; setzen Sie dazu insbesondere das Schl├╝sselwort else wie in Zeile 5. Verwenden
Sie zudem f├╝r beide Zweige die geschweiften Klammern, auch wenn sie eigentlich ├╝berfl├╝ssig sind.

Wie bei der Bedingungspr├╝fung kann man auch auf die geschweiften Klammern verzichten, wenn ein Zweig nur einen Befehl enth├Ąlt; syntaktisch korrekt ist somit auch:

# Eingabe x

if(x %% 2 == 0) cat("x ist gerade\n")   else    cat("x ist ungerade\n")

# weitere Berechnungen

Zu empfehlen ist diese Schreibweise nur in F├Ąllen, bei denen der Inhalt v├Âllig klar ist, bei schwierigen Befehlen ist der Quelltext schwer zu lesen.

Aufgabe:

Testen Sie das letzte Skript, wenn statt Eingabe x erfolgt: x <- NA . K├Ânnen Sie voraussagen, welcher Zweig durchlaufen wird?

Testen Sie das Skript (mit x <- NA ) wenn Sie if(x %% 2 == 0) ersetzen durch: if(identical(x %% 2, 0)) . K├Ânnen Sie voraussagen, welcher Zweig jetzt durchlaufen wird?

Bedingungspr├╝fung f├╝r einen logischen Vektor mit ifelse()

Oben wurde sowohl bei der Bedingungspr├╝fung als auch bei der Alternative betont, dass ein logischer Wert als Bedingung abgefragt wird. Mit ifelse kann man mehrere logische Werte abfragen, aber man sollte ifelse weniger als Kontrollstruktur verstehen, sondern als eine Funktion mit drei Eingabewerten:

ifelse(test, yes, no)

Dabei ist:

  • test ein logischer Vektor,
  • yes der R├╝ckgabewert im Falle, dass eine Komponente von test gleich TRUE ist,
  • no der R├╝ckgabewert im Falle, dass eine Komponente von test gleich FALSE ist.

Damit der R├╝ckgabewert gesetzt werden kann, sollten die drei Vektoren test, yes und no gleiche L├Ąnge haben, andernfalls wird der recycling-Mechanismus angewendet. Der R├╝ckgabewert von ifelse() ist ein Vektor mit derselben L├Ąnge wie test.

Ein Beispiel:

v <- as.integer( runif(n = 20, min = 1, max = 20) )
v
# [1]  5  4 10 18 19 13 12  1 19  5  2 16 12  4 12 11  5  2  9  5

w <- ifelse(test = v > 14, yes = NA, no =  v)
w
# [1]  5  4 10 NA NA 13 12  1 NA  5  2 NA 12  4 12 11  5  2  9  5

v[v > 14] <- NA
v
# [1]  5  4 10 NA NA 13 12  1 NA  5  2 NA 12  4 12 11  5  2  9  5

Zeile 1 bis 3: Es wird ein Vektor v mit Zufallszahlen (ganze Zahlen, zwischen 1 und 20) erzeugt und ausgegeben.

Zeile 5: Der Aufruf von ifelse() mit ifelse(test = v > 14, yes = NA, no = v) sorgt daf├╝r, dass:

  • Komponenten von v, die gr├Â├čer sind als 14, gleich NA gesetzt werden (entspricht etwa dem Beseitigen von Ausrei├čern),
  • die anderen Komponenten von v unver├Ąndert bleiben.

Zeile 6 und 7; Das Ergebnis wird im Vektor w abgespeichert und ausgegeben.

Zeile 9 bis 11: Man erkennt, dass der Vektor w auch ohne ifelse() erzeugt werden kann ÔÇô in diesem Fall sogar deutlich einfacher. Aber es sollte klar sein, dass man mit Hilfe von ifelse() Anweisungen schreiben kann, die sich sonst nicht als Einzeiler formulieren lassen.

Das folgende Beispiel zegt einen komplizierteren Aufruf von ifelse():

u <- as.integer( runif(n = 20, min = 1, max = 20) )
u
# [1]  3  4  4 11 14 17 18  9  5 19 15 17 15 12  5  5  2  5  5  6

v <- as.integer( runif(n = 20, min = 1, max = 20) )
v
# [1]  3  5  3  8  7 16 18  8 18  7  1  6  8  4  6  3  3  5  9 18

pmin(u, v)
# 1]  3  4  3  8  7 16 18  8  5  7  1  6  8  4  5  3  2  5  5  6

pmax(u, v)
# [1]  3  5  4 11 14 17 18  9 18 19 15 17 15 12  6  5  3  5  9 18

w <- ifelse( test = u > 10, yes = pmin(u, v), no =  pmax(u, v) )
w
# [1]  3  5  4  8  7 16 18  9 18  7  1  6  8  4  6  5  3  5  9 18

Oben wurde schon gesagt, dass man die Funktion ifelse() eigentlich nicht zu den Kontrollstrukturen z├Ąhlen sollte, da sie nicht den Ablauf eines Programmes steuert. Man sollte sie eher als Funktion verstehen, die den h├Ąufigsten Gebrauch der Alternative verallgemeinert. Damit ist folgendes gemeint: Die Alternative wird oft so eingesetzt, dass der Wert einer Variable in Abh├Ąngigkeit von einer Bedingung gesetzt wird, etwa:

# x ist eine Zahl, die fr├╝her berechnet wurde, 
# deren Wert aber im Programmablauf nicht bekannt ist

if(x < 0){
    y <- -1
} else {
    y <- 1
}

Dieses Beispiel einer Alternative ist so einfach, dass man es in eine Zeile schreiben kann:

if(x < 0)   y <- -1    else    y <- 1

Und an dieser Darstellung wird sofort klar, dass man die Alternative hier auch als Funktions-Aufruf verstehen kann:

Die Variable y wird durch eine Funktion gesetzt, die drei Eingabewerte besitzt, n├Ąmlich

  • ein logischer Ausdruck cond (hier x < 0) ),
  • der R├╝ckgabewert f├╝r den Fall, dass cond == TRUE ,
  • der R├╝ckgabewert f├╝r den Fall, dass cond == FALSE .

Verallgemeinert man diese Funktion im folgenden Sinn:

  • der logische Ausdruck cond ist ein logischer Vektor beliebiger L├Ąnge,
  • der R├╝ckgabewert ist ein Vektor mit der L├Ąnge von cond,

so hat man genau die Funktion ifelse() konstruiert.

Zusammenfassung

Informationen ├╝ber die Bedingungspr├╝fung mit if und die Alternative mit if else findet man in der Dokumentation unter ?Control im Paket base.

Die Bedingungspr├╝fung

Im allgemeinen Fall sieht eine Bedingungspr├╝fung folgenderma├čen aus:

if(cond){
    # Anweisungen im if-Zweig
}

Dabei ist cond ein logischer Ausdruck der L├Ąnge 1 und in den geschweiften Klammern stehen beliebig viele Anweisungen. Sie werden nur ausgef├╝hrt, wenn cond gleich TRUE ist.

Die Alternative

Im allgemeinen Fall sieht eine Alternative folgenderma├čen aus:

if(cond){
    # Anweisungen im if-Zweig
} else {
    # Anweisungen im else-Zweig
}

Auch hier ist cond ein logischer Ausdruck der L├Ąnge 1. Ist cond gleich TRUE, werden die Anweisungen im if-Zweig ausgef├╝hrt, ist cond gleich FALSE, werden die Anweisungen im else-Zweig ausgef├╝hrt.

Die Funktion ifelse()

Die Funktion ifelse() besitzt drei Eingabewerte, die Vektoren identischer L├Ąnge sein sollten:

ifelse(test, yes, no)

R├╝ckgabewert ist ein Vektor mit der L├Ąnge des logischen Vektors test, wobei der R├╝ckgabewert gleich yes[i] ist, falls test[i] gleich TRUE ist und no[i] , falls test[i] gleich FALSE ist. Falls yes und no nicht die geeigneten L├Ąngen haben, wird der recycling-Mechanismus angewendet.