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
“Die Wahrheit über WAHR und FALSCH” oder “Es gibt keine Zwischentöne”
Manche Fragen kann man nur mit WAHR oder FALSCH beantworten:
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
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.
- A und (nicht(B))
- nicht (nicht (A))
- entweder A oder nicht (B)
- WAHR
- WAHR
- FALSCH
A <- TRUE B <- FALSE #1 A && !B
## [1] TRUE
2
## [1] 2
!!A
## [1] TRUE
3
## [1] 3
xor(A, !B)
## [1] FALSE
if
Wenn das Wörtchen wenn nicht wär’…
Logische Ausdrücke bzw. Variablen sind unverzichtbar für Fallunterscheidungen, wie in wenn-dann-Beziehungen:
temp
hat, dann
erzeuge ein Diagramm daraus.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:
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
undelse
. 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 mitif
mit"Nichts eingepackt!"
abgedeckt hatten. Finde mit Hilfe der Hilfe zuswitch()
(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.
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.
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 mittelsall()
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
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 Niederschlagszeitreiheniederschlag <- 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
undFALSE
in R wie 1 und 0 behandelt werden, ist die Funktionsum()
hier hilfreich.Um zu bestimmen, welche Elemente eines Vektors von Wahrheitswerten
TRUE
sind, verwende die Funktionwhich()
.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
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.
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!"
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üsselwortin
ein Vektor, dessen Elemente iteriert werden, hier also1:7
, also von 1 bis 7. Sollte unser Vektortage_mit_schnee
also eine andere Länge aufweisen, könnte man diese mitlength()
bestimmen.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!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!"
Auch hier hilft es, den zu iterierenden Vektor im letzten Teil der
for()
-Konstruktion zu verändern. Derseq()
-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 ist8 %% 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!"
Computer machen keine Fehler, aber sie können Fehlwerte produzieren
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
Überprüfe mittels der Hilfe zuany()
undall()
any()
, wie durch ein geeignetes Zusatzargument zuany()
undall()
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.
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.
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."
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.
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 einefor()
-Schleife um und nutzebreak
, um bei zu hohen Geschwindigkeiten abzubrechen.Bei Geschwindigkeiten von 12 bis 20 soll die Anlage nun außerdem gedrosselt werden. Setze dies unter der Verwendung vonv_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."
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 Wahrheitswertenist_primzahl[1:100]
, in dem die Einträge für PrimzahlenTRUE
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:
Bestimme die zu warnenden Personen anhand aller Begegnungszeiten, die wir als potenziell infektiös betrachten!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
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