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]]