16 Transformace a sumarizace více proměnných
V předchozích dvou kapitolách jsme si představili, jak transformovat a sumarizovat proměnné. Vždy jsme však pracovali maximálně s jednou nebo dvěma proměnnými najednou. V praxi ovšem nejsou neobvyklé situace, ve kterých je nutné aplikovat určitou funkci na desítky, ne-li stovky proměnných najednou. Naštěstí pro nás, Tidyverse pro tyto příložitosti nabízí funkci across()
.
16.1 Transformace většího množství proměnných
Dataset countries
obsahuje několik kategoriálních proměnných, mezi nimi postsoviet
, eu_member
, maj_belief
a di_cat
. Všechny tyto proměnné jsou typu character, pro analýzu by ovšem bylo lepší je převést na typ factor (pro typy objektů viz Kapitola 4).
Jednou možností je aplikovat funkci as.factor()
na každou proměnnou zvlášť:
%>%
countries mutate(postsoviet = as.factor(postsoviet),
eu_member = as.factor(eu_member),
maj_belief = as.factor(maj_belief),
di_cat = as.factor(di_cat)) %>%
select(postsoviet, eu_member, maj_belief, di_cat) %>%
head(5)
# A tibble: 5 × 4
postsoviet eu_member maj_belief di_cat
<fct> <fct> <fct> <fct>
1 no yes catholic Flawed democracy
2 yes yes orthodox Flawed democracy
3 yes yes nonbelief Flawed democracy
4 no yes protestantism Full democracy
5 yes yes catholic Full democracy
Tímto kódem dosáhneme našeho cíle, nejedná se však o nejelegantnější řešení, jelikož opakovaně aplikujeme stejnou funkci na každou z proměnných zvlášť. To nejen náš kód prodlužuje, ale zároveň zvyšuje šanci, že na některém řádku uděláme chybu. Alternativou je funkce across()
:
%>%
countries mutate(across(.cols = c(postsoviet, eu_member, maj_belief, di_cat),
.fns = as.factor)) %>%
select(postsoviet, eu_member, maj_belief, di_cat) %>%
head(5)
# A tibble: 5 × 4
postsoviet eu_member maj_belief di_cat
<fct> <fct> <fct> <fct>
1 no yes catholic Flawed democracy
2 yes yes orthodox Flawed democracy
3 yes yes nonbelief Flawed democracy
4 no yes protestantism Full democracy
5 yes yes catholic Full democracy
Funkce across()
má dva nezbytné argumenty. Prvním z nich je .col
, pomocí kterého specifikujeme proměnné, na které chceme naši funkci aplikovat. Argument .fns
poté specifikuje funkci samotnou. Výsledek je stejný jako v předchozím případě, náš kód je ale kompaktnější.
Tímto ovšem výhody funkce across()
nekončí. Proměnné je v ní možné specifikovat nejen tím, že je vyjmenuje jednu po druhé, ale i pomocí pomocných funkcí, se kterými jsme se již setkali v kapitole věnované výběrů sloupců (Sekce 10.2).
Pokud bychom například chtěli zaokrouhlit všechny numerické proměnné v datasetu na dvě desetinná místa, není nutné jejich názvy vypisovat ručně. Stačí využít kombinace funkcí where()
a is.numeric()
:
%>%
countries mutate(across(.cols = where(is.numeric),
.fns = round, 2)) %>%
select(where(is.numeric)) %>%
head(5)
# A tibble: 5 × 9
gdp population area life_exp uni_prc poverty_risk mater…¹ hdi dem_i…²
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 450506. 11398589 30528 81.2 0.36 0.2 0.11 0.92 7.78
2 55182. 7050034 110879 74.8 0.25 0.39 0.44 0.81 7.03
3 207772. 10610055 78867 79.2 0.22 0.12 0.1 0.89 7.69
4 298276. 5781190 43094 81.2 0.33 0.17 0.07 0.93 9.22
5 3386000 82792351 357022 81 0.25 0.19 0.09 0.94 8.68
# … with abbreviated variable names ¹material_dep, ²dem_index
V rámci across()
je také možné specifikovat argumenty pro aplikovanou funkci. Výše jsem určili, že numerické proměnné mají být zaoukrouhlené na dvě desetinná místa pomocí .fns = round, 2
, kde 2
je argument funkce round()
. Alternativně bychom mohli využít takzvanou tilda notaci (tilde notation):
%>%
countries mutate(across(.cols = where(is.numeric),
.fns = ~round(., 2))) %>%
select(where(is.numeric)) %>%
head(5)
# A tibble: 5 × 9
gdp population area life_exp uni_prc poverty_risk mater…¹ hdi dem_i…²
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 450506. 11398589 30528 81.2 0.36 0.2 0.11 0.92 7.78
2 55182. 7050034 110879 74.8 0.25 0.39 0.44 0.81 7.03
3 207772. 10610055 78867 79.2 0.22 0.12 0.1 0.89 7.69
4 298276. 5781190 43094 81.2 0.33 0.17 0.07 0.93 9.22
5 3386000 82792351 357022 81 0.25 0.19 0.09 0.94 8.68
# … with abbreviated variable names ¹material_dep, ²dem_index
Na rozdíl od předchozího příkladu, funkci round()
zde předchazí tilda (~
) a prvním argumentem je .
. Tato tečka (.
) slouží jako placeholder pro proměnné dosazované do funkce round()
. Jinak řečeno, funkce across()
postupně dosadí každou proměnnou specifikovanou pomocí argumentu .cols
na místo placeholderu .
. Tilda notace je o něco komplexnější, než předchozí způsob, je ale o mnoho flexibilnější, protože nám umožňuje kontrolovat, do kterého argumentu budou námi proměnné dosazeny.
16.2 Sumarizace většího množsví proměnných
Funkci across()
je možné aplikovat v rámci summarise()
identicky jako v případě mutate()
. Toho využijeme primárně pro výpočet deskriptivních statistik. Stejně jako v předchozích kapitál, i zde můžeme můžeme funkce navazovat na sebe:
%>%
countries summarise(across(.cols = where(is.numeric),
.fns = mean, na.rm = TRUE)) %>%
mutate(across(.cols = everything(),
.fns = round, 2))
# A tibble: 1 × 9
gdp population area life_exp uni_prc poverty_risk mater…¹ hdi dem_i…²
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 484601. 16754743 156019. 79.6 0.29 0.24 0.18 0.87 7.64
# … with abbreviated variable names ¹material_dep, ²dem_index
Všimněme si, že při výpočtu průměru numerických proměnných bylo nutné odstranit chybějící proměnné pomocí na.rm = TRUE
(s tímto argumentem jsme se již setkali, viz Sekce 6.2). Všechny získané průměry jsme poté zaokrouhlili pomocí mutate()
.
Výsledkem jsou data v širokém formátu (Kapitola 12). Pro čitelnost bude lepší je převést do formátu dlouhého:
%>%
countries summarise(across(.cols = where(is.numeric),
.fns = mean, na.rm = TRUE)) %>%
mutate(across(.cols = everything(),
.fns = round, 2)) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "mean")
# A tibble: 9 × 2
variable mean
<chr> <dbl>
1 gdp 484601.
2 population 16754743
3 area 156019.
4 life_exp 79.6
5 uni_prc 0.29
6 poverty_risk 0.24
7 material_dep 0.18
8 hdi 0.87
9 dem_index 7.64
16.3 Analýza po skupinách
Stejně v předchozích kapitolách, i zde můžeme aplikovat funkci group_by()
pro skupinovou sumarizaci (a transformaci) dat:
%>%
countries group_by(eu_member) %>%
summarise(across(.cols = where(is.numeric),
.fns = mean, na.rm = TRUE)) %>%
mutate(across(.cols = -eu_member,
.fns = round, 2))
# A tibble: 2 × 10
eu_member gdp popul…¹ area life_…² uni_prc pover…³ mater…⁴ hdi dem_i…⁵
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 no 152949. 1.19e7 1.45e5 78.6 0.27 0.31 0.26 0.84 6.87
2 yes 567514. 1.83e7 1.60e5 79.9 0.3 0.23 0.17 0.88 7.89
# … with abbreviated variable names ¹population, ²life_exp, ³poverty_risk,
# ⁴material_dep, ⁵dem_index
Výsledkem je dataframe, který sumarizuje numerické proměnné pro západní a postsovětské země zvlášť. Všimněme si, že při zaokrouhlování je nutné funkci round()
aplikovat na všechny proměnné s vyjímkou proměnné eu_member
.
Stejně jako v předchozí sekci, i zde je pro čitelnost lepší převést data do dlouhého formátu. Výsledkem bude dataset vhodný pro vizualizaci nebo statistické modelování:
%>%
countries group_by(eu_member) %>%
summarise(across(.cols = where(is.numeric),
.fns = mean, na.rm = TRUE)) %>%
mutate(across(.cols = -eu_member,
.fns = round, 2)) %>%
pivot_longer(cols = -eu_member,
names_to = "variable",
values_to = "mean")
# A tibble: 18 × 3
eu_member variable mean
<chr> <chr> <dbl>
1 no gdp 152949.
2 no population 11949585.
3 no area 144874.
4 no life_exp 78.6
5 no uni_prc 0.27
6 no poverty_risk 0.31
7 no material_dep 0.26
8 no hdi 0.84
9 no dem_index 6.87
10 yes gdp 567514.
11 yes population 18299258.
12 yes area 159999.
13 yes life_exp 79.9
14 yes uni_prc 0.3
15 yes poverty_risk 0.23
16 yes material_dep 0.17
17 yes hdi 0.88
18 yes dem_index 7.89
Na rozdíl od počítače, lidským čtenářům tento formát zpravdila nepřijde příliš přirozený. Ideálně proto data převedeme zpět do širšího formátu, abychom mohli jednoduše porovnat, která skupina zemí si vede lépe:
%>%
countries group_by(eu_member) %>%
summarise(across(.cols = where(is.numeric),
.fns = mean, na.rm = TRUE)) %>%
mutate(across(.cols = -eu_member,
.fns = round, 2)) %>%
pivot_longer(cols = -eu_member,
names_to = "variable",
values_to = "mean") %>%
pivot_wider(names_from = eu_member,
values_from = mean) %>%
mutate(difference = no - yes)
# A tibble: 9 × 4
variable no yes difference
<chr> <dbl> <dbl> <dbl>
1 gdp 152949. 567514. -414565.
2 population 11949585. 18299258. -6349673.
3 area 144874. 159999. -15124.
4 life_exp 78.6 79.9 -1.31
5 uni_prc 0.27 0.3 -0.0300
6 poverty_risk 0.31 0.23 0.08
7 material_dep 0.26 0.17 0.09
8 hdi 0.84 0.88 -0.0400
9 dem_index 6.87 7.89 -1.02
16.4 Sumarizace více proměnných bez použití across()
Přestože je kombinace funkcí summarise()
a across()
velmi užitečná, výsledný dataset není vždy ve formátu, se kterým je jednoduché dále pracovat, zvláště pokud aplikujeme více než jednu funkci najednou. Existuje ovšem trik, využívající převedu mezi širokým a dlouhým formátem, kterým si můžeme práci ulehčit.
Naším cílem může být spočítat průměr, směrodatnou odchylku, maximum a minimum všech numerickách proměnných. Jednou variantou je aplikovat funkci across()
a specifikovat více funkcí pomocí lst()
. Tato funkce umožňuje aplikovat několik funkcí v rámci jednoho across()
Výsledek ovšem není příliš vzhledný:
%>%
countries summarise(across(.cols = where(is.numeric),
.fns = lst(mean, sd, min, max), na.rm = TRUE))
# A tibble: 1 × 36
gdp_m…¹ gdp_sd gdp_min gdp_max popul…² popul…³ popul…⁴ popul…⁵ area_…⁶ area_sd
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 484601. 7.94e5 10735. 3386000 1.68e7 2.41e7 38114 8.28e7 156019. 189008.
# … with 26 more variables: area_min <dbl>, area_max <dbl>,
# life_exp_mean <dbl>, life_exp_sd <dbl>, life_exp_min <dbl>,
# life_exp_max <dbl>, uni_prc_mean <dbl>, uni_prc_sd <dbl>,
# uni_prc_min <dbl>, uni_prc_max <dbl>, poverty_risk_mean <dbl>,
# poverty_risk_sd <dbl>, poverty_risk_min <dbl>, poverty_risk_max <dbl>,
# material_dep_mean <dbl>, material_dep_sd <dbl>, material_dep_min <dbl>,
# material_dep_max <dbl>, hdi_mean <dbl>, hdi_sd <dbl>, hdi_min <dbl>, …
Výsledek není nepoužitelný, abychom se ovšem dostali k čitelné tabulce, museli bychom několikrát využít převodu mezi širokým a dlouhým formátem.
Alternativním způsobem je vybrat proměnné, se kterými chceme pracovat, převést data do dlouhého formátu a poté již aplikovat klasickou funkci summarise()
. Nakonec jen výsledky zaokrouhlíme:
%>%
countries select(where(is.numeric)) %>%
pivot_longer(everything()) %>%
group_by(name) %>%
summarise(mean = mean(value, na.rm = TRUE),
sd = sd(value, na.rm = TRUE),
min = min(value, na.rm = TRUE),
max = max(value, na.rm = TRUE)) %>%
mutate(across(.cols = where(is.numeric),
.fns = round, 2))
# A tibble: 9 × 5
name mean sd min max
<chr> <dbl> <dbl> <dbl> <dbl>
1 area 156019. 189008. 160 783562
2 dem_index 7.64 1.3 4.37 9.87
3 gdp 484601. 793693. 10735. 3386000
4 hdi 0.87 0.05 0.76 0.95
5 life_exp 79.6 2.82 74.8 83.3
6 material_dep 0.18 0.13 0.04 0.48
7 population 16754743 24110721. 38114 82792351
8 poverty_risk 0.24 0.08 0.12 0.42
9 uni_prc 0.29 0.08 0.16 0.41
Hlavní výhodou této metody je, kromě podle našeho názoru větší přehlednosti, že umožňuje specifikovat argumenty pro každou statistickou funkci zvlášť.