Суммировать несколько столбцов по группам с помощью 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)})