Tidy data: jak si ušetřit mnoho zbytečných starostí

Na motivy Hadley Wickham: Tidy data, Journal of Statistical Software, August 2014, Volume 59, Issue 10.

K této kapitole jsou zpracovány velice hezké „taháky“: http://www.rstudio.com/resources/cheatsheets/ (Data tidying with tidyr a Data transformation with dplyr). Jsou též dostupné přímo z RStudia z menu Help, Cheatsheets.

Všechny potřebné knihovny (dplyr, tidyr a readr) lze instalovat v rámci jednoho zastřešujícího balíčku tidyverse:

install.packages("tidyverse")

Před použitím knihoven je potřeba je nejdříve zavést příkazem (nutné vždy po startu R)

library(tidyverse)

Případně je možné obdobným způsobem instalovat jednotlivé knihovny…

Tidy Data je obecný koncept nezávislý na použitých programech.

Raw Data → Processing Script → Tidy data + Codebook → Data Analysis → Data Communication.

Raw Data: surová data, zaznamenaná v jakémkoliv prvotním (často neuspořádaném) formátu (klidně i jen poznámky na papíře).

Reprodukovatelnost: celý řetězec zpracování jednoznačně popsán skripty – na první pohled je jasný postup a lze ho jednoduše zopakovat. V případě ručních mezikroků detailně zaznamenat (verze programu, nastavení, jak co pojmenovat a jak uložit).

Codebook: informace o proměnných, jejich jednotkách a možných hodnotách. Slouží k tomu, aby se v datech vyznal a mohl s nimi pracovat i ten, kdo nebyl u jejich pořízení.

Tidy data

  1. Každá proměnná v samostatném sloupci.
  2. Každé pozorování v samostatném řádku.
  3. Každý druh pozorované záležitosti v samostatné tabulce.

Poznámka k bodu 3: pokud máme např. tabulku s průběžnými body studentů během jejich studia, jedná se o jednu záležitost. Osobní informace o jednotlivých studentech (datum narození, adresa, telefon) uložíme do samostatné tabulky.

V případě více tabulek je nutný sloupec, dle kterého je lze propojit – ID pozorování – identifikační číslo, řetězec apod.

Čitelné pojmenování sloupců – raději AgeAtDiagnosis než AgeDx.

Uložení dat: co tabulka, to jeden soubor (záložkám v Excelu se vyhýbat). A rozhodně neukládat do jedné záložky více tabulek! Zbytečně si tím v extrémní míře znesnadňujeme následné automatické zpracování.

Messy data: vše ostatní.

  1. Hlavičky sloupců jsou hodnoty, nikoliv jména proměnných.
  2. Více proměnných v rámci jednoho sloupce.
  3. Proměnné uloženy jak v řádcích, tak ve sloupcích.
  4. Více pozorovaných záležitostí uložených ve stejné tabulce.
  5. Jedna pozorovaná záležitost uložena ve více tabulkách.

Pokud jsou data v tidy formátu, můžeme využívat efektivních knihoven pro jejich další zpracování, například dplyr pro souhrnné statistiky a manipulace podle různých kritérií (viz dále v této kapitole) nebo ggplot2 pro efektivní tvorbu pokročilých grafů (viz kapitola Grammar of Graphics (ggplot2)).

Nyní se podívejme na data v nevhodném formátu a jak je převést na tidy data.

Messy data a jejich převod na tidy data pomocí knihovny tidyr

Příklad 1

Tabulka obsahuje průměrnou hodnotu základní frekvence kmitání hlasivek (F0) u 3 žen a 3 mužů při 2 různých úkolech. Čím jsou porušena pravidla tidy data?

data <- read.table(header = TRUE, text = '
task male female
1    119  203
1    143  219
1    94   245
2    114  144
2    127  259
2    95   244
')

„Hlavičky některých sloupců jsou hodnoty, nikoliv jména proměnných.“

Hlavičky sloupců male a female ve skutečnosti nejsou 2 různé proměnné, ale jen 2 hodnoty téže proměnné (sex). Že to skutečně není vhodný formát tabulky, by bylo ještě zřejmější, pokud by se výzkumu zůčastnilo např. 5 žen a 3 muži.

Máme tedy 3 proměnné: task, sex a F0_mean. Opravu (sloučení sloupců) provedem pomocí funkce pivot_longer. Význam jejích parametrů je následující.

  • data: proměnná s daty (data.frame)
  • cols: které sloupce sloučit - lze též zadat everything(), starts_with(“počátečníPísmena”) a další frajeřiny
  • names_to: jak nazvat sloupec, jehož hodnoty se vyplní podle názvů slučovaných sloupců
  • values_to: jak nazvat sloupec hodnot ze sloučených sloupců
dataTidy <- pivot_longer(data, cols = c(male, female), names_to = "sex", values_to = "F0_mean")

Tímto zápisem říkáme: vezmi tabulku data, slučovat se budou sloupce male a female, z názvů slučovaných sloupců male a female se udělají hodnoty nového sloupce sex a čísla z těchto sloupců se převedou do nového sloupce s názvem F0_mean.

dataTidy
## # A tibble: 12 × 3
##     task sex    F0_mean
##    <int> <chr>    <int>
##  1     1 male       119
##  2     1 female     203
##  3     1 male       143
##  4     1 female     219
##  5     1 male        94
##  6     1 female     245
##  7     2 male       114
##  8     2 female     144
##  9     2 male       127
## 10     2 female     259
## 11     2 male        95
## 12     2 female     244

A máme krásný tidy data formát. Můžeme pak např. jednoduše pomocí grafické knihovny ggplot2 říct, ať zobrazí frekvence u jednotlivých úloh (task) a rozdělí se do skupin podle hodnot proměnné sex. Ale o tom až v další kapitole…

Poznámka: pro výběr slučovaných sloupců lze využít i zápisu se symbolem - značícím „vše kromě“, tedy sloučit všechny sloupce kromě sloupce task:

dataTidy <- pivot_longer(data, cols = -task, names_to = "sex", values_to = "F0_mean")

Příklad 2

Opět máme data z předchozího příkladu, ale v jiném tvaru. Čísla úkolů jsou součástí názvů sloupečků (_1 a _2). Čím jsou porušena pravidla tidy data?

data2 <- read.table(header = TRUE, text = '
male_1 male_2 female_1 female_2
119    114    203      144
143    127    219      259
94     95     245      244
')

Opět „hlavičky některých sloupců jsou hodnoty, nikoliv jména proměnných“, ale k tomu ještě „více proměnných v rámci jednoho sloupce“ (sex a task).

data2Tidy <- pivot_longer(data2, cols = everything(), names_to = "sex_task", values_to = "F0_mean") %>%
    separate(sex_task, c("sex", "task"), sep = "_")

První funkci pivot_longer jsme již poznali (parametr cols nyní říká, ať se sloučí všechny sloupce), vytváříme dočasný sloupec se dvěma proměnnými sex_task (zkuste si spustit samostatně jen tuto operaci a podívejte se na výsledek).

## # A tibble: 12 × 2
##    sex_task F0_mean
##    <chr>      <int>
##  1 male_1       119
##  2 male_2       114
##  3 female_1     203
##  4 female_2     144
##  5 male_1       143
##  6 male_2       127
##  7 female_1     219
##  8 female_2     259
##  9 male_1        94
## 10 male_2        95
## 11 female_1     245
## 12 female_2     244

Operátor %>% se nazývá pipeline (potrubí, spojení) a posílá výsledek z jedné funkce do druhé, takže není potřeba ukládat mezivýsledky do proměnných. Pokud je tento operátor použit, následující funkci nezadáváme 1. parametr (název proměnné s daty), protože data jsou jí právě přeposlána tímto operátorem.

Funkce separate rozdělí řetězec na více částí podle zvoleného oddělovače (sep) a výsledek uloží do nových sloupců, jejichž název jsme zvolili sex a task.

data2Tidy
## # A tibble: 12 × 3
##    sex    task  F0_mean
##    <chr>  <chr>   <int>
##  1 male   1         119
##  2 male   2         114
##  3 female 1         203
##  4 female 2         144
##  5 male   1         143
##  6 male   2         127
##  7 female 1         219
##  8 female 2         259
##  9 male   1          94
## 10 male   2          95
## 11 female 1         245
## 12 female 2         244

Příklad 3

Tabulka obsahuje trvání (duration) a průměrnou F0 u pěti různých úkolů. Ne všichni ale absolvovali všechny úkoly, proto tu máme spoustu chybějících hodnot NA (not available). Čím tabulka porušuje pravidla tidy data?

data3 <- read.table(header = TRUE, text = '
name   parameter task1 task2 task3 task4 task5
Eva    duration  4.8   NA    12.3  NA    NA
Eva    F0_mean   230   NA    222   NA    NA
Jiří   duration  NA    7.0   NA    5.9   NA
Jiří   F0_mean   NA    110   NA    113   NA
Zbyněk duration  NA    6.7   NA    NA    15.1
Zbyněk F0_mean   NA    127   NA    NA    118
Monika duration  NA    NA    10.9  6.2   NA
Monika F0_mean   NA    NA    240   235   NA
Patrik duration  5.1   NA    NA    NA    16.3
Patrik F0_mean   98    NA    NA    NA    95
')

„Proměnné uloženy jak v řádcích, tak ve sloupcích.“

Name je o.k., ale task1 až task5 jsou jen hodnoty jedné proměnné task (5 různých úkolů), duration a F0_mean jsou dvě proměnné související s konkrétním jedním pokusem, tudíž by měly tvořit 2 sloupce vždy pro stejný řádek.

data3_krok1 <- pivot_longer(data3, cols = task1:task5, names_to = "task", values_to = "value",
                            values_drop_na = TRUE)

V prvním kroku vezmeme tabulku data3, všechny názvy sloupců v rozsahu task1 až task5 budou hodnoty nové proměnné task a příslušné hodnoty ze sloupců uložíme do nové proměnné value. Všechny hodnoty NA (not available) zahodíme.

## # A tibble: 20 × 4
##    name   parameter task  value
##    <chr>  <chr>     <chr> <dbl>
##  1 Eva    duration  task1   4.8
##  2 Eva    duration  task3  12.3
##  3 Eva    F0_mean   task1 230  
##  4 Eva    F0_mean   task3 222  
##  5 Jiří   duration  task2   7  
##  6 Jiří   duration  task4   5.9
##  7 Jiří   F0_mean   task2 110  
##  8 Jiří   F0_mean   task4 113  
##  9 Zbyněk duration  task2   6.7
## 10 Zbyněk duration  task5  15.1
## 11 Zbyněk F0_mean   task2 127  
## 12 Zbyněk F0_mean   task5 118  
## 13 Monika duration  task3  10.9
## 14 Monika duration  task4   6.2
## 15 Monika F0_mean   task3 240  
## 16 Monika F0_mean   task4 235  
## 17 Patrik duration  task1   5.1
## 18 Patrik duration  task5  16.3
## 19 Patrik F0_mean   task1  98  
## 20 Patrik F0_mean   task5  95
data3_krok2 <- mutate(data3_krok1, task = parse_number(task))

Z hodnot proměnné task (které jsou task1, task2 atd.) extrahujeme jen číslo (funkcí parse_number z balíčku readr), to nám stačí.

## # A tibble: 20 × 4
##    name   parameter  task value
##    <chr>  <chr>     <dbl> <dbl>
##  1 Eva    duration      1   4.8
##  2 Eva    duration      3  12.3
##  3 Eva    F0_mean       1 230  
##  4 Eva    F0_mean       3 222  
##  5 Jiří   duration      2   7  
##  6 Jiří   duration      4   5.9
##  7 Jiří   F0_mean       2 110  
##  8 Jiří   F0_mean       4 113  
##  9 Zbyněk duration      2   6.7
## 10 Zbyněk duration      5  15.1
## 11 Zbyněk F0_mean       2 127  
## 12 Zbyněk F0_mean       5 118  
## 13 Monika duration      3  10.9
## 14 Monika duration      4   6.2
## 15 Monika F0_mean       3 240  
## 16 Monika F0_mean       4 235  
## 17 Patrik duration      1   5.1
## 18 Patrik duration      5  16.3
## 19 Patrik F0_mean       1  98  
## 20 Patrik F0_mean       5  95
data3Tidy <- pivot_wider(data3_krok2, names_from = parameter, values_from = value)

Z hodnot proměnné parameter vytvoříme nové proměnné (nové sloupce), jejichž hodnoty naplníme z proměné value (v nové tabulce tím tedy proměnné parameter a value zaniknou, o konkrétní hodnoty samozřejme nepřijdeme, jen budou přemístěny do jiného formátu).

data3Tidy
## # A tibble: 10 × 4
##    name    task duration F0_mean
##    <chr>  <dbl>    <dbl>   <dbl>
##  1 Eva        1      4.8     230
##  2 Eva        3     12.3     222
##  3 Jiří       2      7       110
##  4 Jiří       4      5.9     113
##  5 Zbyněk     2      6.7     127
##  6 Zbyněk     5     15.1     118
##  7 Monika     3     10.9     240
##  8 Monika     4      6.2     235
##  9 Patrik     1      5.1      98
## 10 Patrik     5     16.3      95

Celý postup by samozřejmě bylo možné zapsat najednou pomocí operátoru pipeline %>%. Zkuste si to.

Příklad 4

Čím jsou porušena pravidla tidy data?

data4 <- read.table(header = TRUE, text = '
id  name    sex  task  duration F0_mean
168 Eva     F    1     4.8      230
168 Eva     F    3     12.3     222
588 Jiří    M    2     7.0      110
588 Jiří    M    4     5.9      113
710 Monika  F    3     10.9     240
710 Monika  F    4     6.2      235
731 Patrik  M    1     5.1      98
731 Patrik  M    5     16.3     95
908 Zbyněk  M    2     6.7      127
908 Zbyněk  M    5     15.1     118
')

„Více pozorovaných záležitostí uložených ve stejné tabulce.“

Osobní informace o lidech (id, name, sex) – vše je 2x, to je jasná známka redundance (a ještě je tu riziko překlepu).

Data je vhodné rozdělit do 2 tabulek: idInfo a results. Obě tabulky budou propojeny přes id číslo.

idInfo <- data4 %>% select(id, name, sex) %>% unique

Z tabulky jsme funkcí select vybrali první 3 sloupce. Funkcí unique jsme zanechali jen unikátní řádky. Pokud se řádky opakují, ve výsledku budou zahrnuty pouze jednou.

idInfo
##    id   name sex
## 1 168    Eva   F
## 3 588   Jiří   M
## 5 710 Monika   F
## 7 731 Patrik   M
## 9 908 Zbyněk   M
results <- data4 %>% select(id, task, duration, F0_mean)

A opět vybíráme jen některé sloupce.

results
##     id task duration F0_mean
## 1  168    1      4.8     230
## 2  168    3     12.3     222
## 3  588    2      7.0     110
## 4  588    4      5.9     113
## 5  710    3     10.9     240
## 6  710    4      6.2     235
## 7  731    1      5.1      98
## 8  731    5     16.3      95
## 9  908    2      6.7     127
## 10 908    5     15.1     118

Mít takto oddělené záznamy je praktické. V případě potřeby je pak možné informace kdykoliv spojit.

right_join(idInfo, results, by = "id")
##     id   name sex task duration F0_mean
## 1  168    Eva   F    1      4.8     230
## 2  168    Eva   F    3     12.3     222
## 3  588   Jiří   M    2      7.0     110
## 4  588   Jiří   M    4      5.9     113
## 5  710 Monika   F    3     10.9     240
## 6  710 Monika   F    4      6.2     235
## 7  731 Patrik   M    1      5.1      98
## 8  731 Patrik   M    5     16.3      95
## 9  908 Zbyněk   M    2      6.7     127
## 10 908 Zbyněk   M    5     15.1     118

Pro další způsoby spojování tabulek viz tahák dostupný přímo z RStudia z menu Help, Cheatsheets, Data Manipulation v dplyr, tidyr. Druhá strana, sekce Combine Data Sets.

Příklad 5

Čím jsou zde porušena pravidla tidy data?

male <- read.table(header = TRUE, text = '
name   task duration F0_mean
Jiří   2    7        110
Jiří   4    5.9      113
Patrik 1    5.1      98
Patrik 5    16.3     95
Zbyněk 2    6.7      127
Zbyněk 5    15.1     118
')

female <- read.table(header = TRUE, text = '
name   task duration F0_mean
Eva    1    4.8      230
Eva    3    12.3     222
Monika 3    10.9     240
Monika 4    6.2      235
')

„Jedna pozorovaná záležitost uložena ve více tabulkách.“

Pokud se jedná o stejný experiment, obě tabulky má smysl sloučit a z male a female vytvořit jednu proměnnou sex.

male <- male %>% mutate(sex = "M")
female <- female %>% mutate(sex = "F")

Tímto jsme do tabulek přidali novou proměnnou sex s příslušnou hodnotou ve všech řádcích.

data5Tidy <- bind_rows(male, female)

A obě tabulky spojíme pod sebe.

data5Tidy
##      name task duration F0_mean sex
## 1    Jiří    2      7.0     110   M
## 2    Jiří    4      5.9     113   M
## 3  Patrik    1      5.1      98   M
## 4  Patrik    5     16.3      95   M
## 5  Zbyněk    2      6.7     127   M
## 6  Zbyněk    5     15.1     118   M
## 7     Eva    1      4.8     230   F
## 8     Eva    3     12.3     222   F
## 9  Monika    3     10.9     240   F
## 10 Monika    4      6.2     235   F

Untidy

Občas se při zpracování dat dostaneme do situace, kdy je potřeba tidy tabulku záměrně předělat do „messy“ tvaru. V následujícím příkladu jsme nahráli 3 osoby, každá přečetla jednu větu ve dvou stylech fonace, a sice normální (modal) a třepené (creaky). Vše bylo čteno dvakrát (sloupec order). Vypočteno bylo trvání a průměrná hodnota F0.

data <- read.table(header = TRUE, text = '
speaker phonation order duration F0_mean
1       modal     1     5.7      127
1       modal     2     5.3      135
1       creaky    1     8.5      110
1       creaky    2     7.4      105
2       modal     1     6.3      243
2       creaky    1     9.2      145
2       modal     2     5.8      219
2       creaky    2     9.3      162
3       modal     1     5.1      140
3       creaky    1     6.0      93
3       creaky    2     5.8      95
')

Rádi bychom vytvořili sloupce s jednotlivými vypočtenými parametry doplněné o index 1 nebo 2 odpovídající hodnotě ve sloupci order, aby pak bylo možné jednoduše vypočítat rozdíl mezi hodnotami prvního a druhého čtení té samé položky tou samou fonací.

messy <- pivot_wider(data, id_cols = c(speaker, phonation), # sloupce sloužící k identifikaci položky
                     names_from = "order",  # sloupec "order" udává pořadí, podle něj se očíslují nové sloupce
                     values_from = duration:F0_mean,   # výčet sloupců s naměřenými hodnotami
                     names_glue = "{.value}.{order}")  # jak slepit nové názvy - zde opět sloupec "order"
## # A tibble: 6 × 6
##   speaker phonation duration.1 duration.2 F0_mean.1 F0_mean.2
##     <int> <chr>          <dbl>      <dbl>     <int>     <int>
## 1       1 modal            5.7        5.3       127       135
## 2       1 creaky           8.5        7.4       110       105
## 3       2 modal            6.3        5.8       243       219
## 4       2 creaky           9.2        9.3       145       162
## 5       3 modal            5.1       NA         140        NA
## 6       3 creaky           6          5.8        93        95

Nyní můžeme vypočítat rozdíly parametrů mezi 1. a 2. čtením. Všimněme si, že bylo správně rozpoznáno, že u třetí osoby chybí druhé čtení v modální fonaci. Ani prohozené pořadí řádků u druhé osoby (vůči logice zbylých dvou osob) převod tabulky nijak nerozhodilo. Kdybychom tyto úpravy dělali ručně, riziko chyby by bylo vysoké.

tab <- messy %>% mutate(deltaDuration = duration.2 - duration.1, deltaF0_mean = F0_mean.2 - F0_mean.1) %>% 
    select(speaker, phonation, deltaDuration, deltaF0_mean)
## # A tibble: 6 × 4
##   speaker phonation deltaDuration deltaF0_mean
##     <int> <chr>             <dbl>        <int>
## 1       1 modal            -0.400            8
## 2       1 creaky           -1.1             -5
## 3       2 modal            -0.5            -24
## 4       2 creaky            0.100           17
## 5       3 modal            NA               NA
## 6       3 creaky           -0.200            2

Rozdíly jsou bezchybně určeny a správně chybí modální fonace třetí osoby, jelikož v datech není druhé čtení.

Kontingenční tabulky

Kontingenční tabulka, neboli tabulka se dvěma vstupy a souhrnnými počty také z principu odporuje formátu tidy data. Přesto je velmi užitečná právě pro zobrazení souhrnných počtů nebo pro statistické testy porovnávající četnosti.

Primární formát uchování dat by měl být tidy data (např. co řádek tabulky, to jeden náhodný výběr ze základního souboru) a převod na kontingenční tabulku pak již snadno provedeme funkcí table(), jak je uvedeno na příkladech v kapitole 15. Testy četností.

Souhrnné statistiky a manipulace pomocí knihovny dplyr

Nyní přichází ta zábavnější část. Předpokládejme, že máme tabulku ve správném formátu tidy data. Pak je radost s ní pracovat. Nejlepší je samozřejmě data hned od začátku ukládat do formátu tidy data, ušetříme si tím starosti. Pakliže ale tento formát nesplňují, musíme je na tidy data nejdříve převést, o čemž byla horní část této stránky.

Funkce v dplyr jsou určeny pro jednodušší a efektivnější spravování dat. Zápis těchto operací je oproti dřívějším knihovnám velice zjednodušen a implementace je vskutku bleskurychlá. Některé funkce jsme již poznali výše.

Knihovna dplyr implementuje „gramatiku“ pro manipulaci s daty (konkrétně slovesa).

  • rename: přejmenování názvů vybraných sloupců
  • arrange: seřadí řádky (podle velikosti jedné či více proměnných), pro sestupné řazení se doplní název proměnné funkcí desc
  • mutate: přidá novou proměnnou (sloupec) nebo transformuje existující proměnnou
  • select: vybere sloupce
  • filter: vybere řádky na základě logických podmínek
  • group_by: rozdělení řádků do skupin podle hodnot nějaké proměnné či kombinace hodnot více proměnných
  • summarize: generuje sumární statistiky různých proměnných, a to ve vrstvách podle skupin vytvořených funkcí group_by
  • group_modify: také umožňuje operace v rámci skupin vytvořených group_by, ale výstupem je opět celá tabulka. Příklad: rozdělíme tabulku na skupiny, v rámci každé provedeme samostatné vyhlazení měřených hodnot polynomiální křivkou.

Dále můžeme používat operátor %>% (pipeline), který jsme již poznali výše.

Příklady

Budeme pracovat s interním dataframe mtcars, seznamte se s ním proto pomocí funkcí View(mtcars) a nápovědy ?mtcars.

Všimněme si, že názvy automobilů jsou uloženy jen jako názvy řádků tabulky. Uděláme z nich „oficiální proměnnou“, tedy nový sloupec.

mtcars$car <- rownames(mtcars)
rownames(mtcars) <- NULL

Seřadíme tabulku podle sloupce car.

mtcars <- mtcars %>% arrange(car)
mtcars
##     mpg cyl  disp  hp drat    wt  qsec vs am gear carb                 car
## 1  15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2         AMC Javelin
## 2  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4  Cadillac Fleetwood
## 3  13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4          Camaro Z28
## 4  14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4   Chrysler Imperial
## 5  22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1          Datsun 710
## 6  15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2    Dodge Challenger
## 7  14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4          Duster 360
## 8  19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6        Ferrari Dino
## 9  32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1            Fiat 128
## 10 27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1           Fiat X1-9
## 11 15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4      Ford Pantera L
## 12 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2         Honda Civic
## 13 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1      Hornet 4 Drive
## 14 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2   Hornet Sportabout
## 15 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4 Lincoln Continental
## 16 30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2        Lotus Europa
## 17 15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8       Maserati Bora
## 18 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4           Mazda RX4
## 19 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4       Mazda RX4 Wag
## 20 22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2            Merc 230
## 21 24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2           Merc 240D
## 22 19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4            Merc 280
## 23 17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4           Merc 280C
## 24 16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3          Merc 450SE
## 25 17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3          Merc 450SL
## 26 15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3         Merc 450SLC
## 27 19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2    Pontiac Firebird
## 28 26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2       Porsche 914-2
## 29 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1      Toyota Corolla
## 30 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1       Toyota Corona
## 31 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1             Valiant
## 32 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2          Volvo 142E

Jaká jsou pravidla řazení řetězců? Jak to naloží s “ch”, velkými písmeny nebo třeba s háčky a čárkami?

Ve starších verzích dplyr se používalo defaultní Locale našeho systému, což bývá např. Czech. Takže Chrysler bylo až za Hornet.

Sys.getlocale(category = "LC_ALL")   # zjisti nastavení Locale

Kvůli nekonzistenci výstupu funkce vzhledem k nastavení systému se tvůrci rozhodli od verze dplyr 1.1.0 ke změně a nyní je vždy defaultní locale “C” (můžeme mu říkat programátorské), které je podobné anglickému, ale například velká písmena řadí zcela samostatně.

A konkrétní locale můžeme vždy zvolit.

tab <- data.frame(text = c("a", "b", "c", "č", "f", "ch", "h", "A", "B", "C"))
tab %>% arrange(text) # defaultní "C" lokale - č je až na konci, velká písmena naopak na začátku
##    text
## 1     A
## 2     B
## 3     C
## 4     a
## 5     b
## 6     c
## 7    ch
## 8     f
## 9     h
## 10    č
tab %>% arrange(text, .locale = "en")
##    text
## 1     a
## 2     A
## 3     b
## 4     B
## 5     c
## 6     C
## 7     č
## 8    ch
## 9     f
## 10    h
tab %>% arrange(text, .locale = "cs") # ch je řazeno česky
##    text
## 1     a
## 2     A
## 3     b
## 4     B
## 5     c
## 6     C
## 7     č
## 8     f
## 9     h
## 10   ch

Seřadíme ji podle cyl (sestupně) a v případě více shodných hodnot budeme řadit ještě podle gear (normálně vzestupně).

mtcars <- mtcars %>% arrange(desc(cyl), gear)
mtcars
##     mpg cyl  disp  hp drat    wt  qsec vs am gear carb                 car
## 1  15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2         AMC Javelin
## 2  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4  Cadillac Fleetwood
## 3  13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4          Camaro Z28
## 4  14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4   Chrysler Imperial
## 5  15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2    Dodge Challenger
## 6  14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4          Duster 360
## 7  18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2   Hornet Sportabout
## 8  10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4 Lincoln Continental
## 9  16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3          Merc 450SE
## 10 17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3          Merc 450SL
## 11 15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3         Merc 450SLC
## 12 19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2    Pontiac Firebird
## 13 15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4      Ford Pantera L
## 14 15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8       Maserati Bora
## 15 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1      Hornet 4 Drive
## 16 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1             Valiant
## 17 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4           Mazda RX4
## 18 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4       Mazda RX4 Wag
## 19 19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4            Merc 280
## 20 17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4           Merc 280C
## 21 19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6        Ferrari Dino
## 22 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1       Toyota Corona
## 23 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1          Datsun 710
## 24 32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1            Fiat 128
## 25 27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1           Fiat X1-9
## 26 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2         Honda Civic
## 27 22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2            Merc 230
## 28 24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2           Merc 240D
## 29 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1      Toyota Corolla
## 30 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2          Volvo 142E
## 31 30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2        Lotus Europa
## 32 26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2       Porsche 914-2

Přejmenujeme sloupec am na razeni.

mtcars <- mtcars %>% rename(razeni = am)
head(mtcars)
##    mpg cyl disp  hp drat    wt  qsec vs razeni gear carb                car
## 1 15.2   8  304 150 3.15 3.435 17.30  0      0    3    2        AMC Javelin
## 2 10.4   8  472 205 2.93 5.250 17.98  0      0    3    4 Cadillac Fleetwood
## 3 13.3   8  350 245 3.73 3.840 15.41  0      0    3    4         Camaro Z28
## 4 14.7   8  440 230 3.23 5.345 17.42  0      0    3    4  Chrysler Imperial
## 5 15.5   8  318 150 2.76 3.520 16.87  0      0    3    2   Dodge Challenger
## 6 14.3   8  360 245 3.21 3.570 15.84  0      0    3    4         Duster 360

Vybereme jen sloupce car a mpg (dojezd v mílích na gallon).

prehled <- mtcars %>% select(car, mpg)
head(prehled)
##                  car  mpg
## 1        AMC Javelin 15.2
## 2 Cadillac Fleetwood 10.4
## 3         Camaro Z28 13.3
## 4  Chrysler Imperial 14.7
## 5   Dodge Challenger 15.5
## 6         Duster 360 14.3

Vybereme jen řádky, kde vs = 0 a carb >= 4, výsledek seřadíme podle názvu.

vyber <- mtcars %>% filter(vs == 0, carb >= 4) %>% arrange(car)
vyber
##     mpg cyl disp  hp drat    wt  qsec vs razeni gear carb                 car
## 1  10.4   8  472 205 2.93 5.250 17.98  0      0    3    4  Cadillac Fleetwood
## 2  13.3   8  350 245 3.73 3.840 15.41  0      0    3    4          Camaro Z28
## 3  14.7   8  440 230 3.23 5.345 17.42  0      0    3    4   Chrysler Imperial
## 4  14.3   8  360 245 3.21 3.570 15.84  0      0    3    4          Duster 360
## 5  19.7   6  145 175 3.62 2.770 15.50  0      1    5    6        Ferrari Dino
## 6  15.8   8  351 264 4.22 3.170 14.50  0      1    5    4      Ford Pantera L
## 7  10.4   8  460 215 3.00 5.424 17.82  0      0    3    4 Lincoln Continental
## 8  15.0   8  301 335 3.54 3.570 14.60  0      1    5    8       Maserati Bora
## 9  21.0   6  160 110 3.90 2.620 16.46  0      1    4    4           Mazda RX4
## 10 21.0   6  160 110 3.90 2.875 17.02  0      1    4    4       Mazda RX4 Wag

Zavedeme novou proměnnou (sloupec) udávající, zda mají auta nadprůměrně nízkou spotřebu.

mtcars <- mtcars %>% mutate(usporne = (mpg > mean(mpg)))
mtcars %>% select(mpg, car, usporne)
##     mpg                 car usporne
## 1  15.2         AMC Javelin   FALSE
## 2  10.4  Cadillac Fleetwood   FALSE
## 3  13.3          Camaro Z28   FALSE
## 4  14.7   Chrysler Imperial   FALSE
## 5  15.5    Dodge Challenger   FALSE
## 6  14.3          Duster 360   FALSE
## 7  18.7   Hornet Sportabout   FALSE
## 8  10.4 Lincoln Continental   FALSE
## 9  16.4          Merc 450SE   FALSE
## 10 17.3          Merc 450SL   FALSE
## 11 15.2         Merc 450SLC   FALSE
## 12 19.2    Pontiac Firebird   FALSE
## 13 15.8      Ford Pantera L   FALSE
## 14 15.0       Maserati Bora   FALSE
## 15 21.4      Hornet 4 Drive    TRUE
## 16 18.1             Valiant   FALSE
## 17 21.0           Mazda RX4    TRUE
## 18 21.0       Mazda RX4 Wag    TRUE
## 19 19.2            Merc 280   FALSE
## 20 17.8           Merc 280C   FALSE
## 21 19.7        Ferrari Dino   FALSE
## 22 21.5       Toyota Corona    TRUE
## 23 22.8          Datsun 710    TRUE
## 24 32.4            Fiat 128    TRUE
## 25 27.3           Fiat X1-9    TRUE
## 26 30.4         Honda Civic    TRUE
## 27 22.8            Merc 230    TRUE
## 28 24.4           Merc 240D    TRUE
## 29 33.9      Toyota Corolla    TRUE
## 30 21.4          Volvo 142E    TRUE
## 31 30.4        Lotus Europa    TRUE
## 32 26.0       Porsche 914-2    TRUE

A nyní by mě zajímalo, kolik průměrně váží auta s nízkou spotřebou a kolik ta s vysokou.

mtcars %>% group_by(usporne) %>% summarize(wtPrumer = mean(wt))
## # A tibble: 2 × 2
##   usporne wtPrumer
##   <lgl>      <dbl>
## 1 FALSE       3.84
## 2 TRUE        2.42

Tak to vypadá, že úsporná auta jsou spíše ta lehčí (opět připomeňme, že souvislost neznamená nutně kauzalitu).

Zvládli bychom vypočítat i 95% konfidenční intervaly tohoto odhadu? V jednom sloupci může být vždy jen jedno číslo, takže interval bude ve dvou sloupcích (spodní a horní hodnota).

mtcars %>% group_by(usporne) %>% summarize(wtPrumer = mean(wt),
    confInt1 = t.test(wt)$conf.int[1], confInt2 = t.test(wt)$conf.int[2])
## # A tibble: 2 × 4
##   usporne wtPrumer confInt1 confInt2
##   <lgl>      <dbl>    <dbl>    <dbl>
## 1 FALSE       3.84     3.47     4.21
## 2 TRUE        2.42     2.09     2.75

A pojďme vymyslet složitější skupiny. Rozdělíme data podle převodovky (razeni), válců (cyl) a počtu karburátorů (carb). V těchto skupinách vypočteme průměr a směrodatnou odchylku z mpg a wt. Výsledky seřadíme od největší průměrné mpg po nejmenší.

Vidíme, že některé skupiny byly asi zastoupeny jen jednou položkou, a proto nebylo možné vypočítat směrodatnou odchylku.

mtcars %>% group_by(razeni, cyl, carb) %>%
    summarize(mpgPrumer = mean(mpg), mpgSD = sd(mpg), wtPrumer = mean(wt), wtSD = sd(wt)) %>%
    arrange(desc(mpgPrumer))
## `summarise()` has grouped output by 'razeni', 'cyl'. You can override using the
## `.groups` argument.
## # A tibble: 13 × 7
## # Groups:   razeni, cyl [6]
##    razeni   cyl  carb mpgPrumer  mpgSD wtPrumer    wtSD
##     <dbl> <dbl> <dbl>     <dbl>  <dbl>    <dbl>   <dbl>
##  1      1     4     1      29.1  5.06      2.07  0.226 
##  2      1     4     2      27.0  4.3       2.01  0.581 
##  3      0     4     2      23.6  1.13      3.17  0.0283
##  4      0     4     1      21.5 NA         2.46 NA     
##  5      1     6     4      21    0         2.75  0.180 
##  6      0     6     1      19.8  2.33      3.34  0.173 
##  7      1     6     6      19.7 NA         2.77 NA     
##  8      0     6     4      18.5  0.990     3.44  0     
##  9      0     8     2      17.2  2.09      3.56  0.194 
## 10      0     8     3      16.3  1.05      3.86  0.184 
## 11      1     8     4      15.8 NA         3.17 NA     
## 12      1     8     8      15   NA         3.57 NA     
## 13      0     8     4      12.6  2.09      4.69  0.903

A nyní chceme udělat nějaké výpočty pro každou skupinu z group_by() samostatně, ale výsledkem nemá být jen souhrnná statistika, ale normálně celá tabulka. K tomu slouží funkce group_modify(). Příklad: rozdělíme do skupin podle počtu válců (cyl), v každé skupině samostatně vypočteme průměrné mpg a vytvoříme nový sloupeček rozdilMPG vypočtený jako mpg mínus průměrMPG.

odectiPrumerMPG <- function(tabulka) {  # vstupem funkce je data.frame = vyrez radku z puvodni tabulky
                                        # odpovidajici konkretni skupine podle provedeneho group_by()
    
    tabulka$rozdilMPG <- tabulka$mpg - mean(tabulka$mpg)  # ...napr. vypocteme novy sloupecek...
    tabulka <- tabulka %>% select(-c(disp:carb))          # ...a treba odstranime nektere jine sloupce...
    
    return(tabulka) # vystupem funkce musi byt take data.frame
}

tabulkaRozdilu <- mtcars %>% group_by(cyl) %>% group_modify(.f = ~ odectiPrumerMPG(.x))
                                        # .x je docasne jmeno data.frame vzdy jedne skupiny z group_by()
print(tabulkaRozdilu, n = 32)
## # A tibble: 32 × 5
## # Groups:   cyl [3]
##      cyl   mpg car                 usporne rozdilMPG
##    <dbl> <dbl> <chr>               <lgl>       <dbl>
##  1     4  21.5 Toyota Corona       TRUE      -5.16  
##  2     4  22.8 Datsun 710          TRUE      -3.86  
##  3     4  32.4 Fiat 128            TRUE       5.74  
##  4     4  27.3 Fiat X1-9           TRUE       0.636 
##  5     4  30.4 Honda Civic         TRUE       3.74  
##  6     4  22.8 Merc 230            TRUE      -3.86  
##  7     4  24.4 Merc 240D           TRUE      -2.26  
##  8     4  33.9 Toyota Corolla      TRUE       7.24  
##  9     4  21.4 Volvo 142E          TRUE      -5.26  
## 10     4  30.4 Lotus Europa        TRUE       3.74  
## 11     4  26   Porsche 914-2       TRUE      -0.664 
## 12     6  21.4 Hornet 4 Drive      TRUE       1.66  
## 13     6  18.1 Valiant             FALSE     -1.64  
## 14     6  21   Mazda RX4           TRUE       1.26  
## 15     6  21   Mazda RX4 Wag       TRUE       1.26  
## 16     6  19.2 Merc 280            FALSE     -0.543 
## 17     6  17.8 Merc 280C           FALSE     -1.94  
## 18     6  19.7 Ferrari Dino        FALSE     -0.0429
## 19     8  15.2 AMC Javelin         FALSE      0.100 
## 20     8  10.4 Cadillac Fleetwood  FALSE     -4.7   
## 21     8  13.3 Camaro Z28          FALSE     -1.80  
## 22     8  14.7 Chrysler Imperial   FALSE     -0.400 
## 23     8  15.5 Dodge Challenger    FALSE      0.400 
## 24     8  14.3 Duster 360          FALSE     -0.800 
## 25     8  18.7 Hornet Sportabout   FALSE      3.6   
## 26     8  10.4 Lincoln Continental FALSE     -4.7   
## 27     8  16.4 Merc 450SE          FALSE      1.30  
## 28     8  17.3 Merc 450SL          FALSE      2.20  
## 29     8  15.2 Merc 450SLC         FALSE      0.100 
## 30     8  19.2 Pontiac Firebird    FALSE      4.1   
## 31     8  15.8 Ford Pantera L      FALSE      0.700 
## 32     8  15   Maserati Bora       FALSE     -0.100

© 13. 5. 2015, 27. 10. 2020 Tomáš Bořil,