x <- 1
Automatisieren
In der heutigen Sitzung lernen wir:
- Conditionals und Control Flow
- Funktionen erstellen
- Loops anwenden
Wir nun zwei Programmierkonzepte kennenlernen, die uns dabei helfen, Tasks zu automatisieren. Wir werden hier nicht in die Tiefe gehen; es geht uns vielmehr darum, Ihnen einen Überblick zu geben, was Sie mit diesen Konzepten machen können. Falls Sie tiefer in die Materie einsteigen möchten, gibt es entsprechende Kurse auf Datacamp.
- Falls Sie eine Einführung in Programmierkonzepte (Conditionals and Control Flow, Functions, Loops) benötigen, empfehlen wir Ihnen den Datacamp Kurs Intermediate R.
Conditionals und Control Flow
Conditionals und Control Flow sind Konzepte, die es uns ermöglichen, den Programmablauf zu steuern. Wir können damit zum Beispiel entscheiden, ob ein bestimmter Codeblock ausgeführt wird oder nicht. Wir können auch den Programmablauf abhängig von bestimmten Bedingungen steuern. Dies ist nützlich, wenn wir zum Beispiel eine bestimmte Aktion nur dann ausführen wollen, wenn eine Bedingung erfüllt ist.
Als simples Beispiel wollen wir eine Zahl x
definieren und prüfen, ob diese grösser als 0 ist. Wenn ja, dann wollen wir die Zahl ausgeben. Wenn nein, dann wollen wir eine Fehlermeldung ausgeben. Um diesen Test x > 0
auszuführen, können wir if
verwenden. if
testet, ob eine Bedingung wahr ist. Falls ja, wird ein Codeblock ausgefĂĽhrt. Der Codeblock wird in geschweiften Klammern {}
eingeschlossen. Wenn die Bedingung nicht erfüllt ist, dann wird der Codeblock nicht ausgeführt. Falls die Bedingung nicht wahr ist, können wir eine Alternative mit else
angeben. Der Codeblock, der ausgefĂĽhrt wird, wenn die Bedingung nicht wahr ist, wird ebenfalls in geschweiften Klammern {}
eingeschlossen.
Ă„ndern sie den Wert von x
und fĂĽhren Sie den Codeblock erneut aus. Was passiert, wenn x
negativ ist?
Wir können nun auch mehrere Bedingungen testen. Dazu verwenden wir if
, else if
und else
. else if
ist eine Alternative zu else
, wenn die erste Bedingung nicht wahr ist. else if
wird dann ausgefĂĽhrt, wenn die erste Bedingung nicht wahr war. else
wird nur dann ausgefĂĽhrt, wenn alle vorherigen Bedingungen nicht wahr waren.
Zum Beispiel wollen nun ebenfalls prĂĽfen, ob x
gleich 0 ist. Falls ja, dann wollen wir eine andere Fehlermeldung ausgeben. Falls nein, dann wollen wir die Fehlermeldung ausgeben, die wir bereits definiert haben.
if (x > 0) {
print(x)
} else if (x == 0) {
print("x is 0")
} else {
print("x is not greater than 0")
}
[1] 1
Ă„ndern sie den Wert von x
erneut und fĂĽhren Sie den Codeblock aus. Was passiert, wenn x
Null ist?
Funktionen
In R kann man Funktionen selber definieren. Dies ist einerseits nĂĽtzlich, wenn man etwas berechnen will, es in R aber dafĂĽr keine Funktion gibt; andererseits sind Funktion nĂĽtzlich, um Code zu strukturieren und zu modularisieren. Dies bedeutet, dass man Schritte in einer Funktion zusammenfassen kann, und diese dann testen und wiederverwenden kann. Eine Daumenregel ist: Wenn Sie einen Codeblock mehr als einmal verwenden, dann sollten Sie ihn in eine Funktion packen.
Eine Funktion besteht aus einem Funktionsnamen, einem oder mehreren Argumenten und einem Funktionskörper. Der Funktionskörper ist der Code, der ausgeführt wird, wenn die Funktion aufgerufen wird. Der Funktionsname ist der Name, unter dem die Funktion in R bekannt ist. Die Argumente sind Werte, welche die Funktion als Input erhält. Funktionen werden mit der Funktion function()
definiert.
Wir definieren nun eine Funktion, welche irgendeine eine Zahl x
als Input erhält und die Zahl x + 1
als Output zurĂĽckgibt.
add_one
ist der Funktionsname. x
ist das beliebige Argument. Innerhalb der geschweiften Klammern {}
ist der Funktionskörper, d.h. der Code, welcher ausgeführt wird, wenn die Funktion aufgerufen wird.
add_one <- function(x) {
x + 1
}
Dieser Code muss in R einmal ausgefĂĽhrt werden, damit die Funktion add_one
definiert wird. Danach kann add_one
aufgerufen werden.
add_one(30)
[1] 31
Was passiert aber nun, wenn wir add_one
mit einem String aufrufen? Wir erhalten einen Fehler, da wir die Funktion add_one
nicht mit einem String aufrufen können. Wir können die Funktion add_one
nur mit Zahlen aufrufen.
Was passiert, wenn sie die Funktion mit einem String aufrufen?
add_one("Hello")
Es wäre sinnvoll, den Input zu prüfen, bevor die Funktion ausgeführt wird. Dies bedeutet, dass wir mit der Funktion is.numeric()
prĂĽfen, ob der Input eine Zahl ist. Falls der Input keine Zahl ist, dann wollen wir eine Fehlermeldung ausgeben. Falls der Input eine Zahl ist, dann wollen wir die Funktion add_one
ausführen. Wir können dies mit if
und else
tun.
add_one <- function(x) {
if (is.numeric(x)) {
x + 1
} else {
print("Hey, this only works if x is a number! Try again!")
}
}
add_one("Hello")
[1] "Hey, this only works if x is a number! Try again!"
Loops
Mit Loops können wir über Vektoren oder Listen iterieren. Dies bedeutet, dass wir einen oder mehrere Schritte in einem Codeblock für jedes Element eines Vektors oder einer Liste ausführen können. Wir können Loops mit for
definieren.
Der Codeblock wird in geschweiften Klammern {}
eingeschlossen. Der Codeblock wird fĂĽr jedes Element des Vektors oder der Liste ausgefĂĽhrt. In einer for
-Schleife brauchen wir eine spezielle Variable, welche bei jedem Durchlauf des Loops den Wert des aktuellen Elements erhält. Diese Variable nennt man eine Iterationsvariable; diese wird oft i
genannt (dies ist aber keine Vorschrift, wir können genausogut auch andere Namen verwenden).
Als erstes Beispiel wollen wir die Namen einzelner FrĂĽchte aus einem Vektor ausgeben.
fruits <- c("apple", "banana", "orange", "cherry")
for (fruit in fruits) {
print(fruit)
}
[1] "apple"
[1] "banana"
[1] "orange"
[1] "cherry"
Anstatt die Elemente direkt zu verwenden, macht es manchmal Sinn, über die Indizes zu iterieren. Wir können dies mit der Funktion seq_along()
tun. seq_along()
gibt eine Folge von Zahlen zurück, welche die Indizes der Elemente des Vektors repräsentieren.
Mit dem FrĂĽchte-Beispiel:
[1] "apple"
[1] "banana"
[1] "orange"
[1] "cherry"
Wir können auch eine deskriptive Nachricht ausgeben.
[1] "apple is a fruit"
[1] "banana is a fruit"
[1] "orange is a fruit"
[1] "cherry is a fruit"
Nun können wir einige der Konzepte kombinieren. Wir wollen nun jedes Element einer Liste testen, ob es eine Frucht oder ein Gemüse ist. Falls es eine Frucht ist, wollen wir die Message “is a fruit” ausgeben. Falls es ein Gemüse ist, wollen wir die Message “is a vegetable” ausgeben.
Wir erstellen nun eine zufällige Liste von Früchten und Gemüse.
[1] "cucumber" "carrot" "orange" "cherry" "banana" "tomato"
Wie können wir nun feststellen, ob ein Element in der Liste eine Frucht oder ein Gemüse ist? Wir können dies z.B. mit %in%
tun. %in%
prĂĽft, ob ein Element in einem Vektor enthalten ist.
"apple" %in% fruits
[1] TRUE
for (food in foods) {
if (food %in% fruits) {
print(paste(food, "is a fruit"))
} else if (food %in% vegetables) {
print(paste(food, "is a vegetable"))
} else {
print(paste(food, "is neither fruit nor vegetable"))
}
}
[1] "cucumber is a vegetable"
[1] "carrot is a vegetable"
[1] "orange is a fruit"
[1] "cherry is a fruit"
[1] "banana is a fruit"
[1] "tomato is a vegetable"
Was passiert, wenn ein Element weder Frucht noch GemĂĽse ist?
Im obigen Code verwenden wir die Funktion paste()
mehrmals. Es könnte Sinn machen, eine eigene Funktion definieren. Wir verwenden die Funktion stopifnot()
, um zu prĂĽfen, ob das Argument ein String ist. Falls dies nicht der Fall ist, wird die Funktion mit einer passenden Fehlermeldung abgebrochen.
for (food in foods) {
what_is(food)
}
[1] "cucumber is a vegetable"
[1] "carrot is a vegetable"
[1] "orange is a fruit"
[1] "cherry is a fruit"
[1] "banana is a fruit"
[1] "tomato is a vegetable"
Alternativen zu for
-Loops
Es gibt in R mehrere Möglichkeiten, um über Vektoren oder Listen zu iterieren, ohne dabei explizite for
-Loops zu schreiben. Dies hat den Vorteil, dass der Code kĂĽrzer und ĂĽbersichtlicher wird.
lapply
und sapply
lapply
und sapply
sind zwei Funktionen, welche ĂĽber Listen iterieren. lapply
und sapply
sind sehr ähnlich. lapply
gibt eine Liste zurück, während sapply
eine Liste retournieren kann.
Als Beispiel wollen wir jedes Element eines Vektors verdoppeln (dies kann in R auch einfacher gemacht werden, aber dies ist nur ein Ăśbungsbeispiel).
numbers <- c(1, 2, 3, 4, 5)
Mit for
können wir dies wie folgt tun.
for (number in numbers) {
print(number * 2)
}
[1] 2
[1] 4
[1] 6
[1] 8
[1] 10
Mit lapply
und sapply
haben wir zwei Möglichkeiten. Wir können entweder eine anonyme Funktion definieren, oder wir können eine Funktion zuerst definieren, und dann verwenden.
\(x) x * 2
definiert eine sogenannte anonyme Funktion. Diese Funktion nimmt ein Argument x
und multipliziert es mit 2, erhält aber keinen eigenen Namen. Folglich können wir diese Funktion nicht wiederverwenden.
lapply(numbers, \(x) x * 2)
[[1]]
[1] 2
[[2]]
[1] 4
[[3]]
[1] 6
[[4]]
[1] 8
[[5]]
[1] 10
Mit einer Funktion, die wir zuerst definieren, sieht unser Beispiel so aus.
double <- function(x) {
x * 2
}
lapply(numbers, double)
[[1]]
[1] 2
[[2]]
[1] 4
[[3]]
[1] 6
[[4]]
[1] 8
[[5]]
[1] 10
sapply(numbers, double)
[1] 2 4 6 8 10
map
Eine weitere Möglichkeit, über Listen zu iterieren, ist die Funktion map
. map
ist eine Funktion aus dem Paket purrr
(wird automatisch geladen, wenn tidyverse
geladen wird). map
gibt immer eine Liste zurĂĽck.
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
âś” dplyr 1.1.1 âś” readr 2.1.4
âś” forcats 1.0.0 âś” stringr 1.5.0
âś” ggplot2 3.4.2 âś” tibble 3.2.1
âś” lubridate 1.9.2 âś” tidyr 1.3.0
âś” purrr 1.0.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
âś– dplyr::filter() masks stats::filter()
âś– dplyr::lag() masks stats::lag()
â„ą Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
numbers |> map(double)
[[1]]
[1] 2
[[2]]
[1] 4
[[3]]
[1] 6
[[4]]
[1] 8
[[5]]
[1] 10
Wenn wir als Output einen Vektor haben wollen, mĂĽssen wir die Funktion unlist()
verwenden.
Reuse
Citation
@online{ellis2023,
author = {Andrew Ellis},
title = {Automatisieren},
date = {2023-03-20},
url = {https://kogpsy.github.io/neuroscicomplabFS23//functions-loops.html},
langid = {en}
}