30  Diagramme mit ggplot()

Das Zusatzpaket ggplot (für grammar of graphics plot) ist Hadley Wickhams1 R-Implementation der Grammar of Graphics von Leland Wilkinson2. Es ist eines der ersten Pakete des Tidyverse (welches damals noch nicht so hieß). Die Funktion zum plotten heisst “ggplot()”, das Installationspaket in R lautet jedoch ggplot2.

# ggplot2 installieren
install.packages("ggplot2", dependencies=T)

# ggplot2 aktivieren
library(ggplot2)

Da ggplot aus dem Tidyverse stammt (siehe Kapitel @ref(Kapitel-Tidyverse)), ist es von Vorteil, wenn die zu verabreitenden Datensätze dem Prinzip “ein Fall pro Zeile” (tidy data bzw. long table) folgen. Das bedeutet, dass jede Beobachtung (auch Wiederholungen) in einer eigenen Zeile steht, und die jeweiligen Variablen durch die Spalten repräsentiert werden.

Die Idee einer Grammatik für Diagramme ist, dass man jedes Diagramm aus den selben Komponenten aufbauen kann:

  1. einem tidy Datensatz

  2. einem Koordinatensystem

  3. und Geomen (Punkte, Balken, Linien, Flächen)

In der Praxis ist ja auch so: wir haben einen Datensatz, und wir haben eine Vorstellung darüber, was wir gerne plotten möchten, z.B. ein Boxplot des Alters der Probanden nach Geschlecht gruppiert. Das Plott soll so aussehen, dass die Boxplots nebeneinander stehen.

Übersetzt bedeutet das, dass auf der X-Achse die Boxplots ausgerichtet sind, und dass die Y-Achse das Alter darstellt.

Diese Angaben werden an ggplot übergeben, wobei wir (ähnlich der Pipe) unsere Angaben mit einem + Zeichen aneinanderreihen können.

Die Zuweisung von Variablen auf das Koordinatensystem erfolgt über die Funktion aes() (für aesthetics). In userem Beispiel würden wir so beginnen:

# Lade Testdatensatz
load(url("https://www.produnis.de/R/nw.RData"))

# Grundlegende Zuweisung an gglot()
ggplot(nw, aes(x=sex, y=age))

Dies produziert noch kein Diagramm, da noch wichtige Angaben fehlen, aber wir haben die grundlegenden Zuweisungen vorgenommen. Aus dem Datensatz nw wurde die Variable sex der X-Achse zugewiesen, und Variable age wurde der Y-Achse zugewiesen. Jetzt muss ggplot() noch wissen, was überhaupt gezeichnet werden soll, also welches “Geom” zum Einsatz kommen soll.

In unserem Beispiel ist das geom_boxplot(), welches wir mit einem + Zeichen an ggplot() übergeben:

ggplot(nw, aes(x=sex, y=age)) +
   geom_boxplot()

Weitere Beispiele zu Boxplots finden Sie in Abschnitt 30.5.

Über das + Zeichen können beliebig viele Layer (Schichten) dem Plot hinzugefügt werden. Dabei werden die aesthetics übernommen, sie können aber in jedem Geom per aes() neu definiert werden.

ggplot biete hierfür viele nützliche Komponenten, unter anderem:

nützliche ggplot() Stylingbefehle
Eigenschaft Befehl
Plot Überschrift ggtitle("Überschrift", subtitle="Unterschrift")
theme(plot.title = element_text(size=8))
theme(plot.subtitle = element_text(size=8), face="bold"))
Plot Untertitel ggtitle(caption="Untertitel")
theme(plot.caption = element_text(size=8), face="italic")
Legende Titel theme(legend.title.x = element_text(size=8))
Legende Text theme(legend.text.x = element_text(size=8))
X-Achse Titel xlab("Titel der X-Achse")
theme(axis.title.x = element_text(size=8))
X-Achse Text theme(axis.text.x = element_text(size=8))
X-Achse Ticks theme(axis.ticks.x = element_text(size=8))
scale_x_continuous(breaks = c(0, 2, 6, 8, 15, 25))
X-Achse Länge xlim(-3,3)
Y-Achse Titel ylab("Titel der Y-Achse")
theme(axis.title.y = element_text(size=8))
Y-Achse Text theme(axis.text.y = element_text(size=8))
Y-Achse Ticks theme(axis.ticks.y = element_text(size=8))
scale_y_continuous(breaks = c(0, 2, 6, 8, 15, 25))
Y-Achse Länge ylim(-3,3)
Text annotate(geom="text", x=0, y=1, label="Huhu", color="black")
Plot aufteilen facet_grid(. ~ Gruppe, scales="free", space="free")
Plot drehen coord_flip()
Theme blank theme_void()
Theme klassisch theme_classic()
vertikale Linie geom_vline(xintercept=0, linetype="dotted")
horizontale Linie geom_hline(yintercept=0, linetype="dashed")

In RStudio ist ein “Cheatsheet” (siehe Abschnitt 26.1) zu ggplot verlinkt. Hier finden Sie (fast) alle verfügbaren Komponenten zusammengefasst. Klicken Sie in der Menüzeile von RStudio auf Help \(\rightarrow\) Cheatsheets \(\rightarrow\) Data Visualization with ggplot2.

30.1 Punktwolke

Schauen wir uns die Arbeitsjahre der Pflegenden im Nachtdienst an, indem wir eine Punktwolke erstellen. Auf der X-Achse sollen die Jahre als Pflegefachkraft abgebildet werden, auf der Y-Achse die Arbeitsjahre im Nachtdienst.

  ggplot(nw, aes(x=workyear, y=worknight)) +
    geom_point()

Als weitere aesthetic können wir festlegen, dass die Farben der Punkte nach Geschlecht unterschiedlich sein sollen. Im Geom geom_point() können wir zudem das Aussehen der Punkte über den Parameter shape ändern.

  ggplot(nw, aes(x=workyear, y=worknight, col=sex)) +
    geom_point(shape=8)

Beachten Sie, dass zwischen col (Rahmen) und fill (Füllung) unterschieden wird, wenn Sie Farben zuweisen.

Die Achsen möchten wir anders beschriften:

  ggplot(nw, aes(x=workyear, y=worknight, col=sex)) +
    geom_point(shape=8) +
    # Beschriftung der X-Achse
    xlab("Arbeitsjahre in der Pflege") +
    # Beschriftung der Y-Achse
    ylab("Arbeitsjahre als Nachtwache") +
    # Beschriftung der Legendenbox
    labs(color="Geschlecht")

Die Textgröße der Achsen könne angepasst werden:

  ggplot(nw, aes(x=workyear, y=worknight, col=sex)) +
    geom_point(shape=8) +
    # Beschriftung der X-Achse
    xlab("Arbeitsjahre in der Pflege") +
    # Beschriftung der Y-Achse
    ylab("Arbeitsjahre als Nachtwache") +
    # Beschriftung der Legendenbox
    labs(color="Geschlecht") +
    # Textgröße X-Beschriftung
    theme(axis.title.x = element_text(size=18)) + 
    # Textgröße der X-Ticks
    theme(axis.text.x = element_text(size=10)) + 
    # Textgröße Y-Beschriftung
    theme(axis.title.y = element_text(size=18)) + 
    # Textgröße der Y-Ticks
    theme(axis.text.y = element_text(size=10))

Nun können wir noch eine Regressionsgerade mit geom_smooth() hinzufügen:

  ggplot(nw, aes(x=workyear, y=worknight, col=sex)) +
    geom_point(shape=8) +
    # Beschriftung der X-Achse
    xlab("Arbeitsjahre in der Pflege") +
    # Beschriftung der Y-Achse
    ylab("Arbeitsjahre als Nachtwache") +
    # Beschriftung der Legendenbox
    labs(color="Geschlecht") +
    # Textgröße X-Beschriftung
    theme(axis.title.x = element_text(size=18)) + 
    # Textgröße der X-Ticks
    theme(axis.text.x = element_text(size=10)) + 
    # Textgröße Y-Beschriftung
    theme(axis.title.y = element_text(size=18)) + 
    # Textgröße der Y-Ticks
    theme(axis.text.y = element_text(size=10))  +
    # Regressionsgerade
    geom_smooth(method="lm")
## `geom_smooth()` using formula 'y ~ x'

Das Plot könne wir zudem in verschiedene Bereiche aufteilen. Hierzu nehmen wir die Variable timework, die angibt, ob die Pflegefachpersonen in Voll- oder Teilzeit arbeiten. Die Variable übergeben wir der Komponente facet_grid().

  ggplot(nw, aes(x=workyear, y=worknight, col=sex)) +
    geom_point(shape=8) +
    # Beschriftung der X-Achse
    xlab("Arbeitsjahre in der Pflege") +
    # Beschriftung der Y-Achse
    ylab("Arbeitsjahre als Nachtwache") +
    # Beschriftung der Legendenbox
    labs(color="Geschlecht") +
    # Textgröße X-Beschriftung
    theme(axis.title.x = element_text(size=18)) + 
    # Textgröße der X-Ticks
    theme(axis.text.x = element_text(size=10)) + 
    # Textgröße Y-Beschriftung
    theme(axis.title.y = element_text(size=18)) + 
    # Textgröße der Y-Ticks
    theme(axis.text.y = element_text(size=10))  +
    # Regressionsgerade
    geom_smooth(method="lm") +
    # Plot aufteilen
    facet_grid(. ~ timework, scales = "free", space = "free")
## `geom_smooth()` using formula 'y ~ x'

Das Plot selbst kann jederzeit als Objekt gespeichert werden. So können Sie anschließend darauf zugreifen. Das ist sehr hilfreich, wenn man am Aussehen herumfeilt.

# Speichere als Objekt "p"
p <- ggplot(nw, aes(x=workyear, y=worknight, col=sex)) +
       # Beschriftung der X-Achse
       xlab("Arbeitsjahre in der Pflege") +
       # Beschriftung der Y-Achse
       ylab("Arbeitsjahre als Nachtwache") +
       # Beschriftung der Legendenbox
       labs(color="Geschlecht") +
       # Textgröße X-Beschriftung
       theme(axis.title.x = element_text(size=18)) + 
       # Textgröße der X-Ticks
       theme(axis.text.x = element_text(size=10)) + 
       # Textgröße Y-Beschriftung
       theme(axis.title.y = element_text(size=18)) + 
       # Textgröße der Y-Ticks
       theme(axis.text.y = element_text(size=10))  +
       geom_smooth(method="lm") +
       # Plot aufteilen
       facet_grid(. ~ timework, scales = "free", space = "free")

# was wurde gespeichert?
p
## `geom_smooth()` using formula 'y ~ x'

Der gespeicherte Plot enthält bereits die Regressionsgeraden.

Von hier aus können Sie leichter “herumspielen”:

# spiele mit "p" herum
p + geom_point(shape=2)  
## `geom_smooth()` using formula 'y ~ x'

# spiele mit "p" herum
p + geom_point(shape=10)  
## `geom_smooth()` using formula 'y ~ x'

Hier eine Auflistung, welche shape-Formen es gibt:

Abb. 30.1: shape nach Zahlen

Abb. 30.2: shape nach Formen

Natürlich funktioniert ggplot() auch in Kombination mit der Pipe.

nw %>% 
  drop_na() %>% 
    ggplot(aes(x=bed)) + 
    geom_bar(fill=rainbow(6)) + 
    coord_flip()

30.2 Diagramme speichern

Zum Speichern der Plots in eine Datei wird die Funktion ggsave() verwendet. Sie speichert das zuletzt erzeugte Plot.

# Plot speichern in Datei "MeinPlot.png"
ggsave("MeinPlot.png", units="mm", width=400, height=200, dpi=300, pointsize=7) 

Haben Sie ein Plot als Objekt zugewiesen, und möchten dieses nun in einer Datei speichern, übergeben Sie den Objektnamen mit dem Parameter plot.

# Plot liegt in Objekt "p"
# speichern in Datei "MeinPlot.png"
ggsave("MeinPlot.png", plot=p, units="mm", width=400, height=200, dpi=300, pointsize=7) 

30.3 Histrogramm

Histogramme können mit dem geom_histogram() hinzugefügt werden.

# Histogramm des Alters aus dem Datensatz "epa"
# lade Datensatz
load(url("https://www.produnis.de/R/epa.RData"))

epa %>% 
  ggplot(aes(x=age)) +
  geom_histogram(aes(y=..density..), col="white", fill="plum4") 
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Mit der Funktion stat_density() können wir die Dichtefunktion des Alters hinzufügen:

epa %>% 
  ggplot(aes(x=age)) +
  geom_histogram(aes(y=..density..), col="white", fill="plum4") + 
  stat_density(geom="line", colour="blue", linetype = "dotted")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Die Dichteverteilung kann auch als Fläche angezeigt werden. Über den Parameter alpha kann eingestellt werden, wie durchsichtig die Fläche sein soll.

epa %>% 
  ggplot(aes(x=age)) +
  geom_histogram(aes(y=..density..), col="white", fill="plum4") + 
  stat_density(geom="area", colour="blue", linetype = "dotted", alpha=0.5)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Die Dichteverteilung der Normalverteilung kann mit der Funktion stat_funtion() ebenso darübergelegt werden.

epa %>% 
  ggplot(aes(x=age)) +
  geom_histogram(aes(y=..density..), col="white", fill="plum4") + 
  stat_density(geom="area", colour="blue", linetype = "dotted", alpha=0.5) +
  stat_function(fun=dnorm, args=(c(mean=62,sd=18)), colour="red", linetype = "dashed")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

30.4 Säulen- und Balkendiagramm

Säulendiagramme werden mit geom_bar() erzeugt. Die Werte der Variable müssen als Faktor vorliegen.

# Lade Testdatensatz
load(url("https://www.produnis.de/R/nw.RData"))

p <-nw %>% 
      drop_na() %>% 
         ggplot(aes(x=bed))
p + geom_bar()

Ein Balkendiagramm wird erzeugt, indem die Kompontent coord_flip() hinzugefügt wird.

p + geom_bar() + coord_flip()

Sie können gruppierte Plots über die Aesthetic fill erzeugen.

  nw %>% 
    drop_na() %>% 
    ggplot(aes(x=bed, fill=timework)) +
    geom_bar()

Über den Parameter position können Sie die Balken nebeneinander gruppieren…

  nw %>% 
    drop_na() %>% 
    ggplot(aes(x=bed, fill=timework)) +
    geom_bar(position="dodge")

… oder als Prozentwerte plotten.

  nw %>% 
    drop_na() %>% 
    ggplot(aes(x=bed, fill=timework)) +
    geom_bar(position="fill")

Möchten Sie ein Barplot aus bereits berechneten Häufigkeiten erstellen, müssen Sie dies dem Geom über den Parameter stat=identity mitteilen. Die Daten müssen als Datenframe übergeben werden.

 # erzeuge Testdaten
 kategorie     <- c("Erdbeere", "Schokolade", "Vanille")
 haeufigkeiten <- c(10,20,30)
 df <- data.frame(kategorie, haeufigkeiten)

 ggplot(df, aes(x=kategorie, y=haeufigkeiten, fill=kategorie))+
    geom_bar(stat="identity", col="black")

30.5 Boxlots

Boxplots werden über das geom_boxplot() erstellt. Aus dem Nachtwachendatensatz erstellen wir ein Boxplot über die “Anzahl der Dienste am Stück” (nights).

load(url("https://www.produnis.de/R/nw.RData"))
ggplot(nw, aes(y=nights))+
    geom_boxplot()

Das Aussehen kann beliebig angepasst werden.

ggplot(nw, aes(y=nights))+
    # Die Ausreisser stylen
    geom_boxplot(colour="darkblue", fill="skyblue", outlier.colour="blue", outlier.shape=8, outlier.size=3) +
    # zeichne Whisker-Linien
    stat_boxplot(geom ='errorbar') +
    # beschrifte Y-Achse
    ylab("Dienste hintereinander") + 
    # Textgröße Y-Achsenskala
    theme(axis.title.y = element_text(size=18)) + 
    # individuelle Ticks Y-Achse
    scale_y_continuous(breaks = c(0, 2, 4, 6, 8, 10 , 15, 25)) +
    # beschrifte X-Achse
    xlab(NULL) +
    # X-Achsenbeschriftung löschen
    theme(axis.ticks.x = element_blank(), axis.text.x = element_blank())

So können auch mehere Gruppen abgebildet werden, zum Beispiel nach Geschlecht gruppiert:

ggplot(nw, aes(x=sex, y=nights, fill=sex)) +
    # Die Ausreisser stylen
    geom_boxplot(colour="darkblue", outlier.colour="blue", outlier.shape=8, outlier.size=3) +
    # zeichne Whisker-Linien
    stat_boxplot(geom ='errorbar') +
    # beschrifte Y-Achse
    ylab("Dienste hintereinander") + 
    # Textgröße Y-Achsenskala
    theme(axis.title.y = element_text(size=18)) + 
    # individuelle Ticks Y-Achse
    scale_y_continuous(breaks = c(0, 2, 4, 6, 8, 10 , 15, 25)) +
    # beschrifte X-Achse
    xlab(NULL) +
    # X-Achsenbeschriftung löschen
    theme(axis.ticks.x = element_blank(), axis.text.x = element_blank()) +
    # Zeichne den Mittelwert als roten Punkt
    stat_summary(fun=mean, colour="darkred", geom="point", shape=18, size=5, show.legend = F)

Hier ein etwas komplexeres Beispiel aus dem Nachtwachen-Datensatz. Zuerst selektieren wir die Variablen zu Pflegestufen, Demenz und freiheitsentziehenden Maßnahmen (FEM) und bringen sie ins long table Format. Dann führen wir eine neue gruppierende Variable “Gruppe” ein. Diese dient als Trenner zwischen den Boxplotgruppen.

# Lade Nachtwachendatensatz
load(url("https://www.produnis.de/R/nw.RData"))
nwbp <- nw %>% 
  # suche die richtigen Spalten heraus
  select(Stufe0, Stufe1, Stufe2, Stufe3, Demenz, Bettgitter, Bettgurt, Schlafmittel) %>% 
  # erzeuge daraus eine "long table"
  pivot_longer(cols=c(Stufe0, Stufe1, Stufe2, Stufe3, Demenz, Bettgitter, Bettgurt, Schlafmittel), names_to="Variable", values_to="Anzahl") %>% 
  # Füge manuell die "Trenner"-Variable hinzu
  bind_cols(., Gruppe=factor(rep(c("Pflegestufe", "Pflegestufe", "Pflegestufe", "Pflegestufe", "Demenz", "FEM", "FEM", "FEM"), 276)))

ggplot(nwbp, aes(Variable, Anzahl))+
  geom_boxplot(outlier.size = 3,outlier.shape=1) +
  stat_boxplot(geom ='errorbar') +
  ylab(NULL) + xlab(NULL)+
  theme(axis.text.x = element_text(size=12))+
  theme(axis.text.y = element_text(size=11))+
  scale_y_continuous(limits=c(0, 70))+
  scale_y_continuous(breaks = c(0,10,20,30,40,50,60,70,100))+
  facet_grid(. ~ Gruppe, scales = "free", space = "free") # "Gruppe" ist der Spaltenname
## Scale for 'y' is already present. Adding another scale for 'y', which will
## replace the existing scale.
## Warning: Removed 321 rows containing non-finite values (stat_boxplot).
## Removed 321 rows containing non-finite values (stat_boxplot).

30.6 Likert-Plots

Likkert-Plots können über das Zusatzpaket likert geplottet werden. Die Daten müssen in einem Datenframe vorliegen und an die Funktion likert() übergeben werden.

# Lade Nachtwachendatensatz
load(url("https://www.produnis.de/R/nw.RData"))

# Erzeuge Datenframe "Pause"
pause <- data.frame(nw$pause)

# übergebe Datenframe an "likert()"
pause <- likert::likert(pause)

# plotte
plot(pause)

Sollen mehrere Variablen gemeinsam ausgewertet werden, erzeugt man einfach ein Datenframe, wobei wie gewohnt jede Spalte eine Variable darstellt.

# Erzeuge neues Datenframe "n"
n <- data.frame(nw[, 29:34])

# übergebe an "likert()"
n <- likert::likert(n)

# plot
plot(n)

30.7 Kreisdiagramm

In ggplot() gibt es kein Geom, mit dem Kreisdiagramme erstellt werden können. Der Trick besteht darin, einfach ein geom_bar() auf einem circulären Koordinationsystem zu plotten. Dies erreichen wir, indem coord_polar() hinzugefügt wird.

Wir übergeben ggplot() ein Datenframe, mit einer Variable für die Gruppennamen, und einer Variable mit den Anteilswerten.

Dass die Anteile bereits berechnet sind teilen wir über den Parameter stat=“identity” mit.

# Erzeuge Datenframe
df <- data.frame(Gruppe=c("A", "B", "C"), Anteil=c(60, 25, 15))

# pfusche Pie-Chart zurecht
ggplot(df, aes(x="", y=Anteil, fill=Gruppe)) +
    geom_bar(stat="identity", width=1) + 
    coord_polar("y", start=0) +
    # entferne Achsen und Ticks
    theme_void()

Liegen die Daten in einer Variable vor, z.B. als factor, ändert sich der Aufruf wie folgt:

# Lade Nachtwachendaten
load(url("https://www.produnis.de/R/nw.RData"))

# plotte Variable "sex" als PieChart
ggplot(nw,aes(x="", fill=factor(sex)))+
  geom_bar(width = 1,color="white") +
  coord_polar("y") +
  labs(fill="Geschlecht")+
  # entferne Achsen und Ticks
  theme_void()


  1. Wickham, H (2010): A layered grammar of graphics, Journal of Computational and Graphical Statistics, 19(1):3-28, doi: 10.1198/jcgs.2009.07098, https://vita.had.co.nz/papers/layered-grammar.html↩︎

  2. Wilkinson, L (2005): The Grammar of Graphics, Springer, ISBN 978-0-387-28695-2↩︎