11  Daten aufbereiten

11.1 Ziel bei der Aufbereitung von Daten

Tidy data

“Happy families are all alike; every unhappy family is unhappy in its own way.” — Leo Tolstoy

“Tidy datasets are all alike, but every messy dataset is messy in its own way.” — Hadley Wickham

Tidy data (aufgeräumte Daten)

  • In jeder Spalte steht eine Variable
  • In jeder Zeile steht eine Beobachtung
  • In jeder Zelle steht ein Wert
  • Wir haben alle Variablen, die wir benötigen

→ Nicht immer so eindeutig, wie es hier klingt

Pragmatischer Ansatz

  • Daten sind dann aufgeräumt, wenn das, was man vorhat, einfach umzusetzen ist
  • Zum Beispiel plotten

11.2 Der Pipe-Operator |>

Verkettete Funktion \(f(x) = \sin\left(\sqrt{ e^x } \right)\)

Mit geschachtelten Funktionen

sin(sqrt(exp(1.5)))
[1] 0.8545027

→ Zuerst wird \(e^{1.5}\) berechnet, dann die Wurzel gezogen und danach der Sinus ermittelt

Mit Pipe-Operator

1.5 |> exp() |> sqrt() |> sin()
[1] 0.8545027

→ Operator |> fügt linke Seite als erstes Argument der Funktion rechts ein

Verkettete Funktion \(f(x) = \sin\left(\sqrt{ e^x } \right)\)

11.3 Variablen auswählen oder umbenennen mit select() und rename()

Wann braucht man das?

  • In der Datenquelle stehen mehr Informationen als benötigt
  • Die Namen der Merkmale sind in der Datei nicht gut gewählt

Beispieldatensatz: Geschwindigkeitsmessung

d_uni <- read_excel("daten/unistrasse-2017-2.xlsx", sheet = "raw(T)", range = "B2:H20712")

Variablen auswählen mit select()

d_uni |> select(Datum, Geschwindigkeit, "Länge (cm)", Fahrzeug)
  • Variablen angeben, die beibehalten werden sollen
  • Anführungszeichen bei Namen mit Leerzeichen

Variablen entfernen mit select()

d_uni |> select(-Fahrtrichtung, -Abstand, -"Länge (Radar)")
  • Variablen mit vorangestelltem - werden entfernt
  • Anführungszeichen bei Namen mit Leerzeichen

Variablen umbenennen mit select()

d_uni |> select(Datum, v = Geschwindigkeit, L = "Länge (cm)", Fahrzeug)
  • Auswählen und umbenennen mit neuer_name = alter_name
  • Andere Variablen einfach beibehalten

Variablen nach Muster auswählen

d <- tibble(A_X = 1:2, B_X = 3:4, C_X = 5:6, X_A = 7:8, X_B = 9:10, X_C = 11:12, ABC = 13:14)
d |> select(starts_with("X_"))
d |> select(ends_with("_X"))
d |> select(-starts_with("X_"))
d |> select(-ends_with("_X"))
  • Mit starts_with() und ends_with() Muster für Namen festlegen
  • Funktioniert auch wieder mit -

Variablen umbenennen mit rename()

d_uni |> rename(v = Geschwindigkeit, laenge = "Länge (Radar)")
  • Variable umbenennen mit neuer_name = alter_name
  • Alle anderen Variablen werden beibehalten

11.4 Beobachtungen auswählen mit filter() und slice()

Wann braucht man das?

  • Wenn einzelne Werte aussortiert werden sollen
  • Wenn man sich nur für bestimmte Beobachtungen interessiert

Beispieldatensatz

d <- d_uni |> select(Datum, Fahrzeug, Abstand, Geschwindigkeit)

Filter: Nur LKW

d |> filter(Fahrzeug == "LKW")
  • Bedingung als logischer Ausdruck mit Name der Variablen

Filter: Nur LKW schneller als 60km/h

d |> filter(Fahrzeug == "LKW" & Geschwindigkeit > 60)
  • Bedingung als logischer Ausdruck mit Name der Variablen
  • Logisches und: &

Filter: Nur LKW oder Fahrzeuge schneller 80km/h

d |> filter(Fahrzeug == "LKW" | Geschwindigkeit > 80)
  • Bedingung als logischer Ausdruck mit Name der Variablen
  • Logisches oder: |

Filter: Fahrzeugtyp nicht erkannt

d |> filter(is.na(Fahrzeug))

Filter: Zeilen mit NA entfernen

d |> filter(!is.na(Fahrzeug))

Filter: Kriterium in Vektor

d |> filter(Fahrzeug %in% c("Zweirad", "LKW"))
  • Werte für Merkmal aus Vektor

Auswahl nach Zeilennummer mit slice()

d |> slice(c(10, 30, 700))
  • Beobachtungen nach Position auswählen

Auswahl am Anfang oder am Ende

d |> slice_head(n = 10)
d |> slice_tail(n = 10)
  • Erste oder letzte n (Anzahl) Beobachtungen auswählen
  • Dabei: head wie Kopf und tail wie Schwanz oder Ende

Auswahl kleinste oder größte Beobachtungen

d |> slice_min(Geschwindigkeit, n = 10)
d |> slice_max(Geschwindigkeit, n = 10)
  • Die kleinsten und größten n (Anzahl) Beobachtungen auswählen
  • min wie Minimum und max wie Maximum

11.5 Beobachtungen sortieren mit arrange()

Wann braucht man das?

  • Wenn man sich für die größten oder kleinsten Werte interessiert
  • Um die Reihenfolge beim Plotten zu ändern, so dass bestimmte Objekte über anderen liegen

Beobachtungen mit kleinster Geschwindigkeit

d |> arrange(Geschwindigkeit)
  • Sortiert aufsteigend nach dem angegebenen Merkmal

Beobachtungen mit größter Geschwindigkeit

d |> arrange(desc(Geschwindigkeit))
  • Mit desc() absteigend sortieren

Nach mehreren Kriterien sortieren (1/2)

d |> arrange(Fahrzeug, Geschwindigkeit)
  • Zuerst nach Fahrzeugen sortieren
  • Innerhalb einer Fahrzeuggruppe nach Geschwindigkeit angeordnet

Nach mehreren Kriterien sortieren (2/2)

d |> arrange(Geschwindigkeit, Fahrzeug)
  • Zuerst nach der Geschwindigkeit sortieren
  • Innerhalb einer Geschwindigkeiten nach Fahrzeugen angeordnet

11.6 Variablen hinzufügen oder verändern mit mutate()

Wann braucht man das?

  • Wenn aus den vorliegenden Werten neue Werte berechnet werden sollen
  • Wenn bestehende Werte verändert werden sollen (typischerweise: Datum)

Geschwindigkeit runden

d |> mutate(V10 = signif(Geschwindigkeit, digits = 1))
  • neue_variable = Ausdruck(alte_variablen)
  • Mit signif() auf eine signifikante Stellte gerundet

Datum konvertieren 1/2

d |> show()
# A tibble: 20,710 × 4
   Datum               Fahrzeug    Abstand Geschwindigkeit
   <chr>               <chr>         <dbl>           <dbl>
 1 13.12.2017 00:03:31 PKW          262.                70
 2 13.12.2017 00:03:37 PKW            5.18              59
 3 13.12.2017 00:03:53 PKW           15.7               79
 4 13.12.2017 00:05:42 PKW          111.                58
 5 13.12.2017 00:06:51 PKW           69.5               60
 6 13.12.2017 00:07:08 Transporter   16.7               57
 7 13.12.2017 00:08:11 PKW           63.9               58
 8 13.12.2017 00:09:41 PKW           90.7               54
 9 13.12.2017 00:11:42 Transporter  122.                56
10 13.12.2017 00:17:31 PKW          354.                61
# ℹ 20,700 more rows
  • Nach dem Einlesen ist das Merkmal Datum eine Zeichenkette

Datum konvertieren 2/2

d |> mutate(Datum = dmy_hms(Datum, tz = "Europe/Berlin")) |> show()
# A tibble: 20,710 × 4
   Datum               Fahrzeug    Abstand Geschwindigkeit
   <dttm>              <chr>         <dbl>           <dbl>
 1 2017-12-13 00:03:31 PKW          262.                70
 2 2017-12-13 00:03:37 PKW            5.18              59
 3 2017-12-13 00:03:53 PKW           15.7               79
 4 2017-12-13 00:05:42 PKW          111.                58
 5 2017-12-13 00:06:51 PKW           69.5               60
 6 2017-12-13 00:07:08 Transporter   16.7               57
 7 2017-12-13 00:08:11 PKW           63.9               58
 8 2017-12-13 00:09:41 PKW           90.7               54
 9 2017-12-13 00:11:42 Transporter  122.                56
10 2017-12-13 00:17:31 PKW          354.                61
# ℹ 20,700 more rows
  • In date-time konvertieren mit dmy_hms(), ggf. Zeitzone angeben

Zusätzlich mit Uhrzeit

d |>
  mutate(
    Datum = dmy_hms(Datum, tz = "Europe/Berlin"),
    Uhrzeit = as_hms(Datum)
  )
  • Mit as_hms() die Uhrzeit heraussuchen (library(hms))

Datum runden

d |>
  mutate(
    Datum = dmy_hms(Datum, tz = "Europe/Berlin"),
    D15 = floor_date(Datum, "15 minutes")
  )
  • floor_date(x, unit) rundet ab auf angegebene Einheit

Alles in einem Rutsch

d_unistrasse <- read_excel("daten/unistrasse-2017-2.xlsx", sheet = "raw(T)", range = "B2:H20712") |>
  select(Datum, Fahrzeug = Fahrzeug, L = `Länge (cm)`, v = Geschwindigkeit) |>
  filter(!is.na(Fahrzeug)) |>
  mutate(
    Datum = dmy_hms(Datum, tz = "Europe/Berlin"),
    D15 = floor_date(Datum, "15 minutes"),
    Uhrzeit = as_hms(Datum)
  )
d_unistrasse
  • Mehrere Operationen nacheinander ausgeführt
  • Lesbarkeit: Jede Operation in eine eigene Zeile

Damit: Plot Uhrzeit und Geschwindigkeit

ggplot(data = d_unistrasse) +
  geom_hex(mapping = aes(x = Uhrzeit, y = v))

  • Histogramm der Geschwindigkeitsverteilung über Uhrzeit

Bedingte Anweisung mit if_else()

d |> mutate(Raser = if_else(Geschwindigkeit >= 70, "Ja", "Nein"))
  • Erster Parameter: Bedingung als logischer Ausdruck
  • Zweiter Parameter: Wert falls Bedingung erfüllt
  • Dritter Parameter: Wert falls Bedingung nicht erfüllt

11.7 Zusammenfassen mit summarize() und group_by()

Wann braucht man das?

  • Um Ausprägungen zu zählen
  • Wenn Kenngrößen von Verteilungen benötigt werden

Nur summarize()

d_unistrasse |>
  summarise(Anzahl = n(), Mittelwert = mean(v), Standardabweichung = sd(v))
  • Wie mutate(), aber Funktionen werden auf alle Werte angewendet
  • Beobachtungen zählen mit n()
  • Funktionen mean() und sd() wie gehabt

→ In dieser Form nicht besonders nützlich

Kombination group_by() und summarize()

d_unistrasse |>
  group_by(Fahrzeug) |>
  summarise(Anzahl = n(), Mittelwert = mean(v), Standardabweichung = sd(v))
  • Daten mit group_by() nach Kriterium gruppieren
  • Danach wird mit summarize() zusammenfassen
    • Gruppierungsmerkmal(e) bleiben erhalten
    • Funktionen in summarize() werden auf Gruppen angewendet

→ In Kombination sehr flexibel einsetzbar

Beispiel: 15-Minuten Geschwindigkeit

d <- d_unistrasse |>
  group_by(D15) |>
  summarise(VMit = mean(v))
ggplot(data = d, mapping = aes(x = D15, y = VMit)) + geom_line() + geom_point()

Effekt von group_by()

d_unistrasse |> str()
tibble [20,707 × 6] (S3: tbl_df/tbl/data.frame)
 $ Datum   : POSIXct[1:20707], format: "2017-12-13 00:03:31" "2017-12-13 00:03:37" ...
 $ Fahrzeug: chr [1:20707] "PKW" "PKW" "PKW" "PKW" ...
 $ L       : num [1:20707] 421 454 456 479 421 487 387 478 493 427 ...
 $ v       : num [1:20707] 70 59 79 58 60 57 58 54 56 61 ...
 $ D15     : POSIXct[1:20707], format: "2017-12-13 00:00:00" "2017-12-13 00:00:00" ...
 $ Uhrzeit : 'hms' num [1:20707] 00:03:31 00:03:37 00:03:53 00:05:42 ...
  ..- attr(*, "units")= chr "secs"

Effekt von group_by() - Attribute zur Gruppierung

d_unistrasse |> group_by(Fahrzeug) |> str()
gropd_df [20,707 × 6] (S3: grouped_df/tbl_df/tbl/data.frame)
 $ Datum   : POSIXct[1:20707], format: "2017-12-13 00:03:31" "2017-12-13 00:03:37" ...
 $ Fahrzeug: chr [1:20707] "PKW" "PKW" "PKW" "PKW" ...
 $ L       : num [1:20707] 421 454 456 479 421 487 387 478 493 427 ...
 $ v       : num [1:20707] 70 59 79 58 60 57 58 54 56 61 ...
 $ D15     : POSIXct[1:20707], format: "2017-12-13 00:00:00" "2017-12-13 00:00:00" ...
 $ Uhrzeit : 'hms' num [1:20707] 00:03:31 00:03:37 00:03:53 00:05:42 ...
  ..- attr(*, "units")= chr "secs"
 - attr(*, "groups")= tibble [5 × 2] (S3: tbl_df/tbl/data.frame)
  ..$ Fahrzeug: chr [1:5] "LKW" "Lastzug" "PKW" "Transporter" ...
  ..$ .rows   : list<int> [1:5] 
  .. ..$ : int [1:2030] 11 34 62 68 103 115 121 153 155 161 ...
  .. ..$ : int [1:813] 95 174 183 188 231 253 279 299 322 332 ...
  .. ..$ : int [1:13995] 1 2 3 4 5 7 8 10 12 13 ...
  .. ..$ : int [1:3709] 6 9 16 25 26 28 30 32 36 38 ...
  .. ..$ : int [1:160] 63 72 154 258 662 691 895 898 991 1209 ...
  .. ..@ ptype: int(0) 
  ..- attr(*, ".drop")= logi TRUE

11.8 Daten umordnen mit pivot_longer() und pivot_wider()

Wann braucht man das?

  • pivot_longer(): Wenn Namen von Variablen Werte sein sollten
  • pivot_wider(): Wenn Werte Namen von Variablen sein sollten

→ Manchmal sind auch beide Varianten notwendig

Breite und lange Tabellen

  • Man spricht von breiten (wide) und langen (long) Tabellenformaten
  • Aufgeräumte Daten (tidy data) sind meistens lang

→ Was man wirklich braucht hängt von der konkreten Situation ab

Beispiel: Energieträger (Quelle: BMWi)

  • Unpraktisch zum Plotten und Weiterverarbeiten
  • Wir würden gerne
    • Die Spalte Energieträger beibehalten
    • Aus den Variablennamen 1990-2017 das Merkmal Jahr machen
    • Die Werte in einem Merkmal Verbrauch speichern

Einlesen der Daten (Quelle: BMWi)

d_et_wide <- read_excel("daten/energiedaten-gesamt-xls.xlsx", sheet = "4", range = "A8:AC17")

Transformation mit pivot_longer() 1/2

d_et_wide |> pivot_longer(cols = "1990":"2017", names_to = "Jahr", values_to = "Verbrauch")
  • Variablen 1990 bis 2017 umordnen
  • names_to = Name: Variable für alte Variablennamen
  • values_to = Name: Variable für Werte

Transformation mit pivot_longer() 2/2

d_et_wide |> pivot_longer(cols = !Energieträger, names_to = "Jahr", values_to = "Verbrauch")
  • Alle Variablen außer Energieträger umordnen
  • names_to = Name: Variable für alte Variablennamen
  • values_to = Name: Variable für Werte

Beispiel vollständig

d_et_long <-
  read_excel("daten/energiedaten-gesamt-xls.xlsx", sheet = "4", range = "A8:AC17") |>
  pivot_longer(!Energieträger, names_to = "Jahr", values_to = "Verbrauch") |>
  mutate(Jahr = as.numeric(Jahr))
ggplot(data = d_et_long) +
  geom_line(mapping = aes(x = Jahr, y = Verbrauch, color = Energieträger))

  • In einem Rutsch einlesen und weiterverarbeiten
  • Jahr in Zahl konvertieren mit mutate()

Werte verteilen mit pivot_wider()

d_et_long |> pivot_wider(names_from = Energieträger, values_from = Verbrauch)
  • names_from = Name: Variable mit neuen Variablennamen
  • values_from = Name: Variable mit Werten, die verteilt werden sollen
  • Damit transponierte der ursprünglichen Tabelle

11.9 Dataframes verknüpfen mit left_join()

Wann braucht man das?

  • Wenn zwei Dataframes kombiniert werden sollen
  • Wenn in einer Spalte Kürzel stehen, die in einem anderen Dataframe hinterlegt sind

Dataframes über einen Schlüssel miteinander verknüpfen

  • Tabellen sind über gemeinsames Merkmal (gelb) miteinander verknüpft
  • Neue Tabelle mit Werten zu Verknüpfungsmerkmal
  • Schlüssel in zweiter Tabelle nicht vorhanden: NAs einsetzen
  • Schlüssel müssen in zweiter Tabelle eindeutig sein!

Dataframes

d_mitarbeitende <- read_excel("daten/mitarbeitende-beispiel.xlsx", range = "B2:E7")
d_abteilungen <- read_excel("daten/mitarbeitende-beispiel.xlsx", range = "G2:I7")

Verknüpfung der Dataframes mit left_join()

d_mitarbeitende |> left_join(d_abteilungen)
  • Dataframes müssen ein Merkmal gemeinsam haben
  • Gegebenenfalls Merkmal mit rename() umbenennen (wie select())
  • Left in left_join(): Alle Zeilen aus linker Tabelle werden übernommen
  • Andere Varianten (inner_join() etc.) für Spezialfälle
  • Ausgabe von left_join unterdrücken mit #| message: false

Anwendung: Kodierte Werte 1/3

  • In den Rohdaten sind Codes eingetragen und nicht Werte
  • Warum macht man das?
    • Arbeit sparen wenn Werte von Hand eingetragen werden
    • Speicherplatz sparen bei umfangreichen Tabellen
  • Für statistische Auswertung: Dataframe mit Werten

Anwendung: Kodierte Werte 2/3

d_ud <- read_excel("daten/beispiel-kodierung.xlsx", range = "B2:D5")

d_ud_k1 <- read_excel("daten/beispiel-kodierung.xlsx", range = "F2:G5")
d_ud_k2 <- read_excel("daten/beispiel-kodierung.xlsx", range = "I2:J5")

Anwendung: Kodierte Werte 3/3

d_ud |>
  left_join(d_ud_k1) |>
  left_join(d_ud_k2) |>
  select(-ends_with("_code"))
  • Unfalldaten mit beiden Kodierungstabellen verknüpfen
  • Variable mit den Codes löschen

11.10 Dataframes aneinanderhängen bind_rows()/bind_cols()

Dataframes aneinander hängen

d1 <- tibble(X = c("A", "B"), Y = c(1, 2))
d2 <- tibble(Y = c(3, 1), Z = c(98, 99))
d1
d2

Nebeneinander

d1 |> bind_cols(d2)
  • Wichtig: Anzahl der Beobachtungen gleich
  • Doppelte Merkmale mit ...

Untereinander

d1 |> bind_rows(d2)
  • Fehlende Merkmale mit NA

11.11 Verschiedenes

Werte durch NAs ersetzen 1/4

Niederschlagsdaten vom Deutschen Wetterdienst

  • Wert -999 kennzeichnet eine fehlende Beobachtung
  • In R sollte das ein NA sein

Werte durch NAs ersetzen 2/4

d_ns <- read_delim(
  "daten/produkt_nieder_monat_18910101_20171231_00555.txt",
  delim = ";", , trim_ws = TRUE, locale = locale(decimal_mark = ".", grouping_mark = ",")
)
  • Wert -999 in fast allen Variablen

Werte durch NAs ersetzen (eine Variable) 3/4

d_ns |>
  mutate(
    MO_NSH = na_if(MO_NSH, -999)
  )
  • Mit na_if(Y, x) alle Werte x in Variable Y durch NA ersetzen

Werte durch NAs ersetzen (mehrere Variablen) 4/4

d_ns |>
  mutate(
    across(c(MO_NSH, MO_RR, MO_SH_S, MX_RS), ~na_if(.x, -999))
  )
  • Mit across die Formel ~na_if(., -999) auf mehrere Variablen anwenden

Werte auftrennen 1/2

d_z <- tibble(X = c("10, A, 4.3", "11, X, 1.9", "2, R, 3.3"))
  • Drei Werte in jeder Beobachtung

Werte auftrennen 2/2

d_z |> separate(X, c("Wert", "Name", "Laenge"))
  • Funktion separate(...) verteilt Werte auf Variablen
  • Erster Parameter: Variable mit Ausgangswerten
  • Zweiter Parameter: Vektor mit neuen Namen

Gruppiertes mutate() 1/3

d_et_long
  • Gesucht: Neue Variable mit dem anteiligen Energieverbrauch
  • Wert ist 100 x Energieverbrauch / Gesamtverbrauch im Jahr

Gruppiertes mutate() 2/3

d <- d_et_long |>
  group_by(Jahr) |>
  mutate(Anteil = 100 * Verbrauch / sum(Verbrauch))
  • Daten nach Jahren gruppieren
  • Mit sum() die Summe in einer Gruppe berechnen

Gruppiertes mutate() 3/3

d |>
  filter(Jahr == 2000)
  • Kontrolle: Die Anteile müssen sich zu 100% addieren
  • 38 + 14 + 11 + 21 + 13 + 1 + 2 = 100, passt also!

Gruppiertes slice() 1/2

d_unistrasse
  • Gesucht ist das schnellste Fahrzeug aus jeder Gruppe

Gruppiertes slice() 2/2

d_unistrasse |> select(-D15) |>
  group_by(Fahrzeug) |>
  slice_max(v, n = 1) |> ungroup()
  • Nach Fahrzeugtyp gruppieren
  • Mit slice_max(v, n = 1) schnellstes Fahrzeug heraussuchen
  • Alternativ mit summarize()

Werte ersetzen mit recode() 1/2

d_et_wide
  • Bezeichnungen teilweise zu lang
  • Verweise auf Erläuterungen für statistische Auswertung nicht hilfreich

Werte ersetzen mit recode() 2/3

d_et_wide |>
  mutate(
    Energieträger = replace_values(
      Energieträger,
      "Erdgas, Erdölgas" ~ "Gas",
      "Wasser- und Windkraft 1) 3)" ~ "Wasser/Wind",
      "andere Erneuerbare 2)" ~ "andere Erneuerbare"
    )
  )
  • Ersetzen mit "Alter Wert" ~ "Neuer Wert"

Werte ersetzen mit recode() 3/3

alt <- c("Erdgas, Erdölgas", "Wasser- und Windkraft 1) 3)", "andere Erneuerbare 2)")
neu <- c("Gas",              "Wasser/Wind",                 "andere Erneuerbare")

d_et_wide |>
  mutate(
    Energieträger = replace_values(
      Energieträger, from = alt, to = neu
    )
  )
  • Alternative mit Vektoren