Kurs
Wenn du darüber nachdenkst, nutzen viele Funktionen in R Formeln: Pakete wie ggplot2, stats, lattice und dplyr arbeiten damit! Typische Beispiele sind Funktionen wie glm(), lm(), facet_wrap() usw. Aber was genau sind diese Formeln, und warum solltest du sie verwenden?
Datenstrukturen in R
Da Formeln eine spezielle Klasse in der Programmiersprache R sind, ist es sinnvoll, kurz die Datentypen und Datenstrukturen zu wiederholen, die dir in dieser Sprache zur Verfügung stehen.
Merke: R ist eine objektorientierte Programmiersprache. Diese Sprache ist um Objekte herum organisiert. Alles in R ist ein Objekt.
Fangen wir vorne an: In der Programmierung arbeitest du mit Datenstrukturen, die deine Daten speichern, und Funktionen, die sie verarbeiten. Eine Datenstruktur ist die Schnittstelle zu Daten, die im Arbeitsspeicher des Computers organisiert sind. Wie die R Language Definition festhält, bietet R keinen direkten Zugriff auf den Speicher, sondern stellt eine Reihe spezialisierter Datenstrukturen bereit, auf die du als „Objekte“ verweist. Jede Datenstruktur ist darauf ausgelegt, bestimmte Aspekte von Speicherung, Zugriff oder Verarbeitung zu optimieren.
Die fünf wichtigsten Datenstrukturen in R sind:
- Atomarer Vektor,
- Liste,
- Matrix,
- Data Frame und
- Array
# Create variables
a <- c(1,2,3,4,5,6,7,8,9)
b <- list(x = LifeCycleSavings[,1], y = LifeCycleSavings[,2])
Tipp: Du kannst die Funktion typeof() verwenden, um den Typ eines R-Objekts zurückzugeben. Der Typ eines Objekts sagt dir mehr über den (internen R-)Typ bzw. den Speichermodus eines Objekts:
# Retrieve the types of `a` and `b`
typeof(a)
typeof(b)
'double'
'list'
Im obigen Beispiel mit den Variablen a und b siehst du, dass die Datenstrukturen Folgen von Datenelementen enthalten. Diese Elemente können vom gleichen oder von unterschiedlichen Datentypen sein. In R findest du die folgenden 6 atomaren Datentypen:
- numeric, z. B.
100,5,4, schließt ganze Zahlen ein. - character, z. B.
"Hello","True"oder"23.4", besteht aus Zeichenketten; - logical, z. B.
TRUEoderFALSE, umfasst „Wahrheitswerte“; - raw, z. B.
48 65 6c 6c 6f, besteht aus Bits; - complex, z. B.
2+5i, umfasst komplexe Zahlen; und schließlich - double, z. B.
3.14, umfasst Dezimalzahlen.
Fast alle Objekte haben in R Attribute. Beispielsweise weißt du vielleicht schon, dass Matrizen und Arrays einfach Vektoren mit dem Attribut dim und optional dimnames sind. Attribute werden genutzt, um die in R verwendete Klassenstruktur zu implementieren. Als objektorientierte Sprache sind Klassen gemeinsam mit Methoden zentral. Eine Klasse ist die Definition eines Objekts. Sie legt fest, welche Informationen das Objekt enthält und wie es verwendet werden kann.
Schau dir das folgende Beispiel an:
# Retrieve the classes of `a` and `b`
class(a)
class(b)
'numeric'
'list'
Hinweis: Wenn ein Objekt kein class-Attribut hat, besitzt es eine implizite Klasse, „matrix“, „array“ oder das Ergebnis der Funktion mode().
Zu den speziellen Klassen, auf die du treffen kannst, gehören unter anderem Dates und Formulas; Und Letztere ist das Thema dieses Tutorials!
Was ist eine Formel in R?
Wie du in der Einführung gelesen hast, bist du Formeln vermutlich schon bei Paketen wie ggplot2 oder in Funktionen wie lm() begegnet. Weil du Formeln in solchen Funktionsaufrufen meist nutzt, um die Idee eines statistischen Modells auszudrücken, ist es nur logisch, dass du diese R-Objekte häufig in Modellierungsfunktionen und in einigen Grafikfunktionen verwendest.
Richtig?
Formeln sind jedoch nicht auf Modelle beschränkt. Sie sind ein mächtiges, universelles Werkzeug, mit dem du zwei Dinge festhalten kannst:
- Einen nicht ausgewerteten Ausdruck und
- Den Kontext bzw. das Environment, in dem dieser Ausdruck erzeugt wurde.
Das erklärt, warum Formeln in Funktionsaufrufen „spezielles Verhalten“ ermöglichen: Sie erlauben es, die Werte von Variablen zu erfassen, ohne sie sofort auszuwerten, damit die Funktion sie interpretieren kann.
Mit den Datenstrukturen frisch im Kopf kannst du diese R-Objekte als „language“-Objekte oder nicht ausgewertete Ausdrücke beschreiben, die die Klasse „formula“ haben und ein Attribut, das das Environment speichert.
Im vorherigen Abschnitt hast du gesehen, dass Objekte interne Typen besitzen, die angeben, wie das Objekt gespeichert wird. Eine Formel ist in diesem Sinn ein Objekt vom Typ „language“.
Aber was heißt das konkret?
Du triffst auf diesen Objekttyp meistens, wenn du die R-Sprache selbst verarbeitest. Sieh dir das folgende Beispiel an, um das besser zu verstehen:
# Retrieve the object type
typeof(quote(x * 10))
# Retrieve the class
class(quote(x * 10))
'language'
'call'
Im Beispiel bittest du R, den Typ und die Klasse von quote(x*10) zurückzugeben. Ergebnis: Der Typ von quote(x*10) ist 'language', die class ist 'call'.
Das ist eindeutig keine Formel, denn class() müsste 'formula' liefern!
Was ist es dann?
Ein Merkmal von Formeln in R ist der Tilde-Operator ~. Mit diesem Operator sagst du sinngemäß: „Erfasse die Bedeutung dieses Codes, ohne ihn sofort auszuwerten.“ Deshalb kannst du eine Formel in R auch als eine Art „Quoting“-Operator verstehen.
Aber wie sieht eine Formel genau aus? Schau dir diese Zeilen an:
# A formula
c <- y ~ x
d <- y ~ x + b
# Double check the class of `c`
class(c)
'formula'
Die Variable links von der Tilde (~) heißt „abhängige Variable“, die Variablen rechts heißen „unabhängige Variablen“ und werden mit Pluszeichen + verbunden.
Gut zu wissen: Die Bezeichnungen variieren je nach Kontext. Unabhängige Variablen kennst du vielleicht auch als „Prädiktor“, „kontrollierte Variable“, „Feature“ etc. Abhängige Variablen tauchen als „Response“, „Outcome“ oder „Label“ auf.
Hinweis: Auch wenn die Formel d im obigen Code mehrere Variablen enthält, besteht die Grundstruktur einer Formel eigentlich nur aus dem Tilde-Symbol ~ und mindestens einer rechten Variable.
Merke: Formeln sind Sprachobjekte mit Attributen, die das Environment speichern:
# Return the type of `d`
typeof(d)
# Retrieve the attributes of `d`
attributes(d)
'language'
$class
[1] "formula"
$.Environment
<environment: R_GlobalEnv>
Wie du siehst, können Variablen in einer Formel z. B. Vektoren sein. Häufig stammen die Variablen jedoch aus einem Data Frame, etwa so:
Sepal.Width ~ Petal.Width + log(Petal.Length) + Species
Hinweis: Beim Erzeugen der Formel werden die an die Symbole gebundenen Datenwerte nicht ausgewertet.
Nun, da du weißt, wie Formeln aussehen und was sie in R sind, ist wichtig zu erwähnen: Das zugrunde liegende Formelobjekt unterscheidet sich je nach einseitiger oder zweiseitiger Formel. Erkennst du daran, ob links eine Variable steht. Fehlt sie, wie in ~ x, ist es eine einseitige Formel.
Das bedeutet auch: Eine einseitige Formel hat die Länge 2, eine zweiseitige die Länge 3.
Nicht überzeugt? Schau dir diesen Code an. Du kannst auf Elemente einer Formel mit doppelten eckigen Klammern zugreifen: [[ und ]].
e <- ~ x + y + z
f <- y ~ x + b
# Return the length of `g`
length(e)
length(f)
# Retrieve the elements at index 1 and 2
e[[1]]
e[[2]]
f[[3]]
2
3
`~`
x + y + z
x + b
Warum Formeln in R verwenden?
Wie du gesehen hast, sind Formeln mächtige, universelle Werkzeuge, mit denen du Variablenwerte erfassen kannst, ohne sie sofort auszuwerten, damit eine Funktion sie interpretieren kann. Das ist bereits ein Teil der Antwort, warum du Formeln in R nutzen solltest.
Außerdem nutzt du diese R-Objekte, um Beziehungen zwischen Variablen auszudrücken.
Im ersten Code unten sagst du sinngemäß „y hängt von x, a und b ab“. Komplexere Formeln gibt es ebenfalls, etwa in der zweiten Zeile: „Die Kelchblattbreite hängt von der Kronblattbreite ab, bedingt durch die Art.“
y ~ x + a + b
Sepal.Width ~ Petal.Width | Species
y ~ x + a + b
Sepal.Width ~ Petal.Width | Species
Formeln in R verwenden
Jetzt kennst du das „Was“ und „Warum“. Zeit für das „Wie“ – von einfachen bis zu komplexeren Formeln. In diesem Abschnitt siehst du, wie du einfache Formeln erstellst und zusammenführst und wie du mit Operatoren komplexere Formeln aufbaust.
So erstellst du eine Formel in R
Das kannst du bereits! Du hast im Tutorial schon Beispiele gesehen. Zur Auffrischung:
y ~ x
~ x + y + z
g <- y ~ x + b
Genau – du tippst die Formel einfach ein!
Oft willst oder musst du aber aus einem R-Objekt, z. B. einem String, eine Formel erstellen. Dann nutzt du formula oder as.formula():
"y ~ x1 + x2"
h <- as.formula("y ~ x1 + x2")
h <- formula("y ~ x1 + x2")
Einfach!
Formeln zusammenführen
Um mehrere Formeln zusammenzuführen, hast du zwei Optionen. Erstens kannst du für jede Formel Variablen anlegen und dann list() nutzen:
# Create variables
i <- y ~ x
j <- y ~ x + x1
k <- y ~ x + x1 + x2
# Concatentate
formulae <- list(as.formula(i),as.formula(j),as.formula(k))
# Double check the class of the list elements
class(formulae[[1]])
'formula'
Alternativ kannst du lapply() verwenden: Übergib als erstes Argument einen Vektor mit allen Formeln und als Funktion as.formula:
# Join all with `c()`
l <- c(i, j, k)
# Apply `as.formula` to all elements of `f`
lapply(l, as.formula)
[[1]]
y ~ x
[[2]]
y ~ x + x1
[[3]]
y ~ x + x1 + x2
Formel-Operatoren
Mit diesen Grundlagen können wir tiefer einsteigen. Charakteristisch ist das Tilde-Symbol ~. Zusätzlich brauchst du abhängige und unabhängige Variablen, die du mit dem Pluszeichen + verbindest.
Aber es gibt mehr!
Neben + gibt es weitere Symbole, die deiner Formel besondere Bedeutung geben:
-zum Entfernen von Termen,:für Interaktion,*für Kreuzung,%in%für Schachtelung und^um Kreuzungen auf einen Grad zu begrenzen.
Beispiele dazu folgen gleich. Starten wir mit + und -:
# Use multiple independent variables
y ~ x1 + x2
# Ignore objects in an analysis
y ~ x1 - x2
Hinweis: : und * brauchst du häufig in der Regressionsmodellierung, um Interaktionsterme zu spezifizieren. Ersteres steht für reine Interaktion (ohne die einzelnen Variablen), letzteres für Kreuzung: Es umfasst beide Variablen und ihre Interaktion.
Vorsicht! Durch diese Operatoren können Formeln unterschiedlich aussehen, aber äquivalent sein. Die folgenden Beispiele führen zur gleichen Regression:
y ~ x1 * x2
y ~ x1 + x2 + x1:x2
Nicht sicher, warum das gleich ist? Sieh dir diese R-Snippets an:
# Set seed
set.seed(123)
# Data
x = rnorm(5)
x2 = rnorm(5)
y = rnorm(5)
# Model frame
model.frame(y ~ x * x2, data = data.frame(x = x, y = y, x2=x2))
| y | x | x2 |
|---|---|---|
| 1.7150650 | -0.56047565 | 1.2240818 |
| 0.4609162 | -0.23017749 | 0.3598138 |
| -1.2650612 | 1.55870831 | 0.4007715 |
| -0.6868529 | 0.07050839 | 0.1106827 |
| -0.4456620 | 0.12928774 | -0.5558411 |
model.frame(y ~ x + x2 + x:x2, data = data.frame(x = x, y = y, x2))
| y | x | x2 |
|---|---|---|
| 1.7150650 | -0.56047565 | 1.2240818 |
| 0.4609162 | -0.23017749 | 0.3598138 |
| -1.2650612 | 1.55870831 | 0.4007715 |
| -0.6868529 | 0.07050839 | 0.1106827 |
| -0.4456620 | 0.12928774 | -0.5558411 |
Kein Problem, wenn du model.frame() noch nicht kennst. Später im Tutorial kommt mehr dazu!
Hier noch ein Beispiel für Schachtelung, was sich zu y ~ a + a:b erweitern lässt:
y ~ a + b %in% a
All diese Operatoren sind praktisch, aber was, wenn du wirklich eine arithmetische Operation ausführen willst? Sagen wir, du möchtest x und x^2 in dein Modell aufnehmen. Du würdest vermutlich y ~ x + x^2 schreiben.
Bekommst du damit das gewünschte Ergebnis? Schau hin:
model.frame( y ~ x + x^2, data = data.frame(x = rnorm(5), y = rnorm(5)))
y ~ x + x^2
| y | x |
|---|---|
| -0.2053091 | 1.18231565 |
| -0.3030972 | 0.04779636 |
| -0.7621604 | 0.86382418 |
| -0.1377784 | -1.18333097 |
| -0.3813125 | -1.25247842 |
Das ist nicht das, was du erwartet hast!
Im Beispiel schützt du den arithmetischen Ausdruck nicht. Daher verwirft R den Term x^2, da er als Duplikat von x gilt.
Warum?
x liefert dir den Haupteffekt von x, und x^2 den Haupteffekt und die Interaktion 2. Ordnung von x. Am Ende landet x im Model Frame, weil der Haupteffekt bereits über den x-Term enthalten ist und es nichts gibt, mit dem x im x^2-Term gekreuzt werden könnte, um Interaktionen 2. Ordnung zu erzeugen.
Um das zu vermeiden, hast du zwei Möglichkeiten:
- Berechne und speichere alle Variablen vorab.
- Nutze den
I()- oder „as-is“-Operator:y ~ x + I(x^2).
Sieh dir an, was I() bewirkt:
model.frame( y ~ x + I(x^2), data = data.frame(x = rnorm(5), y = rnorm(5)))
y ~ x + I(x^2)
| y | x | I(x^2) |
|---|---|---|
| 1.414090 | -0.1996230 | 0.039849.... |
| 1.777646 | -1.0675904 | 1.139749.... |
| 1.710137 | -1.4071841 | 1.980167.... |
| 1.259111 | -1.3747289 | 1.889879.... |
| -1.490866 | 0.8323668 | 0.692834.... |
Diese letzte Codezeile sagt R, die Werte von x^2 vor der Verwendung in der Formel zu berechnen. Hinweis: Mit dem „as-is“-Operator kannst du eine Variable auch skalieren, indem du sie in I() packst:
y ~ I(2 * x)
Das wirkt abstrakt? Dann noch zwei typische Fälle: Bei der polynomialen Regression arbeitest du mit I(). Bei einem faktoriellen ANOVA-Modell, das auf Interaktionen bis depth=2 begrenzt ist, brauchst du I() nicht, da du die Formel zu Haupteffekten von a, b, c und ihren Interaktionen 2. Ordnung erweiterst:
# Polynomial Regression
y ~ x + I(x^2) + I(x^3)
# Factorial ANOVA
y ~ (a*b*c)^2
Praktisch ist auch der Operator ., wenn du mit vielen Variablen arbeitest. Innerhalb einer Formel steht er für alle übrigen Variablen in der Matrix, die noch nicht im Modell enthalten sind. Das ist hilfreich, wenn du eine Regression auf einer Matrix oder einem Dataframe laufen lassen willst, ohne alle Variablennamen zu tippen:
y ~ .
Formeln in R inspizieren
Wenn du eine Formel erstellt hast, möchtest du sie vielleicht untersuchen. Hier sind ein paar Tools, mit denen du deine R-Formeln näher beleuchten kannst.
Hinweis: Einige Wege hast du bereits gesehen, etwa attributes(), typeof(), class() usw.
terms()-Funktion
Zum Untersuchen und Vergleichen von Formeln kannst du terms() nutzen:
m <- formula("y ~ x1 + x2")
terms(m)
y ~ x1 + x2
attr(,"variables")
list(y, x1, x2)
attr(,"factors")
x1 x2
y 0 0
x1 1 0
x2 0 1
attr(,"term.labels")
[1] "x1" "x2"
attr(,"order")
[1] 1 1
attr(,"intercept")
[1] 1
attr(,"response")
[1] 1
attr(,".Environment")
<environment: R_GlobalEnv>
all.vars
Wenn du die Namen der Variablen im Modell wissen willst, nutze all.vars. Die Funktion gibt einen Character-Vektor mit allen in der Formel vorkommenden Namen zurück:
print(all.vars(m))
[1] "y" "x1" "x2"
update()-Funktion
Um Formeln zu ändern, ohne sie in Strings zu verwandeln, kannst du update() verwenden:
update(y ~ x1 + x2, ~. + x3)
y ~ x1 + x2 + x3
Hinweis: Du hättest die Formel auch mit as.character() in einen String umwandeln und dann mit paste() schnell aufbauen können. Wenn du z. B. eine weitere rechte Variable hinzufügen willst, kannst du sie einfach „anpasten“:
as.formula(paste("y ~ x1 + x2", "x3", sep = "+"))
factors <- c("x2", "x3")
as.formula(paste("y~", paste(factors, collapse="+")))
y ~ x1 + x2 + x3
y ~ x2 + x3
Wie du siehst, kannst du mit den Argumenten sep oder collapse festlegen, wie die Terme in deiner Formel getrennt werden.
paste() ist aber nicht der einzige Weg. Du kannst auch reformulate() verwenden:
reformulate(termlabels = factors, response = 'y')
y ~ x2 + x3
is.formula()
Prüfe schnell, ob deine Variable eine Formel ist, indem du sie an is.formula() übergibst. Beachte, dass diese Funktion aus der plyr-Library stammt. Du musst sie also laden, bevor du is.formula() aufrufst!
# Load `plyr`
library(plyr)
# Check `m`
is.formula(m)
TRUE
Wann Formeln verwenden
Du hast bereits gelesen, dass R-Formeln universelle Werkzeuge sind, die nicht auf Modellierung beschränkt sind, und Beispiele gesehen, wo du sie nutzen kannst. Jetzt gehen wir tiefer darauf ein: Du siehst Anwendungsfälle, in denen dir Formeln Vorteile bringen. Natürlich behandeln wir Modellierungs- und Grafikfunktionen aus Paketen wie lattice und stats, aber auch Non-Standard Evaluation in dplyr.
Modellierungsfunktionen
R ist großartig für statistische Modellierung. Diese ist eine vereinfachte, mathematisch formalisierte Annäherung an die Realität und ggf. Grundlage für Vorhersagen. Ein statistisches Modell bildet den Datenentstehungsprozess idealisiert ab. Für die Modellierung brauchst du Modellierungsfunktionen.
Ein typisches Beispiel sind Funktionen, die ein formula-Objekt als Argument erwarten. Weitere Argumente sind oft data für den Data Frame, den du während der Modellierung anheften willst, subset zur Auswahl der Daten usw. Wenn du wissen willst, welche Argumente eine Funktion erwartet, nutze help() oder ? in der R-Konsole.
Diese Funktionen liefern ein Modellobjekt mit allen Informationen zum Fit. Generische R-Funktionen wie print(), summary(), plot(), anova() usw. haben Methoden für spezifische Objektklassen, um passende Informationen auszugeben.
Eine der bekanntesten Funktionen ist lm() zum Fitten linearer Modelle. Du kannst damit Regressionen, einfaktorielle Varianzanalysen und Kovarianzanalysen durchführen. Beispiel mit lm() und anschließender Inspektion via print():
lm.m <- lm(Sepal.Width ~ Petal.Width + log(Petal.Length) + Species,
data = iris,
subset = Sepal.Length > 4.6)
print(lm.m)
Call:
lm(formula = Sepal.Width ~ Petal.Width + log(Petal.Length) +
Species, data = iris, subset = Sepal.Length > 4.6)
Coefficients:
(Intercept) Petal.Width log(Petal.Length) Speciesversicolor
3.1531 0.6620 0.4612 -1.9265
Speciesvirginica
-2.3088
lm() nutzt zunächst die Formel und das passende Environment, um die Beziehungen zwischen Variablen in einen Data Frame zu übersetzen.
Außerdem gibt es model.frame()-Methoden, von denen du bereits eine gesehen hast. Sie werden meist genutzt, um den Model Frame ohne weitere Argumente aus dem gefitteten Objekt zu holen bzw. zu rekonstruieren. So kannst du Spalten erhalten, die zu Argumenten des ursprünglichen Aufrufs außer formula, subset und weights gehören. Die glm()-Methode behandelt z. B. offset, etastart und mustart.
Im nächsten Code holst du mit model.frame() einen Data Frame aus dem gefitteten Objekt zurück. Beachte: Das Argument subset ist leicht verändert.
stats::model.frame(formula = Sepal.Width ~ Petal.Width + log(Petal.Length) + Species,
data = iris,
subset = Sepal.Length > 6.9,
drop.unused.levels = TRUE)
| Sepal.Width | Petal.Width | log(Petal.Length) | Species | |
|---|---|---|---|---|
| 51 | 3.2 | 1.4 | 1.547563 | versicolor |
| 103 | 3.0 | 2.1 | 1.774952 | virginica |
| 106 | 3.0 | 2.1 | 1.887070 | virginica |
| 108 | 2.9 | 1.8 | 1.840550 | virginica |
| 110 | 3.6 | 2.5 | 1.808289 | virginica |
| 118 | 3.8 | 2.2 | 1.902108 | virginica |
| 119 | 2.6 | 2.3 | 1.931521 | virginica |
| 123 | 2.8 | 2.0 | 1.902108 | virginica |
| 126 | 3.2 | 1.8 | 1.791759 | virginica |
| 130 | 3.0 | 1.6 | 1.757858 | virginica |
| 131 | 2.8 | 1.9 | 1.808289 | virginica |
| 132 | 3.8 | 2.0 | 1.856298 | virginica |
| 136 | 3.0 | 2.3 | 1.808289 | virginica |
Tipp: Im Paket stats gibt es weitere Funktionen, die Formeln nutzen, z. B. aggregate().
Für lineare Mixed-Effects-Modelle, mit denen du Zufallseffekte modellieren kannst (z. B. Beobachterunterschiede), nutzt du das Paket nlme mit der Funktion lme(). Auch hier ist formula das erste Argument, und es gibt ein data-Argument!
# Load packages
library(MASS)
library(nlme)
# Get some data
data(oats)
# Adjust the data names and columns
names(oats) = c('block', 'variety', 'nitrogen', 'yield')
oats$mainplot = oats$variety
oats$subplot = oats$nitrogen
# Fit a non-linear mixed-effects model
nlme.m = lme(yield ~ variety*nitrogen,
random = ~ 1|block/mainplot,
data = oats)
# Retrieve a summary
summary(nlme.m)
Linear mixed-effects model fit by REML
Data: oats
AIC BIC logLik
559.0285 590.4437 -264.5143
Random effects:
Formula: ~1 | block
(Intercept)
StdDev: 14.64496
Formula: ~1 | mainplot %in% block
(Intercept) Residual
StdDev: 10.29863 13.30727
Fixed effects: yield ~ variety * nitrogen
Value Std.Error DF t-value p-value
(Intercept) 80.00000 9.106958 45 8.784492 0.0000
varietyMarvellous 6.66667 9.715028 10 0.686222 0.5082
varietyVictory -8.50000 9.715028 10 -0.874933 0.4021
nitrogen0.2cwt 18.50000 7.682957 45 2.407927 0.0202
nitrogen0.4cwt 34.66667 7.682957 45 4.512152 0.0000
nitrogen0.6cwt 44.83333 7.682957 45 5.835427 0.0000
varietyMarvellous:nitrogen0.2cwt 3.33333 10.865342 45 0.306786 0.7604
varietyVictory:nitrogen0.2cwt -0.33333 10.865342 45 -0.030679 0.9757
varietyMarvellous:nitrogen0.4cwt -4.16667 10.865342 45 -0.383482 0.7032
varietyVictory:nitrogen0.4cwt 4.66667 10.865342 45 0.429500 0.6696
varietyMarvellous:nitrogen0.6cwt -4.66667 10.865342 45 -0.429500 0.6696
varietyVictory:nitrogen0.6cwt 2.16667 10.865342 45 0.199411 0.8428
Correlation:
(Intr) vrtyMr vrtyVc ntr0.2 ntr0.4 ntr0.6
varietyMarvellous -0.533
varietyVictory -0.533 0.500
nitrogen0.2cwt -0.422 0.395 0.395
nitrogen0.4cwt -0.422 0.395 0.395 0.500
nitrogen0.6cwt -0.422 0.395 0.395 0.500 0.500
varietyMarvellous:nitrogen0.2cwt 0.298 -0.559 -0.280 -0.707 -0.354 -0.354
varietyVictory:nitrogen0.2cwt 0.298 -0.280 -0.559 -0.707 -0.354 -0.354
varietyMarvellous:nitrogen0.4cwt 0.298 -0.559 -0.280 -0.354 -0.707 -0.354
varietyVictory:nitrogen0.4cwt 0.298 -0.280 -0.559 -0.354 -0.707 -0.354
varietyMarvellous:nitrogen0.6cwt 0.298 -0.559 -0.280 -0.354 -0.354 -0.707
varietyVictory:nitrogen0.6cwt 0.298 -0.280 -0.559 -0.354 -0.354 -0.707
vM:0.2 vV:0.2 vM:0.4 vV:0.4 vM:0.6
varietyMarvellous
varietyVictory
nitrogen0.2cwt
nitrogen0.4cwt
nitrogen0.6cwt
varietyMarvellous:nitrogen0.2cwt
varietyVictory:nitrogen0.2cwt 0.500
varietyMarvellous:nitrogen0.4cwt 0.500 0.250
varietyVictory:nitrogen0.4cwt 0.250 0.500 0.500
varietyMarvellous:nitrogen0.6cwt 0.500 0.250 0.500 0.250
varietyVictory:nitrogen0.6cwt 0.250 0.500 0.250 0.500 0.500
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-1.81300898 -0.56144838 0.01758044 0.63864476 1.57034166
Number of Observations: 72
Number of Groups:
block mainplot %in% block
6 18
Hinweis: Neben nlme gibt es weitere Pakete wie lme4, die sich der Schätzung linearer und verallgemeinerter linearer Mixed-Effects-Modelle widmen.
Ein weiteres Beispiel sind Funktionen für Generalized Linear Models (GLM). In R nutzt du die Funktion glm(). Auch hier kommen die Argumente formula und data zum Einsatz:
# Load package
library(MPDiR)
# Get the data
data(Chromatic)
# Model
glm.m <- glm(Thresh ~ Axis:(I(Age^-1) + Age),
family = Gamma(link = "identity"),
data = Chromatic)
# Get back a summary
summary(glm.m)
Call:
glm(formula = Thresh ~ Axis:(I(Age^-1) + Age), family = Gamma(link = "identity"),
data = Chromatic)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.2160 -0.3728 -0.0805 0.2311 1.2932
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 3.282e-04 9.965e-05 3.294 0.00106 **
AxisDeutan:I(Age^-1) 7.803e-03 3.686e-04 21.172 < 2e-16 ***
AxisProtan:I(Age^-1) 8.271e-03 3.863e-04 21.410 < 2e-16 ***
AxisTritan:I(Age^-1) 1.166e-02 5.284e-04 22.065 < 2e-16 ***
AxisDeutan:Age 1.521e-05 3.418e-06 4.450 1.06e-05 ***
AxisProtan:Age 1.540e-05 3.434e-06 4.484 9.10e-06 ***
AxisTritan:Age 4.812e-05 5.838e-06 8.241 1.48e-15 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for Gamma family taken to be 0.2054848)
Null deviance: 543.35 on 510 degrees of freedom
Residual deviance: 100.40 on 504 degrees of freedom
AIC: -4777.6
Number of Fisher Scoring iterations: 6
Bevor wir zu den Grafikfunktionen kommen, noch ein wichtiger Punkt: Wenn du Formeln in Modellierungsfunktionen wie lm() nutzt, findet eine Standardkonvertierung von der Formel zu Funktionen statt. Zur Veranschaulichung kehren wir zu einem früheren Beispiel zurück:
lm.m <- lm(Sepal.Width ~ Petal.Width + log(Petal.Length) + Species,
data = iris,
subset = Sepal.Length > 4.6)
Obwohl hier eine lineare Regression gefittet wird, wird die Formel sowohl zur Spezifikation des symbolischen Modells als auch zur Erzeugung der Designmatrix genutzt. Eine Designmatrix ist die zweidimensionale Darstellung der Prädiktoren, mit Instanzen in Zeilen und Attributen in Spalten. Sie ist auch als X-Matrix bekannt.
Die Formelmethode definiert dabei, welche Spalten in die Designmatrix aufgenommen werden.
Was heißt das konkret? Schau dir Folgendes an:
# A formula
y ~ x
# A converted formula
y = a_1 + a_2 * x
Das ist eine einfache Konvertierung: y ~ x wird zu y = a_1 + a_2 * x.
Um zu sehen, was R tatsächlich macht, kannst du die Funktion model_matrix() verwenden. Sie erzeugt eine Design- bzw. Modellmatrix, indem sie z. B. Faktoren je nach Kontrasten in Dummy-Variablen expandiert und Interaktionen entsprechend erweitert.
Vergiss nicht, den Data Frame df und die Formel zu übergeben, um ein Tibble zu erhalten, das die Modellgleichung definiert:
# Load packages
library(tidyverse)
library(modelr)
# A data frame
df <- tribble(
~y, ~x1, ~x2,
4, 2, 5,
5, 1, 6
)
# Model matrix
model_matrix(df, y ~ x1)
| (Intercept) | x1 |
|---|---|
| 1 | 2 |
| 1 | 1 |
Es erscheint eine zusätzliche Spalte (Intercept). Das ist das Standardverhalten in R. Der Achsenabschnitt wird einfach als Spalte voller Einsen modelliert. Wenn du das nicht willst, entferne ihn explizit mit -1 in der Formel:
model_matrix(df, y ~ x1-1)
| x1 |
|---|
| 2 |
| 1 |
Hinweis: Die Modellmatrix wächst erwartungsgemäß, wenn du weitere Variablen hinzufügst.
model_matrix(df, y ~ x1 + x2)
| (Intercept) | x1 | x2 |
|---|---|---|
| 1 | 2 | 5 |
| 1 | 1 | 6 |
Grafikfunktionen in R
Ein weiterer wichtiger Einsatzort für Formeln in R sind Grafikfunktionen. Es gibt viele Pakete, hier konzentrieren wir uns auf graphics, lattice, ggplot2 und ggformula.
graphics
Das Base-R-Paket graphics erlaubt dir, ein Streudiagramm per Formel zu spezifizieren oder Punkte, Linien oder Text hinzuzufügen. Beispiel:
# Get data
data(airquality)
# Plot
plot(Ozone ~ Wind, data = airquality, pch = as.character(Month))

Wenn du mehr wissen willst, schau dir diese Seite an.
lattice
lattice basiert auf Grid Graphics. Es ist ein universelles Grafikpaket, das alternative Implementierungen vieler Base-Graphics-Funktionen bietet. Beispiele sind Streudiagramme mit xyplot(), Balkendiagramme mit barchart() und Boxplots mit bwplot().
Tipp: Willst du mehr über lattice lernen? Schau dir den DataCamp-Kurs Data Visualization in R mit lattice an.
Das Besondere: lattice verwendet die Formelschreibweise statistischer Modelle, um den gewünschten Plot und die zu plottenden Variablen zu beschreiben. Zusätzlich kommt die Pipe bzw. der senkrechte Strich | dazu, um eine Konditionierungsvariable anzugeben:
# Load package
library(lattice)
# Plot histogram
histogram(~ Ozone | factor(Month),
data = airquality,
layout = c(2, 3),
xlab = "Ozone (ppb)")

Hinweis: Genau wie bei den Modellierungsfunktionen haben die Funktionen in lattice neben formula auch ein data-Argument, wie zu erwarten.
Du könntest das data-Argument auch weglassen, müsstest dann aber die Daten anhängen:
# Load package
library(lattice)
# Attach data
attach(airquality)
# Plot
histogram(~ Ozone | factor(Month),
layout = c(2, 3),
xlab = "Ozone (ppb)")
ggplot2
Du kannst Formeln in verschiedenen ggplot2-Funktionen verwenden:
geom_smooth()bzw.stats_smooth(), um die in der Glättung zu nutzende Formel anzugeben; Das beeinflusst die Form des Fits.facet_wrap(), um Panels für die Darstellung zu definieren.facet_grid(), um Zeilen und Spalten mit oder ohne Facettierung festzulegen.
# Load package
library(ggplot2)
# Plot
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
geom_smooth(method = "lm",
formula = y ~ splines::bs(x, 3),
se = FALSE)

Beachte: Für diesen Plot verwendet die Formel die Buchstaben x und y, nicht die Variablennamen. Bei facet_wrap() ist das anders:
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
geom_smooth(span = 0.8) +
facet_wrap(~drv)

Du willst mehr über ggplot2 lernen? Sieh dir diesen Kurs an oder wirf einen Blick in die Dokumentation.
ggformula
Das Paket ggformula baut auf ggplot2 auf, stellt aber ein Formeln-basiertes Interface bereit – ähnlich wie lattice. Außerdem kommt der Pipe-Operator zum Einsatz, um komplexere Grafiken aus einfacheren Bausteinen zusammenzusetzen.
Tipp: Wenn du mehr über den Pipe-Operator in R wissen willst, lies dieses Tutorial.
Die Grundform für einen Plot mit ggformula ist
gf_plottype(formula, data = mydata)
Hinweis: Die Funktion gf_plottype() beginnt mit gf, als Erinnerung daran, dass du mit Formeln-basierten Interfaces zu ggplot2 arbeitest; g steht für ggplot2 und f für „formula“.
So sieht das in R aus:
# Load package
library(ggformula)
# Plot
gf_point(mpg ~ hp, data = mtcars)

Natürlich ist das nur ein Basisplot. Du kannst viel mehr tun: andere Glyphen und Attribute wählen, Ein- oder Zwei-Variablen-Plots erstellen, Positionen anpassen usw. Wichtig ist: In diesem Paket sind Formeln die Hauptzutat für Grafiken!
Wenn du tiefer einsteigen willst, lies die Vignette oder die RDocumentation-Seite.
dplyr
dplyr ist ein Beispiel für ein Paket, das mit Non-Standard Evaluation arbeitet. Andere Beispiele: library(magrittr) versus library("magrittr") – beides funktioniert, obwohl in einer Zeile Anführungszeichen stehen und in der anderen nicht! Vergleiche das mit install.packages():
# This will work
install.packages("magrittr")
# This won't work
install.packages(magrittr)
In R musst du normalerweise Anführungszeichen setzen, wenn du Teile eines Objektnamens angibst, aber einige Funktionen – wie library() – brauchen sie nicht. Diese Funktionen sind so gebaut, dass sie nicht dem Standard folgen. Manche haben sogar gar keine Standard-Variante!
Bei dplyr arbeiten die meisten Funktionen wie üblich: Alle Funktionen haben Standard Evaluation (SE). Für die interaktive Nutzung gibt es zusätzlich Non-Standard Evaluation (NSE), die dir Tipparbeit erspart.
(Hand aufs Herz: Die ganzen Anführungszeichen zu tippen, kann nerven!)
Deshalb nutzen die meisten dplyr-Funktionen NSE. Sie folgen nicht den üblichen R-Auswertungsregeln, sondern erfassen den eingegebenen Ausdruck und werten ihn auf eigene Weise aus.
Das heißt aber nicht, dass es keine Standardvariante gibt. Jede Funktion mit NSE hat (und sollte haben) eine Standard-Variante mit Unterstrich, die die eigentliche Berechnung durchführt. Entsprechend gibt es mehrere Verben in dplyr: select(), select_(), mutate(), mutate_() usw.
Interaktiv werden diese Funktionen zuerst mit dem Paket lazyeval ausgewertet, bevor sie zur SE-Variante weitergereicht werden. Unter der Haube wird z. B. select() mit lazyeval ausgewertet und dann an select_() übergeben.
Es gibt drei Arten, Variablen in SE-Funktionen zu quoten, die dplyr und lazyeval verstehen:
- Formeln,
quote()und- Strings
# Load `dplyr`
library(dplyr)
# NSE evaluation
select(iris, Sepal.Length, Petal.Length)
# standard evaluation
select_(iris, ~Sepal.Length)
select_(iris, ~Sepal.Length, ~Petal.Length) #works
select_(iris, quote(Sepal.Length), quote(Petal.Length)) # yes!
select_(iris, "Sepal.Length", "Petal.Length", "Species")
Tipp: Wenn du mehr zu Non-Standard Evaluation lesen willst, schau in das Kapitel in Hadley Wickhams Buch Advanced R.
R-Formel-Pakete
Du hast gesehen, wie du Formeln mit Funktionen wie as.formula, update(), all.vars usw. erstellst und inspizierst. Das waren einfache Operationen und Manipulationen. Was ist mit fortgeschrittenen Manipulationen? Diese Pakete könnten spannend für dich sein!
Formula-Paket
Dieses Paket ist auf CRAN verfügbar und ideal, wenn du Formeln auf das nächste Level heben willst. Es erweitert die Basisklasse formula.
Konkret erweitern Formula-Objekte die Basis-Formelobjekte: Du kannst Formeln definieren, die einen zusätzlichen Operator | für mehrere Teile akzeptieren oder die alle Formeloperatoren (inklusive Pipe) auf der linken Seite halten können, um mehrere Responses zu unterstützen.
Beispiele für Formeln, die du erstellen kannst:
- Mehrteilige Formeln wie
y ~ x1 + x2 | u1 + u2 + u3 | v1 + v2 - Mehrere Responses wie
y1 + y2 ~ x1 + x2 + x3 - Mehrteilige Responses wie
y1 | y2 + y3 ~ xund - Kombinationen der obigen.
# Load package
library(Formula)
# Create formulas
f1 <- y ~ x1 + x2 | z1 + z2 + z3
F1 <- Formula(f1)
# Retrieve the class of `F1`
class(F1)
- 'Formula'
- 'formula'
Hinweis: Die Funktionen as.formula() und is.formula() wurden in diesem Paket ebenfalls erweitert: Du nutzt is.Formula() und as.Formula().
Mehr dazu hier.
formula.tools
Dieses Paket bietet „programmatic utilities for manipulating formulas, expressions, calls, assignments and other R Objects“. Kurz: Du kannst damit Formeln programmatisch analysieren und ändern sowie Namen und Symbole extrahieren und ersetzen. Das Paket stammt von Christopher Brown.
Nützliche Funktionen daraus sind z. B.:
get.vars(): Im Unterschied zuall.vars()extrahiert diese Funktion Variablennamen aus verschiedenen R-Objekten und interpoliert dabei Symbole usw. zu Variablennamen.invert(): Invertiert Operatoren in einem Objekt, z. B. einer Formel.is.one.sided(): Praktisch, um festzustellen, ob eine Formel ein- oder zweiseitig ist.
Merke: Eine Formel ist einseitig, wenn sie so aussieht: ~x; Zweiseitig ist sie als x~y formuliert.
- ...
Es gibt noch mehr zu entdecken!
Geschafft! Du hast dieses Tutorial zu R-Formeln durchgearbeitet. Wenn du weiter lesen willst, schau in Hadley Wickhams R for Data Science. Dort gibt es ein ganzes Kapitel zu Formeln und Modellfamilien in R.
Sieh dir auch DataCamps Getting Started with the Tidyverse: Tutorial an.
Fallen dir weitere Anwendungsfälle oder Pakete ein, mit denen du Formeln bearbeiten kannst? Sag gern auf Twitter Bescheid: @willems_karlijn.
