16.6 Slovesa pracující se dvěma (nebo více) tabulkami
Data bývají často dostupná ve více tabulkách, které mohou například mohou pocházet z různých zdrojů: HDP ze Světové banky, migrace z Eurostatu, atp. Pro účely datové analýzy je nutné takové tabulky spojovat do jednoho celku.
dplyr podporuje dva druhy spojovacích operací:
- bind spojuje tabulky, které mají stejnou strukturu – v podstatě přidává sloupce (
bind_cols()
) nebo řádky (bind_rows()
) - join slučuje tabulky podle určitého definovaného klíče – například sloučí k sobě údaje o jednom člověku z více tabulek, které mohou mít naprosto odlišnou strukturu (více funkcí
*_join()
)
16.6.1 Spojování tabulek s bind_*()
Funkcí bind_rows()
a bind_cols()
je spojovat tabulky se stejnou strukturou. V případě, že jsou pozorování se stejnými proměnnými rozděleny do více tabulek, je potřeba tabulky poskládat “pod sebe”. Jinými slovy přidávat další a další řádky s dodatečnými pozorováními. V tomto případě se hodí použít funkci bind_rows(...)
. Ta jako argument přijímá jména (neomezeného počtu) tabulek a nebo seznam (list) tabulek. Fungování bind_rows()
můžeme demonstrovat na tabulce dplyr::band_members
:
## # A tibble: 3 x 2
## name band
## <chr> <chr>
## 1 Mick Stones
## 2 John Beatles
## 3 Paul Beatles
Do bind_rows()
můžeme vložit více tabulek:
## # A tibble: 9 x 2
## name band
## <chr> <chr>
## 1 Mick Stones
## 2 John Beatles
## 3 Paul Beatles
## 4 Mick Stones
## 5 John Beatles
## 6 Paul Beatles
## 7 Mick Stones
## 8 John Beatles
## 9 Paul Beatles
…nebo list tabulek:
## # A tibble: 9 x 2
## name band
## <chr> <chr>
## 1 Mick Stones
## 2 John Beatles
## 3 Paul Beatles
## 4 Mick Stones
## 5 John Beatles
## 6 Paul Beatles
## 7 Mick Stones
## 8 John Beatles
## 9 Paul Beatles
Výsledky jsou pochopitelně stejné. Schopnost spojit tabulky uložené v seznamu je zvláště užitečná v případě, že pracujeme například s výstupem funkce lapply()
.
Předchozí příklady byly bezproblémové, protože tabulky měly stejnou strukturu – tedy stejně pojmenované sloupce se stejnými datovými typy. Co se stane v případě volání bind_rows()
na nekonzistentní tabulky ukazuje následující příklad:
## # A tibble: 6 x 3
## NAME band name
## <chr> <chr> <chr>
## 1 Mick Stones <NA>
## 2 John Beatles <NA>
## 3 Paul Beatles <NA>
## 4 <NA> Stones Mick
## 5 <NA> Beatles John
## 6 <NA> Beatles Paul
bind_rows()
pod sebe složil hodnoty ze sloupců stejného jména. Sloupce s neshodujícími se jmény zachoval, ale do tabulky doplnil NA
.
## # A tibble: 6 x 3
## NAME band name
## <chr> <chr> <chr>
## 1 Mick Stones <NA>
## 2 John Beatles <NA>
## 3 Paul Beatles <NA>
## 4 <NA> Stones Mick
## 5 <NA> Beatles John
## 6 <NA> Beatles Paul
V případě nekonzistentních datových typů je situace zajímavější. V následujícím příkladu je sloupec name konvertován z character na factor a následně je tabulka spojena s nezměněnou tabulkou band_members
:
## # A tibble: 6 x 2
## name band
## <chr> <chr>
## 1 Mick Stones
## 2 John Beatles
## 3 Paul Beatles
## 4 Mick Stones
## 5 John Beatles
## 6 Paul Beatles
R umí provést automatickou konverzi faktorů na znaky. Provede ji, ale vypíše i upovídané varování. Výsledek je nicméně perfektně použitelný a ve většině případů bude odpovídat přání uživatele.
Větší problém nastane, pokud se ve spojovaných tabulkách vyskytnou sloupce stejného jména a rozdílných datových typů, u kterých R neumí provést automatickou konverzi. V tomto případě jde o double a character:
## Error: <text>:4:9: unexpected ')'
## 3: name = rnorm1)
## 4: )
## ^
Tato operace se neprovede a R vrátí chybu.
Podobně jako bind_rows()
funguje funkce bind_cols()
. Tabulky ovšem neskládá “pod sebe”“, ale”vedle sebe". Předpokladem jejího použití je opět shodná struktura tabulek. To v tomto případě znamená zejména to, že jedno pozorování je vždy na stejném řádku. Pozorování je tak vlastně identifikováno číslem řádku. Syntaxe je stejná jako u bind_rows()
:
## New names:
## * name -> name...1
## * band -> band...2
## * name -> name...3
## * band -> band...4
## * name -> name...5
## * ...
## # A tibble: 3 x 6
## name...1 band...2 name...3 band...4 name...5 band...6
## <chr> <chr> <chr> <chr> <chr> <chr>
## 1 Mick Stones Mick Stones Mick Stones
## 2 John Beatles John Beatles John Beatles
## 3 Paul Beatles Paul Beatles Paul Beatles
Za povšimnutí stojí, že pokud jsou ve spojovaných tabulkách shodná jména sloupců, bind_cols()
je do výsledné tabulky přidá všechny. Aby se však zabránilo nepřípustné duplicitě ve jménech sloupců, rozšíří duplicitní jména o příponu.
Z logiky věci nejsou problém odlišné datové typy ve spojovaných tabulkách, ale problém můžou představovat tabulky s různým počtem řádků:
## Error: Can't recycle `..1` (size 3) to match `..2` (size 2).
Protože u bind_cols()
jsou pozorování fakticky identifikována číslem řádku, tak není možné takovou operaci smysluplně provést. bind_cols()
v takovém případě nic nehádá, nic nepředpokládá ani nerecykluje, ale poctivě vyvěsí bílou vlajku a vrátí chybu.
16.6.2 Slučování tabulek s *_join()
16.6.2.1 Mutating joins
Slučování tabulek lze provádět pomocí různých slučovacích funkcí. Ty jsou v dplyr jednotně pojmenovány tak, že končí řetězcem *_join. Jejich základní skupina – tzv. mutating joins*, tj. slučovací funkce, které přidávají sloupce, se v zásadě chová jako “inteligentní” varianta bind_cols()
. Pozorování však není definováno číslem řádku, ale proměnnou nebo kombinací více proměnných.
Pro ilustraci slučování jsou potřeba dvě tabulky. Vedle band_members
využijeme dplyr::band_instruments
:
## # A tibble: 3 x 2
## name plays
## <chr> <chr>
## 1 John guitar
## 2 Paul bass
## 3 Keith guitar
Pravděpodobně nejčastěji používanou slučovací funkcí je left_join()
:
Jako argument přijímá právě dvě tabulky x
a y
. Slučovací funkce v dplyr obecně umí pracovat pouze se dvěma tabulkami. Toto omezení je však možné obejít – viz dále.
Dalším důležitým parametrem je by
jeho hodnota určuje, podle kterých sloupců se má provést slučování – tedy které sloupce definují pozorování. V případě, že je hodnota parametru NULL
, potom se sloučení provede na základě všech sloupců, jejich jméno je v obou tabulkách. V tomto případě vrátí dplyr informaci o tom, které sloupce použil.
Příklad použití left_join()
:
## Joining, by = "name"
## # A tibble: 3 x 3
## name band plays
## <chr> <chr> <chr>
## 1 Mick Stones <NA>
## 2 John Beatles guitar
## 3 Paul Beatles bass
left_join()
funguje tak, že vrací všechny řádky z x
a všechny sloupce z tabulky x
i y
. Řádky z x
, pro které neexistuje shoda v tabulce y
mají v připojených sloupcích NA
.
V tomto příkladu nebyl vyplněn parametr by
. left_join()
tedy jako klíč pro slučování použil sloupec name
, který se jako jediný vyskytoval v obou slučovaných tabulkách. Volání
## # A tibble: 3 x 3
## name band plays
## <chr> <chr> <chr>
## 1 Mick Stones <NA>
## 2 John Beatles guitar
## 3 Paul Beatles bass
by pochopitelně vedlo ke stejným výsledkům.
V případě slučování tabulek, ve kterých se vyskytují shodná jména sloupců, která ovšem neidentifikují pozorování je nutné parametr by
specifikovat. Sloučení tabulek se stejnými názvy sloupců by jinak dopadlo například takto:
## Joining, by = c("name", "band")
## # A tibble: 3 x 2
## name band
## <chr> <chr>
## 1 Mick Stones
## 2 John Beatles
## 3 Paul Beatles
Výsledkem by byla vstupní tabulka x
. Pří specifikování identifikačního sloupce (například name
) bude výsledek odlišný:
## # A tibble: 3 x 3
## name band.x band.y
## <chr> <chr> <chr>
## 1 Mick Stones Stones
## 2 John Beatles Beatles
## 3 Paul Beatles Beatles
Slučované tabulky v tomto případě obsahují sloupec se shodným jménem, který není použit ke slučování. V tom případě je tento sloupec (podobně jako u bind_cols()
) přejmenován připojením přípony. Podobu přípony je možné specifikovat parametrem suffix
:
## # A tibble: 3 x 3
## name band.prvni band.druhy
## <chr> <chr> <chr>
## 1 Mick Stones Stones
## 2 John Beatles Beatles
## 3 Paul Beatles Beatles
V předchozích příkladech byl parametr by
použit pouze pro specifikaci jednoho sloupce, jehož jméno bylo přítomno v obou slučovaných tabulkách. Možností nastavení je však více:
by
může obsahovat jména více sloupců zadaných jako nepojmenovaný vektor, napříkladby = c("name","band")
by
může obsahovat i pojmenovaný vektor. Ten má ale zvláštní interpretaci. Jméno každého prvku odpovídá v takovém případě jménu sloupce z tabulkyx
a hodnota jménu sloupce z tabulkyy
. To umožňuje slučování tabulek, i když tyto neobsahují ani jeden sloupec se shodným jménem. Praktickou ukázkou je následující příklad, který využívá tabulkudplyr::band_instruments2
:
## # A tibble: 3 x 2
## artist plays
## <chr> <chr>
## 1 John guitar
## 2 Paul bass
## 3 Keith guitar
Cílem je sloučit tabulky band_members
a band_instruments2
podle jména hudebníka. Tato proměnná se však jmenuje name
v band_members
a artist
v band_instruments2
:
## # A tibble: 3 x 3
## name band plays
## <chr> <chr> <chr>
## 1 Mick Stones <NA>
## 2 John Beatles guitar
## 3 Paul Beatles bass
Používání slučovacích funkcí však má svoje úskalí. Představme si situaci, kdy identifikace pozorování není jednoznačná – tedy situaci, kdy identifikátor pozorování odpovídá více řádkům. Vytvoříme tabulku band_instruments3
, která bude právě tuto podmínku splňovat:
## # A tibble: 6 x 2
## name plays
## <chr> <chr>
## 1 John guitar
## 2 Paul bass
## 3 Keith guitar
## 4 John guitar
## 5 Paul bass
## 6 Keith guitar
Identifikátor – jméno hudebníka – teď není unikátní. Přináleží mu právě dva řádky. Následně tuto novou tabulku sloučíme s band_members
:
## # A tibble: 5 x 3
## name band plays
## <chr> <chr> <chr>
## 1 Mick Stones <NA>
## 2 John Beatles guitar
## 3 John Beatles guitar
## 4 Paul Beatles bass
## 5 Paul Beatles bass
Zde je patrné, že výsledek může být problematický. Výsledná tabulka má větší počet řádků, než vstupní tabulka x
– řádky, pro které bylo v tabulce y
více záznamů se namnožily. dplyr tuto operaci provedl bez jakéhokoliv varování. Je proto kritické kontrolovat v průběhu sestavování datasetu konzistenci dat. Jinak se lehko může stát, že výsledná tabulka a analýza na ni postavená bude bezcenná.
left_join()
není jediný mutating join implementovaný v dplyr, další jsou následující:
right_join()
je bratrleft_join()
. Vrací sloupce zx
iy
, ale řádky zy
.inner_join()
vrací sloupce zx
iy
, ale pouze řádky, která jsou jak vx
, tak vy
.full_join()
vrací všechny sloupce a všechny řádky zx
ay
.
16.6.2.2 Filtering joins
Druhou skupinou slučovacích funkcí jsou tzv. filtering joins. Tyto funkce opět pracují nad dvěma tabulkami x
a y
, ale vždy vrací sloupce pouze z tabulky x
.
První takovou funkcí je semi_join()
, který vrací pouze ty řádky, které existují v obou tabulkách x
i y
. Je to vlastně blízký příbuzný inner_join()
.
Pro ilustraci fungování filtering joins můžeme porovnat výsledky těchto funkcí:
## # A tibble: 2 x 3
## name band plays
## <chr> <chr> <chr>
## 1 John Beatles guitar
## 2 Paul Beatles bass
## # A tibble: 2 x 2
## name band
## <chr> <chr>
## 1 John Beatles
## 2 Paul Beatles
inner_join()
vrací sloupce z obou tabulek – skutečně “slučuje”. semi_join()
vrací sloupce pouze z první tabulky – spíše tedy filtruje na základě informací z druhé tabulky.
Druhou funkcí z této skupiny slučovacích funkcí je anti_join()
, která je svým způsobem inverzní k semi_join()
. Vrací řádky z x
, které nejsou obsaženy v y
:
## # A tibble: 1 x 2
## name band
## <chr> <chr>
## 1 Mick Stones
16.6.2.3 Slučování více tabulek
Všechny funkce *_join()
pracují s dvěma tabulkami. V drtivé většině případů je to zcela postačující, nicméně najdou se i výjimky. V takovém případě je možné využit funkci reduce()
z balíku purrr (součást tidyverse).
Funkce má dva základní argumenty:
-.x
…je seznam (list) nebo atomický vektor
-.f
…je funkce přijímající 2 vstupy
-...
…dodatečné argumenty pro funkci .f
reduce()
funguje tak, že provádí “akumulaci”. Nejprve aplikuje funkci .f
na první dva prvky .x
. V druhé iteraci aplikuje .f
na výstup první iterace a na třetí prvek .x
a tak dále.
list(band_members, band_members, band_members, band_instruments) %>%
reduce(., left_join, by = "name")
## # A tibble: 3 x 5
## name band.x band.y band plays
## <chr> <chr> <chr> <chr> <chr>
## 1 Mick Stones Stones Stones <NA>
## 2 John Beatles Beatles Beatles guitar
## 3 Paul Beatles Beatles Beatles bass
Za pozornost stojí varianty jména sloupce band
, které přesně odpovídají mechanismu fungování reduce()
. V první iteraci nastal konflikt jmen sloupců. Obě slučované tabulky obsahovaly sloupec pojmenovaný band
a proto ve výsledné tabulce dostaly příponu. V druhé iteraci již ke konfliktu nedošlo. Stará tabulka totiž obsahovala jména band.x
a band.y
a nová band
. Sloupec z nové tabulky tak byl připojen bez změny jména.
Příklad by mohl pokračovat:
## # A tibble: 3 x 5
## name band.x band.y band.x.x band.y.y
## <chr> <chr> <chr> <chr> <chr>
## 1 Mick Stones Stones Stones Stones
## 2 John Beatles Beatles Beatles Beatles
## 3 Paul Beatles Beatles Beatles Beatles