Pivoting

Tidy tables

Im Tidyverse arbeitet man meist mit langen Tabellen, bei denen jede Zeile nur ein Objekt darstellt. Wickham, der Entwickler von Tidyverse, nennt olche Tabellen “tidy”.

In der Praxis hat man aber oft Tabellen, wo viele Daten nebeneinander stehen, weil diese übersichtlicher sind.

Beispieldaten

Mit dem Tidyverse-Paket mitgepackt ist folgende Tabelle mit Beispieldaten:

suppressPackageStartupMessages( library(tidyverse) )

relig_income
# A tibble: 18 × 11
   religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
   <chr>      <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>      <dbl>
 1 Agnostic      27        34        60        81        76       137        122
 2 Atheist       12        27        37        52        35        70         73
 3 Buddhist      27        21        30        34        33        58         62
 4 Catholic     418       617       732       670       638      1116        949
 5 Don’t k…      15        14        15        11        10        35         21
 6 Evangel…     575       869      1064       982       881      1486        949
 7 Hindu          1         9         7         9        11        34         47
 8 Histori…     228       244       236       238       197       223        131
 9 Jehovah…      20        27        24        24        21        30         15
10 Jewish        19        19        25        25        30        95         69
11 Mainlin…     289       495       619       655       651      1107        939
12 Mormon        29        40        48        51        56       112         85
13 Muslim         6         7         9        10         9        23         16
14 Orthodox      13        17        23        32        32        47         38
15 Other C…       9         7        11        13        13        14         18
16 Other F…      20        33        40        46        49        63         46
17 Other W…       5         2         3         4         2         7          3
18 Unaffil…     217       299       374       365       341       528        407
# ℹ 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
#   `Don't know/refused` <dbl>

Ein Blick in die Hilfeseit (?relig_income) zeigt dass es sich hier um Daten des Pew Research Center handelt.

pivot_longer

Wir möchten die Daten Plotten, aber dazu müssen wir die Tabelle “tidy” machen, d.h., diese “breite” Tabelle in eine “lange” umwandeln. Hierzu dient pivot_longer:

relig_income %>%
pivot_longer( cols = -religion, names_to = "income", values_to = "number" ) 
# A tibble: 180 × 3
   religion income             number
   <chr>    <chr>               <dbl>
 1 Agnostic <$10k                  27
 2 Agnostic $10-20k                34
 3 Agnostic $20-30k                60
 4 Agnostic $30-40k                81
 5 Agnostic $40-50k                76
 6 Agnostic $50-75k               137
 7 Agnostic $75-100k              122
 8 Agnostic $100-150k             109
 9 Agnostic >150k                  84
10 Agnostic Don't know/refused     96
# ℹ 170 more rows

Hier geschieht folgendes:

  • pivot_longer sammelt alle unter cols angegeben Spalten ein. Hier geben wir -religion, also alle Spalten außer der ersten, an.
  • Jeder Wert in den eingesammelten Spalten kommt in seine eigene Zeile.
  • Dazu werden die eingesammelten Spalten durch genau zwei Spalten ersetzt:
  • Eine Spalte enthält, was vorher die Spaltenüberschriften waren (names).
  • Die andere Spalte enthält die Werte (values) in den Spalten.
  • Wie diese beiden neue Spalten heißen sollen, wird mit names_to und values_to angegeben.

Plots

Nun können wir Plots erzeugen, z.B.

relig_income %>%
pivot_longer( cols = -religion, names_to = "income", values_to = "number" ) %>%
mutate( income = fct_inorder(income) ) %>%
ggplot +
  geom_col( aes( x=income, y=number, fill=religion ) ) 

Das fct_reorder bewirkt, dass die Balken in derselben Reihenfolge im PLot auftauchen wie in der Tabelle, statt pseudo-alphabetisch sortiert zu werden.

Ohne das pivot_longer wäre es uns nicht gelungen, den Plot zu erzeugen. Was hätten wir bei aes(x=income) angegeben?

Hier ist ein weiteres Beispiel:

Wir berechnen zunächst für jede Religion, wie viel Prozent der jeweiligen Befragten in jede Einkommenklasse fällt:

relig_income %>%
pivot_longer( cols = -religion, names_to = "income", values_to = "number" ) %>%
mutate( income = fct_inorder(income) ) %>%
group_by( religion ) %>%
mutate( percent = number / sum(number) * 100 ) -> tbl

tbl
# A tibble: 180 × 4
# Groups:   religion [18]
   religion income             number percent
   <chr>    <fct>               <dbl>   <dbl>
 1 Agnostic <$10k                  27    3.27
 2 Agnostic $10-20k                34    4.12
 3 Agnostic $20-30k                60    7.26
 4 Agnostic $30-40k                81    9.81
 5 Agnostic $40-50k                76    9.20
 6 Agnostic $50-75k               137   16.6 
 7 Agnostic $75-100k              122   14.8 
 8 Agnostic $100-150k             109   13.2 
 9 Agnostic >150k                  84   10.2 
10 Agnostic Don't know/refused     96   11.6 
# ℹ 170 more rows

Nun plotten wir dies:

tbl %>%
filter( !str_detect( religion, "refused" ) ) %>%
ggplot +
  geom_col( aes( x=income, y=percent ) ) +
  facet_wrap( ~religion ) +
  theme( axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1) )

pivot_wider

Eben haben wir für jede Religion berechnet, welche Anteil auf jede Einkommensklasse fällt:

Nun möchten wir diese Tabelle wieder in eine breite Tabelle zurück verwandeln, die nun statt der Anzahlen die Prozentwerte enthält, sonst aber genauso wie die ursprüngliche Tabelle aussieht.

Hierzu verwenden wir pivot_wider, die “Umkehrung” von pivot_longer

tbl %>%
pivot_wider( id_cols="religion", names_from="income", values_from="percent" )
# A tibble: 18 × 11
# Groups:   religion [18]
   religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
   <chr>      <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>      <dbl>
 1 Agnostic   3.27       4.12      7.26      9.81      9.20      16.6      14.8 
 2 Atheist    2.33       5.24      7.18     10.1       6.80      13.6      14.2 
 3 Buddhist   6.57       5.11      7.30      8.27      8.03      14.1      15.1 
 4 Catholic   5.19       7.66      9.09      8.32      7.92      13.9      11.8 
 5 Don’t k…   5.51       5.15      5.51      4.04      3.68      12.9       7.72
 6 Evangel…   6.07       9.17     11.2      10.4       9.30      15.7      10.0 
 7 Hindu      0.389      3.50      2.72      3.50      4.28      13.2      18.3 
 8 Histori…  11.4       12.2      11.8      11.9       9.87      11.2       6.57
 9 Jehovah…   9.30      12.6      11.2      11.2       9.77      14.0       6.98
10 Jewish     2.79       2.79      3.67      3.67      4.40      13.9      10.1 
11 Mainlin…   3.87       6.63      8.29      8.77      8.71      14.8      12.6 
12 Mormon     4.99       6.88      8.26      8.78      9.64      19.3      14.6 
13 Muslim     5.17       6.03      7.76      8.62      7.76      19.8      13.8 
14 Orthodox   3.58       4.68      6.34      8.82      8.82      12.9      10.5 
15 Other C…   6.98       5.43      8.53     10.1      10.1       10.9      14.0 
16 Other F…   4.45       7.35      8.91     10.2      10.9       14.0      10.2 
17 Other W…  11.9        4.76      7.14      9.52      4.76      16.7       7.14
18 Unaffil…   5.85       8.07     10.1       9.85      9.20      14.2      11.0 
# ℹ 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
#   `Don't know/refused` <dbl>

Hier ist folgendes geschehen:

  • Hier hat pivot_wider die beiden angegebenen Spalten auf neue Spalten verteilt:
  • Für jeden Wert in der bei names_from angegebenen Spalte wurde eine Spalte mit dem entsprechenden Namen angelegt
  • Die unter id_cols angegebenen Spalten werden beibehalten, alle anderen Spalten entfernt.
  • Für alle Werte-Kombinationen, die in in den unter id_cols angebenen Spalten auftreten, wird eine Zeile angelegt. Deshalb haben wir nun eine Zeile pro Religion.
  • Die Werte aus der bei values_from angegebenen Spalte wurden dann auf die neuen Spalten verteilt, und zwar unter Beachtung der Einträge in der names_from-Spalte (die angibt in welche Spalte der Wert soll) und id_cols, die angibt in welche Zeile er soll.

Zusammenfassung

Die beiden pivot-Funktionen erlauben uns, Tabellen zwischen “breiten” und “langen” Anordnungen der Daten zu konvertieren.