R系列之map函数家族

这篇文章主要学习了map函数家族的各种函数用法。map家族的函数主要是用来将函数作用于传入数据(可以是向量,也可以是数据框)的每个元素的,这和主要针对数据框和列表的apply函数家族形成互补,主要实现的功能相近。同时也学习了map函数家族的变体,增强了其适用性。

背景

在前面的文章R系列之apply函数家族中已经学习了在代码中减少使用循环的方法,不过apply函数家族的函数大部分都是针对dataframe、array以及list设计的,而实际使用过程中经常会遇到需要将函数应用到vector中每个元素的情形,这种情况下就不能使用apply函数家族的函数或者使用比较麻烦。针对上述情况,我们可以使用map函数家族的函数来解决这个问题


map函数家族简介

map函数家族主要包括7个函数,都在purr包中(purr包又在tidyverse包中,使用的时候直接library(tidyverse)),按照返回值数据结构的差异可以分为:

  • map(.x, .f, ...):返回值为列表
  • map_lgl()map_int()map_dbl()map_chr():返回特定数据类型的向量,在使用map_int()时需要注意数据类型的自动提升问题
  • map_dfc()map_dfr():对数据进行col_bindingrow_binding得到数据框

map函数

将函数作用于每个元素,并且返回一个列表(函数在每个元素上的执行结果):

1
2
3
4
5
6
7
8
9
10
# 对9、16、25执行开方操作,返回每个结果的列表
map(c(9, 16, 25), sqrt)
[[1]]
[1] 3

[[2]]
[1] 4

[[3]]
[1] 5


类型特异性的map函数

针对不同返回值的类型,map函数可以分为map_lgl()map_int()map_dbl()map_chr(),分别作用与逻辑值整型数值浮点型字符串类型,这些函数的返回值都是特定类型的向量

map_chr-返回字符型向量

1
2
3
4
5
6
7
8
# 查看生成的数据类型
## 直接生成的序列是整型的
map_chr(1:10, typeof)
[1] "integer" "integer" "integer" "integer" "integer" "integer" "integer" "integer" "integer" "integer"

# 给定的序列是double型的
map_chr(c(1,10), typeof)
[1] "double" "double"

map_dbl-返回浮点型向量

1
2
map_dbl(c(1,2,4),sqrt)
[1] 1.000000 1.414214 2.000000

map_int-返回整型向量

这里使用的时候需要注意,purr包对 type checking是非常严格的,在输出的时候会自动进行类型的提升(如果一个int乘上double,结果就是double型的数据),不同数据类型的升级顺序为:logical -> integer -> double -> character,注意这里只能从低到高,不能从高到低

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 这里生成的1-10的序列是整型的序列
map_int(1:10, function(x) x)
[1] 1 2 3 4 5 6 7 8 9 10
# 这里2*x中的2是浮点型的数据
# int*double的结果是double,所以这里报错
map_int(1:10, function(x) 2*x)
Error: Can't coerce element 1 from a double to a integer

# 声明这里的2是整型的数据,在后面加上L即可
map_int(1:10, function(x) 2L*x)
[1] 2 4 6 8 10 12 14 16 18 20

# 直接使用map_dbl即可
map_dbl(1:10, function(x) 2*x)
[1] 2 4 6 8 10 12 14 16 18 20


map_lgl-返回逻辑向量

1
2
map_lgl(c(1,10), is.double)
[1] TRUE TRUE

输出数据框的map函数

数据准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 注意这里的~相当于function(x)
# ~ lm(mpg ~ wt, data = .x) 相当于 function(x) lm(mpg ~ wt, data = .x)
mtcars %>%
split(.$cyl) %>%
map(~ lm(mpg ~ wt, data = .x)) %>%
map(~ as.data.frame(t(as.matrix(coef(.)))))

# 输出结果
$`4`
(Intercept) wt
1 39.5712 -5.647025

$`6`
(Intercept) wt
1 28.40884 -2.780106

$`8`
(Intercept) wt
1 23.86803 -2.192438

map_dfr-对输出进行row-binding得到dataframe

1
2
3
4
5
6
7
8
9
10
mtcars %>%
split(.$cyl) %>%
map(~ lm(mpg ~ wt, data = .x)) %>%
map_dfr(~ as.data.frame(t(as.matrix(coef(.)))))

# 返回值
(Intercept) wt
1 39.57120 -5.647025
2 28.40884 -2.780106
3 23.86803 -2.192438

map_dfc-对输出进行col-binding得到dataframe

1
2
3
4
5
6
7
8
9
mtcars %>%
split(.$cyl) %>%
map(~ lm(mpg ~ wt, data = .x)) %>%
map_dfc(~ as.data.frame(t(as.matrix(coef(.)))))

# 输出结果
## 重复的col name会加后缀区分
(Intercept) wt (Intercept)1 wt1 (Intercept)2 wt2
1 39.5712 -5.647025 28.40884 -2.780106 23.86803 -2.192438

map函数的变体

变体简介

ListAtomicSame typeNothing
One argumentmap()map_lgl(), …modify()walk()
Two argumentsmap2()map2_lgl(), …modify2()walk2()
One argument + indeximap()imap_lgl(), …imodify()iwalk()
N argumentspmap()pmap_lgl(), …pwalk()

modify-和输入数据格式相同

如果想在使用map函数功能的同时不改变数据的格式(输出和输入数据格式相同)可以使用函数modify()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
df <- data.frame(
x = 1:3,
y = 6:4
)
# 使用map函数会输出列表形式
map(df, ~ .x * 2)
$x
[1] 2 4 6

$y
[1] 12 10 8

# 使用modify函数
# 输出和输入的数据格式相同
modify(df, ~ .x * 2)
x y
1 2 12
2 4 10
3 6 8


map2-多参函数

前面map家族函数中使用的.f函数都是针对单个参数的向量化(每次向.f函数中传递单个参数),如果需要每次向.f函数中传递多个参数则需要使用map2函数家族,下面演示计算加权平均数的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 数值
xs <- map(1:8, ~ runif(10))
# 修改其中一个数值为NA
xs[[1]][[1]] <- NA
# 权重信息
ws <- map(1:8, ~ rpois(10, 5) + 1)

# 计算简单的均值
map_dbl(xs, mean)
# 因为存在NA,所以有一行数据的结果为NA
[1] NA 0.4835817 0.5543698 0.3183199 0.5054429 0.4311387 0.4268043 0.2760347

# 给mean传递参数来去除na
# 注意这里的参数不是向量化的参数,是固定的参数,所以可以放在最后添加,这点和apply函数相同
map_dbl(xs, mean,na.rm=T)
[1] 0.5894843 0.4835817 0.5543698 0.3183199 0.5054429 0.4311387 0.4268043 0.2760347


# 计算加权平均数
# 将数值和权重这些需要向量化的参数放在函数之前,固定的参数放在函数之后
map2_dbl(xs, ws, weighted.mean,na.rm=T)
[1] 0.5369628 0.4876169 0.5852098 0.3186872 0.5118735 0.3620245 0.4655258 0.2537778

三个及以上参数的向量化可以使用pmap函数家族,多个参数使用list()进行包裹传递给pmap函数家族的函数即可:

1
2
3
# 2个参数的向量化也可以使用这个函数
pmap_dbl(list(xs, ws), weighted.mean,na.rm=T)
[1] 0.5369628 0.4876169 0.5852098 0.3186872 0.5118735 0.3620245 0.4655258 0.2537778


参考链接



-----本文结束感谢您的阅读-----

本文标题:R系列之map函数家族

文章作者:showteeth

发布时间:2020年03月27日 - 09:37

最后更新:2020年04月04日 - 16:23

原始链接:http://showteeth.tech/posts/57211.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%