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.7373 -0.3658 0.9595 -0.0311 0.31 ...
##  $ : num [1:1000] 0.918 -1.11 1.279 -1.324 1.883 ...
##  $ : num [1:1000] -0.917 -1.588 1.157 3.268 5.831 ...
##  $ : num [1:1000] 6.13 4.243 0.892 -2.908 5.044 ...
##  $ : num [1:1000] 3.76 11.61 1.97 8 -10.8 ...
##  $ : num [1:1000] 12.96 6.05 -10.78 6.3 -8.44 ...
z %>% map_dbl(mean)  # střední hodnoty jednotlivých vektorů v seznamu
## [1] 0.009917511 0.886833563 2.157806711 3.273518951 4.093692378 4.782304445

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.05218614 0.88155062 2.03616458 3.17173130 3.87357526 4.98427829

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.873 1.935 1.708 0.373 0.056 ...
##  $ : num [1:200] -0.501 1.362 3.603 -3.286 -1.954 ...
##  $ : num [1:300] 0.712 -0.131 0.659 1.333 5.517 ...
##  $ : num [1:400] 9.53 3.63 1.29 1.18 3.49 ...
##  $ : num [1:500] 10.019 -1.654 18.975 8.673 0.321 ...

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.358 0.173 -0.687 -0.396 -1.417 ...
##  $ : num [1:200] 3.93 5.66 4.61 5.2 1.07 ...
##  $ : num [1:300] -2.954 1.007 8.952 0.626 4.811 ...
##  $ : num [1:400] 2.64 -2.86 -3.51 3.97 1.85 ...
##  $ : num [1:500] 8.74 5.16 2.63 5.06 12.81 ...

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] -0.696 -0.29 -1.118 -0.762 0.185 ...
##  $ : num [1:200] 0.121 3.208 3.133 -0.27 -1.102 ...
##  $ : num [1:300] 1.416 -0.628 3.918 -0.355 -1.457 ...
##  $ : num [1:400] -0.045 -0.956 7.953 6.801 13.108 ...
##  $ : num [1:500] -0.96048 7.17341 4.86472 -0.00911 5.44954 ...

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.211 1.383 0.7 -0.3 -1.87 ...
##  $ : num [1:200] -2.29 2.05 -2.71 6.96 5.01 ...
##  $ : num [1:300] 1.372 4.457 -0.529 3.322 1.511 ...
##  $ : num [1:400] 12.644 4.125 1.168 -0.291 3.733 ...
##  $ : num [1:500] 10.231 2.981 0.184 10.634 3.81 ...

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