4  Funktionen in 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 hilfreich, in der Lage zu sein, eigene Funktionen schreiben zu können. Eine Funktion ist ein zusammenhängeder 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 aus der Mathematik.

\[\begin{equation*} y = f(x) \end{equation*}\]

Es sein eine Funktion \(f()\) gegeben, dieser Funktion \(f()\) wird ein Argument (auch Parameter genannt) \(x\) übergeben. Der Name des Parameters ist dabei willkürlich. Die Funktion \(f()\) macht dann etwas mit diesem Parameter und gibt einen Rückgabewert zurück. Der Rückgabewert wird einer neuen Variable \(y\) gewiesen. Sei zum Beispiel die folgende Funktion definiert.

\[\begin{equation*} f(x) = x^2 \end{equation*}\]

Der Funktion \(f()\) wird der Parameter \(x\) übergeben. Dieser Wert wird anschließend quadriert und das Ergebnis der Berechnung wird zurückgegeben. Der Funktionsparameter \(x\) ist dabei wieder willkürlich. Die Funktion hätte genauso auch wie folgt definiert werden können.

\[\begin{equation*} f(y) = y^2 \end{equation*}\]

Letztendlich ist auch der Name der Funktion willkürlich und würde genauso funktionieren als:

\[\begin{equation*} g(z) = z^2 \end{equation*}\]

Um in R eine Funktion anzuwenden, muss die Anweisung einem bestimmten Muster folgen. Dieses Muster folgt: NAME(, , …, ). D.h. sobald ein rundes Klammerpaar auf einen Bezeichner folgt, geht R davon aus, dass die Anwenderin eine Funktion aufrufen möchte. Über , , …, können der Funktion durch Komma getrennte Parameter, auch als Argumente bezeichnet, übergeben werden. Die Anzahl der Parameter hängt dabei von der Definition der Funktion ab. In der Mathematik wäre ein Beispiel für eine Funktion mit zwei Argumenten:

\[\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. Wird zum Beispiel \(f(2,3)\) aufgerufen, wird der gleiche Wert wie bei \(f(3,2) = 25\) erhalten. Dies ist bei der folgenden Funktion nicht mehr der Fall:

\[\begin{equation*} f(x,y) = x^2 + 3y \end{equation*}\]

Hier werden unterschiedliche Ergebnisse erhalten, je nachdem, ob wir die Funktion mit \(f(2, 3) = 13\) oder mit \(f(3, 2) = 15\) aufgerufen wird. Das gleiche Problem entsteht auch, wenn Funktionen in R angewendet werden.

4.1 Funktionen anwenden

In den vorhergehenden Kapitel ist bereits gezeigt worden, dass es in R eine Vielzahl von bereits vordefinierten Funktionen gibt. Ein einfaches Beispiel ist die Wurzelfunktion. In R ist die Funktion mit dem Bezeichner sqrt() verwendbar.

> y <- 9
> sqrt(y)
[1] 3

Im Beispiel wird zunächst der Wert \(9\) dem 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 sqrt(). Für den Anwender nicht einsehbar, holt sich die sqrt()-Funktion den Wert aus y, berechnet die Wurzel und gibt das Ergebnis als Rückgabewert zurück. Das gleiche Ergebnis wird erhalten, wenn der Wert direkt an sqrt() übergeben wird.

> sqrt(9)
[1] 3

Ein weiteres einfaches Beispiel ist die Berechnung des Mittelwerts oder die Summe der Datenreihe \((3, 5, 7)\). In R wird eine geordnete Reihe von Zahlen als Vektor repräsentiert. Um eine 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. Eine Besonderheit von c() ist dabei, dass der Funktion beliebig viele Parameter übergeben werden können.

> c(1, 2, 3)
[1] 1 2 3
> c(1, 2, 3, 4, 5, 6, 7)
[1] 1 2 3 4 5 6 7

Wie ebenfalls in den vorhergehenden Kapitel gesehen, kann die Funktion mean() verwendet werden um den Mittelwert \(\bar{z} = \frac{1}{3}\sum_{i=1}^3z_i\) eines Vektors zu berechnen.

> mean(z)
[1] 5

Vielleicht interessiert einen 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

Ein Unterschied zwischen sum() und mean() besteht dabei auch darin, in welcher Form Parameter übergeben werden können. So funktioniert zum Beispiel die Übergabe von Einzelwerten bei sum() nicht aber bei mean().

> sum(1, 2, 3, 4, 5)
[1] 15
> mean(1, 2, 3, 4, 5)
[1] 1

Daher ist es wichtig sich auch mit der Aufrufsyntax der Funktion auseinander zu setzen. Im zweiten Fall mean(1, 2, 3, 4, 5) für die Anwendung nicht zu einem Fehler, sondern es wird ein fehlerhafter Wert zurückgegeben, da die Funktion falsch angewendet wurde. Solche Fehler führen sind in Programmen oft nur sehr schwer aufzuspüren, da meist erst später im Code ein Problem auftaucht. Wie immer wird es mit zunehmender Erfahrung einfacher solche Problem zu erkennen. Dies sind jetzt nur einige wenige Beispiele, und einer der Skills im Umgang mit R besteht nun zunächst 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.

4.2 Eigene Funktionen schreiben

Im Folgenden wird auf die Syntax eingegangen die notwendig ist um eigene Funktionen zu definieren. 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.

Soll zum Beispiel eine Funktion geschrieben werden, die den gewichteten Mittelwert aus zwei Werten \(x\) und \(y\) berechnet. Die Gewichte \(w\) seien die Werte \((w_x = \frac{1}{3}, w_y = \frac{2}{3})\). Mathematisch würde dies zur der folgenden Definition führen:

\[\begin{equation*} f(x, y) = \frac{1}{3}x + \frac{2}{3}y \end{equation*}\]

D.h. die Funktion bekommt zwei Parameter übergeben. Die Werte der Parameter werden mit den jeweiligen Gewichten multipliziert und die Produkte anschließend addiert. In R kann diese Funktion beispielsweise 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 der IDE 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 der Bezeichner semantisch dem entspricht, was die Funktion berechnet. D.h. es entsteht kein Fehler wenn die Funktion addieren() heißt, aber in Wirklichkeit eine Multiplikation der Parameter durchgeführt wird.

Nachdem die Definition der Funktion 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 einzige Anweisung gibt, wird der berechnete Wert dieser Anweisung vom Funktionsaufruf zurückgegeben.

> mein_gewichteter_mittelwert(3, 6)
[1] 5
Hinweis

Was die letzte Anweisung für den Rückgabewert bedeutet, ist etwas einfacher zu sehen, wenn die Funktion etwas komplizierter geschrieben wird.

> mein_gewichteter_mittelwert_2 <- function(x, y) { 
+   a <- x / 3
+   b <- 2 * y / 3
+   a + b
+ }
> mein_gewichteter_mittelwert_2(3, 6)
[1] 5

In dieser Version werden mit a und b zunächst zwei Zwischenberechnungen durchgeführt und dann in der letzten Anweisung das Ergebnis für den Rückgabewert berechnet. Da die Anweisung a + b die letzte Anweisung im Funktionskörper ist, wird das Ergebnis dieser Anweisung zum Rückgabewert der Funktion.

In vielen anderen Programmiersprachen wird der Rückgabewert explizit durch ein Schlüsselwort return gekennzeichnet. Dies ist in R möglich, aber nicht zwingend notwendig und eher unüblich. 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

4.2.1 Benennung der Argumente

Ein Umstand, der Programmierneulingen immer wieder Probleme bereitet, ist die Benennung der Funktionsparameter. Wie immer in R sind die konkreten Namen der Bezeichner willkürlich, und es muss nur beachtet werden, dass die Bezeichner zu den gewünschten 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

Beide Definitionen führen zu dem gleichen Ergebnis, da die Ersetzung in f_1() von aa_bb durch langer_parameter_name zu der gleichen Definition führen würde. Allerdings würde es 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 finden kann 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

4.2.2 Auflösen der Argumente

Wenn eine Funktion mehr als ein Argument besitzt, dann ist natürlich die korrekte Reihenfolge bzw. korrekte Abbildung der Parameter auf die Bezeichner beim Aufruf der Funktion wichtig. Im einfachsten Fall werden die übergebenen Argumente einfach der Reihenfolge nach den jeweiligen Bezeichnern zugeordnet.

> f_4(1, 2, 3)
[1] 7

Führt dementsprechend zu einem anderen Ergebnis als:

> f_4(3, 2, 1)
[1] 5

Es besteht aber auch 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
> f_4(b = 2, c = 3, a = 1)
[1] 7

Dies Parameter übergabe kann auch gemischt der Reihenfolge und name matching folgent durchgeführt 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 mit Standardwerten (default values) bei der Definition der Funktion zu belegen.

> f_5 <- function(a = 1, b = 2, c = 3) {
+   a + b * c
+ }

In f_5() sind alle drei Argument mit Standardwerten belegt. In diesem Fall kann die Funktion sogar ganz ohne Argumente aufgerufen werden.

> f_5()
[1] 7

Werden keine Parameter übergeben, dann verwendet R die Standardwerte. Dies ermöglicht es auch nur einzelne Argumente zu übergeben. 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

Standardwerte können natürlich auch wieder gemischt Aufgerufen werden.

> f_5(c=10, 20)
[1] 40

In diesem Beispiel, wird zunächst der Bezeichner c mit dem Wert 10 belegt, anschließend erfolgt die Belegung von a mit dem Wert 20 der Reihenfolge folgend, der fehlende Wert von b wird dann mit dem Standardwert belegt.

Zusammengenommen ergeben sich die folgenden Regeln bei der Übergabe von Argumenten:

  1. Positionale Argumente werden der Reihenfolge nach zugewiesen.
  2. named matching wird explizit den Parametern zugeordnet, unabhängig von der Reihenfolge.
  3. Standardwerte werden verwendet, wenn keine Argumente für die entsprechenden Parameter übergeben wurden.

Es lohnt sich diese Regeln sich einzuprägen, da es immer wieder vorkommt, dass in fremden Code bestimmte bestimmte Kombinationen dieser Regeln vorkommen.

4.2.3 Scoping

Als letztes aber nicht minder wichtiges Konzept im Zusammenhang mit Funktionen wird nun noch das scoping behandelt, In R, wie in allen Programmiersprachen, gibt es bestimmte 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 eine Funktion in R aufgerufen wird und darauf hin auf eine Variable zugegriffen werden soll, muss R wissen, wo es nach dieser Variable suchen soll. Scoping beschreibt daher die Regeln, nach denen R entscheidet, wo es nach dem Wert einer Variablen sucht, wenn sie innerhalb einer Funktion verwendet wird.

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
Vorsicht

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.

Diese Informationstiefe sollte für die allermeisten Anwendungsfälle ausreichen. Sollten sich doch einmal weitere Fragen ergeben dann sind die Abschnitte zu Funktionen in R, in R4DS und Advanced R zu empfehlen.

4.3 Funktionen in R-Paketen

Sollte 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. Soll beispielsweise das Paket performance installiert, dann kann dies mit demfolgenden Befehl erreicht werden:

> install.packages("performance")

Wenn mehrere Pakete in einem Schwung installiert werden sollen, dann können die Paketnamen an install.packages() mittels eines Vektors mit den Namen der Pakete als Zeichenketten übergeben werden. 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.

Tipp

Neue Pakete müssen nur beim ersten Mal installiert werden. Danach müssen sie nur noch entweder mit library() oder require() geladen werden.

Tipp

Mit dem Befehl search() können die aktuell geladenen Pakete eingesehen werden, und mit dem Befehl detach() können Pakete auch wieder entladen werden.

Tipp

Beim Erstellen von Skripten ist es meistens sinnvoll, alle benötigten Pakete ganz oben ins Skript zu schreiben, damit die Pakete vor der Ausführung der anderen Anweisungen im Skript geladen werden und dann 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 bzw. Positron zu laden, sollte eher vermieden werden.

Damit ist jetzt auch der Grundlagenteil zu R abgeschlossen. Die erworbenen Grundkenntnisse reichen für die allermeisten Anwendungen. Wenn etwas unklar ist, dann sollte das Wissen nun auch ausreichen um ChapGPT-Code zu verbessern bzw. Information aus der Hilfe oder der Fehlermeldung interpretieren zu können.