15.3 Další funkce z tidyr

Kromě funkcí pivot_*(), které jsou bezesporu nejvíce používané při čistění a transformaci dat, obsahuje tidyr řadu dalších funkcí, které pomáhají s:

  1. Odstraněním méně obvyklých případů při transformaci tabulek do tidy fromátu.
  2. Nakládáním s chybějícími hodnotami.
  3. Konstrukcí vlastních tabulek.

15.3.1 Různé typy pozorování v jedná tabulce: nest() a unnest()

Definice tidy formátu vyžaduje, aby byl každý typ pozorování uchován v oddělené tabulce.

Tabulka population_world obsahuje data, která toto kritérium nesplňují. Obsahuje pozorování jak za jednotlivá pozorování, tak jejich agregované hodnoty:

print(population_world)
## # A tibble: 9 × 5
##   country observation  year Female  Male
##   <chr>   <chr>       <int>  <dbl> <dbl>
## 1 Iceland unit         2005   148.  149.
## 2 Iceland unit         2010   158.  160.
## 3 Iceland unit         2015   164.  165.
## 4 Malta   unit         2005   201.  196.
## 5 Malta   unit         2010   207.  205.
## 6 Malta   unit         2015   210.  208.
## 7 World   aggregate    2005   348.  346.
## 8 World   aggregate    2010   365.  365.
## 9 World   aggregate    2015   375.  374.

Pomocí funkce nest() je možné vytvořit datovou strukturu, která tento problém vyřeší. nest() vytvoří “tabulku tabulek.” Můžeme ji použít pro vytvoření tabulky, která bude obsahovat jednotlivé tabulky v tidy formátu:

population_world_nested <- population_world %>% nest(data = -observation)

print(population_world_nested)
## # A tibble: 2 × 2
##   observation data            
##   <chr>       <list>          
## 1 unit        <tibble [6 × 4]>
## 2 aggregate   <tibble [3 × 4]>

Tabulky v tidy formátu jsou obsaženy v nově vytvořeném sloupci data:

print(population_world_nested$data)
## [[1]]
## # A tibble: 6 × 4
##   country  year Female  Male
##   <chr>   <int>  <dbl> <dbl>
## 1 Iceland  2005   148.  149.
## 2 Iceland  2010   158.  160.
## 3 Iceland  2015   164.  165.
## 4 Malta    2005   201.  196.
## 5 Malta    2010   207.  205.
## 6 Malta    2015   210.  208.
## 
## [[2]]
## # A tibble: 3 × 4
##   country  year Female  Male
##   <chr>   <int>  <dbl> <dbl>
## 1 World    2005   348.  346.
## 2 World    2010   365.  365.
## 3 World    2015   375.  374.

Fungování nest() ilustruje následující diagram:

Fungování nest()

Hlavním praktickým využitím nest() je vytvoření datové struktury, která může sloužit pro následnou datovou analýzu s pomocí nástrojů, které nejsou plně kompatibilní s logikou tidyverse. Zejména u vysoce specializivaných aplikací je takových funkcí překvapivě mnoho. Na některé případy narazíme zejména na konci kurzu.

Syntaxe funkce nest() je velmi jednoduchá (viz ?nest):

nest(data, ...)
  • data…vstupní tabulka (data frame)
  • ...…identifikace sloupců, které mají být součástí nově vytvořených (pod)tabulek. Podobně jako v případě pivot_longer() lze využít více způsobů, jak sloupce specifikovat: select helpers, atp.

Základním způsobem identifikace sloupců je podle jejich jmen:

## # A tibble: 3 × 3
##   country observation data            
##   <chr>   <chr>       <list>          
## 1 Iceland unit        <tibble [3 × 3]>
## 2 Malta   unit        <tibble [3 × 3]>
## 3 World   aggregate   <tibble [3 × 3]>

Jméno vektoru data se použije jako název nově vytvořeného sloupce. Pokud vektor nijak nepojmenujete, vrátí Vám nest() varování a nový sloupec pojmenuje právě data.

Na příkladech výše bylo ukázán příklad výběru sloupců s pomocí speciální funkce -. Funkční by měly být všechny způsoby identifikace podle tidyselect.

Datovou strukturu vytvořenou funkcí nest() lze transformovat do původního stavu pomocí funkce unnest():

population_world_nested %>% unnest(data)
## # A tibble: 9 × 5
##   observation country  year Female  Male
##   <chr>       <chr>   <int>  <dbl> <dbl>
## 1 unit        Iceland  2005   148.  149.
## 2 unit        Iceland  2010   158.  160.
## 3 unit        Iceland  2015   164.  165.
## 4 unit        Malta    2005   201.  196.
## 5 unit        Malta    2010   207.  205.
## 6 unit        Malta    2015   210.  208.
## 7 aggregate   World    2005   348.  346.
## 8 aggregate   World    2010   365.  365.
## 9 aggregate   World    2015   375.  374.

Syntaxe funkce unnest() je následující:

unnest(data, 
       cols, 
       ..., 
       keep_empty = FALSE, 
       ptype = NULL,
       names_sep = NULL, 
       names_repair = "check_unique"
       )

Základní parametry jsou: - data…je vstupní tabulka, - cols…je parametr vymezující sloupce, které se mají transformovat (obsahujících tabulky, které se mají “rozbalit”).

Funkce nest() a unnest() se od tidyr 1.0.0 významně změnily. Navíc k nim přibyly bratříčci chop() a unchop(). Ty se od nest()/unnest() liší v tom, že nevytvářejí nový sloupec tabulek, ale ponechávají původní sloupce, jen transformují jejich obsah na vektor:

## # A tibble: 3 × 5
##   country observation        year      Female        Male
##   <chr>   <chr>       <list<int>> <list<dbl>> <list<dbl>>
## 1 Iceland unit                [3]         [3]         [3]
## 2 Malta   unit                [3]         [3]         [3]
## 3 World   aggregate           [3]         [3]         [3]

Volba mezi nest()/chop() závisí čistě na potřebách následné analýzy.

15.3.2 Více hodnot v jedné buňce

Některé tabulky obsahují v jedné buňce hodnoty více proměnných. Jako příklad může sloužit tabulka tidyr::table3:

print(table3)
## # A tibble: 6 × 3
##   country      year rate             
## * <chr>       <int> <chr>            
## 1 Afghanistan  1999 745/19987071     
## 2 Afghanistan  2000 2666/20595360    
## 3 Brazil       1999 37737/172006362  
## 4 Brazil       2000 80488/174504898  
## 5 China        1999 212258/1272915272
## 6 China        2000 213766/1280428583

Ve sloupci rate je obsažen podíl počtu případů (cases) na celkové populaci (population). Proměnná rate je navíc nutně uložena jako text (character). S takovou proměnnou nelze rozumně pracovat. tidyr obsahuje nástroje, pomocí kterých je možné taková data převést do tidy formátu, který vyžaduje, aby obsahem jedné buňky byla vždy hodnota právě jedné proměnné.

15.3.2.1 Rozdělení jednoho sloupce do mnoha se separate()

Základní funkcí je separate(), která umožňuje jeden sloupec rozdělit do mnoha nových sloupců. V případě tabulky table3 například rozdělit sloupec rate na nové sloupce cases (číslo před “/”) a population (číslo za “/”):

table3 %>% separate(rate, c("cases","population"), sep="/")
## # A tibble: 6 × 4
##   country      year cases  population
##   <chr>       <int> <chr>  <chr>     
## 1 Afghanistan  1999 745    19987071  
## 2 Afghanistan  2000 2666   20595360  
## 3 Brazil       1999 37737  172006362 
## 4 Brazil       2000 80488  174504898 
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583

separate() provádí operaci ilustrovanou následujícím digramem:

Fungování separate() (Wickham, 2016)

Funkce separate() má následující syntaxi a parametry (viz ?separate):

separate(data, col, into, sep = "[^[:alnum:]]+", remove = TRUE,
  convert = FALSE, extra = "warn", fill = "warn", ...)
  • data…vstupní tabulka (data frame)
  • col…specifikace sloupce, který se má rozdělit. Sloupec je specifikován jako jméno bez úvozovek.
  • into…jména nově vytvářených sloupců specifikovaná jako vektor (character vector)
  • sep…udává rozdělení vstupního sloupce na výstupní sloupce. Může být specifikován jako číslo (pozice, na které se hodnoty v buňce rozdělí) a zejména jako regulární výraz.
  • remove…Má být vstupní sloupec zachován ve výstupní tabulce?
  • convert…pokud je nastaveno na TRUE, potom se funkce pokusí o automatickou konverzi výstupních sloupců. Pokud by například byla tato možnost použita v případě table3, potom by výstupní sloupce byly konvertovány do celých čísel (integer).
  • extra…udává, co se má stát, pokud vstupní řetezec obsahuje více hodnot, než je specifikováno výstupních sloupců.
  • fill…udává, co se má stát, pokud vstupní řetězec obsahuje méně hodnot, než je specifikováno výstupních sloupců.

Při výchozím nastavení je parametr extra nastaven na hodnotu warn. To znamená, že v přítomnosti většího počtu hodnot než je specifikováno nově vytvářených sloupců vrátí seprate() varování a zahodí přebytečné hodnoty.

table3 %>% separate(rate, c("cases"), sep="/", remove=FALSE)
## Warning: Expected 1 pieces. Additional pieces discarded in 6 rows [1, 2, 3, 4,
## 5, 6].
## # A tibble: 6 × 4
##   country      year rate              cases 
##   <chr>       <int> <chr>             <chr> 
## 1 Afghanistan  1999 745/19987071      745   
## 2 Afghanistan  2000 2666/20595360     2666  
## 3 Brazil       1999 37737/172006362   37737 
## 4 Brazil       2000 80488/174504898   80488 
## 5 China        1999 212258/1272915272 212258
## 6 China        2000 213766/1280428583 213766

Toto chování lze změnit. V případě nastavení extra na drop provede ve výsledku stejnou opraci, ale nevypíše varování. V případě nastavení parametru na merge rozdělí vstupní hodnotu pouze na stejný počet skupin, jako je zadáno výstupních sloupců. V následujícím případě bude tedy výstup (ve sloupci cases) stejný jako vstup (ve sloupci rate):

table3 %>% separate(rate, c("cases"), sep="/", extra="merge", remove=FALSE)
## # A tibble: 6 × 4
##   country      year rate              cases            
##   <chr>       <int> <chr>             <chr>            
## 1 Afghanistan  1999 745/19987071      745/19987071     
## 2 Afghanistan  2000 2666/20595360     2666/20595360    
## 3 Brazil       1999 37737/172006362   37737/172006362  
## 4 Brazil       2000 80488/174504898   80488/174504898  
## 5 China        1999 212258/1272915272 212258/1272915272
## 6 China        2000 213766/1280428583 213766/1280428583

Může se také stát, že vstupní hondota obsahuje méně skupin, než je specifikováno výstupních sloupců. V následujícím příkladě se snaží separate() rozdělit údaje ze sloupce rate do tří nových sloupců:

table3 %>% separate(rate, c("cases","population","witches"), sep="/")
## Warning: Expected 3 pieces. Missing pieces filled with `NA` in 6 rows [1, 2, 3,
## 4, 5, 6].
## # A tibble: 6 × 5
##   country      year cases  population witches
##   <chr>       <int> <chr>  <chr>      <chr>  
## 1 Afghanistan  1999 745    19987071   <NA>   
## 2 Afghanistan  2000 2666   20595360   <NA>   
## 3 Brazil       1999 37737  172006362  <NA>   
## 4 Brazil       2000 80488  174504898  <NA>   
## 5 China        1999 212258 1272915272 <NA>   
## 6 China        2000 213766 1280428583 <NA>

Vstupní data však obsahují pouze dva bloky. Při výchozím nastavení parametru fill (warn) vrátí separate() varování a vyplní sloupce zprava. Alternativní nastavení right a left nevrací varování, a vyplňují sloupce zprava respektive zleva:

table3 %>% separate(rate, c("cases","population","witches"), sep="/", fill="left")
## # A tibble: 6 × 5
##   country      year cases population witches   
##   <chr>       <int> <chr> <chr>      <chr>     
## 1 Afghanistan  1999 <NA>  745        19987071  
## 2 Afghanistan  2000 <NA>  2666       20595360  
## 3 Brazil       1999 <NA>  37737      172006362 
## 4 Brazil       2000 <NA>  80488      174504898 
## 5 China        1999 <NA>  212258     1272915272
## 6 China        2000 <NA>  213766     1280428583

Funkce separate() má blízké příbuzné v podobě funkcí extract() (pozor na maskování extract balíkem magrittr) a separate_rows().

extract() umožňuje specifikovat jednotlivé skupiny ve vstupním sloupci pomocí regulárních výrazů. Pokud některá skupina ve vstupním výrazu chybí, potom je ve výstupním sloupci kódována jako NA.

separate_rows() nerozkládá vstupní řetězec do sloupců, ale do řádků:

table3 %>% separate_rows(rate, sep="/")
## # A tibble: 12 × 3
##    country      year rate      
##    <chr>       <int> <chr>     
##  1 Afghanistan  1999 745       
##  2 Afghanistan  1999 19987071  
##  3 Afghanistan  2000 2666      
##  4 Afghanistan  2000 20595360  
##  5 Brazil       1999 37737     
##  6 Brazil       1999 172006362 
##  7 Brazil       2000 80488     
##  8 Brazil       2000 174504898 
##  9 China        1999 212258    
## 10 China        1999 1272915272
## 11 China        2000 213766    
## 12 China        2000 1280428583

Použití separate_rows() na table3 nemá smysl. Hodí se například v situaci, kdy jsou v buňce obsaženy identifikátory více různých stavů. Praktickým příkladem může být údaj o zatržení různých checkboxů ve formuláři – takto je obsahují např. CSV exportované z Google Forms.

15.3.2.2 Sloučení mnoha sloupců do jednoho s unite()

tidyr obsahuje funkci, které umožňuje provádět inverzní operaci – tedy slučovat více sloupců do jednoho. Tabulka tidyr::table5 obsahuje rok pozorování rozložený na století a rok:

print(table5)
## # A tibble: 6 × 4
##   country     century year  rate             
## * <chr>       <chr>   <chr> <chr>            
## 1 Afghanistan 19      99    745/19987071     
## 2 Afghanistan 20      00    2666/20595360    
## 3 Brazil      19      99    37737/172006362  
## 4 Brazil      20      00    80488/174504898  
## 5 China       19      99    212258/1272915272
## 6 China       20      00    213766/1280428583

Kompletní letopočet můžeme složit pomocí funkce unite():

table5 %>% unite(year,century,year, sep="")
## # A tibble: 6 × 3
##   country     year  rate             
##   <chr>       <chr> <chr>            
## 1 Afghanistan 1999  745/19987071     
## 2 Afghanistan 2000  2666/20595360    
## 3 Brazil      1999  37737/172006362  
## 4 Brazil      2000  80488/174504898  
## 5 China       1999  212258/1272915272
## 6 China       2000  213766/1280428583

unite() má obdobné rozhraní a parametry jako funkce určené k rozdělování hodnot (viz ?unite):

unite(data, col, ..., sep = "_", remove = TRUE)
  • data…vstupní tabulka (data frame)
  • col…jméno nově vytvořeného sloupce (prosté jméno bez úvozovek)
  • ...…sloupce, ze kterých má být nově vytvořený sloupec vytvořen (viz dplyr::select)
  • sep…znak oddělující hodnoty z jednotlivých sloupců
  • remove…mají být původní sloupce odstraněny?