purrr中有多个迭代函数,可以用于快速解决循环迭代的问题,purrr中常用的迭代函数有map、map2、walk、reduce等等。
map
代码语言:javascript复制map(.x, .f, ...)
map函数接受一个向量、列表,对其每一个元素执行函数。
数据框其实是一种格式化表示的列表,所以也可以使用map迭代。
map函数默认返回列表。
代码语言:javascript复制iris %>% map(mean)
# $Sepal.Length
# [1] 5.843333
#
# $Sepal.Width
# [1] 3.057333
#
# $Petal.Length
# [1] 3.758
#
# $Petal.Width
# [1] 1.199333
#
# $Species
# [1] NA
如果返回值需要是一个数值向量,而不是列表,也可以使用map_dbl、map_chr、map_lgl等形式,代表分别返回数值、字符或者逻辑值。
代码语言:javascript复制iris %>% map_dbl(mean)
#Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 5.843333 3.057333 3.758000 1.199333 NA
可以看到Species不是数值是无法求mean,那么此时可以使用map_if或者map_at跳过Species。
注意:此时的跳过是指的不对跳过的列执行函数mean,直接将其内容返回。
代码语言:javascript复制iris %>% map_at(1:4, mean) # 只对前四列计算mean
对于map_if而言可以使用.else参数控制跳过的列的执行函数。
代码语言:javascript复制iris %>% map_if(is.numeric, mean, .else = ~"不适用")
#$Sepal.Length
#[1] 5.843333
#$Sepal.Width
#[1] 3.057333
#$Petal.Length
#[1] 3.758
#$Petal.Width
#[1] 1.199333
#$Species
#[1] "不适用"
如果还有其他参数,放到函数后边即可:
代码语言:javascript复制iris %>% map_dbl(mean, na.omit=T)
.f有三个快捷方式:公式、字符及数字。
代码语言:javascript复制# 公式:用于简化R的匿名函数格式
# 例如如下两种方式是等价的
iris %>% map(function(x) mean(x, na.omit=T))
iris %>% map(~mean(., na.omit=T))
# 字符:用于快速提取内容
# 例如如下两种方式是等价的
iris %>% dplyr::select(-Species) %>% map(summary) %>%map_dbl(~.["Median"])
iris %>% dplyr::select(-Species) %>% map(summary) %>%map_dbl("Median")
# 数字:也是用于快速提取内容,按位置取值
iris %>% dplyr::select(-Species) %>% map(summary) %>% map_dbl(3)
map2
代码语言:javascript复制map2(.x, .y, .f, ...)
map2可以对两个向量、列表同时进行迭代。
代码语言:javascript复制x <- 1:3
y <- list(4,5,6)
map2(x,y,rnorm,n=5)
# [[1]]
# [1] -1.583531618 -4.480785670 0.008262269 0.452103825 4.257641158
#
# [[2]]
# [1] 2.835951 -2.392248 2.874423 -2.714616 9.362894
#
# [[3]]
# [1] -6.5547160 -4.3619104 -5.5202971 -0.1914993 2.2787720
可以同时对列表的名称和内容进行迭代,先将列表使用enframe转换为一个tibble:
代码语言:javascript复制x <- 1:3
y <- list(4,5,6)
paras <- list(mean=x, sd=y)
paras <- tibble::enframe(paras)
## A tibble: 2 x 2
# name value
# <chr> <list>
#1 mean <int [3]>
#2 sd <list [3]>
map2(paras$name, paras$value, ~stringr::str_c(.x, .y[[1]], collapse=""))
#[[1]]
#[1] "mean1"
#
#[[2]]
#[1] "sd4"
map2在使用管道时,可以使用%$%:
代码语言:javascript复制library(magrittr)
list(mean=1:3, sd=4:6) %>%
tibble::enframe() %$%
map2(name, value, ~stringr::str_c(.x, .y[[1]], collapse=""))
#[[1]]
#[1] "mean1"
#
#[[2]]
#[1] "sd4"
pmap
代码语言:javascript复制pmap(.l, .f, ...)
pmap代表对多个列表进行迭代,将多个列表包装为一个列表传给.f可。
代码语言:javascript复制x <- 1:3
y <- list(4,5,6)
z <- list(1,2,3)
pmap(list(mean=x, sd=y, n=z), rnorm)
#相当于rnorm(mean=1, sd=4, n=1)、rnorm(mean=2, sd=5, n=2)、rnorm(mean=3, sd=6, n=3)
#[[1]]
#[1] 6.224277
#
#[[2]]
#[1] 12.32054 10.57245
#
#[[3]]
#[1] 6.593741 1.389408 3.541097
invoke_map
代码语言:javascript复制invoke_map(.f, .x = NULL, ..., .env = NULL)
invoke_map用于函数(.f参数)也不固定的情况。
代码语言:javascript复制x <- list(n = 3)
y <- list(n = 5, lambda = 10)
z <- list(n = 3, mean = -3, sd = 10)
fun <- c("runif", "rpois", "rnorm")
invoke_map(fun, list(x,y,z))
#相当于runif(n=3)、rpois(n=5, lambda=10)、rnorm(n=3, mean=-3, sd=10)
#[[1]]
#[1] 0.09462862 0.54110640 0.87654702
#
#[[2]]
#[1] 10 6 5 8 9
#
#[[3]]
#[1] -20.92633 -14.80254 -16.16675
walk
除了上述的map系列函数,还有一批形式类似的walk函数,如walk、walk2、pwalk等等,他们用于一些不需要返回值的操作,如绘图和读写文件。
如果此时使用map系列函数,那么就会返回一个值为NULL的列表。
代码语言:javascript复制df <- data.frame(plot1 = rnorm(5,1,1),
plot2 = rnorm(5,2,3),
plot3 = rnorm(5,4,5))
map(df, plot) #除了绘制三张散点图外,还会返回一个值为NULL的列表
#$plot1
#NULL
#$plot2
#NULL
#$plot3
#NULL
walk(df, plot) #只绘图,没有返回值
reduce
reduce是一个特殊的迭代函数,它执行一种“积累”的操作,如累加、累乘:
代码语言:javascript复制reduce(1:100, ` `)
#[1] 5050
reduce(1:5, `*`)
#[1] 120
有些时候,reduce是很方便的,比如可以使用它计算数据框的最大值和最小值:
代码语言:javascript复制reduce(iris, min) #0.1
reduce(iris, max) #7.9
和reduce类似的一个函数是accumulate,它会将中间每一个迭代过程记录下来:
代码语言:javascript复制head(iris) %>% accumulate(min)
#$Sepal.Length
#[1] 5.1 4.9 4.7 4.6 5.0 5.4
#$Sepal.Width
#[1] 3
#$Petal.Length
#[1] 1.3
#$Petal.Width
#[1] 0.2
#$Species
#[1] 0.2