在R语言中,有时为提高运行效率,以向量化操作替代循环操作,原因在于R中的向量化底层为C语言,C语言的执行效率要比R高的多。而提到向量化操作,则离不开apply函数族。今天,来分享常见的apply函数族用法。


文章目录

  • 一、常见apply函数族介绍
  • 二、实战应用
  • 2.0 问题描述
  • 2.1 sapply解法
  • 2.2 tapply解法
  • 三、总结


一、常见apply函数族介绍

函数名

输入

输出

基本语法

apply

一般为矩阵或数据框

向量或矩阵

apply(X,MARGIN,FUN),X为输入数据,FUN为执行函数,MARGIN为作用维度(1为行,2为列)

lapply

矩阵、数据框等

列表

lapply(X,FUN),X为输入数据,FUN为下执行函数

sapply

向量、矩阵、数据框、列表等

向量、矩阵、数组

sapply(X,FUN),X为输入数据,FUN为下执行函数

tapply

多用于数据框

数组、列表等

tapply(X,INDEX,FUN),X为输入数据,INDEX为按某个维度分组,FUN为执行函数

mapply

向量、矩阵、数据框、列表等

向量、矩阵、数组

mapply(FUN,X),FUN可传入多个参数,sapply升级版

vapply

向量、矩阵、数据框、列表等

向量、矩阵、数组

vapply(X,FUN,FUN.VALUE),X为输入数据,FUN为执行函数,FUN.VALUE控制返回类型

这里以笔者最常用的apply、sapply、tapply为例,简单描述下常见的使用场景。

  • 行列标准化

需求:对鸢尾花数据集中的数值变量进行行列标准化

row_normalize<-data.frame(t(apply(iris[,-ncol(iris)],1,scale)))
col_normalize<-data.frame(apply(iris[,-ncol(iris)],2,scale))

R语言中数据的分类 r语言分类函数_数据

  • 对数据框某列清洗

需求:现需提取raw_data中的loc列城市名(安徽、重庆、湖北),并对score列提取分数

#构造数据
raw_data<-data.frame(id=c(1,2,3),
                     name=c("bob","jack","gin"),
                     loc=c("安徽-合肥","重庆-渝中区","湖北-武汉"),
                     score=c("65分","79分","53分"))

#提取函数
str_split<-function(x){
  if(grepl("-",x)){
    city<-strsplit(x,"-")[[1]][1]
    return(city)
  } else if (grepl("分",x)){
    score<-as.numeric(strsplit(x,"分")[[1]][1])
    return(score)
  }
}

#sapply操作loc、score列
raw_data$loc<-sapply(raw_data$loc,str_split)
raw_data$score<-sapply(raw_data$score,str_split)
raw_data

R语言中数据的分类 r语言分类函数_apply函数族_02

  • 分组计算

需求:计算鸢尾花每个种类的花瓣宽度和长度均值

length_mean<-tapply(iris$Petal.Length,iris$Species,mean)
width_mean<-tapply(iris$Petal.Width,iris$Species,mean)
cbind(length_mean,width_mean)

R语言中数据的分类 r语言分类函数_数据处理_03

二、实战应用

通过以上初步描述,已对apply族函数有了基本认识。现以一个具体数据案例,再次强化对其认识。

2.0 问题描述

现有不同站点,每月每天降雨量数据,要求计算每个站点每月最长的持续降雨日(持续降雨日定义:在连续时段内每天降雨量均不为0mm,而在持续时段的前一天和后一天的降雨量均为0)

例如:在4月5日-4月10日降雨量均不为0,在4月4日和4月11日降雨量为0,则4月5日至4月10日为持续降雨日,时长为6天

数据预览:loc_id为站点idrain_fall为当天降雨量

R语言中数据的分类 r语言分类函数_r语言_04


由于每个站点均只有一个月的降雨量数据,因此在对loc_id分组后,可直接计算rainfall即可。基本思路:对于每个站点来说,首先找出rainfall为0的所有天数,然后统计相邻两个0之间的数据长度L,最后统计每个L中的非0个数,再求最大值则是我们需要的最长持续降雨日。

2.1 sapply解法

考虑sapply无法直接处理分组数据,需要先对原数据,按照loc_id进行分组

data<-read.csv("data.csv")

#按loc_id分组
loc_list<-split(data,data$loc_id)

#分组计算逻辑
duration_caculate_sa<-function(x){
  zero_rainfall_loc<-which(x["rainfall"]==0)
  rainfall_vec<-c()
  last_loc<-length(zero_rainfall_loc)-1
  if(last_loc>=1){
    for(i in 1:last_loc){
      duration<-x[zero_rainfall_loc[i]:zero_rainfall_loc[i+1],"rainfall"]
      rainfall_duration<-length(which(duration!=0))
      rainfall_vec<-c(rainfall_vec,rainfall_duration)
    }
    max_rainfall_duration<-max(rainfall_vec)
    return(max_rainfall_duration)
  } else{
    max_rainfall_duration<-0
    return(max_rainfall_duration)
  }
}

sa_rst<-data.frame(sapply(loc_list,duration_caculate_sa))
colnames(sa_rst)<-c("max_rainfall_duration")
head(sa_rst)

R语言中数据的分类 r语言分类函数_数据处理_05


注意:duration_caculate_sa中是对每个loc_id的数据进行计算,此时每个loc_id的数据是数据框,需要以数据框的形式筛选数据

2.2 tapply解法

由于tapply函数本身含有分组参数index,因此可以直接操作

duration_caculate_ta<-function(x){
  zero_rainfall_loc<-which(x==0)
  rainfall_vec<-c()
  last_loc<-length(zero_rainfall_loc)-1
  if(last_loc>=1){
    for(i in 1:last_loc){
      duration<-x[zero_rainfall_loc[i]:zero_rainfall_loc[i+1]]
      rainfall_duration<-length(which(duration!=0))
      rainfall_vec<-c(rainfall_vec,rainfall_duration)
    }
    max_rainfall_duration<-max(rainfall_vec)
    return(max_rainfall_duration)
  } else{
    max_rainfall_duration<-0
    return(max_rainfall_duration)
  }
}

ta_rst<-data.frame(tapply(data$rainfall,data$loc_id,duration_caculate_ta))
colnames(ta_rst)<-c("max_rainfall_duration")
head(ta_rst)

R语言中数据的分类 r语言分类函数_数据_06


注意duration_caculate_ta对每个站点数据处理时,每个站点的数据为数组,并非数据框,此时不能用数据框的形式进行筛选数据

三、总结

apply函数族的思想有点类似大数据中的【分而治之】,在确定分组变量后,我们只需关心每个组内的计算逻辑即可。需要值得注意的是,sapply在进行分组时,里面每组数据的结构是数据框!而tapply由于事先指定了分组条件,因此每组数据的结构是数组!