purrr替代循环
1 purrr循环
引用知乎张敬信的说法:
❝用 R 写 「循环」 从低到高有三种境界:手动 for 循环,apply 函数族,purrr 包泛函式编程。
❞
R写循环有三个境界:
-
手动for循环 -
apply循环 -
purrr泛函式编程
其中,手动for循环我最常用,apply系列半吊子,purrr函数一窍不通,所以要学习一下。
2 泛函式函数
泛函式定义
函数的函数成为泛函式,map(x,f)中,map是函数,f也是函数,f是map的参数,那么map就是泛函数。
dat = data.frame(y1 = rnorm(10),y2 = rnorm(10)+10)
dat
map(dat,mean)
这里,map是函数,mean是map的参数,测试数据:
> dat = data.frame(y1 = rnorm(10),y2 = rnorm(10)+10)
> dat
y1 y2
1 0.5817177 10.355036
2 1.4852696 10.058380
3 1.0901313 11.682624
4 0.2128081 11.119148
5 0.8566806 10.547209
6 0.8843383 9.414661
7 0.4761166 11.070056
8 0.3465340 9.301207
9 1.1130833 10.087095
10 0.6286422 9.983994
> map(dat,mean)
$y1
[1] 0.7675322
$y2
[1] 10.36194
如果使用apply系列的lapply函数,是这样处理的:
> lapply(dat,mean)
$y1
[1] 0.7675322
$y2
[1] 10.36194
两者结果完全一致,
所以,这里map和apply都是泛函式函数。
3 map的不同类型
-
map函数,支持一元函数 -
map2函数,支持二元函数 -
pmap函数,支持多元函数
这里先模拟数据:
> dat = data.frame(x1 =rnorm(10),x2 = rnorm(10),x3 = rnorm(10),x4 = rnorm(10))
> dat
x1 x2 x3 x4
1 1.203531098 -0.33361497 -0.05708186 0.9924523
2 -0.340909576 0.22388983 -1.42836870 -0.2116809
3 -1.710778506 0.31278696 0.17518334 0.8943420
4 -0.001525112 0.22099926 -0.62475728 -0.7134268
5 0.229862264 -1.76353518 -1.04137751 0.5086442
6 -0.382215060 0.06071203 -0.86489543 -0.7195158
7 1.175170874 -0.78679427 -0.23324264 -0.4412758
8 0.151216441 -1.09148795 0.54867008 0.1762894
9 -0.017814828 0.51608686 0.04602458 -0.5771657
10 -1.406719653 0.30200433 -0.18020801 0.3050708
4 map应用
这里,map函数,支持一元函数
map(dat,max)
4.1 「map2应用」
这里map2可以支持二元函数,比如:
map2(dat$x1,dat$x2,~max(.x,.y))
上面的.x和.y表示dat$x1, dat$x2两个元素,~max表示匿名函数。上面需要用map2或者pmax,如果用map就失败:
> map(dat$x1,dat$x2,~max(.x,.y))
[[1]]
NULL
[[2]]
NULL
4.2 「pmap函数」
支持两个,或者两个以上的多元函数,默认是对行进行操作:
> pmap(dat,max)
[[1]]
[1] 1.203531
……
上面的也可以写为:
pmap(dat,~max(..1,..2,..3,..4))
4.3 map不同的后缀
-
*_chr,比如map_chr, map2_chr, pmap_chr等,返回字符
-
*_lgl,返回逻辑型向量
-
*_dbl,返回实数型向量
-
*_int,返回数字型向量
-
*_df,返回数据库
-
*_dfr, 返回数据库行合并
-
*_dfc, 返回数据框列合并
5 匿名函数写法
-
一元的map,可以写为 .x,或者..1 -
二元的map2,可以写为.x,.y,或者..1,..2 -
三元的pmap,可以写为..1,..2,..3,..4
5.1 一元的map
下面三种写法是等价的。
-
第一种,是直接调用max函数,不是匿名函数,不需要~符号,默认是对列处理,如果对行处理,可以用 pmap -
第二种,是调用匿名函数,前面需要用~,参数用 .x -
第三种,是调用匿名函数,前面需要用~,参数用 ..1
map(dat,max)
map(dat,~max(.x))
map(dat,~max(..1))
5.2 二元的map2
和上面一元map用法一样,下面三种也是等价的:
map2(dat$x1,dat$x2, max)
map2(dat$x1,dat$x2, ~max(.x,.y))
map2(dat$x1,dat$x2, ~max(..1,..2))
5.3 多元的pmap
因为多元的,不会有..x, ..y, ..z表示,只会有..1, ..2, ..3表示,所以下面两种也是等价的:
pmap(dat,max)
pmap(dat,~max(..1,..2,..3,..4))
6 函数的参数两种写法
6.1 直接在函数的函数内部
比如,要计算每一列的平均值,允许缺失值,需要用到参数na.rm = T,可以这样写:
> map(dat,~mean(.x,na.rm = T))
$x1
[1] -0.1100182
$x2
[1] -0.2338953
$x3
[1] -0.3660053
$x4
[1] 0.02137338
这里,用到了匿名函数,可以把匿名函数的参数,写在匿名函数里面。
6.2 直接在函数内部写
这里,因为map函数的用法是:map(.x, .f, ...),其中
-
.x 为对象 -
.f 为函数 -
...为.f函数的其它参数
所以,计算每一列的平均值,也可以写为:
> map(dat,mean,na.rm=T)
$x1
[1] -0.1100182
$x2
[1] -0.2338953
$x3
[1] -0.3660053
$x4
[1] 0.02137338
7 map的用法1:批量建模
这里使用我的R包learnasreml中的MET数据,进行测试。
MET数据是作物两年多点的产量试验数据,这里我们分别对每一个地点的品种进行方差分析。
library(learnasreml)
data(MET)
head(MET)
summary(MET)
> library(learnasreml)
> data(MET)
> head(MET)
Year Location Rep Cul Yield
1 2009 KN 1 EarlyCanada 56.236
2 2009 KN 1 CalhounGray 74.167
3 2009 KN 1 StarbriteF1 32.601
4 2009 KN 1 CrimsonSweet 74.167
5 2009 KN 1 GeorgiaRattlesnake 64.794
6 2009 KN 1 FiestaF1 70.907
> summary(MET)
Year Location Rep Cul Yield
2009:200 CI:80 1:100 CalhounGray : 40 Min. : 4.034
2010:200 FL:80 2:100 CrimsonSweet : 40 1st Qu.: 45.166
KN:80 3:100 EarlyCanada : 40 Median : 67.647
SC:80 4:100 FiestaF1 : 40 Mean : 67.473
TX:80 GeorgiaRattlesnake: 40 3rd Qu.: 88.616
Legacy : 40 Max. :180.906
(Other) :160 NA's :3
数据包括:
-
两年,2009,2012 -
五个地点 -
两个重复 -
160个品种 -
产量数据
这里,我们对每一个地点的品种,进行方差分析,常规的做法是:
-
提取每一个地点的数据 -
对每一个地点的数据进行方差分析
loc1 = MET[MET$Location == "CI",]
loc2 = MET[MET$Location == "FL",]
loc3 = MET[MET$Location == "KN",]
loc4 = MET[MET$Location == "SC",]
loc5 = MET[MET$Location == "TX",]
提取每一个地点的数据,单独保存。然后对每一个地点进行方差分析:
summary(aov(Yield ~ Cul, data=loc1))
summary(aov(Yield ~ Cul, data=loc2))
summary(aov(Yield ~ Cul, data=loc3))
summary(aov(Yield ~ Cul, data=loc4))
summary(aov(Yield ~ Cul, data=loc5))
如果使用map函数进行批量建模:
MET %>% split(.$Location) %>% map(.,~aov(Yield ~ Cul,.) %>% summary)
结果如下:
> MET %>% split(.$Location) %>% map(.,~aov(Yield ~ Cul,.) %>% summary)
$CI
Df Sum Sq Mean Sq F value Pr(>F)
Cul 9 8696 966.2 3.193 0.00277 **
Residuals 69 20879 302.6
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
1 observation deleted due to missingness
$FL
Df Sum Sq Mean Sq F value Pr(>F)
Cul 9 10151 1127.9 1.565 0.143
Residuals 69 49723 720.6
1 observation deleted due to missingness
$KN
Df Sum Sq Mean Sq F value Pr(>F)
Cul 9 8236 915.1 4.038 0.000338 ***
Residuals 70 15863 226.6
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
$SC
Df Sum Sq Mean Sq F value Pr(>F)
Cul 9 24478 2719.8 5.6 8.42e-06 ***
Residuals 70 33996 485.7
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
$TX
Df Sum Sq Mean Sq F value Pr(>F)
Cul 9 5784 642.6 1.326 0.24
Residuals 69 33429 484.5
1 observation deleted due to missingness
8 walk与map对比
walk和map函数组合上类似,不同的是walk不返回结果,比如你要保存数据时,就可以用walk函数系列。
-
walk,类似map函数 -
walk2,类似map2函数 -
pwalk,类似pmap函数
上面的MET数据,我们可以将数据按照品种分组,批量的保存名为地点的数据csv中。
data(MET)
head(MET)
MET %>% group_nest(Location) %>% pwalk(~ write.csv(.y,paste0(.x,".csv")))
结果,每个地点都有一个csv文件:
9 map函数用法2:批量读取数据
上面的csv文件,批量读取,然后合并再一起
re = map_dfr(file,read.csv)
10 参考:
https://zhuanlan.zhihu.com/p/168772624
https://mp.weixin.qq.com/s/H6nwbkePeeMhDCEwlC1PUA

阅读原文,阅读体验更佳!

