3.7 Erstellen von eigenen Funktionen
Wie wir einzelne Funktionen (bspw. mean()
und sd()
) anwenden haben wir bereits gelernt. Wir können aber auch selber eine Anwendung oder Funktion schreiben, in der mehrere Funktionen gebündelt sind. Viele Schritte in der Datenaufbereitung und Analyse wiederholen sich ständig. Daher ist es sinnvoll bestimmte Schritte zu “automatisieren”. Wollen wir zum Beispiel statistische Werte zum mtcars
Datensatz haben, so können wir jeden Wert einzeln abfragen mit
length(mtcars$hp) # Anzahl der Werte
## [1] 32
mean(mtcars$hp) # Arithmetisches Mittel
## [1] 146.6875
sd(mtcars$hp) # Standardabweichung
## [1] 68.56287
min(mtcars$hp) # Minimum
## [1] 52
max(mtcars$hp) # Maximum
## [1] 335
median(mtcars$hp) # Median
## [1] 123
Eine Möglichkeit wiederholte Abfragen zu ermöglichen ist das Ablegen in einen einzelnen Vektor:
<- c(
mtcars_hp_descriptives n = length(mtcars$hp),
mean = mean(mtcars$hp),
sd = sd(mtcars$hp),
min = min(mtcars$hp),
max = max(mtcars$hp),
median = median(mtcars$hp)
)
mtcars_hp_descriptives## n mean sd min max median
## 32.00000 146.68750 68.56287 52.00000 335.00000 123.00000
Et voila 🎉 Eingabe des Objektes mtcars_hp_descriptives
ermöglicht es uns die statistischen Kennwerte direkt auszugeben. Im mtcars
Datensatz gibt es aber noch weitere Kenndaten. Wie können wir den code so anpassen, dass wir nicht jedes mal alles wieder neu abtippen müssen. Es wäre doch zu schön wenn so etwas in R möglich ist. Zwei Argumente warum ihr euch bei diesen Schritten gut überlegen solltet eine Funktion zu schreiben:
- Anstatt mehrerer Codezeilen ist in Zukunft für dasselbe Ergebnis eine Codezeile – der Funktionsaufruf – erforderlich.
- Der Code bzw. euer Skript wird weniger redundant und übersichtlicher
- Fehlerkorrekturen und Anpassungen werden vereinfacht, da ihr nur die Funktion ändern müsst und nicht jedes mal die Stellen wo die Funktion auftritt.
Funktionen in R sind Objekte und werden ebenfalls mit dem <-
zugewiesen. Das Schema ist dabei immer das folgende mit drei zentralen Komponenten:
- Funktionsargumente, wie bei unseren bisherigen Funktionen auch (bspw.
na.rm = TRUE
) - Body der Funktion, welcher den Code zum Ausführen enthält
- environment, beinhaltet die Datenstruktur damit die Funktion einen Wert mit dem Namen assoziieren kann
<- function(argument_1, argument_2) {
funktionsname # Body: Code, der ausgeführt wird
}
Mit der Funktion function()
weisen wir einem Objektnamen ein Funktionsobjekt zu. Um den Code auf eine andere Variable anzuwenden, müssten wir jeweils mtcars$hp
ersetzen – z. B. durch mtcars$cyl
oder eine Variable aus einem anderen Datensatz. Wir möchten diesen Teil also durch einen Platzhalter ersetzen, den wir dann als Funktionsargument übergeben können. Mit einem generischen Platzhalter, den wir der Einfachheit halber als x bezeichnen, sähe der Code also wie folgt aus:
<- function(x) { # Wir definieren 'x' als Argument
descriptives <- c(
descriptives_vector n = length(x),
mean = mean(x),
sd = sd(x),
min = min(x),
max = max(x),
median = median(x)
) }
Die Funktionsargumente werden in eine runde Klammer geschrieben ()
und der body in eine eckige []
. Unter Environment taucht unsere Funktion descriptives
nun bei der Rubrik Funktionen auf und kann im Folgenden eingesetzt werden.
descriptives(mtcars$disp)
Komisch das uns die Werte noch nicht angezeigt werden. Dies liegt daran, dass wir abschließend noch definieren müssen, was die Funktion mit der Anfrage durchführen soll. Vereinfacht gesagt, die Werte sollen angezeigt werden.
<- function(x) { # Wir definieren 'x' als Argument
descriptives <- c(
descriptives_vector n = length(x),
mean = mean(x),
sd = sd(x),
min = min(x),
max = max(x),
median = median(x)
)return(descriptives_vector) # oder nur descriptives_vector
}
descriptives(mtcars$cyl) # Zusammenfassung der Zylinder
## n mean sd min max median
## 32.000000 6.187500 1.785922 4.000000 8.000000 6.000000
descriptives(mtcars$mpg) # Zusammenfassung des Verbrauchs
## n mean sd min max median
## 32.000000 20.090625 6.026948 10.400000 33.900000 19.200000
descriptives(mtcars$gear) # Zusammenfassung Anzahl der Gänge
## n mean sd min max median
## 32.0000000 3.6875000 0.7378041 3.0000000 5.0000000 4.0000000
Falls wir jedoch fehlende Werte NA
s in unserem Datensatz haben müssen wir die Funktion anpassen, da dies in den Funktionsargumenten momentan noch fehlt.
<- function(x, na.rm = TRUE) { # Default-Wert für 'na.rm' = TRUE
descriptives <- c(
descriptives_vector n = length(x),
mean = mean(x, na.rm = na.rm),
sd = sd(x, na.rm = na.rm),
min = min(x, na.rm = na.rm),
max = max(x, na.rm = na.rm),
median = median(x, na.rm = na.rm)
)return(descriptives_vector)
}
<- c(3,6,12,4,NA)
a descriptives(a)
## n mean sd min max median
## 5.000000 6.250000 4.031129 3.000000 12.000000 5.000000
💡 Anstelle von TRUE
oder FALSE
erkennt R auch ein einfaches T
oder F
.
Viele Funktionen haben default
als vordefinierte Argumente. Diese könnten wir auch in unserer Funktion ergänzen indem wir na.rm =
setzen:
Dieser Default-Wert wird nun also immer verwendet, wenn wir das Argument nicht angegeben haben. Unsere Funktion ist somit noch flexibler geworden.
3.7.1 Speziellere Anwendungen von Funktionen
Lasst uns diese Kenntnisse erweitern und unsere Daten mit Statistikparametern plotten:
<- function(x = rnorm(100),
spezialplot y = rnorm(100),
add.mean = FALSE,
add.regression = FALSE,
p.threshold = .05,
add.modeltext = FALSE,
# Weitere Argumente die ergänzt werden sollen
...
) {# Erstelle den Plot
# und setze ggf. weitere Argumente wie `main` für Titel
plot(x, y, ...)
# Erstelle Referenzlinie vom Mittelwert wenn add.mean = TRUE
if(add.mean == TRUE) {
abline(h = mean(y), lty = 2)
abline(v = mean(x), lty = 2)
}
# Erstelle Regressionlinie wenn add.regression = TRUE
if(add.regression == TRUE) {
<- lm(y ~ x) # Erstelle Regression mit `lm` linear model
model
<- anova(model)$"Pr(>F)"[1] # Ziehe den p Wert aus dem Objekt p.value
p.value
# Definiere die Farbe in Abhängigkeit von p.value und p.threshold
if(p.value < p.threshold) {line.col <- "red"}
if(p.value >= p.threshold) {line.col <- "black"}
abline(lm(y ~ x), col = line.col, lwd = 2) # Füge die Regressionlinie hinzu
}
# Add regression equation text if add.modeltext is TRUE
if(add.modeltext == TRUE) {
# Erstelle Regressionsdaten
<- lm(y ~ x)
model
# Extrahiere die Koeffizienten vom Objekt `model`
<- model$coefficients
coefficients <- round(coefficients[1], 2)
a <- round(coefficients[2], 2)
b
# Create text
<- paste("Regression Equation: ", a, " + ", b, " * x", sep = "")
model.text
# Add text to top of plot
mtext(model.text, side = 3, line = .5, cex = .8)
} }
Jetzt können wir in unsere Beispielfunktion Daten einsetzen aus dem mtcars
Datensatz:
spezialplot(x = mtcars$mpg,
y = mtcars$hp)
# Das sieht danach aus, dass der Verbrauch größer ist je mehr PS das Auto hat
# Wir können das Überprüfen indem wir die zusätzlichen Argumente in unserer Funktion setzen
spezialplot(x = mtcars$mpg,
y = mtcars$hp,
add.mean = TRUE,
add.regression = TRUE,
add.modeltext = TRUE,
p.threshold = .05,
xlab = "Miles per gallon",
ylab = "Horsepower")
# Und wie schaut das ganze für Gewicht und Verbrauch aus
spezialplot(x = mtcars$mpg,
y = mtcars$wt,
add.mean = TRUE,
add.regression = TRUE,
add.modeltext = TRUE,
p.threshold = .05,
xlab = "Miles per gallon",
ylab = "Weight (1000 lbs)")
Schnelle Analyse: Je mehr Pferdestärken und je größer das Gewicht, desto größer ist auch der Verbrauch des Autos.
💡 Anstelle eine Funktion immer wieder aufs neue zu schreiben empfiehlt es sich ein R Skript anzulegen unter File -> New File -> R script. Hier könnt ihr eure Funktionen reinschreiben, in eurem Projektordner hinterlegen und mit der Funktion source
automatisch in eure aktuelle R session hineinladen. Das spart Platz und beschleunigt eure explorative Datenanlyse erheblich.
# Evaluiere den Code in meinem `custom_function` script zu Beginn meiner Session
source(file = "custom_functions.R")
3.7.2 Nutze ...
als Platzhalter
Wenn man nicht alle Argumente in einer Funktion definieren möchte, sich dieses aber noch für später offen lassen möchte:
<- function(x, ...) { # Here is where the additional arguments go
descriptives <- c(
descriptives_vector n = length(x),
mean = mean(x, ...), # and here
sd = sd(x, ...),
min = min(x, ...),
max = max(x, ...),
median = median(x, ...)
)return(descriptives_vector)
}
<- c(3,6,12,4,NA)
a descriptives(a, na.rm = T)
## n mean sd min max median
## 5.000000 6.250000 4.031129 3.000000 12.000000 5.000000