Grouping functions (tapply, by, aggregate) and the *apply family
每当我想在R中做"map"py时,我通常会尝试在
但是,我从来没有完全理解它们之间的区别 - {
有人可以解释如何使用哪一个?
我当前(可能不正确/不完整)的理解是......
附带问题:我还没有学过plyr或重塑 - 将
R具有许多*应用功能,这些功能在帮助文件中有很好的描述(例如
尽管事实(在其他答案中指出)* apply系列的大部分功能都被极受欢迎的
这个答案旨在作为一种新的useRs的路标,以帮助指导他们针对他们的特定问题正确的*应用功能。注意,这不是为了简单地反刍或替换R文档!希望这个答案可以帮助您确定哪种*应用功能适合您的情况,然后由您来进一步研究。除了一个例外,性能差异将无法解决。
apply - 当您要将函数应用于行或列时
矩阵(和更高维的类似物);通常不建议使用数据帧,因为它会首先强制转换为矩阵。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # Two dimensional matrix M <- matrix(seq(1,16), 4, 4) # apply min to rows apply(M, 1, min) [1] 1 2 3 4 # apply max to columns apply(M, 2, max) [1] 4 8 12 16 # 3 dimensional array M <- array( seq(32), dim = c(4,4,2)) # Apply sum across each M[*, , ] - i.e Sum across 2nd and 3rd dimension apply(M, 1, sum) # Result is one-dimensional [1] 120 128 136 144 # Apply sum across each M[*, *, ] - i.e Sum across 3rd dimension apply(M, c(1,2), sum) # Result is two-dimensional [,1] [,2] [,3] [,4] [1,] 18 26 34 42 [2,] 20 28 36 44 [3,] 22 30 38 46 [4,] 24 32 40 48 |
如果您想要2D矩阵的行/列平均值或总和,请务必
调查高度优化,闪电般快速
lapply - 当你想将一个函数应用于a的每个元素时
依次列出并获取列表。
这是许多其他*应用功能的主力。剥
支持他们的代码,你经常会在下面找到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | x <- list(a = 1, b = 1:3, c = 10:100) lapply(x, FUN = length) $a [1] 1 $b [1] 3 $c [1] 91 lapply(x, FUN = sum) $a [1] 1 $b [1] 6 $c [1] 5005 |
sapply - 当你想将一个函数应用于a的每个元素时
反过来列出,但你想要一个向量,而不是一个列表。
如果您发现自己键入
1 2 3 4 5 6 7 8 9 | x <- list(a = 1, b = 1:3, c = 10:100) # Compare with above; a named vector, not a list sapply(x, FUN = length) a b c 1 3 91 sapply(x, FUN = sum) a b c 1 6 5005 |
在
如果合适,结果为多维数组。例如,如果我们的函数返回相同长度的向量,
1 | sapply(1:5,function(x) rnorm(3,x)) |
如果我们的函数返回一个二维矩阵,
1 | sapply(1:5,function(x) matrix(x,2,2)) |
除非我们指定
1 | sapply(1:5,function(x) matrix(x,2,2), simplify ="array") |
这些行为中的每一个当然都取决于我们的函数返回相同长度或维度的向量或矩阵。
vapply - 当你想使用
从代码中挤出更多速度。
对于
你的函数将返回,这可以节省一些时间强制返回
值适合单个原子向量。
1 2 3 4 5 6 7 8 | x <- list(a = 1, b = 1:3, c = 10:100) #Note that since the advantage here is mainly speed, this # example is only for illustration. We're telling R that # everything returned by length() should be an integer of # length 1. vapply(x, FUN = length, FUN.VALUE = 0L) a b c 1 3 91 |
mapply - 当你有几个数据结构时(例如
向量,列表)并且您想要将函数应用于第1个元素
每个,然后是每个的第二个元素等,强制结果
到
在您的功能必须接受的意义上,这是多变量的
多个参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #Sums the 1st elements, the 2nd elements, etc. mapply(sum, 1:5, 1:5, 1:5) [1] 3 6 9 12 15 #To do rep(1,4), rep(2,3), etc. mapply(rep, 1:4, 4:1) [[1]] [1] 1 1 1 1 [[2]] [1] 2 2 2 [[3]] [1] 3 3 [[4]] [1] 4 |
Map -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Map(sum, 1:5, 1:5, 1:5) [[1]] [1] 3 [[2]] [1] 6 [[3]] [1] 9 [[4]] [1] 12 [[5]] [1] 15 |
rapply - 当您想要递归地将函数应用于嵌套列表结构的每个元素时。
为了让您了解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # Append ! to string, otherwise increment myFun <- function(x){ if(is.character(x)){ return(paste(x,"!",sep="")) } else{ return(x + 1) } } #A nested list structure l <- list(a = list(a1 ="Boo", b1 = 2, c1 ="Eeek"), b = 3, c ="Yikes", d = list(a2 = 1, b2 = list(a3 ="Hey", b3 = 5))) # Result is named vector, coerced to character rapply(l, myFun) # Result is a nested list like l, with values altered rapply(l, myFun, how="replace") |
tapply - 当你想要一个函数应用于a的子集时
向量和子集由一些其他向量定义,通常是a
因子。
*的黑羊适用于各种各样的家庭。帮助文件的使用
短语"衣衫褴褛的阵列"可能有点令人困惑,但实际上却是这样
非常简单。
矢量:
1 | x <- 1:20 |
定义组的因子(长度相同!):
1 | y <- factor(rep(letters[1:5], each = 4)) |
在
1 2 3 | tapply(x, y, sum) a b c d e 10 26 42 58 74 |
可以在定义子组的地方处理更复杂的示例
通过几个因素列表的独特组合。
在精神上类似于split-apply-combine功能
常见于R(
黑羊身份。
好。
在旁注中,这里是各种
1 2 3 4 5 6 7 8 9 | Base function Input Output plyr function --------------------------------------- aggregate d d ddply + colwise apply a a/l aaply / alply by d l dlply lapply l l llply mapply a a/l maply / mlply replicate r a/l raply / rlply sapply l a laply |
从概念上讲,学习
Related functions
tapply andsweep have no corresponding function inplyr , and remain useful.merge is useful for combining summaries with the original data.
来自http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy的幻灯片21:
(希望很明显
(左边是输入,顶部是输出)
首先从Joran的优秀答案开始 - 怀疑任何事情都可以更好。
然后,以下助记符可能有助于记住每个之间的区别。虽然有些是显而易见的,但有些可能不那么明显 - 对于这些,你会在Joran的讨论中找到理由。
助记符
-
lapply 是列表应用,它作用于列表或向量并返回列表。 -
sapply 是一个简单的lapply (函数默认为在可能的情况下返回向量或矩阵) -
vapply 是经过验证的申请(允许预先指定退货对象类型) -
rapply 是嵌套列表的递归应用,即列表中的列表 -
tapply 是标记的应用,其中标签标识子集 -
apply 是通用的:将函数应用于矩阵的行或列(或更一般地,应用于数组的维度)
建立正确的背景
如果使用
这两篇文章可以提供帮助。它们提供了必要的背景,以激发
Lisp的用户将立即认识到这种范式。如果你不熟悉Lisp,一旦你了解了FP,你将获得一个强大的观点用于R - 并且
- 高级R:功能编程,由Hadley Wickham撰写
- R的简单函数式编程,作者:Michael Barton
因为我意识到这篇文章的(非常优秀的)答案缺乏
通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ct <- tapply(iris$Sepal.Width , iris$Species , summary ) cb <- by(iris$Sepal.Width , iris$Species , summary ) cb iris$Species: setosa Min. 1st Qu. Median Mean 3rd Qu. Max. 2.300 3.200 3.400 3.428 3.675 4.400 -------------------------------------------------------------- iris$Species: versicolor Min. 1st Qu. Median Mean 3rd Qu. Max. 2.000 2.525 2.800 2.770 3.000 3.400 -------------------------------------------------------------- iris$Species: virginica Min. 1st Qu. Median Mean 3rd Qu. Max. 2.200 2.800 3.000 2.974 3.175 3.800 ct $setosa Min. 1st Qu. Median Mean 3rd Qu. Max. 2.300 3.200 3.400 3.428 3.675 4.400 $versicolor Min. 1st Qu. Median Mean 3rd Qu. Max. 2.000 2.525 2.800 2.770 3.000 3.400 $virginica Min. 1st Qu. Median Mean 3rd Qu. Max. 2.200 2.800 3.000 2.974 3.175 3.800 |
如果我们打印这两个对象
正如我所说,当我们不能使用
1 2 3 | tapply(iris, iris$Species, summary ) Error in tapply(iris, iris$Species, summary) : arguments must have same length |
R表示参数必须具有相同的长度,例如"我们想要计算
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | bywork <- by(iris, iris$Species, summary ) bywork iris$Species: setosa Sepal.Length Sepal.Width Petal.Length Petal.Width Species Min. :4.300 Min. :2.300 Min. :1.000 Min. :0.100 setosa :50 1st Qu.:4.800 1st Qu.:3.200 1st Qu.:1.400 1st Qu.:0.200 versicolor: 0 Median :5.000 Median :3.400 Median :1.500 Median :0.200 virginica : 0 Mean :5.006 Mean :3.428 Mean :1.462 Mean :0.246 3rd Qu.:5.200 3rd Qu.:3.675 3rd Qu.:1.575 3rd Qu.:0.300 Max. :5.800 Max. :4.400 Max. :1.900 Max. :0.600 -------------------------------------------------------------- iris$Species: versicolor Sepal.Length Sepal.Width Petal.Length Petal.Width Species Min. :4.900 Min. :2.000 Min. :3.00 Min. :1.000 setosa : 0 1st Qu.:5.600 1st Qu.:2.525 1st Qu.:4.00 1st Qu.:1.200 versicolor:50 Median :5.900 Median :2.800 Median :4.35 Median :1.300 virginica : 0 Mean :5.936 Mean :2.770 Mean :4.26 Mean :1.326 3rd Qu.:6.300 3rd Qu.:3.000 3rd Qu.:4.60 3rd Qu.:1.500 Max. :7.000 Max. :3.400 Max. :5.10 Max. :1.800 -------------------------------------------------------------- iris$Species: virginica Sepal.Length Sepal.Width Petal.Length Petal.Width Species Min. :4.900 Min. :2.200 Min. :4.500 Min. :1.400 setosa : 0 1st Qu.:6.225 1st Qu.:2.800 1st Qu.:5.100 1st Qu.:1.800 versicolor: 0 Median :6.500 Median :3.000 Median :5.550 Median :2.000 virginica :50 Mean :6.588 Mean :2.974 Mean :5.552 Mean :2.026 3rd Qu.:6.900 3rd Qu.:3.175 3rd Qu.:5.875 3rd Qu.:2.300 Max. :7.900 Max. :3.800 Max. :6.900 Max. :2.500 |
它确实有效,结果非常令人惊讶。类
请注意,如果第一个参数是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | by(iris, iris$Species, mean) iris$Species: setosa [1] NA ------------------------------------------- iris$Species: versicolor [1] NA ------------------------------------------- iris$Species: virginica [1] NA Warning messages: 1: In mean.default(data[x, , drop = FALSE], ...) : argument is not numeric or logical: returning NA 2: In mean.default(data[x, , drop = FALSE], ...) : argument is not numeric or logical: returning NA 3: In mean.default(data[x, , drop = FALSE], ...) : argument is not numeric or logical: returning NA |
骨料
如果我们以这种方式使用它,
1 2 3 4 5 6 7 8 9 10 11 | at <- tapply(iris$Sepal.Length , iris$Species , mean) ag <- aggregate(iris$Sepal.Length , list(iris$Species), mean) at setosa versicolor virginica 5.006 5.936 6.588 ag Group.1 x 1 setosa 5.006 2 versicolor 5.936 3 virginica 6.588 |
两个直接的区别是
在某些情况下,这些元素使
以下是一些示例(可在文档中找到):
1 2 3 4 5 6 7 8 9 10 | ag <- aggregate(len ~ ., data = ToothGrowth, mean) ag supp dose len 1 OJ 0.5 13.23 2 VC 0.5 7.98 3 OJ 1.0 22.70 4 VC 1.0 16.77 5 OJ 2.0 26.06 6 VC 2.0 26.14 |
我们可以用
1 2 3 4 5 6 7 | att <- tapply(ToothGrowth$len, list(ToothGrowth$dose, ToothGrowth$supp), mean) att OJ VC 0.5 13.23 7.98 1 22.70 16.77 2 26.06 26.14 |
还有一些时候我们不能使用
1 2 3 4 5 6 7 8 9 | ag1 <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, mean) ag1 Month Ozone Temp 1 5 23.61538 66.73077 2 6 29.44444 78.22222 3 7 59.11538 83.88462 4 8 59.96154 83.96154 5 9 31.44828 76.89655 |
我们无法在一次调用中使用
1 2 3 4 5 6 7 8 9 10 | ta1 <- tapply(airquality$Ozone, airquality$Month, mean, na.rm = TRUE) ta2 <- tapply(airquality$Temp, airquality$Month, mean, na.rm = TRUE) cbind(ta1, ta2) ta1 ta2 5 23.61538 65.54839 6 29.44444 79.10000 7 59.11538 83.90323 8 59.96154 83.96774 9 31.44828 76.90000 |
而使用
1 | by(airquality[c("Ozone","Temp")], airquality$Month, mean, na.rm = TRUE) |
其他时候结果是相同的,差异只是在类中(然后它是如何显示/打印的,而不仅仅是 - 例如,如何将其子集化)对象:
1 2 | byagg <- by(airquality[c("Ozone","Temp")], airquality$Month, summary) aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary) |
以前的代码实现了相同的目标和结果,在某些方面,使用的工具只是个人品味和需求的问题;前两个对象在子集方面有非常不同的需求。
有很多很好的答案可以讨论每个函数的用例差异。答案都没有讨论性能上的差异。这是合理的,因为各种函数期望各种输入并产生各种输出,但是它们中的大多数具有通过系列/组来评估的一般共同目标。我的回答是关注绩效。由于上述原因,矢量的输入创建包含在时序中,因此不测量
我一次测试了两个不同的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | library(dplyr) library(data.table) set.seed(123) n = 5e7 k = 5e5 x = runif(n) grp = sample(k, n, TRUE) timing = list() # sapply timing[["sapply"]] = system.time({ lt = split(x, grp) r.sapply = sapply(lt, function(x) list(sum(x), length(x)), simplify = FALSE) }) # lapply timing[["lapply"]] = system.time({ lt = split(x, grp) r.lapply = lapply(lt, function(x) list(sum(x), length(x))) }) # tapply timing[["tapply"]] = system.time( r.tapply <- tapply(x, list(grp), function(x) list(sum(x), length(x))) ) # by timing[["by"]] = system.time( r.by <- by(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE) ) # aggregate timing[["aggregate"]] = system.time( r.aggregate <- aggregate(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE) ) # dplyr timing[["dplyr"]] = system.time({ df = data_frame(x, grp) r.dplyr = summarise(group_by(df, grp), sum(x), n()) }) # data.table timing[["data.table"]] = system.time({ dt = setnames(setDT(list(x, grp)), c("x","grp")) r.data.table = dt[, .(sum(x), .N), grp] }) # all output size match to group count sapply(list(sapply=r.sapply, lapply=r.lapply, tapply=r.tapply, by=r.by, aggregate=r.aggregate, dplyr=r.dplyr, data.table=r.data.table), function(x) (if(is.data.frame(x)) nrow else length)(x)==k) # sapply lapply tapply by aggregate dplyr data.table # TRUE TRUE TRUE TRUE TRUE TRUE TRUE |
1 2 3 4 5 6 7 8 9 10 11 12 | # print timings as.data.table(sapply(timing, `[[`,"elapsed"), keep.rownames = TRUE )[,.(fun = V1, elapsed = V2) ][order(-elapsed)] # fun elapsed #1: aggregate 109.139 #2: by 25.738 #3: dplyr 18.978 #4: tapply 17.006 #5: lapply 11.524 #6: sapply 11.326 #7: data.table 2.686 |
尽管这里有很多好的答案,还有2个基本函数值得一提,有用的
外
1 2 3 | The outer product of the arrays X and Y is the array A with dimension c(dim(X), dim(Y)) where element A[c(arrayindex.x, arrayindex.y)] = FUN(X[arrayindex.x], Y[arrayindex.y], ...). |
这使得它似乎只对线性代数类型的东西有用。但是,它可以像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | A<-c(1,3,5,7,9) B<-c(0,3,6,9,12) mapply(FUN=pmax, A, B) > mapply(FUN=pmax, A, B) [1] 1 3 6 9 12 outer(A,B, pmax) > outer(A,B, pmax) [,1] [,2] [,3] [,4] [,5] [1,] 1 3 6 9 12 [2,] 3 3 6 9 12 [3,] 5 5 6 9 12 [4,] 7 7 7 9 12 [5,] 9 9 9 9 12 |
当我有一个值向量和一个条件向量时,我亲自使用了这个,并希望看到哪些值符合哪些条件。
eapply
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | A<-c(1,3,5,7,9) B<-c(0,3,6,9,12) C<-list(x=1, y=2) D<-function(x){x+1} > eapply(.GlobalEnv, is.function) $A [1] FALSE $B [1] FALSE $C [1] FALSE $D [1] TRUE |
坦率地说,我不会使用这个,但如果你正在构建大量的软件包或创建很多环境,它可能会派上用场。
可能值得一提的是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | dfr <- data.frame(a=1:20, f=rep(LETTERS[1:5], each=4)) means <- tapply(dfr$a, dfr$f, mean) ## A B C D E ## 2.5 6.5 10.5 14.5 18.5 ## great, but putting it back in the data frame is another line: dfr$m <- means[dfr$f] dfr$m2 <- ave(dfr$a, dfr$f, FUN=mean) # NB argument name FUN is needed! dfr ## a f m m2 ## 1 A 2.5 2.5 ## 2 A 2.5 2.5 ## 3 A 2.5 2.5 ## 4 A 2.5 2.5 ## 5 B 6.5 6.5 ## 6 B 6.5 6.5 ## 7 B 6.5 6.5 ## ... |
基本包中没有任何内容像
1 2 3 4 5 6 7 8 9 10 | dfr$foo <- ave(1:nrow(dfr), dfr$f, FUN=function(x) { x <- dfr[x,] sum(x$m*x$m2) }) dfr ## a f m m2 foo ## 1 1 A 2.5 2.5 25 ## 2 2 A 2.5 2.5 25 ## 3 3 A 2.5 2.5 25 ## ... |
我最近发现了相当有用的
扫
基本思想是以行或列方式扫描数组并返回修改后的数组。一个例子将使这一点清楚(来源:datacamp):
假设您有一个矩阵并希望按列标准化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | dataPoints <- matrix(4:15, nrow = 4) # Find means per column with `apply()` dataPoints_means <- apply(dataPoints, 2, mean) # Find standard deviation with `apply()` dataPoints_sdev <- apply(dataPoints, 2, sd) # Center the points dataPoints_Trans1 <- sweep(dataPoints, 2, dataPoints_means,"-") print(dataPoints_Trans1) ## [,1] [,2] [,3] ## [1,] -1.5 -1.5 -1.5 ## [2,] -0.5 -0.5 -0.5 ## [3,] 0.5 0.5 0.5 ## [4,] 1.5 1.5 1.5 # Return the result dataPoints_Trans1 ## [,1] [,2] [,3] ## [1,] -1.5 -1.5 -1.5 ## [2,] -0.5 -0.5 -0.5 ## [3,] 0.5 0.5 0.5 ## [4,] 1.5 1.5 1.5 # Normalize dataPoints_Trans2 <- sweep(dataPoints_Trans1, 2, dataPoints_sdev,"/") # Return the result dataPoints_Trans2 ## [,1] [,2] [,3] ## [1,] -1.1618950 -1.1618950 -1.1618950 ## [2,] -0.3872983 -0.3872983 -0.3872983 ## [3,] 0.3872983 0.3872983 0.3872983 ## [4,] 1.1618950 1.1618950 1.1618950 |
注意:对于这个简单的例子,当然可以通过