14  Transformace proměnných

Transformace proměnných patří mezi nejběžnější operace při práci s daty. Od standardizace proměnných, přes jejich čištění, až po vytváření proměnných nových, obsah následující kapitoly budou denní chléb každého analytika.

Hlavním tahounem je zde funkce mutate() z balíčku dplyr. Přestože transformace proměnných lze provádět i bez ní, má tato funkce několik předností.

14.1 Jednoduché transformace

Funkce mutate() přijímá jako svůj první argument dataframe, dalšími argumenty jsou poté jednotlivé transformace:

countries %>% 
  mutate(gdp_milliards = gdp / 1000,
         poverty_risk  = poverty_risk * 100) %>% 
  select(country, gdp, gdp_milliards, poverty_risk)
# A tibble: 38 × 4
   country       gdp gdp_milliards poverty_risk
   <chr>       <dbl>         <dbl>        <dbl>
 1 Belgium   450506.         451.          20.3
 2 Bulgaria   55182.          55.2         38.9
 3 Czechia   207772.         208.          12.2
 4 Denmark   298276.         298.          17.2
 5 Germany  3386000         3386           19  
 6 Estonia    25657.          25.7         23.4
 7 Ireland   324038.         324.          22.7
 8 Greece    184714.         185.          34.8
 9 Spain    1208248         1208.          26.6
10 France   2353090         2353.          17.1
# … with 28 more rows

Zde je vidět nejen aplikace funkce mutate(), ale i její hlavní primární výhoda. Protože jejím výsledkem je dataframe s provedenými transformacemi, je možné na ní navázat dalšími funkcemi, jako je select() nebo filter(). Novým proměnným také můžeme jednoduše přiřadit jméno (viz Kapitola 5). Pokud uložíme výsledek transformace pod novým jménem, bude vytvořena nová proměnná (v našem případě gdp_milliards). Pokud použijeme jméno již existující proměnné, bude přepsána hodnotami (poverty_risk).

Jednou z analýz, která se v datasetu nabízí, je srovnání ekonomické produktivity zemí, a jednou z nejpopulárnějších metrik ekonomické produktivity je HDP. Čtenáři se znalostmi ekonomie ale již jistě tuší problém. HDP se silně odvíjí od počtu obyvatel a není tedy úplně smysluplné porovnávat obří Německo s maličkým Českem. Pro serióznější analýzu by proto bylo lepší využít standardizovanější míru, jakou je například HDP na hlavu. Tuto proměnnou náš dataset neobsahuje, nemusíme ale smutnit. Máme k dispozici jak HDP, tak počet obyvatel a od kýženého výsledku nás děli jedna matematická operace.

Pro rychlé srovnání zemí by také bylo vhodné převést data do standardizovaných jednotek. Takovou jednotkou jsou mimo jiné z skóry, získatelné odečtením průměru proměnné od každé naměřené hodnoty a vydělením rozdílu směrodatnou odchylkou, tedy \(z_i = \frac{x_i - \bar{x}}{sd(x)}\) . Protože se jedná o populární formu standardizace, R pro ni nabízí funkci scale():

countries %>% 
  mutate(gdp_pc = gdp / population,
         gdp_pc_scaled = scale(gdp_pc)) %>% 
  select(country, gdp_pc, gdp_pc_scaled)
# A tibble: 38 × 3
   country   gdp_pc gdp_pc_scaled[,1]
   <chr>      <dbl>             <dbl>
 1 Belgium  0.0395              0.357
 2 Bulgaria 0.00783            -1.06 
 3 Czechia  0.0196             -0.534
 4 Denmark  0.0516              0.897
 5 Germany  0.0409              0.419
 6 Estonia  0.0194             -0.540
 7 Ireland  0.0671              1.59 
 8 Greece   0.0172             -0.640
 9 Spain    0.0259             -0.252
10 France   0.0352              0.162
# … with 28 more rows

Z transformované proměnné gdp_pc_scaled je vidět, že z skór České republiky je -0.53, naše HDP na hlavu se tedy nachází zhruba půl směrodatné odchylky pod průměrem. Naopak Irsko se těší HDP na hlavu o 1.5 směrodatné odchylky vyšší, než je průměr všech zemí v datasetu.

14.2 Transformace po skupinách

Ve výše zmíněných příkladech byly transformace aplikovány na vybrané proměnné jako celek. Co když ale není naším cílem transformovat všechny hodnoty stejným způsobem?

Pro detailnější analýzu ekonomické produktivity zemí může být zajímavé zohlednit jejich politickou historii. Jak si například Česká republika vede ve srovnání s ostatními postsovětskými zeměmi? Pro zodpovězení této otázky je nutné aplikovat funkcí scale() na každou skupinu proměnné postsoviet zvlášť. Naštěstí pro nás, tato operace nemůže být jednoduší, a to díky funkci group_by(), se kterou jsme se již setkali při řezání dataframů (Sekce 11.3):

countries %>% 
  group_by(postsoviet) %>% 
  mutate(gdp_pc = gdp / population,
         gdp_pc_scaled = scale(gdp_pc)) %>% 
  ungroup() %>% 
  select(country, postsoviet, gdp_pc, gdp_pc_scaled)
# A tibble: 38 × 4
   country  postsoviet  gdp_pc gdp_pc_scaled[,1]
   <chr>    <chr>        <dbl>             <dbl>
 1 Belgium  no         0.0395             -0.245
 2 Bulgaria yes        0.00783            -0.781
 3 Czechia  yes        0.0196              0.522
 4 Denmark  no         0.0516              0.329
 5 Germany  yes        0.0409              2.89 
 6 Estonia  yes        0.0194              0.507
 7 Ireland  no         0.0671              1.07 
 8 Greece   no         0.0172             -1.31 
 9 Spain    no         0.0259             -0.894
10 France   no         0.0352             -0.453
# … with 28 more rows

Přestože tento dataframe na první pohled vypadá velmi podobně jako ten předchozí, hodnoty proměnné gdp_pc_scaled jsou odlišné. Česká republika má nyní hodnotu 0.52. Nachází se tedy zhruba půl směrodatné odchylky nad průměrem ostatních postsovětských zemí. Naopak z skór Irska se snížil na 1.1, protože ve srovnání s ostatními západními zeměmi je jeho HDP na hlavu pouze jednu směrodatnou odchylku nad průměrem.

Tohoto srovnání jsme dosáhli právě tím, že jsme před aplikací funkce mutate() rozdělili dataframe pomocí group_by() a všechny následující operace tedy budou prováděny pro západní a postsovětské funkce zvlášť.

Po použití vypněte

Jakmile jednou aplikujete funkci group_by(), bude aktivní ve všech následujících krocích. To může vést ke zmatkům, zpravidla proto, že na ni zapomenete a aplikujete funkce na každou skupinu zvlášť, aniž byste si to uvědomovali. Proto pokaždé, když skončíte s transformací dat nezapomeňte seskupování ukončit pomocí ungroup().

14.3 Řádkové operace

Přesuňme se teď od ekonomické produktivitě k palčivějším tématům. Jedním z ekonomicko-sociálních problémů, se kterými se musí každá země vypořádat, jsou obyvatelé ohrožení chudobou (proměnná poverty_risk) a obyvatelé v materiální deprivaci (material_dep). Naneštěstí pro nás nemáme k dispozici podíl obyvatel ohrožených alespoň jedním z těchto rizik, můžeme ale získat alespoň konzervativní odhad. Maximální možný podíl lidí ohrožených chudobou nebo v materiální deprivaci je možné získat jednoduše součtem obou hodnot pro každou zemi.

Tento krapet kostrbatý problém nám poslouží pro demonstraci řádkových (rowwise) transformací. R ve svém výchozím nastavení aplikuje funkce po sloupcích (columnwise). To s sebou přináší poněkud zákeřnou komplikaci při snaze sečíst dvě hodnoty na stejném řádku dataframu. Pokud chceme aplikovat funkci po řádcích, nikoliv po sloupcích, je nutné využít funkce rowwise(). Ta funguje velmi obdobně jako group_by(), a to včetně jejího “vypnutí” pomocí ungroup():

countries %>% 
  rowwise() %>% 
  mutate(poverty_or_dep = sum(poverty_risk, material_dep, na.rm = TRUE)) %>% 
  ungroup() %>% 
  select(country, poverty_or_dep)
# A tibble: 38 × 2
   country  poverty_or_dep
   <chr>             <dbl>
 1 Belgium           0.316
 2 Bulgaria          0.827
 3 Czechia           0.22 
 4 Denmark           0.24 
 5 Germany           0.281
 6 Estonia           0.35 
 7 Ireland           0.375
 8 Greece            0.708
 9 Spain             0.394
10 France            0.282
# … with 28 more rows

Pomocí funkce rowwise() jsme získali součet podílu lidí ohrožených chudobou a lidí v materiální deprivaci pro každou ze zemí. Jak je vidět, alespoň do jedné z těchto kategorií v České republice spadá maximální 22 % obyvatel.

14.4 Podmíněné transformace

Jednou z myšlenkových operací, ve které počítače vynikají, je rigidní “pokud je splněna podmínka, udělej X”. Pojďme toho využít.

V předchozí sekci jsme porovnávali země na základě standardizovaného HDP na hlavu. Co kdybychom tuto analýzy chtěli vzít o krok dále a vytvořit novou kategoriální proměnnou, jejíž hodnota bude Above average pro země s nadprůměrným HDP na hlavu, a Below average pro země podprůměrné.

K tomu nám dobře poslouží funkce if_else(), která má tři povinné argumenty. Tím prvním je podmínka, jejímž výsledkem musí být buď hodnota “pravda” (TRUE) nebo “nepravda” (FALSE). Druhým argumentem je operace, která bude provedena, pokud je zmíněná podmínka splněna, třetím argumentem poté nepřekvapivě operace provedené v případě nesplnění podmínky. Aplikace pro náš konkrétní případ by vypadala následovně:

countries %>% 
  mutate(gdp_pc_scaled = scale(gdp / population),
         gdp_pc_cat = if_else(gdp_pc_scaled > 0,
                              true = "Above average",
                              false = "Below average")) %>% 
  select(country, gdp_pc_scaled, gdp_pc_cat)
# A tibble: 38 × 3
   country  gdp_pc_scaled[,1] gdp_pc_cat   
   <chr>                <dbl> <chr>        
 1 Belgium              0.357 Above average
 2 Bulgaria            -1.06  Below average
 3 Czechia             -0.534 Below average
 4 Denmark              0.897 Above average
 5 Germany              0.419 Above average
 6 Estonia             -0.540 Below average
 7 Ireland              1.59  Above average
 8 Greece              -0.640 Below average
 9 Spain               -0.252 Below average
10 France               0.162 Above average
# … with 28 more rows

Co kdybychom ale chtěli, aby výsledkem operace byly více než dvě hodnoty? Možná nám přijde, že klasifikovat země pouze jako nadprůměrné a podprůměrné je příliš redukcionistické (populární to výčitka mezi sociology). Země bychom místo toho raději rozdělili do čtyř kategorií:

  • below average pro země se z skóre nižším než -1

  • slightly below average pro země v intervalu -1 až 0

  • slightly above average analogicky pro země mezi 0 a 1

  • above average pro ty se z skórem vyšším, než 1.

Jednou z možností je využít řadu na sebe navazujících if_else funkcí. Tento postup by technicky fungoval, povede ale k mnoha slzám a frustracím přímo úměrným množství funkcí, které je třeba správně zřetězit. Elegantnějším řešením je využít funkci case_when(), která byla vytvořena právě pro tento případ:

countries %>% 
  mutate(gdp_pc = scale(gdp / population),
         gdp_pc_cat = case_when(gdp_pc < -1 ~ "Below average",
                                gdp_pc <= 0 ~ "Slightly below average",
                                gdp_pc <= 1 ~ "Slightly above average",
                                gdp_pc  > 1 ~ "Above average",
                                TRUE ~ "Unknown")) %>% 
  select(country, gdp_pc, gdp_pc_cat)
# A tibble: 38 × 3
   country  gdp_pc[,1] gdp_pc_cat            
   <chr>         <dbl> <chr>                 
 1 Belgium       0.357 Slightly above average
 2 Bulgaria     -1.06  Below average         
 3 Czechia      -0.534 Slightly below average
 4 Denmark       0.897 Slightly above average
 5 Germany       0.419 Slightly above average
 6 Estonia      -0.540 Slightly below average
 7 Ireland       1.59  Above average         
 8 Greece       -0.640 Slightly below average
 9 Spain        -0.252 Slightly below average
10 France        0.162 Slightly above average
# … with 28 more rows

Funkce case_when() má oproti dosavadním funkcí atypickou syntax. Každá z logických podmínek je kondezovaná do formule podminka ~ vysledek. První řádek v této funkci, gdp_pc < -1 ~ "Below average", tedy říká “pokud je hodnota proměnné gdp_pc menší než -1, vrať hodnotu Below average. Pokud tato podmínka splněná není, funkce zkontroluje podmínku následující. Podmínky jsou ověřovány jedna po druhé, přičemž podmínky na vyšších místech jsou ověřeny dříve. Speciální podmínkou je TRUE ~ vysledek, která je je vždy splněna. To se hodí pokud jsou v datech přítomny hodnoty, které nesplňují žádnou z předchozích podmínek. Kdy se může stát, že hodnota nesplňuje žádnou z našich podmínek? Například, pokud se jedná o hodnotu chybějící:

countries %>% 
  mutate(gdp_pc = scale(gdp / population),
         gdp_pc_cat = case_when(gdp_pc < -1 ~ "Below average",
                                gdp_pc <= 0 ~ "Slightly below average",
                                gdp_pc <= 1 ~ "Slightly above average",
                                gdp_pc  > 1 ~ "Above average",
                                TRUE ~ "Unknown")) %>% 
  select(country, gdp_pc, gdp_pc_cat) %>% 
  filter(is.na(gdp_pc))
# A tibble: 4 × 3
  country                gdp_pc[,1] gdp_pc_cat
  <chr>                       <dbl> <chr>     
1 Liechtenstein                  NA Unknown   
2 Montenegro                     NA Unknown   
3 Turkey                         NA Unknown   
4 Bosnia and Herzegovina         NA Unknown