10.2 Iterace nad více vektory současně

Někdy potřebujeme iterovat nad více vektory současně. Můžeme např. chtít vytvořit seznam vektorů tisíce gaussovských náhodných čísel, kde každý vektor bude mít jinou střední hodnotu a směrodatnou odchylku. Pomocí funkce map() bychom to mohli udělat např. takto:

m <- 0:5  # požadované střední hodnoty
std <- 1:6  # požadované směrodatné odchylky
z <- map(seq_along(m), ~ rnorm(1000, mean = m[.], sd = std[.]))
str(z)  # struktura výsledného seznamu
## List of 6
##  $ : num [1:1000] -0.429 3.3049 -1.5824 -0.0259 1.429 ...
##  $ : num [1:1000] 0.949 4.397 -0.937 2.243 2.489 ...
##  $ : num [1:1000] -1.47 4.451 -0.323 2.964 6.209 ...
##  $ : num [1:1000] -3.62 1.07 -4.01 8.56 7.5 ...
##  $ : num [1:1000] 7.628 11.075 2.747 3.062 0.362 ...
##  $ : num [1:1000] -1.88 12.73 7.93 16.4 7.15 ...
z %>% map_dbl(mean)  # střední hodnoty jednotlivých vektorů v seznamu
## [1] 0.01795407 1.07686537 1.90785726 2.83404567 4.24788831 4.98405769

Balik purrr vsak pro tyto ucely nabizi prijemnejsi funkce. Pro iterace nad dvema vektory zavadi funkci map2(.x, .y, .f, ...) a odpovidajici zjednodusujici funkce map2_lgl(), map2_int() atd. Vsechny tyto funkce berou vektory .x a .y, nad kterymi maji iterovat, jako sve prvni dva parametry. Treti parametr je jmeno iterovane funkce (musi brat aspon dva parametry). Pripadne dalsi parametry jsou predany funkci .f() jako jeji treti a dalsi parametr. Postup vypoctu ukazuje obrazek 10.2.

Funkce `map2(.x, .y, .f)` aplikuje funkci `.f()` na odpovídající prvky vektorů `.x` a `.y` a vrací seznam stejné délky.

Obrázek 10.2: Funkce map2(.x, .y, .f) aplikuje funkci .f() na odpovídající prvky vektorů .x a .y a vrací seznam stejné délky.

Pokud chceme stejně jako výše vytvořit pět náhodných gaussovsky rozdělených vektorů se středními hodnotami 0, 1 atd. a směrodatnými odchylkami 1, 2 atd., můžeme je sestavit takto (všimněte si, že další parametry, zde délka vytvářených vektorů, musejí být uvedeny až za jménem iterované funkce):

z <- map2(0:5, 1:6, rnorm, n = 1000)

Funkce opět umožňuje zadat místo funkce .f() pravostrannou formuli, kterou na funkci sama převede. Zpracovávaný prvek vektoru .x v zadáme jako .x, prvek vektoru .y jako .y. Řekněme tedy, že chce vytvořit tisíc vektorů náhodných čísel s gaussovským rozdělením a různými středními hodnotami a směrodatnými odchylkami, a z těchto vektorů spočítat jejich střední hodnotu. To můžeme udělat takto:

map2_dbl(0:5, 1:6, ~ mean(rnorm(n = 1000, mean = .x, sd = .y)))
## [1] 0.03152333 0.99982716 2.00315820 3.06865858 3.94469261 4.93881101

Pro iterace nad větším počtem vektorů nabízí purrr funkci pmap(.l, .f, ...) a její zjednodušující varianty pmap_lgl() atd., kde .l je buď seznam nebo tabulka vektorů, nad kterými se má iterovat, a .f je buď funkce, která bere příslušný počet parametrů, nebo pravostranná formule, kterou pmap() převede na funkci. Pokud je .f funkce a jednotlivé vektory v .l nejsou pojmenované, pak se předávají do .f podle svého pořadí. Pokud jsou pojmenované, pak se předávají jménem, takže na jejich fyzickém pořadí v .l nezáleží.

Řekněme, že chceme opět vytvořit seznam náhodných výběrů z gaussovského rozdělení. Každý výběr bude mít různý počet prvků, různou střední hodnotu a různou směrodatnou odchylku. Pokud seznam parametrů nepojmenujeme, musíme mít jednotlivé parametry v seznamu v tom pořadí, v jakém je očekává funkce rnorm(), která vygeneruje náhodná čísla:

n <- (1:5) * 100  # počet pozorování je 100, 200, ..., 500
mu <- 0:4  # střední hodnota je 0, 1, ..., 4
sd <- 1:5  # směrodatná odchylka je 1, 2, ..., 5
pars <- list(n, mu, sd)  # nepojmenovaný seznam parametrů v pořadí
z <- pmap(pars, rnorm)
str(z)  # struktura výsledku
## List of 5
##  $ : num [1:100] -0.1737 -0.5462 0.8767 -0.0866 -0.1762 ...
##  $ : num [1:200] 0.242 0.393 0.564 0.232 4.44 ...
##  $ : num [1:300] 9 3.28 2.94 4.85 4.01 ...
##  $ : num [1:400] 0.974 6.671 1.474 -1.641 9.799 ...
##  $ : num [1:500] -0.642 12.44 2.544 8.096 0.786 ...

Pokud jednotlivé parametry v seznamu pojmenujeme, na jejich pořadí nezáleží, protože se předají jménem:

pars <- list(sd = sd, mean = mu, n = n)  # pojmenovaný seznam parametrů
z <- pmap(pars, rnorm)
str(z)  # struktura výsledku
## List of 5
##  $ : num [1:100] -0.275 -0.022 -0.575 0.127 1.634 ...
##  $ : num [1:200] 1.2958 0.1946 0.728 0.0546 4.5983 ...
##  $ : num [1:300] 0.558 1.489 -1.301 1.575 3.618 ...
##  $ : num [1:400] 6.65 3.44 1.58 7.69 -3.09 ...
##  $ : num [1:500] 5.705 6.483 11.685 -0.245 14.568 ...

Pohodlnější je však zadat parametry jako tabulku:

pars <- tibble::tibble(sd = sd, mean = mu, n = n)
z <- pmap(pars, rnorm)
str(z)  # struktura výsledku
## List of 5
##  $ : num [1:100] 1.52828 -0.43726 0.37728 0.00864 -2.22425 ...
##  $ : num [1:200] 0.961 4.259 1.929 3.967 2.336 ...
##  $ : num [1:300] 1.32 2.08 8.88 1.06 5.83 ...
##  $ : num [1:400] -2.07 1.4 1.53 4.75 5.05 ...
##  $ : num [1:500] 6.094 -3.288 12.799 4.66 -0.994 ...

Pokud místo funkce zadáme .f jako pravostrannou formuli, pak první vektor v seznamu nebo tabulce označíme jako ..1, druhý jako ..2 atd.:

z <- pmap(pars, ~ rnorm(n = ..3, mean = ..2, sd = ..1))
str(z)  # struktura výsledku
## List of 5
##  $ : num [1:100] -0.836 0.688 1.681 -0.764 0.963 ...
##  $ : num [1:200] -0.795 2.255 6.734 2.872 -1.009 ...
##  $ : num [1:300] 3.38 8.17 5.61 3.67 1.85 ...
##  $ : num [1:400] 1.702 -0.238 4.224 0.177 8.111 ...
##  $ : num [1:500] 11.635 -0.291 7.028 7.493 11.365 ...

Balík purrr implementuje i funkci modify2() a funkce walk2() a pwalk(), které umožňují iterovat vedlejší efekty nad více vektory.