web-dev-qa-db-ja.com

グループごとに複数の列を合計する

個々の列をグループごとに合計したかったので、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
13
Mark Miller

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 
16
EDi

byを探しています。これは、INDEXを、tapplyが行ごとに想定した方法で使用します。

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

tapplyを使用する際の問題は、_data.frame_をcolumnで索引付けしていたことです。 (_data.frame_は実際には列のlistにすぎないため。)したがって、tapplyは、インデックスが_data.frame_の長さ(5)と一致しないと不平を言いました。

6
nograpes

EDiが示唆したように、byのソースコードを確認しました。そのコードは、tapplyの1行に対する私の変更よりもかなり複雑でした。 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)})
0
Mark Miller