13 min read

Data Transformation in R

Table of Content

  • 1 Introduction
  • 2 Overview
  • 2.1 filter()
  • 2.2 select()
  • 2.3 mutate()
  • 2.4 arrange()
  • 2.5 summarize()
  • 3 Combinations
  • 4 Conclusion
  • 5 Bibliography

1 Introduction

In dem nachfolgenden Post geht es um Data Transformation in R. Je nach Blickwinkel fällt dieses Thema in den Bereich Data Data Wrangling oder Data Exploration (Wickham & Grolemund, 2016).

Quelle: Wickham, H., & Grolemund, G. (2016). R for data science: import, tidy, transform, visualize, and model data. " O’Reilly Media, Inc."

Quelle: Wickham, H., & Grolemund, G. (2016). R for data science: import, tidy, transform, visualize, and model data. " O’Reilly Media, Inc."

Hadley Wickham, einer der Autoren des Packages dplyr, definierte hierbei die folgenden 5 Verben:

  • filter()
  • select()
  • mutate()
  • arrange()
  • summarize()

Für diese Arbeit wurde der Datensatz olympics von der Statistik-Plattform Kaggle https://www.kaggle.com verwendet. Eine Kopie des Datensatzes ist unter https://drive.google.com/open?id=1XsV3WVTb0EXjd9LhlpN2PkvUKJU8iY38 abrufbar.

library("tidyverse")
olympics <- read_csv("olympics.csv")

2 Overview

glimpse(olympics)
## Observations: 271,116
## Variables: 15
## $ ID     <int> 1, 2, 3, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7...
## $ Name   <chr> "A Dijiang", "A Lamusi", "Gunnar Nielsen Aaby", "Edgar ...
## $ Sex    <chr> "M", "M", "M", "M", "F", "F", "F", "F", "F", "F", "M", ...
## $ Age    <int> 24, 23, 24, 34, 21, 21, 25, 25, 27, 27, 31, 31, 31, 31,...
## $ Height <int> 180, 170, NA, NA, 185, 185, 185, 185, 185, 185, 188, 18...
## $ Weight <dbl> 80, 60, NA, NA, 82, 82, 82, 82, 82, 82, 75, 75, 75, 75,...
## $ Team   <chr> "China", "China", "Denmark", "Denmark/Sweden", "Netherl...
## $ NOC    <chr> "CHN", "CHN", "DEN", "DEN", "NED", "NED", "NED", "NED",...
## $ Games  <chr> "1992 Summer", "2012 Summer", "1920 Summer", "1900 Summ...
## $ Year   <int> 1992, 2012, 1920, 1900, 1988, 1988, 1992, 1992, 1994, 1...
## $ Season <chr> "Summer", "Summer", "Summer", "Summer", "Winter", "Wint...
## $ City   <chr> "Barcelona", "London", "Antwerpen", "Paris", "Calgary",...
## $ Sport  <chr> "Basketball", "Judo", "Football", "Tug-Of-War", "Speed ...
## $ Event  <chr> "Basketball Men's Basketball", "Judo Men's Extra-Lightw...
## $ Medal  <chr> NA, NA, NA, "Gold", NA, NA, NA, NA, NA, NA, NA, NA, NA,...

Wir haben es bei diesem Datensatz mit über 271k Observations zu tun. Dabei brauchen wir dieses Mal doch nur ganz ausgewählte Informationen… Mit den 5 oben genannten Befehlen lässt sich dies auch ganz leicht bewerkstelligen.

2.1 filter()

Mit dem Befehl filter() bekommen wir von R genau definierte Zeilen aus dem zugrunde liegenden Datensatz ausgegeben. Es gibt folgende Möglichkeiten zu filtern:

  • ‘==’
  • ‘!=’
  • ‘<’
  • ‘>’
  • ‘<=’
  • ‘>=’

  • “&” heißt “und”
  • “|” heißt “oder”

Als anschauliches Beispiel wollen wir nun wissen, wie viele deutsche Olympiateilnahmen es bisher gab.

olympics %>% filter(NOC == "GER")
## # A tibble: 9,830 x 15
##       ID Name  Sex     Age Height Weight Team  NOC   Games  Year Season
##    <int> <chr> <chr> <int>  <int>  <dbl> <chr> <chr> <chr> <int> <chr> 
##  1   133 Fran~ M        NA     NA     NA Germ~ GER   1900~  1900 Summer
##  2   389 Adol~ M        45     NA     NA Germ~ GER   1928~  1928 Summer
##  3   389 Adol~ M        45     NA     NA Germ~ GER   1928~  1928 Summer
##  4   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  5   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  6   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  7   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  8   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  9   402 Arth~ M        22    184     85 Germ~ GER   2008~  2008 Summer
## 10   402 Arth~ M        30    184     85 Germ~ GER   2016~  2016 Summer
## # ... with 9,820 more rows, and 4 more variables: City <chr>, Sport <chr>,
## #   Event <chr>, Medal <chr>

Nicht schlecht, doch fast 10.000 Teilnahmen. Und wie schaut es mit der Geschlechteraufteilung aus? Wir filtern zuerst nach den männlichen Teilnehmern. Dabei kann man den vorherigen Befehl mit einem &-Zeichen um ein weiteres Kriterium erweitern.

olympics %>% filter(NOC == "GER" & Sex == "M")
## # A tibble: 6,831 x 15
##       ID Name  Sex     Age Height Weight Team  NOC   Games  Year Season
##    <int> <chr> <chr> <int>  <int>  <dbl> <chr> <chr> <chr> <int> <chr> 
##  1   133 Fran~ M        NA     NA     NA Germ~ GER   1900~  1900 Summer
##  2   389 Adol~ M        45     NA     NA Germ~ GER   1928~  1928 Summer
##  3   389 Adol~ M        45     NA     NA Germ~ GER   1928~  1928 Summer
##  4   402 Arth~ M        22    184     85 Germ~ GER   2008~  2008 Summer
##  5   402 Arth~ M        30    184     85 Germ~ GER   2016~  2016 Summer
##  6   505 "Ale~ M        25    186     84 Germ~ GER   1912~  1912 Summer
##  7   508 Bern~ M        NA     NA     NA Germ~ GER   1906~  1906 Summer
##  8   561 Thom~ M        26    184     73 Germ~ GER   1994~  1994 Winter
##  9   561 Thom~ M        26    184     73 Germ~ GER   1994~  1994 Winter
## 10   586 Tobi~ M        31    178     85 Germ~ GER   2002~  2002 Winter
## # ... with 6,821 more rows, and 4 more variables: City <chr>, Sport <chr>,
## #   Event <chr>, Medal <chr>

6,8k männliche Teilnehmer! Und jetzt die weiblichen mit dem gleichen Befehl leicht abgewandelt:

olympics %>% filter(NOC == "GER" & Sex != "M")
## # A tibble: 2,999 x 15
##       ID Name  Sex     Age Height Weight Team  NOC   Games  Year Season
##    <int> <chr> <chr> <int>  <int>  <dbl> <chr> <chr> <chr> <int> <chr> 
##  1   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  2   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  3   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  4   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  5   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  6   769 Leon~ F        23    162     54 Germ~ GER   2016~  2016 Summer
##  7   796 Sabi~ F        32    172     68 Germ~ GER   1992~  1992 Summer
##  8   827 Mare~ F        26    174     73 Germ~ GER   2016~  2016 Summer
##  9   893 Ulri~ F        23    170     60 Germ~ GER   1994~  1994 Winter
## 10   981 Katr~ F        29    177     62 Germ~ GER   1996~  1996 Summer
## # ... with 2,989 more rows, and 4 more variables: City <chr>, Sport <chr>,
## #   Event <chr>, Medal <chr>

!= bedeutet “nicht”. Wir sehen fast 3k weibliche Teilnehmer, die bisher an Olympia teilnahmen. Nun möchten wir wissen, wie viele deutsche Teilnehmer zwischen 20 und 30 Jahre alt waren:

olympics %>% filter(NOC == "GER" & Age >= 20 & Age <= 30)
## # A tibble: 7,113 x 15
##       ID Name  Sex     Age Height Weight Team  NOC   Games  Year Season
##    <int> <chr> <chr> <int>  <int>  <dbl> <chr> <chr> <chr> <int> <chr> 
##  1   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  2   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  3   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  4   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  5   396 Katj~ F        25    165     55 Germ~ GER   2008~  2008 Summer
##  6   402 Arth~ M        22    184     85 Germ~ GER   2008~  2008 Summer
##  7   402 Arth~ M        30    184     85 Germ~ GER   2016~  2016 Summer
##  8   505 "Ale~ M        25    186     84 Germ~ GER   1912~  1912 Summer
##  9   561 Thom~ M        26    184     73 Germ~ GER   1994~  1994 Winter
## 10   561 Thom~ M        26    184     73 Germ~ GER   1994~  1994 Winter
## # ... with 7,103 more rows, and 4 more variables: City <chr>, Sport <chr>,
## #   Event <chr>, Medal <chr>

2.2 select()

Im Vergleich zu dem filter() Befehlt wählt select() nicht einzelne Zeilen, sondern ganze Spalten aus. Sehr häufig kommt es vor, dass Datensätze ganz viele Spalten besitzen, die man für die anstehende Analyse überhaupt nicht braucht. Um den Datensatz übersichtlicher zu gestalten, hat man mit select() die Möglichkeit einzelne Spalten gezielt auszuwählen.

Nachfolgend interessiert mich, ob es einen Zusammenhang zwischen der Körpergröße und dem Körpergewicht gibt. Daführ wähle ich genau diese beiden Spalten aus:

olympics %>% select(Height, Weight)
## # A tibble: 271,116 x 2
##    Height Weight
##     <int>  <dbl>
##  1    180     80
##  2    170     60
##  3     NA     NA
##  4     NA     NA
##  5    185     82
##  6    185     82
##  7    185     82
##  8    185     82
##  9    185     82
## 10    185     82
## # ... with 271,106 more rows

Da ich bei Korrelationen keine fehlenden Werte gebrauchen kann, lösche ich diese mit na.omit raus und lasse mir mit cor() die Stärke des Zusammenhanges ausgeben:

olympics %>% select(Height, Weight) %>% na.omit() %>% cor()
##           Height    Weight
## Height 1.0000000 0.7962131
## Weight 0.7962131 1.0000000

Alterntiv kann der Befehl auch so geschrieben werden:

olympics %>% select(Height, Weight) %>% cor(use = "complete.obs")
##           Height    Weight
## Height 1.0000000 0.7962131
## Weight 0.7962131 1.0000000

Bei r=0,8 kann man von einem starken Zusammenhang sprechen.

2.3 mutate()

Für und während des Analyseprozesses werden wir kaum drum herum kommen, neue Variablen aus dem Datensatz heraus zu kreieren. Der mutate() Befehl tut genau dies. Die Funktion ist wie folgt aufgebaut: mutate(“Name neuer Variable” = “Var1” plus/minus/geteilt “Var2”). Benutzt man nicht wie wir im nachfolgenden Beispiel die Pipe Funktion (%>%), muss vor dem Namen der neuen Variable der zugrunde liegende Datensatz genannt werden. Nachfolgend interessiert mich nun, welchen BMI (Body-Mass-Index) die Sportler hatten. Da der Datensatz diese Variable (noch) nicht hat, erzeugen wir sie eben selbst:

olympics %>% mutate(bmi = Weight / (Height/100)^2)
## # A tibble: 271,116 x 16
##       ID Name  Sex     Age Height Weight Team  NOC   Games  Year Season
##    <int> <chr> <chr> <int>  <int>  <dbl> <chr> <chr> <chr> <int> <chr> 
##  1     1 A Di~ M        24    180     80 China CHN   1992~  1992 Summer
##  2     2 A La~ M        23    170     60 China CHN   2012~  2012 Summer
##  3     3 Gunn~ M        24     NA     NA Denm~ DEN   1920~  1920 Summer
##  4     4 Edga~ M        34     NA     NA Denm~ DEN   1900~  1900 Summer
##  5     5 Chri~ F        21    185     82 Neth~ NED   1988~  1988 Winter
##  6     5 Chri~ F        21    185     82 Neth~ NED   1988~  1988 Winter
##  7     5 Chri~ F        25    185     82 Neth~ NED   1992~  1992 Winter
##  8     5 Chri~ F        25    185     82 Neth~ NED   1992~  1992 Winter
##  9     5 Chri~ F        27    185     82 Neth~ NED   1994~  1994 Winter
## 10     5 Chri~ F        27    185     82 Neth~ NED   1994~  1994 Winter
## # ... with 271,106 more rows, and 5 more variables: City <chr>,
## #   Sport <chr>, Event <chr>, Medal <chr>, bmi <dbl>

2.4 arrange()

Für die Erläuterung des arrange() Befehls bleiben wir in unserer Analyse noch ein wenig bei der gerade neu erzeugten Variable ‘bmi’. Arrange() sortiert einen Vektor neu. Dies kann aufsteigend oder absteigend erfolgen:

olympics %>% mutate(bmi = Weight / (Height/100)^2) %>% arrange(bmi)
## # A tibble: 271,116 x 16
##        ID Name  Sex     Age Height Weight Team  NOC   Games  Year Season
##     <int> <chr> <chr> <int>  <int>  <dbl> <chr> <chr> <chr> <int> <chr> 
##  1 134370 "Alb~ M        24    183     28 Unit~ USA   1956~  1956 Summer
##  2 110533 Lia ~ F        31    169     30 Braz~ BRA   2016~  2016 Summer
##  3  33539 Bndi~ F        17    176     38 Belg~ BEL   1992~  1992 Summer
##  4  33539 Bndi~ F        17    176     38 Belg~ BEL   1992~  1992 Summer
##  5  33539 Bndi~ F        17    176     38 Belg~ BEL   1992~  1992 Summer
##  6  33539 Bndi~ F        17    176     38 Belg~ BEL   1992~  1992 Summer
##  7  33539 Bndi~ F        17    176     38 Belg~ BEL   1992~  1992 Summer
##  8   5381 Mart~ F        16    160     32 Spain ESP   1984~  1984 Summer
##  9   5381 Mart~ F        16    160     32 Spain ESP   1984~  1984 Summer
## 10   5381 Mart~ F        16    160     32 Spain ESP   1984~  1984 Summer
## # ... with 271,106 more rows, and 5 more variables: City <chr>,
## #   Sport <chr>, Event <chr>, Medal <chr>, bmi <dbl>

Oh! Ein Mann mit 1,83m Körpergröße und nur 28kg Gewicht (bmi = 8,36) … komisch, das kann wohl nicht sein.

Nun das Ganze für einen absteigenden BMI Wert:

olympics %>% mutate(bmi = Weight / (Height/100)^2) %>% arrange(desc(bmi))
## # A tibble: 271,116 x 16
##        ID Name  Sex     Age Height Weight Team  NOC   Games  Year Season
##     <int> <chr> <chr> <int>  <int>  <dbl> <chr> <chr> <chr> <int> <chr> 
##  1  12177 Rica~ M        21    183    214 Guam  GUM   2008~  2008 Summer
##  2  12177 Rica~ M        25    183    214 Guam  GUM   2012~  2012 Summer
##  3  87041 Dmit~ M        24    176    175 Russ~ RUS   2004~  2004 Summer
##  4  45319 Shan~ M        28    173    161 Unit~ USA   2000~  2000 Summer
##  5  45319 Shan~ M        32    173    161 Unit~ USA   2004~  2004 Summer
##  6  74435 Holl~ F        22    173    155 Unit~ USA   2012~  2012 Summer
##  7 103580 Vale~ M        28    187    180 Ukra~ UKR   2000~  2000 Summer
##  8  62843 Olha~ F        18    181    167 Ukra~ UKR   2004~  2004 Summer
##  9  62843 Olha~ F        22    181    167 Ukra~ UKR   2008~  2008 Summer
## 10 103413 Leon~ M        29    187    178 Vene~ VEN   2004~  2004 Summer
## # ... with 271,106 more rows, and 5 more variables: City <chr>,
## #   Sport <chr>, Event <chr>, Medal <chr>, bmi <dbl>

Nicht schlecht, wieder ein 1,83m großer Mann an der Spitze mit 214kg Körpergewicht. Aber dieses Mal handelt es sich um einen Judoka und das ist wiederum schon ein wenig realisitscher.

2.5 summarize()

Der Letzte der 5 hier behandelten Befehle für Data Transformation ist summarize(). Er wird fast ausschließlich mit dem group_by() Befehl verwendet. Diese Kombination ermöglicht Vergleiche. Wird summarize() alleine verwendet, bricht er den Dataframe auf eine einzelne Zeile herunter.

Tipp: Um sicher zu gehen, dass die Aggregation richtig erfolgt, verwende ich n() bei jedem summarize() Befehl:

olympics %>% summarize(N = n(), first_year = min(Year), last_year = max(Year), avg_age = mean(na.omit(Age)))
## # A tibble: 1 x 4
##        N first_year last_year avg_age
##    <int>      <dbl>     <dbl>   <dbl>
## 1 271116       1896      2016    25.6

Wir sehen, dass die Olympischen Spiele ab 1896 in unserem Datensatz aufgeführt sind. Die letzten fanden 2016 statt. Durchschnittlich waren die Teilnehmer 25,6 Jahre alt.

Wie zuvor bereits erwähnt, erlaubt uns der summarize() Befehl in Verbindung mit dem group_by() Befehl zu vergleichen. Mit group_by wird eine Varibale des Datensatzes entsprechend groupiert:

olympics %>% group_by(Year) %>% summarize(N = n(), avg_age = mean(na.omit(Age)))
## # A tibble: 35 x 3
##     Year     N avg_age
##    <int> <int>   <dbl>
##  1  1896   380    23.6
##  2  1900  1936    29.0
##  3  1904  1301    26.7
##  4  1906  1733    27.1
##  5  1908  3101    27.0
##  6  1912  4040    27.5
##  7  1920  4292    29.3
##  8  1924  5693    28.4
##  9  1928  5574    29.1
## 10  1932  3321    32.6
## # ... with 25 more rows

Nun können wir das Durchschnittsalter der Athleten pro Jahr vergleichen.

3 Combinations

In der Praxis werden meist die Funktionen in Kombination miteinander benutzt:

olympics %>% filter(NOC == "GER") %>%
  select(Age, Height, Weight, Year) %>%
    mutate(bmi = Weight / (Height/100)^2) %>%
      group_by(Year) %>%
        summarize(N = n(), avg_bmi = mean(na.omit(bmi)))
## # A tibble: 26 x 3
##     Year     N avg_bmi
##    <int> <int>   <dbl>
##  1  1896    94    24.5
##  2  1900   103    25.0
##  3  1904    55    23.9
##  4  1906   157    23.4
##  5  1908   148    21.6
##  6  1912   312    22.2
##  7  1928   450    22.4
##  8  1932   225    23.1
##  9  1936   648    22.9
## 10  1952   442    22.8
## # ... with 16 more rows

Was haben wir hierbei mit einem einzigen Befehl alles gemacht? Als Ergebnis sehen wir den durchschnittlichen BMI Wert von allen deutschen Olympiateilnehmern aufgeteilt nach Jahren.

Das Ganze ist auch ziemlich leicht und schnell graphisch dargestellt:

all<- olympics %>% filter(NOC == "GER") %>%
        select(Age, Height, Weight, Year) %>%
          mutate(bmi = Weight / (Height/100)^2) %>%
           group_by(Year) %>%
              summarize(N = n(), avg_bmi = mean(na.omit(bmi)))

plot(all$Year, all$avg_bmi, type = "l")

4 Conclusion

Mit nur sehr wenigen Befehlen, kann man schon recht viel in Sachen Datentransformation erreichen. Wie man anhand des letzten Beispiels gesehen hat, harmonieren die Packages ‘dplyr’ und ‘ggplot2’ sehr gut miteinander.

5 Bibliography

Wickham, H., & Grolemund, G. (2016). R for data science: import, tidy, transform, visualize, and model data. " O’Reilly Media, Inc.“.