分享

R语言进阶笔记5 | purrr替代循环

 育种数据分析 2021-11-18

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是函数,meanmap的参数,测试数据:

> 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

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多