Kapitola 4 Transformácia údajov a súhrny pomocou dplyr

V tejto kapitole preberieme moderné nástroje na manipuláciu so súbormi údajov (ktoré sú už v čistej tabuľkovej forme - viac o tom v ďalšej kapitole), informácie pochádzajú najmä z online verzií kníh (Wickham a Grolemund 2016, kap. 5), (Ismay a Kim 2019, kap. 3) a (R. D. Peng 2016, kap. 13).

4.1 Všeobecne

Základnou dátovou štruktúrou v R (kontajnerom na údaje) pre ďalšie štatistické spracovanie je data.frame, v ktorom každý riadok predstavuje jedno pozorovanie (meranie, záznam…) a každý stĺpec jednu premennú (veličinu, mieru, vlastnosť, charakteristiku, znak…). Ukázali sme si už základné nástroje na vytváranie a manipuláciu s dátovými rámcami ako napr. subsetting ($, [ ], subset), avšak iné operácie ako komplexnejšie filtrovanie (výber pozorovaní podľa zadaných kritérií), zoraďovanie či tvorenie súhrnov môžu byť trochu únavné a neprehľadné, syntax jazyka R totiž nie je veľmi intuitívna. Toto sa snaží odstrániť balík dplyr (Wickham et al. 2020), ktorý implementuje konzistentnú “gramatiku” (v súlade s názvoslovím databázového jazyka SQL - Structured Query Language) a je veľmi rýchly. Kľúčovými funkciami sú:

  • select - výber stĺpcov

  • filter - výber riadkov na základe logických podmienok

  • arrange - zoradenie riadkov

  • rename - premenovanie stĺpcov (premenných)

  • mutate - pridanie nových stĺpcov napr. transformáciou iných

  • summarise, summarize - generovanie podmienených súhrnov pre daný stĺpec

  • %>% - pipe operátor pre reťazenie príkazov (analógia skladania funkcií v matematike)

Balík je súčasťou ekosystému tidyverse (Wickham et al. 2019), čo je kolekcia balíkov navrhnutých pre data science a vychádzajúcich zo spoločnej filozofie, gramatiky a dátových štruktúr. Funkcie zdieľajú niekoľko spoločných vlastností:

  1. prvý argument je data frame,
  2. ďalšie argumenty špecifikujú, čo sa má s dátami z prvého argumentu urobiť, pričom na stĺpce stačí odkázať menom,
  3. výstupom je opäť data frame,
  4. dátové rámce musia byť riadne formátované, “čisté” (tidy)

4.2 Prakticky

Balík pri načítaní predefinuje niektoré známe funkcie, preto je dobrým zvykom písať funkcie celým menom, napr. base::filter()

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

Prvou kľúčovou funkciou je select, ktorou sa vyberá podmnožina stĺpcov. Z datasetu mtcars pre ilustráciu vyberme postupne stĺpce - najprv jednotlivo po mene, napr. dojazd, hmotnosť, potom všetky stĺpce začínajúce písmenom “c” okrem carb, ďalej ôsmy stĺpec a nakoniec všetky stĺpce v poradí od zdvihového objemu až po drat:

data(mtcars)
tmp <- select(mtcars, c(mpg, wt), starts_with("c"), - carb, 8, disp:drat)
head(tmp)
##                    mpg    wt cyl vs disp  hp drat
## Mazda RX4         21.0 2.620   6  0  160 110 3.90
## Mazda RX4 Wag     21.0 2.875   6  0  160 110 3.90
## Datsun 710        22.8 2.320   4  1  108  93 3.85
## Hornet 4 Drive    21.4 3.215   6  1  258 110 3.08
## Hornet Sportabout 18.7 3.440   8  0  360 175 3.15
## Valiant           18.1 3.460   6  1  225 105 2.76

Týchto pomocných funkcií je ešte oveľa viac: ends_with, contains, matches, num_range, one_of, everything.

Pre zmenu, výber riadkov zabezpečuje funkcia filter. Obmedzme výber na všetky autá s hmotnosťou pod 3000 libier a výkonom nad 150 koní:

filter(tmp, wt < 3.0 & hp > 150)
##               mpg   wt cyl vs disp  hp drat
## Ferrari Dino 19.7 2.77   6  0  145 175 3.62

Na formulovanie podmienok sa dajú použiť aj funkcie ako is.na, between, near.

Zoradenie riadkov podľa jedného alebo viacerých stĺpcov zabezpečuje arrange, napr. tu zoradíme autá primárne podľa počtu valcov zostupne a sekundárne podľa uloženia valcov.

arrange(tmp, desc(cyl), vs)
##                      mpg    wt cyl vs  disp  hp drat
## Hornet Sportabout   18.7 3.440   8  0 360.0 175 3.15
## Duster 360          14.3 3.570   8  0 360.0 245 3.21
## Merc 450SE          16.4 4.070   8  0 275.8 180 3.07
## Merc 450SL          17.3 3.730   8  0 275.8 180 3.07
## Merc 450SLC         15.2 3.780   8  0 275.8 180 3.07
## Cadillac Fleetwood  10.4 5.250   8  0 472.0 205 2.93
## Lincoln Continental 10.4 5.424   8  0 460.0 215 3.00
## Chrysler Imperial   14.7 5.345   8  0 440.0 230 3.23
## Dodge Challenger    15.5 3.520   8  0 318.0 150 2.76
## AMC Javelin         15.2 3.435   8  0 304.0 150 3.15
## Camaro Z28          13.3 3.840   8  0 350.0 245 3.73
## Pontiac Firebird    19.2 3.845   8  0 400.0 175 3.08
## Ford Pantera L      15.8 3.170   8  0 351.0 264 4.22
## Maserati Bora       15.0 3.570   8  0 301.0 335 3.54
## Mazda RX4           21.0 2.620   6  0 160.0 110 3.90
## Mazda RX4 Wag       21.0 2.875   6  0 160.0 110 3.90
## Ferrari Dino        19.7 2.770   6  0 145.0 175 3.62
## Hornet 4 Drive      21.4 3.215   6  1 258.0 110 3.08
## Valiant             18.1 3.460   6  1 225.0 105 2.76
## Merc 280            19.2 3.440   6  1 167.6 123 3.92
## Merc 280C           17.8 3.440   6  1 167.6 123 3.92
## Porsche 914-2       26.0 2.140   4  0 120.3  91 4.43
## Datsun 710          22.8 2.320   4  1 108.0  93 3.85
## Merc 240D           24.4 3.190   4  1 146.7  62 3.69
## Merc 230            22.8 3.150   4  1 140.8  95 3.92
## Fiat 128            32.4 2.200   4  1  78.7  66 4.08
## Honda Civic         30.4 1.615   4  1  75.7  52 4.93
## Toyota Corolla      33.9 1.835   4  1  71.1  65 4.22
## Toyota Corona       21.5 2.465   4  1 120.1  97 3.70
## Fiat X1-9           27.3 1.935   4  1  79.0  66 4.08
## Lotus Europa        30.4 1.513   4  1  95.1 113 3.77
## Volvo 142E          21.4 2.780   4  1 121.0 109 4.11

Premenovanie stĺpcov pomocou rename:

tmp <- rename(tmp, wt_lbs = wt, disp_in3 = disp)
head(tmp)
##                    mpg wt_lbs cyl vs disp_in3  hp drat
## Mazda RX4         21.0  2.620   6  0      160 110 3.90
## Mazda RX4 Wag     21.0  2.875   6  0      160 110 3.90
## Datsun 710        22.8  2.320   4  1      108  93 3.85
## Hornet 4 Drive    21.4  3.215   6  1      258 110 3.08
## Hornet Sportabout 18.7  3.440   8  0      360 175 3.15
## Valiant           18.1  3.460   6  1      225 105 2.76

Vytvorenie nových stĺpcov cez funkciu mutate, v ktorej opäť možno použiť množstvo pomocných funkcií, napr. recode, if_else … (pozri nápovedu):

tmp <- mutate(tmp, disp_dm3 = disp_in3 * 16e-3, disp2cyl_dm3 = disp_dm3 / cyl)
head(tmp)
##                    mpg wt_lbs cyl vs disp_in3  hp drat disp_dm3 disp2cyl_dm3
## Mazda RX4         21.0  2.620   6  0      160 110 3.90    2.560    0.4266667
## Mazda RX4 Wag     21.0  2.875   6  0      160 110 3.90    2.560    0.4266667
## Datsun 710        22.8  2.320   4  1      108  93 3.85    1.728    0.4320000
## Hornet 4 Drive    21.4  3.215   6  1      258 110 3.08    4.128    0.6880000
## Hornet Sportabout 18.7  3.440   8  0      360 175 3.15    5.760    0.7200000
## Valiant           18.1  3.460   6  1      225 105 2.76    3.600    0.6000000

Výpočet štatistických súhrnov je možný pomocou summarize.

summarize(tmp, mpg_mean = mean(mpg), hp_mean = mean(hp)) 
##   mpg_mean  hp_mean
## 1 20.09062 146.6875

Táto funkcia však ukáže svoju silu až v kombinácii s group_by. Vypočítajme napríklad priemerný dojazd a výkon motora podľa počtu valcov a ich uloženia.

summarize(group_by(mtcars, cyl, vs), 
          mpg_mean = mean(mpg), hp_mean = mean(hp), number_obs = n(),
          .groups = "drop"
)
## # A tibble: 5 x 5
##     cyl    vs mpg_mean hp_mean number_obs
## * <dbl> <dbl>    <dbl>   <dbl>      <int>
## 1     4     0     26      91            1
## 2     4     1     26.7    81.8         10
## 3     6     0     20.6   132.           3
## 4     6     1     19.1   115.           4
## 5     8     0     15.1   209.          14

Všimnime si, že výsledné dáta sú uložené v dátovej štruktúre podobnej dátovému rámcu, ktorá sa volá tibble a je súčasťou tidyverse. Oproti data.frame a) pri vzniku tibble sa nikdy nezmení názov ani typ premennej, b) vo výpise sa zobrazujú iba stĺpce, ktoré sa zmestia na obrazovku, niekoľko prvých riadkov a dátový typ stĺpca, c) volanie neexistujúceho stĺpca skončí chybou namiesto výsledku Null, d) subsetovanie [ vždy vráti tibble a [[ vždy vektor, e) pri definovaní stĺpca sa zrecykluje iba vektor dĺžky 1, a nakoniec f) tibble nevytvára ani nepoužíva názvy riadkov.

Na záver si predstavíme jeden veľmi užitočný a návykový nástroj, ktorým sa dá vyhnúť vytvoreniu pomocných/dočasných objektov vo výpočtovom prostredí a celkovo sprehľadňuje zdrojový kód. Je ním pipe operátor %>% importovaný z balíku magrittr (kde má ešte množstvo rôznych modifikácií) a slúži na reťazenie príkazov podobne ako skladáme funkcie, napr. x %>% f() %>% g() vykoná to isté ako g(f(x)).
Vypočítajme napr. priemerný dojazd všetkých automobilov s priamou orientáciou valcov a to podľa typu prevodovky a v jednotkách km/l.

mtcars %>% 
  filter(vs == 1) %>% 
  mutate(kmpl = 0.43 * mpg) %>% 
  group_by(am) %>%
  summarize(priemerny_dojazd = mean(kmpl)) %>% 
  ungroup()   # to isté ako .groups = "drop"
## # A tibble: 2 x 2
##      am priemerny_dojazd
## * <dbl>            <dbl>
## 1     0             8.92
## 2     1            12.2

So základnými nástrojmi R bez použitia dplyr by to vyzeralo napr. takto:

tmp <- subset(mtcars, vs == 1)
aggregate(kmpl ~ am, 
          data = cbind(tmp, kmpl = 0.43*tmp$mpg), 
          FUN = function(x) mean(x)
)
##   am      kmpl
## 1  0  8.919429
## 2  1 12.199714

Jednoduchšou (a predvídateľnejšou) alternatívou ku %>% je pipe operátor %>>% z balíku pipeR.

Hoci balík dplyr sprístupňuje pipe operátor, tento v ďalších kapitolách bude používaný nezávisle (nebude načítaný ani dplyr ani magrittr) a to pomocou príkazu:

`%>%` <- magrittr::`%>%` 

4.3 Cvičenie

  1. Načítajte data frame Cars93 z balíka MASS.
  2. Vytvorte nový data frame auta93 výberom všetkých premenných, ktoré spĺňajú aspoň jednu z nasledujúcich podmienok
    • prvé tri (použite operátor sekvencie :)
    • ich názov obsahuje “Price” ale neobsahuje “.Price” (contains, operator -)
    • ich názov sa začína na “MPG” (start_with)
    • všetky od indikátora airbagov až po výkon motora okrem typu pohonu DriveTrain (operátory :,-)
    • hmotnosť a pôvod vozidla
  3. Pipe operátorom vytvorte sekvenciu nasledujúcich príkazov
    1. auta93
    2. premenovanie premennej EngineSize na CylindersVolume (rename)
    3. prevod hmotnosti z libier na kilogramy (mutate)
    4. výber všetkých amerických automobilov s hmotnosťou do 1200 kg (filter)
    5. zoradenie primárne podľa kategórie auta Type a v druhom rade podľa ceny vzostupne (arrange)
    6. vypísanie (print)
    7. združenie podľa kategórie auta a výpočet priemerneho dojazdu v meste (group_by, summarise).