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, čiže s dátovými rámcami. (O tom, ako dáta dostať do čistej formy, pojednáva kapitola 6.) Informácie pochádzajú najmä zo zdrojov (Wickham a Grolemund 2016, kap. 5; Ismay a Kim 2019, kap. 3; R. D. Peng 2016, kap. 13).
4.1 Všeobecne
Základnou dátovou štruktúrou (kontajnerom na údaje) v R pre ďalšie štatistické spracovanie je dátový rámec (data.frame). V ňom každý riadok predstavuje jedno pozorovanie (meranie, záznam…) a každý stĺpec jednu premennú (veličinu, mieru, vlastnosť, charakteristiku, štatistický znak…). Už sme si ukázali základné nástroje na vytváranie a manipuláciu s dátovými rámcami ako napr. výber prvkov ($, [ ], subset), avšak zložitejšie operácie ako napr. filtrovanie (výber pozorovaní podľa zadaných kritérií), zoraďovanie riadkov či tvorenie súhrnov (agregácia hodnôt premenných) 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. 2021, 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 dplyr 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 v tejto kolekcii zdieľajú niekoľko spoločných vlastností:
- prvý argument je data frame,
- ďalšie argumenty špecifikujú, čo sa má s dátami z prvého argumentu urobiť, pričom na stĺpce stačí odkázať menom (t. j. názvom elementu, bez príslušnosti ku dátovému objektu),
- výstupom je opäť data frame,
- dátové rámce musia byť riadne formátované, „čisté” (angl. 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 (aj s príslušnosťou ku balíku), napr. stats::filter()
zavolá funkciu filter z predinštalovaného balíka stats a nie rovnomennú funkciu z balíka dplyr.
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 funkcia select, ktorou sa vyberá podmnožina stĺpcov. V nasledujúcom príklade z datasetu mtcars pre ilustráciu vyberme postupne stĺpce – najprv jednotlivo po mene dojazd a 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)
<- select(mtcars, c(mpg, wt), starts_with("c"), - carb, 8, disp:drat)
tmp 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í na výber stĺpcov je ešte oveľa viac: ends_with, contains, matches, num_range, all_of, any_of, everything. Spolu s nimi môžme používať známe operátory sekvencie :
, negácie !
, logického súčtu |
a súčinu &
, a kombinačnú funkciu c()
.
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, a tiež porovnávacie a logické operátory.
Zoradenie riadkov podľa jedného alebo viacerých stĺpcov zabezpečuje funkcia arrange. Prednastavené je vzostupné radenie (angl. ascending). Tu napríklad zoradíme autá primárne podľa počtu valcov zostupne (angl. descending) a sekundárne podľa uloženia valcov vzostupne.
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 funkcie rename má syntax nové = staré
:
<- rename(tmp, wt_lbs = wt, disp_in3 = disp)
tmp 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). V nasledujúcom príklade vytvoríme stĺpec s objemom valcov v metrickej sústave a hneď ho použijeme na vyjadrenie objemu jedného valca:
<- mutate(tmp, # objekt, z ktorého sa vychádza
tmp disp_dm3 = disp_in3 * 16e-3,
disp1cyl_dm3 = disp_dm3 / cyl # stĺpec disp_dm3 už existuje
) head(tmp)
## mpg wt_lbs cyl vs disp_in3 hp drat disp_dm3 disp1cyl_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 funkcie summarize. Nasledujúci príkaz vypočíta priemernú hodnotu dojazdu a výkonu.
summarize(tmp, mpg_mean = mean(mpg), hp_mean = mean(hp))
## mpg_mean hp_mean
## 1 20.09062 146.6875
Ak chceme aplikovať jednu alebo viac agregačných funkcií postupne na viacero stĺpcov, pomôžeme si funkciou across:
summarize(mtcars,
across(.cols = c(mpg:hp, -cyl), # výber je podobný ako pri select()
.fns = mean # viac funkcií na aplikovanie sa vloží cez list()
)
)## mpg disp hp
## 1 20.09062 230.7219 146.6875
Funkcia summarize ukáže svoju plnú silu až v kombinácii s funkciou 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" # zruší vnútorné členenie na skupiny
)## # A tibble: 5 × 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 systému tidyverse. Rozdiely oproti data.frame sú také, že
- pri vzniku tibble sa nikdy nezmení názov ani typ premennej,
- vo výpise sa zobrazujú iba stĺpce, ktoré sa zmestia na obrazovku, niekoľko prvých riadkov a dátový typ stĺpca,
- volanie neexistujúceho stĺpca skončí chybou namiesto výsledku Null,
- výber prvkov pomocou
[
vždy vráti tibble a[[
vždy vektor, - pri definovaní stĺpca sa zrecykluje iba vektor dĺžky 1,
- tibble nevytvára ani nepoužíva názvy riadkov.
Na záver si predstavíme jeden veľmi užitočný a (subjektívne) 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 niekoľko 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))
. V prostredí RStudio sa vkladá klávesovou skratkou [Ctrl] + [Shift] + [M].
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:
%>% # východiskový dátový objekt
mtcars filter(vs == 1) %>% # ponechaj iba riadky so straight engine
mutate(kmpl = 0.43 * mpg) %>% # pridaj stĺpec dojazdu v iných jednotkách
group_by(am) %>% # zoskup podľa typu prevodovky
summarize(priemerny_dojazd = mean(kmpl)) # vypočítaj priemer
## # A tibble: 2 × 2
## am priemerny_dojazd
## <dbl> <dbl>
## 1 0 8.92
## 2 1 12.2
So základnými nástrojmi R bez použitia nástrojov balíka dplyr by to vyzeralo napr. takto:
<- subset(mtcars, vs == 1)
tmp 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
V balíku magrittr sú aj iné pipe operátory, napr. %T%
posunie argument funkcii, ale nepočká na jej odpoveď (slúži napríklad na vykreslenie), alebo %$%
nevloží objekt do prvého argumentu, len ho sprístupní, ďalej operátor %<>%
prepíše východiskový objekt výsledkom reťaze príkazov:
library(magrittr)
<- data.frame(A = 2, B = 3)
x %>% cbind(C = sum(.)) # použitie bodky ako zástupného symbolu (za vstup)
x ## A B C
## 1 2 3 5
%>% { c(D = .$A ^ .$B) } # krútené zátvorky spolu s . zastupujú anonymnú funkciu
x ## D
## 8
%$% c(C = A + B) # sprístupňuje prvky vstupného objeku po mene
x ## C
## 5
%T>% plot() %>% cbind(C = 4) # použitie napr. pre zobrazenie medzivýsledku x
## A B C
## 1 2 3 4
x %<>% cbind(C = sum(.)) # prepíše x výsledkom
x
## A B C
## 1 2 3 5
detach("package:magrittr")
Jednoduchšou (a predvídateľnejšou) alternatívou ku %>%
je napr. pipe operátor %>>%
z balíku pipeR.
V roku 2021 – ako odpoveď na obrovskú popularitu pipe operátora z magritttr – bol do štandardných knižníc jazyka R implementovaný jednoduchý pipe operátor |>
. Kvôli spôsobu jeho definície sú jeho možnosti zatiaľ (k 2.2.2022) pomerne obmedzené, neumožňuje použitie zástupného znaku pre vstupný objekt (.
) ani funkcie odvodené od bežných aritmetických operátorov (napr. '+'
), tieto prekážky sa však dajú obísť použitím anonymnej funkcie (na úkor prehľadnosti zápisu):
2 |> '+'(3) # nefunguje, ale reťaz 2 %>% '+'(3) funguje
2 |> (function(x) x + 3)()
|> (\(df) df$A + df$B)() # \(x) je skrátený zápis function(x)
x ## Error: function '+' not supported in RHS call of a pipe
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
- Načítajte data frame Cars93 z balíka MASS.
- 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 reťazec „Price” ale neobsahuje „.Price” (contains, operator
-
), - ich názov sa začína na „MPG” (starts_with),
- všetky od indikátora airbagov až po výkon motora okrem typu pohonu DriveTrain (operátory
:
,-
), - hmotnosť a pôvod vozidla.
- prvé tri (použite operátor sekvencie
- Pipe operátorom vytvorte sekvenciu nasledujúcich príkazov:
- auta93 (východískový objekt),
- premenovanie premennej EngineSize na CylindersVolume (rename),
- prevod hmotnosti z libier na kilogramy (mutate),
- výber všetkých amerických automobilov s hmotnosťou do 1200 kg (filter),
- zoradenie primárne podľa kategórie auta Type a v druhom rade podľa ceny vzostupne (arrange),
- vypísanie (print),
- rozdelenie riadkov podľa kategórie auta a výpočet priemerneho dojazdu v meste (group_by, summarise).