> y <- 9
> sqrt(y)
[1] 3
R
Eine zentrale Tätigkeit im Umgang mit jeder Programmiersprache ist das Schreiben von eigenen Funktionen. Es ist zwar möglich, relativ weit zu kommen, ohne eigene Funktionen zu schreiben, aber es ergeben sich im Umgang mit Daten immer wieder Situationen, in denen immer wieder die gleichen Schritte durchgeführt werden. Dies führt dann oft zu Copy-and-Paste von Anweisungen. In solchen Fällen ist es oft hilfreich, in der Lage zu sein, eigene Funktionen schreiben zu können. Eine Funktion ist ein Block von Code, der einmal definiert wird und dann immer wieder verwendet werden kann. Funktionen bieten somit die Möglichkeit, eigenen Code in verschiedene logische Einheiten zu strukturieren und dadurch leichter lesbar und robuster zu machen. Letztendlich beruht der große Erfolg von R
darauf, dass durch neue Funktionen die Funktionalität von R
ständig erweitert werden kann. Daher ist ein zumindest rudimentäres Verständnis der Semantik von Funktionen in R
enorm hilfreich.
Ein erstes, hilfreiches mentales Template für Funktionen in Programmiersprachen sind Funktionen wie wir sie in der Mathematik kennengelernt haben.
\[\begin{equation*} y = f(x) \end{equation*}\]
Wir haben eine Funktion \(f()\), dieser Funktion \(f()\) übergeben wir ein Argument (auch Parameter genannt) \(x\). Die Funktion \(f()\) macht dann etwas mit diesem Parameter und gibt einen Rückgabewert zurück, den wir einer neuen Variable \(y\) zuweisen. Sei zum Beispiel die folgende Funktion definiert.
\[\begin{equation*} f(x) = x^2 \end{equation*}\]
. h. der Funktion \(f()\) wird der Parameter \(x\) übergeben. Dieser Wert wird anschließend quadriert und das Ergebnis der Berechnung wird zurückgegeben.
In R
werden Funktionen nach dem Muster NAME(PAR1, PAR2, …, PARk) aufgerufen. D. h. sobald ein rundes Klammerpaar auf einen Bezeichner folgt, geht R
davon aus, dass die Anwenderin eine Funktion aufrufen möchte. Über
\[\begin{equation*} f(x,y) = x^2 + y^2 \end{equation*}\]
D.h. der Funktion \(f()\) werden nun zwei Argumente \(x\) und \(y\) übergeben. In diesem Fall würde es tatsächlich auch keinen Unterschied machen, in welcher Reihenfolge die Argument übergeben werden, da die Operation innerhalb der Funktion symmetrisch auf beiden Argumenten ist. Rufen wir zum Beipiel \(f(2,3)\) auf, erhalten wir den gleichen Wert wie bei \(f(3,2) = 25\). Dies ist bei der folgenden Funktion nicht mehr der Fall:
\[\begin{equation*} f(x,y) = x^2 + 3y \end{equation*}\]
Hier erhalten wir unterschiedliche Ergebnisse, je nachdem, ob wir die Funktion mit \(f(2, 3) = 13\) oder mit \(f(3, 2) = 15\) aufrufen. Das gleiche Problem entsteht auch, wenn wir Funktionen in R
anwenden.
Wie wir bereits gesehen haben, gibt es in R
eine Vielzahl von bereits vordefinierten Funktionen. Ein einfaches Beispiel ist die Wurzelfunktion. In R
ist die Funktion mit dem Bezeichner sqrt()
definiert.
> y <- 9
> sqrt(y)
[1] 3
Im Beispiel wird zunächst der Wert \(9\) der Bezeichner y
zugewiesen. Im zweiten Schritt wird der Bezeichnet y
an die Wurzelfunktion sqrt()
übergeben. D.h. der Bezeichner y
wird ein Parameter der Funktion. Für uns nicht einsehbar holt sich die sqrt()
-Funktion den Wert aus y
, berechnet den Wurzelwert und gibt diesen als Rückgabewert zurück.
Ein weiteres Beispiel könnte beispielsweise die Berechnung des Mittelwerts oder die Summe der Datenreihe \((3, 5, 7)\) sein. Wie wir bereits gesehen haben, wird in R
eine geordnete Reihe von Zahlen als Vektor repräsentiert. Um den Vektor zu erstellen wird die Funktion c()
(kurz für concatenation) verwendet.
> z <- c(3, 5, 7)
Mit dieser Anweisung hat R
einen Vektor mit den drei Einträgen erstellt. Um den Mittelwert \(\bar{z} = \frac{1}{3}\sum_{i=1}^3z_i\) zu berechnen kann nun die Funktion mean()
verwendet werden.
> mean(z)
[1] 5
Vielleicht interessiert aber auch die Summe \(\bar{z} = \sum_{i=1}^3z_i\) der Vektorelemente. Dafür kann die sum()
Funktion verwendet werden.
> sum(z)
[1] 15
Dies sind jetzt nur einige wenige Beispiele, und einer der Skills im Umgang mit R
besteht nun darin, sich die Namen der Funktionen zu merken. Dies kann am schnellsten durch den täglichen Umgang mit R
erreicht werden. Am besten ab heute.
Im Folgenden wird die Syntax für die Definition eigener Funktionen gezeigt. Dabei werden nur die wichtigsten Elemente angesprochen, da um ein umfassendes Verständnis zu erlangen, deutlich tiefer in die Programmierung mit R
eingestiegen werden müsste, als das zum jetzigen Zeitpunkt notwendig ist.
Wollen wir zum Beispiel eine Funktion schreiben, die den gewichteten Mittelwert aus zwei Werten \(x\) und \(y\) berechnet. Die Gewichte \(w\) seien Gewichte \((w_x = \frac{1}{3}, w_y = \frac{2}{3})\). Mathematisch würde dies folgende Definition nach sich ziehen:
\[\begin{equation*} f(x, y) = \frac{1}{3}x + \frac{2}{3}y \end{equation*}\]
In R
kann diese Funktion wie folgt ausgedrückt werden:
> mein_gewichteter_mittelwert <- function(x, y) {
+ x / 3 + 2 * y / 3
+ }
Um eine eigene Funktion zu definieren, muss das Schlüsselwort function()
verwendet werden. Wenn R
den Ausdruck function
auf der Eingabe sieht, dann interpretiert es den Ausdruck als Definition einer neuen Funktion. Auf das Schlüsselwort folgen runde Klammern ()
, die die benötigten Parameter der Funktion umschließen. Im Beispiel werden zwei Parameter definiert: \(x\) und \(y\). Die Namen der Parameter sind dabei vollkommen willkürlich und müssen lediglich zum späteren Ausdruck in der Funktion passen. Es folgen dann zwei geschweifte Klammern {}
, die den sogenannten Funktionskörper definieren. Im Funktionskörper sind die Anweisungen hinterlegt, welche die Funktionalität der Funktion bestimmen und die gewünschten Berechnungen ausführen. Konkret stehen hier die Ausdrücke, mit denen die Funktion ihren Ergebniswert berechnen kann. Um die Funktion aufrufbar zu machen, braucht sie wieder einen Bezeichner. Der Bezeichner wird, wie bereits bekannt, mittels des Zuweisungsoperators <-
definiert.
In R-Studio sollte in der Environment nach dem Ausführen der Anweisung nun ein Eintrag mit dem Namen mein_gewichteter_mittelwert
stehen. Der Name ist wiederum vollkommen willkürlich, und R
kontrolliert nicht, ob mein Bezeichner semantisch dem entspricht, was die Funktion berechnet.
Nachdem die Funktion definiert und ausgeführt wurde, kann die neue Funktion wie jede andere Funktion in R
aufgerufen werden. Beim Aufruf der Funktion werden die Parameter entsprechend der übergebenen Werte in den Klammern im Funktionskörper ersetzt, und R
führt die Anweisungen im Funktionskörper aus.
Der Rückgabewert der Funktion, also deren Ergebnis, ist die letzte Anweisung des Funktionskörpers. Im vorliegenden Beispiel, da es nur eine Anweisung gibt, wird dieser Wert zurückgegeben.
> mein_gewichteter_mittelwert(3, 6)
[1] 5
Was die letzte Anweisung für den Rückgabewert bedeutet, ist etwas einfacher zu sehen, wenn wir die Funktion etwas komplizierter schreiben.
> mein_gewichteter_mittelwert_2 <- function(x, y) {
+ a <- x / 3
+ b <- 2 * y / 3
+ a + b
+ }
> mein_gewichteter_mittelwert_2(3, 6)
[1] 5
Hier haben wir mit a
und b
zunächst zwei Zwischenberechnungen durchgeführt und dann in der letzten Anweisung das Ergebnis für den Rückgabewert berechnet.
In anderen Programmiersprachen wird die Rückgabe explizit durch ein Schlüsselwort return
gekennzeichnet. Dies ist in R
möglich, aber nicht zwingend notwendig. Daher sind die beiden folgenden Funktionen gleichwertig:
> foo_1 <- function(x) {
+ x + 2
+ }
>
> foo_2 <- function(x) {
+ ergebnis <- x + 2
+ return(ergebnis)
+ }
>
> foo_1(1)
[1] 3
> foo_2(1)
[1] 3
Ein Umstand, der Programmierneulingen immer wieder Probleme bereitet, ist die Benennung der Parameter. Wie immer in R
sind die konkreten Namen der Bezeichner willkürlich, und es muss nur beachtet werden, dass die Bezeichner zu den Anweisungen passen. Hier wieder zweimal die gleiche Funktion in unterschiedlichen Versionen.
> f_1 <- function(aa_bb) {
+ aa_bb + 2
+ }
>
> f_2 <- function(langer_parameter_name) {
+ langer_parameter_name + 2
+ }
>
> f_1(1)
[1] 3
> f_2(1)
[1] 3
Dies würde zu einem Fehler führen, wenn die Bezeichner und die Parameter nicht zueinander passen.
> f_3 <- function(aa_bb) {
+ langer_parameter_name + 2
+ }
>
> f_3(1)
Error in f_3(1): Objekt 'langer_parameter_name' nicht gefunden
In diesem Fall beschwert sich R
, dass es den Bezeichner langer_parameter_name
nicht kennt und dementsprechend die Anweisung nicht ausführen kann. D. h. bei der Definition der Funktion müssen die Bezeichner zu den Anweisungen passen.
Wenn eine Funktion mehrere Argumente hat, dann müssen die übergebenen Argumente zu den definierten Argumenten passen.
> f_4 <- function(a, b, c) {
+ a + b * c
+ }
>
> f_4(1, 2, 3)
[1] 7
Dementsprechend, wenn Argumente beim Aufruf fehlen, kommt es zu einem Fehler.
> f_4(1, 2)
Error in f_4(1, 2): Argument "c" fehlt (ohne Standardwert)
Die Definition der Funktionsargumente wird auch als Signatur der Funktion bezeichnet. Die Signatur kann mittels der Funktion formals()
abgefragt werden.
> formals(f_4)
$a
$b
$c
Im einfachsten Fall werden die übergebenen Argumente einfach der Reihenfolge nach den jeweiligen Bezeichnern zugeordnet. Es besteht aber die Möglichkeit, über das sogenannte named matching die Argumente in einer beliebigen Reihenfolge zu übergeben. In diesem Fall müssen die Argumentnamen explizit mit angegeben werden.
> f_4(c = 3, a = 1, b = 2)
[1] 7
Dies kann auch gemischt gemacht werden, z. B.:
> f_4(c = 3, 1, 2)
[1] 7
Hier wurde der Parameter c
über named matching definiert, und die verbleibenden Argumente wurden dann der Reihenfolge nach übergeben. Was passiert im folgenden Beispiel?
> f_4(b = 2, 1, 3)
Es besteht auch die Möglichkeit, Funktionsargumenten Standardwerte (default values) bei der Definition der Funktion zu übergeben.
> f_5 <- function(a = 1, b = 2, c = 3) {
+ a + b * c
+ }
In diesem Fall kann die Funktion sogar ganz ohne Argumente aufgerufen werden.
> f_5()
[1] 7
Oder es können einzelne Argumente übergeben werden. Hier muss aber darauf geachtet werden, dass zunächst immer die Position verwendet wird, wenn nicht named matching verwendet wird.
> f_5(3)
[1] 9
> f_5(b = 3)
[1] 10
Zusammengenommen ergeben sich die folgenden Regeln bei der Übergabe von Argumenten:
In R
gibt es Regeln, die festlegen, wie und wo Variablen in einer Funktion “sichtbar” sind oder verwendet werden können. Diese Regeln nennt man Scoping-Regeln. Sie bestimmen, wo R
nach Variablen sucht, wenn sie innerhalb einer Funktion verwendet werden.
Wenn du eine Funktion in R
aufrufst und darin auf eine Variable zugreifen möchtest, muss R
wissen, wo es nach dieser Variable suchen soll. Scoping beschreibt also die Regeln, nach denen R
entscheidet, wo es nach dem Wert einer Variable sucht, wenn du sie innerhalb einer Funktion verwendest.
R
verwendet hauptsächlich das sogenannte Lexical Scoping. Lexical Scoping bedeutet, dass R
nach Variablen in der Umgebung der Funktion sucht, basierend darauf, wo die Funktion definiert wurde und nicht, wo sie aufgerufen wird. Das bedeutet, dass R
beim Suchen nach einer Variable von innen nach außen schaut. Dabei beginnt R
bei der Funktion selbst, um dann in den äußeren Umgebungen zu suchen, bis es die Variable findet. Wird die Variable nicht gefunden, dann wird ein Fehler geworfen.
Wenn innerhalb einer Funktion eine Variable definiert wird, wird diese Variable nur in dieser Funktion verwendet. Die Variable hat keinen Einfluss auf Variablen außerhalb der Funktion. Dies wird als local Scope bezeichnet. In der folgenden Funktion wird im Funktionskörper eine Variable \(x\) definiert und deren Wert ausgegeben.
> f_6 <- function() {
+ x <- 10
+ print(x)
+ }
>
> f_6()
[1] 10
Die Variable x
existiert nur innerhalb der Funktion. Sobald die Ausführung der Funktion beendet ist, “verschwindet” x
wieder. Auf den Bezeichner x
kann dann von außerhalb der Funktion nicht zugegriffen werden. Es wurde vorher schon gezeigt, dass, wenn ein Bezeichner verwendet wird, der nicht definiert ist, R
dann einen Fehler wirft.
> f_7 <- function() {
+ print(x)
+ }
Das war allerdings nur die halbe Wahrheit, denn der folgende Code wirft keinen Fehler mehr.
> x <- 5
> f_8 <- function() {
+ print(x)
+ }
> f_8()
[1] 5
Was ist hier passiert? Bei Aufruf von f_8()
trifft die Funktion auf den Bezeichner x
, dieser ist weder innerhalb des Funktionskörpers noch als Funktionsargument definiert. Jetzt greift die von innen-nach-außen-Regel. R
schaut jetzt in der Umgebung, in der die Funktion definiert wurde, nach, ob es dort ein x
gibt (tatsächlich ist es etwas komplizierter, aber dieses einfache Modell reicht erst mal). Diese Art der Variablenauflösung hat einen entscheidenden Nachteil, nämlich, dass wenn sich der Wert von x
in der Umgebung ändert, sich auch der zurückgegebene Wert der Funktion ändert, trotzdem die Funktion mit den gleichen Argumenten aufgerufen wurde.
> x <- 5
> f_8()
[1] 5
> x <- 10
> f_8()
[1] 10
Solcher Code führt praktisch immer zu fehleranfälligen und sehr schwer zu korrigierenden Programmen. Daher sollte dies möglichst vermieden werden, und die Funktion sollte alle Informationen, die sie zur Ausführung braucht, in Form von Argumenten übergeben bekommen.
Wenn ein Bezeichner in der Umgebung und innerhalb der Funktion den gleichen Namen hat, wird immer der Wert des in der Funktion definierten Bezeichners verwendet. Dies gilt, egal ob der Wert als Argument oder innerhalb des Funktionskörpers definiert wurde.
> x <- 5
> f_9 <- function() {
+ x <- 10
+ x
+ }
> f_9()
[1] 10
> f_10 <- function(x) {
+ x + 2
+ }
> f_10(10)
[1] 12
In beiden Fällen verwenden die Funktionen f_9()
und f_10()
das im Funktionskörper bzw. in der Argumentliste definierte x
und nicht das in der umschließenden Umgebung.
Dies sollte für jetzt reichen. Wer tiefer in die Programmierung von Funktionen in R
einsteigen möchte, sollte sich R4DS und Advanced R anschauen.
R
-PaketenSollte sich der Fall ergeben, dass keine geeignete Funktion mit R
mitgeliefert wird, dann können Zusatzfunktionen mittels sogenannter Pakete installiert werden. Ein Paket kann dabei als eine Sammlung von Funktionen und Anweisungen angesehen werden, mit deren Hilfe die Funktionalität von R
erweitert werden kann. Daher wird zunächst Information darüber benötigt, in welchem Paket die gewünschte Funktionalität vorhanden ist. Hierfür reicht meistens eine kurze Suche mittels Google aus.
Ist das Paket nun bekannt, dann sind zwei Schritte durchzuführen, wobei der 1. Schritt nur beim ersten Mal durchgeführt werden muss. Zunächst muss das benötigte Paket in der lokalen, d. h. derjenigen auf dem Rechner laufenden, R
-Umgebung installiert werden, wenn es noch nicht bereits vorher installiert wurde (in R-Studio: Reiter unten-links Packages).
Ein Paket kann mittels des Befehls install.packages()
installiert werden. Der Name des Pakets muss als Zeichenkette an die Funktion übergeben werden. Wollen wir beispielsweise das Paket performance
installieren, führen wir den folgenden Befehl aus:
> install.packages("performance")
Wenn mehrere Pakete in einem Schwung installiert werden sollen, dann übergibt man install.packages()
einen Vektor mit den Namen der Pakete als Zeichenketten. Sollen zum Beispiel die Pakete performance
und ggplot2
installiert werden, dann kann die folgende Anweisung verwendet werden:
> install.packages(c("performance", "ggplot2"))
R
kontaktiert im Hintergrund einen sogenannten CRAN-Server im Internet und lädt das Paket sowie benötigte Abhängigkeiten automatisch herunter. Wenn alles gut läuft, dann ist das Paket nun in der lokalen Umgebung, also auf dem Rechner, installiert. Die Funktionalität des Pakets steht jedoch noch nicht direkt zur Verfügung! Das Paket muss mit einem weiteren Befehl in die derzeit aktive Arbeitsumgebung geladen werden.
Um das Paket in die aktive Arbeitsumgebung zu laden, gibt es zwei Befehle in R
: library()
und require()
. Bei require()
überprüft R
zunächst, ob das Paket schon geladen wurde, während bei library()
das Paket einfach geladen wird, unabhängig davon, ob das Paket schon geladen ist. Um die Funktionalität von performance
jetzt in der aktiven Session zu nutzen, kann dementsprechend library()
benutzt werden:
> library(performance)
Hier muss der Name des Pakets nicht als Zeichenkette übergeben werden, sondern kann einfach als Bezeichner übergeben werden. Dieser zweite Schritt des Ladens des Pakets muss jedes Mal nach einem Neustart von R
wieder durchgeführt werden. Wenn R
startet, sind immer nur eine kleine Anzahl von Standardpaketen geladen, und Zusatzpakete werden durch R
beim Start nicht automatisch geladen.
Neue Pakete müssen nur beim ersten Mal installiert werden. Danach müssen sie nur noch entweder mit library()
oder require()
geladen werden.
Mit dem Befehl search()
können die aktuell geladenen Pakete eingesehen werden, und mit dem Befehl detach()
können Pakete auch wieder entladen werden.
Beim Erstellen von Skripten ist es meistens sinnvoll, alle benötigten Pakete ganz oben ins Skript zu schreiben, sodass die Pakete vor der Ausführung der anderen Anweisungen im Skript zur Verfügung stehen. So kann sichergestellt werden, dass der Code auch alle benötigten Pakete zur Verfügung hat. D. h. Pakete über die GUI in R-Studio zu laden, sollte eher vermieden werden.