每当我想在 R 中做一些“map”py 操作时,我通常会尝试使用 apply
系列中的函数。
但是,我从来没有完全理解它们之间的区别——{sapply
、lapply
等} 如何将函数应用于输入/分组输入、输出将是什么样子,甚至输入可以做什么be - 所以我经常把它们都看完,直到我得到我想要的。
有人可以解释如何在什么时候使用哪一个吗?
我目前(可能不正确/不完整)的理解是......
sapply(vec, f):输入是一个向量。输出是一个向量/矩阵,其中元素 i 是 f(vec[i]),如果 f 有一个多元素输出,则给你一个矩阵 lapply(vec, f):与 sapply 相同,但输出是一个列表? apply(matrix, 1/2, f):输入是一个矩阵。输出是一个向量,其中元素 i 是 f(矩阵的行/列 i) tapply(vector, grouping, f):输出是一个矩阵/数组,其中矩阵/数组中的一个元素是 f 在 a 处的值对向量的 g 进行分组,并且 g 被(dataframe,grouping,f)推送到行/列名称:让 g 成为一个分组。将 f 应用于组/数据框的每一列。漂亮地打印每列的分组和 f 的值。 aggregate(matrix, grouping, f):类似于 by,但不是漂亮地打印输出,而是将所有内容粘贴到数据框中。
附带问题:我还没有学习 plyr 或 reshape —— plyr
或 reshape
会完全取代所有这些吗?
*apply()
和 by
的直接替代品。 plyr(至少对我来说)似乎更加一致,因为我总是确切地知道它期望什么数据格式以及它会吐出什么。这为我省去了很多麻烦。
doBy
和选择 &应用 data.table
的功能。
sapply
只是在输出中添加了 simplify2array
的 lapply
。 apply
强制转换为原子向量,但输出可以是向量或列表。 by
将数据帧拆分为子数据帧,但它不单独在列上使用 f
。仅当存在“data.frame”类的方法时,f
才可能被 by
按列应用。 aggregate
是通用的,因此对于第一个参数的不同类存在不同的方法。
有许多 *apply 函数,这些函数在帮助文件(例如 ?apply
)中有详细描述。但是,它们的数量已经足够多,以至于初学者可能很难决定哪一个适合他们的情况,甚至很难记住它们。他们可能有一种普遍的感觉,“我应该在这里使用 *apply 函数”,但一开始很难让它们保持一致。
尽管事实(在其他答案中指出)*apply 系列的大部分功能都包含在非常流行的 plyr
包中,但基本功能仍然有用且值得了解。
该答案旨在充当新用户的一种路标,以帮助将他们引导至针对其特定问题的正确 *apply 功能。请注意,这并不是为了简单地反刍或替换 R 文档!希望这个答案可以帮助您决定哪个 *apply 函数适合您的情况,然后由您来进一步研究它。除了一个例外,将不会解决性能差异。
apply - 当您想将函数应用于矩阵的行或列(以及更高维的类似物)时;通常不建议用于数据帧,因为它会首先强制转换为矩阵。 # 二维矩阵 M <- matrix(seq(1,16), 4, 4) # 将最小值应用于行 apply(M, 1, min) [1] 1 2 3 4 # 将最大值应用于列 apply(M, 2 , max) [1] 4 8 12 16 # 3 维数组 M <- array( seq(32), dim = c(4,4,2)) # 对每个 M[*, , ] 应用求和 - 即求和2nd and 3rd dimension apply(M, 1, sum) # 结果是一维的 [1] 120 128 136 144 # 在每个 M[*, *, ] 上求和 apply(M, c(1 ,2), sum) # 结果是二维的 [,1] [,2] [,3] [,4] [1,] 18 26 34 42 [2,] 20 28 36 44 [3,] 22 30 38 46 [4,] 24 32 40 48 如果您想要二维矩阵的行/列均值或求和,请务必研究高度优化、闪电般快速的 colMeans、rowMeans、colSums、rowSums。
lapply - 当您想依次将函数应用于列表的每个元素并获取列表时。这是许多其他 *apply 函数的主力。剥开他们的代码,你会经常在下面找到 lapply。 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 = 总和) $a [1] 1 $b [1] 6 $c [1] 5005
sapply - 当你想依次对列表的每个元素应用一个函数,但你想要一个向量,而不是一个列表。如果你发现自己在输入 unlist(lapply(...)),请停下来考虑 sapply。 x <- list(a = 1, b = 1:3, c = 10:100) # 与上述比较;命名向量,而不是列表 sapply(x, FUN = length) abc 1 3 91 sapply(x, FUN = sum) abc 1 6 5005 在更高级的 sapply 使用中,它会尝试将结果强制转换为多维数组, 如果合适的话。例如,如果我们的函数返回相同长度的向量,sapply 会将它们用作矩阵的列: sapply(1:5,function(x) rnorm(3,x)) 如果我们的函数返回一个二维矩阵,sapply基本上会做同样的事情,将每个返回的矩阵视为一个长向量: sapply(1:5,function(x) matrix(x,2,2)) 除非我们指定 simple = "array",在这种情况下它将使用单个矩阵构建多维数组: sapply(1:5,function(x) matrix(x,2,2), simple = "array") 这些行为中的每一个当然取决于我们的函数返回向量或相同长度或尺寸的矩阵。
vapply - 当您想使用 sapply 但可能需要从代码中挤出更多速度或想要更多类型安全性时。对于 vapply,您基本上给 R 一个示例,说明您的函数将返回什么样的东西,这可以节省一些时间来强制返回值以适应单个原子向量。 x <- list(a = 1, b = 1:3, c = 10:100) #注意,由于这里的优势主要是速度,所以这个例子只是为了说明。我们告诉 R #length() 返回的所有内容都应该是 #length 1 的整数。 vapply(x, FUN = length, FUN.VALUE = 0L) abc 1 3 91
mapply - 当您有多个数据结构(例如向量、列表)并且您想将函数应用于每个元素的第一个元素,然后是每个元素的第二个元素等时,将结果强制转换为向量/数组,如树莓派。这是多变量的,因为您的函数必须接受多个参数。 #对第一个元素、第二个元素等求和 mapply(sum, 1:5, 1:5, 1:5) [1] 3 6 9 12 15 #To do rep(1,4), rep(2, 3) 等 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 - 使用 SIMPLIFY = FALSE 映射的包装器,因此保证返回一个列表。映射(总和,1:5,1:5,1:5)[[1]] [1] 3 [[2]] [1] 6 [[3]] [1] 9 [[4]] [1 ] 12 [[5]] [1] 15
rapply - 当您想要将函数应用于嵌套列表结构的每个元素时,递归。为了让您了解 rapply 是多么罕见,我在第一次发布此答案时忘记了它!显然,我敢肯定很多人都在使用它,但是 YMMV。 rapply 最好使用用户定义的函数来说明:# Append !为字符串,否则递增 myFun <- function(x){ if(is.character(x)){ return(paste(x,"!",sep="")) } else{ return(x + 1) } } #嵌套列表结构 l <- list(a = list(a1 = "Boo", b1 = 2, c1 = "Eeek"), b = 3, c = "Yikes", d = list(a2 = 1, b2 = list(a3 = "Hey", b3 = 5))) # 结果被命名为向量,强制转换为字符 rapply(l, myFun) # 结果是一个像 l 一样的嵌套列表,值改变 rapply(l, myFun, how= “代替”)
tapply - 当您想将函数应用于向量的子集并且子集由某个其他向量定义时,通常是一个因子。 *apply 家族的害群之马。帮助文件中“ragged array”这个短语的使用可能有点令人困惑,但实际上很简单。一个向量:x <- 1:20 一个因子(长度相同!)定义组:y <- factor(rep(letters[1:5], each = 4)) 将定义的每个子组内的 x 值相加by y:tapply(x, y, sum) abcde 10 26 42 58 74 可以处理更复杂的示例,其中子组由多个因子列表的唯一组合定义。 tapply 在精神上类似于 R 中常见的 split-apply-combine 函数(aggregate、by、ave、ddply 等),因此它是害群之马。
在旁注中,这里是各种 plyr
函数与基本 *apply
函数的对应关系(从 plyr 网页 http://had.co.nz/plyr/ 中的 plyr 文档介绍)
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
plyr
的目标之一是为每个函数提供一致的命名约定,在函数名称中编码输入和输出数据类型。它还提供了输出的一致性,因为来自 dlply()
的输出很容易传递给 ldply()
以产生有用的输出等。
从概念上讲,学习 plyr
并不比理解基本 *apply
函数更难。
plyr
和 reshape
函数在我的日常使用中几乎取代了所有这些函数。但是,同样来自 Intro to Plyr 文档:
相关函数tapply和sweep在plyr中没有对应的函数,仍然有用。合并对于将摘要与原始数据结合起来很有用。
*apply()
系列函数更容易学习。对我来说,ddply()
非常直观,因为我熟悉 SQL 聚合函数。 ddply()
成为我解决许多问题的锤子,其中一些问题可以用其他命令更好地解决。
来自 http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy 的幻灯片 21:
https://i.stack.imgur.com/UMzZ4.png
(希望很明显,apply
对应于 @Hadley 的 aaply
,而 aggregate
对应于 @Hadley 的 ddply
等等。如果您没有从这张图片中得到它,同一张幻灯片的幻灯片 20 会澄清。)
(左边是输入,上面是输出)
首先从 Joran's excellent answer 开始——怀疑任何事情都可以做得更好。
那么下面的助记符可能有助于记住它们之间的区别。虽然有些是显而易见的,但有些可能不那么明显——因为这些你会在 Joran 的讨论中找到理由。
助记符
lapply 是一个列表应用,它作用于列表或向量并返回一个列表。
sapply 是一个简单的 lapply (如果可能,函数默认返回向量或矩阵)
vapply 是经过验证的应用(允许预先指定返回对象类型)
rapply 是嵌套列表的递归应用,即列表中的列表
tapply 是一个标记应用,其中标记标识子集
apply 是通用的:将函数应用于矩阵的行或列(或更一般地,应用于数组的维度)
建立正确的背景
如果使用 apply
系列对您来说仍然感觉有点陌生,那么可能是您错过了一个关键观点。
这两篇文章可以提供帮助。它们为激发 apply
系列函数提供的函数式编程技术提供了必要的背景。
Lisp 的用户会立即认出这个范式。如果您不熟悉 Lisp,一旦您了解了 FP,您将获得在 R 中使用的强大观点——apply
会更有意义。
高级 R:函数式编程,作者 Hadley Wickham
R 中的简单函数式编程,作者:Michael Barton
因为我意识到这篇文章的(非常优秀的)答案缺少 by
和 aggregate
解释。这是我的贡献。
经过
但是,如文档中所述,by
函数可以作为 tapply
的“包装器”。当我们想要计算 tapply
无法处理的任务时,by
的力量就会出现。一个例子是这段代码:
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
如果我们打印这两个对象 ct
和 cb
,我们“基本上”会得到相同的结果,唯一的区别在于它们的显示方式和不同的 class
属性,分别是 cb
的 by
和ct
的 array
。
正如我所说,当我们不能使用 tapply
时,by
的力量就会出现;以下代码是一个示例:
tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) :
arguments must have same length
R 说参数必须具有相同的长度,例如“我们想要计算 iris
中所有变量的 summary
沿因子 Species
”:但 R 不能这样做,因为它不知道如何处理。
使用 by
函数,R 为 data frame
类分派一个特定方法,然后让 summary
函数工作,即使第一个参数的长度(以及类型)不同。
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
它确实有效,结果非常令人惊讶。它是类 by
的对象,它沿着 Species
(例如,对于它们中的每一个)计算每个变量的 summary
。
请注意,如果第一个参数是 data frame
,则分派函数必须具有该类对象的方法。例如,如果我们将这段代码与 mean
函数一起使用,我们将得到这段毫无意义的代码:
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
总计的
如果我们以这种方式使用 aggregate
,则可以将其视为另一种不同的使用方式 tapply
。
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
两个直接的区别是 aggregate
必须 的第二个参数是一个列表,而 tapply
可以(非强制性)是一个列表,并且 {1 } 是一个数据框,而 tapply
之一是 array
。
aggregate
的强大之处在于它可以使用 subset
参数轻松处理数据的子集,并且它还具有用于 ts
对象和 formula
的方法。
在某些情况下,这些元素使 aggregate
更易于使用该 tapply
。以下是一些示例(可在文档中找到):
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
我们可以使用 tapply
实现相同的效果,但语法稍难一些,输出(在某些情况下)可读性较差:
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
还有一些时候我们不能使用 by
或 tapply
而我们必须使用 aggregate
。
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
我们无法在一次调用中使用 tapply
获得先前的结果,但我们必须计算每个元素沿 Month
的平均值,然后将它们组合起来(另请注意,我们必须调用 na.rm = TRUE
,因为 formula
方法aggregate
函数的默认值为 na.action = na.omit
):
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
而使用 by
我们无法实现这一点,实际上以下函数调用会返回错误(但很可能它与提供的函数 mean
有关):
by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)
其他时候结果是相同的,差异只是在类中(然后它是如何显示/打印的,而不仅仅是——例如,如何对其进行子集化)对象:
byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)
之前的代码实现了相同的目标和结果,在某些时候使用什么工具只是个人品味和需求的问题;前两个对象在子集方面有非常不同的需求。
data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))
这是tapply的使用. With the right splitting there is nothing you cant do with
tapply. The only thing is it returns a matrix. Please be careful by saying we cant use
tapply`
有很多很好的答案讨论了每个功能的用例差异。没有一个答案讨论性能差异。这是合理的,因为各种函数期望不同的输入并产生不同的输出,但它们中的大多数都有一个通用的共同目标来按系列/组进行评估。我的答案将集中在性能上。由于上述从向量创建的输入包含在时序中,因此 apply
函数也未被测量。
我一次测试了两个不同的函数 sum
和 length
。测试的音量为 50M 输入和 50K 输出。我还包括了两个当前流行的包,它们在提出问题时并未被广泛使用,data.table
和 dplyr
。如果您的目标是获得良好的性能,两者都绝对值得一看。
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
# 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 个基本函数值得一提,有用的 outer
函数和晦涩难懂的 eapply
函数
外
outer
是一个非常有用的函数,它被隐藏为一个更普通的函数。如果您阅读 outer
的帮助,它的描述会说:
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], ...).
这使得它看起来只对线性代数类型的东西有用。但是,它可以很像 mapply
用于将函数应用于两个输入向量。不同之处在于 mapply
会将函数应用于前两个元素,然后是后两个元素,以此类推,而 outer
会将函数应用于第一个向量中的一个元素和第二个向量中的一个元素的每个组合。例如:
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
与 lapply
类似,只是它不是将函数应用于列表中的每个元素,而是将函数应用于环境中的每个元素。例如,如果您想在全局环境中查找用户定义函数的列表:
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
坦率地说,我并不经常使用它,但如果您正在构建大量包或创建大量环境,它可能会派上用场。
可能值得一提的是 ave
。 ave
是 tapply
的友好表弟。它以一种可以直接插入数据框的形式返回结果。
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
## ...
对于整个数据帧,基本包中没有任何内容类似于 ave
(因为对于数据帧,by
类似于 tapply
)。但你可以捏造它:
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
## ...
我最近发现了相当有用的 sweep
函数,为了完整起见将其添加到此处:
扫
基本思想是扫描数组行或列并返回修改后的数组。一个例子可以说明这一点(来源:datacamp):
假设您有一个矩阵并希望按列standardize:
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,"-")
# 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
注意:对于这个简单的例子,同样的结果当然可以通过
apply(dataPoints, 2, scale)
sweep
与此处提到的所有其他函数一样,是一个高阶函数,例如 apply
、sapply
、lapply
,因此对于超过 1,000 次投票的已接受答案以及其中给出的示例,可以提出相同的问题。只需看看那里为 apply
给出的示例。
sweep(matrix(1:6,nrow=2),2,7:9,list)
。它通常比 apply
更有效,因为在 apply
循环的地方,sweep
能够使用矢量化函数。
在最近在 CRAN 上发布的 collapse 包中,我尝试将大多数常见的应用功能压缩成两个函数:
dapply (Data-Apply) 将函数应用于矩阵和 data.frames 的行或(默认)列,并且(默认)返回相同类型和具有相同属性的对象(除非每次计算的结果是原子的并且 drop = TRUE )。性能与 data.frame 列的 lapply 相当,并且比应用矩阵行或列快约 2 倍。可通过 mclapply 获得并行性(仅适用于 MAC)。
句法:
dapply(X, FUN, ..., MARGIN = 2, parallel = FALSE, mc.cores = 1L,
return = c("same", "matrix", "data.frame"), drop = TRUE)
例子:
# Apply to columns:
dapply(mtcars, log)
dapply(mtcars, sum)
dapply(mtcars, quantile)
# Apply to rows:
dapply(mtcars, sum, MARGIN = 1)
dapply(mtcars, quantile, MARGIN = 1)
# Return as matrix:
dapply(mtcars, quantile, return = "matrix")
dapply(mtcars, quantile, MARGIN = 1, return = "matrix")
# Same for matrices ...
BY 是一个 S3 泛型,用于使用向量、矩阵和 data.frame 方法进行拆分-应用-组合计算。它比 tapply、by 和 aggregate 快得多(也比 plyr 快,但在大数据上 dplyr 更快)。
句法:
BY(X, g, FUN, ..., use.g.names = TRUE, sort = TRUE,
expand.wide = FALSE, parallel = FALSE, mc.cores = 1L,
return = c("same", "matrix", "data.frame", "list"))
例子:
# Vectors:
BY(iris$Sepal.Length, iris$Species, sum)
BY(iris$Sepal.Length, iris$Species, quantile)
BY(iris$Sepal.Length, iris$Species, quantile, expand.wide = TRUE) # This returns a matrix
# Data.frames
BY(iris[-5], iris$Species, sum)
BY(iris[-5], iris$Species, quantile)
BY(iris[-5], iris$Species, quantile, expand.wide = TRUE) # This returns a wider data.frame
BY(iris[-5], iris$Species, quantile, return = "matrix") # This returns a matrix
# Same for matrices ...
分组变量列表也可以提供给 g
。
谈论性能:collapse 的主要目标是促进 R 中的高性能编程,并超越拆分-应用-组合。为此,该包具有一整套基于 C++ 的快速泛型函数:fmean
、fmedian
、fmode
、fsum
、fprod
、fsd
、fvar
、fmin
、{9 }、ffirst
、flast
、fNobs
、fNdistinct
、fscale
、fbetween
、fwithin
、fHDbetween
、fHDwithin
、flag
、fdiff
和 fgrowth
。他们在一次通过数据的过程中执行分组计算(即没有拆分和重组)。
句法:
fFUN(x, g = NULL, [w = NULL,] TRA = NULL, [na.rm = TRUE,] use.g.names = TRUE, drop = TRUE)
例子:
v <- iris$Sepal.Length
f <- iris$Species
# Vectors
fmean(v) # mean
fmean(v, f) # grouped mean
fsd(v, f) # grouped standard deviation
fsd(v, f, TRA = "/") # grouped scaling
fscale(v, f) # grouped standardizing (scaling and centering)
fwithin(v, f) # grouped demeaning
w <- abs(rnorm(nrow(iris)))
fmean(v, w = w) # Weighted mean
fmean(v, f, w) # Weighted grouped mean
fsd(v, f, w) # Weighted grouped standard-deviation
fsd(v, f, w, "/") # Weighted grouped scaling
fscale(v, f, w) # Weighted grouped standardizing
fwithin(v, f, w) # Weighted grouped demeaning
# Same using data.frames...
fmean(iris[-5], f) # grouped mean
fscale(iris[-5], f) # grouped standardizing
fwithin(iris[-5], f) # grouped demeaning
# Same with matrices ...
在包小插曲中,我提供了基准。使用快速函数编程比使用 dplyr 或 data.table 编程要快得多,尤其是在较小的数据上,而且在大数据上也是如此。
不定期副业成功案例分享
by
是纯粹的 split-lapply 而aggregate
是tapply
在他们的核心。我认为黑羊是很好的面料。aggregate
和by
的项目符号? (经过您的描述,我终于理解了它们!但是它们很常见,因此将这两个功能分开并提供一些具体示例可能会很有用。)