1 Legende

Die folgende Notation wird in diesem Dokument benutzt:

ein_paar_R_befehle() #ein Kommentar dazu, wird von R ignoriert
## [1] "Die Ausgabe eines R-Befehls"

Aufgaben

2 Logische Variablen

“Die Wahrheit über WAHR und FALSCH” oder “Es gibt keine Zwischentöne”

Manche Fragen kann man nur mit WAHR oder FALSCH beantworten:

  • Bin ich angeschnallt?
  • Ist es später als 10 Uhr?
  • Schmeckt mir Lakritze?

Dafür gibt es in R und anderen Programmiersprachen einen eigenen Datentyp, die boole’sche (nach G. Boole) oder logische Variablen genannt werden. Diese können nur die beiden Werte TRUE (wahr) und FALSE (falsch) annehmen:

mir_gehts_gut <- TRUE
ich_hab_hunger <- FALSE

R akzeptiert auch die jeweils abgekürzten Formen T und F, diese sollten aber besser nicht verwendet werden.


TRUE und FALSE sind festeingebaute Konstanten für R. Diese können nicht verändert werden:

TRUE <- "nicht ganz wahr" #das geht so nicht!

Das gilt für T und F nicht. Diese sind zwar per Grundeinstellung mit TRUE und FALSE identisch:

T #gibt TRUE zurück
## [1] TRUE
F #gibt FALSE zurück
## [1] FALSE

, sie können aber auch (versehentlich) umdefiniert werden:

T = "jain" #gibt TRUE zurück
F = TRUE

Damit lässt sich dann viel Verwirrung stiften.


Bool’sche Variable werden z.B. aus Vergleichsoperationen erzeugt :

# Vergleich von Zahlenwerten
7 > 2 
## [1] TRUE
7 < 2 
## [1] FALSE
# Vergleich von Zeichenketten
"Golm" == "Potsdam"
## [1] FALSE

Der Test auf Gleichheit erfordert unbedingt das doppelte Gleichheitszeichen ==. Das einfache Gleichheitszeichen = entspricht der Zuweisung <- und hat damit eine andere Funktion!

# Vergleich von Zahlen auf Ungleichheit
pi != 3.333
## [1] TRUE

Die Ergebnisse derartiger Vergleiche kann man wiederum in logischen Variablen speichern, um später darauf zurückzugreifen:

niederschlag <- 0.5
regenwetter <- (niederschlag >= 1)
regenwetter
## [1] FALSE

2.1 Logische Operationen

Mit logischen Variablen lässt sich eine ganze Menge an ‘logischer Arithmetik’ betreiben:

!regenwetter # Negation
## [1] TRUE

Das Resultat hat also immer den entgegengesetzten Wahrheitswert.

kalt <- TRUE
erkaeltung <- regenwetter && kalt #logische UND-Verknüpfung
erkaeltung
## [1] FALSE

Das Resultat ist nur WAHR, wenn beide Seiten auch WAHR sind, sonst FALSCH.

rasensprenkler <- TRUE
rasen_feucht <- regenwetter || rasensprenkler  #logische ODER-Verknüpfung
rasen_feucht
## [1] TRUE

Das Resultat ist WAHR, sobald mindestens eine Seite WAHR ist.

mit_schokocreme = TRUE
mit_roestzwiebeln = TRUE

eierkuchen_schmeckt <- xor(mit_schokocreme, mit_roestzwiebeln) #exklusives ODER ("entweder-oder")

Nur wenn entweder die eine oder die andere Seite der Bedingung WAHR ist, wird das Ergebnis auch WAHR.

Aufgabe Wahrheitswerte

Welche Wahrheitswerte haben folgende Ausdrücke? Versuche erst, sie im Kopf auszuwerten, dann die Ausdrücke in R zu formulieren und das Ergebnis zu überprüfen.

Es sei A = WAHR, B = FALSCH.

  1. A und (nicht(B))
  2. nicht (nicht (A))
  3. entweder A oder nicht (B)
  1. WAHR
  2. WAHR
  3. FALSCH
 A <- TRUE
 B <- FALSE
 #1
 A && !B
## [1] TRUE
 2
## [1] 2
 !!A
## [1] TRUE
 3
## [1] 3
 xor(A, !B)
## [1] FALSE

3 Programmflusskontrolle A: Bedingungen mit if

Wenn das Wörtchen wenn nicht wär’…

Logische Ausdrücke bzw. Variablen sind unverzichtbar für Fallunterscheidungen, wie in wenn-dann-Beziehungen:

  • Wenn es regnet, dann nimm den Regenschirm mit.
  • Wenn der Dataframe eine Spalte namens temp hat, dann erzeuge ein Diagramm daraus.
  • Wenn Dich die bösen Buben locken, dann bleib zu Haus und stopfe Socken.

In R sieht das folgendermaßen aus:

regenwetter <- TRUE
if (regenwetter)
{
  print("Regenschirm eingepackt!")
}
## [1] "Regenschirm eingepackt!"

In der Klammer nach dem if steht also ein Ausdruck, der einer logischen Variable entspricht, also den Wert TRUE oder FALSE annimmt. Er kann damit auch deutlich komplizierter aufgebaut sein:

regenwetter <- TRUE
warmer_sommerregen <- FALSE
unterwegs_zur_regenschirmtauschboerse <- TRUE
if ((regenwetter && !warmer_sommerregen) || unterwegs_zur_regenschirmtauschboerse)
{
  print("Regenschirm eingepackt!")
}
## [1] "Regenschirm eingepackt!"

Die Konstruktion kann außerdem um eine Aktion ergänzt werden, die im nicht-zutreffenden Fall ausgeführt wird:

regenwetter <- FALSE
if (regenwetter)
  print("Regenschirm eingepackt!") else
  print("Schirm bleibt zu Hause!")
## [1] "Schirm bleibt zu Hause!"

Hier wird also immer nur die erste ODER die zweite Anweisung ausgeführt.

Das else gehört in die selbe Zeile wie die erste Anweisung.

Derartige Konstruktionen kann man mit Aktivitätsdiagrammen darstellen, die etwa so aussehen können: image/svg+xml (ggf. vorherige Anweisungen) Schirm einpacken regenwetter TRUE FALSE Schirm bleibt zu Hause (ggf. folgende Anweisungen)

Diese können insbesondere bei komplexeren Fällen hilfreich sein.

Aufgabe Sonnencreme

Jetzt wollen wir bei gutem Wetter nicht nur den Regenschirm zu Hause lassen, sondern auch noch die Sonnencreme einpacken. Wie muss der Code oben erweitert werden?

Die offensichtliche Lösung

regenwetter <- TRUE
if (regenwetter)
  print("Regenschirm eingepackt!") else
  print("Schirm bleibt zu Hause!")
## [1] "Regenschirm eingepackt!"
  print("Sonnencreme eingepackt!")
## [1] "Sonnencreme eingepackt!"

macht nicht ganz, was wir wollen, denn bei Regenwetter wird die Sonnencreme auch mitgenommen. Die Fallunterscheidung umfasst also jeweils nur den einen Befehl jeweils nach if und else. Sollen mehrere Befehle umfasst werden, Klammern wir diese in Blöcke mit Hilfe der geschweiften Klammern { ... }:

regenwetter <- TRUE
if (regenwetter)
{
  print("Regenschirm eingepackt!")
  print("Gummistiefel angezogen!")
} else
{
  print("Schirm bleibt zu Hause!")
  print("Sonnencreme eingepackt!")
}
## [1] "Regenschirm eingepackt!"
## [1] "Gummistiefel angezogen!"

Eine sorgfältige Formatierung des Quelltextes durch entsprechende Einrückungen ist zwar für den Computer nicht notwendig, erleichtert aber die Lesbarkeit des Programmcodes erheblich. Hierfür kann man in RStudio auch den entsprechenden Block markieren und mittels Strg+I die Einrückung automatisch korrigieren lassen.


Wenn es nicht gerade “alternativlos” ist, gibt es im richtigen Leben manchmal ja auch mehr als zwei Alternativen. Wir könnten unsere Tagesaustattung z.B. je nach Wetterlage zwischen Pudelmütze, Regenschirm und Sonnencreme wählen:

wetter <- "sonnig" #hier "frostig", "regnerisch" oder "sonnig" wählen
if (wetter == "frostig")
{
  print("Pudelmütze eingepackt!")
} else if (wetter == "regnerisch")
{
  print("Regenschirm eingepackt!")
} else if (wetter == "sonnig")
{
  print("Sonnencreme eingepackt!")
} else 
{
  print("Nichts eingepackt.")
}
## [1] "Sonnencreme eingepackt!"

Die haben wir also drei Fälle abgedeckt und sogar an eine vierte Variante gedacht, falls keiner der drei Fälle zutrifft, also für alle anderen Werte von wetter. Die geschweiften Klammern sind in diesem Fall optional, da ja immer nur ein Befehl auftaucht.


Eine derartige Mehrfachauswahl lässt sich auch mit dem Befehl switch() realisieren:

wetter <- "sonnig" #hier "frostig", "regnerisch" oder "sonnig" wählen

gegenstand <- switch(wetter, frostig="Pudelmütze", regnerisch="Regenschirm", sonnig="Sonnencreme") #"gegenstand" enthält das gewählte Accessoire

print(paste(gegenstand, "eingepackt!")) #"paste" klebt die Zeichenketten für gegenstand und "eingepackt!" aneinander
## [1] "Sonnencreme eingepackt!"

Aufgabe Sonnencreme mit switch(): In der obigen Lösung fehlt noch die Alternative für “alle restlichen Fälle”, die wir in der Konstruktion mit if mit "Nichts eingepackt!" abgedeckt hatten. Finde mit Hilfe der Hilfe zu switch() (aufrufen über ?switch) heraus, wie dies umgesetzt werden kann!

wetter <- "irgendwie anders" #hier "frostig", "regnerisch" oder "sonnig" wählen

gegenstand <- switch(wetter, frostig="Pudelmütze", 
regnerisch="Regenschirm", sonnig="Sonnencreme", "Nichts") #"gegenstand" enthält das gewählte Accessoire

print(paste(gegenstand, "eingepackt!")) #"paste" klebt die Zeichenketten für gegenstand und "eingepackt!" aneinander
## [1] "Nichts eingepackt!"

Für bestimmte Fälle mag switch nützlich sein. Es erlaubt jedoch keine (mehrteiligen) Befehle für die einzelnen Alternativen, sondern nur Wertzuweisung. Auch lassen sich zusammengesetzte logische Abfragen (z.B. temperatur > 0 && temperatur < 4) nicht umsetzen. Für längere Auswahllisten bieten sich übersichtlichere Methoden (z.B. mit match()) an. Daher wird switch eher seltener benutzt.


4 Logische Aggregatoren

Wer will sich schon um jedes einzelne Detail kümmern!?

Manchmal möchte man nun eine Bedingung prüfen, die vom Vergleich mehrerer Werte abhängt.

Aufgabe Frost

So könnten wir uns z.B. fragen, ob es während einer einer Woche Frost gegeben hat, also die Zeitreihe der Minimaltemperaturdaten t_min <- c(2, 1, 0, -1, 3, 2, 4) mindestens einmal unter 0 °C gefallen ist.

Wie könnte man diese Frage mit den bisher bekannten Mitteln beantworten?

t_min ist eine Vektor, auf dessen einzelne Werte sich so zugreifen lässt:

t_min <- c(2, 1, 0, -1, 3, 2, 4) 
t_min[1] #Wert des ersten Tages
## [1] 2
t_min[2] #Wert des zweiten Tages
## [1] 1
t_min <- c(2, 1, 0, -1, 3, 2, 4) 

frostwoche <- 
t_min[1] < 0 ||
t_min[2] < 0 ||
t_min[3] < 0 ||
t_min[4] < 0 ||
t_min[5] < 0 ||
t_min[6] < 0 ||
t_min[7] < 0 

frostwoche #Ergebnis ausgeben
## [1] TRUE

Diese Lösung ist allerdings sehr unelegant. Einerseits müssen wir dafür ziemlich viele Zeilen tippen, andererseits funktioniert die Lösung auch nur, solange wir genau sieben Werte im Vektor t_min haben. Füge einen achten Wert bei t_min an – dieser wird in der Prüfung nicht berücksichtigt, solange Du nicht auch die Abfrage veränderst.

4.1 Aggregierung mit any und all

Hier werden die logischen Aggregierungsfunktionen any und all nützlich. Diese prüfen, ob in einem Vektor von logischen Variablen mindestens ein Eintrag (any) bzw. alle Einträge (all) TRUE sind:

t_min <- c(2, 1, 0, -1, 3, 2, 4) 
tage_mit_frost <- t_min < 0

frost_woche    <- any(tage_mit_frost) 
eisige_woche   <- all(tage_mit_frost) 

frost_woche 
## [1] TRUE
eisige_woche
## [1] FALSE

Mit dieser Umsetzung funktioniert der Code nun auch mit beliebig langen Zeitreihen gleichermaßen.

Aufgabe Urlaub für any()

Unglücklicherweise hat die Funktion any() heute Urlaub. Wie kann man deren Funktionalität im o.g. Beispiel auch mittels all() ersetzen?

Die Woche hatte Frost, wenn nicht alle Tage ohne Frost waren.

t_min <- c(2, 1, 0, -1, 3, 2, 4) 
tage_mit_frost <- t_min < 0
frost_woche          <- !all(!tage_mit_frost) 
#oder ausführlicher:
tage_ohne_frost <- !tage_mit_frost
frost_woche          <- !all(tage_ohne_frost)
frost_woche
## [1] TRUE

4.2 Verkettung von logischen Vektoren

Wir haben mit der Variablen tage_mit_frost einen Vektor von Wahrheitswerten erzeugt, der für jeden Tag die Information enthält, ob es Frost gab:

t_min <- c(2, 1, 0, -1, 3, 2, 4, -2) 
tage_mit_frost <- t_min < 0
tage_mit_frost
## [1] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE

Man kann nun auch mehrere derartiger Vektoren benutzen, um mehrteilige Bedingungen zu prüfen. Dafür verwenden wir analog zu den bekannten Operatoren && und || nun deren vektorisierte Formen & und |. Diese führen die entsprechenden Operationen elementweise aus:

A <- c(TRUE, FALSE, TRUE, FALSE) 
B <- c(TRUE, TRUE, FALSE, FALSE) 
A & B #elementweises UND
## [1]  TRUE FALSE FALSE FALSE
A | B #elementweises ODER
## [1]  TRUE  TRUE  TRUE FALSE

Die versehentliche Verwendung von && und || statt & und | beschränkt die Operation auf die jeweils ersten Elemente beider Vektoren – das kann zu unerwartetem Verhalten führen:

A && B #nur das jeweils erste element wird berücksichtigt mit Warnung, oder Fehler
## Error in A && B: 'length = 4' in coercion to 'logical(1)'
A || B #nur das jeweils erste element wird berücksichtigt mit Warnung, oder Fehler
## Error in A || B: 'length = 4' in coercion to 'logical(1)'

Aufgabe Schnee

Nun haben wir neben den Temperaturdaten auch eine Niederschlagszeitreihe niederschlag <- c(0, 0, 3, 2, 0, 0, 0). Wir nehmen an, dass an Frosttagen mit Niederschlag Schnee gefallen sein könnte. Gab es Schneefall im betrachteten Zeitraum?
t_min        <- c(2, 1, 0, -1, 3, 2, 4) 
niederschlag <- c(0, 0, 3, 2, 0, 0, 0)
tage_mit_frost        <- t_min        < 0
tage_mit_niederschlag <- niederschlag > 0

tage_mit_schnee <- tage_mit_frost & tage_mit_niederschlag
any(tage_mit_schnee)
## [1] TRUE

Im obigen Beispiel wurde der Code teilweise so formatiert, dass analoge Strukturen bündig untereinander erscheinen. Dies ist für die Funktionsweise des Programms unerheblich, aber kann die Lesbarkeit des Codes deutlich erhöhen.

Wieviele Schneetage gab es? An welchen Tagen genau fiel der Schnee? Wie hoch ist die Summe des als Schnee gefallenen Niederschlags?

Da TRUE und FALSE in R wie 1 und 0 behandelt werden, ist die Funktion sum() hier hilfreich.

Um zu bestimmen, welche Elemente eines Vektors von Wahrheitswerten TRUE sind, verwende die Funktion which().

sum  (tage_mit_schnee) #Wieviele Schneetage gab es?
## [1] 1
which(tage_mit_schnee) #An welchen Tagen hat es geschneit?
## [1] 4
sum(niederschlag[which(tage_mit_schnee)]) #Niederschlagssumme der Schneetage
## [1] 2
#oder gleichbedeutend
sum(niederschlag[tage_mit_schnee]) #Niederschlagssumme der Schneetage
## [1] 2

5 Programmflusskontrolle B: Schleifen mit for

Stay DRY, don’t get WET!

Die naive Lösung der Frost-Aufgabe ist ein Beispiel für die Wiederholung von (nahezu) identischen Kommandos. Neben zusätzlichem Schreibaufwand birgt derartiges Vorgehen immer die Gefahr von zusätzlichen Fehlern. Außerdem sind Anpassungen im Code mit viel Aufwand verbunden. Dies spiegelt sich in der Programmiermaxime “Don’t repeat yourself!” (DRY) wider. Vermeide Wiederholungen von Code! Use ‘Style & Taste’, not ‘Copy & Paste’! Gegenteilige Lösungen werden auch als WET (“write everything twice” / “write every time” / “we enjoy typing” / “waste everyone’s time”) bezeichnet. Mögen diese anfangs schneller geschrieben und in Einzelfällen auch besser lesbar sein, überwiegen i.d.R. doch die Vorteile von DRY.

5.1 Beispiel Schneefegen

Gegeben sei ein Vektor von Tagen, an denen Schnee fällt tage_mit_schnee. Es soll nun für jeden Tag ausgegeben werden, ob wir aufstehen müsse, um Schnee zu fegen oder ausschlafen können. Ein WET-Lösung könnte so aussehen:

tage_mit_schnee = c(FALSE, FALSE, TRUE, FALSE, TRUE, TRUE, TRUE)
if (tage_mit_schnee[1]) print("Tag 1: Schneefegen!") else print("Tag 1: Ausschlafen!")
if (tage_mit_schnee[2]) print("Tag 2: Schneefegen!") else print("Tag 2: Ausschlafen!")
if (tage_mit_schnee[3]) print("Tag 3: Schneefegen!") else print("Tag 3: Ausschlafen!")
if (tage_mit_schnee[4]) print("Tag 4: Schneefegen!") else print("Tag 1: Ausschlafen!")
if (tage_mit_schnee[5]) print("Tag 5: Schneefegen!") else print("Tag 5: Ausschlafen!")
if (tage_mit_schnee[6]) print("Tag 6: Schneefegen!") else print("Tag 6: Ausschlafen!")
if (tage_mit_schnee[7]) print("Tag 7: Schneefegen!") else print("Tag 7: Ausschlafen!")
## [1] "Tag 1: Ausschlafen!"
## [1] "Tag 2: Ausschlafen!"
## [1] "Tag 3: Schneefegen!"
## [1] "Tag 1: Ausschlafen!"
## [1] "Tag 5: Schneefegen!"
## [1] "Tag 6: Schneefegen!"
## [1] "Tag 7: Schneefegen!"

Deutlich zu sehen: Ziemlich viel redundanter Code. Beim x-fachen Kopieren der Zeile habe ich glatt vergessen, für Tag 4 im zweiten Teil der Ausgabe die Zahl anzupassen. q.e.d.

Dies ist ein klassischer Fall für eine for-Schleife:

tage_mit_schnee <- c(FALSE, FALSE, TRUE, FALSE, TRUE, TRUE, TRUE)
for (zaehler in 1:7) #die Variable zaehler bekommt nacheinander die Werte 1 bis 7...
{                         #...und mit diesen wird jeweils der folgende Block ausgeführt
  if (tage_mit_schnee[zaehler])
    print(paste("Tag", zaehler,": Schneefegen!")) else 
    print(paste("Tag", zaehler,": Ausschlafen!"))
}
## [1] "Tag 1 : Ausschlafen!"
## [1] "Tag 2 : Ausschlafen!"
## [1] "Tag 3 : Schneefegen!"
## [1] "Tag 4 : Ausschlafen!"
## [1] "Tag 5 : Schneefegen!"
## [1] "Tag 6 : Schneefegen!"
## [1] "Tag 7 : Schneefegen!"
image/svg+xml Schnee fegen! tag mit schnee[zaehler] TRUE FALSE Ausschlafen! tag mit schnee[zaehler] TRUE FALSE zaehler <= 7 TRUE FALSE
Aktivitätsdiagramm for-Schleife

Feine Sache. Sollten wir uns nun entscheiden, statt Schnee zu fegen, liegenzubleiben, müssen wir das lediglich an einer einzigen Stelle im Code ändern. Faulheit siegt!

Aufgabe Schneefegen

Die Zeitreihe der Schneetage sei nun von unbekannter, d.h. variabler Länge. Passe die Schleife so an, dass sie für beliebige zufällige Zeitreihen tage_mit_schnee <- sample(x=c(TRUE, FALSE), size=runif(1, min=1, max=30), replace = TRUE) funktioniert!


sample() zieht zufällig mit aus dem Vektorx=c(TRUE, FALSE) mit Zurücklegen (replace = TRUE)) eine Anzahl von Elementen, die wiederum per Zufall aus dem Zahlenbereich 1 bis 30 (size=runif(1, min=1, max=30)) bestimmt werden.


In der for-Schleife folgt nach dem Schlüsselwort in ein Vektor, dessen Elemente iteriert werden, hier also 1:7, also von 1 bis 7. Sollte unser Vektor tage_mit_schnee also eine andere Länge aufweisen, könnte man diese mit length() bestimmen.

tage_mit_schnee <- sample(x=c(TRUE, FALSE), size=runif(1, min=1, max=30), replace = TRUE)
for (zaehler in 1:length(tage_mit_schnee)) #die Variable zaehler bekommt nacheinander die Werte > 1 bis zur Länge von tage_mit_schnee...
{                         #...und mit diesen wird jeweils der folgende Block ausgeführt
  if (tage_mit_schnee[zaehler])
    print(paste("Tag", zaehler,": Schneefegen!")) else 
    print(paste("Tag", zaehler,": Ausschlafen!"))
}
## [1] "Tag 1 : Ausschlafen!"
## [1] "Tag 2 : Ausschlafen!"
## [1] "Tag 3 : Ausschlafen!"
## [1] "Tag 4 : Schneefegen!"
## [1] "Tag 5 : Schneefegen!"
## [1] "Tag 6 : Ausschlafen!"
## [1] "Tag 7 : Schneefegen!"
## [1] "Tag 8 : Schneefegen!"
## [1] "Tag 9 : Ausschlafen!"
## [1] "Tag 10 : Ausschlafen!"
## [1] "Tag 11 : Ausschlafen!"
## [1] "Tag 12 : Schneefegen!"
## [1] "Tag 13 : Ausschlafen!"
## [1] "Tag 14 : Ausschlafen!"
Wir bekommen einen neuen Mitbewohner in unserem Haus und sind nur noch an ungeraden Tagen verantwortlich. Passe die Schleife durch a) Veränderung des Schleifenkopfs oder b) durch Veränderung des Schleifenkörpers an!

Auch hier hilft es, den zu iterierenden Vektor im letzten Teil der for()-Konstruktion zu verändern. Der seq()-Befehl bietet da vielfältige Möglichkeiten…

tage_mit_schnee <- sample(x=c(TRUE, FALSE), size=runif(1, min=1, max=30), replace = TRUE)
for (zaehler in seq(from=1, to=length(tage_mit_schnee), by=2)) #die Variable zaehler bekommt nacheinander alle ungeraden Werte von 1 bis zur Länge von tage_mit_schnee...
{                         #...und mit diesen wird jeweils der folgende Block ausgeführt
 if (tage_mit_schnee[zaehler])
   print(paste("Tag", zaehler,": Schneefegen!")) else 
   print(paste("Tag", zaehler,": Ausschlafen!"))
}
## [1] "Tag 1 : Schneefegen!"
## [1] "Tag 3 : Schneefegen!"
## [1] "Tag 5 : Ausschlafen!"
## [1] "Tag 7 : Ausschlafen!"
## [1] "Tag 9 : Ausschlafen!"

Ob eine Zahl gerade oder ungerade ist, kann man mit dem Modulo-Operator %% ermitteln. Dieser gibt den jeweiligen Rest bei ganzzahliger Division zurück. So ist 8 %% 3 gleich 2, denn wenn man 8 durch 3 teilt, bleibt der Rest 2.

tage_mit_schnee <- sample(x=c(TRUE, FALSE), size=runif(1, min=1, max=30), replace = TRUE)
for (zaehler in 1:length(tage_mit_schnee)) #die Variable zaehler bekommt nacheinander alle >ungerade Werte von 1 bis zur Länge von tage_mit_schnee...
{                         #...und mit diesen wird jeweils der folgende Block ausgeführt
 if (zaehler %% 2 == 1)
   if (tage_mit_schnee[zaehler])
     print(paste("Tag", zaehler,": Schneefegen!")) else 
     print(paste("Tag", zaehler,": Ausschlafen!"))
}
## [1] "Tag 1 : Ausschlafen!"
## [1] "Tag 3 : Schneefegen!"
## [1] "Tag 5 : Ausschlafen!"
## [1] "Tag 7 : Schneefegen!"
## [1] "Tag 9 : Ausschlafen!"
## [1] "Tag 11 : Ausschlafen!"
## [1] "Tag 13 : Schneefegen!"
## [1] "Tag 15 : Schneefegen!"
## [1] "Tag 17 : Schneefegen!"
## [1] "Tag 19 : Schneefegen!"

Die letzte Aufage soll nun noch darum erweitert werden, dass in der Ausgabe auch der Wochentag auftaucht. Tag 1 soll dabei immer der Montag sein.

Ähnlich wie bei b) hilft hier auch der Modulo-Operator %%. Mit seiner Hilfe lässt sich ein geeigneter Vektor ansteuern, der die Namen der Wochentage enthält.

tage_mit_schnee <- sample(x=c(TRUE, FALSE), size=runif(1, min=1, max=30), replace = TRUE)
wochentage <- c("Mo", "Di", "Mi", "Do", "Fr", "Sa", "So")
for (zaehler in seq(from=1, to=length(tage_mit_schnee), by=2)) #die Variable zaehler bekommt nacheinander alle ungeraden Werte von 1 bis zur Länge von tage_mit_schnee...
{                         #...und mit diesen wird jeweils der folgende Block ausgeführt
 index_wochentag <- ((zaehler-1) %% 7)+1 #berechnet die Nummer des Wochentags (1 - 7)
 if (tage_mit_schnee[zaehler])
   print(paste("Tag", zaehler,"(", wochentage[index_wochentag],"): Schneefegen!")) else 
   print(paste("Tag", zaehler,"(", wochentage[index_wochentag],"): Ausschlafen!"))
}
## [1] "Tag 1 ( Mo ): Schneefegen!"
## [1] "Tag 3 ( Mi ): Ausschlafen!"
## [1] "Tag 5 ( Fr ): Schneefegen!"
## [1] "Tag 7 ( So ): Ausschlafen!"
## [1] "Tag 9 ( Di ): Ausschlafen!"
## [1] "Tag 11 ( Do ): Ausschlafen!"

6 Fehlwerte in logischen Vergleichen

Computer machen keine Fehler, aber sie können Fehlwerte produzieren

6.1 Beispiel Niederschlagsintensität

Gegeben sind die Zeitreihen der Niederschlagshöhe n_hoehe [mm] und der zugehörigen Niederschlagsdauer n_dauer [h]. Wenn die Niederschlagsintensität [mm/h] höher als 20 mm/h ausfällt, soll eine Warnung ausgegeben werden. Ein naiver Ansatz könnte so aussehen :

n_hoehe <- c(20, 25, 0, 15, 30, 12, 51, 0) #Niederschlagshöhe  [mm]
n_dauer <- c( 3,  1, 0,  4,  4,  5,  2, 0) #Niederschlagsdauer [h]

intensitaet <- n_hoehe / n_dauer

for (zaehler in 1:length(intensitaet))
  if (intensitaet[zaehler] > 20)
    print(paste("Intensivniederschlag am Tag", zaehler))
## Error in if (intensitaet[zaehler] > 20) print(paste("Intensivniederschlag am Tag", : Fehlender Wert, wo TRUE/FALSE nötig ist
## [1] "Intensivniederschlag am Tag 2"

Bis zum Tag 2 funktioniert die Schleife noch, dann erzeugt sie die Meldung missing value where TRUE/FALSE needed. Das Problem ist in der Tat die Intensität, die für die Tage 3 und 7 aufgrund der Division durch 0 nicht berechnet werden kann:

intensitaet <- n_hoehe / n_dauer
intensitaet
## [1]  6.666667 25.000000       NaN  3.750000  7.500000  2.400000 25.500000
## [8]       NaN

Für derartige Operationen ohne Ergebnis liefert R den ‘Sonderwert’ NaN (“not a number”). Weitere typische ‘Sonderwerte’, die atypische Resultate kennzeichnen, sind +/-Inf (“Infinity”), NA (“not availabe”), NULL (nicht gesetzt) und integer(0) (Integer-Vektor mit Länge 0) . Mit diesen sind dann Vergleiche oder weitere Operationen meist nicht auswertbar:

a <- 0/0 #liefert NaN
a > 3    #liefert NA -> nicht als TRUE oder FALSE auswertbar
## [1] NA
b <- 1/0 #liefert Inf
b > 3    #liefert TRUE -> auswertbar
## [1] TRUE
c <- as.numeric("3komma5") #liefert NA (plus Warnung)
c > 3 #liefert NA -> nicht als TRUE oder FALSE auswertbar
## [1] NA
eine_liste <- list(X=2, Y=3) #eine Liste mit zwei Elementen
d <- eine_liste$Z            #liefert NULL
d > 3  #liefert logical(0) -> nicht als TRUE oder FALSE auswertbar
## logical(0)
e <- which(letters == "ö") #liefert integer(0)
e > 3    #liefert logical(0) -> nicht als TRUE oder FALSE auswertbar
## logical(0)

Ist also ein Ergebnis potenziell von solchen Sonderwerten (`NA, NaN, Inf, NULL, logical()) betroffen, sollte seine (un-)Gültigkeit überprüft werden. Dafür eignen sich die folgenden Funktionen:

# Erkennen von NULL und logical(0)
b <- NULL
length(b) == 0 #liefert TRUE für NULL und logical(0)
## [1] TRUE

Noch spezifischer geht es mit is.null().

# Erkennen von NA, NaN und Inf
a <- NaN
is.finite(a) # liefert TRUE für alle regulären Zahlen
## [1] FALSE

Noch spezifischer geht es mit is.na(), is.nan() und is.finite().

Aufgabe Starkniederschlag

Behebe mit diesem Wissen den naiven Ansatz von oben.
n_hoehe <- c(20, 25, 0, 15, 30, 12, 51, 0) #Niederschlagshöhe  [mm]
n_dauer <- c( 3,  1, 0,  4,  4,  5,  2, 0) #Niederschlagsdauer [h]

intensitaet <- n_hoehe / n_dauer

for (zaehler in 1:length(intensitaet))
 if (is.finite(intensitaet[zaehler]) && intensitaet[zaehler] > 20)
   print(paste("Intensivniederschlag am Tag", zaehler))
## [1] "Intensivniederschlag am Tag 2"
## [1] "Intensivniederschlag am Tag 7"

Alternativ könnte man auch die Variable intensitaet von allen Sonderwerten bereinigen und die Schleife unverändert lassen: intensitaet[!is.finite(intensitaet)] <- 0

In der obigen Lösung taucht die Passage is.finite(intensitaet[zaehler]) && intensitaet[zaehler] > 20 auf. Im “kritischen Fall” wird hier

is.finite(NaN) && NaN > 20, also

FALSE && NA

ausgewertet, was zu einem gültigen FALSE auswertet wird. In der UND-Verknüpfung ist es nämlich egal, welcher Wahrheitswert an der Stelle des NA steht – das Ergebnis ist in beiden Fällen FALSE.

Aufgabe NA-Verknüpfung

In welcher anderen logischen Operation kann ein NA-Wert verknüpft und dennoch ein gültiger Wahrheitswert erhalten werden?
TRUE || NA   #liefert TRUE
## [1] TRUE
NA   || TRUE #liefert TRUE
## [1] TRUE

Aufgrund dieser Möglichkeit funktionieren auch

a <- c(1, 2, NA, 4, 5) #ein Zahlenvektor mit NA-Element
any(a < 6)
all(a > 6)
## [1] TRUE
## [1] FALSE

Die komplementären Varianten (Relationszeichen umgedreht)

any(a > 6)
all(a < 6)
## [1] NA
## [1] NA

liefern allerdings keine logisch gültigen Ergebnisse.

Aufgabe NA-Werte in Aufrufen von any() und all()

Überprüfe mittels der Hilfe zu any(), wie durch ein geeignetes Zusatzargument zu any() und all() ein gültiger Wahrheitswert erzeugt werden kann!
any(a > 6, na.rm=TRUE)
## [1] FALSE
all(a < 6, na.rm=TRUE)
## [1] TRUE

Das Argument na.rm=TRUE ist auch für viele andere aggregierende Funktionen (z.B. max(), mean(), sd() u.s.w.) nützlich, wenn NA-Werte ignoriert werden sollen.

7 Programmflusskontrolle C: Schleifen mit while

Kein Ende in Sicht.

In den vorherigen Beispielen war die notwendige Anzahl von Iterationen durch die Schleifen vorbestimmt, z.B. durch die Länge des betreffenden Vektors. Denkbar sind jedoch auch Situationen, wo dies nicht der Fall ist.

7.1 Beispiel Windkraftanlage

An einer Windkraftanlage befindet sich ein Windmesser, der die Windgeschwindigkeit v_wind misst (wir simulieren diese Messung hier der Einfachheit halber mit dem Zufallsgenerator). Die Anlage soll laufen, solange die Windgeschwindigkeit unter 20 m/s liegt und sich bei einer höheren Geschwindigkeit abschalten.

v_wind <- 5  #Anfangswert der Windgeschwindigkeit
v_max  <- 20 #Abschaltgeschwindigkeit

while (v_wind < v_max)
{
  print(paste("Wind:", v_wind,"; Anlage läuft."))
  v_wind <- max(0, v_wind + runif(n = 1, min=-v_wind/2, max=(1.2*v_max-v_wind))/2) #Windmesser: hier als zufällige Änderung der Windgeschwindigkeit
}
print(paste("Wind:", v_wind,"; Anlage gestoppt."))
## [1] "Wind: 5 ; Anlage läuft."
## [1] "Wind: 14.2562358193099 ; Anlage läuft."
## [1] "Wind: 14.3825638083073 ; Anlage läuft."
## [1] "Wind: 15.5009909051614 ; Anlage läuft."
## [1] "Wind: 15.9591274381244 ; Anlage läuft."
## [1] "Wind: 15.6489067684062 ; Anlage läuft."
## [1] "Wind: 17.9626696496571 ; Anlage läuft."
## [1] "Wind: 20.2253514771487 ; Anlage gestoppt."

Wie auch bei der for()-Schleife wird der zu wiederholende Code-Block mit {...} eingeklammert. Der Unterschied besteht darin, dass while() den logischen Ausdruck in der Klammer als Laufbedingung auswertet (hier v_wind < 20) und dies nicht an eine Zählervariable knüpft. Die Anzahl der Schleifendurchläufe steht also nicht von vornherein fest und ändert sich je nach der zufälligen Ausprägung des Windes.

Die Schleifenbedingung wird immer am Anfang, d.h. vor dem auszuführenden Block, geprüft. Wenn sie schon von Anfang an FALSCH ist (z.B. wenn `v_wind mit 21 initialisiert wird), wird die Schleife gar nicht ausgeführt.

Aufgabe Windkraftanlage

Die Anlage soll nun nach maximal 10 Zyklen gestoppt werden, um gewartet werden zu können. Ist die Maximalgeschwindigkeit vorher erreicht, wird wie bisher auch angehalten. Der Wert des Zählerstands der Zyklen soll zusätzlich zur Windgeschwindigkeit ausgegeben werden.
v_wind <- 5  #Anfangswert der Windgeschwindigkeit
v_max  <- 20 #Abschaltgeschwindigkeit
zaehler <- 0 #zählt die Schleifendurchläufe

while (v_wind < v_max && zaehler < 10)
{
 print(paste("Wind:", v_wind,"; Zähler:", zaehler, ": Anlage läuft."))
 v_wind <- max(0, v_wind + runif(n = 1, min=-v_wind/2, max=(1.2*v_max-v_wind))/2) #Windmesser; hier zufällige Änderung der Windgeschwindigkeit
 zaehler <- zaehler + 1            #Zähler hochzählen
}
print(paste("Wind:", v_wind,"; Anlage gestoppt."))
## [1] "Wind: 5 ; Zähler: 0 : Anlage läuft."
## [1] "Wind: 10.7449684150051 ; Zähler: 1 : Anlage läuft."
## [1] "Wind: 9.505662451474 ; Zähler: 2 : Anlage läuft."
## [1] "Wind: 10.0059851794733 ; Zähler: 3 : Anlage läuft."
## [1] "Wind: 14.9476234594655 ; Zähler: 4 : Anlage läuft."
## [1] "Wind: 13.6457636464201 ; Zähler: 5 : Anlage läuft."
## [1] "Wind: 18.3985388759992 ; Zähler: 6 : Anlage läuft."
## [1] "Wind: 15.9774202361598 ; Zähler: 7 : Anlage läuft."
## [1] "Wind: 18.986741196341 ; Zähler: 8 : Anlage läuft."
## [1] "Wind: 16.9427055740007 ; Zähler: 9 : Anlage läuft."
## [1] "Wind: 18.9858561476843 ; Anlage gestoppt."

8 Programmflusskontrolle D: Schleifen-Steuerung mit next und break

Abkürzen und aus der Routine ausbrechen

Innerhalb von for und while-Schleifen lässt sich mit den Befehlen next und break der Programmablauf noch flexibler gestalten: next springt dabei wieder an den Schleifenanfang, während break die Schleife verlässt. Beide Befehle beziehen sich immer nur auf die innerste Schleife. Bei geschachtelten Schleifen werden also die äußeren Schleifen weiter ausgeführt.

8.1 Beispiel Unvollständiges Alphabet

Fritzchen soll fünf mal das Alphabet aufsagen. Er kennt es aber nur bis zum “G”, und das “D” vergisst er auch jedes Mal:

print (LETTERS) #LETTERS ist ein voreingestellter Vektor aller Großbuchstaben

for (zaehler in 1:5) #fünf mal Aufsagen
{
  print(zaehler) 
  for (buchstabe in LETTERS)
  {
    if (buchstabe =="D") next   #das D wird vergessen
    print(buchstabe)
    if (buchstabe =="G") break  #weiter als G kann Fritzchen nicht
  }
}
##  [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S"
## [20] "T" "U" "V" "W" "X" "Y" "Z"
## [1] 1
## [1] "A"
## [1] "B"
## [1] "C"
## [1] "E"
## [1] "F"
## [1] "G"
## [1] 2
## [1] "A"
## [1] "B"
## [1] "C"
## [1] "E"
## [1] "F"
## [1] "G"
## [1] 3
## [1] "A"
## [1] "B"
## [1] "C"
## [1] "E"
## [1] "F"
## [1] "G"
## [1] 4
## [1] "A"
## [1] "B"
## [1] "C"
## [1] "E"
## [1] "F"
## [1] "G"
## [1] 5
## [1] "A"
## [1] "B"
## [1] "C"
## [1] "E"
## [1] "F"
## [1] "G"

Die Funktionalität vonbreak und next lässt sich oft auch vollständig mit if()-Konstruktionen lösen. Insbesondere bei komplexen Schleifen erleichtern sie aber erheblich die Lesbarkeit.

Aufgabe Windkraftanlage 2

Baue die Lösung der vorherigen Windkraft-Aufgabe in eine for()-Schleife um und nutze break, um bei zu hohen Geschwindigkeiten abzubrechen.
v_wind <- 5  #Anfangswert der Windgeschwindigkeit
v_max  <- 20 #Abschaltgeschwindigkeit

for (zaehler in 0:9)
{
 # Windmesser; hier als zufällige Änderung der Windgeschwindigkeit
 v_wind <- max(0, v_wind + runif(n = 1, min=-v_wind/2, max=(1.2*v_max-v_wind))/2) 
 if (v_wind >= v_max) break
 print(paste("Wind:", v_wind,"; Zähler:", zaehler, ": Anlage läuft."))
}
print(paste("Wind:", v_wind,"; Anlage gestoppt."))
## [1] "Wind: 4.26763728575315 ; Zähler: 0 : Anlage läuft."
## [1] "Wind: 11.408649468654 ; Zähler: 1 : Anlage läuft."
## [1] "Wind: 14.3857726856628 ; Zähler: 2 : Anlage läuft."
## [1] "Wind: 18.1846328307549 ; Zähler: 3 : Anlage läuft."
## [1] "Wind: 21.0098801380429 ; Anlage gestoppt."
Bei Geschwindigkeiten von 12 bis 20 soll die Anlage nun außerdem gedrosselt werden. Setze dies unter der Verwendung von next() um!
v_wind <- 5  #Anfangswert der Windgeschwindigkeit
v_max  <- 20 #Abschaltgeschwindigkeit

for (zaehler in 0:9)
{
  # Windmesser; hier als zufällige Änderung der Windgeschwindigkeit
  v_wind <- max(0, v_wind + runif(n = 1, min=-v_wind/2, max=(1.2*v_max-v_wind))/2)
  if (v_wind >= v_max) break
  if (v_wind >= 12) 
  {  
    print(paste("Wind:", v_wind,"; Zähler:", zaehler, ": Anlage gedrosselt."))
    next
  }
  
  print(paste("Wind:", v_wind,"; Zähler:", zaehler, ": Anlage läuft."))
}
print(paste("Wind:", v_wind,"; Anlage gestoppt."))
## [1] "Wind: 7.55917475145543 ; Zähler: 0 : Anlage läuft."
## [1] "Wind: 15.2293199418616 ; Zähler: 1 : Anlage gedrosselt."
## [1] "Wind: 15.9468353766853 ; Zähler: 2 : Anlage gedrosselt."
## [1] "Wind: 15.0478120198314 ; Zähler: 3 : Anlage gedrosselt."
## [1] "Wind: 14.9084241963836 ; Zähler: 4 : Anlage gedrosselt."
## [1] "Wind: 11.19952928895 ; Zähler: 5 : Anlage läuft."
## [1] "Wind: 11.1481656339799 ; Zähler: 6 : Anlage läuft."
## [1] "Wind: 9.33169207751845 ; Zähler: 7 : Anlage läuft."
## [1] "Wind: 14.6822076462615 ; Zähler: 8 : Anlage gedrosselt."
## [1] "Wind: 14.3019414213913 ; Zähler: 9 : Anlage gedrosselt."
## [1] "Wind: 14.3019414213913 ; Anlage gestoppt."

Finde alle Primzahlen von 1 bis 100! Nutze dazu die Modulo-Funktion (siehe Hinweis Schneefegen). Liefere am Ende einen Vektor von Wahrheitswerten ist_primzahl[1:100], in dem die Einträge für Primzahlen TRUE sind.
ist_primzahl <- array(TRUE, 100) #alle Zahlen gelten zunächst als Primzahl, bis wir das Gegenteil bewiesen haben

for (zahl in 2:100)
{
 for (teiler in 2:(zahl-1)) #teste alle möglichen Teiler bis kurz unterhalb der Zahl selbst
 {   
   if (zahl %% teiler == 0) 
   {
     ist_primzahl[zahl] <- FALSE  
     break
   }
 }
}
  
which(ist_primzahl)
##  [1]  1  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
Die vorangegangene Lösung ist noch optimierbar: Beim Testen der möglichen Teiler müssen wir nicht die Teilbarkeit durch alle kleineren Zahlen, sondern nur durch alle kleineren Primzahlen testen. Optimiere die Lösung!
ist_primzahl <- array(TRUE, 100) #alle Zahlen gelten zunächst als Primzahl, bis wir das Gegenteil bewiesen haben

for (zahl in 2:100)
{
 for (teiler in 2:(zahl-1)) #teste alle möglichen Teiler bis kurz unterhalb der Zahl selbst
 {   
   if (!ist_primzahl[teiler]) next #nicht-primzahlige Teiler müssen nicht getestet werden
   if (zahl %% teiler == 0) 
   {
     ist_primzahl[zahl] <- FALSE  
     break
   }
 }
}
  
which(ist_primzahl)
##  [1]  1  3  4  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89
## [26] 97


Wir wollen eine Methode zur Warnung potenziell infizierter Personen (“Kontaktverfolgung”) schreiben. Gegeben ist dazu eine Tabelle begegnungszeit der Begegnungszeiten:

set.seed(2) #um vergleichbare Zufallsergebnisse zu erhalten

anzahl_personen <- 6
anzahl_tage <- 20

#erzeugt eine Zufallsmatrix der Begegnungszeiten mit Zahlen von 0 bis 1
begegnungszeit <- round(anzahl_tage*matrix(runif(n=anzahl_personen^2 ), nrow = anzahl_personen))
begegnungszeit[lower.tri(begegnungszeit)] = t(begegnungszeit)[lower.tri(begegnungszeit)]
diag(begegnungszeit) <- 0 #Zeitpunkt der Begegnung mit sich selbst
begegnungszeit
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]    0    3   15    9    7    0
## [2,]    3    0    4    1   10    3
## [3,]   15    4    0   13    3   16
## [4,]    9    1   13    0    7   17
## [5,]    7   10    3    7    0   10
## [6,]    0    3   16   17   10    0

Person 1 hat also Person 2 am Tag 3 getroffen. Die Tabelle ist daher symmetrisch.

Am Tag 10 hat sich Person 1 infiziert:

infektionszeit <- array(Inf, anzahl_personen) #Infektionszeit für jede Person. Inf bedeutet hier "nicht infiziert"
infektionszeit[1] <- 10 #setze die Infektionszeit von Person 1 auf Tag 10
Bestimme die zu warnenden Personen anhand aller Begegnungszeiten, die wir als potenziell infektiös betrachten!
set.seed(2) #um vergleichbare Zufallsergebnisse zu erhalten

anzahl_personen <- 6
anzahl_tage <- 20

#erzeugt eine Zufallsmatrix der Begegnungszeiten mit Zahlen von 0 bis 1
begegnungszeit <- round(anzahl_tage*matrix(runif(n=anzahl_personen^2 ), nrow = anzahl_personen))
begegnungszeit[lower.tri(begegnungszeit)] = t(begegnungszeit)[lower.tri(begegnungszeit)]
diag(begegnungszeit) <- 0 #Zeitpunkt der Begegnung mit sich selbst

infektionszeit <- array(Inf, anzahl_personen)
infektionszeit[1] <- 10 #setze die Infektionszeit von Person 1
erstinfektionen <- TRUE

while(any(erstinfektionen))
{
 erstinfektionen = FALSE #verhindert die Endlosschleife, falls gar keine Neuinfektionen stattfinden
 for (infizierter in which(!is.na(infektionszeit))) #iteriere durch alle bekannten Infizierten
 {
   infektionen <- begegnungszeit[infizierter,] > infektionszeit[infizierter]
   if(!any(infektionen)) next
   erstinfektionen <- infektionszeit[infektionen] > begegnungszeit[infizierter,infektionen]
   #sind die gefundenen Infektionen Erstinfektionen?
   infektionszeit[infektionen] <- pmin(infektionszeit[infektionen], begegnungszeit[infizierter,infektionen])
   if (any(erstinfektionen)) break 
   #neue Infektionen entdeckt, starte Überprüfung von vorn
 }
}

infektionszeit #gib die Infektionszeiten aller Personen an
## [1]  10 Inf  15  17 Inf  16