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:
- Odstraněním méně obvyklých případů při transformaci tabulek do tidy fromátu.
- Nakládáním s chybějícími hodnotami.
- 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 %>% nest(data = -observation)
population_world_nested
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:
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()
:
%>% unnest(data) population_world_nested
## # 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 “/”):
%>% separate(rate, c("cases","population"), sep="/") table3
## # 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:
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 naTRUE
, 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.
%>% separate(rate, c("cases"), sep="/", remove=FALSE) table3
## 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
):
%>% separate(rate, c("cases"), sep="/", extra="merge", remove=FALSE) table3
## # 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ů:
%>% separate(rate, c("cases","population","witches"), sep="/") table3
## 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:
%>% separate(rate, c("cases","population","witches"), sep="/", fill="left") table3
## # 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ů:
%>% separate_rows(rate, sep="/") table3
## # 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()
:
%>% unite(year,century,year, sep="") table5
## # 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 (vizdplyr::select
)sep
…znak oddělující hodnoty z jednotlivých sloupcůremove
…mají být původní sloupce odstraněny?