在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))
- 对数据框某列清洗
需求:现需提取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
- 分组计算
需求:计算鸢尾花每个种类的花瓣宽度和长度均值
length_mean<-tapply(iris$Petal.Length,iris$Species,mean)
width_mean<-tapply(iris$Petal.Width,iris$Species,mean)
cbind(length_mean,width_mean)
二、实战应用
通过以上初步描述,已对apply族函数有了基本认识。现以一个具体数据案例,再次强化对其认识。
2.0 问题描述
现有不同站点,每月每天降雨量数据,要求计算每个站点每月最长的持续降雨日(持续降雨日定义:在连续时段内每天降雨量均不为0mm,而在持续时段的前一天和后一天的降雨量均为0)
例如:在4月5日-4月10日降雨量均不为0,在4月4日和4月11日降雨量为0,则4月5日至4月10日为持续降雨日,时长为6天
数据预览:loc_id
为站点id
,rain_fall
为当天降雨量
由于每个站点均只有一个月的降雨量数据,因此在对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)
注意: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)
注意:duration_caculate_ta
对每个站点数据处理时,每个站点的数据为数组,并非数据框,此时不能用数据框的形式进行筛选数据!
三、总结
apply
函数族的思想有点类似大数据中的【分而治之】,在确定分组变量后,我们只需关心每个组内的计算逻辑即可。需要值得注意的是,sapply在进行分组时,里面每组数据的结构是数据框!而tapply由于事先指定了分组条件,因此每组数据的结构是数组!