Start
Ziele dieses Tutorials
Hier werden die Grundlagen der Datentranformation mit
dplyr
undtidyr
gelehrt, sowie die Visualisierung von Daten mitggplot2
.Hier werden keine allgemeine Grundlagen der R-Programmierung gelehrt! Bitte schaue dir das R & RStudio Grundlagen Tutorial an.
Tidyverse
Das tidyverse ist eine Sammlung von open-source R-Paketen, die speziell für die Datenaufbereitung und -visualisierung entwickelt wurde. Alle Pakete basieren auf einer konsistenten Design-Philosophie, Grammatik und auch Datenstruktur, welche innerhalb des tidyverse-Universums einheitlich ist, aber z.T auch sehr stark von base-R abweicht. Zu den Paketen im tidyverse gehören:
dplyr
(Datenmanipulation),tidyr
(Datenbereinigung),ggplot2
(Datenvisualisierung),readr
(Datenimport),stringr
(Umgang mit Strings/Text),forcats
(Umgang mit kategorialen daten),purrr
(funktionales Programmieren) undtibble
(Alternative zudata.frames
).
Warum ist das tidyverse sinnvoll als Ergänzung zu base R?
Benutzerfreundlichkeit und Lesbarkeit: Der tidyverse bietet eine intuitive Syntax, die mit Pipes (%>%) arbeitet. Dies ermöglicht eine klare und lesbare Darstellung von Datenmanipulationsprozessen, wodurch auch komplexe Analysen nachvollziehbar bleiben.
Konsistenz: Alle Pakete im tidyverse folgen gemeinsamen Designprinzipien und Funktionen, was den Lernaufwand reduziert und eine nahtlose Integration zwischen den Paketen ermöglicht.
Effizienz: Viele tidyverse-Funktionen sind im Vergleich zu base-R einheitlicher und damit auch leichter anzuwenden, wenn man sich innerhalb der Syntax gut auskennt.
Erweiterte Funktionalität: Das tidyverse bietet moderne Werkzeuge, die in base-R fehlen oder umständlicher umzusetzen sind, wie z. B. das einfache Pivotieren von Tabellen, die Gruppierung von Daten oder das Erstellen aussagekräftiger Visualisierungen.
Community und Dokumentation: Das tidyverse hat eine große Community, hervorragende Dokumentationen und Tutorials, die den Einstieg und die Weiterentwicklung erleichtern.
Kritische Punkte und Herausforderungen
Häufige Änderungen: Der tidyverse entwickelt sich kontinuierlich weiter, und Funktionen werden mitunter angepasst oder durch neue ersetzt. Dies kann dazu führen, dass älterer Code schnell veraltet und nachträglich angepasst werden muss. Viele Pakete enthalten daher einen Lifecycle-Status, der angibt, ob eine Funktion stabil ist oder zukünftig durch andere ersetzt wird.
Lernaufwand: Wer an die Arbeitsweise von base-R gewöhnt ist, muss sich zunächst an die andere Syntax und Denkweise des tidyverse gewöhnen.
Performance in spezifischen Fällen: Während der tidyverse in vielen Bereichen performant ist, kann es in sehr spezialisierten Anwendungsfällen oder bei extrem großen Datensätzen weniger effizient sein als optimierte Alternativen.
Abhängigkeiten: Das tidyverse umfasst eine Vielzahl von Paketen, was die Abhängigkeiten und den Overhead im Vergleich zu reinem base-R erhöht.
Insgesamt bietet das tidyverse eine kohärente, mächtige und weit verbreitete Ergänzung zu base-R.
dplyr– Grundlagen
Kapitel
Übersicht über die wichtigsten
Funktionen
Beispieldatensatz
Piping mit %>%
select()
filter()
arrange()
rename()
mutate()
summarize()
group_by()
Übersicht über die wichtigsten Funktionen
All diese Funktionen werden in diesem Kapitel näher erklärt:
select()
: Auswahl von Spaltenfilter()
: Auswahl von Zeilen basierend auf Bedingungenarrange()
: Sortieren von Zeilenrename()
: Umbenennen von Spaltenmutate()
: Hinzufügen oder Verändern von Spaltensummarize()
: Aggregation von Daten, z. B. Berechnung von Mittelwertengroup_by()
: Gruppieren von Daten für Aggregationen
Beispieldatensatz
Der Datensatz df
wird nachfolgend im Kapitel genutzt und
ist in den jeweiligen R-Umgebungen hinterlegt.
set.seed(3)
df <- data.frame(
id = 1:6,
sex = c("M", "F", "M", "F", "M", "F"),
age = c(25, 35, 45, 20, 30, 40),
intelligence = c(110, 120, 130, 115, 125, 135),
school_years = c(10, 7, 8, 12, 10, 8),
income = sample(2000:5000, 6),
student = c(FALSE, TRUE, FALSE,TRUE, TRUE, FALSE),
x_1 = rnorm(6),
x_2 = rnorm(6),
x_3 = rnorm(6),
x_4 = rnorm(6),
life1 = rnorm(6),
life2 = rnorm(6),
life3 = rnorm(6),
life4 = rnorm(6),
x1_study = rnorm(6),
x2_study = rnorm(6),
x3_study = rnorm(6),
x_study_na = rnorm(6)
)
df
Piping mit %>%
Der Pipe-Operator (%>%
) ist ein zentrales Werkzeug im
tidyverse und hilft dabei, Datenmanipulationen in einer klaren
und lesbaren Weise zu verketten. Er ermöglicht es, mehrere Funktionen
nacheinander auf einen Datensatz anzuwenden, ohne dabei den
Datensatz-Objekt in jedem Funktionsaufruf explizit wiederholen zu
müssen. Der Shortcut für ein schnelles Erstellen des Operators ist
Strg + Shift + M
für Windows und
Cmd + Shift + M
für MacOS.
Betrachten wir folgendes Beispiel, in dem wir Daten mit vier Funktionen manipulieren. Was die einzelnen Funktionen machen, werden wir uns später im Detail anschauen.
# Option 1
# Zergliederung der Schritte und Speicherung der Zwischenschritte
df <- select(df, X, sex, age, intelligence)
df <- filter(df, age <= 40)
df <- arrange(df, sex)
df <- rename(df, subjects = X)
Die erste Optione ist zwar gut leserlich, aber sehr umständlich gecoded, da sie einige Wiederholungen enthält. So wird der einzelne Bearbeitungsschritt jedes Mal zwischen gespeichert und der Datensatz muss jedes Mal neu übergeben werden.
# Option 2
# Verschachtelung der Funktionen ineinander
df <- rename(
arrange(
filter(
select(df, X, sex, age, intelligence),
age <= 40
),
sex
),
subjects = X
)
Die zweite Option ist deutlich effizienter geschrieben, jedoch schwer zum Lesen und Debuggen.
Der Pipe-Operator %>%
hilft nun, genaus diese
Verarbeitungs-Ketten besser darzustellen im Code. Anstatt den Datensatz
in jeder Funktion explizit zu nennen, wird der Datensatz implizit an die
nachfolgende Funktionen weitergegeben. So sieht der Code mit
Pipe-Operator aus:
# Option 3
# empfohlene Variante
df %>%
# der Originaldatensatz wird nun weitergeleitet an die select Funktion
select(id, sex, age, intelligence) %>%
# der veränderte Datensatz wird weitergeleitet nach filter()
filter(age <= 40) %>%
arrange(sex) %>%
rename(subjects = id)
Die Funktionsweise bleibt gleich, aber der Code wird übersichtlicher und leichter lesbar. Die Pipe-Schreibweise verbindet die Übersichtlichkeit von Option 1 und die Effizienz von Option 2.
Vorteile des Pipe-Operators
- Lesbarkeit: Der Code zeigt die Verarbeitungsschritte klar und in einer logischen Reihenfolge.
- Weniger Wiederholungen: Der Datensatz-Objekt muss nicht bei jeder Funktion neu referenziert werden.
- Effizienz bei komplexen Prozessen: Die Verkettung macht besonders bei vielen Verarbeitungsschritten Sinn.
Hier ist ein Beispiel mit dem Beispieldatensatz:
# Datenmanipulation mit Pipe-Operator
df %>%
select(id, sex, age, intelligence) %>%
filter(age <= 40) %>%
arrange(sex) %>%
rename(subjects = id)
Der Pipe-Operator (%>%
) ist ein mächtiges Werkzeug,
das den Code übersichtlicher macht und oft näher am menschlichen Denken
dran ist. Diese Art zu Programmieren ist so beliebt geworden, dass es
inzwischen auch einen Pipe-Operator in base-R gibt, welcher so
aussieht: |>
. Die Operatoren sind sich in vielen
Funktionalitäten ähnlich. Wir werden innerhalb dieses Tutorials wegen
der Einfachheit immer den %>%
Operator verwenden.
Übungen
Aufgabe: Schreibe den
Code um und verwende hier den Pipe Operator %>%
. Ändere
ansonsten so wenig, wie möglich.
Tipp
<-
, welche durch die Pipe
ersetzt werden.
df <- df %>% group_by() %>% select() %>% filter() %>% rename() df
df <- group_by(df, sex)
df <- select(df, id, sex, age, intelligence)
df <- filter(df, age <= 40)
df <- rename(df, subjects = id)
df
df <- df %>%
group_by(sex) %>%
select(id, sex, age, intelligence) %>%
filter(age <= 40) %>%
rename(subjects = id)
df
select()
Mit der Funktion select()
können spezifische Spalten
eines Dataframes ausgewählt werden. Die Funktionen bietet viele
Möglichkeiten, die Spalten-Auswahl zu gestalten.
Die wichtigsten Funktionen und Operatoren sind:
:
um einen Bereich von Spalten auszuwählen!
um Spalten auszuschließen&
(und) sowie|
(oder) um Bedingungen zu kombinierenstarts_with("")
um Spalten auszuwählen, die mit einem bestimmten Präfix beginnenends_with("")
um Spalten auszuwählen, die mit einem bestimmten Suffix endencontains("")
um Spalten auszuwählen, die einen bestimmten Text enthalteneverything()
um alle Spalten auszuwählen (z. B. nach vorheriger Modifikation der Reihenfolge)num_range("prefix", range)
um nummerierte Spalten auszuwählen, z. B.x1
,x2
, …matches("")
um Spalten basierend auf regulären Ausdrücken “Regex” auszuwählen
Spalten auflisten
Das Komma,
wir verwendet, um alle gewünschten Spaltennamen
aufzulisten.
df %>%
select(sex, age, intelligence)
Spaltenbereich auswählen
Der Doppelpunkt:
wird verwendet, um eine Spaltensequenz
auszuwählen. Es bedeutet: “Wähle alle Spalten zwischen (einschließlich)
sex
und intelligence
”. Die Reihenfolge basiert
auf der tatsächlichen Anordnung der Spalten im Datensatz
df
.
df %>%
select(sex:intelligence)
Spalten ausschließen
Negiere die ausgewählten Spalten mit!
. Es werden alle
Spalten ausgewählt, außer den negierten Spalten.
df %>%
select(!sex:intelligence)
Spalten auswählen, welche einen bestimmten character String enthalten
Der gesuchte String kann Text, aber auch Zahlen oder Symbole enthalten.
df %>%
select(contains("x_"))
df %>%
select(starts_with("life"))
Alles oder den übrigen Rest auswählen
# Wähle age an erster Stelle und füge dann den Rest aller Spalten dazu
df %>%
select(age, everything())
Nummerierte Spalten auswählen
Es gibt oft Spaltennamen, welche dem Muster folgen [Textname +
aufsteigende Zahl]. Solch ein Muster kann auch leicht ausgewählt werden
mit num_range
.
df %>%
select(num_range("x_", 1:3))
Komplexe Mustererkennung
Es können auch nach sehr komplexen Mustern Spaltennamen ausgewählt werden. Dafür werden jedoch reguläre Ausdrücke, sogenannte Regex verwendet, was eine eigene Sprache darstellt. Weitere Informationen dazu gibt es hier.
Eine kleine Übersicht über wichtige Regex-Symbole:
Symbol | Beschreibung | Beispiel |
---|---|---|
. |
Beliebiges Zeichen (außer Zeilenumbruch) | a.c erkennt abc , a1c ,
etc. |
^ |
Start des Textes oder einer Zeile | ^Hello erkennt Hello world . |
$ |
Ende des Textes oder einer Zeile | world$ erkennt Hello world . |
* |
Null oder mehr Wiederholungen | ab* erkennt a , ab ,
abb . |
+ |
Eine oder mehr Wiederholungen | ab+ erkennt ab , abb ,
etc. |
? |
Null oder eine Wiederholung | ab? erkennt a , ab . |
{n} |
Genau n Wiederholungen | a{3} erkennt aaa . |
{n,} |
Mindestens n Wiederholungen | a{2,} erkennt aa , aaa , … |
{n,m} |
Zwischen n und m Wiederholungen | a{2,4} erkennt aa , aaa . |
[0-9] |
Ziffern zwischen 0 bis 9 | a[0-9] erkennt a5 , a0 . |
[a-z] |
nur kleine Buchstaben | _[a-z] erkennt _a , _b . |
[A-Z] |
nur große Buchstaben | _[A-Z] erkennt _A , _B . |
[A-Za-z] |
große und kleine Buchstaben | _[A-Za-z] erkennt _A ,
_b . |
[A-Za-zÄÖÜäöüß] |
alle Buchstaben mit Umlauten oder Sonderzeichen (wie ä, ö, ü, ß) ) | _[A-Za-zÄÖÜäöüß] erkennt _A ,
_ä . |
# suche nach Spaltennamen, welche das _ Symbol und nachfolgend
# eine oder mehrere Ziffern enthält
df %>%
select(matches("_[0-9]+"))
Übungen
Aufgabe: Wähle die Spalten “income”, “id” und “school_years” aus.
Tipp
select()
.
df %>% select(income, id, school_years)
Aufgabe: *Wähle alle Spalten aus, die mit “_study” enden.*
Tipp
select()
.
ends_with()
.
df %>%
select(ends_with("_study"))
Aufgabe: Wähle alle Spalten aus, welche eine Ziffer enthalten.
Tipp
matches()
.
matches()
.
".*[0-9]+.*"
als Regex-Syntax.
df %>% select(matches(".*[0-9]+.*"))
filter()
Mit filter()
kannst du Zeilen basierend auf einer oder
mehreren Bedingungen auswählen. Diese Bedingungen können mit:
Vergleichsoperatoren (
==
,!=
,<
,>
,<=
,>=
) definiert werden,Logischen Operatoren (
&
für “und”,|
für “oder”), undZeichenfolgen in Anführungszeichen für Textwerte.
# Wähle Zeilen aus, in denen die Variable sex
# den Inhalt F enthält
df %>%
filter(sex == "F")
# Das Geschlecht muss weiblich sein und das
# Alter muss über 40 Jahre sein
df %>%
filter(sex == "F" & age < 40)
Übungen
Aufgabe: Wähle alle Studenten aus.
Tipp
filter()
.
df %>% filter(student == TRUE)
df %>% filter(student == TRUE & age >= 30)
Aufgabe: Wähle Personen aus, die älter als 40 Jahre sind oder ein Einkommen über 4000€ haben.
Tipp
|
für eine oder-Logik.
df %>% filter(age > 40 | income > 4000)
arrange()
Die Funktion arrange()
verändert die Zeilen eines
Datensatzes basierend auf einer oder mehreren Spalten. Standardmäßig
erfolgt die Sortierung aufsteigend, aber mit der Funktion
desc()
kann auch absteigend sortiert werden.
# Die Zeilen werden zuerst sotiert nach dem
# Geschlecht und danach nach dem Alter
df %>%
arrange(sex, age)
# Das Alter wird nun aufsteigend sortiert
df %>%
arrange(desc(age))
Übungen
Aufgabe: Sortiere die Daten nach der Intelligenz.df %>% arrange(intelligence)
Aufgabe: Sortiere die Daten nach dem Einkommen. Das höchste Einkommen soll in der ersten Zeile erscheinen.
Tipp
desc()
.
df %>% arrange(desc(income))
rename()
Die rename()
-Funktion hilft dabei, Spalten eines
Datensatzes umzubenennen. Sie unterstützt sowohl die Umbenennung
einzelner Spalten, als auch mehrere Umbenennungen auf einmal.
Die Syntaxlogik ist: rename(neuer_name = alter_name)
df %>%
rename(person_id = id,
experience_1 = x_1)
Übungen
Aufgabe: Benenne die Spalte “intelligence” in “iq_score” um.
Tipp
alter_name = neuer_name
.
df %>%
rename(iq_score = intelligence)
mutate()
Die Funktion wird verwendet, um neue Spalten zu erstellen oder bestehende Spalten in einem Datensatz zu überschreiben. Sie ist vielseitig einsetzbar und erlaubt mathematische Berechnungen, logische Operationen sowie die Anwendung von Funktionen auf Spalten.
Besonderheiten von mutate()
:
Existierende Spalten überschreiben : Wenn die Spalte bereits existiert, wird sie mit den neuen Werten überschrieben.
Neue Spalten erstellen : Wenn die Spalte noch nicht existiert, wird sie neu erstellt.
Mathematische Operationen : Du kannst einfache mathematische Berechnungen wie Addition
+
, Subtraktion-
, Multiplikation*
und Division/
verwenden.Vektorisierung : Die Operationen werden auf jede Zeile des Datensatzes angewendet.
Funktionen verwenden : Du kannst R-Funktionen wie
mean()
,if_else()
,n()
und benutzerdefinierte Funktionen verwenden.Position: Du kannst bestimmen, wo die neuen Splaten plaziert werden sollen mit den Funktionsargumenten
.before = Spaltenname
und.after = Spaltenname
.
Neue Spalte erstellen
# Erstellt eine neue Spalte age_months,
# die das Alter in Monaten darstellt
df %>%
mutate(age_months = age * 12, .before = age)
Mehrere Spalten gleichzeitig erstellen oder berechnen
# Berechnet den Unterschied des Alters und des
# IQ im Vergleich zum Mittelwert:
df %>%
mutate(age_diff = age - mean(age),
iq_diff = intelligence - mean(intelligence),
.after = age)
Spalte mit logischer Bedingung erstellen
Erstellt eine Spalteage_general
, die Menschen in die
Kategorien “jung” (≤ 37 Jahre) und “alt” (> 37 Jahre) einteilt:
df %>%
mutate(age_general = if_else(age <= 37, "young", "old"),
.after = age)
Existierende Spalte überschreiben
Achtung: wenn ein Spaltenname gewählt wird, der bereits im Datensatz existiert, wird der bestehende Inhalt überschrieben.
# das Einkommen wird um 10% erhöht
df %>%
mutate(income = income * 1.10,
.after = age)
Übungen
Aufgabe: Erstelle eine neue Spalte namens “name” und setze “unbekannt” als Wert.
Tipp
mutate()
.
.after = id
.
df %>%
mutate(name = "unbekannt", .after = id)
Aufgabe: Überschreibe die Werte von “income”, sodass R den Wert als fehlend (“not available”) begreift.
Tipp
mutate()
.
df %>%
mutate(income = NA)
Aufgabe: Erstelle eine neue Spalte namens “mean_income” und berechne dort den Durchschnitt des Einkommens aller Personen.
Tipp
mutate()
.
.after = id
.
mean()
.
df %>%
mutate(mean_income = mean(income), .after = id)
summarize()
Die Funktion summarize()
(auch
summarise()
geschrieben) ermöglicht es, Datensätze durch
Aggregation von Werten zu kondensieren. Dabei werden neue Spalten
erstellt, die zusammenfassende Statistiken (wie mean()
,
sum()
, max()
, etc.) enthalten. Das Ergebnis
ist ein neuer Datensatz mit weniger Zeilen als der
Originaldatensatz.
Die Syntaxlogik ist:
summarize(neue_spalte = Funktion(spaltenname))
df %>%
summarize(
total_age = sum(age),
average_age = mean(age),
max_age = max(age)
)
In diesem Beispiel:
Berechnet
total_age
die Summe der Werte in der Spalte “age.”Ermittelt
average_age
den Durchschnittswert der Spalte “age.”Findet
max_age
den höchsten Wert in der Spalte “age.”
Jede neue Spalte im Ergebnis enthält den Output der entsprechenden Funktion. Da alle Werte zu einer einzigen Zusammenfassung aggregiert werden, reduziert sich die Anzahl der Zeilen deutlich.
Übungen
Aufgabe: Aggregiere die Tabelle: Erstelle dabei eine neue Spalte namens “mean_income” und berechne dort den Durchschnitt des Einkommens aller Personen.
Tipp
summarize()
.
mean()
.
df %>%
summarize(
mean_income = mean(income)
)
df %>%
summarize(
mean_income = mean(income),
median_school = median(school_years)
)
group_by()
Mit der Funktion group_by()
kannst du deinen Datensatz
in verschiedene Gruppen aufteilen, um dann für jede Gruppe getrennte
Operationen auszuführen. Dies ist besonders nützlich, wenn du
gruppenspezifische Statistiken oder Filter anwenden möchtest. Zum
Beenden einer Gruppierung innerhalb einer Pipe kannst du
ungroup()
verwenden.
Die Syntaxlogik ist: group_by(gruppe1)
oder auch
group_by(gruppe1, gruppe2)
Kurze Übersicht:
group_by()
: Teilt den Datensatz in Gruppen auf.ungroup()
: Hebt die Gruppierung wieder auf, sodass nachfolgende Operationen wieder auf den gesamten Datensatz angewendet werden.
Diese Funktionen lassen sich hervorragend mit folgenden Funktionen kombinieren:
summarize()
: Erzeugt zusammenfassende Statistiken für jede Gruppe.filter()
: Filtert Zeilen innerhalb jeder Gruppe basierend auf gruppenspezifischen Bedingungen.mutate()
: Fügt gruppenbezogene Berechnungen als neue Spalten hinzu.
Beispiel 1: Zusammenfassen nach Geschlecht
Im folgenden Beispiel werden Daten nach der Variable “sex” gruppiert und für jede Gruppe werden der Gesamtwert, der Durchschnitt und der Maximalwert des Alters berechnet:
df %>%
group_by(sex) %>%
summarize(
total_age = sum(age),
average_age = mean(age),
max_age = max(age)
)
In diesem Fall wird für jede Geschlecht eine eigene Zusammenfassung erstellt.
Beispiel 2: Filter() und mutate() in Gruppen
In diesem Beispiel wird der Datensatz nach “sex” gruppiert, dann werden nur Zeilen beibehalten, bei denen das Alter über dem jeweiligen Gruppenmittelwert liegt. Anschließend wird eine neue Spalte “age_diff” erstellt, die den Unterschied zwischen dem individuellen Alter und dem Gruppenmittelwert angibt:
df %>%
group_by(sex) %>%
filter(age > mean(age)) %>%
mutate(age_diff = age - mean(age), .after = age) %>%
ungroup()
In diesem Beispiel:
group_by(sex)
: Teilt den Datensatz in Gruppen basierend auf dem Geschlecht.filter(age > mean(age))
: Behalte nur die Zeilen, in denen das Alter größer als der Durchschnitt der jeweiligen Gruppe (hier das Geschlecht) ist.mutate(age_diff = age - mean(age))
: Fügt eine neue Spalte hinzu, die die Differenz zwischen dem individuellen Alter und dem Gruppenmittelwert berechnet.ungroup()
: Hebt die Gruppierung auf, damit nachfolgende Operationen wieder auf den gesamten Datensatz angewendet werden können.
Übungen
Aufgabe: Berechne in Abhängigkeit des Geschlechts das durchschnittliche Einkommen (aggregiere die Daten). Benenne die neue Spalte “mean_income”.
Tipp
group_by()
.
summarize()
.
mean()
.
df %>% group_by() %>% summarize()
df %>%
group_by(sex) %>%
summarize(mean_income = mean(income))
Aufgabe: Berechne in Abhängigkeit des Geschlechts das durchschnittliche Einkommen (aggregiere die Daten). Benenne die neue Spalte “mean_income”. Berechne danach noch das durchschnittliche Alter pro Geschlecht (speichere das Ergebnis in “mean_age”) ab. Löse danach die Gruppenstruktur auf.
Tipp
group_by()
.
summarize()
.
mean()
.
ungroup()
.
df %>% group_by() %>% summarize() %>% ungroup()
df %>%
group_by(sex) %>%
summarize(mean_income = mean(income),
mean_age = mean(age)) %>%
ungroup()
dplyr – Daten zusammenführen
Kapitel
join Funktionenbind Funktionen
Wir stellen hier zwei verschiedene Varianten vor, Daten zusammenzuführen.
Die join
-Funktionen sind dazu geeignet Datensätze anhand
von verknüpften IDs miteinander zu verbinden. bin_col
ermöglicht es auch Spalten zusammen zu “kleben” - hier findet jedoch
keine Zuordnung oder logische Prüfung der Zeilen statt.
join Funktionen
In vielen Datenanalysen hast du es mit mehreren Datensätzen zu tun, die zusammengehören, aber unterschiedliche Informationen enthalten. Mit den join-Funktionen kann man die Daten zueinander zuordnen und dabei zusammenführen.
In unserem Beispiel gibt es zwei Tabellen:
students: Enthält alle immatrikulierten Studenten, ihren Namen und die Anzahl der bereits absolvierten Semester.
enrollments: Enthält die Kursanmeldungen. Hierzu gehören neben der Kurs-ID und dem Kursnamen auch die Studenten ID und der Name, wie er in der Anmeldung erscheint.
Dabei kann es vorkommen, dass:
Ein Student in beiden Tabellen vorkommt (z. B. Max oder Sophie).
Ein Student nur in “students” vorhanden ist (z. B. Lukas oder Mia, falls keine Anmeldung erfolgt).
Anmeldungen von Studenten existieren, die nicht im aktuellen “students”-Datensatz aufgeführt sind (z. B. student_id 5).
Mit den join
-Funktionen in dplyr
kann man
die Informationen der Datensätze zusammenführen.
Hier ist eine grafische Übersicht über die einzelnen Funktionen und was sie bedeuten:
Datensatz “students”
Datensatz “enrollments”
Anmerkung: In diesem Beispiel ist nur die Studierenden-ID eine eindeutige und fehlerfreie Art, die Personen (Zeilen) eindeutig zuzuordnen. Der Datensatz “enrollments” enthält z.T. unvollständige Angaben zum Namen, deswegen wird nur die Studierenden-ID verwendet.
full_join()
Mit full_join()
werden beide Datensätze anhand des
gemeinsamen ID vollständig student_id
kombiniert, ohne eine
Person (oder Zeile) zu verlieren. Es handelt sich somit um Studierende,
welche entweder offiziell als immatrikuliert gelistet sind und/oder sich
aktuell zu Prüfungen angemeldet haben (es handelt sich um eine logische
ODER Verknüpfung).
full_join(students, enrollments,
# mit welcher ID werden die Daten zugeordnet
by = "student_id",
# wie werden doppelte Spaltennamen benannt
suffix = c("_stud", "_enroll"))
Die Informationen werden sofern möglich zusammengeführt, wenn Werte
fehlen werden NA
Werte vergeben.
inner_join()
Mit inner_join()
werden beide Datensätze anhand des
gemeinsamen ID student_id
kombiniert. Es werden nur die
Zeilen (Studierende) zurückgegeben, bei denen die ID in beiden
Datensätzen vorhanden ist. Es handelt sich somit um Studierende, welche
offiziell als immatrikuliert gelistet sind und sich aktuell zu Prüfungen
angemeldet haben (es handelt sich um eine logische UND
Verknüpfung).
inner_join(students, enrollments,
# mit welcher ID werden die Daten zugeordnet
by = "student_id",
# wie werden doppelte Spaltennamen benannt
suffix = c("_stud", "_enroll"))
Die ID 2 (Sophie) wird zwei Mal angezeigt, da sie sich zu zwei Klausuren angemeldet hat.
left_join()
left_join()
liefert alle Zeilen aus dem linken Datensatz
(hier: “students”) und ergänzt, falls vorhanden, die zugehörigen
Informationen aus dem rechten Datensatz (hier:“enrollments”). Falls ein
Student keine Kursanmeldung hat (keinen Eintrag in “enrollments”
existiert), erscheinen in den entsprechenden Spalten
NA
.
right_join(students, enrollments, by = "student_id", suffix = c("_stud", "_enroll"))
Alle immatrikulierten Studenten werden angezeigt mit der Information
der angemeldeten Klausuren. Da Lukas und Mia keine Anmeldung in
“enrollments” haben, werden die fehlenden Informationen durch
NA
definiert. Studenten, die in mehreren Kursen angemeldet
sind (wie Sophie), erscheinen mehrfach. Beatrice wird nicht angezeigt,
da sie nicht offiziell immatrikuliert ist.
semi_join()
Diese Funktionen behält alle Zeilen (Personen) des ersten Datensatzes (“students”), welche sich auch im zweiten Datensatz befinden (“enrollments”) und es fügt zusätzliche Spalten des zweiten Datensatzes nicht hinzu.
Vorteile von semi_join()
Datenfilterung: Du erhältst einen klaren Überblick darüber, welche Einträge in beiden Datensätzen vorhanden sind, ohne die Daten durch zusätzliche Spalten zu erweitern.
Einfachheit: Da keine zusätzlichen Informationen aus dem rechten Datensatz übernommen werden, bleibt die Struktur des linken Datensatzes erhalten.
Effizienz: Perfekt geeignet, wenn du nur prüfen möchtest, ob ein Eintrag in einem zweiten Datensatz vorhanden ist.
semi_join(students, enrollments, by = "student_id")
Es werden alle immatrikulierten Studierenden angezeigt, welche sich auch zu einem Kurs angemeldet haben. Die Informationen des Kurses werden nicht zusätzlich mit angezeigt.
anti_join()
Gibt die Zeilen aus dem “enrollments”-Datensatz zurück, für die kein passender Eintrag im “students”-Datensatz gefunden wird.
anti_join(enrollments, students, by = "student_id", suffix = c("_stud", "_enroll"))
Hier handelt es sich um alle Personen (Zeilen), die sich zu einer Klausur angemeldet haben, obwohl sie nicht offiziell immatrikuliert sind.
Übungen
In unserem Beispiel gibt es zwei Datensätze:
df_id: Die Übersicht über alle registrierten Proband*innen
df_med: Die erhobenen medizinischen Daten von fast allen registrierten Proband*innen
In echte Studien kann es passieren, dass Probanden obwohl sie bereits für die Studie registriert sind nicht an der Studie teilnehmen und somit keine Daten vorliegen.
Datensatz “df_id”
Datensatz “df_med”
Aufgabe: Fülle den Datensatz “df_id” auf mit allen Infos aus “df_med”.
Tipp
*_join(df_id, df_med,____)
.
left_join(____)
.
by = "proband_id"
.
left_join(df_id, df_med, by = "proband_id")
anti_join(df_id, df_med, by = "proband_id")
# Lösung 1 ----
inner_join(df_id, df_med, by = "proband_id")
# Lösung 2 ----
# In diesem Beispiel kann man auch das machen
right_join(df_id, df_med, by = "proband_id")
semi_join(df_id, df_med, by = "proband_id")
bind Funktionen
Die bind-Funktionen ermöglichen es unkompliziert Datensätze miteinander zu verbinden. Die Daten können anhand der Spalten oder der Zeilen zusammengefügt werden.
Zeilen zusammenfügen mit bind_rows()
Mit bind_rows() kannst du mehrere Datensätze vertikal (also zeilenweise) zusammenführen. Dies ist nützlich, wenn du beispielsweise Daten aus verschiedenen Quellen hast, welche dieselbe Struktur haben (ähnliche oder identische Spalten) beinhalten, und diese in einen einzigen Datensatz integrieren möchtest.
# Datensatz 1
df1 <- data.frame(
id = c(1, 2),
name = c("Alice", "Bob"),
hobby = c( "sport", "baking")
)
df1
# Datensatz 2
df2 <- data.frame(
id = c(3, 4),
name = c("Claudia", "Dieter"),
allergy = c(TRUE, FALSE)
)
df2
# Zusammenführen der Zeilen
df <- bind_rows(df1, df2)
df
Alle Zeilen aus df1 und df2 werden untereinander angeordnet. Alle
Spalten werden übernommen. Fehlende Werte werden mit NA
aufgefüllt.
Spalten zusammenfügen mit bind_cols()
Mit bind_cols()
kannst du Data Frames spaltenweise (also
horizontal) zusammenführen. Dabei werden die Datensätze nebeneinander
angeordnet. Wichtig ist, dass die Datensätze dieselbe Anzahl an Zeilen
haben, da sonst die Zusammenführung nicht eindeutig erfolgt. Im besten
Fall gibt es auch keine Überlappung der Spaltennamen.
# Datensatz 1
df1 <- data.frame(
id = c(3, 4),
name = c("Claudia", "Dieter")
)
df1
# Datensatz 2
df2 <- data.frame(
hobby = c( "sport", "baking"),
allergy = c(TRUE, FALSE)
)
df2
# Zusammenführen der Spalten
df <- bind_cols(df1, df2)
df
Übungen
Aufgabe: Füge die zwei Datensätze zusammen mit einer bind-Funktion. Gebe den Datensatz aus.
Tipp
bind_rows()
.
Oder möchtest du neue Spalten ergänzen? Dann nutze
bind_cols()
.
# Datensatz 1
df_A <- data.frame(
id = c("x1", "x2"),
name = c("Alice", "Bob"),
hobby = c( "sport", "baking")
)
# Datensatz 2
df_B <- data.frame(
id = c("x3", "x4"),
name = c("Claudia", "Dieter"),
hobby = c( "dancing", "singing")
)
# Schreibe ab hier deinen Code:
# Datensatz 1
df_A <- data.frame(
id = c("x1", "x2"),
name = c("Alice", "Bob"),
hobby = c( "sport", "baking")
)
# Datensatz 2
df_B <- data.frame(
id = c("x3", "x4"),
name = c("Claudia", "Dieter"),
hobby = c( "dancing", "singing")
)
# Schreibe ab hier deinen Code:
df <- bind_rows(df_A, df_B)
df
Tipp
bind_rows()
.
Oder möchtest du neue Spalten ergänzen? Dann nutze
bind_cols()
.
# Datensatz 1
df_A <- data.frame(
sport_types = c("tennis", "dancing", "football"),
hobby_types = c( "sport", "cooking", "art")
)
# Datensatz 2
df_B <- data.frame(
animals = c("tiger", "bird", "snake"),
colors = c( "red", "green", "yellow")
)
# Schreibe ab hier deinen Code:
# Datensatz 1
df_A <- data.frame(
sport_types = c("tennis", "dancing", "football"),
hobby_types = c( "sport", "cooking", "art")
)
# Datensatz 2
df_B <- data.frame(
animals = c("tiger", "bird", "snake"),
colors = c( "red", "green", "yellow")
)
# Schreibe ab hier deinen Code:
df <- bind_cols(df_A, df_B)
df
tidyr – Pivoting
Kapitel
pivot_longer(): Umwandeln von
breiten in lange Datensätze
pivot_wider(): Umwandeln von
langen in breite Datensätze
pivot_longer(): Umwandeln von breiten in lange Datensätze
In diesem Tutorial lernst du, wie du einen Datensatz zum Thema
Säugetiere im Wide-Format mithilfe der Funktion
pivot_longer()
aus dem tidyr
-Paket in das
Long-Format überführst. Im Wide-Format werden verschiedene Messgrößen in
separaten Spalten dargestellt, während im long-Format jede Beobachtung
in einer eigenen Zeile erscheint. Das long-Format ist häufig vorteilhaft
für Visualisierungen (insbesondere auch ggplot2
).
Einfaches Beispiel
Zunächst erstellen wir einen Datensatz, in dem für jedes Tier das Gewicht in den Jahren 2010, 2020 und 2030 festgehalten wird.
# Beispieldatensatz im Wide-Format erstellen
mammals <- data.frame(
Animal = c("Elephant", "Lion", "Rhinoceros"),
Weight_2010 = c(4800, 180, 2200),
Weight_2020 = c(5000, 190, 2300),
Weight_2030 = c(5200, 195, 2400),
Weight_2040 = c(4900, 185, 2220)
)
Hier können wir uns den Datensatz anschauen:
Datensatz “mammals”
Umwandeln in das Long-Format mit
pivot_longer()
Wir transformieren nun den Datensatz, sodass die Spalten mit den Gewichtsangaben in zwei Spalten umgewandelt werden:
Year
: Enthält das Jahr (z. B. “2010”, “2020”, “2030”).Weight_kg
: Enthält das Gewicht des Tieres in Kilogramm.
mammals_long <- mammals %>%
pivot_longer(
cols = starts_with("Weight_"),
names_to = "Year",
values_to = "Weight_kg"
) %>%
# Entferne den Präfix "Weight_" aus der Year-Spalte
mutate(Year = sub("Weight_", "", Year))
mammals_long
Erklärungen:
pivot_longer()
: Diese Funktion “schmilzt” die Spalten, die mit “Weight_” beginnen, in zwei neue Spalten um.cols = starts_with("Weight_")
: Wählt alle Spalten aus, die nun umgewandelt werden sollen.names_to = "Year"
: Speichert die ursprünglichen Spaltennamen (z. B. “Weight_2010”) in der neuen Spalte Year.values_to = "Weight_kg"
: Legt den Namen der neuen Spalte fest, in der die Gewichtswerte abgelegt werden.mutate(Year = sub("Weight_", "", Year))
: Entfernt den Text “Weight_” aus den Werten in der Spalte Year, sodass nur noch das Jahr übrig bleibt.
Das Ergebnis ist ein Long-Format-Datensatz, in dem jede Zeile eine Gewichtsmessung eines Tieres in einem bestimmten Jahr darstellt. Der Datensatz ist nun länger geworden (hat mehr Zeilen) und schmaler (hat wneiger Spalten).
Komplexeres Beispiel
Jetzt schauen wir uns noch einen komplexeren Datensatz an bei dem es zwei verschiedene inhaltliche Spalten gibt (die Größe und das Gewicht).
Die Komplexität des Pivoting hängt maßgeblich von der Konsistenz der
Spaltennamen ab. Es wird dringend empfohlen, eine einheitliche Benennung
zu verwenden und klare Trennzeichen wie den Unterstrich _
einzusetzen. Falls die Spaltennamen inkonsistent sind, sollte dies vor
dem Pivoting idealerweise mit Hilfe von dplyr::rename()
bereinigt werden.
mammals_2 <- data.frame(
Animal = c("Elephant", "Lion", "Rhinoceros"),
Region = c("Africa", "Africa", "Asia"),
Weight_2010 = c(4800, 180, 2200),
Height_2010 = c(300, 120, 170),
Weight_2020 = c(5000, 190, 2300),
Height_2020 = c(320, 130, 180),
Weight_2030 = c(5200, 195, 2400),
Height_2030 = c(340, 135, 190)
)
Datensatz “mammals_2”
In diesem Beispiel enthält der Datensatz Informationen zu Gewicht und Höhe von Säugetieren in den Jahren 2010, 2020 und 2030 sowie Angaben zu Tierart und Region.
Wir fassen nun alle Spalten, außer Animal und Region, in zwei neue Spalten zusammen:
Measurement
: Enthält den Typ der Messgröße, z. B. “Weight” oder “Height”.Year
: Enthält das Jahr, das ursprünglich Teil des Spaltennamens war (z. B. “2030”).
Die eigentlichen Werte aus den ursprünglichen Spalten werden in der neuen Spalte “Value” abgelegt. Dadurch wird aus einer Vielzahl von Spalten (wie etwa “Height_2030”, “Weight_2010” usw.) ein einheitlicher Datensatz, in dem jede Zeile eine einzelne Messung eines Tieres darstellt.
mammals_long <- mammals_2 %>%
pivot_longer(
cols = -c(Animal, Region),
names_to = c("Measurement", "Year"),
names_sep = "_",
values_to = "Value"
)
mammals_long
Erklärung:
pivot_longer()
: Diese Funktion wandelt mehrere Spalten in zwei Spalten um.cols = -c(Animal, Region)
: Alle Spalten außer Animal und Region werden transformiert.names_to = c("Measurement", "Year")
: Die ursprünglichen Spaltennamen werden in zwei Teile aufgespalten – den Messwert-Typ und das Jahr.names_sep = "_"
: Der Unterstrich (“_“) dient als Trennzeichen für die Spaltennamen und deren Separierung.values_to = "Value"
: Die Messwerte werden in der neuen Spalte Value gespeichert.
Übungen
In diesem Beispiel hast du einen Datensatz über Früchte, der die Anzahl verkaufter Früchte in verschiedenen Monaten enthält.
fruits_wide <- data.frame(
fruit = c("appel", "banana", "orange"),
sales_jan = c(120, 150, 100),
sales_feb = c(140, 170, 110),
sales_mar = c(130, 160, 105)
)
Aufgabe: Verwandle den
Datensatz “fruits_wide” ins Long-Format, sodass du eine Spalte
Monat
erhältst (mit den Werten “Jan”, “Feb”, “Mar”) und
eine Spalte Verkauf
für die Verkaufszahlen.
Tipp
pivot_longer()
.
names_to
und values_to
verwenden, um
passende Spaltennamen zu erzeugen.
mutate(Monat = sub(___))
innerhalb von um den Präfix
„Sales_“ aus den Monatsnamen zu entfernen.
fruits_long <- fruits_wide %>%
pivot_longer(
cols = starts_with("sales_"),
names_to = "month",
values_to = "sales"
) %>%
mutate(month = sub("sales_", "", month))
fruits_long
pivot_wider(): Umwandeln von langen in breite Datensätze
In diesem Tutorial lernst du, wie du einen Datensatz zum Thema
Pflanzenwachstum im Long-Format mithilfe der Funktion
pivot_wider()
aus dem tidyr
-Paket in das
Wide-Format überführst. Im Long-Format erscheinen Beobachtungen in
einzelnen Zeilen, während das Wide-Format verschiedene Messgrößen oder
Beobachtungszeitpunkte in separaten Spalten darstellt. Dieses Format
kann vorteilhaft für bestimmte Datenanalysen und Tabellenübersichten
sein.
Einfaches Beispiel
Zunächst schauen wir uns den Datensatz plants_long
an,
der für verschiedene Pflanzenarten Höhenmessungen in verschiedenen
Jahren enthält:
plants_long <- data.frame(
Species = rep(c("Oak", "Pine", "Birch"), each = 3),
Year = rep(c("2010", "2020", "2030"), times = 3),
Height_cm = c(150, 180, 210, 200, 250, 290, 170, 210, 240)
)
Datensatz “plants_long”
Umwandeln in das Wide-Format mit pivot_wider()
Wir transformieren nun den Datensatz, sodass jedes Jahr eine eigene Spalte bildet:
2010
,2020
,2030
: Enthalten die gemessenen Pflanzenhöhen je Jahr.
plants_wide <- plants_long %>%
pivot_wider(
names_from = Year,
names_prefix = "height_cm_",
values_from = Height_cm
)
plants_wide
Erklärungen:
pivot_wider()
: Wandelt die Werte aus der Spalte Year in separate Spaltennamen um.names_from = Year
: Definiert, welche Spalte als neue Spaltenüberschrift verwendet wird.names_prefix = "height_cm_"
: Die Spaltennamen werden noch mit dem Prefix “height_cm_” ausgestattet.values_from = Height_cm
: Definiert, welche Werte in den neuen Spalten erscheinen.
Der Datensatz ist nun im wide-Format: der Datensatz ist breiter geworden (mehr Spalten) und weniger lang (weniger Zeilen).
Komplexeres Beispiel
Wir betrachten nun einen Datensatz, der neben der Höhe auch die Blattgröße verschiedener Pflanzen in unterschiedlichen Jahren enthält:
plants_long_2 <- data.frame(
Species = rep(c("Oak", "Pine", "Birch"), each = 6),
Year = rep(c("2010", "2020", "2030"), each = 2, times = 3),
Measurement = rep(c("Height_cm", "Leaf_cm"), times = 9),
Value = c(150, 10, 180, 12, 210, 14, 200, 8, 250, 9, 290, 11, 170, 7, 210, 8, 240, 9)
)
Datensatz “plants_long_2”
Dieser Datensatz enthält zwei Messgrößen pro Pflanze und Jahr: Höhe
(Height_cm
) und Blattgröße (Leaf_cm
).
Jetzt wandeln wir diesen komplexeren Datensatz um, sodass jede Kombination aus Jahr und Messgröße eine eigene Spalte bildet:
plants_wide_2 <- plants_long_2 %>%
pivot_wider(
names_from = c(Measurement, Year),
values_from = Value
)
plants_wide_2
Erklärung:
names_from = c(Measurement, Year)
: Hier werden die neuen Spalten aus der Kombination von Messgrößen (Measurement) und Jahren (Year) gebildet.values_from = Value
: Die Werte der neuen Spalten stammen aus der ursprünglichen Spalte “Value”.
Übungen
In diesem Beispiel erhältst du einen Datensatz über Niederschlagsmengen, gemessen in zwei Städten (Berlin und Hamburg) über drei Monate.
rainfall_long <- data.frame(
city = rep(c("Berlin", "Hamburg"), each = 3),
month = rep(c("january", "february", "march"), times = 2),
rainfall_mm = c(35, 30, 40, 50, 45, 55)
)
Aufgabe: Wandle den Datensatz “rainfall_long” ins Wide-Format um, sodass jeder Monat eine eigene Spalte bildet, welche die Niederschlagswerte enthält.
Tipp
pivot_wider()
.
Niederschlag_mm
übernommen werden.
names_to =
und values_to =
.
rainfall_wide <- rainfall_long %>%
pivot_wider(
names_from = month,
values_from = rainfall_mm
)
rainfall_wide
Datenaufbereitung – Übungen
Datensatz
Wir haben hier einen Datensatz, der Informationen über Studierende sowie ihre Testergebnisse aus verschiedenen Semestern enthält:
Hier können wir uns den Datensatz anschauen, den wir bearbeiten möchten:
students_data
Datenaufbereitung dplyr
Aufgabe: Erstelle eine dplyr Pipe und überschreibe den Datensatz:
1. Wähle nur die ID, den Namen, den Kurs und die Testergebnisse aller Semester aus (aber keine Anwesenheitswerte).
2. Benenne die Spalte “stud_id” in “student_id” um.
4. Überschreibe die Inhalte in “student_id”: die Spalte sollen den Datentyp “numeric” haben.
5. Danach filtere alle Studierenden heraus, die im Fach “Physics” sind.
Zeige dir den Datensatz am Ende an.
Tipp
students_data
zu.
select()
.
rename()
.
mutate()
.
filter()
.
df <- students_data %>% select() %>% rename() %>% mutate() %>% filter() df
students_data <- students_data %>%
select(stud_id, name, course, starts_with("test_")) %>%
rename(student_id = stud_id) %>%
mutate(student_id) %>%
filter(course != "Physics")
students_data
Pivoting
Hier können wir uns den Datensatz anschauen, den wir bearbeiten möchten:
students_data
Aufgabe: Verwandle den Datensatz “students_data” so, dass eine Spalte “Semester” entsteht (mit Werten “Sem1”, “Sem2”, “Sem3”) und eine Spalte “Test_Score”, in der die Testergebnisse gespeichert sind.
Tipp
students_data
zu.
pivot_longer()
.
_
sinnvoll.
students_data <- students_data %>%
pivot_longer(
cols = starts_with("test_"),
names_to = c(".value", "Semester"),
names_sep = "_"
)
students_data
ggplot2 – Plot erstellen
Hinweis: Die Übungen zu dem Kapitel sind im Kapitel Visualisierungen – Übungen
Kapitel
Ideen & Literaturfull_join
Data Mapping
Plot erstellen
Entscheidungshilfe für Plottypen
geom_point()
geom_line()
Weitere Linien
geom_smooth()
geom_bar()
geom_col()
geom_histogram()
geom_boxplot()
geom_violin()
geom_density()
geom_area()
geom_ribbon()
geom_jitter()
ggplot2
ist ein sehr mächtiges Werkzeug zur
Visualisierung von Daten.
Eine Visualisierung, also ein Bild, wird nachfolgend “Plot” genannt
in Anlehnung an die englische Bezeichnung. Die nachfolgende Struktur ist
wichtig um einen Plot zu programmieren mit ggplot2
Schritte zur Erstellung eines Plots:
die Daten müssen definiert werden
Spalten im Datensatz müssen zu grafischen Elementen zugeordnet werden (x-Achse, y-Achse, Farben, Größen, …)
der Diagrammtyp muss festgelegt werden (Histogramm, Liniendiagramm, Balkendiagramm, Streudiagramm, …)
das Diagramm kann individuell angepasst werden (Themen/Designs, zusätzliche Linien, Größen, Farben, Beschriftungen, …)
Ideen & Literatur
Weiterführende Literatur: ggplot2 Buch
Plot Galerie zur Inspiration:
Kunst Galerie von Danielle Navarro
Beispieldatensatz
set.seed(3)
N = 140
df <- sprtt::draw_sample_normal(2, f = 0.25, max_n = N/2, sd = c(0.8, 1.2), sample_ratio = c(1,1))
df <- df %>%
mutate(
group = factor(x, levels = c(1,2), labels = c("treatment", "placebo")),
sex = sample(c("female", "male", "unknown"), size = N, replace = TRUE, prob = c(0.50, 0.40, 0.10)),
age = sample(33:67, size = N, replace = TRUE),
life_satisfaction = round(10 + y*-0.9 + rnorm(N, sd = 3)),
depression = round(20 + life_satisfaction*-0.6 + rnorm(N, sd = 3.2)),
intelligence = round(rnorm(N, 100, 10)),
finances = cumsum(rnorm(N, 0.1, 1)) + 100
) %>%
select(-x)
Hier können wir uns den Datensatz anschauen, den wir visualisieren
möchten: Der Datensatz befindet sich bereits im long-Format und das ist
besonders hilfreich für ggplot2
.
Fall der Datensatz in einem anderen Format vorliegt, müssten wir zunächst den Datensatz umstrukturieren. Siehe dazu das Otter Kapitel tidyr - Pivoting.
df
Piping mit +
Man kann die einzelnen Ebenen eines Plots auch pipen - es wird hier
jedoch das +
Symbol genutzt und nicht wie wir es von
dplyr
kennen %>%
.
df %>% # die Daten werden mit der dplyr Pipe übergeben
filter() %>%
ggplot() + # aber hier wird in die + Pipe gewechselt
geom_point() +
theme_bw()
In diesem Beispiel werden die Daten zunächst mit dplyr
gepiped und noch gefiltert. Nach dem die ggplot()
Funktion
aufgerufen wurde wird zu +
gewechselt. Danach wird jede
Ebene desd Plots mit +
hinzugefügt.
Data Mapping
Der erste Schritt ist es die Daten zu definieren und auf die Bildachsen (x-Achse, y-Achse) zu “mappen” (zuzuweisen). Der Plot bleibt damit erst mal noch leer - wir legen hier aber bereits sehr wichtige Grundlagen des Plots fest.
Leere Plots erstellen
Hier ist ein Beispiel bei dem ein leerer Plot erstellt wird und die Daten zu den Achsen zugeordnet werden. Alle Funktionsargumente werden hier ausgeschrieben. Dies ist eine sehr ausführliche Schreibweise. Es hilft für das Verständnis es sich so zu merken.ggplot(
data = df, # die Daten werden als Argument übergeben
mapping = aes(x = group, y = life_satisfaction)
)
df %>% # die Daten werden mit dplyr übergeben
ggplot(
aes(x = group, y = life_satisfaction)
)
Oft wird mit dplyr
davor der Datensatz noch angepasst
und direkt an ggplot2
übergeben.
df %>%
filter(sex == "female") %>%
ggplot(
aes(x = group, y = life_satisfaction)
)
+
gepiped in der das Design (Theme genannt) angepasst wird.
ggplot(
data = df,
mapping = aes(x = group, y = life_satisfaction)
) +
theme_bw(base_size = 20)
aesthetics
Wir haben bereits in den oberen Beispielen gesehen, dass die Funktion
aes()
genutzt wird, um die x- und y-Achsen zu definieren.
aes()
ist die Kurzschreibweise für
aesthetics()
. Hier wird definiert, welche Spalten im
Datensatz zu welchen Elementen im Plot zugeordnet werden sollen.
ggplot(
df,
aes(x = group, # x-Achse
y = intelligence, # y-Achse
colour = age, # Umrandungsfarbe
shape = age, # Form (Kreis, Viereck, ...)
size = age, # Größe der Formen
fill = age, # Füllfarbe
alpha = age, # Transparenz
group = age, # Gruppenzuordnung
linetype = age, # Linienart
linewidth = age # Liniendicke
)
)
x- und oder y-Achse sind Pflichtargumente, um ein Plot erstellen zu
können. Damit können die Daten von 1 bis 2 Variablen (Spalten im
Datensatz) innerhalb eines Plots visualisiert werden. Die anderen
Argumente wie color
oder shape
ermöglichen es
noch komplexere Zusammenhänge mit weiteren Variablen darzustellen.
Hinweis: Man kann visuell auch mehrere aes-Argumente
nutzen für dieselbe Variable. So werden z.B. color
und
shape
oft gleichzeitig einer Variable zugeordnet. Dies hat
den Vorteil, dass man auch als schwarz-weiß-Grafik die Gruppen
unterscheiden kann.
df %>%
ggplot(
aes(x = group,
y = life_satisfaction,
colour = sex,
shape = sex)
)
Im nächsten Schritt wählen wir die Art des Plots aus - damit können wir dann auch endlich unsere Daten sehen!
Plot erstellen
Die Art des Plots wird bestimmt durch die
geom_*
-Funktion also z. B. geom_point
.
Hier ist eine Übersicht über die verschiedenen
Geom-Funktion | Diagrammtyp | Beschreibung (Deutsch) |
---|---|---|
geom_point() |
Punktdiagramm (Scatterplot) | Zeigt einzelne Datenpunkte – ideal für Zusammenhänge/Verteilungen. |
geom_line() |
Liniendiagramm | Verbindet Datenpunkte mit Linien – gut für zeitliche Entwicklungen. |
geom_smooth() |
Trendlinie | Fügt eine geglättete Linie (z. B. LOESS oder linear) hinzu. |
geom_bar() |
Balkendiagramm (zähle) | Erzeugt Balken basierend auf Häufigkeiten. |
geom_col() |
Balkendiagramm (direkt) | Wie geom_bar() , aber mit vorgegebenen y-Werten. |
geom_histogram() |
Histogramm | Zeigt die Verteilung numerischer Daten (automatisch gebinnt). |
geom_boxplot() |
Boxplot | Visualisiert Median, Quartile, Ausreißer. |
geom_violin() |
Violinplot | Kombination aus Boxplot & Dichtekurve. |
geom_density() |
Dichtekurve | Schätzung der Wahrscheinlichkeitsdichte einer Verteilung. |
geom_area() |
Flächendiagramm | Wie Liniendiagramm, aber mit Fläche unter der Linie gefüllt. |
geom_ribbon() |
Banddiagramm | Zeigt Unsicherheitsbereiche (z. B. Konfidenzintervalle). |
geom_tile() |
Heatmap | Farbige Kacheln – gut für z. B. Korrelationsmatrizen. |
geom_hex() |
Hexbin-Diagramm | Alternative zu Scatterplot mit großen Datenmengen. |
geom_jitter() |
Streudiagramm mit Jitter | Punktdiagramm mit zufälligem Versatz gegen Überlagerung. |
geom_text() |
Textbeschriftungen | Fügt Textlabels an Datenpunkten hinzu. |
geom_label() |
Beschriftungen mit Rahmen | Wie geom_text() , aber mit Hintergrundrahmen. |
geom_segment() |
Liniensegmente | Zeichnet Linien zwischen zwei Koordinaten. |
geom_abline() |
Gerade (Achsenabschnitt + Steigung) | Fügt einfache Gerade (z. B. Regressionslinie) hinzu. |
geom_vline() |
Vertikale Linie | Fügt eine vertikale Linie bei einem festen x-Wert ein. |
geom_hline() |
Horizontale Linie | Fügt eine horizontale Linie bei einem festen y-Wert ein. |
geom_path() |
Pfad | Wie geom_line() , aber beachtet die Reihenfolge der
Punkte. |
geom_polygon() |
Polygon | Zeichnet geschlossene Formen – z. B. Karten. |
geom_map() |
Kartendarstellung | Für geografische Karten mit Regionen. |
geom_sf() |
Simple Features (Karten) | Für räumliche Daten im sf -Format. |
Entscheidungshilfe für Plottypen
Anzahl Variablen | Art der Variable(n) | Empfohlene Plots |
---|---|---|
1 | kontinuierlich | Histogramm, Dichtekurve (geom_histogram ,
geom_density ) |
1 | kategorisch (diskret) | Balkendiagramm (geom_bar ) |
2 | kontinuierlich & kontinuierlich | Punktdiagramm, Liniendiagramm (geom_point ,
geom_line ) |
2 | kategorisch & kontinuierlich | Boxplot, Violinplot, Jitter (geom_boxplot ,
geom_violin , geom_jitter ) |
2 | kategorisch & kategorisch | Gruppierte Balken, Mosaikplots |
>2 | Kombinationen | Zusätzliche Variablen über Farbe, Form, Facets |
geom_point()
Ein Punktdiagramm (Scatterplot) stellt zwei kontinuierliche Variablen gegenüber. Es eignet sich gut zur Darstellung von Zusammenhängen oder Korrelationen.
ggplot(df, aes(x = life_satisfaction, y = depression)) +
geom_point()

Wichtige Argumente:
alpha
für Transparenzcolor
,size
,shape
zur Darstellung von Zusatzinformationen
ggplot(df,
aes(x = life_satisfaction,
y = depression,
colour = sex,
shape = sex)) +
geom_point()

geom_line()
Ein Liniendiagramm verbindet Datenpunkte entsprechend ihrer Reihenfolge. Besonders nützlich für Zeitreihen oder Trends über eine kontinuierliche Achse.
ggplot(df, aes(x = 1:nrow(df), y = finances)) +
geom_line()

Wichtige Argumente:
color
,linetype
,size
ggplot(df,
aes(x = 1:nrow(df),
y = finances,
colour = sex)) +
geom_line()

Weitere Linien
Es gibt verschiedene Möglichkeiten Linien zu einem Plot hinzuzufügen:
geom_abline
: diagonale Linien mit einer Steigung (slope) und einem y-Achsenabschnitt (intercept)geom_hline
: horizontale Linien mit einem y-Achsenabschnittgeom_vline
: vertikale Linien mit einem x-Achsenabschnitt
ggplot(df, aes(x = life_satisfaction, y = depression)) +
geom_point() +
geom_abline(intercept = 0, slope = 1, color = "red")

ggplot(df, aes(x = life_satisfaction, y = depression)) +
geom_point() +
geom_hline(yintercept = mean(df$depression), color = "blue")

ggplot(df, aes(x = life_satisfaction, y = depression)) +
geom_point() +
geom_vline(xintercept = mean(df$depression), color = "green")

geom_smooth()
Zeichnet eine geglättete Linie zur Darstellung von Trends. Standardmäßig wird eine LOESS-Kurve oder lineares Modell gewählt
ggplot(df, aes(x = 1:nrow(df), y = finances)) +
geom_smooth()
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Wichtige Argumente:
method = "lm"
wählt die smoothing-Funktion aus, hier z.B. lineares Modellformula = "y ~ x"
Formel, welche das Modell definiertse = TRUE
zum Aktivieren/Deaktivieren des Konfidenzbandes
ggplot(df,aes(x = 1:nrow(df), y = finances)) +
geom_smooth(method = "lm",
formula = "y ~ x",
se = TRUE)

geom_bar()
Ein Balkendiagramm zählt Häufigkeiten einer kategorialen Variable.
ggplot(df, aes(x = sex)) +
geom_bar()

Wichtige Argumente:
fill
,color
zur Gestaltungposition = "dodge"
für gruppierte Balken
ggplot(df, aes(x = sex, fill = group)) +
geom_bar()

ggplot(df, aes(x = sex, fill = group)) +
geom_bar(position = "dodge")

geom_col()
Im Gegensatz zu geom_bar()
verwendet
geom_col()
vorgegebene y-Werte (z. B. Mittelwerte). Ideal
für gruppierte oder aggregierte Daten.
# Daten werden aufbereitet
df %>%
group_by(group) %>%
summarise(mean_dep = mean(depression)) %>%
# Plot
ggplot(aes(x = group, y = mean_dep)) +
geom_col()

Wichtige Argumente:
fill
,width
,position
zur Gestaltung
# Daten werden aufbereitet
df %>%
group_by(group) %>%
summarise(mean_dep = mean(depression)) %>%
# Plot
ggplot(aes(x = group, y = mean_dep, fill = group)) +
geom_col(width = 0.5)

geom_histogram()
Ein Histogramm visualisiert die Verteilung einer kontinuierlichen Variable durch Bins.
ggplot(df, aes(x = depression)) +
geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Wichtige Argumente:
bins
oderbinwidth
zum Steuern der Auflösungfill
,color
zur Gestaltung
ggplot(df, aes(x = depression, group = sex, fill = sex)) +
geom_histogram(bins = 20)

geom_boxplot()
Boxplots zeigen Median, Quartile, und Ausreißer – sehr nützlich zum Vergleich von Gruppen.
ggplot(df, aes(x = group, y = depression)) +
geom_boxplot()

Wichtige Argumente:
notch = TRUE
für Konfidenzintervall des Mediansoutlier.shape
,outlier.colour
ggplot(df, aes(x = group, y = depression, fill = sex)) +
geom_boxplot(notch = TRUE)
Notch went outside hinges
ℹ Do you want `notch = FALSE`?
Notch went outside hinges
ℹ Do you want `notch = FALSE`?

geom_violin()
Eine Kombination aus Boxplot und Dichtekurve – stellt die Verteilung je Gruppe anschaulich dar.
ggplot(df, aes(x = group, y = depression)) +
geom_violin()

Wichtige Argumente:
draw_quantiles
zum Einzeichnen bestimmter Quantilescale = "count"
zeigt die Fläche der Violins in Abhängigkeit der Beobachtungen
ggplot(df, aes(x = group, y = depression, fill = sex)) +
geom_violin(scale = "count",
draw_quantiles = c(0.25, 0.5, 0.75))

geom_density()
Zeigt die geschätzte Dichteverteilung einer kontinuierlichen Variable – alternative zum Histogramm.
Wichtige Argumente:
adjust
beeinflusst die Glättungalpha
für Transparenz bei Gruppenvergleichen
ggplot(df, aes(x = depression, fill = group)) +
geom_density(alpha = 0.5)

geom_area()
Stellt eine kumulative Fläche unter einer Linie dar – ideal für Verläufe mit Gesamtmengen.
# Daten werden aufbereitet
df %>%
count(life_satisfaction) %>%
arrange(life_satisfaction) %>%
# Plot
ggplot(aes(x = life_satisfaction, y = n)) +
geom_area()

geom_ribbon()
Zeigt Konfidenz- oder Streubereiche um eine Linie. Benötigt untere
(ymin
) und obere (ymax
) Grenze.
# Daten werden aufbereitet
df %>%
group_by(life_satisfaction) %>%
summarise(m = mean(depression), sd = sd(depression)) %>%
mutate(ymin = m - sd, ymax = m + sd) %>%
# Plot
ggplot(aes(x = life_satisfaction, y = m)) +
geom_ribbon(aes(ymin = ymin, ymax = ymax), alpha = 0.3) +
geom_line()

ggplot2 – Plot anpassen
Hinweis: Die Übungen zu dem Kapitel sind im Kapitel Visualisierungen – Übungen
Beschriftungen
Sehr häufig möchte man Beschriftungen hinzufügen.
mit labs()
kann man die üblichen Textbereiche
beshriften: Titel, Achsen-Beschriftungen und weiteres.
Manchmal möchte man im Plot selbst Beschriftungen hinzufügen und
deren Position selbst bestimmen - dazu eignet sich
geom_text()
. Eine Umrandung des Textes kann man einfach mit
geom_label()
umsetzen.
labs()
df %>%
ggplot(
aes(x = group,
y = depression,
fill = sex)
) +
geom_boxplot() +
labs(
title = "Title of the plot",
subtitle = "Subtitle of the plot",
x = "x axis",
y = "y axis",
tag = "tag",
caption = "This is the caption",
alt = "plot description" # für Screenreader
)

geom_text()
Fügt numerische oder kategoriale Labels an Positionen hinzu – z. B. zur Beschriftung von Mittelwerten oder Punkten.
Wichtige Argumente:
vjust
,hjust
für die Platzierungcheck_overlap = TRUE
vermeidet Überschneidungen
# Daten werden aufbereitet
df_avg <- df %>% group_by(group) %>% summarise(mean_dep = mean(depression))
# Plot
ggplot(df, aes(x = group, y = depression)) +
geom_boxplot() +
geom_text(
data = df_avg, # anderer Datensatz wird ausgewählt
# die Position wird in Abhängigkeit des
# Mittelwertes gesetzt
aes(x = group,
y = mean_dep + 1,
label = round(mean_dep, 1)),
color = "red")

geom_label()
Ist wie geom_text()
, aber mit Rahmen um den Text. Dies
ist nützlich für deutlichere Hervorhebung.
Wichtige Argumente:
label.size
,label.r
(Eckenradius)fill
,color
zur Gestaltung
# Daten werden aufbereitet
df_medians <- df %>% group_by(group) %>% summarise(median_dep = median(depression))
# Plot
ggplot(df, aes(x = group, y = depression)) +
geom_violin(fill = "lightblue") +
geom_label(data = df_medians,
aes(x = group,
y = median_dep,
label = paste0("Median: ", median_dep)))

Themes (Designs)
Das Standarddesign von ggplot2
wird nicht unbedingt als
schön wahrgenommen.😉 Zum Glück geht es ganz schnell ein neues Design
auszuwählen.
Hier ist eine Übersicht über die vorgefertigten Themes.
Wenn dir diese Themes nicht ganz gefallen gefallen, nutze
theme()
, um Details zu ändern.
In diesem Beispiel kannst du gerne mal verschiedene Themes
ausprobieren! Ändere einfach den Code und führe es aus. Als erstes
Beispiel ist das Design theme_bw()
ausgewählt.
ggplot(df, aes(x = group, y = depression)) +
geom_boxplot() +
theme_bw() # hier wird das Theme ausgewählt

Schriftgröße
Die Schriftgröße (base_size
) ist ein Argument innerhalb
der Theme-Funktionen. Damit kann man schnell alle Schriften vergrößern
oder verkleinern.
ggplot(df, aes(x = group, y = depression)) +
geom_violin(fill = "lightblue") +
# Beschriftungen
labs(x = "Behandlung",
y = "Depression",
title = "Wirksamkeit der Behandlung") +
# Anpassung des Designs und der Schriftgröße
theme_bw(base_size = 20)

Farbskalen
Wenn das Argumnet aes(fill = variable)
genutzt wird,
werden automatisch Farben vergeben. Diese Farben gefallen auch nicht
jedem und können ausgetauscht werden mit anderen Farbskalen.
Es gibt sehr viele Farbpakete in R, wir stellen hier eins der
bekanntesten vor: viridis
Das viridis
R Paket enthält diese Skalen.
Abhängig davon, ob deine Variable dem fill oder dem color Argument zugeordnet ist oder ob deine Variable kontinuierlich oder diskret ist, musst du eine andere Funktion wählen.
Achtung: der letzte Buchstabe des Funktionsnamens
bestimmt, ob es sich um d
eine diskrete oder um
c
eine kontinuierliche Funktion handelt:
color:
- diskret:
scale_color_viridis_d()
- kontinuierlich:
scale_color_viridis_c()
fill:
- diskret:
scale_fill_viridis_d()
Hier ist ein Beispiel mit den Standardeinstellungen:
ggplot(df, aes(x = group, y = depression, fill = sex)) +
geom_boxplot() +
scale_fill_viridis_d()

Die dunkle Farbe ist doch sehr dunkel, wir können nun durch weitere Argumente bestimmen, wo die Farbskala beginnen und enden soll.
Hier wird der Beginn der Skala mehr in die Mitte verschoben, damit wird die Farbe ein wenig heller.
ggplot(df, aes(x = group, y = depression, fill = sex)) +
geom_boxplot() +
scale_fill_viridis_d(
option = "viridis",
begin = 0.3, # 0 ist der Standard
end = 1 # 1 ist der Standard
)

Probiere hier gerne andere Werte für end
und
begin
aus und schaue, wie sich die Farben verändern!
Hier haben wir noch eine andere Skala inferno
:
ggplot(df, aes(x = group, y = depression, fill = sex)) +
geom_boxplot() +
scale_fill_viridis_d(
option = "inferno",
begin = 0.5, # 0 ist der Standard
end = 1 # 1 ist der Standard
)

Farben
Hier ist eine Übersicht über alle Standardfarben, die es in R gibt!
Damit kann man auch eigene Farbskalen festlegen! Nutze dafür die
Funktionen scale_color_manual
oder
scale_fill_manual
.
ggplot(df, aes(x = group,
y = depression,
fill = sex)) + # wir definieren fill
geom_boxplot() +
# wir müssen fill auswählen
scale_fill_manual(values = c("female" = "firebrick", "male" = "gold", "unknown" = "darkcyan"))

Plot speichern
Mit ggsave()
können wir ganz einfach unsere Plots
speichern. Die Funktion speichert immer das zuletzt erstellte Plot.
Wichtig ist den Pfad anzugeben, wo das Bild gespeichert werden soll - dabei muss auch der Dateiname vollständig angegeben werden.
# Erstellung des Plots
ggplot(df, aes(x = group, y = depression, fill = sex)) +
geom_boxplot()
# Speicherung
ggsave(
"images/plot_example.png",
width = 35,
height = 30,
units = "cm",
dpi = 300 # Auflösung
)
Praktisch ist auch, dass man ganz leicht die Größe und die Auflösung des Plots bestimmen kann.
Visualisierungen – Übungen
Datensatz
Hier können wir uns den Datensatz anschauen, den wir visualisieren möchten:
df
Boxplots
Wir möchten die Gesundheit der Tiere (health_value
)
darstellen.
Aufgabe 1: Versuche den angezeigten Plot so ähnlich wie möglich nachzubauen.
Tipp
geom_boxplot()
.
aes(y=health_value)
.
ggplot(df, aes()) + geom_boxplot().
ggplot(df, aes(y = health_value)) +
geom_boxplot()
Aufgabe 2: Versuche den angezeigten Plot so ähnlich wie möglich nachzubauen.
Tipp
geom_boxplot()
.
aes(y=health_value)
.
labs()
.
ggplot(df, aes()) + geom_boxplot() + theme_classic()`.
ggplot(df, aes(y = health_value)) +
geom_boxplot() +
labs(title = "Heath of the animals",
y = "health score") +
theme_classic(base_size = 14)
Aufgabe 3: Versuche den angezeigten Plot so ähnlich wie möglich nachzubauen.
Tipp
geom_boxplot()
.
animals
für x
und auch für
fill
.
labs()
.
scale_fill_viridis_d()
für die Farben.
ggplot(df, aes()) + geom_boxplot() + labs() + scale_fill_viridis_d() + theme_classic()`.
ggplot(df, aes(x = animals, y = health_value, fill = animals)) +
geom_boxplot() +
labs(title = "Heath of the animals",
y = "health score") +
scale_fill_viridis_d(
option = "viridis",
begin = 0.3
) +
theme_classic(base_size = 14)
Balkendiagramme
Wie sieht die Verteilung der Trainings aus? Dabei interessiert uns auch die Geschlechterverteilung.
Aufgabe 1: Versuche den angezeigten Plot so ähnlich wie möglich nachzubauen.
Tipp
geom_bar()
.
labs()
.
scale_fill_viridis_d()
für die Farben.
ggplot(df, aes()) + geom_bar() + theme_light()`.
df %>%
mutate(training = as.factor(training)) %>%
ggplot(aes(x = training)) +
geom_bar() +
theme_light(base_size = 12)
Wir möchten den Plot noch ein wenig schöner machen und auch noch das Geschlecht als Variable hinzunehmen.
Aufgabe 2: Versuche den angezeigten Plot so ähnlich wie möglich nachzubauen.
Tipp
geom_bar()
.
labs()
.
scale_fill_viridis_d()
für die Farben.
ggplot(df, aes()) + geom_bar() + labs() + scale_fill_viridis_d() + theme_light()`.
df %>%
mutate(training = as.factor(training)) %>%
ggplot(aes(x = training, fill = sex)) +
geom_bar() +
labs(title = "Distribution of the training") +
scale_fill_viridis_d(
option = "inferno",
begin = 0.2
) +
theme_light(base_size = 12)
Punkte & Linien
Wir möchten nun sehen, wie die Gesundheit (health_value
)
mit dem Blutwert (blood_value
) zusammenhängt. Dabei soll
auch direkt das Geschlecht noch berücksichtigt werden.
Aufgabe 1: Versuche den angezeigten Plot so ähnlich wie möglich nachzubauen.
Tipp
geom_point()
.
color
und nicht fill
für das
Geschlecht
labs()
.
scale_color_viridis_d()
für die Farben. Achte hierbei
auf color
.
ggplot(df, aes()) + geom_bar() + labs() + scale_color_viridis_d() + theme_classic()`.
ggplot(df, aes(x = health_value, y = blood_value, color = sex)) +
geom_point() +
labs(title = "Distribution of the training") +
scale_color_viridis_d(
option = "inferno",
begin = 0.2,
end = 0.8
) +
theme_classic(base_size = 12)
In einem nächsten Schritt möchten wir noch lineare Modelle fitten und als Linien für die Geschlechter getrennt hinzufügen. Damit es übersichtlicher wird, entfernen wir alle Datenpunkte, bei denen das Geschlecht unbekannt ist.
Aufgabe 2: Versuche den angezeigten Plot so ähnlich wie möglich nachzubauen.
Tipp
geom_point()
.
filter()
, um die Daten zu ändern.
geom_smooth()
.
geom_smooth(method = "lm")
.
color
und nicht fill
für das
Geschlecht
labs()
.
scale_color_viridis_d()
für die Farben. Achte hierbei
auf color
.
ggplot(df, aes()) + geom_point() + geom_smooth() + labs() + scale_color_viridis_d() + theme_classic()`.
df %>%
filter(sex != "unknown") %>%
ggplot(aes(x = health_value, y = blood_value, color = sex)) +
geom_point() +
geom_smooth(method = "lm",
formula = "y ~ x",
se = TRUE) +
labs(title = "Distribution of the training") +
scale_color_viridis_d(
option = "inferno",
begin = 0.4,
end = 0.8
) +
theme_classic(base_size = 12)