例如,假设我有一些data.frame
df
:
df <- read.table(text = "
P Q R
c 1 10
a 1 0
a 2 0
b 2 0
b 1 10
c 2 10
b 1 0
a 2 10
",
stringsAsFactors = FALSE,
header=T)
…和以一个data.frame
为参数的函数foo
可以想象根据其中一列的值将df
拆分为较小的data.frame
,例如P
,并将foo
应用于每个较小的data.frame
。
下面我展示了我能想出的解决这个问题的最佳方案,但我怀疑已经存在更精简的解决方案来执行这种自然的操作。如果是这样,我的问题是:它们是什么?
注:我在下面展示了两个用例;第一个是我认为可以显著改进的。至于第二个问题,我认为我的解决方案可能已经是最好的了;我包含这个用例只是以防我的猜测是错误的。
我的解决方案取决于foo
是一个函数,我调用它的返回值,还是一个我只调用它的副作用。
对于前一种情况(foo
调用其值),假设foo
为:
## returns a one-row data.frame corresponding to a random row of
## dataframe
## NB: this is *just an example* for the sake of this question
foo <- function (dataframe) {
dataframe[sample(nrow(dataframe), 1), ]
}
…那么我的解决方案就是:
set.seed(0)
sapply(unique(df$P), function (value) foo(df[df$P == value, ]),
simplify = FALSE)
## $c
## P Q R
## 6 c 2 10
##
## $a
## P Q R
## 2 a 1 0
##
## $b
## P Q R
## 5 b 1 10
对于后一种情况(foo
调用其副作用),假设foo
是这样的:
## prints to stdout a one-row data.frame corresponding to a random
## row of dataframe
## NB: this is *just an example* for the sake of this question
foo <- function (dataframe) {
cat(str(dataframe[sample(nrow(dataframe), 1), ]))
}
…那么我的解决方案就是:
set.seed(0)
for (value in unique(df$P)) foo(df[df$P == value, ])
## 'data.frame': 1 obs. of 3 variables:
## $ P: chr "c"
## $ Q: int 2
## $ R: int 10
## 'data.frame': 1 obs. of 3 variables:
## $ P: chr "a"
## $ Q: int 1
## $ R: int 0
## 'data.frame': 1 obs. of 3 variables:
## $ P: chr "b"
## Q: int 1
## R: int 10
您可以使用by
函数实现这两个用例。但是,为了复制您的结果,我们将更改函数以返回或输出组的最后一行,而不是随机选择的一行。这是必要的,因为组内的行顺序由by
修改。在实际用例中,这种顺序应该无关紧要。这个只有很重要,因为您的结果依赖于一个随机数生成器来选择分组行。
在你的第一个用例中:
foo <- function (dataframe) {
dataframe[nrow(dataframe), ]
}
out1 <- sapply(unique(df$P), function (value) foo(df[df$P == value, ]),
simplify = FALSE)
结果out1
是一个list
:
str(out1) ## this displays the structure of the out1 object
##List of 3
## $ c:'data.frame': 1 obs. of 3 variables:
## ..$ P: chr "c"
## ..$ Q: int 2
## ..$ R: int 10
## $ a:'data.frame': 1 obs. of 3 variables:
## ..$ P: chr "a"
## ..$ Q: int 2
## ..$ R: int 10
## $ b:'data.frame': 1 obs. of 3 variables:
## ..$ P: chr "b"
## ..$ Q: int 1
## ..$ R: int 0
我们可以使用by
获得相同的结果,它返回一个by
类的对象,它是list
:
by.out1 <- with(df, by(df, P, foo))
str(by.out1)
##List of 3
## $ a:'data.frame': 1 obs. of 3 variables:
## ..$ P: chr "a"
## ..$ Q: int 2
## ..$ R: int 10
## $ b:'data.frame': 1 obs. of 3 variables:
## ..$ P: chr "b"
## ..$ Q: int 1
## ..$ R: int 0
## $ c:'data.frame': 1 obs. of 3 variables:
## ..$ P: chr "c"
## ..$ Q: int 2
## ..$ R: int 10
## - attr(*, "dim")= int 3
## - attr(*, "dimnames")=List of 1
## ..$ P: chr [1:3] "a" "b" "c"
## - attr(*, "call")= language by.data.frame(data = df, INDICES = P, FUN = foo)
## - attr(*, "class")= chr "by"
这里,我们使用by
和with
在由df
构建的环境中执行by
。这允许我们通过名称指定df
的列(例如P
),而不需要引号。
对于第二个用例(通过cat
显示到控制台):
foo <- function (dataframe) {
cat(str(dataframe[nrow(dataframe), ]))
}
for (value in unique(df$P)) foo(df[df$P == value, ])
##'data.frame': 1 obs. of 3 variables:
## $ P: chr "c"
## $ Q: int 2
## $ R: int 10
##'data.frame': 1 obs. of 3 variables:
## $ P: chr "a"
## $ Q: int 2
## $ R: int 10
##'data.frame': 1 obs. of 3 variables:
## $ P: chr "b"
## $ Q: int 1
## $ R: int 0
同样,我们可以通过by
:
获得相同的结果。with(df, by(df, P, foo))
##'data.frame': 1 obs. of 3 variables:
## $ P: chr "a"
## $ Q: int 2
## $ R: int 10
##'data.frame': 1 obs. of 3 variables:
## $ P: chr "b"
## $ Q: int 1
## $ R: int 0
##'data.frame': 1 obs. of 3 variables:
## $ P: chr "c"
## $ Q: int 2
## $ R: int 10
by
函数在base
R包中。正如Dave2e所提到的,还有许多其他包具有类似的数据操作功能。其中一些提供了更多的语法糖以方便使用,另一些提供了更好的优化,或者两者兼而有之。其中包括:plyr
、dplyr
和data.table
。