R数据阐明傍边的化整为零(Split-Apply-Combine)计策
本文心得自:The Split-Apply-Combine Strategy for Data Analysis, Hadley Wickham, Journal of Statistical Software, April 2011, V.40.
引子:
我们经常会碰着这样的问题,数据量很大,并不需要依顺序来依次处理惩罚。公道分块处理惩罚,并最终整合起来是一个不错的选择。这也就是所谓的Split-Apply-Combine Strategy计策。这在速度上会有比做一个loop有优势,因为它可以并行处理惩罚数据。
什么时候我们需要利用到化整为零的计策呢?有以下三种环境:
- 数据需要分组处理惩罚
- 数据需要凭据每行可能每列来处理惩罚
- 数据需要分级处理惩罚,和分组很雷同,可是分级时需要思量分级之间的干系。
化整为零计策有点雷同于由Google推广的map-reduce计策。虽然map-reduce计策的基本是网格,而这里的Split-Apply-Combine的基本完全可以是单机,甚至不支持并行处理惩罚的单机都可以。
然而,化整为零并不是一个很直观的编程进程。最直观的进程是利用Loop轮回。这里利用一个例子来讲授一下如何实现化整为零计策。在plyr包中有数据ozone,它是一个三维矩阵(24X24X72),个中最后一维72是指的6年12个月每个月的功效。也就是ozone是一个包罗了持续72个月24X24的三维矩阵数据。三维别离是lat,long,time。我们需要由对时间robust linear model之后的残基residuals。
> library(plyr) # need for dataset ozone > library(MASS) # need for function rlm > month <- ordered(rep(1:12, length=72)) #set time sequence > #try one set > one <- ozone[1,1,] > model <- rlm(one ~ month - 1); model Call: rlm(formula = one ~ month - 1) Converged in 9 iterations Coefficients: month1 month2 month3 month4 month5 month6 month7 month8 month9 month10 month11 month12 264.3964 259.2036 255.0000 252.0052 258.5089 265.3387 274.0000 276.6724 277.0000 285.0000 283.6036 273.1964 Degrees of freedom: 72 total; 60 residual Scale estimate: 4.45 > deseas <- resid(model) |
此刻我们对每一组数据都做沟通的处理惩罚。首先利用for loop,这样较量直观
> deseasf <- function(value) rlm(value ~ month -1) #the function > models <- as.list(rep(NA, 24*24)) #prepare the variable > dim(models) <- c(24, 24) > deseas <- array(NA, c(24,24,72)) #prepare the variable > dimnames(deseas) <- dimnames(ozone) > for (i in seq_len(24)) { #for loop for first dimension + for(j in seq_len(24)) { #for loop for second dimension + mod <- deseasf(ozone[i, j, ]) #apply function + models[[i, j]] <- mod #save data + deseas[i, j, ] <- resid(mod) #get residure + } + } |
接下来我们利用化整为零的计策。因为数据可以分成24X24块来处理惩罚,每一块都是单独运算,可以并行处理惩罚。而利用for loop,只能一块接一块的处理惩罚,在速度上大概没有并行处理惩罚来得快。而在R傍边,有一系列相关的函数,apply, lapply, sapply, tapply, mapply, sweep。我们先得相识这些函数,然后再来应用它们。最简朴的是apply。
其形式是apply(array, margin, function, …)。首先,apply的工具是矩阵array可能matrix。它的第二个参数是指的维度,假如你的array是一个二维矩阵,需要按横排的方法计较每一排的平均值,那么你的第二个参数就应该是1。假如需要按纵列的方法计较每一列的平均值,那么第二个参数就应该是2。虽然还可以利用c(1,2)这样的方法来配置第二个参数,就是并行计较每个值。第三个参数是需要应用的函数。之后的…是需要传入函数的其它参数。而apply的返回值就是由function来确定的,它大概是vector, array or list。下面举个例子较量容易领略。
> x<-cbind(x1=3,x2=c(4:1,2:5)) > dimnames(x)[[1]]<-letters[1:8] > x x1 x2 a 3 4 b 3 3 c 3 2 d 3 1 e 3 2 f 3 3 g 3 4 h 3 5 > apply(x,2,mean,trim =.2) #在这里,trim =.2就是mean(x, trim = 0, na.rm = FALSE, ...)函数傍边的一个参数。 x1 x2 3 3 > apply(x,1,mean,trim =.2) a b c d e f g h 3.5 3.0 2.5 2.0 2.5 3.0 3.5 4.0 > col.sums <- apply(x, 2, sum) > row.sums <- apply(x, 1, sum) > rbind(cbind(x, Rtot = row.sums), Ctot = c(col.sums, sum(col.sums))) x1 x2 Rtot a 3 4 7 b 3 3 6 c 3 2 5 d 3 1 4 e 3 2 5 f 3 3 6 g 3 4 7 h 3 5 8 Ctot 24 24 48 > sum.plus.y <- function(x,y){ + sum(x) + y + } > apply(x, 1, sum.plus.y, 3) #利用自界说函数 a b c d e f g h 10 9 8 7 8 9 10 11 |
#p#分页标题#e#
领略了apply,就可较量容易地领略lapply, sapply, vapply了。这三者针对的工具是list可能Vector。其形式为
lapply(X, FUN, ...) sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE) vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE) |
我们看到,它没有了apply傍边所需要的第二个参数margin,其原因就是输入工具不是array可能matrix,而是list可能Vector。先举个最简朴的应用例子。
> x <- list(a = 1:10, beta = exp(-3:3), logic = c(TRUE,FALSE,FALSE,TRUE)) > x $a [1] 1 2 3 4 5 6 7 8 9 10 $beta [1] 0.04978707 0.13533528 0.36787944 1.00000000 2.71828183 7.38905610 20.08553692 $logic [1] TRUE FALSE FALSE TRUE > lapply(x,mean) $a [1] 5.5 $beta [1] 4.535125 $logic [1] 0.5 |
也就是说,其x的维度应该是一维的,虽然,其下面的元素可以是多维的。那么假如x是一个矩阵呢?我们先来看例子。
> x<-cbind(x1=3,x2=c(4:1,2:5)) > dimnames(x)[[1]]<-letters[1:8] > x x1 x2 a 3 4 b 3 3 c 3 2 d 3 1 e 3 2 f 3 3 g 3 4 h 3 5 > x<-as.data.frame(x) > as.list(x) $x1 [1] 3 3 3 3 3 3 3 3 $x2 [1] 4 3 2 1 2 3 4 5 > lapply(x,function(.ele) mean(.ele)) $x1 [1] 3 $x2 [1] 3 > sapply(x,mean) x1 x2 3 3 > vapply(x,mean,1) x1 x2 3 3 |
从它们的说明文件我们知道,无论你传入的x是什么,它首先做的一步说是利用as.list来将其转换成一个一维的list。所以,一个data.frame传入lapply之后,它的colnames将会转换成list的names,而rownames大概会丢失。较量可知,lapply和sapply的不同在于,lapply的返回值是一个list,而sapply的返回值是一个矩阵。sapply的返回值其实就是在lapply的基本上再利用了simplify2array(x, higher=TRUE)函数,利用其功效酿成一个array。为了更清楚地相识sapply和vapply,我们看下面的例子
> i39 <- sapply(3:9, seq) > i39 [[1]] [1] 1 2 3 [[2]] [1] 1 2 3 4 [[3]] [1] 1 2 3 4 5 [[4]] [1] 1 2 3 4 5 6 [[5]] [1] 1 2 3 4 5 6 7 [[6]] [1] 1 2 3 4 5 6 7 8 [[7]] [1] 1 2 3 4 5 6 7 8 9 > sapply(i39, fivenum) [,1] [,2] [,3] [,4] [,5] [,6] [,7] [1,] 1.0 1.0 1 1.0 1.0 1.0 1 [2,] 1.5 1.5 2 2.0 2.5 2.5 3 [3,] 2.0 2.5 3 3.5 4.0 4.5 5 [4,] 2.5 3.5 4 5.0 5.5 6.5 7 [5,] 3.0 4.0 5 6.0 7.0 8.0 9 > vapply(i39, fivenum, + c(Min. = 0, "1st Qu." = 0, Median = 0, "3rd Qu." = 0, Max. = 0)) [,1] [,2] [,3] [,4] [,5] [,6] [,7] Min. 1.0 1.0 1 1.0 1.0 1.0 1 1st Qu. 1.5 1.5 2 2.0 2.5 2.5 3 Median 2.0 2.5 3 3.5 4.0 4.5 5 3rd Qu. 2.5 3.5 4 5.0 5.5 6.5 7 Max. 3.0 4.0 5 6.0 7.0 8.0 9 |
个中,fivenum函数会返回一组数的最小值,四分位低值(lower-hinge),中值,四分位高值(upper-hinge),以及较大值。从上面的较量中,我们很清楚的看到,sapply返回值的分列形式,以list的names为colnames。可以想象,它利用的是按列填充matrix的方法输出的。而vapply是在sapply的基本上,为rownames做出了界说。
除了上面先容的,尚有tapply,mapply,sweep等。它们的界说如下。假如需要相识和把握它们,需要熟悉上面先容的apply, lapply, sapply以及vapply。还需要相识split。所以这里就不多加表明白(因为篇幅会很长)。
tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE) mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE) sweep(x, MARGIN, STATS, FUN="-", check.margin=TRUE, ...) |
有了上面的相识,我们可以看一下最前面的for loop可以奈何改写为apply形式了。
> models <- apply(ozone, 1:2, deseasf) #这里相当于for loop傍边的 for(i in seq_len(24)){for(j in seq_len(24)){mod<-deseasf(ozone[i,j,]); models[[i,j]]<-mod;}}, 可是运算却是并行处理惩罚的。 > resids_list <- lapply(models, resid) > resids <- unlist(resids_list) > dim(resids) <- c(72, 24, 24) > deseas <- aperm(resids, c(2, 3, 1)) > dimnames(deseas) <- dimnames(ozone) |
#p#分页标题#e#
假如,我们知道的并不是ozone这样一个24X24X72的已知确切维度的三维数组,如果我们只有一个名为ozonedf的matrix,它的5列别离为lat, long, time, month,和value。我们假如需要做上述的阐明应该怎么办呢?在思路上,我们的想法大概会是先从ozonedf出产生成一个雷同ozone这样子的数据,然后再利用apply,lapply这样的函数来完成绩可以。第一步生成ozone这样子的数据,就是化整为零计策(Split-Apply-Combine)的第一步了。
R> deseasf_df <- function(df) { + rlm(value ~ month - 1, data = df) + } R> pieces <- split(ozonedf, list(ozonedf$lat, ozonedf$long)) R> models <- lapply(pieces, deseasf_df) R> results <- mapply(function(model, df) { + cbind(df[rep(1, 72), c("lat", "long")], resid(model)) + }, models, pieces) R> deseasdf <- do.call("rbind", results) |
12下一页