Letzte Änderung am 11. November 2024 um 09:59:39

“The greatest value of a picture is when it forces us to notice what we never expected to see.” — John Tukey

Worum geht es in der explorativen Datenanalyse? Die explorative Datenanalyse wurde von John Tukey (1915–2000) begründet. Er war ein Pionier im Bereich der frühen Informatik und Computerwissenschaften. Warum explorativ, was wollen wir den erforschen? Denn nichts anderes bedeutet ja explorativ (deu. erforschend, erkundend, untersuchend) – wir wollen etwas entdecken. Die Idee war ziemlich innovativ und neu als Tukey u. a. (1977) in seinem Buch die explorative Datenanalyse erstmal als Begriff vorstellte. Schon vorher beschrieb Tukey (1962) in seinem Artikel “The Future of Data Analysis” die zukünftige Datenanalyse, wie Tukey sie sich vorstellte, was auch sein folgendes Zitat belegt.

“I know of no person or group that is taking nearly adequate advantage of the graphical potentialities of the computer.” — John Tukey

Nun musst du wissen, dass in den 60’ziger Jahren der Computer in den Kinderschuhen steckte. Mal eben eine Berechnung durchführen, dass war dann schon so eine Sache der damaligen Zeit. Vieles bis fast alles wurde als Tabellen veröffentlicht und selten wurde eine Abbildung händisch dazu erstellt. An dieser Stelle sei einmal auf die Microsoft Excel Werbung aus dem Jahr 1990 verwiesen. Daher war die Idee, sich Daten zu visualisieren entsprechend neu und wirklich ein wissenschaftlicher Fortschritt. Erstaunlicherweise glauben wir heute manchmal dann Zahlen in einer Tabelle mehr als einer Abbildung. Wir schreiben dann den Zahlen eine größere Genauigkeit und Aussagekraft zu, als einer explorativen Abbildung. Dabei können wir in einer Abbildung viel mehr sehen, als in einer Tabelle.

“There is no data that can be displayed in a pie chart, that cannot be displayed BETTER in some other type of chart.” — John Tukey

Abbildung 17.1— “The reason to avoid pie charts” Quelle: wumo.com

Daher ist ein wichtiger Teil in der Analyse von Daten die Visualisierung und damit die Darstellung in einer Abbildung. Wir haben aber eine großem Auswahl an möglichen Abbildungen, deshalb auch das lange Kapitel hier. Siehe dazu auch die Arbeit von Riedel u. a. (2022) mit dem Titel Replacing bar graphs of continuous data with more informative graphics: are we making progress? um mehr über die Nutzung der Abbildungen im Laufe der Zeit zu erfahren. Nicht alles was schon immer genutzt wurde, macht auch Sinn weiter genutzt zu werden. Wir haben mit R und {ggplot} eine Möglichkeit Abbildungen von Daten sehr viel besser zu erstellen als noch vor ein paar Jahrzehnten möglich war.

Grundsätzlich glauben wir dabei keiner Auswertung eines mathematischen Algorithmus, wenn wir nicht die Bestätigung in einer Abbildung sehen. Wenn wir also einen signifikanten Unterschied aus einem Algorithmus wiedergegeben bekommen, dann müssen wir auch den Unterschied in einer Abbildung sehen können. Eine statistische Analyse und deren Visualisierung gehen Hand in Hand. Daher ist die Visualisierung die Grundlage für ein fundiertes, wissenschaftliches Arbeiten. In diesem Kapitel stelle ich dir verschiedene Abbildungen vor, die uns helfen werden zu Verstehen ob es einen Zusammenhang zwischen \(y\) und \(x\) gibt. Wir haben ein \(y\) vorliegen, was wir auf die y-Achse eines Graphen legen und daneben dann mehrere Variablen bzw. Spalten die wir \(x\) nennen. Eine der Variablen legen wir auf die x-Achse des Graphen. Nach den anderen \(x\) färben wir die Abbildung ein. Wir nennen dabei eine Abbildung auch häufig Plot. Das ist der englische Begriff und hat nichts in unserem Kontext mit einer Fläche oder einer Handlung in einem Film zu zu tun. Im den beiden folgenden Abbildungen siehst du einmal die beiden häufigsten wissenschaftlichen Fragestellungen in der explorativen Datenanalyse.

Abbildung 17.2— Visualisierung der beiden häufigsten wissenschaftlichen Fragestellungen in der explorativen Datenanalyse. (A) Die Frage nach dem Unterschied. Unterschieden sich die Gruppen auf der x-Achse gegeben den Werten auf der y-Achse? Um den Unterschied darzustellen können Barplots oder Boxplots verwendet werden. (B) Die Frage nach dem Zusammenhang. Wie verändern sich die Werte der y-Achse, wenn sich die Werte auf der x-Achse ändern? Um den Zusammenhang zu zeigen kann ein Scatterplot verwendet werden. Die Gerade durch die Punkte kann durch eine (nicht) lineare Regression bestimmt werden. [Zum Vergrößern anklicken]

In diesem Kapitel wollen wir durch die bedeutendsten Abbildungen in der explorativen Datenanalyse durchgehen. Ich habe die Abbildungen etwas nach Kontext sortiert und geschaut, dass die Abbildungen zum Gruppennterschied oder Zusammenhang zweier Variablen als erstes besprochen werden.

Beginnen wir also mit dem Teil, der in der Statistik der mir immer am meisten Spaß macht. Oder aber um es in den Worten von John Tukey, den Begründer der explorativen Datenanalyse zu sagen:

“This is my favorite part about analytics: Taking boring flat data and bringing it to life through visualization.” — John Tukey

Wir immer geht natürlich hier auch noch viel mehr am Abbildungen. Du musst nicht alles in ggplot machen und darüber hinaus gibt es dann auch noch einiges an anderen R Paketen. In dem folgenden Kasten habe ich dir mal ein paar Quellen rausgesucht, wo du einmal gucken kannst, wenn du noch mehr Inspiration benötigst. Ich nutze die Seiten auch als Inspiration, den meisten weiß man gar nicht was alles geht, bis man die Visualisierung gesehen hat.

Weitere Möglichkeiten der Visualisierung

Im Folgenden einmal eine Auswahl an weiteren Möglichkeiten sich Abbildungen in R zu visualisieren. Teilweise ist es dann auch eine Sammlung an Informationen für mich, damit ich eine Idee habe, was noch so alles geht. Manchmal braucht man ja auch noch eine zusätzlich Inspiration.

  • Data visualization with ggplot2 :: Cheat Sheet – als Überblick über alles was eigentlich in ggplot möglich ist. Du kriegst dann nochmal ein Überblick über die geome, die es gibt und wie die einzelnen Funktionen und Möglichkeiten miteinander interagieren. Du kannst dir das Cheat Sheet auch auf Deutsch runterladen.
  • R Charts by R Coder – ein großer Überblick über alle möglichen Abbildungen sortiert nach verschiedenen Kategorien. Hier würde ich auf jeden Fall mal drüber schauen, dann kriegst du nochmal eine Idee was neben ggplot noch so alles geht. Ich finde die Seite immer sehr inspirierend.
  • ALL YOUR FIGURE ARE BELONG TO US ist eine weitere tolle Übersicht an möglichen Abbildungen in {ggplot} Universum. Die Übersicht zeigt nochmal verschiedene R Pakte mit den entsprechenden möglichen Abbildungen.
  • sjPlot - Data Visualization for Statistics in Social Science – auch hier geitbes dann die Möglichkeit nochmal etwas anders auf die Visualisierung zu schauen. Denn hier haben wir dann die Möglichkeiten der Sozialwissenschaften mehr abgedeckt. Das mag insbesondere im Bereich von Fragebögen und Marketing von Interesse sein.
  • Als spannende zusätzlichen Ressourcen seinen das R Paket {cowplot} sowie das R Paket {ggpubr} genannt. Besonders das Paket cowplot liefert noch schöne Formatierungsmöglichkeiten wobei dann ggpubr statistische Test mit der Visualisierung verbindet.
  • {plotly} R Open Source Graphing Library sowie im besonderen dann die Seite Getting Started with {plotly} in {ggplot2} liefert dann interaktive Abbildungen, wo du dann direkt hereinzoomen kannst. Das ist bei sehr großen Abbildungen immer sehr praktisch. Auch kannst du die Werte einer einzelnen Beobachtung gleich im Plot ablesen.
  • How to create BBC style graphics – einmal als ein Beispiel für die Anwendung von ggplot in der Wirtschaft und Data Science. Auch hier sieht man schön die Ideen die möglich sind.
  • Visualizing Distributions with Raincloud Plots (and How to Create Them with ggplot2) – manchmal ist dann der Barplot oder Dynamiteplot dann doch irgendwie nicht das richtige Werkzeug um die Daten zu zeigen. Daher hier nochmal der Blogpost mit einer Diskussion was neben dem Säulendiagramm noch so alles gehen könnte.
  • ggplot Wizardry Hands-On zeigt nochmal sher schön was alles in {ggplot} geht, wenn die entsprechenden zusätzlichen Pakete geladen werden. Ich schaue auch hier immer mal wieder rein, wenn ich eine Abbildung besonders schön machen möchte. Hier liegt dann der Fokus auf dem R Paket{gghalves} sowie dem R Paket {ggdist}.
  • Die Publikation Error bars in experimental biology von Cumming u. a. (2007) liefert nochmal einen Überblick über Fehlerbalken und welche Fehlerbalken man den nun nutzen sollte.

17.1 Genutzte R Pakete

Wir wollen folgende R Pakete in diesem Kapitel nutzen.

R Code [zeigen / verbergen]
pacman::p_load(tidyverse, magrittr, readxl, ggmosaic, 
               janitor, see, patchwork, latex2exp, ggbeeswarm,
               ggdist, gghalves, ggbreak, duke, wesanderson, 
               conflicted)
conflicts_prefer(dplyr::summarise)
conflicts_prefer(dplyr::filter)
conflicts_prefer(latex2exp::TeX)

An der Seite des Kapitels findest du den Link Quellcode anzeigen, über den du Zugang zum gesamten R-Code dieses Kapitels erhältst.

17.2 Daten

Wir importieren also den Datensatz flea_cat_dog.xlsx und wollen einzelne Variablen visualisieren. Wir kennen den Datensatz schon aus den vorherigen Beispielen. Dennoch nochmal hier der Datensatz in Tabelle 17.1 einmal dargestellt.

R Code [zeigen / verbergen]
flea_dog_cat_tbl <- read_excel("data/flea_dog_cat.xlsx") |> 
  mutate(animal = as_factor(animal))

Im Folgenden ist es wichtig, dass du dir die Spaltennamen merkst. Wir können nur die exakten, wortwörtlichen Spaltennamen verwenden. Sonst erhalten wir einen Fehler. Deshalb haben wir auch keine Leerzeichen in den Spaltennamen.

Tabelle 17.1— Tabelle von sieben Hunde- und Katzenflöhen mit der Sprunglänge [cm], Anzahl an Haaren am rechten Flohbein, Gewicht der Flöhe, Boniturnote sowie der Infektionsstatus für Flohschnupfen.
animal jump_length flea_count weight grade infected
dog 5.7 18 2.1 8 0
dog 8.9 22 2.3 8 1
dog 11.8 17 2.8 6 1
dog 5.6 12 2.4 8 0
dog 9.1 23 1.2 7 1
dog 8.2 18 4.1 7 0
dog 7.6 21 3.2 9 0
cat 3.2 12 1.1 7 1
cat 2.2 13 2.1 5 0
cat 5.4 11 2.4 7 0
cat 4.1 12 2.1 6 0
cat 4.3 16 1.5 6 1
cat 7.9 9 3.7 6 0
cat 6.1 7 2.9 5 0

Wir brauchen dann ab und an auch nochmal mehr Datenpunkte, daher nehmen wir auch einmal den Gummibärchendatensatz und schauen uns dort die Variablen gender, height und age einmal genauer an. Wie immer nutzen wir die Funktion select() um die Spalten zu selektieren. Abschließend verwandeln wir das Geschlecht gender noch in einen Faktor und entfernen alle fehlenden Werte mit na.omit().

R Code [zeigen / verbergen]
gummi_tbl <- read_excel("data/gummibears.xlsx")  |>
  select(gender, height, age) |> 
  mutate(gender = factor(gender, labels = c("männlich", "weiblich"))) |> 
  na.omit()

Und hier dann einmal der ausgewählte Datensatz der Gummibärchendaten. Ich zeige hier nur die ersten sieben Zeilen als Ausschnitt, die eigentlichen Daten sind mit \(n = 728\) Beobachtungen viel größer.

Tabelle 17.2— Tabelle mit dem Auzug der Gummibärchendaten von den ersten sieben Beobachtungen für das Geschlecht, die Körpergröße und dem Alter.
gender height age
männlich 193 35
weiblich 159 21
weiblich 159 21
weiblich 180 36
männlich 180 22
männlich 180 22
weiblich 163 21

17.3 Grundlagen in {ggplot}

Wir nutzen in R das R Paket {ggplot2} um unsere Daten zu visualisieren. Die zentrale Idee von {ggplot2} ist, dass wir uns eine Abbildung wie ein Sandwich bauen. Zuerst legen wir eine Scheibe Brot hin und legen uns dann Scheibe für Scheibe weitere Schichten übereinander. Oder die Idee eines Bildes, wo wir erst die Leinwand definieren und dann Farbschicht über Farbschicht auftragen. Im Gegensatz zu dem Pipe-Operator |> nutzt {ggplot2} den Operator + um die verschiedenen Funktionen (geom_) miteinander zu verbinden. Das Konzept von {ggplot2}ist schlecht zu beschreiben deshalb habe ich auch noch zwei Videos hierfür gemacht. Um den Prozess von {ggplot2} zu visualisieren - aber wie immer, nutze was du brauchst.

Erstellen wir also erstmal unseren erste Visualisierung in dem R Paket {ggplot2}. Im Folgenden spreche ich dann aber immer von {ggplot}. Die Funktion ggplot() ist die zentrale Funktion, die die Leinwand erschafft auf der wir dann verschiedene Schichten aufbringen werden. Diese Schichten heißen geom. Es gibt nicht nur ein geom sondern mehrere. Zum Beispiel das geom_boxplot() für die Erstellung von Boxplots, das geom_histogram() für die Erstellung von Histogrammen. Die Auswahl ist riesig. Die einzelnen Schichten werden dann über den Operator + miteinander verbunden. Soviel erstmal zur Trockenübung. Schauen wir uns das ganze einmal an einem Beispiel an. Dafür müssen wir dann erstmal einen Datensatz laden, damit wir auch etwas zum abbilden haben.

Wie immer empfehle ich dir dann auch das entsprechende Video auf YouTube anzuschauen. In Textform ist eine echte Herausforderung zu erklären wie man Plots baut. Der folgende R Code erstellt die Leinwand in der Abbildung 17.3 für die folgende, zusätzliches Schichten (geom). Wir haben also immer erst eine leere Leinwand auf der wir dann zusätzlich geome plotten. Wir bauen uns sozusagen ein Sandwich.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, 
       aes(x = animal , y = jump_length)) +
  theme_minimal()

Wir schauen uns einmal den Code im Detail an.

  • ggplot ruft die Funktion auf. Die Funktion ist dafür da den Plot zu zeichnen.
  • data = flea_dog_cat_tbl benennt den Datensatz aus dem der Plot gebaut werden soll.
  • aes()ist die Abkürzung für aesthetics und beschreibt, was auf die \(x\)-Achse soll, was auf die \(y\)-Achse soll sowie ob es noch andere Faktoren in den Daten gibt. Wir können nämlich noch nach anderen Spalten die Abbildung einfärben oder anderweitig ändern.
    • x braucht den Spaltennamen für die Variable auf der \(x\)-Achse.
    • y braucht den Spaltennamen für die Variable auf der \(y\)-Achse.
  • + theme_minimal() setzt das Canvas oder die Leinwand auf schwarz/weiß. Sonst wäre die Leinwand flächig grau.

Mit Faktoren meine ich hier andere Gruppenvariablen. Variablen sind ein anderes Wort für Spalten. Also Variablen die wir mit as_factor erschaffen haben und die uns noch mehr über unsere Daten dann verraten können. Hier ist es dann etwas abstrakt, aber es wird dann später in der Anwendung klarer.

Abbildung 17.3— Leere ggplot() Leinwand mit den Spalten animal und jump_length aus dem Datensatz flea_dog_cat_tbl. [Zum Vergrößern anklicken]

Am Ende sehen wir, dass wir nichts sehen. In der Abbildung 17.3 ist nichts dargestellt. Der Grund ist, dass wir noch kein geom hinzugefügt haben. Das geom beschreibt nun wie die Zahlen in der Datentabelle flea_dog_cat_tbl visualisiert werden sollen. Wir habe eine sehr große Auswahl an geomen, deshalb gibt es gleich einmal eine Auswahl an Abbildungen.

Auf der Seite ggplot2 extensions gibt es noch eine Vielzahl an weiteren tollen Möglichkeiten zu entdecken! Einfach durch die Galerie mit über 120 Erweiterungen streifen und sich inspirieren lassen!

17.4 Die häufigsten Abbildungen

Im Folgenden gehen wir dann einmal die wichtigsten Abbildungen einmal durch. Viele der Abbildungen kennst du vielleicht schon und dann musst du hier nur noch schauen, wie die Abbildungen in ggplot zu realisieren sind. Ansonsten gilt wie immer, es ist nur ein kleiner Ausschnitt, du findest auf der Hilfeseite von ggplot eine sehr viel größere Übersicht.

17.4.1 Barplot

Der Barplot oder das Balkendiagramm auch Säulendiagramm ist eigentlich veraltet. Wir haben mit dem Boxplot eine viel bessere Methode um eine Verteilung und gleichzeitig auch die Gruppenunterschiede zu visualisieren. Warum nutzen wir jetzt so viel den Barplot? Das hat damit zu tun, dass früher - oder besser bis vor kurzem - in Excel kein Boxplot möglich war. Daher nutzte jeder der mit Excel seine Daten auswertet den Barplot.

Muss die 0 mit auf die y-Achse?

Der einzige Grund, warum wir einen Barplot nutzen wollen würden, wäre wenn wir unbedingt die 0 mit auf der y-Achse haben wollten.

Weil aber eben noch viel der Barplot genutzt wird, stelle ich natürlich den Barplot auch hier vor. Der Barplot beinhaltet aber weniger Informationen als der Boxplot. Wir haben nur die Standardabweichung als Maßzahl für die Streuung. Beim Boxplot haben wir den Interquartilesabstand (abk . IQR), der uns mehr über die Streuung aussagt. Aber gut, häufig musst du den Barplot in deiner Abschlussarbeit machen. Zuerst betrachten wir die theoretische Darstellung. Dann zeige ich dir die Werte für unsere Sprungweiten der Hundeflöhe. Im letzten Tab findest du dann die Implementierung in {ggplot}.

In den folgenden beiden Abbildungen siehst du einmal zwei Säulendigramme. Die Säulen gehen exakt so hoch wie der Mittelwert \(\bar{y}\) der entsprechenden Gruppe \(A\). Dann berechnen wir noch für die Fehlerbalken die Standardabweichung (abk. SD). Je nachdem wie wir die Säulendigramme darstellen wollen, zeigen wir nur \(\bar{y} + SD\) oder aber \(\bar{y} \pm SD\). Hier gibt es keine richtige Regel, das hängt sehr vom Geschmack ab und unterscheidet sich auch von Publikation zu Publikation. Je nachdem was wir als Outcome gemessen haben, können wir auch Überlegen anstatt der Standardabweichung den Standardfehler (abk. SE) zu nutzen.

Abbildung 17.4— Darstellung der Mittelwerte \(\bar{y}\) und der Standardabweichung (abk. SD) in einem Säulendigramm. Es gibt zwei Arten der Darstellung, die sich dann auf die Fehlerbalken bezieht. (A) Nur mit \(\bar{y} + SD\) (B) Mit \(\bar{y} \pm SD\). [Zum Vergrößern anklicken]

In den beiden folgenden Abbildungen siehst du einmal die Sprungweite der Hundeflöhe als ein Säulendigramm dargestellt. Wir haben den Mittelwert mit 8.13 sowie die Standardabweichung mit 2.14 berechnet. Daraus ergeben sich dann die Grenzen der Fehlerbalken. Ich habe dann in der rechten Abbildung die Fehlerbalken dann noch mit dem Standardfehler von 0.81 dargestellt. Wie du siehst, werden dann die Fehlerbalken kleiner, der Mittelwert bleibt natürlich in beiden Fällen gleich. Mehr dazu dann in dem Kapitel zur deskriptiven Statistik und unter diesem Kasten.

Abbildung 17.5— Darstellung der Mittelwerte \(\bar{y}\) und der Standardabweichung (abk. SD) sowie dem Standardfehler (abk. SE) in einem Säulendigramm. (A) Mit \(\bar{y} \pm SD\) (B) Mit \(\bar{y} \pm SE\). [Zum Vergrößern anklicken]

Wenn wir ein Säulendigramm in ggplot() erstellen wollen, dann müssen wir jetzt den Mittelwert und die Streuung für die Gruppen in unseren Daten der Hunde- und Katzenflöhe berechnen. Du kannst als Streuung die Standardabweichung sd oder den Standardfehler se nehmen. Wir nehmen wir einmal die Standardabweichung für die Abbiildung.

R Code [zeigen / verbergen]
stat_tbl <- flea_dog_cat_tbl |> 
  group_by(animal) |> 
  summarise(mean = mean(jump_length),
            sd = sd(jump_length),
            se = sd/sqrt(n()))

Wir nutzen nun das Objekt stat_tbl um den Barplot mit der Funktion ggplot() zu erstellen. Dabei müssen wir zum einen schauen, dass die Balken nicht übereinander angeordnet sind. Nebeneinander angeordnete Balken kriegen wir mit der Option stat = "identity" in dem geom_bar(). Dann müssen wir noch die Fehlerbalken ergänzen mit dem geom_errorbar(). Hier kann nochmal mit der Option width = an der Länge der Fehlerenden gedreht werden.

R Code [zeigen / verbergen]
ggplot(stat_tbl, aes(x = animal, y = mean, fill = animal)) + 
  theme_minimal() +
  geom_bar(stat = "identity") +
  geom_errorbar(aes(ymin = mean-sd, ymax = mean+sd),
                width = 0.2)
Abbildung 17.6— Säulendigramm der Sprungweiten der Hunde- und Katzenflöhe. [Zum Vergrößern anklicken]

Dank der Funktion coord_flip() können wir auch schnell aus dem Säulendiagramm ein Balkendiagramm bauen. Du musst dann immer schauen, was besser in deine Visualisierung passt.

R Code [zeigen / verbergen]
ggplot(stat_tbl, aes(x = animal, y = mean, fill = animal)) + 
  theme_minimal() +
  geom_bar(stat = "identity") +
  geom_errorbar(aes(ymin = mean-sd, ymax = mean+sd),
                width = 0.2) +
  coord_flip()
Abbildung 17.7— Balkendigramm der Sprungweiten der Hunde- und Katzenflöhe. Die Funktion coord_flip() macht aus einem Säulendiagramm ein Balkendiagramm. [Zum Vergrößern anklicken]

Dann habe ich mich doch noch hingesetzt und einmal für dich die Videos gemacht, wie du dann einen Barplot oder eben ein Säulendigramm in Excel erstellst. Das ganze macht dann nur als Video Sinn, denn sonst kannst du ja nicht nachvollziehen, was ich geklickt habe.

Hier also erstmal die einfachere Variante mit dem 1-faktoriellen Barplot. Beginnen wollen wir wie immer mit der Berechnung der Mittelwerte und der Standardabweichung. Bitte nutze für die Standardabweichung die Funktion STABW.S() in Excel.

Und im Anschluss nochmal das Video für den 2-faktoriellen Barplot. Du hast jetzt eben nicht nur eine Behandlungsgruppe vorliegen sondern zwei Behandlungsgruppen. Dann musst du etwas mehr Arbeit reinstecken um alle Mittelwerte und Standardabweichungen zu berechnen. Bitte nutze auch hier für die Standardabweichung die Funktion STABW.S() in Excel.

Bei der Darstellung des Barplots haben wir die Wahl wie wir den Fehlerbalken darstellen. Dazu haben wir dann verschiedene Maßzahlen zu Auswahl. Theoretisch haben wir die freie Wahl zwischen der Standardabweichung und dem Standardfehler, aber es gibt Ausnahmen.

Standardabweichung (abk. SD) oder Standardfehler (abk. SE) als Fehlerbalken?

Wenn du etwas misst, was natürliche numerische Grenzen hat, wie zum Beispiel relative Anteile \([0,1]\) oder aber etwas zählst \([0, \infty]\), dann empfiehlt sich der Standardfehler, da dieser nicht über die numerischen Grenzen geht. Die Standardabweichung kann hier negative Werte oder aber prozentuale Werte größer 1 oder kleiner 0 liefern. Das wollen wir nicht.

Schau dir auch hier mal in den Kästen zum Zerforschen rein, da findest du dann noch mehr Inspiration aus anderen Abbildungen, die ich nachgebaut habe. Ich bin einmal über den Campus gelaufen und habe geschaut, welche Abbildungen auf den Postern verwendet werden und habe diese nachgebaut.

In diesem Zerforschenbeispiel wollen wir uns einen einfaktoriellen Barplot oder Säulendiagramm anschauen. Daher fangen wir mit der folgenden Abbildung einmal an. Wir haben hier ein Säulendiagramm mit Compact letter display vorliegen. Daher brauchen wir eigentlich gar nicht so viele Zahlen. Für jede der vier Behandlungen jeweils einmal einen Mittelwert für die Höhe der Säule sowie einmal die Standardabweichung. Die Standardabweichung addieren und subtrahieren wir dann jeweils von dem Mittelwert und schon haben wir die Fehlerbalken.

Abbildung 17.8— Ursprüngliche Abbildung, die nachgebaut werden soll. Ein simples Säulendiagramm mit sehr für Farbblinde ungünstigen Farben. Es sind die Mittelwerte sowie die Standardabweichung durch die Fehlerbalken dargestellt.

Als erstes brauchen wir die Daten. Die Daten habe ich mir in dem Datensatz zerforschen_barplot_simple.xlsx selber ausgedacht. Ich habe einfach die obige Abbildung genommen und den Mittelwert abgeschätzt. Dann habe ich die vier Werte alle um den Mittelwert streuen lassen. Dabei habe ich darauf geachtet, dass die Streuung dann in der letzten Behandlung am größten ist. Da wir beim Einlesen keine Umlaute oder sonstige Leerzeichen wollen, habe ich alles sehr simple aufgeschrieben und dann in R in der Funktion factor() richtig über die Option levels sortiert und über die Option labels sauber beschrieben. Dann passt auch die Sortierung der \(x\)-Achse.

R Code [zeigen / verbergen]
barplot_tbl <- read_excel("data/zerforschen_barplot_simple.xlsx") |> 
  mutate(trt = factor(trt, 
                      levels = c("water", "rqflex", 
                                 "nitra", "laqua"),
                      labels = c("Wasserdestilation",
                                 "RQflex Nitra",
                                 "Nitrachek",
                                 "Laqua Nitrat")))
barplot_tbl 
# A tibble: 16 × 2
   trt               nitrat
   <fct>              <dbl>
 1 Wasserdestilation    135
 2 Wasserdestilation    130
 3 Wasserdestilation    145
 4 Wasserdestilation    135
 5 RQflex Nitra         120
 6 RQflex Nitra         130
 7 RQflex Nitra         135
 8 RQflex Nitra         135
 9 Nitrachek            100
10 Nitrachek            120
11 Nitrachek            130
12 Nitrachek            130
13 Laqua Nitrat         230
14 Laqua Nitrat         210
15 Laqua Nitrat         205
16 Laqua Nitrat         220

Jetzt brauchen wir noch die Mittelwerte und die Standardabweichung für jede der vier Behandlungen. Den Code kennst du schon von oben wo wir die Barplots für die Sprungweiten der Hunde- und Katzenflöhe gebaut haben. Hier habe ich dann den Code entsprechen der Daten barplot_tbl angepasst. Wir haben ja als Gruppierungsvariabel trt vorliegen und wollen die Mittelwerte und die Standardabweichung für die Variable nitrat berechnen.

R Code [zeigen / verbergen]
stat_tbl <- barplot_tbl |> 
  group_by(trt) |> 
  summarise(mean = mean(nitrat),
            sd = sd(nitrat))
stat_tbl
# A tibble: 4 × 3
  trt                mean    sd
  <fct>             <dbl> <dbl>
1 Wasserdestilation  136.  6.29
2 RQflex Nitra       130   7.07
3 Nitrachek          120  14.1 
4 Laqua Nitrat       216. 11.1 

Und dann haben wir auch schon die Abbildung 17.9 erstellt. Ja vielleicht passen die Standardabweichungen nicht so richtig, da könnte man nochmal an den Daten spielen und die Werte solange ändern, bis es besser passt. Du hast aber jetzt eine Idee, wie der Aufbau funktioniert.

R Code [zeigen / verbergen]
ggplot(data = stat_tbl, aes(x = trt, y = mean,
                            fill = trt)) +
  theme_minimal() +
  geom_bar(stat = "identity") +
  geom_errorbar(aes(ymin = mean-sd, ymax = mean+sd),
                width = 0.2) +
  labs(x = "", 
       y = "Nitrat-Konzentration \n im Tannensaft [mg/L]") +
  ylim(0, 250) +
  theme(legend.position = "none") + 
  scale_fill_okabeito() +
  annotate("text", 
           x = c(1.05, 2.05, 3.05, 4.05), 
           y = stat_tbl$mean + stat_tbl$sd + 8, 
           label = c("b", "b", "a", "c"))
1
Hier werden die Säulen des Säulendiagramms erstellt.
2
Hier werden die Fehlerbalken erstellt. Die Option width steuert wie breit die Fehlerbalken sind.
3
Hier wird eine Farbpalette für farbblinde Personen geladen.
Abbildung 17.9— Die Abbildung des Säulendiagramms in ggplot nachgebaut.

Am Ende kannst du dann folgenden Code noch hinter deinen ggplot Code ausführen um dann deine Abbildung als *.png-Datei zu speichern. Dann hast du die Abbildung super nachgebaut und sie sieht auch wirklich besser aus.

R Code [zeigen / verbergen]
ggsave("my_ggplot_barplot.png", width = 5, height = 3)

In diesem Zerforschenbeispiel wollen wir uns einen zweifaktoriellen Barplot oder Säulendiagramm anschauen. Wir haben hier ein Säulendiagramm mit compact letter display vorliegen. Daher brauchen wir eigentlich gar nicht so viele Zahlen. Für jede der vier Zeitpunkte und der Kontrolle jeweils einmal einen Mittelwert für die Höhe der Säule sowie einmal die Standardabweichung. Die Standardabweichung addieren und subtrahieren wir dann jeweils von dem Mittelwert und schon haben wir die Fehlerbalken.

Abbildung 17.10— Ursprüngliche Abbildung, die nachgebaut werden soll. Ein Barplot mit zwei Faktoren Zeit und die Iodine Form.

Als erstes brauchen wir die Daten. Die Daten habe ich mir in dem Datensatz zerforschen_barplot_2fac_target.xlsx selber ausgedacht. Ich habe einfach die obige Abbildung genommen und den Mittelwert abgeschätzt. Dann habe ich die drei Werte alle um den Mittelwert streuen lassen. Da wir beim Einlesen keine Umlaute oder sonstige Leerzeichen wollen, habe ich alles sehr simple aufgeschrieben und dann in R in der Funktion factor() richtig über die Option levels sortiert und über die Option labels sauber beschrieben. Dann passt auch die Sortierung der \(x\)-Achse.

R Code [zeigen / verbergen]
barplot_tbl <- read_excel("data/zerforschen_barplot_2fac_target.xlsx") |> 
  mutate(time = factor(time, 
                       levels = c("ctrl", "7", "11", "15", "19"),
                       labels = c("Contr.", "07:00", "11:00", "15:00", "19:00")),
         type = as_factor(type))
barplot_tbl 
# A tibble: 27 × 3
   time  type  iodine
   <fct> <fct>  <dbl>
 1 07:00 KIO3      50
 2 07:00 KIO3      55
 3 07:00 KIO3      60
 4 07:00 KI        97
 5 07:00 KI        90
 6 07:00 KI        83
 7 11:00 KIO3      73
 8 11:00 KIO3      75
 9 11:00 KIO3      78
10 11:00 KI       130
# ℹ 17 more rows

Jetzt brauchen wir noch die Mittelwerte und die Standardabweichung für jede der vier Behandlungen. Hier nur kurz, den Code kennst du schon aus anderen Zerforschenbeispielen zu den Barplots.

R Code [zeigen / verbergen]
stat_tbl <- barplot_tbl |> 
  group_by(time, type) |> 
  summarise(mean = mean(iodine),
            sd = sd(iodine))
stat_tbl
# A tibble: 9 × 4
# Groups:   time [5]
  time   type   mean    sd
  <fct>  <fct> <dbl> <dbl>
1 Contr. KIO3    5    3   
2 07:00  KIO3   55    5   
3 07:00  KI     90    7   
4 11:00  KIO3   75.3  2.52
5 11:00  KI    150   20   
6 15:00  KIO3   80    7   
7 15:00  KI    130   20   
8 19:00  KIO3   90.3 19.5 
9 19:00  KI     95    6   

Und dann geht es auch schon los. Wir müssen am Anfang einmal scale_x_discrete() setzen, damit wir gleich den Zielbereich ganz hinten zeichnen können. Sonst ist der blaue Bereich im Vordergrund. Dann färben wir auch mal die Balken anders ein. Muss ja auch mal sein. Auch nutzen wir die Funktion geom_text() um das compact letter display gut zu setzten. Die \(y\)-Position berechnet sich aus dem Mittelwert plus Standardabweichung innerhalb des geom_text(). Leider haben wir nur einen Balken bei der Kontrolle, deshalb hier nachträglich der Buchstabe mit annotate(). Ich habe mich dann noch entschieden neben dem Barplot noch den Boxplot als Alternative zu erstellen.

Einmal der Barplot wie beschrieben. Am besten löscht du immer mal wieder eine Zeile Code damit du nachvollziehen kannst, was die Zeile Code in der Abbildung macht. Vergleiche auch einmal diese Abbildung der Barplots mit der Abbildung der Boxplots und überlege, welche der beiden Abbildungen dir mehr Informationen liefert.

R Code [zeigen / verbergen]
ggplot(data = stat_tbl, aes(x = time, y = mean,
                               fill = type)) +
  theme_minimal() +
  scale_x_discrete() +
  annotate("rect", xmin = 0.25, xmax = 5.75, ymin = 50, ymax = 100, 
           alpha = 0.2, fill = "darkblue") +                        
  annotate("text", x = 0.5, y = 75, hjust = "left", label = "Target area") + 
  geom_bar(stat = "identity", 
           position = position_dodge(width = 0.9, preserve = "single")) +
  geom_errorbar(aes(ymin = mean-sd, ymax = mean+sd),
                width = 0.2,  
                position = position_dodge(width = 0.9, preserve = "single")) +
  scale_fill_manual(name = "Type", values = c("darkgreen", "darkblue")) + 
  theme(legend.position = c(0.1, 0.8),
        legend.title = element_blank(), 
        legend.spacing.y = unit(0, "mm"), 
        panel.border = element_rect(colour = "black", fill=NA),
        axis.text = element_text(colour = 1, size = 12),
        legend.background = element_blank(),
        legend.box.background = element_rect(colour = "black")) +
  labs(x = "Time of application [time of day]",
       y =  expression(Iodine~content~"["*mu*g~I~100*g^'-1'~f*.*m*.*"]")) +
  scale_y_continuous(breaks = c(0, 50, 100, 150, 200),
                     limits = c(0, 200)) +
  geom_text(aes(label = c("", "b", "bc", "bc", "d", "bc", "d", "bc", "c"), 
                y = mean + sd + 2),  
            position = position_dodge(width = 0.9), vjust = -0.25) + 
  annotate("text", x = 0.77, y = 15, label = "a") 
Abbildung 17.11— Die Abbildung des Säulendiagramms in ggplot nachgebaut. Wir nutzen das geom_text() um noch besser unser compact letter display zu setzen.

Für die Boxplost brauchen wir dann noch ein Objekt mehr. Um das compacte letter dislay an die richtige Position zu setzen brauchen wir noch eine \(y\)-Position. Ich nehme hier dann das 90% Quantile. Das 90% Quantile sollte dann auf jeden Fall über die Schnurrhaare raus reichen. Wir nutzen dann den Datensatz letter_pos_tbl in dem geom_text() um die Buchstaben richtig zu setzen.

R Code [zeigen / verbergen]
letter_pos_tbl <- barplot_tbl |> 
  group_by(time, type) |> 
  summarise(quant_90 = quantile(iodine, probs = c(0.90)))
letter_pos_tbl
# A tibble: 9 × 3
# Groups:   time [5]
  time   type  quant_90
  <fct>  <fct>    <dbl>
1 Contr. KIO3       7.4
2 07:00  KIO3      59  
3 07:00  KI        95.6
4 11:00  KIO3      77.4
5 11:00  KI       166  
6 15:00  KIO3      85.6
7 15:00  KI       146  
8 19:00  KIO3     106  
9 19:00  KI        99.8

Und dann müssen wir nur noch das geom_bar() und geom_errorbar() entfernen und durch das geom_boxplot() ersetzen. Dann haben wir auch schon unsere wunderbaren Boxplots. Das Problem sind natürlich die wenigen Beobachtungen, deshalb sehen die Boxplots teilweise etwas wild aus. Beachte auch das wir die Orginaldaten nutzen und nicht die zusammengefassten Daten.

R Code [zeigen / verbergen]
ggplot(data = barplot_tbl, aes(x = time, y = iodine,
                               fill = type)) +
  theme_minimal() +
  scale_x_discrete() +
  annotate("rect", xmin = 0.25, xmax = 5.75, ymin = 50, ymax = 100, 
           alpha = 0.2, fill = "darkblue") +                        
  annotate("text", x = 0.5, y = 75, hjust = "left", label = "Target area") + 
  geom_boxplot(position = position_dodge(width = 0.9, preserve = "single")) +
  scale_fill_manual(name = "Type", values = c("darkgreen", "darkblue")) + 
  theme(legend.position = c(0.1, 0.8),
        legend.title = element_blank(), 
        legend.spacing.y = unit(0, "mm"), 
        panel.border = element_rect(colour = "black", fill=NA),
        axis.text = element_text(colour = 1, size = 12),
        legend.background = element_blank(),
        legend.box.background = element_rect(colour = "black")) +
  labs(x = "Time of application [time of day]",
       y =  expression(Iodine~content~"["*mu*g~I~100*g^'-1'~f*.*m*.*"]")) +
  scale_y_continuous(breaks = c(0, 50, 100, 150, 200),
                     limits = c(0, 200)) +
  geom_text(data = letter_pos_tbl, 
            aes(label = c("", "b", "bc", "bc", "d", "bc", "d", "bc", "c"), 
                y = quant_90 + 5),  
            position = position_dodge(width = 0.9), vjust = -0.25) + 
  annotate("text", x = 0.77, y = 15, label = "a") 
Abbildung 17.12— Die Abbildung des Säulendiagramms in ggplot als Boxplot nachgebaut. Wir nutzen das geom_text() um noch besser unser compact letter display zu setzen, dafür müssen wir usn aber nochmal ein Positionsdatensatz bauen.

Am Ende kannst du dann folgenden Code noch hinter deinen ggplot Code ausführen um dann deine Abbildung als *.png-Datei zu speichern. Dann hast du die Abbildung super nachgebaut und sie sieht auch wirklich besser aus.

R Code [zeigen / verbergen]
ggsave("my_ggplot_barplot.png", width = 5, height = 3)

In diesem Zerforschenbeispiel wollen wir uns einen zweifaktoriellen Barplot oder Balkendiagramm anschauen. Wir haben hier ein echtes Balkendiagramm mit compact letter display vorliegen. Daher brauchen wir eigentlich gar nicht so viele Zahlen. Für jede der vier Zeitpunkte und der Kontrolle jeweils einmal einen Mittelwert für die Länge des Balkens sowie einmal die Standardabweichung. Die Standardabweichung addieren und subtrahieren wir dann jeweils von dem Mittelwert und schon haben wir die Fehlerbalken. Ich habe hier dann jeweils drei Werte für jede Faktorkombination. Die brauche ich dann auch, weil ich später nochmal vergleichend den Boxplot erstellen möchte. Der macht meiner Ansicht nach hier mehr Sinn.

Abbildung 17.13— Ursprüngliche Abbildung, die nachgebaut werden soll. Ein zweifaktorielles Balkendiagramm mit einem Zielbereich und compact letter display

Als erstes brauchen wir wieder die Daten. Die Daten habe ich mir in dem Datensatz zerforschen_barplot_2fac.xlsx selber ausgedacht. Ich habe einfach die obige Abbildung genommen und den Mittelwert abgeschätzt. Dann habe ich die drei Werte alle um den Mittelwert streuen lassen. Das war es dann auch schon.

R Code [zeigen / verbergen]
barplot_tbl <- read_excel("data/zerforschen_barplot_2fac_flipped.xlsx") |> 
  mutate(iod = fct_rev(iod),
         fruit = fct_rev(fruit))
barplot_tbl
# A tibble: 12 × 3
   iod   fruit iod_yield
   <fct> <fct>     <dbl>
 1 KI    65-75        85
 2 KI    65-75       100
 3 KI    65-75        75
 4 KI    75-80        52
 5 KI    75-80        70
 6 KI    75-80        30
 7 KIO3  65-75        55
 8 KIO3  65-75        48
 9 KIO3  65-75        60
10 KIO3  75-80        45
11 KIO3  75-80        53
12 KIO3  75-80        38

Jetzt brauchen wir noch die Mittelwerte und die Standardabweichung für jede der vier Behandlungen. Hier nur kurz, den Code kennst du schon aus anderen Zerforschenbeispielen zu den Barplots.

R Code [zeigen / verbergen]
stat_tbl <- barplot_tbl |> 
  group_by(iod, fruit) |> 
  summarise(mean = mean(iod_yield),
            sd = sd(iod_yield))
stat_tbl
# A tibble: 4 × 4
# Groups:   iod [2]
  iod   fruit  mean    sd
  <fct> <fct> <dbl> <dbl>
1 KIO3  75-80  45.3  7.51
2 KIO3  65-75  54.3  6.03
3 KI    75-80  50.7 20.0 
4 KI    65-75  86.7 12.6 

Im Folgenden stelle ich die zusammengefassten Daten stat_tbl als Balkendiagramm dar. Die ursprünglichen Daten barplot_tbl kann ich nutzen um die Boxplots zu erstellen. Hier ist wichtig nochmal zu erinnern, das wir Barplots auf dem Mittelwert und der Standardabweichung darstellen und die Boxplots auf den Originaldaten. Mit der unteren Grenze machen Boxplots mehr Sinn, wenn du wissen willst, ob du einen Zielbereich vollkommen erreicht hast.

Zuerst einmal der Barplot, wie wir ihn auch schon oben in der Abbildung sehen. Wir nutzen hier zum einen die Funktion coord_flip() um ganz zum Schluss die Abbildung zu drehen. Deshalb musst du aufpassen, denn vor dem Flippen ist ja alles auf der \(y\)-Achse die \(y\)-Achse und umgekehrt. Deshalb müssen wir dann auch teilweise die Ordnung der Level in den einzelnen Faktoren drehen, damit wir wieder die richtige Reihenfolge nach dem Flip haben. Wir müssen ganz am Anfang einmal scale_x_discrete() setzen, damit wir den Zielbereich als erstes einzeichnen können. Sonst ist der Zielbereich nicht ganz hinten in der Abbildung und überdeckt die Balken. Deshalb ist das Wort “Zielbereich” auch recht weit hinten im Code, damit es eben im Vordergrund ist. Sonst ist eigentlich vieles gleich. Wir nutzen hier einmal das R Paket latex2exp für die Erstellung der mathematischen Formeln.

R Code [zeigen / verbergen]
ggplot(data = stat_tbl, aes(x = iod, y = mean,
                            fill = fruit)) +
  theme_minimal() +
  scale_x_discrete() +
  annotate("rect", xmin = 0, xmax = 3, ymin = 50, ymax = 100, 
           alpha = 0.2, fill = cbbPalette[6]) + 
  geom_hline(yintercept = c(25, 50, 75, 100), linetype = "dashed") +
  geom_bar(stat = "identity", position = position_dodge(0.9)) + 
  geom_errorbar(aes(ymin = mean-sd, ymax = mean+sd), 
                width = 0.2, position = position_dodge(0.9)) +
  labs(x = "Iodform",
       y =TeX(r"(Iodgehalt $[\mu g\, l\, (100g\, FM)^{-1}]$)")) +
  scale_fill_okabeito(name = "Frucht-\ngröße [mm]", breaks=c('65-75', '75-80')) +
  theme(legend.position = c(0.85, 0.2),
        legend.box.background = element_rect(color = "black"),
        legend.box.margin = margin(t = 1, l = 1),
        legend.text.align = 0) +
  annotate("label", x = 1.5, y = 75, label = "Zielbereich", size = 5) +
  annotate("text", x = c(0.8, 1.25, 1.8, 2.25), y = c(55, 62, 72, 102),
           label = c("a", "a", "a", "b")) +
  coord_flip() 
Abbildung 17.14— Die Abbildung des Balkendiagramms in ggplot nachgebaut. Ein extra Zielbereich ist definiert sowie die Legende in die Abbildung integriert.

Für die Boxplots müssen wir gar nicht viel tun. Wir müssen nur noch das geom_bar() und geom_errorbar() entfernen und durch das geom_boxplot() ersetzen. Dann haben wir auch schon unsere wunderbaren Boxplots. Das Problem sind natürlich die wenigen Beobachtungen, deshalb sehen die Boxplots teilweise etwas wild aus. Beachte auch das wir die Orginaldaten nutzen und nicht die zusammengefassten Daten. Am Ende machen Boxplots mit einer unteren Grenze mehr Sinn, wenn wir uns Fragen, ob wir einen Zielbereich erreicht haben. Da sind dann doch Balkendiagramme etwas ungeeignet.

R Code [zeigen / verbergen]
ggplot(data = barplot_tbl, aes(x = iod, y = iod_yield,
                               fill = fruit)) +
  theme_minimal() +
  geom_hline(yintercept = c(25, 50, 75, 100), linetype = "dashed") +
  geom_boxplot() +
  labs(x = "Iodform",
       y =TeX(r"(Iodgehalt $[\mu g\, l\, (100g\, FM)^{-1}]$)")) +
  scale_fill_okabeito(name = "Frucht-\ngröße [mm]", breaks=c('65-75', '75-80')) +
  theme(legend.position = c(0.85, 0.2),
        legend.box.background = element_rect(color = "black"),
        legend.box.margin = margin(t = 1, l = 1),
        legend.text.align = 0) +
  annotate("rect", xmin = 0, xmax = 3, ymin = 50, ymax = 100, 
           alpha = 0.2, fill = cbbPalette[6]) + 
  annotate("label", x = 1.5, y = 75, label = "Zielbereich", size = 5) +
  annotate("text", x = c(0.8, 1.25, 1.8, 2.25), y = c(55, 62, 72, 102),
           label = c("a", "a", "a", "b")) +
  coord_flip() 
Abbildung 17.15— Die Abbildung des Balkendiagramms in ggplot nachgebaut. Durch den Boxplot erhalten wir auch untere Grenzen, was bei der Frage, ob wir in einem Zielbereich sind, viel sinnvoller ist, als ein Balkendiagramm. Eine höhere Fallzahl als \(n=3\) würde die Boxplots schöner machen.

17.4.2 Boxplot

Mit dem Boxplot können wir den Median und die Quartile visualisieren. Im Folgenden unterscheide ich dann einmal die theoretische Betrachtung des Boxplots sowie die händische Darstellung für unsere Flohdaten als Beispiel. Dann implementieren wir den Boxplot für die Sprungweiten der Hunde- und Katzenflöhe noch einmal abschließend in {ggplot}. Im Folgenden siehst du im ersten Tab einen Boxplot, der den Median und die Quartile eines beliebigen Datensatzes visualisiert.

Die Box wird aus dem Interquartilesabstand (abk . IQR) gebildet. Der Median wird als Strich in der Box gezeigt. Die Schnurrhaare (eng. Whiskers) sind das 1.5-fache des IQR, wenn nicht das Minimum der Beobachtungen oder das Maximum jeweils größer oder kleiner ist. Punkte die außerhalb der Whiskers liegen werden als einzelne Punkte dargestellt. Diese einzelnen Punkte werden auch als Ausreißer (eng. Outlier) bezeichnet. Ob es sich nun wirklich um Ausreißer handelt, muss biologisch geklärt werden.

Abbildung 17.16— Liegender Boxplot zu Visualisierung der statistischen Maßzahlen Median und Quartile. Die Box wird aus dem Interquartilesabstand (abk . IQR) gebildet, welches die Distanz zwischen den \(1^{st}\) und \(3^{rd}\) Quartile entspricht. Der Median wird als Strich in der Box gezeigt. Die Whiskers sind das 1.5-fache des IQR, wenn das Minimum der Werte nicht größer oder das Maximum der Werte nicht kleiner ist. Punkte die außerhalb der Whiskers liegen werden als einzelne Punkte dargestellt. [Zum Vergrößern anklicken]

Jetzt können wir einmal händisch den Boxplot für die Hundeflöhe und der Sprungweite zeichnen. Ich habe dafür dann einmal die Werte für das \(1^{st}\) und \(3^{rd}\) Quartile mit jeweils 5.7 und 9.1 bestimmt. Der Median der Sprungweite der Hundeflöhe liegt bei 8.2 und der Interquartilesabstand (abk . IQR) ist 3.4, wie du in der nicht maßstabsgetreuen Abbildung sehen kannst. Die Länge der Whisker berechne ich einmal mit \(1^{st} - 1.5 \times IQR = 0.6\) sowie \(3^{rd} + 1.5 \times IQR = 14.2\) und vergleiche die beiden Werte mit dem Minimum von 5.7 und 11.8 der Sprungweite. In beiden Fällen entscheide ich mich dann für die Minimum und Maximum-Werte.

Abbildung 17.17— Liegender Boxplot zu Visualisierung der Sprungweite der Hundeflöhe. Die Abbildung ist nicht maßstabsgetreu. Die Box wird aus dem Interquartilesabstand (abk . IQR) gebildet, welches die Distanz zwischen den \(1^{st}\) und \(3^{rd}\) Quartile entspricht. Es liegen keine Ausreißer in den Daten vor. [Zum Vergrößern anklicken]

Auch wenn die Abbildung nicht maßstabsgetreu ist, können wir abschätzen, dass unsere Sprungweiten der Hundeflöhe in etwa normalverteilt sind, da der Median in etwa in der Mitte der Box liegt. Darüber hinaus sind auch die Whiskers in etwa gleich lang. Ja, in der Statistik ist vieles eine Abschätzung.

Jetzt kommt die Kür, wie erstellen wir einen Boxplot in R mit dem R Paket {ggplot}? Dafür schauen wir uns erstmal an, wie ein Boxplot aussehen würde. Im Folgenden siehst du einmal die Abbildung eines liegenden Boxplot der Hundeflöhe und deren Sprungweiten erstellt in {ggplot}. Wir du erkennen kannst, gibt es da kaum einen Unterschied zu der händischen Darstellung.

Abbildung 17.18— Liegender Boxplot zu Visualisierung der Sprungweite der Hundeflöhe. Die Linien entsprechen den jeweiligen gemessenen Sprungweiten. [Zum Vergrößern anklicken]

Nun ist ein liegender Boxplot eher ungewöhnlich in der Darstellung. Zum zeichnen und verstehen macht das schon Sinn, aber wenn wir später dann mit dem Boxplot arbeiten, dann nutzen wir die stehende Varaiante.

Abbildung 17.19— Stehender Boxplot zu Visualisierung der Sprungweite der Hundeflöhe. Die Linien entsprechen den jeweiligen gemessenen Sprungweiten. [Zum Vergrößern anklicken]

Häufig nutze wir Boxplots um Gruppen, wie hier die Tierarten, miteinander zu vergleichen. In Abbildung 17.19 ist daher der Boxplot für die Sprungweite in [cm] der Hunde- und Katzenflöhe dargestellt. Wir erkennen auf einen Blick, dass die Sprungweite von den Hundeflöhen weiter ist als die Sprungweite der Katzenflöhe. Im Weiteren können wir abschätzen, dass die Streuung etwa gleich groß ist. Die Boxen sind in etwa gleich groß und die Whiskers in etwa gleich lang.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, 
       aes(x = animal, y = jump_length, fill = animal)) +
  theme_minimal() +
  geom_boxplot() +
  labs(x = "Tierart", y = "Sprungweite [cm]") +
  theme(legend.position = "none")
Abbildung 17.20— Stehender Boxplot zu Visualisierung der Sprungweite der Hunde- und Katzenflöhe. [Zum Vergrößern anklicken]

Der Boxplot erlaubt uns auch abzuschätzen, ob wir eine Normalverteilung vorliegen haben oder aber ob die Varianz in zwei oder. mehr Gruppen annähernd ist. Wir sprechen dann von Varianzhomogenität.

Abschätzung der Normalverteilung

Der Median liegt in der Mitte der Box und die Whiskers sind ungefähr gleich lang. Wir können von einer approximativen Normalverteilung ausgehen.

Abschätzung der Varianzhomogenität

Die Boxen sind über alle Gruppen ungefähr gleich groß und auch die Whiskers haben in etwa die gleiche Länge. Wir können dann von einer Varianzhomogenität über die Gruppen ausgehen.

Schau dir auch hier mal in den Kästen zum Zerforschen rein, da findest du dann noch mehr Inspiration aus anderen Abbildungen, die ich nachgebaut habe. Ich bin einmal über den Campus gelaufen und habe geschaut, welche Abbildungen auf den Postern verwendet werden und habe diese nachgebaut.

In diesem Zerforschenbeispiel wollen wir uns einen zweifaktoriellen Barplot oder Säulendiagramm anschauen. Wir haben hier ein Säulendiagramm mit den Mittelwerten über den Faktor auf der \(x\)-Achse vorliegen. Daher brauchen wir eigentlich gar nicht so viele Zahlen. Für jede der drei Fruchtgrößen jeweils einmal einen Mittelwert für die Höhe der Säule sowie einmal die Standardabweichung für die vier Stickstoffangebote. Das addiert sich dann auf, aber es geht noch. Die Standardabweichung addieren und subtrahieren wir dann später jeweils von dem Mittelwert und schon haben wir die Fehlerbalken. Da ich auch hier einmal als Alternative die Boxplots erstellen will, brauche ich hier mehr Werte aus denen ich dann die Mittelwerte und die Standardabweichung berechne.

Abbildung 17.21— Ursprüngliche Abbildung, die nachgebaut werden soll. Ein zweifaktorieller Barplot mit Mittelwerten über dem Faktor auf der \(x\)-Achse.

Als erstes brauchen wir wieder die Daten. Die Daten habe ich mir in dem Datensatz zerforschen_barplot_2fac.xlsx selber ausgedacht. Ich habe einfach die obige Abbildung genommen und den Mittelwert abgeschätzt. Dann habe ich die drei Werte alle um den Mittelwert streuen lassen. Das war es dann auch schon.

R Code [zeigen / verbergen]
barplot_tbl <- read_excel("data/zerforschen_barplot_2fac.xlsx") |> 
  mutate(frucht = factor(frucht, 
                         levels = c("klein", "mittel", "groß"),
                         labels = c("Klein", "Mittel", "Groß")),
         nmin = as_factor(nmin))
barplot_tbl 
# A tibble: 36 × 3
   frucht nmin  yield
   <fct>  <fct> <dbl>
 1 Klein  150     1.5
 2 Klein  150     2.2
 3 Klein  150     2.8
 4 Klein  200     2.4
 5 Klein  200     1.9
 6 Klein  200     2.9
 7 Klein  250     3.7
 8 Klein  250     3.9
 9 Klein  250     3.5
10 Klein  300     2.5
# ℹ 26 more rows

Jetzt brauchen wir noch die Mittelwerte und die Standardabweichung für jede der drei Fruchtgrößen und Stickstoffangebote. Hier nur kurz, den Code kennst du schon aus anderen Zerforschenbeispielen zu den Barplots. Das ist soweit erstmal nichts besonderes und ähnelt auch der Erstellung der anderen Barplots.

R Code [zeigen / verbergen]
stat_all_tbl <- barplot_tbl |> 
  group_by(frucht, nmin) |> 
  summarise(mean = mean(yield),
            sd = sd(yield))
stat_all_tbl
# A tibble: 12 × 4
# Groups:   frucht [3]
   frucht nmin   mean    sd
   <fct>  <fct> <dbl> <dbl>
 1 Klein  150    2.17 0.651
 2 Klein  200    2.4  0.5  
 3 Klein  250    3.7  0.2  
 4 Klein  300    2.5  0.4  
 5 Mittel 150    5.03 1.05 
 6 Mittel 200    7.03 0.252
 7 Mittel 250    8    0.200
 8 Mittel 300    7.7  0.200
 9 Groß   150    6.1  1.95 
10 Groß   200    8.27 0.702
11 Groß   250    9.07 1.00 
12 Groß   300    8.97 1.00 

Weil wir dann noch die globalen Mittelwerte der Früchte über alle Stickstofflevel wollen, müssen wir nochmal die Mittelwerte und die Standardabweichung nur für die drei Fruchtgrößen berechnen. Daher haben wir dann zwei Datensätze, die uns eine Zusammenfassung der Daten liefern.

R Code [zeigen / verbergen]
stat_fruit_tbl <- barplot_tbl |> 
  group_by(frucht) |> 
  summarise(mean = mean(yield))
stat_fruit_tbl
# A tibble: 3 × 2
  frucht  mean
  <fct>  <dbl>
1 Klein   2.69
2 Mittel  6.94
3 Groß    8.1 

Auch hier möchte ich einmal den Barplot nachbauen und dann als Alternative noch den Barplot anbieten. Am nervigsten war der Zeilenumbruch in der Legendenbeschriftung mit N\(_min\). Es hat echt gedauert, bis ich die Funktion atop() gefunden hatte, die in einer expression() einen Zeilenumbruch erzwingt. Meine Güte war das langwierig. Der Rest ist eigentlich wie schon in den anderen Beispielen. Da schaue dann doch nochmal da mit rein.

Einmal der Barplot wie beschrieben. Vergleiche auch einmal diese Abbildung der Barplots mit der Abbildung der Boxplots in dem anderem Tab und überlege, welche der beiden Abbildungen dir mehr Informationen liefert.

R Code [zeigen / verbergen]
ggplot(data = stat_all_tbl, aes(x = frucht, y = mean,
                                fill = nmin)) +
  theme_minimal() +
  geom_bar(stat = "identity", position = position_dodge(0.9)) + 
  geom_errorbar(aes(ymin = mean-sd, ymax = mean+sd), 
                width = 0.2, position = position_dodge(0.9)) +
  labs(x = "Fruchtgröße zum Erntezeitpunkt", 
       y = expression(atop("Gesamtfruchtertrag", "["*kg~FM~m^"-2"*"]")),
       fill = expression(atop(N[min]~"Angebot", "["*kg~N~ha^"-1"*"]"))) +
  scale_y_continuous(breaks = c(0, 2, 4, 6, 8, 10, 12),
                     limits = c(0, 12)) +
  scale_fill_okabeito() +
  theme(legend.position = "right",
        legend.box.background = element_rect(color = "black"),
        legend.box.margin = margin(t = 1, l = 1),
        legend.text.align = 0) +
  annotate("text", x = c(1, 2, 3, 1.45), y = c(6, 10, 11, 3),
           label = c(expression(bar(y)*" = "*2.7),
                     expression(bar(y)*" = "*6.9),
                     expression(bar(y)*" = "*8.1),
                     "SD"))
Abbildung 17.22— Die Abbildung des Säulendiagramms in ggplot nachgebaut.

Für die Boxplots müssen wir gar nicht viel tun. Wir müssen nur noch das geom_bar() und geom_errorbar() entfernen und durch das geom_boxplot() ersetzen. Dann haben wir auch schon unsere wunderbaren Boxplots. Das Problem sind natürlich die wenigen Beobachtungen, deshalb sehen die Boxplots teilweise etwas wild aus. Beachte auch das wir die Orginaldaten nutzen und nicht die zusammengefassten Daten.

R Code [zeigen / verbergen]
ggplot(data = barplot_tbl, aes(x = frucht, y = yield,
                               fill = nmin)) +
  theme_minimal() +
  geom_boxplot() +
  labs(x = "Fruchtgröße zum Erntezeitpunkt", 
       y = expression(atop("Gesamtfruchtertrag", "["*kg~FM~m^"-2"*"]")),
       fill = expression(atop(N[min]~"Angebot", "["*kg~N~ha^"-1"*"]"))) +
  scale_y_continuous(breaks = c(0, 2, 4, 6, 8, 10, 12),
                     limits = c(0, 12)) +
  scale_fill_okabeito() +
  theme(legend.position = "right",
        legend.box.background = element_rect(color = "black"),
        legend.box.margin = margin(t = 1, l = 1),
        legend.text.align = 0) +
  annotate("text", x = c(1, 2, 3), y = c(6, 10, 11),
           label = c(expression(bar(y)*" = "*2.7),
                     expression(bar(y)*" = "*6.9),
                     expression(bar(y)*" = "*8.1)))
Abbildung 17.23— Die Abbildung des Säulendiagramms in ggplot als Boxplot nachgebaut.

Am Ende kannst du dann folgenden Code noch hinter deinen ggplot Code ausführen um dann deine Abbildung als *.png-Datei zu speichern. Dann hast du die Abbildung super nachgebaut und sie sieht auch wirklich besser aus.

R Code [zeigen / verbergen]
ggsave("my_ggplot_barplot.png", width = 5, height = 3)

Hier wollen wir einmal eine etwas größere Abbildung 40.13 mit einer Menge Boxplots zerforschen. Da ich den Datensatz dann nicht zu groß machen wollte und Boxplots zu zerforschen manchmal nicht so einfach ist, passen die Ausreißer dann manchmal dann doch nicht. Auch liefern die statistischen Tests dann nicht super genau die gleichen Ergebnisse. Aber das macht vermutlich nicht so viel, hier geht es ja dann eher um den Bau der Boxplots und dem rechnen des statistischen Tests in {emmeans}. Die Reihenfolge des compact letter displays habe ich dann auch nicht angepasst sondern die Buchstaben genommen, die ich dann erhalten habe. Die Sortierung kann man ja selber einfach ändern. Wir haben hier ein einfaktorielles Design mit einem Behandlungsfaktor mit drei Leveln vorliegen. Insgesamt schauen wir uns vier Endpunkte in veränderten Substrat- und Wasserbedingungen an.

Abbildung 17.24— Ursprüngliche Abbildung, die nachgebaut werden soll. Insgesamt vier Outcomes sollen für zwei Behandlungen ausgewertet werden. Das praktische ist hier, dass wir es nur mit einem einfaktoriellen Design zu tun haben.

Im Folgenden lade ich einmal den Datensatz, den ich dann per Auge zusammengesetzt habe. Das war jetzt etwas anstrengender als gedacht, da ich nicht weiß wie viele Beobachtungen einen Boxplot bilden. Je mehr Beobachtungen, desto feiner kann man den Boxplot abstimmen. Da ich hier nur mit sieben Beobachtungen ja Gruppe gearbeitet habe, habe ich es nicht geschafft die Ausreißer darzustellen. Das wäre mir dann zu viel Arbeit geworden. Nachdem ich jetzt die Daten geladen habe, muss ich noch über die Funktion pivot_longer() einen Datensatz passenden im Longformat bauen. Abschließend mutiere ich dann noch alle Faktoren richtig und vergebe bessere Namen als labels sonst versteht man ja nicht was die Spalten bedeuten.

R Code [zeigen / verbergen]
boxplot_mult_tbl <- read_excel("data/zerforschen_boxplot_mult.xlsx") |> 
  pivot_longer(cols = fresh_weight:flower_number,
               values_to = "rsp",
               names_to = "plant_measure") |> 
  mutate(trt = as_factor(trt),
         plant_measure = factor(plant_measure,
                                levels = c("fresh_weight", "dry_weight",
                                           "plant_height", "flower_number"),
                                labels = c("Fresh weight (g)", "Dry weight (g)",
                                           "Plant height (cm)", "Flower number")),
         type = factor(type, labels = c("Substrate", "Water"))) 

Ich habe mir dann die beiden Behandlungen substrate und water in die neue Spalte type geschrieben. Die Spaltennamen der Outcomes wandern in die Spalte plant_measure und die entsprechenden Werte in die Spalte rsp. Dann werde ich hier mal alle Outcomes auf einmal auswerten und nutze dafür das R Paket {purrr} mit der Funktion nest() und map(). Ich packe mir als die Daten nach type und plant_measure einmal zusammen. Dann habe ich einen neuen genesteten Datensatz mit nur noch acht Zeilen. Auf jeder Zeile rechne ich dann jeweils mein Modell. Wie du dann siehst ist in der Spalte data dann jeweils ein Datensatz mit der Spalte trt und rsp für die entsprechenden 21 Beobachtungen.

R Code [zeigen / verbergen]
boxplot_mult_nest_tbl <- boxplot_mult_tbl |>
  group_by(type, plant_measure) |>
  nest() 

boxplot_mult_nest_tbl
# A tibble: 8 × 3
# Groups:   type, plant_measure [8]
  type      plant_measure     data             
  <fct>     <fct>             <list>           
1 Substrate Fresh weight (g)  <tibble [21 × 2]>
2 Substrate Dry weight (g)    <tibble [21 × 2]>
3 Substrate Plant height (cm) <tibble [21 × 2]>
4 Substrate Flower number     <tibble [21 × 2]>
5 Water     Fresh weight (g)  <tibble [21 × 2]>
6 Water     Dry weight (g)    <tibble [21 × 2]>
7 Water     Plant height (cm) <tibble [21 × 2]>
8 Water     Flower number     <tibble [21 × 2]>

Zur Veranschaulichung rechne ich jetzt einmal mit mutate() und map() für jeden der Datensätze in der Spalte data einmal ein lineares Modell mit der Funktion lm(). Ich muss nur darauf achten, dass ich die Daten mit .x einmal an die richtige Stelle in der Funktion lm() übergebe. Dann habe ich alle Modell komprimiert in der Spalte model. Das geht natürlcih auch alles super in einem Rutsch.

R Code [zeigen / verbergen]
boxplot_mult_nest_model_tbl <- boxplot_mult_nest_tbl |>
  mutate(model = map(data, ~lm(rsp ~ trt, data = .x)))

boxplot_mult_nest_model_tbl 
# A tibble: 8 × 4
# Groups:   type, plant_measure [8]
  type      plant_measure     data              model 
  <fct>     <fct>             <list>            <list>
1 Substrate Fresh weight (g)  <tibble [21 × 2]> <lm>  
2 Substrate Dry weight (g)    <tibble [21 × 2]> <lm>  
3 Substrate Plant height (cm) <tibble [21 × 2]> <lm>  
4 Substrate Flower number     <tibble [21 × 2]> <lm>  
5 Water     Fresh weight (g)  <tibble [21 × 2]> <lm>  
6 Water     Dry weight (g)    <tibble [21 × 2]> <lm>  
7 Water     Plant height (cm) <tibble [21 × 2]> <lm>  
8 Water     Flower number     <tibble [21 × 2]> <lm>  

Jetzt können wir einmal eskalieren und insgesamt acht mal die ANOVA rechnen. Das sieht jetzt nach viel Code aus, aber am Ende ist es nur eine lange Pipe. Am Ende erhalten wir dann den \(p\)-Wert für die einfaktorielle ANOVA für die Behandlung trt wiedergegeben. Wir sehen, dass wir eigentlich nur einen signifikanten Unterschied in der Wassergruppe und der Pflanzenhöhe erwarten sollten. Da der \(p\)-Wert für die Wassergruppe und der Blütenanzahl auch sehr nah an dem Signifikanzniveau ist, könnte hier auch etwas sein, wenn wir die Gruppen nochmal getrennt testen.

R Code [zeigen / verbergen]
boxplot_mult_nest_model_tbl  |> 
  mutate(anova = map(model, anova)) |> 
  mutate(parameter = map(anova, model_parameters)) |> 
  select(type, plant_measure, parameter) |> 
  unnest(parameter) |> 
  clean_names() |> 
  filter(parameter != "Residuals") |> 
  select(type, plant_measure, parameter, p)
# A tibble: 8 × 4
# Groups:   type, plant_measure [8]
  type      plant_measure     parameter        p
  <fct>     <fct>             <chr>        <dbl>
1 Substrate Fresh weight (g)  trt       0.813   
2 Substrate Dry weight (g)    trt       0.381   
3 Substrate Plant height (cm) trt       0.959   
4 Substrate Flower number     trt       0.444   
5 Water     Fresh weight (g)  trt       0.384   
6 Water     Dry weight (g)    trt       0.843   
7 Water     Plant height (cm) trt       0.000138
8 Water     Flower number     trt       0.0600  

Dann schauen wir uns nochmal das \(\eta^2\) an um zu sehen, wie viel Varianz unsere Behandlung in den Daten erklärt. Leider sieht es in unseren Daten sehr schlecht aus. Nur bei der Wassergruppe und der Pflanzenhöhe scheinen wir durch die Behandlung Varianz zu erklären.

R Code [zeigen / verbergen]
boxplot_mult_nest_model_tbl %>%  
  mutate(eta = map(model, eta_squared)) %>% 
  unnest(eta) %>% 
  clean_names() %>% 
  select(type, plant_measure, eta2) 
# A tibble: 8 × 3
# Groups:   type, plant_measure [8]
  type      plant_measure        eta2
  <fct>     <fct>               <dbl>
1 Substrate Fresh weight (g)  0.0228 
2 Substrate Dry weight (g)    0.102  
3 Substrate Plant height (cm) 0.00466
4 Substrate Flower number     0.0863 
5 Water     Fresh weight (g)  0.101  
6 Water     Dry weight (g)    0.0188 
7 Water     Plant height (cm) 0.628  
8 Water     Flower number     0.268  

Dann können wir auch schon den Gruppenvergleich mit dem R Paket {emmeans} rechnen. Wir nutzen hier die Option vcov. = sandwich::vcovHAC um heterogene Varianzen zuzulassen. Im Weiteren adjustieren wir nicht für die Anzahl der Vergleiche und lassen uns am Ende das compact letter display wiedergeben.

R Code [zeigen / verbergen]
emm_tbl <- boxplot_mult_nest_model_tbl |> 
  mutate(emm = map(model, emmeans, ~trt, vcov. = sandwich::vcovHAC)) |> 
  mutate(cld = map(emm, cld, Letters = letters, adjust = "none")) |> 
  unnest(cld) |> 
  select(type, plant_measure, trt, rsp = emmean, group = .group) |> 
  mutate(group = str_trim(group))

emm_tbl
# A tibble: 24 × 5
# Groups:   type, plant_measure [8]
   type      plant_measure     trt     rsp group
   <fct>     <fct>             <fct> <dbl> <chr>
 1 Substrate Fresh weight (g)  UWF    95.4 a    
 2 Substrate Fresh weight (g)  TWF   101   a    
 3 Substrate Fresh weight (g)  Peat  105   a    
 4 Substrate Dry weight (g)    UWF    12.3 a    
 5 Substrate Dry weight (g)    Peat   14.3 ab   
 6 Substrate Dry weight (g)    TWF    15.1 b    
 7 Substrate Plant height (cm) TWF    37.9 a    
 8 Substrate Plant height (cm) Peat   38.0 a    
 9 Substrate Plant height (cm) UWF    39.1 a    
10 Substrate Flower number     UWF    14.6 a    
# ℹ 14 more rows

Da die Ausgabe viel zu lang ist, wollen wir ja jetzt einmal unsere Abbildungen in {ggplot} nachbauen. Dazu nutze ich dann einmal zwei Wege. Einmal den etwas schnelleren mit facet_wrap() bei dem wir eigentlich alles automatisch machen lassen. Zum anderen zeige ich dir mit etwas Mehraufwand alle acht Abbildungen einzeln baust und dann über das R Paket {patchwork} wieder zusammenklebst. Der zweite Weg ist der Weg, wenn du mehr Kontrolle über die einzelnen Abbildungen haben willst.

Die Funktion facet_wrap() erlaubt es dir automatisch Subplots nach einem oder mehreren Faktoren zu erstellen. Dabei muss auch ich immer wieder probieren, wie ich die Faktoren anordne. In unserem Fall wollen wir zwei Zeilen und auch den Subplots erlauben eigene \(x\)-Achsen und \(y\)-Achsen zu haben. Wenn du die Option scale = "free" nicht wählst, dann haben alle Plots alle Einteilungen der \(x\)_Achse und die \(y\)-Achse läuft von dem kleinsten zu größten Wert in den gesamten Daten.

R Code [zeigen / verbergen]
boxplot_mult_tbl |> 
  ggplot(aes(trt, rsp)) +
  theme_minimal() +
  stat_boxplot(geom = "errorbar", width = 0.25) + 
  geom_boxplot(outlier.shape = 18, outlier.size = 2) +
  stat_summary(fun.y = mean, geom = "point", shape = 23, size = 3, fill = "red") +
  facet_wrap(~ type * plant_measure, nrow = 2, scales = "free") +
  labs(x = "", y = "") +
  geom_text(data = emm_tbl, aes(y = rsp, label = group), size = 3, fontface = "bold",
            position = position_nudge(0.2), hjust = 0, vjust = 0, color = "red")
Abbildung 17.25— Nachbau der Abbildung mit der Funktion facte_wrap() mit Boxplots und dem Mittelwert. Neben dem Mittelwert finden sich das compact letter display. Auf eine Einfärbung nach der Behandlung wurde verzichtet um die Abbildung nicht noch mehr zu überladen.

Jetzt wird es etwas wilder. Wir bauen uns jetzt alle acht Plots einzeln und kleben diese dann mit dem R Paket {patchwork} zusammen. Das hat ein paar Vorteile. Wir können jetzt jeden einzelnen Plot bearbeiten und anpassen wie wir es wollen. Damit wir aber nicht zu viel Redundanz haben bauen wir uns erstmal ein Template für ggplot(). Dann können wir immer noch die Werte für scale_y_continuous() in den einzelnen Plots ändern. Hier also einmal das Template, was beinhaltet was für alle Abbildungen gelten soll.

R Code [zeigen / verbergen]
gg_template <- ggplot() +
  aes(trt, rsp) +
  theme_minimal() +
  stat_boxplot(geom = "errorbar", width = 0.25) + 
  geom_boxplot(outlier.shape = 18, outlier.size = 2) +
  stat_summary(fun.y = mean, geom = "point", shape = 23, size = 3, fill = "red") +
  labs(x = "")

Ja, jetzt geht es los. Wir bauen also jeden Plot einmal nach. Dafür müssen wir dann jeweils den Datensatz filtern, den wir auch brauchen. Dann ergänzen wir noch die korrekte \(y\)-Achsenbeschriftung. So können wir dann auch händisch das compact letter display über die Whisker einfach setzen. Im Weiteren habe ich dann auch einmal als Beispiel noch die \(y\)-Achseneinteilung mit scale_y_continuous() geändert. Ich habe das einmal für den Plot p1 gemacht, der Rest würde analog dazu funktionieren.

R Code [zeigen / verbergen]
p1 <- gg_template %+%
  filter(boxplot_mult_tbl, type == "Substrate" & plant_measure == "Fresh weight (g)") +
  labs(y = "Fresh weight (g)") +
  annotate("text", x = c(1, 2, 3), y = c(170, 120, 135), label = c("a", "a", "a"), 
           color = "red", size = 3, fontface = "bold") +
  scale_y_continuous(breaks = seq(40, 180, 20), limits = c(60, 180))
p2 <- gg_template %+%
  filter(boxplot_mult_tbl, type == "Substrate" & plant_measure == "Dry weight (g)") +
  labs(y = "Dry weight (g)")
p3 <- gg_template %+%
  filter(boxplot_mult_tbl, type == "Substrate" & plant_measure == "Plant height (cm)") +
  labs(y = "Plant height (cm)")
p4 <- gg_template %+%
  filter(boxplot_mult_tbl, type == "Substrate" & plant_measure == "Flower number") +
  labs(y = "Flower number")
p5 <- gg_template %+%
  filter(boxplot_mult_tbl, type == "Water" & plant_measure == "Fresh weight (g)") +
  labs(y = "Fresh weight (g)")
p6 <- gg_template %+%
  filter(boxplot_mult_tbl, type == "Water" & plant_measure == "Dry weight (g)") +
  labs(y = "Dry weight (g)")
p7 <- gg_template %+%
  filter(boxplot_mult_tbl, type == "Water" & plant_measure == "Plant height (cm)") +
  labs(y = "Plant height (g)")
p8 <- gg_template %+%
  filter(boxplot_mult_tbl, type == "Water" & plant_measure == "Flower number") +
  labs(y = "Flower number")

Dann kleben wir die Abbildungen einfach mit einem + zusammen und dann wählen wir noch aus, dass wir vier Spalten wollen. Dann können wir noch Buchstaben zu den Subplots hinzufügen und noch Titel und anderes wenn wir wollen würden. Dann haben wir auch den Plot schon nachgebaut. Ich verzichte hier händisch überall das compact letter display zu ergänzen. Das macht super viel Arbeit und endlos viel Code und hilft dir dann aber in deiner Abbildung auch nicht weiter. Du musst dann ja bei dir selber die Positionen für die Buchstaben finden.

R Code [zeigen / verbergen]
patch <- p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 +
  plot_layout(ncol = 4)
patch + plot_annotation(tag_levels = 'A',
                        title = 'Ein Zerforschenbeispiel an Boxplots',
                        subtitle = 'Alle Plots wurden abgelesen und daher sagen Signifikanzen nichts.',
                        caption = 'Disclaimer: Die Abbildungen sagen nichts aus.')
Abbildung 17.26— Nachbau der Abbildung mit dem R Paket {patchwork} mit Boxplots und dem Mittelwert. Neben dem Mittelwert finden sich das compact letter display bei dem ersten Plot. Auf eine Einfärbung nach der Behanldung wurde verzichtet um die Abbildung nicht noch mehr zu überladen.

Am Ende kannst du dann folgenden Code noch hinter deinen ggplot Code ausführen um dann deine Abbildung als *.png-Datei zu speichern. Dann hast du die Abbildung super nachgebaut und sie sieht auch wirklich besser aus.

R Code [zeigen / verbergen]
ggsave("my_ggplot_boxplot.png", width = 12, height = 6)

17.4.3 Scatterplot

Der Scatterplot wird auch \(xy\)-Plot genannt. Wir stellen in einem Scatterplot zwei kontinuierliche Variablen dar. Wir haben also auf der \(x\)-Achse Kommazahlen genauso wie auf der \(y\)-Achse. Aber dazu dann gleich mehr in den folgenden Tabs zur theoretischen Betrachtung, einem Beispiel und der Umsetzung in {ggplot}. Ein Scatterplot nutzen wir relativ häufig, wenn wir den Zusammenhang zwischen kontinuierlichen Zahlen zeigen wollen.

Das Ziel eines Scatterplots ist es eine Abhängigkeit zwischen den Werten auf der \(y\)-Achse und der \(x\)-Achse darzustellen. Wenn sich die Werte auf der \(x\)-Achse ändern, wie ändern sich dann die Werte auf der \(y\)-Achse? Im einfachsten Fall zeichnen wir für die Wertepaare von \(x\) und \(y\) jeweils ein Punkt in unserer Koordinatensystem.

Abbildung 17.27— Typischer Scatterplot mit gängigen Bezeichnungen der \(x\)-Achse sowie der \(y\)-Achse. [Zum Vergrößern anklicken]

Um den Zusammenhang zwischen \(y\) und \(x\) zu visualisieren legen wir eine Linie durch die Punkte. Diese Gerade folgt dann einer Geradengleichung der Form \(y = \beta_0 + \beta_1 x\). Wobei der Wert für \(\beta_0\) den y-Achsenabschnitt beschreibt und der Wert für \(\beta_1\) die Steigung der Geraden. Wir wir methodisch zu den Zahlen einer Geradengleichung kommen und damit den Werten für \(\beta_0\) und \(\beta_1\), kannst du dann später in dem Kapitel zur linearen Regression nachlesen. Im Prinzip fragen wir uns also, wie hänge die Werte auf der \(y\)-Achse von den Werten auf der \(x\)-Achse ab? Wenn sich also die Werte auf der \(x\)-Achse erhöhen oder kleiner werden, wie verhalten sich dann die Werte auf der \(y\)-Achse?

Abbildung 17.28— Visualisierung eines Scatterplots mit einer Geraden durch die Punktewolke. Die lineare Regression wurde zur Bestimmung der Geradengleichung der Geraden durch die Punkte des Scatterplots genutzt. Die Linie oder Gerade wird durch die statistischen Maßzahlen oder Parameter \(\beta_0\) als y-Achsenabschnitt und \(\beta_1\) als Steigung der Gerade beschrieben. [Zum Vergrößern anklicken]

Wenn wir ein Scatterplot zeichnen wollen, dann brauchen wir erstmal paarweise Werte für \(x\) und \(y\). In der folgenden Tabelle findest du die Informationen von sieben HUndeflöhen zu der Sprungweite in [cm] und dem Gewicht der Flöhe in [mg]. Gibt es nun einen Zusammenhang zwischen der Sprungweite und dem Gewicht der Flöhe? Springen kräftigere Flöhe weiter? Oder eher nicht so weit, weil schwere Flöhe eben nicht so hoch springen können? Diesen Zusammenhang können wir dann mit einem Scatterplot visualisieren.

Tabelle 17.3— Die Sprungweite in [cm] und das Gewicht in [mg] von sieben Hundeflöhen.
Index jump_length weight
1 1.2 0.8
2 1.8 1.0
3 1.3 1.2
4 1.7 1.9
5 2.6 2.0
6 1.8 2.7
7 2.7 2.8

In der folgenden Abbildung siehst du einmal den Zusammenhang zwischen der Sprungweite und dem Gewicht von von unseren sieben Hundeflöhen. Für das dritte und fünfte Tier habe ich dir einmal das Wertepaar mit \((x_3, y_3)\) und \((x_5, y_5)\) beschriftet.

Abbildung 17.29— Visualisierung des Scatterplots für die Sprungweite in [cm] und das Gewicht in [mg] von sieben Hundeflöhen. Das dritte und fünfte Tier wurde mit dem Wertepaar \((x_3, y_3)\) und \((x_5, y_5)\) beschriftet. [Zum Vergrößern anklicken]

Nun können wir auch hier einmal die Geradengleichung ergänzen. Wenn wir das ganze händisch machen, dann würden wir die Werte für \(\\beta_0\) als y-Achsenabschnitt und \(\\beta_1\) als Steigung der Gerade visuell abschätzen. Das machen wir natürlich nicht in der Anwendung. Dafür habe ich dann das Kapitel zur linearen Regression geschrieben. Aus einer linearen Regression erhalten wir dann auch die Werte \(\beta_0 = 0.99\) und \(\beta_1 = 0.51\), die wir dann in die Regressiongleichung einsetzen. In der folgenden Abbildung siehst du den Zusammenhang nochmal dargestellt.

Abbildung 17.30— Visualisierung der Bestimmung der Geradengleichung anhand eines Scatterplots für die Sprungweite in [cm] und das Gewicht in [mg] von sieben Hundeflöhen. Wir legen eine Gerade durch eine Punktewolke. Die Gerade wird durch die statistischen Maßzahlen bzw. Parameter \(\beta_0\) und \(\beta_1\) beschrieben. [Zum Vergrößern anklicken]

Als erstes brauchen wir wieder einen kleineren Datensatz und ich nehme hier mal die Sprungweiten und das Körpergewicht von sieben Katzenflöhen. Dafür nutze ich die Funktionen filter() und select() um mir einen kleineren Datensatz zu bauen.

R Code [zeigen / verbergen]
flea_cat_jump_weight_tbl <- flea_dog_cat_tbl |> 
  filter(animal == "cat") |> 
  select(jump_length, weight)

Die Abbildung 17.31 zeigt den Scatterplot für die Spalte weight auf der \(x\)-Achse und jump_length auf der \(y\)-Achse für unsere sieben Katzenflöhe. Mit der Funktion geom_point() können wir die Punktepaare für jede Beobachtung zeichnen. Wie du erkennen kannst, hat das Gewicht der Katzenflöhe einen Einfluss auf die Sprungweite.

R Code [zeigen / verbergen]
ggplot(data = flea_cat_jump_weight_tbl, aes(x = weight, y = jump_length)) +
  theme_minimal() +
  geom_point() +
  labs(x = "Gewicht der Katzenflöhe [mg]", y = "Sprungweite in [cm]") +
  ylim(0, NA) + xlim(0, NA)
Abbildung 17.31— Zusammenhang zwischen der Sprungweite in [cm] und Gewicht der Katzenflöhe. Jeder Punkt stellt eine Beobachtung dar.

Jetzt wollen wir noch die Geradengleichung aus einer simplen linearen Regression zu der Abbildung ergänzen. Die Gleichung ist dabei wie folgt.

\[ jump\_length = 0.6377 + 1.8188 \cdot weight \]

Wir bauen uns hier eine Funktion in R, die die Geradengleichung repräsentiert.

R Code [zeigen / verbergen]
jump_func <- \(x){0.6377 + 1.8188 * x}

Wir können jetzt die Geradengleichung einmal über die Funktion geom_function() zu der Abbildung ergänzen. Es gebe auch die Möglichkeit die Funktion geom_smooth() zu nutzen, aber dann haben wir nicht die Werte für die Gerade sondern nur die Gerade in der Abbildung. Das macht besonders bei mehreren Gruppen Sinn, wenn wir mal schauen wollen, ob es einen Zusammenhang gibt.

R Code [zeigen / verbergen]
ggplot(data = flea_cat_jump_weight_tbl, aes(x = weight, y = jump_length)) +
  theme_minimal() +
  geom_point() +
  geom_function(fun = jump_func, color = "blue") +
  labs(x = "Gewicht der Katzenflöhe [mg]", y = "Sprungweite in [cm]") +
  ylim(0, NA) + xlim(0, NA)
Abbildung 17.32— Zusammenhang zwischen der Sprungweite in [cm] und Gewicht der Katzenflöhe. Jeder Punkt stellt eine Beobachtung dar. Eine eigene Geradengleichung wurde durch geom_function() ergänzt.

Dann habe ich mich doch noch hingesetzt und einmal für dich ein Video gemacht, wie du dann einen Scatterplot mit Trendlinie und Formel in Excel erstellst. Das ganze macht dann nur als Video Sinn, denn sonst kannst du ja nicht nachvollziehen, was ich geklickt habe.

Wenn du mehr über die Regression lernen willst schau dir auch hier mal in den Kästen zum Zerforschen rein, da findest du dann noch mehr Inspiration aus anderen Abbildungen, die ich nachgebaut habe. Ich bin einmal über den Campus gelaufen und habe geschaut, welche Abbildungen auf den Postern verwendet werden und habe diese nachgebaut.

In diesem Zerforschenbeispiel wollen wir uns eine simple lineare Regression in einem Scatterplot anschauen. Das schöne an dem Beispiel ist, dass wir hier zum einen noch einen Pfeil einfügen wollen und dass wir nur einen Bereich darstellen wollen. Die Gerade fürht also nicht durch den gesamten Plot. Sonst haben wir dann noch die Herausforderung, dass wir einmal die Geradengleichung zusammen mit dem Bestimmtheitsmaß \(R^2\) ergänzen müssen.

Abbildung 17.33— Ursprüngliche Abbildung, die nachgebaut werden soll. Eine simple lineare Regression mit Bestimmtheitsmaß \(R^2\) für die Gerade durch die Punkte.

Auch hier mache ich es mir etwas einfacher und erschaffe mir die Daten dann direkt in R. Ich baue mir also einen tibble() mit den ungefähren Werten aus der Abbildung. Das war ein wenig aufwendig, aber nach einigem Hin und Her passte dann auch die Werte zu der Abbildung.

R Code [zeigen / verbergen]
shoot_n_tbl <- tibble(n_in_shoot = c(2.5, 3.5, 3.9, 4.1, 4.2, 4.3 ,4.5, 4.7, 5.1, 5.1, 5.8),
                      freshmass = c(7.5, 9, 12.5, 11, 18, 16, 12, 17, 16, 20, 21))

Dann rechnen wir auch schon die lineare Regression mit der Funktion lm() die uns dann die Koeffizienten der Geraden wiedergibt.

R Code [zeigen / verbergen]
shoot_n_fit <- lm(freshmass ~ n_in_shoot, data = shoot_n_tbl)

Dann einmal die Koeffizienten mit der Funktion coef() ausgelesen. Den Rest brauchen wir hier nicht für die Abbildung.

R Code [zeigen / verbergen]
shoot_n_fit |> coef()
(Intercept)  n_in_shoot 
  -4.333333    4.353599 

Aus den Koeffizienten baue ich mir dann auch die Geradengleichung einmal in R. Ich kann dann die Gleichung mit der Funktion geom_function() gleich in meine nachgebaute Abbildung ergänzen.

R Code [zeigen / verbergen]
shoot_n_func <- \(x){4.35 * x - 4.33}

Ich berechne dann einmal das Bestimmtheitsmaß \(R^2\) mit der Funktion r2() aus dem R Paket {performance}. Wir sehen, ich habe die Werte recht gut abgelesen, dass passt ganz gut mit dem Bestimmtheitsmaß \(R^2\).

R Code [zeigen / verbergen]
shoot_n_fit |> r2()
# R2 for Linear Regression
       R2: 0.750
  adj. R2: 0.722

Und dann bauen wir uns schon die Abbildung 17.34 einmal nach. Der etwas aufwendigere Teil sind die Achsenbeschriftungen sowie die Ergänzung des Pfeils und der Beschriftung. Das verbraucht dann immer etwas mehr Code und Platz. Ich habe dann etwas die Achseneinteilung geändert und stelle nicht den ganzen Bereich dar. Es reicht auch vollkommen nur den Bereich zu visualisieren, der von Interesse ist. Daher beginnt dann meine \(x\)-Achse auch bei Zwei und nicht bei Null.

R Code [zeigen / verbergen]
shoot_n_tbl |> 
  ggplot(aes(n_in_shoot, freshmass)) +
  theme_minimal() +
  geom_point(size = 4, color = "red4") +
  geom_function(fun = shoot_n_func, color = "black", size = 1, xlim = c(2.5, 6)) + 
  labs(x = "N in shoot [% DM]", y = TeX(r"(Shoot fresh mass \[g plant$^{-1}$\] - 17 DAP)")) +
  scale_x_continuous(breaks = c(2, 3, 4, 5, 6), limits = c(2, 6)) +
  scale_y_continuous(breaks = c(0, 5, 10, 15, 20, 25), limits = c(0, 25)) +
  annotate("text", x = 2.5, y = 22, hjust = "left", color = "black", size = 4, 
           label = TeX(r"($y = 4.35 \cdot x - 4.33$)")) +
  annotate("text", x = 2.5, y = 20, hjust = "left", color = "black", size = 4, 
           label = TeX(r"($R^2 = 0.75$)")) +
  geom_curve(x = 3.5, y = 5.1, xend = 2.55, yend = 7,
             arrow = arrow(length = unit(0.03, "npc"), type = "closed"),
             curvature = -0.5) +
  annotate("text", x = 3.6, y = 4.5, label = "Trt. with\n50% cattail",
           hjust = "left", size = 4)
Abbildung 17.34— Visualisierung der simplen linearen Regression mit einem Pfeil sowie den Informationen zu der Geradengleichung und dem Bestimmtheitsmaß \(R^2\).

In diesem Zerforschenbeispiel wollen wir uns eine simple lineare Regression in einem Scatterplot anschauen. Das stimmt nicht so ganz, den die Schwierigkeit liegt darin, dass es sich um zwei Scatterplots handelt. Klar, du kannst die beiden Abbildungen einfach getrennt erstellen und dann wäre gut. Ich zeige dir dann aber noch zwei weitere Möglichkeiten. Daher fangen wir mit der folgenden Abbildung einmal an. Wir haben hier zwei Scatterplots mit jeweils einer linearen Regression, dargestellt durch eine Gerade mit Regressionsgleichung, vorliegen. Hier brauchen wir dann mal ein paar mehr Zahlen, die ich mir dann aber so grob aus der Abbildung abgeleitet habe.

Abbildung 17.35— Ursprüngliche Abbildung, die nachgebaut werden soll. Zwei lineare Regressionen mit den jeweiligen Regressionsgleichungen.

Wir laden als erstes wieder den Datensatz, den ich mir aus der obigen Abbildung erstellt habe. Wie immer beim Zerforschen habe ich nicht so genau drauf geachtet nur das die Zahlen so grob stimmen. Die Erstellung der Daten kann hier recht langwierig sein, aber hier geht es ja mehr um die Nutzung von ggplot. Also mach dir keinen Gedanken, wenn die Punkte nicht so perfekt passen.

R Code [zeigen / verbergen]
regression_tbl <- read_excel("data/zerforschen_regression_linear.xlsx") |> 
  mutate(type = factor(type, labels = c("Basil", "Oregano")))
regression_tbl 
# A tibble: 40 × 3
   type  washed unwashed
   <fct>  <dbl>    <dbl>
 1 Basil      0        0
 2 Basil     10       15
 3 Basil     20       18
 4 Basil     30       32
 5 Basil     40       36
 6 Basil     50       52
 7 Basil     60       59
 8 Basil     70       72
 9 Basil     80       85
10 Basil    100      105
# ℹ 30 more rows

Den folgenden Teil kannst du überspringen, wenn es dir um die Abbildung geht. Ich möchte in den zwei folgenden Tabs einmal die simple lineare Regression für die Abbildung mit dem Basilikum und einmal für das Oregano rechnen.

Wir erstellen uns einmal eine simple lineare Regression mit der Funktion lm(). Mehr zu dem Thema und die Maßzahlen der Güte einer linearen Regression wie das Bestimmtheitsmaß \(R^2\) findest du im Kapitel zur simplen linearen Regression. Deshalb hier nur die Durchführung und nicht mehr.

R Code [zeigen / verbergen]
fit <- lm(unwashed ~ washed, data = filter(regression_tbl, type == "Basil"))

fit |> 
  parameters::model_parameters() |> 
  select(Parameter, Coefficient)
# Fixed Effects

Parameter   | Coefficient
-------------------------
(Intercept) |        2.10
washed      |        1.00
R Code [zeigen / verbergen]
performance::r2(fit)
# R2 for Linear Regression
       R2: 0.994
  adj. R2: 0.994

Wir nutzen jetzt gleich die Koeffizienten aus der linearen Regression für die Erstellung der Geradengleichung.

Auch hier gilt wie im anderen Tab, dass wir uns einmal eine simple lineare Regression mit der Funktion lm() erstellen. Mehr zu dem Thema und die Maßzahlen der Güte einer linearen Regression wie das Bestimmtheitsmaß \(R^2\) findest du im Kapitel zur simplen linearen Regression. Deshalb hier nur die Durchführung und nicht mehr.

R Code [zeigen / verbergen]
fit <- lm(unwashed ~ washed, data = filter(regression_tbl, type == "Oregano"))

fit |> 
  parameters::model_parameters() |> 
  select(Parameter, Coefficient)
# Fixed Effects

Parameter   | Coefficient
-------------------------
(Intercept) |        8.17
washed      |        0.99
R Code [zeigen / verbergen]
performance::r2(fit)
# R2 for Linear Regression
       R2: 0.997
  adj. R2: 0.996

Wir nutzen jetzt gleich die Koeffizienten aus der linearen Regression für die Erstellung der Geradengleichung.

Soweit so gut. In den beiden obigen Tabs haben wir jetzt die Koeffizienten der Regressionsgleichung berechnet. Wir kriegen also aus der Funktion lm() die Steigung und den y-Achsenabschnitt (eng. Intercept). Damit können wir uns dann die beiden Funktionen für die Gerade der Basilikumdaten und der Oreganodaten bauen. Wir werden dann in ggplot mit der Funktion geom_function() die entsprechenden Gerade zeichnen.

R Code [zeigen / verbergen]
basil_func <- \(x){2.10 + 1.00 * x}
oregano_func <- \(x){8.17 + 0.99 * x}

Du hast jetzt im Folgenden die Wahl zwischen drei Lösungen des Problems. Jede dieser Lösungen ist vollkommen in Ordnung und ich zeige dir hier nur die Möglichkeiten. Nimm einfach die Lösung, die dir am besten gefällt und passt. Was machen wir nun? Wir stellen einmal die beiden Abbildungen getrennt voneinander dar. Im Weiteren nutzen wir einmal die Funktion facet_wrap() um nach einem Faktor die Abbildungen aufzutrennen. Am Ende nutzen wir noch das R Paket patchwork um aus zwei Abbildungen dann eine schön annotierte Abbildung zu machen.

Der Kern der Abbildung 61.2 und Abbildung 61.3 ist die Funktion filter(). Wir bauen uns sozusagen zweimal einen Datensatz und leiten dann den Datensatz in die Funktion ggplot() weiter. Der Trick ist eigentlich, dass wir große Teile des Codes kopieren und dann für das Oregano wieder verwenden. Wenn du dir beide Chunks mal näher anschaust, wirst du große Änlichkeiten sehen. Im Prinzip musst du nur aufpassen, dass du jeweils die richtigen Geradenfunktionen einsetzt.

R Code [zeigen / verbergen]
filter(regression_tbl, type == "Basil") |> 
  ggplot(aes(x = washed, y = unwashed, color = type)) +
  theme_minimal() +
  geom_function(fun = basil_func, color = cbbPalette[2], linetype = 'dashed') + 
  geom_point(color = cbbPalette[2]) +
  scale_x_continuous(name = TeX(r"(Iodine content in \textbf{unwashed} herbs $[\mu g\, l \, (100 g\, FM)^{-1}]$)"), 
                     breaks = seq(0, 600, 150)) +
  scale_y_continuous(name = TeX(r"(Iodine content in \textbf{washed} herbs $[\mu g\, l \, (100 g\, FM)^{-1}]$)"), 
                     breaks = seq(0, 600, 150)) + 
  theme(legend.position = "none") +
  annotate("text", x = 150, y = 100, hjust = "left", color = cbbPalette[2],  
           label = TeX(r"($y = 2.10 + 1.00 \cdot x;\; R^2 = 0.99$)")) 
Abbildung 17.36— Einmal die einfache Abbildung der linearen Regression in ggplot für Basilikum nachgebaut. Beachte die Funktion filter(), die den jeweiligen Datensatz für die beiden Kräuter erzeugt.

Und nochmal die simple Regression in dem Scatterplot für das Oregano. Bitte beachte einmal die Beschreibungen im Code und du wirst sehen, dass hier sehr viel gleich zum obigen Codeblock ist. In dem Tab zum R Paket patchwork zeige ich dir dann noch die Möglichkeit ein Template zu erstellen und dann einiges an Zeilen an Code zu sparen. Aber es geht auch so.

R Code [zeigen / verbergen]
filter(regression_tbl, type == "Oregano") |> 
  ggplot(aes(x = washed, y = unwashed, color = type)) +
  theme_minimal() +
  geom_function(fun = oregano_func, color = cbbPalette[3], linetype = 'dashed') +
  geom_point(color = cbbPalette[3]) +
  scale_x_continuous(name = TeX(r"(Iodine content in \textbf{unwashed} herbs $[\mu g\, l \, (100 g\, FM)^{-1}]$)"),
                     breaks = seq(0, 900, 150)) + 
  scale_y_continuous(name = TeX(r"(Iodine content in \textbf{washed} herbs $[\mu g\, l \, (100 g\, FM)^{-1}]$)"), 
                     breaks = seq(0, 900, 150)) +
  theme(legend.position = "none") +
  annotate("text", x = 150, y = 100, hjust = "left", color = cbbPalette[3],  
           label = TeX(r"($y = 8.17 + 0.99 \cdot x;\; R^2 = 0.99$)")) 
Abbildung 17.37— Einmal die einfache Abbildung der linearen Regression in ggplot für Oregano nachgebaut. Beachte die Funktion filter(), die den jeweiligen Datensatz für die beiden Kräuter erzeugt.

Hier brauchen wir jetzt das R Paket grid damit wir am Anschluss noch unsere Abbildungen mit den Gleichungen beschriften können. Die Idee ist eigentlich recht simple. Wir haben den Faktor type und nutzen die Funktion facet_wrap() um nach diesem Faktor zwei Abbildungen zu bauen. Unser Faktor hat zwei Level Basilikum und Oregano und deshalb erhalten wir auch zwei Subbplots. Wir können dann auch entscheiden, wie die Abbildungen angeordnet werden sollen, aber da bitte einmal bei Hilfeseite von facet_wrap(). Sonst sit alles gleich wie im ersten Tab. Also bitte nochmal da schauen.

R Code [zeigen / verbergen]
ggplot(data = regression_tbl, aes(x = washed, y = unwashed,
                                  color = type)) +
  theme_minimal() +
  scale_color_okabeito() +
  geom_function(data = filter(regression_tbl, type == "Basil"),
                fun = basil_func, color = cbbPalette[2], linetype = 'dashed') + 
  geom_function(data = filter(regression_tbl, type == "Oregano"),
                fun = oregano_func, color = cbbPalette[3], linetype = 'dashed') + 
  geom_point() +
  facet_wrap(~ type) + 
  scale_x_continuous(name = TeX(r"(Iodine content in \textbf{unwashed} herbs $[\mu g\, l \, (100 g\, FM)^{-1}]$)"),
                     breaks = seq(0, 900, 150)) +
  scale_y_continuous(name = TeX(r"(Iodine content in \textbf{washed} herbs $[\mu g\, l \, (100 g\, FM)^{-1}]$)"),
                     breaks = seq(0, 900, 150)) +
  theme(legend.position = "none") 
 
grid::grid.text(TeX(r"($y = 2.10 + 1.00 \cdot x;\; R^2 = 0.99$)"),  
                x = 0.2, y = 0.2, just = "left", gp = grid::gpar(col = cbbPalette[2])) 
grid::grid.text(TeX(r"($y = 8.17 + 0.99 \cdot x;\; R^2 = 0.99$)"),  
                x = 0.65, y = 0.2, just = "left", gp = grid::gpar(col = cbbPalette[3]))
Abbildung 17.38— Einmal die einfache Abbildung der linearen Regression in ggplot für Oregano nachgebaut. Beachte die Funktion facet_wrap(), die den jeweiligen Datensatz für die beiden Kräuter erzeugt.

Und dann sind wir auch schon fertig. Gut, das ist jetzt mit der Regressionsgleichung etwas fricklig, aber das ist es meistens, wenn du viel auf einmal darstellen willst. Vielleicht ist dann noch die Idee mit dem R Paket patchwork im nächsten Tab hilfreich.

Jetzt drehen wir nochmal frei und holen alles raus was geht. Wir nutzen zum einen das R Paket patchwork um zwei Abbildungen miteinander zu verbinden. Prinzipiell geht das auch mit dem R Paket grid und der Funktion grid.arrange(), aber dann wird das hier sehr voll. Wir nutzen am Ende nur eine Funktion aus dem Paket grid um wiederum die \(x\)-Achse schön hinzukriegen. Als erstes wollen wir uns aber ein Template in ggplot bauen, dass wir dann mit einem neuen Datensatz durch den Operator %+% mit einem neuen Datensatz versehen können.

Im Folgenden stecken wir den ganzen Datensatz in eine ggplot()-Funktion. Später wählen wir dann mit filter() die beiden Kräuterdatensätze aus. Wir definieren in dem Template alles, was wir auch für die beiden Abbildungen brauchen würden. Das spart dann etwas an Zeilen Code. Manchmal dann aber auch nicht ganz so viel, denn wir müssen für die einzelnen Datensätze dann doch noch einiges anpassen.

R Code [zeigen / verbergen]
p_template <- ggplot(regression_tbl, aes(x = washed, y = unwashed,
                                         color = type)) +
  theme_minimal() +
  geom_point() +
  scale_x_continuous(name = "",
                     breaks = seq(0, 900, 150), limits = c(0, 900)) +
  scale_y_continuous(name = TeX(r"(\textbf{Washed} herbs $[\mu g\, l \, (100 g\, FM)^{-1}]$)"),
                     breaks = seq(0, 900, 150), limits = c(0, 900)) +
  theme(legend.position = "none")

Wir nutzen jetzt das p_template und ergänzen den gefilterten Datensatz für das Basilikum mit dem Operator %+%. Dann wählen wir noch die passende Farbe über die Option order = 1 aus und ergänzen die Geradengleichung sowie den Titel für die Abbildung.

R Code [zeigen / verbergen]
p_basil <- p_template %+%
  filter(regression_tbl, type == "Basil") +
  scale_color_okabeito(order = 1) +
  geom_function(fun = basil_func, color = cbbPalette[2], 
                linetype = 'dashed') +
  annotate("text", x = 150, y = 100, hjust = "left", color = cbbPalette[2], 
           label = TeX(r"($y = 2.10 + 1.00 \cdot x;\; R^2 = 0.99$)")) +
  ggtitle("Basil")

Das Ganze dann nochmal für das Oregano, aber hier entfernen wir die \(y\)-Achse. Wir brauchen nur eine auf der linken Seite. Das ist auch der Grund warum wir keine \(x\)-Achse benannte haben, dass machen wir dann über die beiden Plots zusammen ganz am Ende. Auch hier ergänzen wir dann die Gweradengleichung sowie den Titel der Abbildung.

R Code [zeigen / verbergen]
p_oregano <- p_template %+%
  filter(regression_tbl, type == "Oregano") +
  scale_color_okabeito(order = 2) +
  geom_function(fun = oregano_func, color = cbbPalette[3], 
                linetype = 'dashed') +
  theme(axis.title.y = element_blank()) +
  annotate("text", x = 150, y = 100, hjust = "left", color = cbbPalette[3], 
           label = TeX(r"($y = 8.17 + 0.99 \cdot x;\; R^2 = 0.99$)")) +
  ggtitle("Oregano")

Jetzt geht es los mit dem Zusammenbauen. Wir können dazu einfach das + nutzen. Wenn du mehr wissen willst, was du noch ändern kannst, dann schaue einmal das Tutorium Adding Annotation and Style für das R Paket patchworkan. Da kannst du dann auch die Schriftgröße und weiteres ändern. Wir müssen dann ganz am Ende nochmal mit der Funktion grid.draw() die gemeinsame \(x\)-Achse ergänzen. Am Ende habe ich noch die Achsenbeschriftungen gekürzt und die Informationen in den Titel mit der Funktion plot_annotation geschoben. Dann habe ich noch die Subplots mit einem Buchstaben versehen. Und dann sind wir auch schon fertig.

R Code [zeigen / verbergen]
p_basil + p_oregano +
  plot_annotation(title = 'Iodine content in herbs',
                  subtitle = 'The iodine content is measured in washed and unwashed herbs',
                  caption = 'Disclaimer: The measurement has been done in freshmatter',
                  tag_levels = 'A')
grid::grid.draw(grid::textGrob(TeX(r"(\textbf{Unwashed} herbs $[\mu g\, l \, (100 g\, FM)^{-1}]$)"), 
                               y = 0.07))
Abbildung 17.39— Einmal die einfache Abbildung der linearen Regression in ggplot nachgebaut. Die Abbildung A zeigt die Punkte und die Geradengleichung für das Basilikum. Die Abbildung B die entsprechenden Informationen für das Oregano. Die beiden Achsenbeschriftungen wurden gekürzt und die Informationen in den Titel übernommen.

Am Ende kannst du dann folgenden Code noch hinter deinen ggplot Code ausführen um dann deine Abbildung als *.png-Datei zu speichern. Dann hast du die Abbildung super nachgebaut und sie sieht auch wirklich besser aus.

R Code [zeigen / verbergen]
ggsave("my_ggplot_simple_regression.png", width = 5, height = 3)

17.4.4 Histogramm

Wozu nutzen wir das Histogramm? Wir brauchen das Histogramm um die Verteilung der Messwerte \(y\) abzuschätzen. Daher wie sind unsere Sprungweiten, Anzahlen oder Gewichte unserer Hunde- und Katzenflöhe verteilt. Zuerst brauchen wir aber viele Beobachtungen. Wir brauchen für ein anständiges Histogramm, wo du auch was erkennen kannst, mindestens 20 Beobachtung pro Gruppe. Hier ist das pro Gruppe sehr wichtig. Zwar haben wir auch in unseren Hunde- und Katzenflohdaten vierzehn Beobachtungen, aber nur sieben pro Gruppe! Da können wir dann mit einem Histogramm nicht viel erkennen und dann nutzen wir den Boxplot für die Abschätzung der Verteilung. Häufig irritiert bei einem Histogramm auch, dass wir auf der x-Achse die Werte der y-Achse darstellen und dann auf der y-Achse des Histogramms die Anzahlen zählen. Aber dazu dann gleich mehr in den folgenden Tabs zur theoretischen Betrachtung, einem Beispiel und der Umsetzung in {ggplot}. Ein Histogramm nutzen wir eigentlich in einer laufenden Analyse als ein statistisches Tool und berichten es eher selten.

Für die Erstellung eines Histogramm müssen wir unterscheiden, ob wir als Outcome etwas zählbares vorliegen haben. Also ob unser Outcome Kategorien hat. Wir zählen die Anzahl an Haaren eines Flohbeins oder aber die Noten von Schülern. Wir können aber auch andere Kategorien vorliegen haben, aber es müssen schon ein paar sein. Mit ein paar meine ich dann mehr als fünf Kategorien. Ein Notenspiegel macht ja auch nur Sinn, da wir da gut zehn Notenschritte drin haben. Im Folgenden siehst du einmal eine Abbildung mit den Kategorien A bis I. Jetzt zählen wir wie oft kommt A vor, wie oft kommt B vor und so weiter. Die Anzahlen tragen wir dann als Balken ein. Dann haben wir die absoluten Anzahlen gezählt. Wenn wir aber Histogramme mit unterschiedlichen Beobachtungen vergleichen wollen, dann macht es mehr Sinn sich die relativen Häufigkeiten anzuschauen. In unserem Fall haben wir achtzehn Beobachtungen vorliegen, also bedeutet eine Beobachtung \(1/18 = 0.055\) relativen Anteil.

Abbildung 17.40— Histogramm von achtzehn Flöhen für einen kategorialen Endpunkt der Kategorien A bis I. Auf der linken Seite sind die absoluten Anzahlen dargestellt und auf der rechten Seite die entsprechenden relativen Häufigkeiten. [Zum Vergrößern anklicken]

Wenn wir keine Kategorien vorliegen haben, dann müssen wir uns für unser Outcome welche Überlegen. Das heißt wir nutzen das so genannte bining (deu. eindosen, ungebräuchlich). Wenn du das Gewicht von Flöhen misst, dann hast du Kommazahlen und damit kontinuierliche Daten vorliegen. Damit unterscheiden sich alle deine Messwerte vermutlich. Deshalb fasst du “gleiche” Werte zusammen. Meistens machen wir das, indem wir Beobachtungen in zwischen zwei Zahlen als eine Kategorie zählen. Wie da die Grenzen liegen, ist immer unterschiedlich und hängt von der Fragestellung ab.

Abbildung 17.41— Histogramm von achtzehn Flöhen für einen kontinuierlichen Endpunkt. Ein Balken entspricht immer einem Zahlenraum entsprechend der x-Achse. Auf der linken Seite sind die absoluten Anzahlen dargestellt und auf der rechten Seite die entsprechenden relativen Häufigkeiten. [Zum Vergrößern anklicken]

Betrachten wir einmal die Erstellung eine Histogramms für einen Endpunkt mit Kategorien. Dafür habe ich mir einmal die Boniturnoten für achtzehn Hundeflöhe in der folgenden Tabelle ausgedacht. Wir brauchen eben mehr Daten als wir in den ursprünglichen Hunde- und Katzenflohdaten vorliegen haben. Jetzt wollen wir uns einmal die Verteilung der Boniturnoten anschauen.

Tabelle 17.4— Die Boniturnote von achtzehn Hundeflöhen.
3 6 4 7 3 4 5 4 1 4 2 2 3 2 3 5 3 6

In der folgenden Abbildung siehst du einmal die Verteilung der achtzehn Boniturnoten der Hundeflöhe dargestellt. Wir sehen sofort, dass wir am meisten die Note 3 vergeben haben. Wir haben nämlich fünfmal die Note 3 an unseren Flöhen festgestellt. Die Boniturnote 5 haben wir dann an zwei Flöhen erhoben. Da wir achtzehn Flöhe haben, entspricht jede Anzahl dann \(5.5%\) auf der relativen Skala.

Abbildung 17.42— Histogramm von achtzehn Flöhen für die Boniturnoten als kategorialen Endpunkt. [Zum Vergrößern anklicken]

Schauen wir uns im zweiten Beispiel einmal das Gewicht von achtzehn Hundeflöhen an. Hier siehst du, dass wir Kommazahlen also kontinuierliche Daten vorliegen haben. Keine der Zahlen ist doppelt, so dass wir hier dann keine Balken hochzählen können. Damit wir das aber können, bilden wir Zahlenräume in denen wir die Gewichte zusammenfassen.

Tabelle 17.5— Das Gewicht in [mg] von achtzehn Hundeflöhen.
4 0.7 2 4.1 3.1 3.8 2.1 3.3 3 1.7 2.8 5.2 6.1 5.9 4.9 7.4 2.7 4.3

In der folgenden Abbildung siehst du einmal die Zusammenfassung unserer Flohgewichte in die Zahlenräume der Größe \(1mg\). Gewichte, die in den Bereich \(x \pm 0.5\) fallen, werden dann in einem Balken zusammengefasst. Wir haben zum Beispiel in dem Bereich mit \(x = 2\) und somit \(2 \pm 0.5\) dann die drei Werte 1.7, 2.0 und 2.1 vorliegen. Wir zeichnen einen Balken der Höhe drei. Ebenso haben wir in dem Bereich \(5.5 < x \leq 6.5\) zwei Beobachtungen vorliegen. Wir zeichnen hier einen Balken der Höhe zwei. Die Entscheidung wie weit der Zahlenraum zum zusammenfassen reichen soll, ist meist ein Ausprobieren. Das ist natürlich bei der händischen Erstellung problematisch.

Abbildung 17.43— Histogramm von achtzehn Flöhen für das Gewicht als kontinuierlichen Endpunkt. [Zum Vergrößern anklicken]

Für die Erstellung eines Histogramms mit {ggplot} nutzen wir unsere Spieldaten aus dem Tab zur händischen Erstellung eines Histogramms. Nun schauen wir uns jetzt einmal achtzehn Hundflöhe an und bestimmen die Boniturnote, dargestellt in der Spalte grade. Darüber hinaus bestimmen wir auch noch das mittlere Gewicht der Flöhe auf dem jeweiligen Hund, dargestellt in der Spalte weight.

R Code [zeigen / verbergen]
flea_hist_tbl <- tibble(grade = c(1, 2, 2, 2, 3, 3, 3, 3, 3, 
                                  4, 4, 4, 4, 5, 5, 6, 6, 7),
                        weight = c(0.7, 1.7, 2.0, 2.1, 2.7, 
                                   2.8, 3.0, 3.1, 3.3, 3.8, 
                                   4.0, 4.1, 4.3, 4.9, 5.2, 
                                   5.9, 6.1, 7.4))

In {ggplot} können wir ein Histogramm mit der Funktion geom_histogram() erstellen. Dabei ist es dann immer etwas verwirrend, dass wir unser Outcome als \(y\) dann auf der x-Achse darstellen. Wir können die Option binwidth nutzen, um zu entscheiden, wie viele Noten in einem Balken zusammengefasst werden sollen. Sinnvoll ist natürlich hier eine binwidth von 1, da wir pro Balken eine Boniturnote zählen wollen.

R Code [zeigen / verbergen]
ggplot(data = flea_hist_tbl, aes(x = grade)) +
  geom_histogram(binwidth = 1, fill = "gray", color = "black") +
  theme_minimal() +
  labs(x = "Boniturnote", y = "Anzahl") 
Abbildung 17.44— Histogramm der Boniturnoten von achtzehn Hundeflöhen. [Zum Vergrößern anklicken]

Anders sieht es für kontinuierliche Variablen mit Kommazahlen aus. Schauen wir uns das Gewicht der Flöhe an, so sehen wir, dass es sehr viele Zahlen gibt, die nur einmal vorkommen. Hier können wir dann mit binwidth den Bereich einstellen, in denen die Zahlen fallen sollen. Auch hier ist es immer ein Gefummel, wenn wir zu wenige Beobachtungen vorliegen haben. Die beste Bandbreite für die Balken zu finden, ist immer ein Ausprobieren.

R Code [zeigen / verbergen]
ggplot(data = flea_hist_tbl, aes(x = weight)) +
  geom_histogram(binwidth = 1, fill = "gray", color = "black") +
  theme_minimal() +
  labs(x = "Gewicht [mg]", y = "Anzahl") 
Abbildung 17.45— Histogramm des Gewichts von achtzehn Hundeflöhen. [Zum Vergrößern anklicken]

17.4.5 Density Plot

Eine weitere Möglichkeit sich eine Verteilung anzuschauen, ist die Daten nicht als Balkendiagramm sondern als Densityplot - also Dichteverteilung - anzusehen. Im Prinzip verwandeln wir die Balken in eine Kurve. Damit würden wir im Prinzip unterschiedliche Balkenhöhen ausgleichen und eine “glattere” Darstellung erreichen. Wir wir aber gleich sehen werden, benötigen wir dazu eine Menge an Beobachtungen und auch dann ist das Ergebnis eventuell nicht gut zu interpretieren. Eine händische Darstellung ist nicht möglich, wir machen Dichteverteilungen nur in R aber nicht selber auf einem Blattpapier.

Anbei einmal die Darstellung des Densityplots mit einem Histogramm im Hintergrund. Auf der \(y\)-Achse ist die Dichte als schwer zu interpretieren angeben. Im Prinzip interpretieren wir die Dichte nicht direkt sondern schätzen an der Kurve die Verteilung der Daten ab. Wie die Dichtekurve entsteht, ist hier nicht von Belang. Wir nutzen die Dichtekurve auch häufig zusammen mit anderen Abbildungen.

Abbildung 17.46— Beispielhafte Dichtekurve für die Abschätzung der Verteilung der Daten. [Zum Vergrößern anklicken]

Schauen wir uns einmal die Densityplots in {ggplot} an. Wir nutzen dazu die Funktion geom_density(). Die Werte der Dichte interpretieren wir nicht direkt sondern betrachten nur die Form der Dichtekurve. In unserem Fall sieht die Kurve sehr nach einer Normalverteilung aus. Um die ganze Kurve zu sehen muss ich nochmal an xlim() drehen und den x-Achsenbereich erhöhen.

R Code [zeigen / verbergen]
ggplot(data = flea_hist_tbl, aes(x = grade)) +
  geom_density(fill = "#CC79A7", color = "#CC79A7", alpha = 0.3) +
  theme_minimal() +
  xlim(-1, 10) +
  labs(x = "Boniturnote", y = "Dichte")
Abbildung 17.47— Densityplot der Boniturnote achtzehn Hundeflöhen. [Zum Vergrößern anklicken]

17.4.6 Mosaicplot

Der Mosaicplot ist ein eher seltener Plot. Wir nutzen ihn aber häufig in der Fragebogenanalyse oder aber in den Gesundheitswissenschaften. Da wir dann aber immer wieder Überschneidungen haben, wollen wir hier auch den Mosaicplot lernen. Was macht der Mosaicplot? Wir können mit dem Mosaicplot zwei Variablen, die nur aus Kategorien bestehen visualisieren. Ein Mosaicplot macht dabei für nur eine Gruppe keinen Sinn. Wir brauchen immer ein \(X\) mit mindestens zwei Gruppen sowie ein \(Y\) mit mindestens zwei Gruppen. Betrachten wir also einmal den Mosaicplot theoretisch, dann einmal händisch am Beispiel der mit Flohschnupfen infizierten Hunde- und Katzenflöhe sowie die Umsetzung in {ggplot}.

Ein Mosaicplot ist nichts anderes als die Visualisierung einer 2x2 Kreuztabelle. Daher im Folgenden einmal eine typische 2x2 Kreuztabelle als Abbildung. Wir schreiben in die Kästen die jeweiligen Anzahlen des Auftretens und berechnen dann die Anteile in der Spalte. Hier ist schon die erste Entscheidung zu treffen. Wie wollen wir die Anteile berechnen? Auf der ganzen Tabelle und daher durch \(n\) teilen? Oder aber über die Zeilen und damit über die Kategorien von \(Y\)? Häufig wollen wir wissen, wie sich die Gruppen in \(X\) im Bezug auf \(Y\) unterscheiden. Daher werden die Gruppen in die Spalten geschrieben und wir berechnen die Anteile spaltenweise. In der folgenden 2x2 Tafl siehst du einmal die Erstellung für den spaltenweisen Anteilsvergleich.

Abbildung 17.48— Eine theoretische 2x2 Tafel für zwei Gruppen und einem kategoriellen Endpunkt \(y\) mit den beiden Ausprägungen ja/nein oder 0/1. Für jedes der Felder wird spaltenweise der Anteil berechnet. Insgesamt wurden \(n\) Beobachtungen gemacht. [Zum Vergrößern anklicken]

Der Mosaicplot ist jetzt nichts anderes als die Visualisierung der obigen theoretischen 2x2 Kreuztabelle. Dabei kippen wir einmal die Beschriftung der Spalten von oben nach unten, da wir es ja jetzt mit einer Abbildung zu tun haben und nicht mit einer Abbildung. Der eigentliche Witz eines Mosaicplot ist jetzt die Flächen spaltenweise den berechneten Anteilen anzupassen. Auf diese Weise können wir die Anteile der Gruppen sofort visuell erfassen.

Abbildung 17.49— Theoretischer Mosaicplot, der die Anteile spaltenweise für die beiden Guppen an dem Endpunkt \(Y\) wiedergibt. Dabei ist die Fläche \(A\), die jedes Quadrat einnimmt proportional zu dem berechneten Anteil. Die Anteile können so visuell schnell verglichen werden. [Zum Vergrößern anklicken]

Für unsere händische Erstellung des Mosaicplots brauchen wir erstmal eine 2x2 Kreuztabelle. Hierz nutzen wir den Anteil der Flöhe mit Flohschnupfen in unseren Hunde- und Katzenflohdaten. Dabei wollen wir wissen, ob sich die beiden Tierarten als Gruppen voneinander unterscheiden. Damit ist unser \(Y\) der Infektionsstatus (ja/nein) und unser \(X\) die beiden Tierarten (dog/cat). Es ergibt sich dann die folgende 2x2 Kreuztabelle. Beachte auch die entsprechenden Randsummen, die auch noch eine Information über die Daten beinhalten. So finden wir nämlich mehr gesunde Tiere in unseren Daten als kranke Tiere über die beiden Tierart hinweg.

Abbildung 17.50— Eine 2x2 Tafel für die Hunde- und Katzenflöhe und dem entsprechenden Infektionsstatus mit den beiden Ausprägungen ja/nein oder 0/1. Für jedes der Felder wird spaltenweise der Anteil berechnet. So haben 43% der Hundeflöhe einen Schnupfen aber nur 29% der Katzen. Insgesamt wurden \(n\) Beobachtungen gemacht. [Zum Vergrößern anklicken]

Leider sind die Prozentzahlen in der 2x2 Kreuztabelle schwer visuell zu unterscheiden. Deshalb zeichnen wir den Mosaicplot so, dass die Flächen die Anteile in den Spalten widerspiegeln. So ist die Fläche von 43% kranken Hundeflöhen fast doppelt so groß wie die Fläche der 29% kranken Katzen. Daher lassen sich so schnell die Gruppen miteinander vergleichen. Wir sehen hier sehr schön, dass weit weniger Katzenflöhe mit Flohschnupfen infiziert sind als Hundeflöhe.

Abbildung 17.51— Mosaicplot des Infektionsstatus für die Hunde- und Katzenflöhe. Der Anteil ist für beide Gruppen spaltenweise an dem Endpunkt \(Y\) berechnet. Dabei ist die Fläche \(A\), die jedes Quadrat einnimmt proportional zu dem berechneten Anteil. Die Anteile können so visuell schnell verglichen werden. [Zum Vergrößern anklicken]

Für unseren Mosaicplot in R nutzen wir das R Paket {ggmosaic} welches uns dann ermöglicht einen Mosaicplot in {ggplot} zu erstellen. Zuerst müssen wir uns aber noch die Daten zusammenbauen. Dafür brauchen wir die Spalten infected und animal einmal als Faktoren und nicht als Zahlen oder Wörter. Um die 2x2 Tabelle in R in der richtigen Orientierung vorliegen zu haben, müssen wir nochmal einen kleinen Klimmzug über mutate() nehmen. Wir wandeln die Variable infected in einen Faktor um und sortieren die Level entsprechend, so dass wir die richtige Ordnung wie später im Mosaicplot haben.

R Code [zeigen / verbergen]
flea_dog_cat_mosaic_tbl <- flea_dog_cat_tbl |> 
  mutate(animal = factor(animal, levels = c("dog", "cat")),
         infected = factor(infected, levels = c(0, 1))) 

Betrachten wir jetzt einmal die 2x2 Kreuztabelle der beiden Spalten animal und infected mit der Funktion tabyl() aus dem R Paket {janitor} um einen Überblick zu erhalten.

R Code [zeigen / verbergen]
flea_dog_cat_mosaic_tbl %>% 
  tabyl(infected, animal) 
 infected dog cat
        0   4   5
        1   3   2

Dannn können wir uns einmal den Mosaicplot in {ggplot} anschauen. Wir nutzen dafür das R Paket {ggmosaic} mit der Funktion geom_mosaic(). Abbildung 17.52 zeigt den Mosaic Plot für die Variable animal und infected. Die unterschiedlich großen Flächen bilden die Verhältnisse der 2x2 Tabelle ab. So sehen wir, dass es mehr uninfizierte Flöhe als infizierte Tiere Flöhe. Am meisten gibt es uninfizierte Katzenflöhe.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_mosaic_tbl) +
  theme_minimal() +
  geom_mosaic(aes(x = product(infected, animal), fill = animal)) +
  theme(legend.position = "none")
Abbildung 17.52— Mosaicplot des Infektionsstatus für die Hunde- und Katzenflöhe. Der Anteil ist für beide Gruppen spaltenweise an dem Endpunkt \(Y\) berechnet. Dabei ist die Fläche \(A\), die jedes Quadrat einnimmt proportional zu dem berechneten Anteil. Die Anteile können so visuell schnell verglichen werden. [Zum Vergrößern anklicken]

17.4.7 Dotplot, Beeswarm und Raincloud Plot

Wenn wir weniger als fünf Beobachtungen haben, dann ist meist ein Boxplot verzerrend. Wir sehen eine Box und glauben, dass wir viele Datenpunkte vorliegen haben. Bei 3 bis 7 Beobachtungen je Gruppe bietet sich der Dotplot als eine Lösung an. Wir stellen hier alle Beobachtungen als einzelne Punkte dar. Wie erstellen wir nun einen Dotplot in R? Wir nutzen dazu die Funktion geom_dotplot() wie folgt.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, aes(x = animal, y = grade,
                                    fill = animal)) +
  geom_dotplot(binaxis = "y", stackdir = "center") +
  theme_minimal() +
  labs(x = "Tierart", y = "Boniturnote [1-9]") 
Abbildung 17.53— Der Dotplot für die Anzahl der Flöhe für die beiden Tierarten Hund und Katze.

In Abbildung 17.53 sehen wir den Dotplot aus der Datei flea_dog_cat.xlsx. Auf der x-Achse sind die Level des Faktors animal dargestellt und auf der y-Achse die Notenbewertung grade der einzelnen Hunde und Katzen. Die Funktion geom_dotplot() erschafft das Layer für die Dots bzw. Punkte. Wir müssen in der Funktion noch zwei Dinge angeben, damit der Plot so aussieht, dass wir den Dotplot gut interpretieren können. Zum einen müssen wir die Option binaxis = y wählen, damit die Punkte horizontal geordnet werden. Zum anderen wollen wir auch, dass die Punkte zentriert sind und nutzen dafür die Option stackdir = center.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, aes(x = animal, y = grade,
                            fill = animal)) +
  geom_dotplot(binaxis = "y", stackdir = "center") +
  stat_summary(fun = median, fun.min = median, fun.max = median,
               geom = "crossbar", width = 0.5) +
  theme_minimal() +
  labs(x = "Tierart", y = "Boniturnote [1-9]") 
Abbildung 17.54— Der Dotplot für die Anzahl der Flöhe für die beiden Tierarten Hund und Katze. Die schwarze Linie stelt den Median für die beiden Tierarten dar.

Nun macht es wenig Sinn bei sehr wenigen Beobachtungen noch statistische Maßzahlen mit in den Plot zu zeichnen. Sonst hätten wir auch gleich einen Boxplot als Visualisierung der Daten wählen können. In Abbildung 17.54 sehen wir die Ergänzung des Medians. Hier müssen wir etwas mehr angeben, aber immerhin haben wir so eine Idee, wo die “meisten” Beobachtungen wären. Aber auch hier ist Vorsicht geboten. Wir haben sehr wenige Beobachtungen, so dass eine Beobachtung mehr oder weniger große Auswirkungen auf den Median und die Interpretation hat.

Dann möchte ich hier den Beeswarm als eine Alternative zu dem Dotplot vorstellen. Insbesondere wenn du sehr viele Beobachtungen hast, dann hat der Beeswarm bessere Eigenschaften als der Dotplot. Es gibt hier auch die tolle Hilfeseite zu Beeswarm plot in ggplot2 with geom_beeswarm() und natürlich noch die Möglichkeit ein Violin Plot zu ergänzen. Auch hier dann mal bei der Hilfeseite Violin plot with data points in ggplot2 schauen. In Abbildung 17.55 siehst du dann einmal das Alter und die Körpergröße für die beiden Geschlechter in den Gummibärchendaten aufgeteilt.

R Code [zeigen / verbergen]
ggplot(data = gummi_tbl, aes(x = gender, y = age,
                             color = gender)) +
  geom_beeswarm() +
  theme_minimal() +
  labs(x = "Geschlecht", y = "Alter in Jahren") +
  theme(legend.position = "none")

ggplot(data = gummi_tbl, aes(x = gender, y = height,
                             color = gender)) +
  geom_beeswarm() +
  theme_minimal() +
  labs(x = "Geschlecht", y = "Körpergröße in cm") +
  theme(legend.position = "none")
(a) Alter nach Geschlecht
(b) Körpergröße nach Geschlecht
Abbildung 17.55— Der Beeswarm ist ein Dotplot für eine große Anzahl an Beobachtungen. Hier schauen wir uns einmal das Alter und die Körpergröße aufgeteilt nach Geschlecht an.

Und dann bringen wir in der Abbildung 17.56 mal verschiedene Abbildungen zusammen mit dem R Paket{gghalves}. Wir können mit {gghalves} halbe Plots erstellen und diese dann miteinander kombinieren. Damit packen wir dann in die Mitte Boxplots. Links von den Boxplots zeichnen wir die einzelnen Beobachtungen als Punkte mit stat_dots() und die Verteilung der einzelnen Beobachtungen zeichnen wir mit dem R Paket {ggdist}. Das Tutorium Visualizing Distributions with Raincloud Plots liefert dann noch mehr Anleitungen für noch mehr Varianten. Wie du aber schon am R Code siehst, ist das eine etwas komplexere Abbildung geworden.

R Code [zeigen / verbergen]
ggplot(gummi_tbl, aes(x = gender, y = age, color = gender)) +
  theme_minimal() +
  stat_halfeye(adjust = 0.5, width = 0.4, .width = 0, 
    justification = -0.3, point_colour = NA) + 
  geom_boxplot(width = 0.15, outlier.shape = NA) +
  stat_dots(side = "left", justification = 1.12, binwidth = .25) +
  coord_cartesian(xlim = c(1.2, 1.9), clip = "off") +
  labs(x = "Geschlecht", y = "Alter in Jahren") +
  scale_color_okabeito() +
  theme(legend.position = "none")

ggplot(gummi_tbl, aes(x = gender, y = height, color = gender)) +
  theme_minimal() +
  stat_halfeye(adjust = 0.5, width = 0.4, .width = 0, 
    justification = -0.3, point_colour = NA) + 
  geom_boxplot(width = 0.15, outlier.shape = NA) +
  stat_dots(side = "left", justification = 1.12, binwidth = .25) +
  coord_cartesian(xlim = c(1.2, 1.9), clip = "off") +
  labs(x = "Geschlecht", y = "Körpergröße in cm") +
  scale_color_okabeito() +
  theme(legend.position = "none")
(a) Alter nach Geschlecht
(b) Körpergröße nach Geschlecht
Abbildung 17.56— Der {gghalves}-Plot als Kombination vom Dotplot, Boxplot sowie Densityplot. Mit der Art der Abbildung spart man sich dann drei Abbildungen. Hier haben wir dann alle Informationen über die Körpergröße sowie dem Alter in Abhängigkeit vom Geschlecht in einer Abbildung.

17.4.8 Violinplot

Eine etwas neuere Abbildung, die eigentlich gar so neu ist, ist der Violinplot. Der Violinplot verbindet im Prinzip den Boxplot zusammen mit dem Densityplot. Wir haben am Ende eben eine Verteilung der Daten visualisiert. Wir schauen uns aber nicht wie in einem Histogramm die Werte als Balken an, sondern glätten die Balken zu einer Kurve. Wie immer gibt es auch ein Tutorium mit noch mehr Hilfe unter ggplot2 violin plot : Quick start guide - R software and data visualization. Wir schauen uns jetzt mal die Erstellung von Violinplots in verschiedenen Kombinationen mit anderen Abbildungen an.

Da ein Violinplot keinen Median oder sonst eine deskriptive Zahl beinhaltet, müssen wir uns eine Funktion erstellen, die den Mittelwert plusminus Standardabweichung wiedergibt. Die Funktion rufen wir dann innerhalb von ggplot() auf und erhalten dann den Mittelwert und Standardabweichung als einen Punkt mit zwei Linien dargestellt.

R Code [zeigen / verbergen]
data_summary <- function(y) {
   m <- mean(y)
   ymin <- m - sd(y)
   ymax <- m + sd(y)
   return(c(y = m, ymin = ymin, ymax = ymax))
}

In der Abbildung 17.57 siehst du einmal einen Violinplot mit der Funktion geom_violin(). Ich nutze eigentlich immer die Option trim = FALSE damit die Violinplots nicht so abgeschnitten sind. Der Nachteil ist dann, dass eventuell Werte angezeigt werden, die in den Daten nicht vorkommen, aber das ist auch sonst der Fall bei anderen Densityplots. Hier sieht es dann einfach besser aus und deshalb nutze ich es gerne. Durch die Funktion stat_summary() ergänze ich dann noch den Mittelwert und die Standardabweichung.

R Code [zeigen / verbergen]
ggplot(data = gummi_tbl, aes(x = gender, y = height,
                             color = gender)) +
  theme_minimal() +
  geom_violin(trim = FALSE) +
  theme(legend.position = "none") +
  stat_summary(fun.data = data_summary) +
  labs(x = "Geschlecht", y = "Körpergröße in cm") +
  scale_color_okabeito()
Abbildung 17.57— Der Violinplot die Körpergröße aufgeteilt nach Geschlecht als die simpelste Art der Darstellung mit einem Violinplot. Ergänzt noch durch den Mittelwert plusminus der Standardabweichung.

In der nächsten Abbildung 17.58 siehst du dann die Implementierung des Violinplot aus dem R Paket {see} mit der Funktion geom_violindot(). Auch hier trimme ich nicht die Spitzen der Violinplots und vergrößere die Punkte in dem Dotplot. Die Stärke von der Funktion ist der halbe Violinplot zusammen mit einem Dotplot, wir haben dann beides. Zum einen können wir die Werte sehen, wie sie sich in einem Histogramm anordnen würden. Zum anderen haben wir dann auch den Densityplot als geglättete Kurve daneben. Ich habe auch hier den Mittelwert und die Standardabweichung ergänzt, musste aber die Position in der \(x\)-Richtung etwas verschieben.

R Code [zeigen / verbergen]
ggplot(data = gummi_tbl, aes(x = gender, y = height,
                             color = gender)) +
  theme_minimal() +
  geom_violindot(dots_size = 4, trim = FALSE) +
  theme(legend.position = "none") +
  stat_summary(fun.data = data_summary, 
               position = position_nudge(x = 0.1)) +
  labs(x = "Geschlecht", y = "Körpergröße in cm") +
  scale_color_okabeito()
Abbildung 17.58— Der Violinplot die Körpergröße aufgeteilt nach Geschlecht als die simpelste Art der Darstellung mit einem Violinplot. Ergänzt noch durch den Mittelwert plusminus der Standardabweichung. Hier müssen wir aber die Darstellung auf der \(x\)-Achse um \(0.1\) etwas verschieben.

Du musst natürlich keine Funktion aus einem anderen Paket nehmen. Der Violinplot lässt sich als ganzer Plot auch mit dem Dotplot kombinieren. Wir plotten als erstes in den Hintergrund den Violinplot, ergänzen dann darüber den Dotplot und zeichnen ganz zum Schluss noch die Mittelwerte und die Standardabweichung ein. So erhalten wir dann die Abbildung 17.59.

R Code [zeigen / verbergen]
ggplot(data = gummi_tbl, aes(x = gender, y = height,
                             fill = gender)) +
  theme_minimal() +
  geom_violin(alpha = 0.5, trim = FALSE) +
  geom_dotplot(binaxis = "y", stackdir = "center",
               dotsize = 0.5) +
  stat_summary(fun.data = data_summary, size = 1, linewidth = 2) +
  theme(legend.position = "none") +
  labs(x = "Geschlecht", y = "Körpergröße in cm") +
  scale_fill_okabeito()
Abbildung 17.59— Der Violinplot die Körpergröße aufgeteilt nach Geschlecht als die simpelste Art der Darstellung mit einem Violinplot. Ergänzt noch durch den Mittelwert plusminus der Standardabweichung sowie den einzelnen Beobachtungen aus einem Dotplot.

In der Abbildung 17.60 sehen wir dann anstatt von einem Dotplot einen Beeswarm. Wie immer ist es Geschmackssache welcher Plot einem mehr zusagt. Der Beeswarm wirkt immer etwas kompakter und so lässt sich hier auch mehr erkennen. Das Problem ist eher, dass die Punkte sich nicht füllen lassen, so dass wir dann doch ein recht einheitliches Bild kriegen. Hier muss ich dann immer überlegen, was ich dann wie einfärben will.

R Code [zeigen / verbergen]
ggplot(data = gummi_tbl, aes(x = gender, y = height,
                             fill = gender, color = gender)) +
  theme_minimal() +
  geom_violin(alpha = 0.5, trim = FALSE) +
  geom_beeswarm() +
  theme(legend.position = "none") +
  stat_summary(fun.data = data_summary, size = 1, linewidth = 2,
               color = "black") +
  labs(x = "Geschlecht", y = "Körpergröße in cm") +
  scale_fill_okabeito() +
  scale_color_okabeito()
Abbildung 17.60— Der Violinplot die Körpergröße aufgeteilt nach Geschlecht als die simpelste Art der Darstellung mit einem Violinplot. Ergänzt noch durch den Mittelwert plusminus der Standardabweichung sowie den einzelnen Beobachtungen aus einem Beeswarm.

17.5 Optionen in {ggplot}

Im Folgenden dann noch eine Sammlung an nützlichen Optionen und Möglichkeiten, die einem das Leben einfacher machen und die Abbildungen dann noch schöner. Nicht alles musst du in ggplot machen, manchmal geht es dann in PowerPoint dann doch schneller mal eben einen Text zu ergänzen. Sehe das hier deshalb als Ergänzung und meinen privaten Raum, den ich nutze um mir den Code zu merken.

17.5.1 Überschriften, Achsen und Legenden

Wenn du mehr machen willst, also die Überschriften anpassen oder aber die Achsenbeschriftung ändern, dann gibt es hier global Hilfe im ggplot Manual. Die Webseite R Cookbook hat auch spezielle Hilfe für ggplot().

In Abbildung 17.61 siehst du eine Abbildung mit Titel und veränderten Beschriftungen. Die Möglichkeiten sind nahezu unbegrenzt und sprengen auch hier den Rahmen. Im Zweifel im R Tutorium vorbeischauen oder aber in der Vorlesung fragen.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, aes(x = animal, y = jump_length,
                                    fill = animal)) +
  geom_boxplot() +
  labs(title = "Frischgewicht in Abhängigkeit von der Behandlung",
       x = "Behandlung", y = "Frischgewicht in kg/ha") +
  scale_x_discrete(labels = c("Katze", "Hund")) +
  scale_fill_discrete(name = "Behandlung", labels = c("Katze", "Hund")) +
  theme_minimal() 
Abbildung 17.61— Beispielhafte Abbildung mit Titel und geänderter Achsenbeschrittung. [Zum Vergrößern anklicken]

17.5.2 Abbildungen abspeichern

Wenn du eine Abbildung abspeichern willst, dann musst du nur nach dem ggplot-Code die Funktion ggsave() setzen. Wie du im hier im Folgenden siehst, speichere ich die Abbildung der Boxplots der Hunde- und Katzenflöhe einmal in der Datei flea_dog_boxplot.png ab. Dabei wähle ich eine Breite width und eine Höhe height von jeweils 5. Du musst dann immer etwas spielen, je größer die Zahlen, desto größer die Abbildung und die Auflösung.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, 
       aes(x = animal, y = jump_length)) +
  geom_boxplot() 

## Abspeichern des obigen ggplots  
ggsave("flea_dog_boxplot.png", width = 5, height = 5)

Wie immer hilft auch die Hilfeseite von ggsave() weiter, wenn es um mehr Optionen und Qualität der Abbildungen geht.

17.5.3 Mathematische Ausdrücke in den Achsenbeschriftungen

Besuche auch ggplot2 extensions für weitere tolle Möglichkeiten!

Häufig wollen wir nicht nur einfache Achsenbeschriftungen haben, sondern auch irgendwie komplexere Einheiten wie Eisendüngergehalt im Boden in \([kg\, ha]^{-1}\) darstellen. Jetzt soll die Einheit auch in dieser Form mit in die Achsenbeschriftung. Wir können dafür zwei Wege wählen. Einmal über das R Paket {latex2exp} und die Funktion TeX() oder aber die Funktion expression(), wofür wir dann kein eigenes R Paket brauchen. Beide Wege haben Vor- und Nachteile. Wir gehen aber beide mal durch. Mehr Informationen durch das Tutorium Using latex2exp oder aber eben der Klassiker mit Plot math expression.

Wir können die Funktion expression() nutzen um uns mathematische Formeln zu bauen. Leider ist das Ganze etwas frickelig und auch ich brauche immer drei Anläufe, bis die Formel dann passt. Im Folgenden aber einmal zwei Beispiel für mathematische Formeln und Ausdrücke. Beachte, dass du jedes Leerzeichen durch eine Tilde ~ abbilden musst. Ich nutze die Funktion expression() sehr selten und nur wenn die Formel wirklich sehr einfach ist. Da wir aber schon mit eckigen Klammern Probleme kriegen und diese so nervig mit " einklammern müssen, nutze ich dann das Paket {latex2exp} was ich im Folgenden vorstellen werde.

Hier aber erstmal zwei Beispiele für eine Formel mit der Funktion expression(). Wenn du mehr über die Möglichkeiten wissen willst, dann schauen auch einmal auf die Hilfeseite von Plot math oder du googelst dir die Lösung wie ich früher zusammen.

R Code [zeigen / verbergen]
plot(expression(Eisendüngeform~und~-höhe~"[kg ha]"^-1), cex = 1.5, main = "")

R Code [zeigen / verbergen]
plot(expression(Fe-Gehalt~"["~mg%.%(kg~TM)^-1~"]"), cex = 1.5, main = "")

Für mich ausdrücklich einfacher geht es mit dem R Paket {latex2exp} und der Funktion TeX() sowie die Helferfunktion r"()". Ja, hier muss man dann noch eine andere Programmiersprache kennen, aber wie immer, du wirst nur schlauer. Die Informationen zur Matheumgebung in \(\LaTeX\) kommen dann nochmal extra zwischen zwei Dollarzeichen $. Ja, das ist etwas wirr für einen Anfänger, aber wir nutzen hier auch zwei Programmiersprachen zusammen. Zum einen \(\LaTeX\) um die Mathesymbole sauber darzustellen und dann R um die Abbildungen in ggplot() zu bauen. Mehr Informationen zu der Matheumgebung in \(\LaTeX\) findest du einmal in der LaTeX Mathehilfe I sowie der LaTeX Mathehilfe II.

Wie bauen wir uns also unseren mathematischen Ausdruck? Als erstes brauchen wir die Funktion Tex(), die sagt einfach nur aus, dass jetzt \(\LaTeX\)-Code kommt. Dann wollen wir noch einen String brauen in dem der \(\LaTeX\)-Code für unseren mathematischen Ausdruck drin steht. Diesen String bauen wir mit r"()". Achtung, hier ist das Gänsefüßchen oben und unten vor und nach der runden Klammer sehr wichtig. In den Ausdruck können wir dann Text schreiben Eisengehalt oder aber einen mathematischen Ausdruck abgrenzt von zwei Dollarzeichen $ wie $[kg\, ha]^{-1}$. \(\LaTeX\) kann nämlich nicht nur mathematische Ausdrücke sondern ist eigentlich ein Textverarbeitungsprogramm. Deshalb musst du hier nochmal zwischen Text und mathematischen Ausdruck unterscheiden.

Hier nochmal aufgeschlüsselt wie der Code aussieht. Wir schreiben den Code nachher in einer Zeile, aber zum Verständnis ist es besser, wenn wir den Code einmal aufgeklappt sehen.

R Code [zeigen / verbergen]
TeX(
    r"(
      Eisengehalt $[kg\, ha]^{-1}$
    )"
   )

Wir wollen uns das Ergebnis einmal in einem simplen plot() anschauen. Wir nutzen die Funktionalität natürlich später in ggplot, aber hier ist es so einmal einfacher zu sehen.

R Code [zeigen / verbergen]
plot(cex = 2, main = "",
  TeX(r"(
         Eisengehalt $[kg\, ha]^{-1}$
      )")
    )

Auch können wir sehr viel komplexere Formeln erstellen. Beachte auch hier, dass wir zwei Matheumgebungen in \(\LaTeX\) vorliegen haben.

R Code [zeigen / verbergen]
plot(cex = 2, main = "",
  TeX(r"(
         A $\LaTeX$ formula: $\frac{2hc^2}{\lambda^5}\frac{1}{e^{\frac{hc}{\lambda k_B T}} - 1}$
      )")
  )

In der Abbildung 17.62 dann nochmal die Anwendung in einem ggplot in dem wir die Achsen entsprechend beschriften und dann auch noch eine ausgedachte Regressionsgeleichung zu der Abbildung ergänzen.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, aes(x = flea_count, y = jump_length)) +
  geom_point() +
  stat_smooth(method = "lm", se = FALSE) +
  theme_minimal() +
  labs(x = TeX(r"(Eisengehalt und -höhe $[kg\, ha]^{-1}$)"), 
       y = TeX(r"(Fe-Gehalt $[mg \cdot (kg TM)^{-1}]$)")) +
  annotate("text", x = 10, y = 10,
           label = TeX(r"($y = \beta_0 + \beta_1 \cdot x;\; R^2 = 0.24$)"))
Abbildung 17.62— Zusammenhang zwischen dem Eisengehalt und -höhe im Boden und dem Eisengehalt in Salat. Zusätzlich ergänzt eine Regressiongleichung und ein ausgedachtes Bestimmtheitsmaß. [Zum Vergrößern anklicken]

Wenn du dann mal die Funktion Tex() in geom_text() verwenden willst, dann musst du einmal etwas anpassen. Dann klappt es aber auch hier. Das hat mich mal echt Nerven und Zeit gekostet, deshalb lagere ich die Information mal hier für mich.

R Code [zeigen / verbergen]
ggplot() +
  theme_void() +
  geom_text(aes(0, 0, 
                label = TeX(r'($\alpha  x^\alpha$, where $\alpha \in 1\ldots 5$)',
                                  output = "character")), parse = TRUE) 

17.5.4 Farbpaletten

Besuche auch ggplot2 extensions für weitere tolle Möglichkeiten!

Neben den klassischen Farben im R Paket {ggplot2} gibt es noch weit, weit mehr Farbpaletten. Wir nutzen in der Folge immer wieder die Okabe-Ito Farbpalette aus dem R Paket {see}. Die Okabe-Ito Farbpalette ist speziell so gebaut, dass die Farben sich gut für farbenblinde Personen unterscheiden. Mehr zum R Paket {see} auf der Hilfeseite des Paketes. Der Kontrast zwischen den Farben ist sehr gut. Wenn du eine andere Farbpalette nutzen willst, findest du hier noch andere Color Scales.

R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, 
       aes(x = animal, y = jump_length,
           fill = animal)) +
  geom_boxplot() +
  scale_fill_okabeito() +
  theme_minimal()
Abbildung 17.63— Beispielhafte Abbildung der Okabe-Ito Farbpalette für Boxplots. [Zum Vergrößern anklicken]
R Code [zeigen / verbergen]
ggplot(data = flea_dog_cat_tbl, 
       aes(x = animal, y = jump_length,
           color = animal)) +
  geom_point() +
  scale_color_okabeito() +
  theme_minimal()
Abbildung 17.64— Beispielhafte Abbildung der Okabe-Ito Farbpalette für Punkte. [Zum Vergrößern anklicken]

Das Ganze geht dann auch händisch mit dem folgenden Code für die jeweiligen Farben. Anbei einmal die Farbpalette dargestellt.

Abbildung 17.65— Farbpalette nach dem Okabe-Ito-Schema ebenfalls für farbblinde Personen erstellt. [Zum Vergrößern anklicken]

Die Farben sind dann in der Reihenfolge wie folgt kodiert.

R Code [zeigen / verbergen]
cbbPalette <- c("#E69F00", "#56B4E9", "#009E73", 
                "#F0E442", "#0072B2", "#D55E00", 
                "#CC79A7", "#999999", "#000000")

Wenn wir Boxplots einfärben wollen dann nehmen wir den folgenden Code.

R Code [zeigen / verbergen]
scale_fill_manual(values = cbPalette)

Und das hier ist die Ergänzung für Punkte und Linien.

R Code [zeigen / verbergen]
scale_colour_manual(values = cbPalette)

Neben der Okabe-Ito Farbpalette liefert das R Paket {duke} noch eine andere Möglichkeit eine Farbpalette für Farbblinde zu generieren.

Abbildung 17.66— Farbpalette nach dem Duke-Schema ebenfalls für farbblinde Personen erstellt. [Zum Vergrößern anklicken]

Die Farben sind dann in der Reihenfolge wie folgt kodiert.

R Code [zeigen / verbergen]
dukePalette <- c("#012169", "#C84E00", "#00539B", "#339898", 
                 "#A1B70D", "#E89923", "#FFD960", "#262626")

Die Funktionen hier sind scale_duke_color_discrete() sowie scale_duke_continuous() und scale_duke_fill_discrete().

Manchmal benötigen wir auch Farbverläufe. In R heißen diese Farbverläufe dann Farbpaletten. Eine Einführung liefert das Tutorium Using RColorBrewer palettes. Ich selber nutze gerne das R Paket {wesanderson} welches sehr schöne Farbverläufe hat. Mehr kannst du auf der GitHub Seite Wes Anderson Palettes erfahren. Wir können die Paletten ganz einfach mit der Funktion wes_palette() laden.

R Code [zeigen / verbergen]
wes_palette("Zissou1")

Das schöne ist hier, dass wir einfach wie folgt Farbverläufe erstellen können. Wir wollen hier 21 Farbwerte haben und das Ganze dann als kontinuierlichen Verlauf.

R Code [zeigen / verbergen]
wes_palette("Zissou1", 21, type = "continuous")

17.5.5 Abbildungen nebeneinander

Besuche auch ggplot2 extensions für weitere tolle Möglichkeiten!

Das R Paket {patchwork} erlaubt es mehrere ggplot Abbildungen nebeneinander oder in einem beliebigen Layout miteinander zu verbinden. Das tolle ist, dass die Idee sehr intuitiv ist. Wir nutzen wieder das + um verschiedene Plots miteinander zu verbinden. Im Folgenden erschaffen wir uns zwei ggplots und speichern die Plots in den Objekten p1 und p2. Das ist wie wir es bisher kennen, nur das jetzt keine Abbildung erscheint sondern beide Plots in zwei Objekten gespeichert sind.

R Code [zeigen / verbergen]
p1 <- ggplot(data = flea_dog_cat_tbl, 
             aes(x = flea_count, y = jump_length,
                 color = animal)) +
  geom_point() +
  scale_color_okabeito() +
  theme_minimal()

p2 <- ggplot(data = flea_dog_cat_tbl, 
                aes(x = animal, y = jump_length,
                    color = animal)) +
  geom_point() +
  scale_color_okabeito() +
  theme_minimal()

Wie können wir nun die beiden Abbildungen nebeneinander zeichnen? Wir nutzen einfach das + Symbol.

R Code [zeigen / verbergen]
p1 + p2
Abbildung 17.67— Beispielhafte Abbildung der zweier Plots nebeneinander. [Zum Vergrößern anklicken]

Auf der Seite des R Paket {patchwork} findest du viel mehr Möglichkeiten das Layout anzupassen und auch die einzelnen Subplots zu beschriften.

17.5.6 Gebrochene \(y\)-Achse

Besuche auch ggplot2 extensions für weitere tolle Möglichkeiten!

Das R Paket {ggbreak} erlaubt es dir in die \(x\)-Achse oder aber \(y\)-Achse Lücken einzusetzen oder aber die Achsen eben gebrochen darzustellen. Zur Demonstration bauen wir uns nochmal den stat_tbl für die Hunde- und Katzenflöhe. Wir berechnen hier dann die Mittelwerte und nicht mehr die Standardabweichung, da es sonst nicht so gut mit der Darstellung mit der gebrochenen \(y\)-Achse für dieses Beispiel klappt.

R Code [zeigen / verbergen]
stat_tbl <- flea_dog_cat_tbl |> 
  group_by(animal) |> 
  summarise(mean = mean(jump_length))

In der Abbildung 17.68 siehst du einmal die Abbildung der Mittelwerte der Sprungweiten der Hunde- und Katzenflöhe als Barplots dargestellt. Ich habe hier einen Bruch auf der \(y\)-Achse mit der Funktion scale_y_break() bei 1 bis 4 eingefügt und den Abstand über die Option space etwas visuell vergrößert. Mit der Option scales könntest du dann noch die Skalierung der gebrochenen \(y\)-Achse anpassen.

R Code [zeigen / verbergen]
ggplot(stat_tbl, aes(x = animal, y = mean, fill = animal)) + 
  theme_minimal() +
  geom_bar(stat = "identity") +
  scale_y_break(c(1, 4), space = 0.5)
Abbildung 17.68— Beispielhafte Abbildung der Barplots mit gebrochener \(y\)-Achse. Die Fehlerbalken wurden aus Gründen der besseren Darstellung der zerbrochenen \(y\)-Achse entfernt. [Zum Vergrößern anklicken]

Referenzen

Cumming G, Fidler F, Vaux DL. 2007. Error bars in experimental biology. The Journal of cell biology 177: 7–11.
Riedel N, Schulz R, Kazezian V, Weissgerber T. 2022. Replacing bar graphs of continuous data with more informative graphics: are we making progress? Clinical Science 136: 1139–1156.
Tukey JW. 1962. The future of data analysis. The annals of mathematical statistics 33: 1–67.
Tukey JW, others. 1977. Exploratory data analysis. Reading, MA.