16  Daten formen

Work in Progress

Die Funktionen zum Formen von Datenrahmen werden durch die tidyverse-Bibliothek tidyr bereitgestellt.

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.2     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.2     ✔ tibble    3.2.1
✔ lubridate 1.9.2     ✔ tidyr     1.3.0
✔ purrr     1.0.1     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

16.1 Transponieren

Beim Transponieren von Datenrahmen werden die Spalten und Zeilen von Merkmalen neu ausgerichtet.

Das Transponieren von Datenrahmen ist keine vollständige Operation, sondern unterliegt je nach Ausgangsorientierung unterschiedlichen Anforderungen.

Die Funktion pivot_longer() überführt Vektoren von der Matrixform (Breitform) in die Vektorform (Langform). Alle der transponierten Vektoren müssen dafür vom gleichen Datentyp sein. Beim Transponieren in die Vektorform werden immer zwei Vektoren erzeugt:

  • Der Wertevektor, der die Werte der ursprünglichen Vektoren aufnimmt und
  • Der Namenvektor für die ursprünglichen Vektorennamen.

Falls keine anderen Angaben gemacht werden, heisst der Wertevektor value und der Namenvektor name.

Merke

Der Namenvektor ist gleichzeitig ein Sekundärindex.

Fall nicht alle Vektoren transponiert werden, dann werden die nicht transponierten Teile der Datensätze auf alle Datensätze der transponierten Vektoren erweitert.

Merke

Ein Primärindex wird nach dem Transponieren zum Sekundärindex.

Beispiel 16.1 (Transponieren in die Vektorform)  

# Ursprünglich Daten
(schweizerStaedte = 
    read_csv("geschlechter_schweizer_staedte.csv") |>
    select(-c(S, N)))
Rows: 10 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (1): Ort
dbl (7): Gesamt, S_M, S_F, N_M, N_F, S, N

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# A tibble: 10 × 6
   Ort         Gesamt    S_M    S_F   N_M   N_F
   <chr>        <dbl>  <dbl>  <dbl> <dbl> <dbl>
 1 Winterthur  118681  57832  59074   817   958
 2 Zürich      435319 214943 212778  3858  3740
 3 Biel/Bienne  55600  27243  27827   232   298
 4 Bern        137529  65215  69291  1580  1443
 5 Luzern       85840  40576  43264   837  1163
 6 Basel       178270  84896  88656  2586  2132
 7 St. Gallen   78109  38303  38628   529   649
 8 Lugano       63796  30116  32348   548   784
 9 Lausanne    145524  68516  72902  2119  1987
10 Genève      207630  97990 105850  1496  2294
# Transponieren in die Vektorform
(schweizerStaedte |>
    pivot_longer(- c(Ort, Gesamt)) ->
        schweizerStaedteLang)
# A tibble: 40 × 4
   Ort         Gesamt name   value
   <chr>        <dbl> <chr>  <dbl>
 1 Winterthur  118681 S_M    57832
 2 Winterthur  118681 S_F    59074
 3 Winterthur  118681 N_M      817
 4 Winterthur  118681 N_F      958
 5 Zürich      435319 S_M   214943
 6 Zürich      435319 S_F   212778
 7 Zürich      435319 N_M     3858
 8 Zürich      435319 N_F     3740
 9 Biel/Bienne  55600 S_M    27243
10 Biel/Bienne  55600 S_F    27827
# ℹ 30 more rows

Die Funktion pivot_wider() transponiert einen Datenrahmen von der Vektorform (Langform) in die Matrixform (Breitform). Damit diese Operation durchführbar ist, muss neben dem Wertevektor ein Sekundärindex für die Vektorennamen und ein Sekundärindex für die zeilenweise Zuordnung der Werte vorliegen. Dieser Index wird von R verwendet, um die Vektorennamen zu erzeugen: Die Operation erzeugt für jeden diskreten Wert im Sekundärindex einen Vektor für die Matrixform. Der zweite Sekundärindex wird nach dem Transponieren zum Primärindex.

Beispiel 16.2 (Transponieren in die Matrixform)  

# Daten transponieren
schweizerStaedteLang |> 
    select(-Gesamt) |>
    pivot_wider(names_from = Ort)
# A tibble: 4 × 11
  name  Winterthur Zürich `Biel/Bienne`  Bern Luzern Basel `St. Gallen` Lugano
  <chr>      <dbl>  <dbl>         <dbl> <dbl>  <dbl> <dbl>        <dbl>  <dbl>
1 S_M        57832 214943         27243 65215  40576 84896        38303  30116
2 S_F        59074 212778         27827 69291  43264 88656        38628  32348
3 N_M          817   3858           232  1580    837  2586          529    548
4 N_F          958   3740           298  1443   1163  2132          649    784
# ℹ 2 more variables: Lausanne <dbl>, Genève <dbl>

Existieren weitere Vektoren im Datenrahmen, dann werden diese beim Transponieren zusammengefasst, so dass die Werte nach dem Transponieren nur noch einmal im Ergebnis erscheinen.

Achtung

Existiert kein zweiter Sekundärindex beim Transponieren in die Breitform, dann fasst R alle Datensätze zusammen, bei denen alle Werte in den zusätzlichen Vektoren gleich sind. Dieses Verhalten kann dazuführen, dass mehr Werte transponiert werden müssen als Zieldatensätze erzeugt werden können. In solchen Fällen werden die überzähligen Werte als Listenvektor abgelegt (Beispiel 16.3). Dieses Verhalten ist normalerweise unerwünscht.

Beispiel 16.3 (Transponieren mehrdeutiger Werte in die Matrixform)  

# Ursprüngliche Daten einlesen
(abDaten = read_csv2("data_ab_semi.csv")) 
ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
Rows: 136 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ";"
chr (1): Angebot
dbl (3): Punkte, Interesse, Bedeutung

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# A tibble: 136 × 4
   Punkte Angebot Interesse Bedeutung
    <dbl> <chr>       <dbl>     <dbl>
 1   207. B               5         5
 2   101. A               2         3
 3   312. A               5         5
 4   242. B               3         2
 5   256. A               2         2
 6   188. B               6         2
 7   343. B               3         6
 8   202. A               5         5
 9   175. A               3         3
10   213. B               3         2
# ℹ 126 more rows
# Daten transponieren
abDaten |> 
    pivot_wider(names_from = Angebot, values_from = Punkte)
Warning: Values from `Punkte` are not uniquely identified; output will contain
list-cols.
• Use `values_fn = list` to suppress this warning.
• Use `values_fn = {summary_fun}` to summarise duplicates.
• Use the following dplyr code to identify duplicates.
  {data} %>%
  dplyr::group_by(Interesse, Bedeutung, Angebot) %>%
  dplyr::summarise(n = dplyr::n(), .groups = "drop") %>%
  dplyr::filter(n > 1L)
# A tibble: 31 × 4
   Interesse Bedeutung B         A        
       <dbl>     <dbl> <list>    <list>   
 1         5         5 <dbl [4]> <dbl [4]>
 2         2         3 <dbl [4]> <dbl [4]>
 3         3         2 <dbl [6]> <dbl [2]>
 4         2         2 <dbl [1]> <dbl [6]>
 5         6         2 <dbl [2]> <NULL>   
 6         3         6 <dbl [4]> <dbl [3]>
 7         3         3 <dbl [8]> <dbl [2]>
 8         2         4 <dbl [2]> <dbl [2]>
 9         4         2 <dbl [2]> <dbl [2]>
10         5         6 <dbl [1]> <dbl [1]>
# ℹ 21 more rows

16.1.1 Rezept: Operationen zusammenfassen

16.1.1.1 Problem

Es sollen mehrere Vektoren eines Datenrahmens auf die gleiche Art transformiert oder aggregiert werden.

16.1.1.2 Lösung

Die Vektoren werden zuerst in die Vektorform (Langform) transponiert. Anschliessend wird eine (gruppierte) Transformation oder gruppierte Aggregation durchgeführt.

Beispiel 16.4 (Zusammengefasstes Aggregieren)  

schweizerStaedte |> 
    pivot_longer(-c(Ort, Gesamt)) |>
    group_by(Ort, Gesamt) |>
    summarise(
        sum(value)
    )
`summarise()` has grouped output by 'Ort'. You can override using the `.groups`
argument.
# A tibble: 10 × 3
# Groups:   Ort [10]
   Ort         Gesamt `sum(value)`
   <chr>        <dbl>        <dbl>
 1 Basel       178270       178270
 2 Bern        137529       137529
 3 Biel/Bienne  55600        55600
 4 Genève      207630       207630
 5 Lausanne    145524       145524
 6 Lugano       63796        63796
 7 Luzern       85840        85840
 8 St. Gallen   78109        78109
 9 Winterthur  118681       118681
10 Zürich      435319       435319

16.1.1.3 Erklärung

Durch das transponieren entstehen ein Namenvektor und ein Wertevektor, wobei der Namenvektor auch als Sekundärindex behandelt werden kann. Sich wiederholende Operationen lassen sich so in einer gruppierten Operation zusammenfassen. Durch das Transponieren lässt sich die Code-Komplexität reduzieren, wodurch die Lösung insgesamt weniger fehleranfällig wird.

Diese Logik lässt sich auf alle Datenoperationen anwenden. Im Beispiel 16.6 werden die Anteile am Gesamtvolumen von vier Merkmalen berechnet. Ohne das Transponieren würden repetitive Codeteile entstehen (Beispiel 16.5). Die sich wiederholenden Codeteile entfallen durch das Transponieren und können in einer Operation behandelt werden.

Beispiel 16.5 (Bestimmung des Anteils mehrerer Merkmale ohne Transponieren)  

schweizerStaedte |> 
    mutate(
        Anteil_S_M = S_M / Gesamt,
        Anteil_S_F = S_F / Gesamt, 
        Anteil_N_M = N_M / Gesamt,
        Anteil_N_F = N_F / Gesamt
    ) |>
    select(c(Ort, starts_with("Anteil")))
# A tibble: 10 × 5
   Ort         Anteil_S_M Anteil_S_F Anteil_N_M Anteil_N_F
   <chr>            <dbl>      <dbl>      <dbl>      <dbl>
 1 Winterthur       0.487      0.498    0.00688    0.00807
 2 Zürich           0.494      0.489    0.00886    0.00859
 3 Biel/Bienne      0.490      0.500    0.00417    0.00536
 4 Bern             0.474      0.504    0.0115     0.0105 
 5 Luzern           0.473      0.504    0.00975    0.0135 
 6 Basel            0.476      0.497    0.0145     0.0120 
 7 St. Gallen       0.490      0.495    0.00677    0.00831
 8 Lugano           0.472      0.507    0.00859    0.0123 
 9 Lausanne         0.471      0.501    0.0146     0.0137 
10 Genève           0.472      0.510    0.00721    0.0110 

Beispiel 16.6 (Bestimmung des Anteils mehrerer Merkmale)  

schweizerStaedte |> 
    pivot_longer(- c(Ort, Gesamt)) |>
    mutate(
        value = value / Gesamt
    ) |>
    # Anpassen des Ergebnisses zur Präsentation
    select(- Gesamt) |>
    pivot_wider(names_prefix = "Anteil_")
# A tibble: 10 × 5
   Ort         Anteil_S_M Anteil_S_F Anteil_N_M Anteil_N_F
   <chr>            <dbl>      <dbl>      <dbl>      <dbl>
 1 Winterthur       0.487      0.498    0.00688    0.00807
 2 Zürich           0.494      0.489    0.00886    0.00859
 3 Biel/Bienne      0.490      0.500    0.00417    0.00536
 4 Bern             0.474      0.504    0.0115     0.0105 
 5 Luzern           0.473      0.504    0.00975    0.0135 
 6 Basel            0.476      0.497    0.0145     0.0120 
 7 St. Gallen       0.490      0.495    0.00677    0.00831
 8 Lugano           0.472      0.507    0.00859    0.0123 
 9 Lausanne         0.471      0.501    0.0146     0.0137 
10 Genève           0.472      0.510    0.00721    0.0110 

16.1.2 Rezept: Gruppiertes Zählen schöner präsentieren

16.1.2.1 Problem

Beim gruppierten Zählen werden die Sekundärindizes und die Ergebnisse nebeneinander dargestellt, so dass sich die Ergebnisse nur schwer vergleichen lassen.

16.1.2.2 Lösung

Nach dem gruppierten Zählen wird das Ergebnis in die Matrixform transponiert.

Beispiel 16.7 (Lesbarere Präsentation des gruppierten Zählen)  

daten = read_csv2("data_ab_semi.csv")
ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
Rows: 136 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ";"
chr (1): Angebot
dbl (3): Punkte, Interesse, Bedeutung

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
daten |>
    count(Angebot, Interesse) |>
    pivot_wider(
        names_from = Angebot, 
        values_from = n, 
        values_fill = 0
    )
# A tibble: 7 × 3
  Interesse     A     B
      <dbl> <int> <int>
1         1     3     2
2         2    13    11
3         3    13    25
4         4    17    22
5         5     9    10
6         6     4     6
7         7     0     1

16.1.2.3 Erklärung

Werden beim Zählen über einen Datenrahmen mehrere Sekundärindizes verwendet, ist das Ergebnis nur schwer interpretierbar. Damit das Ergebnis für Menschen leichter zu lesen ist, muss die spaltenweise Darstellung in ein eleganteres Format überführt werden. Mit der Funktion pivot_wider() lassen sich die Daten in eine Kreuztabelle überführen. Das Ergebnis ähnelt nach dem Transponieren dem Ergebnis der table()-Funktion. Auf diese Weise lassen sich Häufungen in den Werten leichter erkennen.

In der Lösung werden drei besondere Parameter eingesetzt:

  1. Der Parameter names_from zeigt an, aus welchem Index die neuen Vektornamen erzeugt werden sollen.
  2. Der Parameter values_from zeigt an, in welchem Vektor die zu transponierenden Werte stehen.
  3. der Parameter values_fill kommt zur Anwendung, wenn für eine Position kein Wert bereitsteht. Für die Zählung bedeutet das, dass nichts gezählt wurde. Deshalb wurde der Wert im Beispiel auf den Wert 0 gesetzt.

16.2 Hierarchisieren

Beim Hierarchisieren werden ausgewählte Vektoren eines Datenrahmens entlang eines Sekundärinzes zu separaten Datenrahmen organisiert und in den ursprünglichen Datenrahmen eingebettet. Über solche eingebettete Datenrahmen lassen sich hierarchische Datenstrukturen erzeugen, die beispielsweise in einem JSON- oder YAML-Dokument (s. Kapitel 7) gespeichert werden können. In R übernimmt diese Aufgabe die Funktion nest().

Weil R jedoch keine Vektoren mit Datenrahmen erlaubt, müssen diese Datenrahmen zusätzlich in eine Liste geschachtelt werden. Diese Liste dient nur zur Ablage in einem Vektor und besteht immer nur aus einem Element, nämlich dem eingebetteten Datenrahmen.

Mit der Funktion unnest() lassen sich eingebettete Datenrahmen wieder ausbetten. Diese Operation funktioniert jedoch nur dann zuverlässig, wenn alle eingebetteten Datenrahmen einheitliche Vektoren haben. Ist diese Bedingung nicht gegeben, dann erzeugt R zusätzliche Vektoren mit NA für alle Datensätze, die die Vektoren ursprünglich nicht enthielten. Die Funktion unnest() führt mehrere Spaltenkonkatenationen aus und ähnelt damit im Ergebnis der Funktion bind_rows().

Die beiden Funktionen nest() und unnest() sind speziell zur Erzeugung bzw. zum Auflösen von eingebetteten Datenrahmen gedacht. Gelegentlich enthält ein Datenrahmen einen Vektor mit einfachen Listen oder benannten Listen. Solche Vektoren sind vom Typ list und sollten mit den Funktionn unnest_longer() für einfache Listen sowie unnest_wider() für einheitlich benannte Listen (Wickham et al., 2023).

16.3 Transponieren mit Zeichenketten

Ein spezieller Fall ist gegeben, wenn die Daten in Zeichenketten kodiert sind. Streng genommen handelt es sich hierbei nicht um eine Transponierenoperation, sondern um eine normale Transformation. Die entsprechenden Funktionen für diese Operation wurden aber nach dem gleichen Prinzip wie beim Hierarchisieren bzw. beim Transponieren angepasst.

Wie beim Transponieren kann ein Vektor in die Lang- oder die Breitform getrennt werden. Für die meisten Anwendungen eignen sich zwei Funktionen:

  • separate_longer_delim()
  • separate_wider_delim()

Gelegentlich sind die Daten in einer Zeichenkette nicht über ein eindeutiges Trennzeichen, sondern über ein Trennmuster kodiert. In diesem Fall kann das Trennmuster als regulärer Ausdruck der Funktion separate_longer_regex() bzw. separate_wider_regex() übergeben werden.

Für festkodierte Werte stehen die beiden Funktionen separate_longer_position() und separate_wider_position() zur Verfügung. Diese Funktionen erwarten einen Vektor mit den Feldbreiten, um die Werte trennen zu können.

Die Umkehrfunktion für alle separate_-Funktionen ist die Funktion unite(), mit der sich die Werte aus einer Lang- oder Breitform zu einem Zeichenkettevektor verketten lassen.