查看原文
其他

每一个R语言初学者都应该掌握for循环!

果子 果子学生信 2023-06-15

今天群里有人提问:

如何对数据框进行多变量分组,分别求平均数?

给出的原始数据是这个样子的

data <- data.table::fread("Temperature.txt",data.table = F)

年代是从1990到2005


月份是从1月到12月

要求按照Year和Month对行进行分组,求得每组的平均Temperature。

这个事情,第一反应就是dplyr包中的group_by联合summarize

library(dplyr)
results1 <- data %>% 
  group_by(Year,Month) %>% 
  summarise(Mean = mean(Temperature,na.rm = T))

十分方便,最终的结果是这个样子的

因为群里有人说,常用的实现这个目的的方法至少10种,而我脑子里面只有两种,所以内心十分恐慌。并且,他们还要求使用apply来完成,我彻底沦陷了。

想来想去,Hadley Wickham大神的这个包是有毒的,一用上之后便不思进取,因为实在太好用了。在神包tidyverse之前,作者还有一种方法可以实现这个操作,就是plyr包的split-apply-combine思想

方法二

用起来也是十分简单,而且,思路和函数组成都很类似,因为是一个作者写的。

library(plyr)
results2 <- ddply(data,.(Year,Month),
                  summarise,
                  Mean = mean(Temperature,na.rm = T))

方法三

做这个事情的本质是批量,那么map函数配合bind_rows函数肯定是可以的,但是过程很波折

library(purrr)
library(dplyr)
results3 <- data %>% 
  select(Year,Month,Temperature) %>% 
  split(.$Year) %>% 
  map(function(x){
  data.frame(t(bind_cols(map(split(x,x$Month),function(y){
    mean =mean(y$Temperature,na.rm = T)
    c(y$Year[1],y$Month[1],mean)
  }))))
  }) %>% 
  bind_rows()
colnames(results3) <- c("Year","Month","Mean")

方法四

相比较而言,lapply函数配合do.call函数就简单一点

library(dplyr)
## 先切割
results4 <- data %>% 
  select(Year,Month,Temperature) %>% 
  split(.$Year)
##lapply批量操作,操作中内嵌一个lapply
results4 <- lapply(results4, function(x){
  do.call(rbind,lapply(split(x,x$Month),function(y){
    mean =mean(y$Temperature,na.rm = T)
    c(y$Year[1],y$Month[1],mean)}))
})
## 结果变成数据框
results4 <- data.frame(do.call(rbind,results4))
## 命名
colnames(results4) <- c("Year","Month","Mean")

方法五

在很长一段时间内,只要是批量的事情,我都是用for循环来搞定的。它解决了我大部分问题,所以,我上课的时候强调,每一个人都要学会写for循环。
一般当你小有所成,就会出现鄙视链,开始瞧不起那些用for循环的初学者,并嘲笑别人说,for循环速度太慢了!但是,对于初学者而言,解决问题才是最重要的,速度可以放在一边,况且,速度并没有那么重要。
可以看看以前写过的这个帖子,for循环50s,并行化8s。我觉得没什么差别。
8秒完成2万个基因的生存分析,人人都可以!
至于用lapply还有map这些批量函数,我纯粹是为了要面子。还有就是用lapply是一种自然而然水到渠成的行为,当你能够随意写出for循环后,你就可以把它改写成函数,一旦写成函数,apply家族的成员就可以批量对其操作。

写好for循环的关键只有一个:

清晰地定义做一件事情的每一个步骤。

具体到当前的情况,我的想法是这样的

1. 先建立一个空的数据框,他有三列,我准备逐行填满
2. 第1行的第1列,我填入的是一个年份,比如1999
3. 确定年份后,在1999年中,我再开始选择第1个月,把这个数值填入第1行第2列
4. 当确定了年份,月份,那么就会产生一个小数据框,我就可以对温度这一列求平均值,填入第1行的第3列。
5. 第一行完成后,下面的行依葫芦画瓢。

基于这个思想,我写出了以下的循环:

results5 <- data.frame()
i=1
for (Year in unique(data$Year)) {
  for (Month in unique(data$Month)) {
# 第一列
    results5[i,1]= Year
# 第二列
    results5[i,2]= Month
# 第三列
    results5[i,3]= mean(data[(data$Year==Year & data$Month==Month),"Temperature"],na.rm=T)
    i=i+1
  }
}
colnames(results5) <- c("Year","Month","Mean")

最终这5个方法的结果一样

但是,我依然不知道其他5种方法是什么。

总结一下:

  • 1.group_by联合summarize是批量分组计算的首选

  • 2.只要是需要批量做的事情,都可以用for循环来完成

  • 3.写好for循环的秘诀只有一个:清晰地定义做一件事情的每一个步骤。

如果你需要练习的那个小文件,回复"我爱循环"自助获取。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存