--- title: "Manipulace s daty s nástroji z balíku dplyr" author: "Štěpán Mikula" documentclass: article output: pdf_document: default html_document: theme: cerulean toc: yes toc_float: yes fontsize: 10pt classoption: a4paper --- # Manipulace s daty s nástroji z balíku *dplyr* {#kap:dplyr} Balík *tidyr* obsahuje nástroje, které umožňují uživateli velmi jednoduše měnit organizační strukturu tabulky a provádět některé operace spojené s čištěním dat. Praktická práce s daty však vyžaduje více a to nejen v oblasti čištění dat. Typicky potřebujete subsetovat pozorování nebo proměnné, modifikovat nebo vytvářet nové proměnné a agregovat pozorování. Data také obvykle nejsou pouze v jediném zdroji -- tabulce. Je tedy nutné tabulky různými způsoby slučovat. Pro tyto úkoly existuje v R balík *dplyr* -- součást *tidyverse*. ```r library(tidyverse) ``` V této lekci se naučíte: - subsetovat pozorování a proměnné - vytvářet nové a modifikovat stávající proměnné - vytvářet agregované hodnoty z více pozorování - provádět jednotlivé operace zvlášť pro různé skupiny pozorování - spojovat *(join)* a slučovat *(bind)* tabulky - a další... Funkcionalitu, kterou Vám poskytuje *dplyr* můžete získat i s nástroji *base* R. Nicméně používání *dplyr* vám přinese snadnější interaktivní práci, vyšší rychlost (zejména pro "středně velká" data) a srozumitelnost kódu. Výhodou je i kompatibilita API s ostatními částmi *tidyverse*. Pro uživatele, kteří hodlají pracovat s velkými objemy dat (včetně Big Data), je důležitá i další možnost, kterou *dplyr* nabízí. Kód napsaný v *dplyr* může být vykonán s pomocí různých backendů -- nástrojů, které provádí samotnou práci s daty. Vedle základního (defaultního) backendu je k dispozici balík *dtplyr*, který umožňuje vykonat kód s pomocí tříd a funkcí `data.table`, nebo balík *dbplyr*, který umožňuje kód vnitřně přeložit do SQL a nechat ho vykonat vzdálenou databázi. Uživatelé, kteří mají k dispozici speciální infrastrukturu pro analýzu Big Data, mohou podobně využít i backend pro Apache Spark z balíku *sparklyr*. Možnost změnit backend dělá z *dplyr* mocný nástroj, protože umožňuje jednoduché škálování úloh. *dplyr* má implementováno mnoho dalších pokročilých funkcí. Obsahem této lekce jsou však spíše základy, které nicméně pokrývají vše, co je běžně potřeba pro interaktivní práci s daty. O *dplyr* platí více než o všech jiných balících zmíněných v celém kurzu, že je stále ve velmi aktivním vývoji. Dochází k častým a hlubokým změnám jak v API, tak v backendu (respektive backendech). Tato kapitola byla připravována s požitím verze 1.0.0. Udržujte svůj *dplyr* **aktualizovaný**. Určitě je také doporučeníhodné sledovat vývoj a čas od času si projít seznam zahrnutých funkcí. ### Co je obsahem balíku *dplyr*? Hadley Wickham používá pro označení skupin funkcí slovní druhy. Základním slovním druhem v balíku *dplyr* je sloveso. *dplyr* obsahuje slovesa (funkce), které pracují s jednou tabulkou (např. *vyber*, *seřaď*, nebo *agreguj*), nebo (primárně) dvěma tabulkami (*spoj* a *sluč*). Samostatnou funkcionalitou je schopnost *dplyr* spouštět slovesa nejen nad celou jednou tabulkou, ale také nad jejími částmi (skupinami pozorování). Takové operace se nazývají "zgrupované". Obsah přednášky: - slovesa (funkce) pracující s jednou tabulkou - zgrupované operace - slovesa (funkce) pracující se dvěma (nebo více) tabulkami Pro demonstraci funkcí z balíku *dplyr* jsou použita data z balíku *nycflights13* -- údaje o letech z/do NYC v roce 2013: ```r library(nycflights13) ``` Ve většině příkladů budeme používat tabulku `planes` s údaji o letadlech, která do NYC létala: ```r planes %>% print ``` ``` ## # A tibble: 3,322 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing m… EMBRAER EMB-1… 2 55 NA Turbo-… ## 2 N102UW 1998 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 3 N103US 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 4 N104UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 5 N10575 2002 Fixed wing m… EMBRAER EMB-1… 2 55 NA Turbo-… ## 6 N105UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 7 N107US 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 8 N108UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 9 N109UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 10 N110UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## # … with 3,312 more rows ``` #### Poznámka *dplyr* je pokročilou evolucí balíku `plyr`. Balík `plyr` je stále v závislostech některých balíků, které využívají jeho služeb. To může (a pravděpodobně bude) vyvolávat konflikty, kterým se dá čelit jednoduchým způsobem -- vždy nejprve načíst `plyr` a až následně *dplyr*. Pokud načtete balíky v opačném pořadí, tak Vám *dplyr* vypíše upovídané varování. (Tento problém se v čase zmenšuje. Stále však taková situace může reálně nastat.) ## Slovesa pracující s jednou tabulkou ### Výběr řádků (pozorování) Základní funkcí, která umožňuje výběr řádků je `filter()`. Podobně jako většina funkcí z balíku *dplyr* má extrémně jednoduchou syntax: ```r filter(.data, ...) ``` Vstupem funkce je tabulka (`.data`) a jeden nebo více logických predikátů. Výstupem funkce je podmnožina řádků, která splňuje všechny zadané predikáty. Například se můžeme chtít podívat na letadla, která vyrobil Airbus nebo Boeing a mají více než dva motory: ```r planes %>% filter(manufacturer %in% c("AIRBUS INDUSTRIE","BOEING"), engines > 2) ``` ``` ## # A tibble: 2 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N281AT NA Fixed wing mu… AIRBUS INDUST… A340-… 4 375 NA Turbo-… ## 2 N670US 1990 Fixed wing mu… BOEING 747-4… 4 450 NA Turbo-… ``` Vybrány byly pouze řádky splňující všechny podmínky najednou. Následující volání `filter()`, které spojuje dvě výše použité podmínky do jedné logickým AND (`&`) proto vrátí stejný výsledek: ```r planes %>% filter(manufacturer %in% c("AIRBUS INDUSTRIE","BOEING") & engines > 2) ``` ``` ## # A tibble: 2 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N281AT NA Fixed wing mu… AIRBUS INDUST… A340-… 4 375 NA Turbo-… ## 2 N670US 1990 Fixed wing mu… BOEING 747-4… 4 450 NA Turbo-… ``` Podmínky použité ve funkci `filter()` musí po vyhodnocení vracet logické hodnoty `TRUE`/`FALSE`. Ve `filter()` tedy můžeme používat funkce, které vracejí logickou hodnotu. Například nás mohou zajímat všechna letadla z rodiny A340. Budeme tedy chtít vybrat všechny řádky, u nichž proměnná `model` začíná na "A340". Kromě balíku *dplyr* budeme potřebovat i *stringr*: ```r library(stringr) planes %>% filter(str_detect(model,"^A340")) ``` ``` ## # A tibble: 1 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N281AT NA Fixed wing mu… AIRBUS INDUST… A340-… 4 375 NA Turbo-… ``` Je možné používat i funkce, které nevracejí logické hodnoty. V takové případě je však nutné jejich výsledek na logickou proměnou transformovat. Řekněme, že by nás zajímala letadla, kde na jeden motor připadá méně než 10 sedadel: ```r planes %>% filter(seats/engines < 10) ``` ``` ## # A tibble: 39 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N201AA 1959 Fixed wing s… CESSNA 150 1 2 90 Recipro… ## 2 N202AA 1980 Fixed wing m… CESSNA 421C 2 8 90 Recipro… ## 3 N315AT NA Fixed wing s… JOHN G HESS AT-5 1 2 NA 4 Cycle ## 4 N347AA 1985 Rotorcraft SIKORSKY S-76A 2 14 NA Turbo-s… ## 5 N350AA 1980 Fixed wing m… PIPER PA-31-… 2 8 162 Recipro… ## 6 N364AA 1973 Fixed wing m… CESSNA 310Q 2 6 167 Recipro… ## 7 N365AA 2001 Rotorcraft AGUSTA SPA A109E 2 8 NA Turbo-s… ## 8 N376AA 1978 Fixed wing s… PIPER PA-32R… 1 7 NA Recipro… ## 9 N377AA NA Fixed wing s… PAIR MIKE E FALCON… 1 2 NA Recipro… ## 10 N378AA 1963 Fixed wing s… CESSNA 172E 1 4 105 Recipro… ## # … with 29 more rows ``` Ve speciálních případech je užitečné vybírat řádky ne podle splnění určitých podmínek, ale podle jiných kritérií. Pro tyto případy je v balíku *dplyr* obsažena funkce `slice()`. *(Poznámka: zde diskutované funkce `slice()` získala s verzí dplyr mnoho nových úloh a rolí. V předchozích verzích umožňovala pouze výběr řádku na základě jeho čísla.)* V základní variantě přijímá `slice()` číslo řádku, nebo jejich rozsah, který má být vybrán: ```r planes %>% slice(1L) ``` ``` ## # A tibble: 1 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing mul… EMBRAER EMB-14… 2 55 NA Turbo-… ``` ```r planes %>% slice(1L:5L) ``` ``` ## # A tibble: 5 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing mu… EMBRAER EMB-1… 2 55 NA Turbo-… ## 2 N102UW 1998 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 3 N103US 1999 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 4 N104UW 1999 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 5 N10575 2002 Fixed wing mu… EMBRAER EMB-1… 2 55 NA Turbo-… ``` ```r planes %>% slice(c(1,2:5)) ``` ``` ## # A tibble: 5 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing mu… EMBRAER EMB-1… 2 55 NA Turbo-… ## 2 N102UW 1998 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 3 N103US 1999 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 4 N104UW 1999 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 5 N10575 2002 Fixed wing mu… EMBRAER EMB-1… 2 55 NA Turbo-… ``` Poslední příklad ukazuje, že (a) číslo řádku nemusí být nutně zadáno jako integer (`L`) a (b) vstupem může být vektor vytvořený funkcí `c()`. Speciální variantou `slice()` jsou varianty `slice_head()`, `slice_tail()`, `slice_min()` a `slice_max()`. Ty vrací `n` prvních/posledních řádků respektive `n` řádků s nejnižší/nejvyzšší hodnotou: ```r planes %>% slice_head(n = 5) ``` ``` ## # A tibble: 5 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing mu… EMBRAER EMB-1… 2 55 NA Turbo-… ## 2 N102UW 1998 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 3 N103US 1999 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 4 N104UW 1999 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 5 N10575 2002 Fixed wing mu… EMBRAER EMB-1… 2 55 NA Turbo-… ``` ```r planes %>% slice_max(seats, n = 1) ``` ``` ## # A tibble: 1 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N670US 1990 Fixed wing mult… BOEING 747-4… 4 450 NA Turbo-… ``` Počet řádků nemusí být určen jako absolutní číslo, ale je možné ho specifikovat v parametru `prop` jako podíl všech pozorování: ```r planes %>% slice_head(prop = 0.001) ``` ``` ## # A tibble: 3 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing mu… EMBRAER EMB-1… 2 55 NA Turbo-… ## 2 N102UW 1998 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 3 N103US 1999 Fixed wing mu… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ``` Toto volání vrátí 0,1 % pozorování ze začátku tabulky. Poslední speciální variantou `slice()` je `slice_sample()`, která umožňuje provést náhodný výběr pozorování z tabulky. (V předchozích verzích `dplyr` k tomu sloužily funkce `sample_*()`.) Počet řádků v náhodném výběru může být stanoveno opět absolutním číslem (parametr `n`) nebo podílem (parametr `prop`). Funkce umožňuje používat ve výběru váhy (parametr `weight_by`) a zvolit výběr s nahrazením (parametr `replace`). ### Výběr sloupců (proměnných) {#sec:dplyr-select} Pro výběr sloupců slouží funkce `select()`. Syntax je podobná jako v případě `filter()`: ```r select(.data, ...) ``` Do `select()` vstupuje tabulka a identifikace sloupců, které mají být vybrány. Například: ```r planes %>% select(tailnum, manufacturer) ``` ``` ## # A tibble: 3,322 × 2 ## tailnum manufacturer ## ## 1 N10156 EMBRAER ## 2 N102UW AIRBUS INDUSTRIE ## 3 N103US AIRBUS INDUSTRIE ## 4 N104UW AIRBUS INDUSTRIE ## 5 N10575 EMBRAER ## 6 N105UW AIRBUS INDUSTRIE ## 7 N107US AIRBUS INDUSTRIE ## 8 N108UW AIRBUS INDUSTRIE ## 9 N109UW AIRBUS INDUSTRIE ## 10 N110UW AIRBUS INDUSTRIE ## # … with 3,312 more rows ``` Příklad ukazuje první a základní možnost, jak identifikovat sloupec -- a to jeho jménem. `select()` však umožňuje specifikovat sloupec i pomocí čísla pozice. Následující volání funkce `select()` tak vrací stejný výsledek, jako tomu byl v případě identifikace sloupců jejich jménem. ```r planes %>% select(1,4) ``` ``` ## # A tibble: 3,322 × 2 ## tailnum manufacturer ## ## 1 N10156 EMBRAER ## 2 N102UW AIRBUS INDUSTRIE ## 3 N103US AIRBUS INDUSTRIE ## 4 N104UW AIRBUS INDUSTRIE ## 5 N10575 EMBRAER ## 6 N105UW AIRBUS INDUSTRIE ## 7 N107US AIRBUS INDUSTRIE ## 8 N108UW AIRBUS INDUSTRIE ## 9 N109UW AIRBUS INDUSTRIE ## 10 N110UW AIRBUS INDUSTRIE ## # … with 3,312 more rows ``` ```r planes %>% names ``` ``` ## [1] "tailnum" "year" "type" "manufacturer" "model" ## [6] "engines" "seats" "speed" "engine" ``` #### Funkce `select()` a speciální funkce Při identifikaci sloupců je možné využít speciální funkce. Některé fungují pouze "uvnitř" `select()` a některých dalších funkcí z *tidyverse*. První taková funkce je `:`. Umožňuje specifikovat rozsah sloupců, místo vypisování všech prvků. Všechny následující volání tak vrací stejný výsledek: ```r planes %>% select(1,2,3,4,8) planes %>% select(tailnum, year, type, manufacturer, speed) planes %>% select(1:4, 8) planes %>% select(tailnum:manufacturer, speed) ``` Další speciální funkcí je `-` (mínus). Tato funkce umožňuje "negativní" výběr. Při jejím použití není sloupec zahrnut, ale naopak vypuštěn: ```r planes %>% select(-tailnum, -year, -type, -manufacturer, -speed) ``` ``` ## # A tibble: 3,322 × 4 ## model engines seats engine ## ## 1 EMB-145XR 2 55 Turbo-fan ## 2 A320-214 2 182 Turbo-fan ## 3 A320-214 2 182 Turbo-fan ## 4 A320-214 2 182 Turbo-fan ## 5 EMB-145LR 2 55 Turbo-fan ## 6 A320-214 2 182 Turbo-fan ## 7 A320-214 2 182 Turbo-fan ## 8 A320-214 2 182 Turbo-fan ## 9 A320-214 2 182 Turbo-fan ## 10 A320-214 2 182 Turbo-fan ## # … with 3,312 more rows ``` Speciální funkce je možné kombinovat -- je například možné vypustit sloupce identifikované rozsahem (`:`): ```r planes %>% select(-tailnum:-manufacturer, -speed) ``` ``` ## # A tibble: 3,322 × 4 ## model engines seats engine ## ## 1 EMB-145XR 2 55 Turbo-fan ## 2 A320-214 2 182 Turbo-fan ## 3 A320-214 2 182 Turbo-fan ## 4 A320-214 2 182 Turbo-fan ## 5 EMB-145LR 2 55 Turbo-fan ## 6 A320-214 2 182 Turbo-fan ## 7 A320-214 2 182 Turbo-fan ## 8 A320-214 2 182 Turbo-fan ## 9 A320-214 2 182 Turbo-fan ## 10 A320-214 2 182 Turbo-fan ## # … with 3,312 more rows ``` Výsledné tabulky jsou pochopitelně shodné. Obě tyto speciální funkce vyžadují přesnou specifikaci jména nebo pozice sloupce. V reálném životě občas pracujeme s poněkud vágnějším zadáním. Mohli bychom chtít například vybrat všechny sloupce, které obsahují informace o motorech. Ty jsou v tabulce `planes` dva `engine` (typ motoru) a `engines` (počet motorů). První možností je samozřejmě možné použít následující volání a vybrat sloupce jejich výčtem: ```r planes %>% select(engine,engines) ``` To však není praktické v případě, že pracujeme s větším množstvím sloupců, jejichž názvy jsou systematické. V tom případě je užitečné sáhnout po *select helpers* (funkcích pomocníčcích chcete-li). *dplyr* jich nabízí hned několik: - `starts_with()` vybírá sloupce, jejichž jméno začíná na řetězec, který je argumentem funkce `starts_with()` - `ends_with()` vybírá sloupce, jejichž jméno končí na řetězec, který je argumentem funkce `ends_with()` - `contains()` vybírá sloupce, jejichž jméno obsahuje řetězec, který je argumentem funkce `contains()` - `matches()` vybírá sloupce, jejichž jméno odpovídá zadanému regulárnímu výrazu - `num_range()` slouží pro výběr sloupců, jejichž jméno je tvořeno kombinací řetězce a čísla -- například `trial_1`, `trial_2`,... - `everything()` vrací všechny sloupce - `last_col()` vrací poslední sloupec Pro výběr proměnných se vztahem k motorům lze použít hned tři funkce: ```r planes %>% select(starts_with("engine")) planes %>% select(contains("engine")) planes %>% select(matches("^engine")) ``` První a třetí varianta vybere všechny sloupce, které začínají na "engine". Druhé variantě postačí k výběru, že řetězec "engine" se vyskytuje kdekoliv ve jméně sloupce. Další *select helpers* umožňují vybrat sloupce podle jmen ze vstupního vektoru. - `all_of()` vrátí tabulku s vybranými sloupci pouze tehdy, pokud se ji podaří najít všechna jména obsažená ve vstupním vektoru. V opačném případě vrátí chybu. - `any_of()` vrátí prostě jenom ty sloupce, které v tabulce najde. ```r planes %>% select(all_of("engine")) # Vrátí jeden sloupec planes %>% select(all_of(c("engine","Engine"))) # Vrátí chybu planes %>% select(any_of(c("engine","Engine"))) # Vrátí jeden sloupec planes %>% select(any_of(c("Engine"))) # Nevrátí řádný sloupec. ``` *Select helpers* mohou být kombinováni se všemi ostatními způsoby identifikace sloupců: ```r planes %>% select(tailnum, starts_with("engine")) ``` ``` ## # A tibble: 3,322 × 3 ## tailnum engines engine ## ## 1 N10156 2 Turbo-fan ## 2 N102UW 2 Turbo-fan ## 3 N103US 2 Turbo-fan ## 4 N104UW 2 Turbo-fan ## 5 N10575 2 Turbo-fan ## 6 N105UW 2 Turbo-fan ## 7 N107US 2 Turbo-fan ## 8 N108UW 2 Turbo-fan ## 9 N109UW 2 Turbo-fan ## 10 N110UW 2 Turbo-fan ## # … with 3,312 more rows ``` Posledním *select helper* je `where()`. Tato funkce umožňuje vybrat sloupce s pomocí funkce vracející logickou hdonotu. Je tak možné například vybrat pouze sloupce, které obahují celá čísla: ```r planes %>% select(where(is.integer)) ``` ``` ## # A tibble: 3,322 × 4 ## year engines seats speed ## ## 1 2004 2 55 NA ## 2 1998 2 182 NA ## 3 1999 2 182 NA ## 4 1999 2 182 NA ## 5 2002 2 55 NA ## 6 1999 2 182 NA ## 7 1999 2 182 NA ## 8 1999 2 182 NA ## 9 1999 2 182 NA ## 10 1999 2 182 NA ## # … with 3,312 more rows ``` Všiměte si, že samotná funkce `is.integer` je parametrem `where()` a nikoliv její výstup. Je proto nutné do funkce zadat `is.integer` a nikoliv `is.integer()`. Funkce `where()` je jedna z novinek v *dplyr* 1.0.0 a společně s dalšími funkcemi nahradila tzv. scoped varianty základních funkcí (`select_if()` atp.). *Select helper* s velmi specifickým využitím je `everything()`, které slouží k vybrání všeho. Nebo lépe všeho ostatního. Pokud z nějakého důvodu chceme změnit pořadí sloupců v tabulce, potom se hodí právě `everything()`. ```r planes %>% select(engine, engines, everything()) ``` ``` ## # A tibble: 3,322 × 9 ## engine engines tailnum year type manufacturer model seats speed ## ## 1 Turbo-fan 2 N10156 2004 Fixed wing … EMBRAER EMB-1… 55 NA ## 2 Turbo-fan 2 N102UW 1998 Fixed wing … AIRBUS INDUS… A320-… 182 NA ## 3 Turbo-fan 2 N103US 1999 Fixed wing … AIRBUS INDUS… A320-… 182 NA ## 4 Turbo-fan 2 N104UW 1999 Fixed wing … AIRBUS INDUS… A320-… 182 NA ## 5 Turbo-fan 2 N10575 2002 Fixed wing … EMBRAER EMB-1… 55 NA ## 6 Turbo-fan 2 N105UW 1999 Fixed wing … AIRBUS INDUS… A320-… 182 NA ## 7 Turbo-fan 2 N107US 1999 Fixed wing … AIRBUS INDUS… A320-… 182 NA ## 8 Turbo-fan 2 N108UW 1999 Fixed wing … AIRBUS INDUS… A320-… 182 NA ## 9 Turbo-fan 2 N109UW 1999 Fixed wing … AIRBUS INDUS… A320-… 182 NA ## 10 Turbo-fan 2 N110UW 1999 Fixed wing … AIRBUS INDUS… A320-… 182 NA ## # … with 3,312 more rows ``` Změní pořadí sloupců tak, že na první pozici přesune `engine` a `engines` a následně do tabulky vyskládá všechny ostatní sloupce. Díky `everything()` není nutné jejich jména vypisovat. Do verze 1.0.0 bylo použití `select()` prakticky jedinou možností jak změnit pořadí sloupců. O dtéto verze v *dplyr* existuje specializovaná funkce `relocate()`, která také umí pracovat se *select helpers*. #### Další speciální funkce Při práci s výběry, které jsou v podstatě jen logickým vektorem nad jmény sloupců, je možné používat logické operátory `!` (negace), `|` (OR) a `&` (AND): ```r planes %>% select(!starts_with("engi")) # Vrátí sloupce, které nezačínají na "engi" ``` ``` ## # A tibble: 3,322 × 7 ## tailnum year type manufacturer model seats speed ## ## 1 N10156 2004 Fixed wing multi engine EMBRAER EMB-145XR 55 NA ## 2 N102UW 1998 Fixed wing multi engine AIRBUS INDUSTRIE A320-214 182 NA ## 3 N103US 1999 Fixed wing multi engine AIRBUS INDUSTRIE A320-214 182 NA ## 4 N104UW 1999 Fixed wing multi engine AIRBUS INDUSTRIE A320-214 182 NA ## 5 N10575 2002 Fixed wing multi engine EMBRAER EMB-145LR 55 NA ## 6 N105UW 1999 Fixed wing multi engine AIRBUS INDUSTRIE A320-214 182 NA ## 7 N107US 1999 Fixed wing multi engine AIRBUS INDUSTRIE A320-214 182 NA ## 8 N108UW 1999 Fixed wing multi engine AIRBUS INDUSTRIE A320-214 182 NA ## 9 N109UW 1999 Fixed wing multi engine AIRBUS INDUSTRIE A320-214 182 NA ## 10 N110UW 1999 Fixed wing multi engine AIRBUS INDUSTRIE A320-214 182 NA ## # … with 3,312 more rows ``` ```r planes %>% select(starts_with("e") & ends_with("s")) # Vrátí sloupce začínající na "e" a zároveň končící na "s" ``` ``` ## # A tibble: 3,322 × 1 ## engines ## ## 1 2 ## 2 2 ## 3 2 ## 4 2 ## 5 2 ## 6 2 ## 7 2 ## 8 2 ## 9 2 ## 10 2 ## # … with 3,312 more rows ``` ```r planes %>% select(starts_with("e") | ends_with("s")) # Vrátí sloupce začínající na "e" nebo končící na "s" ``` ``` ## # A tibble: 3,322 × 3 ## engines engine seats ## ## 1 2 Turbo-fan 55 ## 2 2 Turbo-fan 182 ## 3 2 Turbo-fan 182 ## 4 2 Turbo-fan 182 ## 5 2 Turbo-fan 55 ## 6 2 Turbo-fan 182 ## 7 2 Turbo-fan 182 ## 8 2 Turbo-fan 182 ## 9 2 Turbo-fan 182 ## 10 2 Turbo-fan 182 ## # … with 3,312 more rows ``` Podobně je možné výběry kombinovat pomocí funkce `c()`: ```r planes %>% select(starts_with(c("e","s"))) # Vrátí sloupce začínající na "e" nebo na "s" ``` ``` ## # A tibble: 3,322 × 4 ## engines engine seats speed ## ## 1 2 Turbo-fan 55 NA ## 2 2 Turbo-fan 182 NA ## 3 2 Turbo-fan 182 NA ## 4 2 Turbo-fan 182 NA ## 5 2 Turbo-fan 55 NA ## 6 2 Turbo-fan 182 NA ## 7 2 Turbo-fan 182 NA ## 8 2 Turbo-fan 182 NA ## 9 2 Turbo-fan 182 NA ## 10 2 Turbo-fan 182 NA ## # … with 3,312 more rows ``` #### Výběr a přejmenování sloupce Jednou ze speciálních funkcí je i `=`. To slouží v `select()` pro přejmenování. Například volání ```r planes %>% select(tailnum, company = manufacturer) ``` ``` ## # A tibble: 3,322 × 2 ## tailnum company ## ## 1 N10156 EMBRAER ## 2 N102UW AIRBUS INDUSTRIE ## 3 N103US AIRBUS INDUSTRIE ## 4 N104UW AIRBUS INDUSTRIE ## 5 N10575 EMBRAER ## 6 N105UW AIRBUS INDUSTRIE ## 7 N107US AIRBUS INDUSTRIE ## 8 N108UW AIRBUS INDUSTRIE ## 9 N109UW AIRBUS INDUSTRIE ## 10 N110UW AIRBUS INDUSTRIE ## # … with 3,312 more rows ``` vybere sloupce `tailnum` a `manufacturer`. Sloupec `manufacturer` však zároveň přejmenuje na `company`. Speciálně pro přejmenovávání sloupců je v *dplyr* obsažena funkce `rename()` (fakticky jde jen o lehkou mutaci `select()`). Ta sloupce nevybírá, ale jen přejmenovává. Použití `=` je v ní povinné: ```r planes %>% rename(tailnum, company = manufacturer) ``` ``` ## Error: All renaming inputs must be named. ``` Po opravě získáme správný výsledek: ```r planes %>% rename(company = manufacturer) ``` ``` ## # A tibble: 3,322 × 9 ## tailnum year type company model engines seats speed engine ## ## 1 N10156 2004 Fixed wing multi engine EMBRAER EMB-… 2 55 NA Turbo… ## 2 N102UW 1998 Fixed wing multi engine AIRBUS… A320… 2 182 NA Turbo… ## 3 N103US 1999 Fixed wing multi engine AIRBUS… A320… 2 182 NA Turbo… ## 4 N104UW 1999 Fixed wing multi engine AIRBUS… A320… 2 182 NA Turbo… ## 5 N10575 2002 Fixed wing multi engine EMBRAER EMB-… 2 55 NA Turbo… ## 6 N105UW 1999 Fixed wing multi engine AIRBUS… A320… 2 182 NA Turbo… ## 7 N107US 1999 Fixed wing multi engine AIRBUS… A320… 2 182 NA Turbo… ## 8 N108UW 1999 Fixed wing multi engine AIRBUS… A320… 2 182 NA Turbo… ## 9 N109UW 1999 Fixed wing multi engine AIRBUS… A320… 2 182 NA Turbo… ## 10 N110UW 1999 Fixed wing multi engine AIRBUS… A320… 2 182 NA Turbo… ## # … with 3,312 more rows ``` Tabulka obsahuje všechny sloupce, ale jeden z nich byl přejmenován. ## Tvorba a úprava obsahu Balík *dplyr* obsahuje dvě základní funkce pro vytváření a agregaci obsahu v tabulkách: `mutate()` a `summarise()` ### Tvorba nových sloupců s `mutate()` Funkce `mutate()` vytváří nové sloupce, proměnné, v tabulce. Zachovává tedy počet řádků v tabulce a přidává nové sloupce. Syntax `mutate()` je podobně jako u dalších funkcí z *tidyverse* poměrně střídmá: ```r mutate(.data, ...) ``` Funkce přijímá vstupní tabulku a specifikaci sloupců, které se mají vytvořit v `...`. Fungování `mutate()` může být ilustrováno následujícím (mírně zjednodušujícím) schématem: ![Tvorba nových sloupců s `mutate()`](figs/dplyr/fig_mutate.png){width=50%} `mutate()` může být použito i pro modifikaci stávajících sloupců. V tomto případě však `mutate()` interně nejprve vytvoří nový sloupec a až následně jím nahradí sloupec původní. Při modifikaci sloupce na opravdu velkých tabulkách tak může `mutate()` spotřebovávat nečekané množství systémových zdrojů. Praktické využití `mutate()` je možné ilustrovat na příkladu. Například můžeme chtít pro každé pozorování (řádek, letadlo) v tabulce `planes` spočítat, kolik sedadel připadá na jeden motor a zjistit, zda se jedná o vrtulové letadlo: ```r planes %>% mutate( seats_per_engine = (seats/engines) %>% round(), turbo_prop_plane = engine == "Turbo-prop" ) #%>% ``` ``` ## # A tibble: 3,322 × 11 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing m… EMBRAER EMB-1… 2 55 NA Turbo-… ## 2 N102UW 1998 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 3 N103US 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 4 N104UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 5 N10575 2002 Fixed wing m… EMBRAER EMB-1… 2 55 NA Turbo-… ## 6 N105UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 7 N107US 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 8 N108UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 9 N109UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 10 N110UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## # … with 3,312 more rows, and 2 more variables: seats_per_engine , ## # turbo_prop_plane ``` ```r #select(seats_per_engine, turbo_prop_plane, everything()) ``` `mutate()` vytvořilo dva nové sloupce. Sloupec `seats_per_engine` obsahuje zaokrouhlený počet sedadel na motor. Za povšimnutí stojí způsob, jakým byl jeho výpočet ve funkci `mutate()` specifikován. Na levé straně je jméno nově vytvářeného sloupce. Na pravé straně od "=" je postup, který se má použít pro vytvoření jejího obsahu. Jména sloupců z tabulky se přitom používají jako proměnné. Příklad také ukazuje, že v `mutate()` je možné používat komplikované výrazy včetně trubek `%>%`. V jednom volání `mutate()` je možné vytvořit více nových sloupců. Jednotlivé specifikace jsou ve volání odděleny čárkou. Druhý vytvořený sloupec ukazuje příklad vytvoření logické proměnné. Ohledně typu zpracovávaných nebo výsledných proměnných nemá `mutate()` žádné omezení. `mutate()` přidává nově vytvořené sloupce na konec tabulky. Proto je v příkladu použita funkce `select()`, která je přesunuje na začátek tabulky. Verze 1.0.0 umožňuje nastavit, kde se v tabulce nové sloupce vytvoří, nicméně tato funkcionalita je stále ve fázi vývoje. U popisu fungování `mutate()` je výše zmíněná možnost modifikace stávajících sloupců. V praxi se taková operace provede jednoduše. Předpokládejme, že chceme sloupec `year` nahradit jeho vlastním logaritmem: ```r planes %>% mutate( year = log(year) ) ``` ``` ## # A tibble: 3,322 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 7.60 Fixed wing m… EMBRAER EMB-1… 2 55 NA Turbo-… ## 2 N102UW 7.60 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 3 N103US 7.60 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 4 N104UW 7.60 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 5 N10575 7.60 Fixed wing m… EMBRAER EMB-1… 2 55 NA Turbo-… ## 6 N105UW 7.60 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 7 N107US 7.60 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 8 N108UW 7.60 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 9 N109UW 7.60 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 10 N110UW 7.60 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## # … with 3,312 more rows ``` Pokud jméno nového sloupce odpovídá některému sloupci, který již je v tabulce obsažen, je tento novým sloupcem nahrazen. `mutate()` umí pracovat i s proměnnými, které nejsou součástí tabulky. V následujícím případě je nově vytvořený sloupec `this_is_true` naplněn konstantou přiřazenou do proměnné `x`. ```r x <- TRUE planes %>% mutate( this_is_true = x ) %>% select(this_is_true, everything()) ``` ``` ## # A tibble: 3,322 × 10 ## this_is_true tailnum year type manufacturer model engines seats speed ## ## 1 TRUE N10156 2004 Fixed win… EMBRAER EMB-… 2 55 NA ## 2 TRUE N102UW 1998 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 3 TRUE N103US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 4 TRUE N104UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 5 TRUE N10575 2002 Fixed win… EMBRAER EMB-… 2 55 NA ## 6 TRUE N105UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 7 TRUE N107US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 8 TRUE N108UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 9 TRUE N109UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 10 TRUE N110UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## # … with 3,312 more rows, and 1 more variable: engine ``` Stejného výsledku by bylo dosaženo, kdyby byla konstanta definována přímo v `mutate()` tj. `this_is_true = TRUE`. Na proměnnou, která takto do `mutate()` vstupuje zvnějšku je uvaleno omezení: musí mít délku jedna, nebo délku odpovídající počtu řádků tabulky. Tato podmínka není v následujícím příkladu splněna (vektor `x` má délku 3): ```r x <- c(TRUE, TRUE, TRUE) planes %>% mutate( this_is_true = x ) %>% select(this_is_true, everything()) ``` ``` ## Error: Problem with `mutate()` column `this_is_true`. ## ℹ `this_is_true = x`. ## ℹ `this_is_true` must be size 3322 or 1, not 3. ``` Pokud má vektor `x` délku 1, potom je tato jedna hodnota přiřazena ke každému řádku. Pokud je délka `x` právě rovna počtu řádků, potom je ke každému řádku přiřazena hodnota na odpovídající pozici: ```r x <- 1:nrow(planes) planes %>% mutate( new_variable = x ) %>% select(new_variable, everything()) ``` ``` ## # A tibble: 3,322 × 10 ## new_variable tailnum year type manufacturer model engines seats speed ## ## 1 1 N10156 2004 Fixed win… EMBRAER EMB-… 2 55 NA ## 2 2 N102UW 1998 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 3 3 N103US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 4 4 N104UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 5 5 N10575 2002 Fixed win… EMBRAER EMB-… 2 55 NA ## 6 6 N105UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 7 7 N107US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 8 8 N108UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 9 9 N109UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 10 10 N110UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## # … with 3,312 more rows, and 1 more variable: engine ``` Naprosto stejná pravidla platí pro funkce. V příkladu je použita funkce `rnorm(n)`, která vrací `n` výběrů z normálního rozdělení. První dva příklady jsou vyhodnoceny bez problémů. Poslední je nekorektní a skončí chybou, protože `rnorm(3)` vrací vektor o délce 3. ```r planes %>% mutate( new_variable = rnorm(1) ) %>% select(new_variable, everything()) ``` ``` ## # A tibble: 3,322 × 10 ## new_variable tailnum year type manufacturer model engines seats speed ## ## 1 0.328 N10156 2004 Fixed win… EMBRAER EMB-… 2 55 NA ## 2 0.328 N102UW 1998 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 3 0.328 N103US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 4 0.328 N104UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 5 0.328 N10575 2002 Fixed win… EMBRAER EMB-… 2 55 NA ## 6 0.328 N105UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 7 0.328 N107US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 8 0.328 N108UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 9 0.328 N109UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 10 0.328 N110UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## # … with 3,312 more rows, and 1 more variable: engine ``` ```r planes %>% mutate( new_variable = rnorm(nrow(planes)) ) %>% select(new_variable, everything()) ``` ``` ## # A tibble: 3,322 × 10 ## new_variable tailnum year type manufacturer model engines seats speed ## ## 1 3.02 N10156 2004 Fixed win… EMBRAER EMB-… 2 55 NA ## 2 -0.147 N102UW 1998 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 3 -0.544 N103US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 4 -1.02 N104UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 5 0.0999 N10575 2002 Fixed win… EMBRAER EMB-… 2 55 NA ## 6 0.563 N105UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 7 0.876 N107US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 8 -0.130 N108UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 9 -0.619 N109UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 10 1.05 N110UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## # … with 3,312 more rows, and 1 more variable: engine ``` ```r planes %>% mutate( new_variable = rnorm(3) ) %>% select(new_variable, everything()) ``` ``` ## Error: Problem with `mutate()` column `new_variable`. ## ℹ `new_variable = rnorm(3)`. ## ℹ `new_variable` must be size 3322 or 1, not 3. ``` #### Úskalí `mutate()` Výše byl použit příklad, ve kterém byla při stanovení hodnoty použita proměnná definovaná mimo tabulku. Při troše smůly se může stát, že jméno této proměnné se bude shodovat se jménem některého sloupce. V souladu s logikou R dostane přednost obsah sloupce: ```r tailnum <- TRUE planes %>% mutate( this_is_true = tailnum ) %>% select(this_is_true, everything()) ``` ``` ## # A tibble: 3,322 × 10 ## this_is_true tailnum year type manufacturer model engines seats speed ## ## 1 N10156 N10156 2004 Fixed win… EMBRAER EMB-… 2 55 NA ## 2 N102UW N102UW 1998 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 3 N103US N103US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 4 N104UW N104UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 5 N10575 N10575 2002 Fixed win… EMBRAER EMB-… 2 55 NA ## 6 N105UW N105UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 7 N107US N107US 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 8 N108UW N108UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 9 N109UW N109UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## 10 N110UW N110UW 1999 Fixed win… AIRBUS INDUS… A320… 2 182 NA ## # … with 3,312 more rows, and 1 more variable: engine ``` Další úskalí v použití spočívá v tom, že `mutate()` pracuje nad celou tabulkou a ne nad jednotlivými řádky. Toto chování lze změnit pomocí vhodného zgrupování, ale je potřeba ho mít na paměti. Co to znamená v praxi: ```r planes %>% mutate( mean_year = mean(year, na.rm = TRUE) ) %>% select(mean_year, everything()) ``` ``` ## # A tibble: 3,322 × 10 ## mean_year tailnum year type manufacturer model engines seats speed engine ## ## 1 2000. N10156 2004 Fixed … EMBRAER EMB-… 2 55 NA Turbo… ## 2 2000. N102UW 1998 Fixed … AIRBUS INDU… A320… 2 182 NA Turbo… ## 3 2000. N103US 1999 Fixed … AIRBUS INDU… A320… 2 182 NA Turbo… ## 4 2000. N104UW 1999 Fixed … AIRBUS INDU… A320… 2 182 NA Turbo… ## 5 2000. N10575 2002 Fixed … EMBRAER EMB-… 2 55 NA Turbo… ## 6 2000. N105UW 1999 Fixed … AIRBUS INDU… A320… 2 182 NA Turbo… ## 7 2000. N107US 1999 Fixed … AIRBUS INDU… A320… 2 182 NA Turbo… ## 8 2000. N108UW 1999 Fixed … AIRBUS INDU… A320… 2 182 NA Turbo… ## 9 2000. N109UW 1999 Fixed … AIRBUS INDU… A320… 2 182 NA Turbo… ## 10 2000. N110UW 1999 Fixed … AIRBUS INDU… A320… 2 182 NA Turbo… ## # … with 3,312 more rows ``` `mutate()` v tomto případě vypočítal průměrnou hodnotu ze všech roků a tu přiřadil ke všem sloupcům. Opět je to dáno tím, že `mean` neprodukuje vektor o délce odpovídající počtu řádků, ale vektor o délce 1. ### Agregace proměnných se `summarise()` Podstatou agregace je shrnutí obsahu tabulky (jednoho nebo více sloupců) a vytvoření nové tabulky, která obsahuje tyto agregované hodnoty (typicky statistiky jako průměr, medián, minimum, atp.). ![Agregace obsahu tabulky se `summarise()`](figs/dplyr/fig_summarise.png){width=50%} Pro tyto účely slouží v *dplyr* funkce `summarise()`. Její použití se v logice velmi podobá `mutate()`. To ilustruje následující příklad: ```r planes %>% summarise( min_year = min(year, na.rm = TRUE), max_year = max(year, na.rm = TRUE), min_engines = min(engines, na.rm = TRUE), max_engines = max(engines, na.rm = TRUE) ) ``` ``` ## # A tibble: 1 × 4 ## min_year max_year min_engines max_engines ## ## 1 1956 2013 1 4 ``` V tomto volání funkce `summarise()` jsou vytvořeny 4 agregované hodnoty: maxima a minima ze sloupců `year` a `engines`. Výsledkem je tabulka, která podle stanovených pravidel shrnuje celou tabulku do jediného řádku. ## Další užitečné funkce z balíku *dplyr* Balík *dplyr* obsahuje opravdu velmi mnoho funkcí, které pracují nad jednou tabulkou. V této kapitole je představen lehký výběr těch, které se v praxi datové analýzy používají opravdu často. Funkce `distinct()` je ekvivalentem `unique()` -- vrací tabulku, která obsahuje pouze unikátní pozorování. V případě shody více řádků zachovává v nové tabulce první z nich. Proti `unique()` je rychlejší a hlavně umožňuje specifikovat sloupce, podle kterých se má unikátnost pozorování posuzovat: ```r planes %>% distinct(manufacturer, type) ``` ``` ## # A tibble: 37 × 2 ## type manufacturer ## ## 1 Fixed wing multi engine EMBRAER ## 2 Fixed wing multi engine AIRBUS INDUSTRIE ## 3 Fixed wing multi engine BOEING ## 4 Fixed wing multi engine AIRBUS ## 5 Fixed wing multi engine BOMBARDIER INC ## 6 Fixed wing single engine CESSNA ## 7 Fixed wing multi engine CESSNA ## 8 Fixed wing single engine JOHN G HESS ## 9 Fixed wing multi engine GULFSTREAM AEROSPACE ## 10 Rotorcraft SIKORSKY ## # … with 27 more rows ``` V základním nastavení je výstupní tabulka omezena pouze na proměnné, které byly použity k posouzení unikátnosti. Toto chování se dá změnit pomocí parametru `.keep_all`: ```r planes %>% distinct(manufacturer, type, .keep_all = TRUE) ``` ``` ## # A tibble: 37 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing… EMBRAER EMB-1… 2 55 NA Turbo-f… ## 2 N102UW 1998 Fixed wing… AIRBUS INDUSTR… A320-… 2 182 NA Turbo-f… ## 3 N11206 2000 Fixed wing… BOEING 737-8… 2 149 NA Turbo-f… ## 4 N125UW 2009 Fixed wing… AIRBUS A320-… 2 182 NA Turbo-f… ## 5 N131EV 2009 Fixed wing… BOMBARDIER INC CL-60… 2 95 NA Turbo-f… ## 6 N201AA 1959 Fixed wing… CESSNA 150 1 2 90 Recipro… ## 7 N202AA 1980 Fixed wing… CESSNA 421C 2 8 90 Recipro… ## 8 N315AT NA Fixed wing… JOHN G HESS AT-5 1 2 NA 4 Cycle ## 9 N344AA 1992 Fixed wing… GULFSTREAM AER… G-IV 2 22 NA Turbo-f… ## 10 N347AA 1985 Rotorcraft SIKORSKY S-76A 2 14 NA Turbo-s… ## # … with 27 more rows ``` Užitečnou funkcí je řazení pozorování. To má v *dplyr* na starosti funkce `arrange()`. `arrange()` přijímá jako parametry vstupní tabulku a jména sloupců, podle kterých má tabulku seřadit: ```r planes %>% arrange(manufacturer,engines) ``` ``` ## # A tibble: 3,322 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N365AA 2001 Rotorcraft AGUSTA SPA A109E 2 8 NA Turbo-s… ## 2 N125UW 2009 Fixed wing mu… AIRBUS A320-… 2 182 NA Turbo-f… ## 3 N126UW 2009 Fixed wing mu… AIRBUS A320-… 2 182 NA Turbo-f… ## 4 N127UW 2010 Fixed wing mu… AIRBUS A320-… 2 182 NA Turbo-f… ## 5 N128UW 2010 Fixed wing mu… AIRBUS A320-… 2 182 NA Turbo-f… ## 6 N150UW 2013 Fixed wing mu… AIRBUS A321-… 2 199 NA Turbo-f… ## 7 N151UW 2013 Fixed wing mu… AIRBUS A321-… 2 199 NA Turbo-f… ## 8 N152UW 2013 Fixed wing mu… AIRBUS A321-… 2 199 NA Turbo-f… ## 9 N153UW 2013 Fixed wing mu… AIRBUS A321-… 2 199 NA Turbo-f… ## 10 N154UW 2013 Fixed wing mu… AIRBUS A321-… 2 199 NA Turbo-f… ## # … with 3,312 more rows ``` `arrange()` nejprve řadí tabulku podle první zadaného sloupce, následně podle druhého, atp. Směr řazení je možné změnit pomocí speciální funkce `desc()`: ```r planes %>% arrange(desc(manufacturer),engines) ``` ``` ## # A tibble: 3,322 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N397AA 1985 Fixed wing… STEWART MACO FALCO… 1 2 NA Recipro… ## 2 N521AA NA Fixed wing… STEWART MACO FALCO… 1 2 NA Recipro… ## 3 N347AA 1985 Rotorcraft SIKORSKY S-76A 2 14 NA Turbo-s… ## 4 N537JB 2012 Rotorcraft ROBINSON HELIC… R66 1 5 NA Turbo-s… ## 5 N376AA 1978 Fixed wing… PIPER PA-32… 1 7 NA Recipro… ## 6 N425AA 1968 Fixed wing… PIPER PA-28… 1 4 107 Recipro… ## 7 N545AA 1976 Fixed wing… PIPER PA-32… 1 7 126 Recipro… ## 8 N350AA 1980 Fixed wing… PIPER PA-31… 2 8 162 Recipro… ## 9 N525AA 1980 Fixed wing… PIPER PA-31… 2 8 162 Recipro… ## 10 N377AA NA Fixed wing… PAIR MIKE E FALCO… 1 2 NA Recipro… ## # … with 3,312 more rows ``` ## Operace nad sloupci Funkce z balíku *dplyr* umožňují spouštět funkce nad specifikovanými sloupci tabulky. Prvním příkladem užití takové funkcionality může být výběr sloupců určitého datového typu: ```r planes %>% select(where(is.character)) ``` ``` ## # A tibble: 3,322 × 5 ## tailnum type manufacturer model engine ## ## 1 N10156 Fixed wing multi engine EMBRAER EMB-145XR Turbo-fan ## 2 N102UW Fixed wing multi engine AIRBUS INDUSTRIE A320-214 Turbo-fan ## 3 N103US Fixed wing multi engine AIRBUS INDUSTRIE A320-214 Turbo-fan ## 4 N104UW Fixed wing multi engine AIRBUS INDUSTRIE A320-214 Turbo-fan ## 5 N10575 Fixed wing multi engine EMBRAER EMB-145LR Turbo-fan ## 6 N105UW Fixed wing multi engine AIRBUS INDUSTRIE A320-214 Turbo-fan ## 7 N107US Fixed wing multi engine AIRBUS INDUSTRIE A320-214 Turbo-fan ## 8 N108UW Fixed wing multi engine AIRBUS INDUSTRIE A320-214 Turbo-fan ## 9 N109UW Fixed wing multi engine AIRBUS INDUSTRIE A320-214 Turbo-fan ## 10 N110UW Fixed wing multi engine AIRBUS INDUSTRIE A320-214 Turbo-fan ## # … with 3,312 more rows ``` Výběr byl proveden s použitím dodatečné select-helper funce `where()`. Do té byl vložen výraz, který byl vyhodnocen nad sloupci tabulky. Výsledek, logický vektor, byl potom použit pro výběr funkcí `select()`. Všiměte si, že funkce `is.character()` je v příkladu použita bez závorek. Parametrem `where()` je totiž funkce samotná a nikoliv její výstup. *dplyr* umožňuje uživateli provádět i sofistikovanější operace -- typicky modifikovat sloupce, které splňují určitou podmínku. Můžeme například chtít konvertovat číselné sloupce na `character`. Pro tyto účely slouží funkce `across()`. Ta má dva vstupy: (a) výraz, který identifikuje sloupce, které se mají modifikovat, a (b) výraz, který se má pro samotnou modifikaci použít. řešení by mohlo vypadat následujícím způsobem: ```r planes %>% mutate( across( where(is.numeric), as.character ) ) ``` ``` ## # A tibble: 3,322 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing m… EMBRAER EMB-1… 2 55 Turbo-… ## 2 N102UW 1998 Fixed wing m… AIRBUS INDUST… A320-… 2 182 Turbo-… ## 3 N103US 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 Turbo-… ## 4 N104UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 Turbo-… ## 5 N10575 2002 Fixed wing m… EMBRAER EMB-1… 2 55 Turbo-… ## 6 N105UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 Turbo-… ## 7 N107US 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 Turbo-… ## 8 N108UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 Turbo-… ## 9 N109UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 Turbo-… ## 10 N110UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 Turbo-… ## # … with 3,312 more rows ``` Funkce `across()` funguje výborně s funkcemi jako je `mutate()` nebo `summarise()`. V *dplyr* 1.0.4 přibyly nové funkce, které jsou navženy tak, aby podobným způsobem umožnili pracovat s `filter()`. Jde o `if_any()` a `if_all()`. Jejich syntaxe a parametry jsou stejné jako u `across()`, ale na ro rozdíl od `across()` vracejí logický vektor, který je požadovaným vstupem funkce `filter()`. Následující příklad ukazuje, jak řešit obyvklý problém v datové analýze -- někde nám utíkají pozorování, protože některé řádky v nějakém sloupci obsahují `NA`. Funkce `if_any()` nám umožňuje takové řádky lehce najít: ```r planes %>% filter( if_any( everything(), # tj. hledej ve všech sloupcích is.na ) ) ``` ``` ## # A tibble: 3,299 × 9 ## tailnum year type manufacturer model engines seats speed engine ## ## 1 N10156 2004 Fixed wing m… EMBRAER EMB-1… 2 55 NA Turbo-… ## 2 N102UW 1998 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 3 N103US 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 4 N104UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 5 N10575 2002 Fixed wing m… EMBRAER EMB-1… 2 55 NA Turbo-… ## 6 N105UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 7 N107US 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 8 N108UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 9 N109UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## 10 N110UW 1999 Fixed wing m… AIRBUS INDUST… A320-… 2 182 NA Turbo-… ## # … with 3,289 more rows ``` Funkce `across()`, `where()`, `if_any()` a `if_all()` nahrazují tzv. scoped varianty základních funkcí ze starších verzí *dplyr*. ## Operace nad skupinami řádků Všechny předchozí funkce lze s různou mírou elegance nahradit funkcemi ze základního R. Grupované operace však lze nahradit jen obtížně a v žádném případě ne elegantně. Podstatou zgrupované operace je vyhodnocení funkce nad jednotlivými segmenty tabulky. Na obrázku je zgrupovaná operace provedena funkce `summarise()`: ![Zgrupované operace, příklad `summarise()`](figs/dplyr/fig_group.png){width=50%} `summarise()` je vykonáno nad jednotlivými barvenými grupami. Výsledky za jednotlivé grupy jsou následně složeny do nové tabulky. V praktickém nasazení nás například může zajímat minimální, maximální a průměrný počet sedadel v letadlech jednotlivých výrobců. V prvním kroku je potřeba pomocí funkce `group_by()` vytvořit grupování -- tj. identifikovat řádky, které tvoří jednu grupu. Následně je možné volat funkci `summarise()`: ```r planes %>% group_by(manufacturer) %>% summarise( min_seats = min(seats, na.rm = TRUE), mean_seats = mean(seats, na.rm = TRUE), max_seats = max(seats, na.rm = TRUE) ) ``` ``` ## # A tibble: 35 × 4 ## manufacturer min_seats mean_seats max_seats ## ## 1 AGUSTA SPA 8 8 8 ## 2 AIRBUS 100 221. 379 ## 3 AIRBUS INDUSTRIE 145 187. 379 ## 4 AMERICAN AIRCRAFT INC 2 2 2 ## 5 AVIAT AIRCRAFT INC 2 2 2 ## 6 AVIONS MARCEL DASSAULT 12 12 12 ## 7 BARKER JACK L 2 2 2 ## 8 BEECH 9 9.5 10 ## 9 BELL 5 8 11 ## 10 BOEING 100 175. 450 ## # … with 25 more rows ``` Protože nás zajímají počty sedadel v letadlech *"jednotlivých výrobců"* je pro zgrupování použita proměnná `manufacturer`. `group_by` však umí vytvořit i grupy tvořené kombinací více proměnných. Například by bylo možné zjistit počty sedadel pro skupinu vymezenou výrobcem a typem letounu: ```r planes %>% group_by(manufacturer, type) %>% summarise( min_seats = min(seats, na.rm = TRUE), mean_seats = mean(seats, na.rm = TRUE), max_seats = max(seats, na.rm = TRUE) ) ``` ``` ## `summarise()` has grouped output by 'manufacturer'. You can override using the `.groups` argument. ``` ``` ## # A tibble: 37 × 5 ## # Groups: manufacturer [35] ## manufacturer type min_seats mean_seats max_seats ## ## 1 AGUSTA SPA Rotorcraft 8 8 8 ## 2 AIRBUS Fixed wing multi engine 100 221. 379 ## 3 AIRBUS INDUSTRIE Fixed wing multi engine 145 187. 379 ## 4 AMERICAN AIRCRAFT INC Fixed wing single engine 2 2 2 ## 5 AVIAT AIRCRAFT INC Fixed wing single engine 2 2 2 ## 6 AVIONS MARCEL DASSAULT Fixed wing multi engine 12 12 12 ## 7 BARKER JACK L Fixed wing single engine 2 2 2 ## 8 BEECH Fixed wing multi engine 9 9.5 10 ## 9 BELL Rotorcraft 5 8 11 ## 10 BOEING Fixed wing multi engine 100 175. 450 ## # … with 27 more rows ``` V `group_by()` je možné použít proměnné všech typů (jakkoliv u *double* to asi příliš často nedává smysl). Grupované operace pochopitelně nejsou omezeny pouze na `summarise()`. Následující příklad ukazuje použití s `mutate()`. Jako cvičení můžete kód analyzovat a zjistit, co dělá. ```r planes %>% group_by(manufacturer) %>% mutate( year_diff = year - mean(year, na.rm = TRUE) ) %>% select(tailnum, manufacturer, year, year_diff) %>% arrange(manufacturer, year) ``` ``` ## # A tibble: 3,322 × 4 ## # Groups: manufacturer [35] ## tailnum manufacturer year year_diff ## ## 1 N365AA AGUSTA SPA 2001 0 ## 2 N186US AIRBUS 2002 -5.20 ## 3 N187US AIRBUS 2002 -5.20 ## 4 N188US AIRBUS 2002 -5.20 ## 5 N338NB AIRBUS 2002 -5.20 ## 6 N339NB AIRBUS 2002 -5.20 ## 7 N340NB AIRBUS 2002 -5.20 ## 8 N341NB AIRBUS 2002 -5.20 ## 9 N342NB AIRBUS 2002 -5.20 ## 10 N343NB AIRBUS 2002 -5.20 ## # … with 3,312 more rows ``` V prvním kroku kód přidá zgrupování k tabulce planes. Výsledek této operace můžeme vidět pomocí funkce `class()`: ```r planes %>% group_by(manufacturer) %>% class() ``` ``` ## [1] "grouped_df" "tbl_df" "tbl" "data.frame" ``` Třída tabulky `planes` byla rozšířena o `grouped_df`. To umožní kompatibilním metodám nakládat s tabulkou speciálním způsobem: provést operaci zgrupovaně. Pokud pro danou funkci není "zgrupovaná" metoda dostupná, provede se funkce jako obvykle nad celou tabulkou: ```r planes %>% group_by(manufacturer) %>% summary() ``` ``` ## tailnum year type manufacturer ## Length:3322 Min. :1956 Length:3322 Length:3322 ## Class :character 1st Qu.:1997 Class :character Class :character ## Mode :character Median :2001 Mode :character Mode :character ## Mean :2000 ## 3rd Qu.:2005 ## Max. :2013 ## NA's :70 ## model engines seats speed ## Length:3322 Min. :1.000 Min. : 2.0 Min. : 90.0 ## Class :character 1st Qu.:2.000 1st Qu.:140.0 1st Qu.:107.5 ## Mode :character Median :2.000 Median :149.0 Median :162.0 ## Mean :1.995 Mean :154.3 Mean :236.8 ## 3rd Qu.:2.000 3rd Qu.:182.0 3rd Qu.:432.0 ## Max. :4.000 Max. :450.0 Max. :432.0 ## NA's :3299 ## engine ## Length:3322 ## Class :character ## Mode :character ## ## ## ## ``` Třída `grouped_df()` zůstává u tabulky zachována, dokud není jinou funkcí odstraněna. *dplyr* umožňuje grupování odstranit funkcí `ungroup()`: ```r planes %>% group_by(manufacturer) %>% ungroup() %>% class() ``` ``` ## [1] "tbl_df" "tbl" "data.frame" ``` Částečnou výjimkou z tohoto pravidla je funkce `summarise()`, která získala parametr `.groups`, který umožňuje nastavit, jak má být nastaveno grupování u výstupní tabulky. V dalším kroku příkladu je volána funkce `mutate()`. Ta zvlášť pro každou grupu vypočítá průměrný rok výroby (`mean(year, na.rm = TRUE)`) a tuto hodnotu (vektor o délce 1) odečte od všech hodnot v proměnné `year`. Výsledkem je tak vektor o délce identické s počtem řádků v grupě. Tento vektor je přidán jako sloupec `year_diff`. Tabulka je následně zpřehledněna voláním `select()` a `mutate()`. ### Bezpečné grupování Zgrupované operace představují mimořádně mocný nástroj, který zásadně zjednodušuje a zpřehledňuje datovou analýzu. Mají však svá rizika a to zejména mezi židlí a klávesnicí. Pokud uživatel zapomene, že ve skutečnosti pracuje se zgrupovanou tabulkou může dostat bez varování zásadně odlišné výsledky. Proto je rozumné tabulku "na konci trubky" odgrupovat. Potenciální riziko si uvědomují i tvůrci *dplyr*. Například od verze 1.0.0 vrací `summarise()` po operaci nad zgrupovanou tabulkou varování. Od této verze je také možné také parametrem funkce nastavit, zda a jak má `summarise()` grupování zachovat. (V případě nastavení tohoto parametru už žádné varování nevrací.) ## 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()`) ### 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`: ```r band_members ``` ``` ## # A tibble: 3 × 2 ## name band ## ## 1 Mick Stones ## 2 John Beatles ## 3 Paul Beatles ``` Do `bind_rows()` můžeme vložit více tabulek: ```r bind_rows(band_members, band_members, band_members) ``` ``` ## # A tibble: 9 × 2 ## name band ## ## 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: ```r bind_rows( list(band_members, band_members, band_members) ) ``` ``` ## # A tibble: 9 × 2 ## name band ## ## 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 `map()` z\ balíku **purrr**. 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: ```r band_members %>% rename(NAME = name) %>% bind_rows(., band_members) ``` ``` ## # A tibble: 6 × 3 ## NAME band name ## ## 1 Mick Stones ## 2 John Beatles ## 3 Paul Beatles ## 4 Stones Mick ## 5 Beatles John ## 6 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`. ```r band_members %>% rename(NAME = name) %>% bind_rows(., band_members) ``` ``` ## # A tibble: 6 × 3 ## NAME band name ## ## 1 Mick Stones ## 2 John Beatles ## 3 Paul Beatles ## 4 Stones Mick ## 5 Beatles John ## 6 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`: ```r band_members %>% mutate( name = as.factor(name) ) %>% bind_rows(., band_members) ``` ``` ## # A tibble: 6 × 2 ## name band ## ## 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*: ```r band_members %>% mutate( name = rnorm1) ) %>% bind_rows(., band_members) ``` ``` ## Error: :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()`: ```r bind_cols(band_members, band_members, band_members) ``` ``` ## New names: ## * name -> name...1 ## * band -> band...2 ## * name -> name...3 ## * band -> band...4 ## * name -> name...5 ## * ... ``` ``` ## # A tibble: 3 × 6 ## name...1 band...2 name...3 band...4 name...5 band...6 ## ## 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ů: ```r band_members %>% sample_n(2) %>% bind_cols(band_members,.) ``` ``` ## 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. ### Slučování tabulek s `*_join()` #### 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`: ```r band_instruments ``` ``` ## # A tibble: 3 × 2 ## name plays ## ## 1 John guitar ## 2 Paul bass ## 3 Keith guitar ``` Pravděpodobně nejčastěji používanou slučovací funkcí je `left_join()`: ```r left_join(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) ``` 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()`: ```r left_join(band_members, band_instruments) ``` ``` ## Joining, by = "name" ``` ``` ## # A tibble: 3 × 3 ## name band plays ## ## 1 Mick Stones ## 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í ```r left_join(band_members, band_instruments, by = "name") ``` ``` ## # A tibble: 3 × 3 ## name band plays ## ## 1 Mick Stones ## 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: ```r left_join(band_members, band_members) ``` ``` ## Joining, by = c("name", "band") ``` ``` ## # A tibble: 3 × 2 ## name band ## ## 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ý: ```r left_join(band_members, band_members, by = "name") ``` ``` ## # A tibble: 3 × 3 ## name band.x band.y ## ## 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`: ```r left_join(band_members, band_members, by = "name", suffix = c(".prvni",".druhy")) ``` ``` ## # A tibble: 3 × 3 ## name band.prvni band.druhy ## ## 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říklad `by = 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 tabulky `x` a hodnota jménu sloupce z tabulky `y`. 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á tabulku `dplyr::band_instruments2`: ```r band_instruments2 ``` ``` ## # A tibble: 3 × 2 ## artist plays ## ## 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`: ```r left_join(band_members, band_instruments2, by = c("name" = "artist")) ``` ``` ## # A tibble: 3 × 3 ## name band plays ## ## 1 Mick Stones ## 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: ```r band_instruments3 <- bind_rows(band_instruments, band_instruments) print(band_instruments3) ``` ``` ## # A tibble: 6 × 2 ## name plays ## ## 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`: ```r left_join(band_members, band_instruments3, by = "name") ``` ``` ## # A tibble: 5 × 3 ## name band plays ## ## 1 Mick Stones ## 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 bratr `left_join()`. Vrací sloupce z `x` i `y`, ale řádky z `y`. - `inner_join()` vrací sloupce z `x` i `y`, ale pouze řádky, která jsou jak v `x`, tak v `y`. - `full_join()` vrací všechny sloupce a všechny řádky z `x` a `y`. #### 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í: ```r inner_join(band_members, band_instruments, by = "name") ``` ``` ## # A tibble: 2 × 3 ## name band plays ## ## 1 John Beatles guitar ## 2 Paul Beatles bass ``` ```r semi_join(band_members, band_instruments, by = "name") ``` ``` ## # A tibble: 2 × 2 ## name band ## ## 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`: ```r anti_join(band_members, band_instruments, by = "name") ``` ``` ## # A tibble: 1 × 2 ## name band ## ## 1 Mick Stones ``` #### 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*). ```r reduce(.x, .f, ..., .init) ``` 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. ```r list(band_members, band_members, band_members, band_instruments) %>% reduce(., left_join, by = "name") ``` ``` ## # A tibble: 3 × 5 ## name band.x band.y band plays ## ## 1 Mick Stones Stones Stones ## 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: ```r list(band_members, band_members, band_members, band_members) %>% reduce(., left_join, by = "name") ``` ``` ## # A tibble: 3 × 5 ## name band.x band.y band.x.x band.y.y ## ## 1 Mick Stones Stones Stones Stones ## 2 John Beatles Beatles Beatles Beatles ## 3 Paul Beatles Beatles Beatles Beatles ```