2013-07-27 24 views
12

Chciałem zsumować poszczególne kolumny według grupy i moją pierwszą myślą było użyć tapply. Jednak nie mogę uzyskać tapply do pracy. Czy można używać tapply do sumowania wielu kolumn? Jeśli nie, dlaczego nie?suma wielu kolumn według grupy z tapply

Przeszukałem internet obszernie i znalazłem wiele podobnych pytań opublikowanych już w 2008 roku. Jednak na żadne z tych pytań nie udzielono odpowiedzi bezpośrednio. Zamiast tego odpowiedzi niezmiennie sugerują użycie innej funkcji.

Poniżej znajduje się przykładowy zestaw danych, dla którego chcę podsumować jabłka według stanu, czereśnie według stanu i śliwki według stanu. Poniżej skompilowałem wiele alternatyw dla tapply, które działają pod .

U dołu pokazuję prostą modyfikację kodu źródłowego tapply, która umożliwia tapply wykonanie pożądanej operacji.

Mimo to, być może przeoczyłem prosty sposób wykonania pożądanej operacji z tapply. Nie szukam alternatywnych funkcji, chociaż mile widziane są dodatkowe alternatywy.

Biorąc pod uwagę prostotę mojej modyfikacji kodu źródłowego tapply, zastanawiam się, dlaczego to, lub coś podobnego, nie zostało jeszcze zaimplementowane.

Dziękuję za radę. Jeśli moje pytanie jest duplikatem, z przyjemnością opublikuję moje pytanie jako odpowiedź na to inne pytanie.

Oto zestaw przykład dane:

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) 

To nie działa:

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

stronie pomocy mówi:

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

X  an atomic object, typically a vector. 

Byłem zdezorientowany przez frazę typically a vector który Zastanawiam się, czy można użyć ramki danych. Nigdy nie było jasne, co oznacza atomic object.

Oto kilka alternatyw dla tapply, które działają. Pierwsza alternatywa to obejście, które łączy tapply z 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 

Oto kod źródłowy tapply chyba że zmienił linię:

nx <- length(X) 

do:

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

Ta zmodyfikowana wersja tapply wykonuje żądaną operację:

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 

Odpowiedz

16

tapply prace na wektorze, na data.frame można użyć by (co jest opakowaniem dla tapply, spójrz na kod):

> 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 
6

Szukacie by. Używa on INDEX w sposób, w jaki założyłeś tapply, według wiersza.

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

Problem z korzystaniem z tapply jest to, że zostały indeksowania kolumnydata.frame przez . (Ponieważ data.frame jest naprawdę tylko list kolumn.) Więc tapply skarżył się, że wskaźnik nie dopasować długość data.frame który wynosi 5.

0

Spojrzałem na kodzie źródłowym dla by, jak sugeruje EDI. Ten kod był znacznie bardziej złożony niż moja zmiana w stosunku do jednej linii w tapply. Zauważyłem, że my.tapply nie działa z bardziej złożonym scenariuszem poniżej, gdzie apples i cherries są zsumowane przez state i county. Jeśli otrzymam my.tapply do pracy z tą sprawą, mogę opublikować kod później:

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)})