美文网首页R for data scienceR数据处理R语言训练
R语言里面的分组操作,不止group_by

R语言里面的分组操作,不止group_by

作者: 9d760c7ce737 | 来源:发表于2019-05-07 17:22 被阅读15次

    今天的帖子是对昨天的补充,也算是对于分组批量操作的总结。


    昨天群里有人提问:

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

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

    ## 我喜欢用data.table中的fread读取数据,参数data.table=F,限定读入数据后是data.frame。
    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))
    

    方法三

    洲更给我提供了一个方法,是aggregate,因此我跟洲更商量,就此打住,把时间留给更有意义的事情。

    results3 <- aggregate(data$Temperature,list(data$Year,data$Month),mean,na.rm=T)
    

    方法四

    Y叔说,用tapply也是可以的,不过他返回的是个table,需要转换两次才能得到正确结果

    results4 <- as.data.frame(as.table(with(data,tapply(Temperature,list(Year,Month),mean,na.rm=T))))
    

    方法五

    已经有了四个一行代码搞定的方法了,不妨再添加一个,这个方法基于data.table语法,所以,先要把data.frame转换成data.table

    data1 <- data[,c("Temperature","Year","Month")]
    results5 <-  data.table::setDT(data1)[ ,lapply(.SD, mean,na.rm=T) , by=c("Year","Month")]
    

    方法六

    在大家谈到,有了tidyverse啥都不想要了之后,Y叔叔悠悠地说了一句,那之前的人们是怎么生活的呢?
    我想,应该是基于split+批量操作

    所以,以下的这些方法都是基于split
    一开始,我以为spli只能裂解一个变量,所以就需要在批量操作时分别再次split然后再操作,说实话,写的时候,就像合上电脑走人。

    先尝试了,map函数配合bind_rows函数,因为我受Hadley Wickham影响很大,喜欢用管道符号,

    library(purrr)
    library(dplyr)
    results6 <- 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(results6) <- c("Year","Month","Mean")
    

    方法七

    现在请无视这些标题,只作为序号使用。
    而map干的事情和lapply是一样的,lapply我更加熟悉一点,在当前情况下更简单。

    library(dplyr)
    results <- data %>% 
      select(Year,Month,Temperature) %>% 
      split(.$Year) %>% 
      lapply(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)}))
    })
    results7 <- data.frame(do.call(rbind,results))
    colnames(results7) <- c("Year","Month","Mean")
    

    方法八

    早上查google的时候,我发现,split可以同时裂解多个变量,那么,这个事情就大大简化了

    library(dplyr)
    library(purrr)
    results8 <- data %>% 
      select(Year,Month,Temperature) %>% 
      split(paste(.$Year,.$Month)) %>% 
      map(function(x){
        mean=mean(x$Temperature,na.rm = T)
        data.frame(x$Year[1],x$Month[1],mean)
        }) %>% 
      bind_rows()
    

    而且split的裂解语法至少有三种形式,上一个是paste(.$Year,.$Month),下面这个是list(.$Year,.$Month)

    library(dplyr)
    library(purrr)
    results8<- data %>% 
      select(Year,Month,Temperature) %>% 
      split(list(.$Year,.$Month)) %>% 
      map(function(x){
        mean=mean(x$Temperature,na.rm = T)
        data.frame(x$Year[1],x$Month[1],mean)
      }) %>% 
      bind_rows()
    

    还可以写成这个样子的:

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

    方法9

    在split裂解多个变量的情况下,再使用lapply,简单,效果也很好

    library(dplyr)
    results9 <- data %>% 
      select(Year,Month,Temperature) %>% 
      split(paste(.$Year,.$Month)) %>% 
      lapply(function(x){
        mean=mean(x$Temperature,na.rm = T)
        data.frame(x$Year[1],x$Month[1],mean)
      }) 
    results9 <- do.call(rbind,results9)
    

    方法10

    上一步中裂解后得到的是一个个的小数据框,对这些小数据框可以使用apply,终于用到这玩意了。

    library(dplyr)
    results10 <- data %>% 
      select(Year,Month,Temperature) %>% 
      split(.[,c("Year","Month")]) %>% 
      map(function(x){
         data.frame(t(apply(x,2,function(x){mean(x,na.rm = T)})))
      }) %>% 
      bind_rows()
    

    如果是lapply加上apply,代码更简单一点

    library(dplyr)
    results <- data %>% 
      select(Year,Month,Temperature) %>% 
      split(.[,c("Year","Month")]) %>% 
      lapply(function(x){
        apply(x,2,function(x){mean(x,na.rm = T)})
      })
    results10 <- data.frame(do.call(rbind,results))
    

    方法11

    如果追求只用apply实现,不方便,因为apply接受的是矩阵,或者数据框,我们可以用for循环实现对于split结果的获取。

    library(dplyr)
    results <- data %>% 
      select(Year,Month,Temperature) %>% 
      split(.[,c("Year","Month")])
    results11 <- data.frame()
    for (i in 1:192) {
      results11[i,c(1:3)] = apply(results[[i]],2,function(x){mean(x,na.rm = T)})
    }
    

    方法12

    理论上,只要能用for循环完成的事情,apply都可以做,所以,我们人为地设置一个序号组成的数据框让apply来批量,然后对于每一个元素,再使用apply批量求平均数。这样,整个代码中就只有apply了。

    library(dplyr)
    results <- data %>% 
      select(Year,Month,Temperature) %>% 
      split(.[,c("Year","Month")])
    results12 <- data.frame(t(apply(data.frame(seq(1,192)),1,function(x){
      apply(results[[x]],2,function(x){mean(x,na.rm = T)})
    })))
    

    方法13

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

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

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

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

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

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

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

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


    reshape2中的melt和dcast函数联合使用也可以达到效果,但是我就是不用!那是一段痛苦的回忆,我在R语言学习路上的一个大拦路虎就是melt和dcast,不理解,也不愿理解,拦了我很久,之后碰到spread和gather才一路通畅。
    所以,我对tidyverse这个包,饱含深情,感激涕零。甚至,我看到国外有些大神直接建议,初学者学习R语言,从tidyverse开始,直接跳过基础部分。在这方面,小洁最有发言权,她能熟练使用tidyverse的时候,根本不知道还有R语言基础这件事,属于练了神功再下凡的品类。

    总结一下:

    • 1.group_by联合summarize是批量分组计算的首选
    • 2.还有ddply,aggregate,tapply可以选择。
    • 3.基于split裂解+批量也能解决问题
    • 4.只要是需要批量做的事情,都可以用for循环来做
    • 5.写好for循环的秘诀只有一个:清晰地定义做一件事情的每一个步骤。

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

    相关文章

      网友评论

        本文标题:R语言里面的分组操作,不止group_by

        本文链接:https://www.haomeiwen.com/subject/taneoqtx.html