Programmierung für
Data Science

Vorlesung 2

Prof. Dr. Michael Bücker

0.1 Heute arbeiten wir so

  • Grundlage heute: vor allem Datenmanipulation und Visualisierung aus dem Skript.
  • Rhythmus je Block: Orientierung, zwei Kurzübungen, Arbeitsphase, Besprechung.
  • In den Kurzübungen klären wir Syntax, Stolperstellen und saubere Arbeitsabläufe gemeinsam.
  • In den Arbeitsphasen arbeiten Sie selbstständig oder in kleinen Teams.
  • Ziel für heute: Daten einlesen, prüfen, transformieren, visualisieren und knapp in Quarto dokumentieren.

Heute zuerst ohne KI

Lösen Sie die Aufgaben heute zunächst ohne KI. Für Daten, Pakete und Fehlermeldungen ist der eigene Diagnoseweg im Moment wichtiger.

0.2 Tagesstruktur

Zeit Inhalt
08:30 - 10:00 Import und erste Einblicke: Textdateien lesen, RStudio-Import, str, head, summary
Orientierung, zwei Kurzübungen, Arbeitsphase mit CSV-Datei
10:15 - 11:45 Data Wrangling I: Tidy Data, filter, select, mutate, summarise
gemeinsamer Einstieg, dann größere Datenaufgabe
12:30 - 14:00 Data Wrangling II, Joins und Datenbanken: Pipes, group_by, left_join, DBI, dbplyr, fifa23
Join-Kurzübungen, dann Arbeitsphasen mit fifa23 und videogames
14:15 - 15:45 Visualisierung: Base R, ggplot2, passende Diagrammwahl, Export
Orientierung, zwei Kurzübungen, längere Arbeitsphase
16:00 - 17:30 Quarto und Transfer: Aufbau einer .qmd-Datei, Chunks, Optionen, Mini-Report
gemeinsamer Einstieg, Abschlussaufgabe, Besprechung

Pausen und Besprechungen nutzen wir wieder, um offene Fragen sofort zu klären und den Schwierigkeitsgrad bei Bedarf anzupassen.

1 Import und Export von Daten

1.1 Orientierung: Textdateien, Pfade und Importwege

  • Häufigstes Austauschformat in der Praxis: Textdateien wie csv oder tsv.
  • Wichtige Unterschiede: Trennzeichen, Dezimalzeichen, Spaltenüberschriften, Kodierung von fehlenden Werten.
  • In Base R ist read.table(...) die allgemeine Importfunktion, read.csv(...) die einfache Variante für Standard-CSV.
  • Mit readLines(...) lässt sich vor dem Einlesen schnell prüfen, wie eine Datei aufgebaut ist.
  • Über die RStudio-Oberfläche lässt sich ein Import auch ohne direkt geschriebenen Code vorbereiten.
file <- "data/zaehlstelle_neutor_2017.csv"

readLines(file, n = 3)
rad <- read.csv(file)

Achtung

Große Datensätze sollten Sie nie einfach komplett in die Konsole drucken. Für den ersten Überblick sind head(...), tail(...) und str(...) fast immer die bessere Wahl.

1.2 Orientierung: Eingelesene Datensätze schnell prüfen

Funktion Wozu? Typischer Einsatz
str(df) Struktur, Typen und erste Werte sofort nach dem Import
names(df) Spaltennamen prüfen nachsehen, wie Variablen heißen
dim(df) Zeilen und Spalten gemeinsam prüfen erster Größencheck
nrow(df), ncol(df) Zeilen oder Spalten getrennt prüfen schnelle Rückfrage
head(df, n) erste Zeilen ansehen Import plausibilisieren
tail(df, n) letzte Zeilen ansehen Randbereiche prüfen
summary(df) Variablen zusammenfassen auffällige Werte und NAs erkennen

Tipp

Die sinnvolle Standardreihenfolge nach dem Import ist oft: str(...), dim(...), head(...), summary(...).

1.3 Kurzübung: Eine CSV-Datei mit read.csv einlesen

  1. Legen Sie an:
file <- "data/zaehlstelle_neutor_2017.csv"
  1. Prüfen Sie mit readLines(file, n = 3), wie die Datei aufgebaut ist.
  2. Lesen Sie die Datei mit read.csv(file) als Objekt rad ein.
  3. Prüfen Sie mit class(rad), in welcher Struktur der Datensatz vorliegt.
  4. Geben Sie mit head(rad, 2) die ersten zwei Zeilen aus.
  • Fokus:
  • Datei zuerst ansehen
  • dann gezielt einlesen
  • Struktur und erste Zeilen sofort prüfen

1.4 Kurzübung: Überblick mit str, names und summary

  1. Prüfen Sie mit str(rad), welche Variablen im Datensatz enthalten sind.
  2. Lassen Sie sich mit names(rad) alle Spaltennamen ausgeben.
  3. Bestimmen Sie mit dim(rad), nrow(rad) und ncol(rad) die Größe des Datensatzes.
  4. Geben Sie die letzten drei Zeilen mit tail(rad, 3) aus.
  5. Prüfen Sie mit summary(rad), welche Variablen numerisch und welche kategorial wirken.
  • Fokus:
  • Überblick statt Vollausgabe
  • Datentypen und Dimensionen lesen
  • summary(...) interpretieren

1.5 Arbeitsphase: Zähldaten importieren, prüfen und Teilmenge exportieren

  1. Lesen Sie data/zaehlstelle_neutor_2017.csv als Objekt rad ein.
  2. Prüfen Sie die Struktur mit str(rad) und die Größe mit dim(rad).
  3. Erstellen Sie ein Objekt kalte_stunden mit allen Zeilen, für die Temperatur <= 0 gilt.
  4. Behalten Sie in kalte_stunden nur die Spalten Datum, Anzahl, Temperatur und Wetter.
  5. Prüfen Sie die ersten zehn Zeilen und die Anzahl der Zeilen.
  6. Exportieren Sie kalte_stunden als Datei kalte_stunden.csv ohne zusätzliche Zeilennummern.
  7. Optional: Lesen Sie die exportierte Datei wieder ein und vergleichen Sie names(...) und dim(...).
rad <- read.csv("data/zaehlstelle_neutor_2017.csv")
kalte_stunden <- rad[
  rad$Temperatur <= 0,
  c("Datum", "Anzahl", "Temperatur", "Wetter")
]
  • Das Ziel ist ein sauber geprüfter Teil-Datensatz plus ein funktionierender Export.

1.6 Besprechung: Häufige Importfehler früh erkennen

  • Falscher Pfad: Datei wird nicht gefunden.
  • Falsches Trennzeichen oder Dezimalzeichen: Spalten wirken „zusammengeklebt“.
  • Fehlende Werte werden nicht als NA erkannt.
  • Große Datensätze werden versehentlich komplett in die Konsole gedruckt.
  • Nach dem Import werden Typen, Namen und Dimensionen nicht geprüft.

Merksatz

Import ist noch nicht Analyse. Erst wenn Struktur, Namen und Größenordnung plausibel sind, lohnt sich die eigentliche Datenarbeit.

2 Data Wrangling I

2.1 Orientierung: Tidy Data als Arbeitsgrundlage

  • Ein „sauberer“ Datensatz hat pro Zeile genau eine Beobachtung.
  • Jede Spalte steht für genau eine Variable.
  • Jede Zelle enthält genau einen Wert.
  • Viele Wrangling-Funktionen setzen diese Struktur stillschweigend voraus.
  • Sobald Werte oder Variablen „vermischt“ sind, wird spätere Analyse unnötig schwer.

Merksatz

Base-R-Äquivalente kennen Sie aus den Grundlagen. In diesem Block arbeiten wir bewusst mit dplyr, damit die Datenlogik und nicht der Syntaxvergleich im Vordergrund steht.

2.2 Orientierung: Beobachtungen auswählen mit filter(...)

  • filter(...) behält genau die Zeilen, für die alle Bedingungen TRUE ergeben.
  • Mehrere Bedingungen können direkt in einer Funktion kombiniert werden.
  • Das Ergebnis bleibt ein Datensatz mit denselben Variablen, aber weniger Beobachtungen.
  • Typisch ist die Kombination mit !is.na(...), um fehlende Werte bewusst auszuschließen.

flights %>%
  filter(arr_delay > 120, !is.na(arr_delay))

Achtung

filter(...) verändert nur Zeilen. Wenn sich plötzlich Spalten ändern, wurde meist die falsche Funktion gewählt.

2.3 Orientierung: Variablen auswählen mit select(...)

  • select(...) reduziert oder ordnet Spalten um.
  • Die Zeilenanzahl bleibt dabei unverändert.
  • Variablen lassen sich direkt beim Namen angeben.
  • Später helfen Auswahlfunktionen wie starts_with(...) oder contains(...).

flights %>%
  select(year, month, day, origin, dest, arr_delay)

Tipp

Lesen Sie select(...) immer als Spaltenentscheidung: Was soll im Datensatz sichtbar bleiben?

2.4 Orientierung: Neue Variablen mit mutate(...)

  • mutate(...) ergänzt neue Variablen oder überschreibt bestehende.
  • Die Zeilenanzahl bleibt dabei gleich.
  • Berechnungen greifen direkt auf vorhandene Spalten zu.
  • Das ist der Standardweg für Kennzahlen pro Beobachtung.

flights %>%
  mutate(delay_total = dep_delay + arr_delay)

Merksatz

mutate(...) liefert wieder einen Datensatz gleicher Länge. Es wird ergänzt, nicht verdichtet.

2.5 Orientierung: Window-Funktionen in mutate(...)

  • Window-Funktionen berechnen pro Zeile ein Ergebnis auf Basis ganzer Spalten.
  • Typische Beispiele sind dense_rank(...), lag(...) oder kumulative Summen.
  • Auch hier bleibt die Zeilenanzahl erhalten.
  • Besonders interessant wird das später in Kombination mit group_by(...).

flights %>%
  mutate(delay_rank = dense_rank(arr_delay))

Hinweis

Window-Funktionen beantworten nicht die Frage „Wie viele?“ oder „Wie groß im Mittel?“, sondern erzeugen neue Werte pro Beobachtung.

2.6 Orientierung: Daten verdichten mit summarise(...)

  • summarise(...) fasst viele Zeilen zu Kennzahlen zusammen.
  • Häufig entstehen Mittelwerte, Maxima, Summen oder Zählungen.
  • Ohne group_by(...) entsteht meist eine Zeile für den gesamten Datensatz.
  • Mit group_by(...) entsteht eine Zeile pro Gruppe.

flights %>%
  summarise(
    max_arr_delay = max(arr_delay, na.rm = TRUE),
    mean_arr_delay = mean(arr_delay, na.rm = TRUE)
  )

Merksatz

summarise(...) verändert die Bedeutung der Zeilen. Nach einer Verdichtung steht nicht mehr jede Zeile für eine einzelne Beobachtung.

2.7 Kurzübung: Beobachtungen filtern und Variablen auswählen

  1. Laden Sie die Pakete nycflights13 und dplyr.
  2. Erstellen Sie ein Objekt df_2hdelay mit allen Flügen, für die arr_delay > 120 gilt.
  3. Behalten Sie nur die Spalten year, month, day, origin, dest und arr_delay.
  4. Prüfen Sie mit head(...), ob die Struktur plausibel ist.
  5. Geben Sie mit nrow(...) aus, wie viele Flüge im Ergebnis enthalten sind.
  • Fokus:
  • Zeilen filtern
  • Spalten auswählen
  • Ergebnis kritisch prüfen

2.8 Kurzübung: Variablen erzeugen und mit summarise verdichten

  1. Erstellen Sie aus flights ein Objekt mit den Spalten origin, dest, dep_delay und arr_delay.
  2. Ergänzen Sie mit mutate(...) eine neue Variable delay_total = dep_delay + arr_delay.
  3. Berechnen Sie mit summarise(...) den größten und den durchschnittlichen Wert von arr_delay.
  4. Prüfen Sie zusätzlich, wie viele fehlende Werte in arr_delay enthalten sind.
  • Fokus:
  • neue Spalte erzeugen
  • Kennzahlen verdichten
  • fehlende Werte bewusst behandeln

2.9 Arbeitsphase: Flugdaten für eine konkrete Frage vorbereiten

Fragestellung: Welche Ziele sind im Januar von Newark (EWR) aus besonders verspätungsanfällig?

  1. Filtern Sie alle Flüge aus flights mit month == 1, origin == "EWR" und nicht fehlender arr_delay.
  2. Behalten Sie nur dest, dep_delay und arr_delay.
  3. Ergänzen Sie delay_total = dep_delay + arr_delay.
  4. Fassen Sie die Daten je Ziel (dest) zusammen:
    • Anzahl der Flüge
    • mittlere Ankunftsverspätung
    • größte Ankunftsverspätung
  5. Behalten Sie nur Ziele mit mindestens 20 Flügen.
  6. Sortieren Sie absteigend nach mittlerer Ankunftsverspätung.
  7. Interpretieren Sie die ersten fünf Ziele kurz.
flights %>%
  filter(month == 1, origin == "EWR")
  • Ziel ist eine verdichtete Tabelle, die eine echte Frage beantwortet und nicht nur ein Zwischenergebnis erzeugt.

2.10 Besprechung: filter, select, mutate, summarise sauber trennen

  • filter(...) verändert Zeilen.
  • select(...) verändert Spalten.
  • mutate(...) ergänzt oder verändert Variablen.
  • summarise(...) verdichtet einen Datensatz zu Kennzahlen.
  • Viele Fehler entstehen, wenn diese vier Rollen vermischt werden.

Merksatz

Fragen Sie bei jedem Schritt zuerst: Verändere ich Beobachtungen, Variablen oder verdichte ich den Datensatz?

3 Data Wrangling II, Joins und Datenbanken

3.1 Orientierung: Pipes gezielt lesen

  • Die Pipe %>% übergibt das linke Ergebnis als erstes Argument an die nächste Funktion.
  • Längere Verarbeitungsketten werden dadurch lesbarer als verschachtelte Funktionsaufrufe.
  • Jede Zeile steht für einen eigenen Verarbeitungsschritt.
  • Eine gute Lese-Reihenfolge ist: Startdatensatz, Auswahl, Gruppierung, Verdichtung, Sortierung.
flights %>%
  filter(month == 1, arr_delay >= 0) %>%
  group_by(origin) %>%
  summarise(avg_delay = mean(arr_delay, na.rm = TRUE)) %>%
  arrange(desc(avg_delay))

Tipp

Lesen Sie Pipe-Code zunächst von oben nach unten. Fragen Sie sich bei jeder Zeile: Was ist jetzt das aktuelle Objekt?

3.2 Orientierung: Gruppierung verändert die Berechnungsebene

  • group_by(...) markiert, für welche Gruppen nachfolgende Funktionen rechnen sollen.
  • Erst das nächste Verb wie summarise(...) oder mutate(...) nutzt diese Gruppierung.
  • Mit summarise(...) entsteht meist eine Zeile pro Gruppe.
  • Genau diese Veränderung der Berechnungsebene ist einer der wichtigsten Schritte im Wrangling.

flights %>%
  group_by(origin) %>%
  summarise(avg_delay = mean(arr_delay, na.rm = TRUE))

Merksatz

group_by(...) rechnet noch nichts aus. Die Wirkung sieht man erst im nächsten Schritt.

3.3 Orientierung: Gruppierte Window-Funktionen mit mutate(...)

  • Auch mutate(...) kann gruppiert arbeiten.
  • Dann wird eine neue Variable innerhalb jeder Gruppe separat berechnet.
  • Die Zeilenanzahl bleibt erhalten, aber die Bedeutung der neuen Variable hängt von der Gruppe ab.
  • Typische Beispiele sind Ränge, Anteile oder Abstände innerhalb einer Gruppe.

flights %>%
  group_by(origin) %>%
  mutate(delay_rank = dense_rank(arr_delay))

Hinweis

Die Kombination group_by(...) plus mutate(...) ist didaktisch wichtig, weil hier oft unbemerkt gruppenspezifische Ergebnisse entstehen.

3.4 Orientierung: Joins und Schlüsselvariablen

  • Joins verbinden zwei Tabellen über eine gemeinsame Schlüsselvariable.
  • Bei nycflights13 ist carrier ein typischer Schlüssel zwischen flights und airlines.
  • left_join(...) behält alle Zeilen der linken Tabelle.
  • inner_join(...) behält nur Zeilen mit Treffern auf beiden Seiten.
  • Fehlerquellen sind fast immer falsche Schlüssel oder unerwartete Duplikate.

Achtung

Ein Join ist nicht nur „Spalten anhängen“. Entscheidend ist, ob die Schlüsselvariablen wirklich eindeutig und fachlich passend sind.

3.5 Orientierung: Beziehungen in nycflights13

  • flights ist die zentrale Faktentabelle.
  • airlines ergänzt Namen zu carrier.
  • airports ergänzt Informationen zu Start- und Ziel-Flughäfen.
  • weather liefert stundenweise Kontextdaten.
  • Gute Join-Entscheidungen beginnen mit der fachlichen Frage: Welche Tabelle ergänzt welche Information?

Tipp

Prüfen Sie vor jedem Join erst das Datenmodell: Schlüssel, Granularität und erwartete Zeilenzahl des Ergebnisses.

3.6 Orientierung: fifa23 als erste dbplyr-Datenbank

  • fifa23 eignet sich gut für den Einstieg, weil Spieler, Vereine und Ligen fachlich leicht verständlich sind.
  • Relevante Tabellen heute: players (18.533 Zeilen), clubs (667) und optional leagues (52).
  • Wichtige Schlüssel: players$club_team_id -> clubs$club_id, players$league_id -> leagues$league_id.
  • Gute Fragen beginnen fachlich: Welche Beobachtungseinheit hat meine Analyse und welche Tabelle ergänzt nur Informationen?

Merksatz

Ein Datenbankschema ist kein Beiwerk. Es zeigt Ihnen, welche Tabellen zusammenpassen und welche Join-Schlüssel fachlich sinnvoll sind.

3.7 Orientierung: videogames als zweite Übungsdatenbank

  • videogames ist kleiner und analytischer: Spiele, Publisher, Plattformen und Verkäufe sind sauber getrennt.
  • Relevante Tabellen heute: sales (65.296 Zeilen), game (11.360), publisher (577).
  • Wichtige Schlüssel: sales$game_id -> game$id, sales$publisher_id -> publisher$id.
  • Eine gute Analyse verbindet hier meist Genre, Region, Publisher und Verkaufszahlen.

Tipp

Bearbeiten Sie zuerst die fifa23-Aufgabe. Wenn Verbindung, tbl(...), Join und collect() sicher laufen, wechseln Sie zur zweiten Datenbank.

3.8 Orientierung: DBI, Backends und dbplyr

  • DBI stellt die Verbindung zur Datenbank her.
  • RPostgres ist hier der passende Treiber für PostgreSQL.
  • dbplyr übersetzt dplyr-Code in SQL.
  • Vor collect() bleiben Tabellen lazy in der Datenbank.
  • Mit show_query(...) prüfen Sie, welchen SQL-Code dbplyr erzeugt.
con <- DBI::dbConnect(
  RPostgres::Postgres(),
  host = "wi-sql.fh-muenster.de",
  user = "wi_student",
  password = "w12019sql",
  dbname = "fifa23"
)

DBI::dbListTables(con)
players <- tbl(con, "players")

Hinweis

Vor collect() arbeitet dbplyr noch nicht mit einem Data Frame in R, sondern schiebt die Operation möglichst lange in PostgreSQL.

3.9 Kurzübung: Pipe-Ausdrücke Schritt für Schritt lesen

  1. Erstellen Sie aus flights eine Pipeline, die
    • nur Januar-Flüge mit arr_delay >= 0 behält,
    • nach origin gruppiert,
    • die mittlere Ankunftsverspätung je Flughafen berechnet,
    • und das Ergebnis absteigend sortiert.
  2. Geben Sie die Tabelle aus.
  3. Formulieren Sie in einem Satz, was jede Zeile des Ergebnisses fachlich bedeutet.
  • Fokus:
  • Filterung
  • Gruppierung
  • Verdichtung
  • fachliche Interpretation

3.10 Kurzübung: Flug- und Airline-Daten zusammenführen

  1. Laden Sie airlines aus dem Paket nycflights13.
  2. Erstellen Sie ein Objekt flights_airlines, das flights und airlines über carrier mit left_join(...) verbindet.
  3. Behalten Sie nur carrier, name, origin und arr_delay.
  4. Prüfen Sie mit head(...), ob Airline-Namen nun sichtbar sind.
  5. Vergleichen Sie mit nrow(...), ob sich die Zahl der Zeilen durch den Join verändert hat.
  • Fokus:
  • Schlüsselvariable carrier
  • left_join(...) lesen und anwenden
  • Join-Ergebnis kritisch prüfen

3.11 Kurzübung: Erste Abfrage in fifa23

  1. Stellen Sie die Verbindung zur Datenbank fifa23 her.
  2. Prüfen Sie mit dbListTables(con), ob players, clubs und leagues vorhanden sind.
  3. Erzeugen Sie players <- tbl(con, "players").
  4. Wählen Sie short_name, overall, age und nationality.
  5. Filtern Sie overall >= 90, sortieren Sie absteigend und zeigen Sie den SQL-Code.
  6. Laden Sie die ersten zehn Zeilen mit collect() nach R.
players %>%
  filter(overall >= 90) %>%
  select(short_name, overall, age, nationality)
  • Fokus:
  • Verbindung
  • lazy table
  • show_query(...)
  • collect() erst am Ende

3.12 Arbeitsphase: fifa23-Clubs mit starken Kadern identifizieren

  1. Stellen Sie die Verbindung zur Datenbank fifa23 her.
  2. Erzeugen Sie players <- tbl(con, "players") und clubs <- tbl(con, "clubs").
  3. Filtern Sie players auf overall >= 80 und entfernen Sie fehlende club_team_id.
  4. Verbinden Sie players und clubs über club_team_id = club_id.
  5. Berechnen Sie je team_name die Anzahl dieser Spieler und den mittleren overall.
  6. Behalten Sie nur Clubs mit mindestens 5 solchen Spielern.
  7. Sortieren Sie absteigend nach mean_overall, zeigen Sie den SQL-Code und laden Sie das Ergebnis nach R.
players %>%
  inner_join(clubs,
             by = c("club_team_id" = "club_id"))
  • Schlüssel:
  • club_team_id
  • club_id
  • collect() erst nach
  • show_query(...)

3.13 Arbeitsphase: videogames-Verkäufe nach Publishern analysieren

  1. Stellen Sie die Verbindung zur Datenbank videogames her.
  2. Erzeugen Sie sales <- tbl(con, "sales"), game <- tbl(con, "game") und publisher <- tbl(con, "publisher").
  3. Filtern Sie auf region_name == "Europe".
  4. Verbinden Sie sales mit game und publisher.
  5. Beschränken Sie auf das Genre Sports und berechnen Sie je publisher_name die gesamte num_sales.
  6. Sortieren Sie absteigend, zeigen Sie den SQL-Code und holen Sie die Top 10 mit collect() nach R.
sales %>%
  filter(region_name == "Europe")
  • Schlüssel:
  • game_id
  • publisher_id
  • show_query(...)
  • Top 10 erst ganz am Ende

3.14 Besprechung: Schlüssel, Joins und collect() bewusst einsetzen

  • Eine Schlüsselvariable verbindet Tabellen fachlich, nicht nur technisch.
  • left_join(...) ist oft der sichere Standard, wenn alle Zeilen der linken Tabelle erhalten bleiben sollen.
  • In Datenbanken entscheidet das Schema, welche Join-Schlüssel wirklich passen.
  • show_query(...) hilft, die Wirkung von dbplyr transparent zu machen.
  • collect() sollte bewusst spät kommen, damit große Datenmengen nicht zu früh nach R geladen werden.

Merksatz

Fragen Sie bei dbplyr immer doppelt: Arbeite ich noch lazy in PostgreSQL oder schon mit einem Data Frame in R, und welche Tabelle ergänzt meine aktuelle Beobachtungseinheit?

4 Visualisierung

4.1 Orientierung: Basisgrafiken passend auswählen

Fragestellung Base-R-Funktion Typischer Input Geeignet für
Zusammenhang zweier numerischer Variablen plot(...) x, y Streudiagramme
Verteilung einer numerischen Variablen hist(...) ein numerischer Vektor Histogramme
Gruppen vergleichen boxplot(...) Vektor oder Formel y ~ gruppe Boxplots
Kategorien zählen oder Werte je Kategorie zeigen barplot(...) Vektor oder Tabelle Balkendiagramme

Hinweis

Base R ist besonders stark, wenn Sie schnell eine erste, robuste Grafik brauchen. Für systematischen Aufbau und konsistente Erweiterung ist ggplot2 meist angenehmer.

4.2 Orientierung: ggplot2 als System

  • ggplot2 trennt klar zwischen Daten, Mapping und Geometrie.
  • Grundlogik: ggplot(data, aes(...)) + geom_*()
  • Zusätzliche Layer wie labs(...), scale_*(), theme(...) oder facet_*() verfeinern den Plot.
  • Wichtig ist die Unterscheidung zwischen Mapping in aes(...) und festen Stilwerten außerhalb von aes(...).
library(ggplot2)

ggplot(mtcars, aes(x = wt, y = mpg)) +
  geom_point() +
  labs(
    title = "mpg in Abhängigkeit von wt",
    x = "Gewicht",
    y = "Miles per Gallon"
  )

Achtung

aes(color = factor(cyl)) mappt Farbe auf Daten. geom_point(color = "steelblue") setzt nur einen festen Stil. Genau diese Unterscheidung ist eine der häufigsten Fehlerquellen.

4.3 Kurzübung: Einen Base-R-Plot passend konfigurieren

  1. Erstellen Sie mit plot(...) ein Streudiagramm für wt gegen mpg aus mtcars.
  2. Ergänzen Sie mindestens:
    • einen Titel,
    • Achsenbeschriftungen,
    • eine Punktfarbe,
    • ein Punktsymbol.
  3. Zeichnen Sie mit abline(...) zusätzlich die lineare Regressionsgerade ein.
  • Fokus:
  • passende Funktion wählen
  • zentrale Grafikparameter bewusst setzen
  • zusätzliche grafische Ebene ergänzen

4.4 Kurzübung: Einen ersten ggplot aufbauen

  1. Laden Sie ggplot2.
  2. Erstellen Sie einen Scatterplot für wt gegen mpg aus mtcars.
  3. Färben Sie die Punkte nach factor(cyl).
  4. Ergänzen Sie Titel und Achsenbeschriftungen mit labs(...).
  5. Ergänzen Sie optional ein schlichtes Theme wie theme_minimal().
  • Fokus:
  • data, aes, geom_point
  • Mapping einer kategorialen Variable
  • Beschriftungen ergänzen

4.5 Arbeitsphase: Eine Fragestellung als Grafik beantworten

Bearbeiten Sie eine der folgenden Fragen mit einer passenden Grafik in Base R oder ggplot2.

  1. Verteilung von arr_delay in flights Vorschlag: Histogramm ohne fehlende Werte
  2. Unterschiede von arr_delay zwischen JFK, LGA und EWR Vorschlag: Boxplot
  3. Zusammenhang zwischen dep_delay und arr_delay Vorschlag: Scatterplot auf einer Stichprobe von 2000 Flügen
flights_sample <- flights %>%
  filter(!is.na(dep_delay), !is.na(arr_delay)) %>%
  slice_sample(n = 2000)
  • Mindestanforderungen:
  • sinnvoller Plottyp
  • verständlicher Titel und saubere Achsenbeschriftung
  • bewusster Umgang mit fehlenden Werten oder Stichproben
  • Export mit ggsave(...) oder über ein Base-R-Grafikgerät

4.6 Besprechung: Mapping, fester Stil und Export

  • In ggplot2 gehört datenabhängige Darstellung in aes(...), feste Gestaltung außerhalb.
  • Ein Plot ist erst dann „fertig“, wenn Titel, Achsen und Legende verständlich sind.
  • Für ggplot2 ist ggsave(...) oft der bequemste Exportweg.
  • In Base R gilt die übliche Reihenfolge: Grafikgerät öffnen, Plot zeichnen, dev.off().

Merksatz

Eine gute Grafik beantwortet eine konkrete Frage. Erst danach kommen Farbe, Theme und Exportformat.

5 Quarto

5.1 Orientierung: Aufbau eines Quarto-Dokuments

  • Eine Quarto-Datei (.qmd) verbindet YAML, Markdown und Code-Chunks.
  • YAML enthält Metadaten wie Titel, Autor und Ausgabeformat.
  • Markdown strukturiert Text mit Überschriften, Listen und Links.
  • Code-Chunks erzeugen reproduzierbare Ergebnisse direkt im Dokument.
  • Typischer Ablauf: schreiben, speichern, rendern, prüfen, verbessern.
---
title: "Mini-Report"
author: "Max Mustermann"
format: html
---

## Einleitung

```{r}
summary(mtcars$mpg)
```

Tipp

Wenn Sie Quarto lernen, denken Sie nicht zuerst an perfekte Berichte, sondern an einen sauberen Workflow aus Text, Code und Ergebnis an einem Ort.

5.2 Orientierung: Wichtige Chunk-Optionen

Option Wirkung Typischer Einsatz
eval Code ausführen oder nicht Beispiele zeigen, aber nicht rechnen
echo Quellcode im Ergebnis zeigen oder verbergen Arbeitsblatt vs. Bericht
warning Warnungen anzeigen oder unterdrücken sauberere Ausgabe
message Paketmeldungen anzeigen oder unterdrücken weniger Rauschen
fig-cap Bildunterschrift setzen Abbildungen dokumentieren

Hinweis

Chunk-Optionen lösen kein inhaltliches Problem. Sie steuern nur, wie der vorhandene Code im Dokument erscheint.

5.3 Kurzübung: YAML, Markdown und Code-Chunks unterscheiden

Schauen Sie sich das folgende Grundgerüst an und bearbeiten Sie es direkt:

---
title: "Mini-Report"
format: html
---

## Datenüberblick

```{r}
head(mtcars)
```
  1. Ergänzen Sie Ihren Namen als author.
  2. Fügen Sie unterhalb von ## Datenüberblick einen kurzen erklärenden Satz ein.
  3. Ersetzen Sie head(mtcars) durch summary(mtcars$mpg).
  • Fokus:
  • YAML und Markdown unterscheiden
  • Chunk-Inhalt gezielt ändern
  • kleine QMD-Datei lesen lernen

5.4 Kurzübung: Chunk-Optionen gezielt setzen

  1. Erstellen Sie einen Chunk, der einen Plot zeigt, aber Warnungen ausblendet.
  2. Erstellen Sie einen zweiten Chunk, der ausgeführt wird, dessen Code aber nicht sichtbar sein soll.
  3. Ergänzen Sie für die Grafik eine Bildunterschrift mit fig-cap.
  4. Formulieren Sie in einem Satz, warum echo und eval nicht dasselbe sind.
  • Fokus:
  • echo vs. eval
  • Ausgabe bewusst steuern
  • grafische Ergebnisse dokumentieren

5.5 Arbeitsphase: Ein kleines Analyseblatt in Quarto erstellen

Erstellen Sie eine Datei mini-report.qmd mit folgendem Mindestumfang:

  1. YAML-Header mit title, author und format: html.
  2. Eine kurze Einleitung mit 2 bis 3 Sätzen.
  3. Einen Code-Chunk, der einen Datensatz lädt oder verwendet.
  4. Einen zweiten Chunk mit einer Grafik.
  5. Mindestens zwei sinnvolle Chunk-Optionen.
  6. Rendern Sie das Dokument und prüfen Sie das Ergebnis.

Als Datenquelle können Sie z. B. mtcars, airquality oder den bereits importierten Datensatz rad verwenden.

---
title: "Mini-Report"
author: "..."
format: html
---
  • Starten Sie klein. Ein sauber gerenderter Mini-Report ist didaktisch wertvoller als ein halbfertiges Großprojekt.

5.6 Besprechung: Typischer Workflow mit Quarto

  • Datei anlegen, speichern, kleine Schritte rendern, Fehler direkt korrigieren.
  • Viele Renderprobleme kommen aus YAML-Einrückungen oder fehlerhaften Chunk-Grenzen.
  • Erst wenn das Grundgerüst läuft, lohnt sich Feinschliff bei Text, Layout und Abbildungen.

Merksatz

Quarto ist kein Extra neben der Analyse. Es ist die Form, in der Analyse, Code und Ergebnis sauber zusammengeführt werden.