16.4 Zgrupované operace: slovesa na steroidech

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()

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í. Následně je možné volat funkci summarise():

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)
    )
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 35 x 4
##    manufacturer           min_seats mean_seats max_seats
##    <chr>                      <int>      <dbl>     <int>
##  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:

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()` regrouping output by 'manufacturer' (override with `.groups` argument)
## # A tibble: 37 x 5
## # Groups:   manufacturer [35]
##    manufacturer           type                    min_seats mean_seats max_seats
##    <chr>                  <chr>                       <int>      <dbl>     <int>
##  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 engi…         2        2           2
##  5 AVIAT AIRCRAFT INC     Fixed wing single engi…         2        2           2
##  6 AVIONS MARCEL DASSAULT Fixed wing multi engine        12       12          12
##  7 BARKER JACK L          Fixed wing single engi…         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á.

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 x 4
## # Groups:   manufacturer [35]
##    tailnum manufacturer  year year_diff
##    <chr>   <chr>        <int>     <dbl>
##  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():

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:

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():

planes %>% 
    group_by(manufacturer) %>% 
    ungroup() %>% 
    class()
## [1] "tbl_df"     "tbl"        "data.frame"

Podobně však funguje například i as.data.frame():

planes %>% 
    group_by(manufacturer) %>% 
    as.data.frame() %>% 
    class()
## [1] "data.frame"

Ten však odstraní nejen zgrupování, ale i ztibblování. Výsledkem je tak prostý data.frame.

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().

16.4.1 Varianty group_by()

dplyr obsahuje i speciální varianty group_by() vedle tzv. scoped variant group_by_at(), group_by_if a group_by_all() obsahuje zejména rowwise(). rowwise() vytváří skupiny o jednom řádku. Je ho možné použít pro obejití omezení některých funkcí. Pokud je to možné, je rozumné se použití rowwise() vyhnout. Jeho použití typicky velmi zpomaluje celý kód.

16.4.2 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.

16.4.3 Pohled do zpětného zrcátka: grupování a tidyr

Balíčky v tidyverse jsou velmi provázány. I funkce z jiných balíků umí pracovat se zgrupovanými tabulkami. Příkladem může být nest() z tidyr, která má metodu pro zgrupovanou tabulku:

planes %>% 
    group_by(manufacturer) %>% 
    nest()
## # A tibble: 35 x 2
## # Groups:   manufacturer [35]
##    manufacturer         data                
##    <chr>                <list>              
##  1 EMBRAER              <tibble [299 × 8]>  
##  2 AIRBUS INDUSTRIE     <tibble [400 × 8]>  
##  3 BOEING               <tibble [1,630 × 8]>
##  4 AIRBUS               <tibble [336 × 8]>  
##  5 BOMBARDIER INC       <tibble [368 × 8]>  
##  6 CESSNA               <tibble [9 × 8]>    
##  7 JOHN G HESS          <tibble [1 × 8]>    
##  8 GULFSTREAM AEROSPACE <tibble [2 × 8]>    
##  9 SIKORSKY             <tibble [1 × 8]>    
## 10 PIPER                <tibble [5 × 8]>    
## # … with 25 more rows