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

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

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
##    <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()` 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
##    <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 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á.

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
##    <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"

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

16.5.1 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í.)