Funktionen in R
Was sind Funktionen und was haben Funktionen eigentlich mit dem Titelbild zu tun? Tatsächlich musste ich erstmal überlegen, was für ein Bild passend wäre für dieses Thema. „Funktion“ ist schon ein sehr abstrakter Begriff, zu dem man meistens Grafiken mit mathematischen Funktionen oder eben mit Programmiercode findet, wenn man nach entsprechenden Bildern im Internet sucht. Aber mehr dazu später.
Was ist nun eine Funktion? Laut Wiki ist es „die Bezeichnung eines Programmkonstrukts, mit dem der Programm-Quellcode strukturiert werden kann, so dass Teile der Funktionalität des Programms wiederverwendbar sind. […] Funktionen gelten als spezielle Varianten von Unterprogrammen.“ Eine Funktion fasst also einen oder mehrere Schritte zusammen, welche anschließend nur mit Aufruf eben dieser Funktion durchlaufen werden. Wir können klare Parallelen zur Mathematik schlagen: Definieren wir zum Beispiel f(x) = x^2 + 1
, so können wir anschließend f in weiteren Berechnungen benutzen: f(a) + f(b)
. In diesem Beispiel sehen wir auch, dass unsere Funktion f einen sogenannten Parameter x enthält, der als Platzhalter für einen bestimmten Wert steht. Funktionen können auch mehrere Parameter enthalten, zum Beispiel eine Flächeninhaltsfunktion: A(a, b) = a*b
.
Funktionen in R
In R hantieren wir ständig mit Funktionen. Berechnen wir zum Beispiel den Mittelwert eines Vektors, benutzen wir mean: mean(myVector)
. Diese Funktion hat auch weitere Parameter; so können wir zum Beispiel angeben, wie mit fehlenden Werten (NA’s) umgegangen werden soll: mean(myVector, na.rm=TRUE)
. Möchten wir übrigens wissen, was Funktionen machen, welche Parameter sie besitzen, und was ihr Output ist, so erhalten wir mehr Informationen in der Dokumentation: ?mean
(mit dem Fragezeichen vor einem Funktionsnamen signalisieren wir R, dass wir die Dokumentation zu der jeweiligen Funktion aufrufen möchten).
Eigene Funktionen schreiben
In R ist es und möglich, eigene Funktionen zu definieren und diese danach zu benutzen. Das geht ganz einfach:
myFunction <- function() {
}
Diese Zeilen gehen wir jetzt mal Schritt für Schritt durch. Wir sehen, dass wir einen Objektnamen definieren, myFunction, und diesem Objekt wird nun etwas zugewiesen, <-. Mit dem Schlüsselwort function sagen wir, dass myFunction eine Funktion sein soll. In unserem Beispiel hat die Funktion keine Parameter, von daher steht nichts in den Klammern, (). Die geschweiften Klammern bilden den Funktionsrumpf, in dem nun alle Anweisungen stehen, die zur Funktion gehören. Alles nach der schließenden geschweiften Klammer gehört nicht mehr zur Funktion.
Nun möchten wir aber, dass die Funktion etwas bestimmtes tut, denn noch ist sie leer und es wird nichts ausgeführt. Bauen wir uns mal eine Funktion, die uns einen Gruß hinterlässt:
Greet <- function(name) {
greetings <- paste0("Hallo, ", name, "!")
cat(greetings)
}
Diese Funktion hat einen Parameter, name (das was der Funktion während der Ausführung dann übergeben wird, wird auch statt Parameter Argument genannt). Dieser Parameter wird dann in der ersten Zeile gleich benutzt. Die paste0-Funktion verbindet Variablen zu einer Zeichenkette; in unserem Fall entsteht dadurch eine "Begrüßung". Mit cat lässt sich vereinfacht gesagt ein Objekt ausgeben - wenn kein weiteres Argument für diese Funktion übergeben wird, dann wird es in die R-Konsole geschrieben. Hier wird also das oben erstellte Objekt greetings in der Konsole ausgegeben. Wir können die Funktion nun mit ihrem Namen aufrufen und einen bestimmten Namen für name mitliefern: Greet("Kurt")
.
Weiteres Beispiel
In diesem Beispiel bauen wir uns eine Funktion, die uns einen kleinen Beispieldatensatz zurückgibt:
CreateSampleDataset <- function(nrow=100) {
Condition <- rbinom(nrow, 1, 0.5)
IQ <- rnorm(nrow, 100, 15)
Age <- rnorm(nrow, 40, 7.5)
Motivation <- runif(nrow, 1, 10)
dfSampleData <- data.frame(Condition, IQ, Age, Motivation)
return(dfSampleData)
}
Okay, was macht diese Funktion? Wie schon oben erwähnt, erstellt sie einen Beispieldatensatz. Der Datensatz hat dabei nrow Fälle. Im Vergleich zur obigen Greet-Funktion sind hier aber noch zwei Dinge anders: Zum einen gibt diese Funktion etwas zurück, was durch das Schlüsselwort return deutlich wird, und zum anderen hat die Funktion ein sogenanntes "default argument" für nrow, nämlich 100. Das heißt, dass wir bei einem Funktionsaufruf nrow nicht angeben müssen und es somit standardmäßig auf 100 gesetzt wird. Somit können wir die Funktion unterschiedlich aufrufen: dfDataset <- CreateSampleDataset(75)
. In diesem Fall weisen wir dem Objekt dfDataset das Resultat der Funktion zu - eben ein Datensatz mit 75 Fällen. Wir können das Argument auch weglassen: dfDataset <- CreateSampleDataset()
. Diesmal wird der Datensatz 100 Fälle beinhalten, eben weil es in der Funktionsdefinition so angegeben wurde. Hierbei ist wichtig zu beachten, dass Argumente angegeben werden müssen, wenn es kein Standardwert gibt. Für die Funktion aus dem vorigen Beispiel, Greet, muss ein Wert für name angegeben werden.
Was man beachten sollte
Es gibt einige Richtlinien für das Schreiben von Funktionen, die anfangs vielleicht etwas kleinlich wirken, auf lange sicht aber den Code übersichtlicher, flexibler, einfacher, möglicherweise schneller, usw. machen. Wichtig sind vor allem folgende:
- Eine Funktion sollte nur für eine Sache gut sein. Sie soll nicht zehn Sachen auf einmal machen. Wenn man viele inhaltlich unterschiedliche Schritte zusammenfassen will, so sollte man für jeden Schritt eine einzelne Funktion schreiben. Somit gibt es nicht eine "Monster-Funktion", sondern viele kleine, von denen jede einzelne Funktion ihren Job macht - ganz unabhängig von den anderen.
- Der Funktionsname soll deutlich machen, wofür die Funktion gut ist. Selbsterklärend, oder? Wer weiß schon, was eine Funktion mit dem Namen func_1_new macht?
- Eine Funktion sollte eine überschaubare Anzahl an Zeilen verbrauchen. In "Clean Code" schreibt Robert C. Martin, dass Funktionen nicht einmal 20 Zeilen lang sein sollten.
- Die Anzahl der Funktionsargumente sollte klein gehalten werden. Eine Funktion, die 9 Argumente verlangt ist keine gute und einfach zu benutzende Funktion.
- Inputs und Outputs sollten klar sein. Bei statisch-typisierten Programmiersprachen ist dieser Punkt einfacher, da die Funktionsargumente von einem bestimmten Typ sein müssen (z.B. int für Ganzzahlen). In R gibt es da keine direkte Prüfung und wir müssen aufpassen. Probier einfach mal folgendes aus: dfDataset <- CreateSampleDataset(nrow="twenty")
.
Nun komm ich zurück aufs Titelbild zu sprechen: Das Bild zeigt ein Teil des Produktionswerks von BMW in Leipzig. In der Automobilproduktion gibt es viele verschiedene Roboter, von denen jeder nur für einen kleinen Ausschnitt aus der gesamten Produktionskette verantwortlich ist und nur eine Funktion hat. Der jeweilige Roboter erwartet einen bestimmten Input und liefert einen bestimmten Output (der Autolack sollte lieber auf die Karosserie und nicht auf das Armaturenbrett...). Ebenso sind die Funktionen getrennt voneinander und können somit flexibel kombiniert werden. In die selbe Karosserie könnte man zum Beispiel verschieden starke Motoren einbauen und im Innenraum können die Sitze verschiedene Materialien haben. Funktionen können also sehr viel Arbeit abnehmen und ermöglichen es, komplexere Logiken übersichtlich zu halten.
Hast du noch mehr Fragen zu Funktionen oder zu einem bestimmten Problem in R? Schreib mir einfach eine Mail: mail@r-coding.de.
Bleib außerdem auf dem Laufenden mit dem r-coding Newsletter. Du erhältst Infos zu neuen Blogeinträgen, sowie kleine Tipps und Tricks zu R. Melde dich jetzt an: http://r-coding.de/newsletter.
Viel Erfolg!
Foto von Wikipedia [Link], Author 'BMW Werk Leipzig'; zeigt das BMW Werk in Leipzig, lizensiert unter CC BY-SA 2.0, Bild wurde durch schwarz-weiß-Filter modifiziert.
Ein Kommentar