在行组上应用数据帧消费函数

  • 本文关键字:函数 数据帧 应用
  • 更新时间 :
  • 英文 :


例如,假设我有一些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"

这里,我们使用bywith在由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所提到的,还有许多其他包具有类似的数据操作功能。其中一些提供了更多的语法糖以方便使用,另一些提供了更好的优化,或者两者兼而有之。其中包括:plyrdplyrdata.table

最新更新