8 map() - kordame sama operatsiooni igale listi liikmele

Järgnevad meetodid töötavad nii listidel, data frame-del kui vektoritel. Seda sellepärast, et formaalselt on list vektori tüüp (rekursiivne vektor), kuhu on võimalik elementidena panna mida iganes, k.a. teisi vektoreid. Enamus R-i funktsioone, mis töötavad lihtsate mitte-rekursiivsete vektoritega (ja df-dega), ei tööta listide peal.

purrr::map() perekonna funktsioonid töötavad nii lihtsate vektorite kui listide peal. Need funktsioonid rakendavad kasutaja poolt ette antud funktsiooni järjest igale vektori elemendile. map() vajab 2 argumenti: vektor ja funktsioon, mida selle vektori elementidele rakendada. map() võtab sisse listi (vektori, data frame) ja väljastab listi (vektori, data frame). Seega saab seda hästi pipe-s rakendada.

Kui tahad, map() anda funktsiooni lisaargumentidega, siis need eraldatakse komadega map(list1, round, digits = 2).

Kui sa ei taha väljundina listi, vaid lihtsat numbrilist vektorit, siis kasuta map_dbl().

1:10 %>%
  map(rnorm, n = 10) %>%
  map_dbl(mean)
#>  [1]  0.873  2.408  3.029  4.393  4.647  6.449  6.968
#>  [8]  7.740  9.335 10.322

map()-l on kokku 8 versiooni erinevate väljunditega.

  • map() - list

  • map_dbl() - floating point number vektor

  • map_chr() - character vektor

  • map_dfc() - data frame column binded

  • map_dfr() - data frame row binded (lisab iga elemendi df-i reana)

  • map_int() - integer vektor

  • map_lgl() - logical vektor

  • walk() - nähtamatu väljund (kasutatakse funktsioonide puhul, mis ei anna command line-le väljundit, nagu plot() või failide salvestamine).

sisestame map-i funktsiooni asemel ekspressiooni max(df1$col1) - min(df1$col1):

ekspressiooni juhatab sisse ~ (tilde) ja seal asendatakse see, millega opereeritakse, .x -ga: ~ max(.x) - min(.x).

Kasutades funktsiooni pluck() nopime järgnevas koodis igast “params” listi alam-listist mu ja sd ning kasutame neid, et genereerida 3 portsu juhuslikke arve, millest igas on 5 juhuslikku arvu, mis on genereeritud vastavalt selles alam-listis spetsifitseeritud mu-le ja sigmale.

params <- list(
  "norm1" = list("mu" = 0, "sd" = 1),
  "norm2" = list("mu" = 1, "sd" = 1),
  "norm3" = list("mu" = 2, "scale" = 1)
)
params %>% map(~rnorm(5, mean = pluck(.x, 1), sd = pluck(.x, 2)))
#> $norm1
#> [1] -0.317 -0.809 -1.149  0.153 -1.379
#> 
#> $norm2
#> [1]  0.108  1.100  0.345  0.486 -1.047
#> 
#> $norm3
#> [1] 1.782 1.324 2.605 1.486 0.906

enframe() konverteerib nimedega vektori df-ks, millel on 2 veergu (name, value).

8.0.1 map2()

Itereerib üle kahe vektori - map2(.x, .y, .f).

.x ja .y on sama pikad vektorid (ühe-elemendine vektor kõlbab ka - seda lihtsalt retsükleeritakse nii mitu korda, kui teises vektoris on liikmeid).

.f on funktsioon või ekspressioon (valem)

Ekspressioon map2()-le algab ikka tildega; esimese vektori elemendid on .x teise vektori elemendid on .y

Näiteks map2(x, y, ~ .x + .y) liidab vektorid x ja y

8.0.2 pmap()

itereerib üle 3+ vektori. Näiteks pmap(list(x, y, z), sum) liidab 3 vektorit (x, y ja z on ise vektorid)

Järgnevas koodis anname ette listi long_numbers kolme vektoriga (pi, exp(1) ja sqrt(2)) ning vektori digits kolme liikmega, mida kasutab funktsiooni round() argument digits. Andes sellele argumendile 3 erinevat väärtust saame me kolm erinevat ümardamist kolmele listi long_numbers liikmele.

long_numbers <- list(pi, exp(1), sqrt(2))
digits <- list(2, 3, 4)
pmap(list(x = long_numbers, digits = digits), round)
#> [[1]]
#> [1] 3.14
#> 
#> [[2]]
#> [1] 2.72
#> 
#> [[3]]
#> [1] 1.41

pmap() ekspressioonide sisesed elemndid on ..1, ..2, ..3 jne, mitte .x ja .y nagu map2-l.

NB! pmap-i saab sisetada data frame, mille peal see töötab rea kaupa.

parameters <- data.frame(
  n = c(1, 2, 3),
  min = c(0, 5, 10),
  max = c(1, 6, 11)
)
parameters %>% pmap(runif)
#> [[1]]
#> [1] 0.595
#> 
#> [[2]]
#> [1] 5.29 5.78
#> 
#> [[3]]
#> [1] 10.2 10.1 10.3

See töötab sest runif() võtab 3 argumenti ja df-l parameters on 3 veergu.

Järgmine funktsioon rakendub suvalisele df-le rea kaupa ja arvutab igale reale näit sd. Aga selleks transponeerime read veergudeks ja rakendame tavalist map()-i.

rmap <- function (.x, .f, ...) {
    if(is.null(dim(.x))) stop("dim(X) must have a positive length")
    .x <- t(.x) %>% as.data.frame(.,stringsAsFactors=F)
    purrr::map_dfr(.x=.x,.f=.f,...)
}
parameters %>% rmap(sd)
#> # A tibble: 1 x 3
#>      V1    V2    V3
#>   <dbl> <dbl> <dbl>
#> 1 0.577  2.08  4.36

apply teeb sama lihtsamini.

apply(parameters, 1, sd)
#> [1] 0.577 2.082 4.359

8.0.3 invoke_map()

itereerib üle funktsioonide vektori, millele järgneb argumentide vektor. 1. funktsioon esimese argumendiga jne.

functions <- list(rnorm, rlnorm, rcauchy)
n <- list(c(5, 2, 3), 2, 3)
invoke_map(functions, n)
#> [[1]]
#> [1] 1.914 2.232 6.047 4.555 0.926
#> 
#> [[2]]
#> [1] 0.8859 0.0926
#> 
#> [[3]]
#> [1]  -1.04 -10.11  -3.21

anname sisse esimese argumendi (100) igasse funktsiooni

functions <- list(rnorm, rlnorm, rcauchy)
n <- c(5, 2, 3)
invoke_map(functions, n, 100)
#> [[1]]
#> [1]  97.4 100.0  99.4 100.3  99.7
#> 
#> [[2]]
#> [1] 1.56e+43 2.05e+44
#> 
#> [[3]]
#> [1] 100.3  99.5  99.1

mitu argumenti igale funktsioonile:

args <- list(norm = c(3, mean = 0, sd = 1), 
             lnorm = c(2, meanlog = 1, sdlog = 2),
             cauchy = c(1, location = 10, scale = 100))

invoke_map(functions, args)
#> [[1]]
#> [1] 0.123 1.480 0.394
#> 
#> [[2]]
#> [1] 2.037 0.152
#> 
#> [[3]]
#> [1] -49.4

8.0.3.1 map shortcuts

pluck() võtab listist välja elemendi (vektori, data frame jms) nii nagu see on (mitte listina).

list1 <- list(
  numbers = 1:3,
  letters = c("a", "b", "c"),
  logicals = c(TRUE, FALSE)
)

pluck(list1, 1) # list1 %>% pluck(1)
#> [1] 1 2 3
pluck(list1, "numbers") # list1 %>% pluck("numbers")
#> [1] 1 2 3

Andes map()-le ette character stringi (elemendi nime), saame tagasi elemendi igast alam-listist, mille nimi vastab sellele stringile. See on shotcut pluck-ile.

params <- list(
  "norm1" = list("mu" = 0, "sd" = 1),
  "norm2" = list("mu" = 1, "sd" = 1),
  "norm3" = list("mu" = 2, "scale" = 1)
)
map_dbl(params, "mu")
#> norm1 norm2 norm3 
#>     0     1     2

Sama teeb, kui map-le ette anda elemendi positsioon listis

map_dbl(params, 1)
#> norm1 norm2 norm3 
#>     0     1     2

Nii saab kätte samad tulbad (vektorid) mitmest data frame-st (kui list sisaldab data frame-sid).

veel mõned abifunktsioonid:

lmap() works exclusively with functions that take lists imap() applies a function to each element of a vector, and its index map_at() and map_if() only map a function to specific elements of a list.

8.0.3.2 List column

Df-i veerg, mille andmetüüp on list. Näiteks mudeliobjektid, funktsioonid ja teised df-d võivad minna list columnisse! List columnid on ise listid, mitte andmevektorid.

  List veerud võimaldavad panna samasse tabelisse erinevaid asju - 
  andmeid, mudeleid, mudeli koefitsiente jms. 

nest() teeb uue df-i, kus on 1. veerg grupeeriva muutuja tasemetega, millele järgneb list column, mille iga element on tibble. Iga tibble sisaldab relevantset infot grupeeriva muutuja vastava taseme kohta.

library(gapminder)
(nested_gapminder <- gapminder %>% group_by(country) %>% nest())
#> # A tibble: 142 x 2
#> # Groups:   country [142]
#>   country               data
#>   <fct>       <list<df[,5]>>
#> 1 Afghanistan       [12 × 5]
#> 2 Albania           [12 × 5]
#> 3 Algeria           [12 × 5]
#> 4 Angola            [12 × 5]
#> 5 Argentina         [12 × 5]
#> 6 Australia         [12 × 5]
#> # … with 136 more rows

unnest() teeb algse df-i tagasi.

iga nested_gapminder$data element on ise df:

nested_gapminder %>% 
  pluck("data") %>% 
  pluck(1) # %>% lm(lifeExp ~ year, data = .)
#> # A tibble: 12 x 5
#>   continent  year lifeExp      pop gdpPercap
#>   <fct>     <int>   <dbl>    <int>     <dbl>
#> 1 Asia       1952    28.8  8425333      779.
#> 2 Asia       1957    30.3  9240934      821.
#> 3 Asia       1962    32.0 10267083      853.
#> 4 Asia       1967    34.0 11537966      836.
#> 5 Asia       1972    36.1 13079460      740.
#> 6 Asia       1977    38.4 14880372      786.
#> # … with 6 more rows
#fitime ühe mudeli 1. elemendile (1. riik)

fit a model to each tibble nested within nested_gapminder and then store those models as a list column

fitime mudeli igale listi veerule (igale riigile). väljund on ilge list.

model1 <- nested_gapminder %>%  
  pluck("data") %>%
  map(~ lm(lifeExp ~ year, data = .x))

arvutame mudelid igale riigile ja pistame väljundi (mudeliobjekti) nested_gapminder uude list columnisse:

models1 <- nested_gapminder %>% 
  mutate(models = map(data, ~ lm(lifeExp ~ year, data = .x)))

võtame välja mudeli koefitsiendi year ja paneme uude veergu nimega coefficient:

models1 <-  models1 %>% mutate( coefficient = map_dbl(models, ~coef(.x) %>% pluck("year")) )

df-i veerg models on ühtlasi list, millele saame map_dbl() rakendada.

järgnevad 3 koodirida teevad sama asja - võtavad välja 1. mudeli:

models1 %>% 
  pluck("models") %>% 
  pluck(1)

models1[[1, 3]]

models1$models[[1]]