首页 文章

使用tapply按组分组多个列

提问于
浏览
13

我想按组分别列出各个列,我的第一个想法是使用 tapply . 但是,我不能让 tapply 工作 . 可以 tapply 用于对多列进行求和吗?如果没有,为什么不呢?

我已经广泛搜索了互联网,发现很多类似的问题早在2008年就已发布 . 但是,这些问题都没有直接得到解答 . 相反,响应总是建议使用不同的功能 .

下面是一个示例数据集,我希望按州分配苹果,按州和国家分析李子 . 在下面,我编写了许多可行的 tapply 替代品 .

在底部,我展示了对 tapply 源代码的简单修改,允许 tapply 执行所需的操作 .

不过,也许我忽略了用 tapply 执行所需操作的简单方法 . 我不是在寻找替代功能,但欢迎其他替代方案 .

鉴于我对 tapply 源代码的修改非常简单,我想知道为什么它或类似的东西还没有实现 .

谢谢你的任何建议 . 如果我的问题是重复的,我很乐意将我的问题作为对其他问题的回答 .

以下是示例数据集:

df.1 <- read.table(text = '

    state   county   apples   cherries   plums
       AA        1        1          2       3
       AA        2       10         20      30
       AA        3      100        200     300
       BB        7       -1         -2      -3
       BB        8      -10        -20     -30
       BB        9     -100       -200    -300

', header = TRUE, stringsAsFactors = FALSE)

这不起作用:

tapply(df.1, df.1$state, function(x) {colSums(x[,3:5])})

帮助页面说:

tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)

X       an atomic object, typically a vector.

我对 typically a vector 这句话感到困惑,这让我想知道是否可以使用数据框 . 我从来都不清楚 atomic object 的意思 .

以下是 tapply 的几种替代方法 . 第一种选择是将 tapplyapply 结合起来的变通方法 .

apply(df.1[,c(3:5)], 2, function(x) tapply(x, df.1$state, sum))

#    apples cherries plums
# AA    111      222   333
# BB   -111     -222  -333

with(df.1, aggregate(df.1[,3:5], data.frame(state), sum))

#   state apples cherries plums
# 1    AA    111      222   333
# 2    BB   -111     -222  -333

t(sapply(split(df.1[,3:5], df.1$state), colSums))

#    apples cherries plums
# AA    111      222   333
# BB   -111     -222  -333

t(sapply(split(df.1[,3:5], df.1$state), function(x) apply(x, 2, sum)))

#    apples cherries plums
# AA    111      222   333
# BB   -111     -222  -333

aggregate(df.1[,3:5], by=list(df.1$state), sum)

#   Group.1 apples cherries plums
# 1      AA    111      222   333
# 2      BB   -111     -222  -333

by(df.1[,3:5], df.1$state, colSums)

# df.1$state: AA
#   apples cherries    plums 
#      111      222      333 
# ------------------------------------------------------------ 
# df.1$state: BB
#   apples cherries    plums 
#     -111     -222     -333

with(df.1, 
     aggregate(x = list(apples   = apples, 
                        cherries = cherries,
                        plums    = plums), 
               by = list(state   = state), 
               FUN = function(x) sum(x)))

#   state apples cherries plums
# 1    AA    111      222   333
# 2    BB   -111     -222  -333

lapply(split(df.1, df.1$state), function(x) {colSums(x[,3:5])} )

# $AA
#   apples cherries    plums 
#      111      222      333 
#
# $BB
#   apples cherries    plums 
#     -111     -222     -333

这是 tapply 的源代码,除了我更改了行:

nx <- length(X)

至:

nx <- ifelse(is.vector(X), length(X), dim(X)[1])

此修改版本的 tapply 执行所需的操作:

my.tapply <- function (X, INDEX, FUN = NULL, ..., simplify = TRUE)
{
    FUN <- if (!is.null(FUN)) match.fun(FUN)
    if (!is.list(INDEX)) INDEX <- list(INDEX)
    nI <- length(INDEX)
    if (!nI) stop("'INDEX' is of length zero")
    namelist <- vector("list", nI)
    names(namelist) <- names(INDEX)
    extent <- integer(nI)
    nx     <- ifelse(is.vector(X), length(X), dim(X)[1])  # replaces nx <- length(X)
    one <- 1L
    group <- rep.int(one, nx) #- to contain the splitting vector
    ngroup <- one
    for (i in seq_along(INDEX)) {
    index <- as.factor(INDEX[[i]])
    if (length(index) != nx)
        stop("arguments must have same length")
    namelist[[i]] <- levels(index)#- all of them, yes !
    extent[i] <- nlevels(index)
    group <- group + ngroup * (as.integer(index) - one)
    ngroup <- ngroup * nlevels(index)
    }
    if (is.null(FUN)) return(group)
    ans <- lapply(X = split(X, group), FUN = FUN, ...)
    index <- as.integer(names(ans))
    if (simplify && all(unlist(lapply(ans, length)) == 1L)) {
    ansmat <- array(dim = extent, dimnames = namelist)
    ans <- unlist(ans, recursive = FALSE)
    } else {
    ansmat <- array(vector("list", prod(extent)),
            dim = extent, dimnames = namelist)
    }
    if(length(index)) {
        names(ans) <- NULL
        ansmat[index] <- ans
    }
    ansmat
}

my.tapply(df.1$apples, df.1$state, function(x) {sum(x)})

#  AA   BB 
# 111 -111

my.tapply(df.1[,3:4] , df.1$state, function(x) {colSums(x)})

# $AA
#   apples cherries 
#      111      222 
#
# $BB
#   apples cherries 
#     -111     -222

3 回答

  • 6

    tapply 适用于矢量,对于data.frame,您可以使用 by (这是 tapply 的包装,请查看代码):

    > by(df.1[,c(3:5)], df.1$state, FUN=colSums)
    df.1$state: AA
      apples cherries    plums 
         111      222      333 
    ------------------------------------------------------------------------------------- 
    df.1$state: BB
      apples cherries    plums 
        -111     -222     -333
    
  • 0

    你在找 by . 它按照你假设 tapply 的方式使用 INDEX .

    by(df.1, df.1$state, function(x) colSums(x[,3:5]))
    

    使用 tapply 的问题在于您要按列索引 data.frame . (因为 data.frame 实际上只是 list 列 . )所以, tapply 抱怨你的索引与你的 data.frame 的长度不匹配 .

  • 16

    正如EDi建议的那样,我查看了 by 的源代码 . 该代码比我对 tapply 中的一行更改要复杂得多 . 我现在发现 my.tapply 不适用于下面更复杂的情况,其中 applescherriesstatecounty 求和 . 如果我使用 my.tapply 来处理这种情况,我可以在以后发布代码:

    df.2 <- read.table(text = '
    
        state   county   apples   cherries   plums
           AA        1        1          2       3
           AA        1        1          2       3
           AA        2       10         20      30
           AA        2       10         20      30
           AA        3      100        200     300
           AA        3      100        200     300
    
           BB        7       -1         -2      -3
           BB        7       -1         -2      -3
           BB        8      -10        -20     -30
           BB        8      -10        -20     -30
           BB        9     -100       -200    -300
           BB        9     -100       -200    -300
    
    ', header = TRUE, stringsAsFactors = FALSE)
    
    # my function works
    
       tapply(df.2$apples  , list(df.2$state, df.2$county), function(x) {sum(x)})
    my.tapply(df.2$apples  , list(df.2$state, df.2$county), function(x) {sum(x)})
    
    # my function works
    
       tapply(df.2$cherries, list(df.2$state, df.2$county), function(x) {sum(x)})
    my.tapply(df.2$cherries, list(df.2$state, df.2$county), function(x) {sum(x)})
    
    # my function does not work
    
    my.tapply(df.2[,3:4], list(df.2$state, df.2$county), function(x) {colSums(x)})
    

相关问题