Суммировать несколько столбцов по группам с помощью tapply

Я хотел суммировать отдельные столбцы по группам, и моей первой мыслью было использовать 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 которые работают Первая альтернатива - это обходной путь, который объединяет tapply с apply,

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 ответа

Решение

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 

Ты ищешь by, Он использует INDEX так, как вы предполагали tapply будет по ряду.

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

Проблема с вашим использованием tapply это то, что вы индексировали data.frame по столбцу. (Так как data.frame на самом деле просто list столбцов.) Итак, tapply жаловался, что ваш индекс не соответствует длине вашего data.frame который 5.

Я посмотрел на исходный код by, как предложил EDi. Этот код был значительно сложнее, чем мое изменение в одной строке tapply, Теперь я обнаружил, что my.tapply не работает с более сложным сценарием ниже, где apples а также cherries суммируются state а также county, Если я получу 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)})
Другие вопросы по тегам