【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
每当我想在R中做“ map” py任务时,我通常都会尝试在apply
系列中使用一个函数。
但是,我从未完全理解它们之间的区别-{ sapply
, lapply
等}如何将函数应用于输入/分组输入,输出将是什么样,甚至输入是什么-所以我经常只是遍历所有这些,直到得到想要的东西。
谁能解释什么时候使用哪一个?
我目前(可能不正确/不完整)的理解是...
sapply(vec, f)
:输入是向量。 输出是一个向量/矩阵,其中元素i
为f(vec[i])
,如果f
具有多元素输出,则为您提供矩阵lapply(vec, f)
:与sapply
相同,但是输出是一个列表?-
apply(matrix, 1/2, f)
:输入是一个矩阵。 输出是一个向量,其中元素i
为f(矩阵的行/列i) -
tapply(vector, grouping, f)
:输出是一个矩阵/数组,其中矩阵/数组中的元素是向量分组g
处的f
值,并且g
被推到行/列名 -
by(dataframe, grouping, f)
:令g
为一个分组。 将f
应用于组/数据框的每一列。 在每列漂亮地打印分组和f
的值。 -
aggregate(matrix, grouping, f)
:类似于by
,但是aggregate不会将输出漂亮地打印by
,而是将所有内容粘贴到数据框中。
侧问题:我还没有学会plyr或重塑-将plyr
或reshape
取代所有这些完全?
#1楼
首先从乔兰(Joran)的出色回答开始 -值得怀疑的事情会更好。
然后,以下助记符可能有助于记住它们之间的区别。 尽管有些显而易见,但有些则不那么明显---对于这些,您会在Joran的讨论中找到正当理由。
助记符
-
lapply
是一个列表应用,它作用于列表或向量并返回一个列表。 -
sapply
是一个简单的lapply
(函数默认情况下会默认返回向量或矩阵) -
vapply
是已验证的应用 (允许预先指定返回对象类型) -
rapply
是对嵌套列表(即列表中的列表)的递归应用 -
tapply
是带标签的应用,其中标签标识子集 -
apply
是泛型的 :将函数应用于矩阵的行或列(或更一般地说,应用于数组的维)
建立正确的背景
如果使用apply
家庭仍然对您感到有些陌生,则可能是您缺少关键观点。
这两篇文章会有所帮助。 它们提供了必要的背景来激发由apply
功能家族提供的功能编程技术 。
Lisp的用户将立即认识到该范例。 如果您不熟悉Lisp,一旦对FP有所了解,您将获得在R中使用的强大观点-并且apply
会更有意义。
#2楼
这也许是值得一提的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
## ...
#3楼
因为我意识到这篇文章的(非常出色的)答案缺乏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
属性,分别by
cb
和ct
array
。
正如我已经说过的,当我们不能使用tapply
时, by
的力量就会产生。 以下代码是一个示例:
tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) :
arguments must have same length
R说参数必须具有相同的长度,比如说“我们要计算沿Species
因子的iris
中所有变量的summary
”:但是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
可以 (不是强制性)是一个列表,并且aggregate
的输出是一个数据帧,而tapply
的一个是一个array
。
aggregate
的强大之处在于它可以使用subset
参数轻松处理数据的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
,因为aggregate
函数的formula
方法默认情况下具有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)
先前的代码实现了相同的目标和结果,在某些时候,使用哪种工具只是个人喜好和需求的问题。 就子集而言,前两个对象有非常不同的需求。
#4楼
有很多很棒的答案讨论了每个功能用例的差异。 没有答案讨论性能差异。 这是合理的,因为各种功能期望各种输入并产生各种输出,但是大多数功能具有按系列/组进行评估的通用目标。 我的答案将集中在性能上。 由于上述原因,向量中的输入创建已包含在时序中,因此未测量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
#5楼
在旁注中,这是各种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到Plyr文件中:
相关职能
tapply
和sweep
在没有相应的功能plyr
,并保持有效。merge
对于将摘要与原始数据结合起来很有用。
来源:oschina
链接:https://my.oschina.net/u/3797416/blog/3142798