グループ化関数(tapply、by、aggregate)および* applyファミリー


1040

Rで何か「マップ」pyを実行したいときはいつでも、通常はapplyファミリーの関数を使用しようとします。

しかし、私はそれらの違いを完全には理解していません-どのように{ sapplylapplyなど}出力がどのように見えるか、または入力が可能であっても、何の入力/グループ化された入力に関数を適用する-多くの場合、私はので、私が欲しいものを手に入れるまでそれらすべてを通り抜けてください。

誰かがいつどのように使用するか説明できますか?

私の現在の(おそらく正しくない/不完全な)理解は...

  1. sapply(vec, f):入力はベクトルです。出力はベクトル/行列で、要素if(vec[i])fあり、複数要素の出力がある場合に行列を提供します

  2. lapply(vec, f): と同じ sapplyですが、出力はリストですか?

  3. apply(matrix, 1/2, f):入力は行列です。出力はベクトルで、要素はiはf(行列の行/列i)です。
  4. tapply(vector, grouping, f):出力は行列/配列であり、行列/配列の要素はベクトルのfグループ化gでの値であり、g、行/列名にプッシュされます
  5. by(dataframe, grouping, f)gグループ化しましょう。fグループ/データフレームの各列に適用されます。f各列のグループと値をきれいに出力します。
  6. aggregate(matrix, grouping, f):に似てbyいますが、出力をきれいに出力する代わりに、aggregateはすべてをデータフレームに貼り付けます。

サイド質問:私はまだplyrまたはリシェイプを学んでいない-でしょうplyrreshape、完全にこれらのすべてを置き換えますか?


33
あなたの側の質問に:多くの事柄のためにplyrは*apply()andの直接の代替品ですby。plyr(少なくとも私にとっては)はずっと一貫しているように見えます。というのも、私は常にそれが期待するデータ形式と正確に何を出力するかを正確に知っているからです。それは私を多くの面倒から救います。
JD Long

12
また、以下を追加することをお勧めします:doByの選択と適用機能data.table
イテレータ

7
sapply出力にをlapply追加するだけsimplify2arrayです。apply原子ベクトルに強制変換しますが、出力はベクトルまたはリストにすることができます。byデータフレームをサブデータフレームに分割fしますが、列で個別に使用することはありません。「data.frame」クラスのメソッドがある場合のみ、fによって列ごとに適用される可能性がありますbyaggregateジェネリックであるため、最初の引数のクラスごとに異なるメソッドが存在します。
IRTFM 2013年

8
記憶法:lは「リスト」、sは「簡略化」、tは「タイプごと」(グループ化の各レベルはタイプ)
Lutz Prechelt

パッケージRfastには、次のようないくつかの関数も存在します。eachcol.apply、apply.conditionなど、Rの同等の関数よりも高速です
Stefanos

回答:


1330

Rには多くの* apply関数があり、ヘルプファイル(など?apply)に適切に記述されています。ただし、それらの数は十分にありますが、useRの開始時に、どちらが自分の状況に適しているのかを判断したり、すべてを覚えたりすることさえ難しい場合があります。「ここでは* apply関数を使用する必要がある」という一般的な感覚があるかもしれませんが、最初はすべてをまっすぐにするのは難しい場合があります。

* applyファミリーの機能の多くは非常に人気があるため、他の回答で述べられているように plyrパッケージで基本機能は有用であり、知っておく価値があります。

この回答は、新しいuseRの特定の問題に対する正しい* apply関数に誘導するのに役立つ、一種の道標として機能することを目的としています。これは、Rのドキュメントを単に逆流したり置き換えたりするためのものではありません。この答えが、どの*適用機能が自分の状況に合っているかを判断するのに役立ち、それをさらに調査するのはあなた次第です。1つの例外を除いて、パフォーマンスの違いは解決されません。

  • 適用 - あなたが行または列行列の(と高次元類似体)に関数を適用したい場合には、データフレームは最初に行列に強制変換されるため、通常はお勧めできません。

    # Two dimensional matrix
    M <- matrix(seq(1,16), 4, 4)
    
    # apply min to rows
    apply(M, 1, min)
    [1] 1 2 3 4
    
    # apply max to columns
    apply(M, 2, max)
    [1]  4  8 12 16
    
    # 3 dimensional array
    M <- array( seq(32), dim = c(4,4,2))
    
    # Apply sum across each M[*, , ] - i.e Sum across 2nd and 3rd dimension
    apply(M, 1, sum)
    # Result is one-dimensional
    [1] 120 128 136 144
    
    # Apply sum across each M[*, *, ] - i.e Sum across 3rd dimension
    apply(M, c(1,2), sum)
    # Result is two-dimensional
         [,1] [,2] [,3] [,4]
    [1,]   18   26   34   42
    [2,]   20   28   36   44
    [3,]   22   30   38   46
    [4,]   24   32   40   48

    あなたは、2D行列の行/列手段または合計をしたい場合は、高度に最適化され、雷、迅速に調査するようにしてくださいcolMeansrowMeanscolSumsrowSums

  • lapply - あなたは順番にリストの各要素に関数を適用し、リストの背中を取得したいときに。

    これは、他の* apply関数の多くの主力です。それらのコードの皮をむくと、lapplyその下にあることがよくあります。

    x <- list(a = 1, b = 1:3, c = 10:100) 
    lapply(x, FUN = length) 
    $a 
    [1] 1
    $b 
    [1] 3
    $c 
    [1] 91
    lapply(x, FUN = sum) 
    $a 
    [1] 1
    $b 
    [1] 6
    $c 
    [1] 5005
  • sapply - あなたは順番にリストの各要素に関数を適用したいのですが、あなたが欲しいベクトルバックではなく、リストを。

    入力していることに気づいたらunlist(lapply(...))、立ち止まって検討してください sapply

    x <- list(a = 1, b = 1:3, c = 10:100)
    # Compare with above; a named vector, not a list 
    sapply(x, FUN = length)  
    a  b  c   
    1  3 91
    
    sapply(x, FUN = sum)   
    a    b    c    
    1    6 5005 

    より高度な使用法ではsapply、必要に応じて結果を多次元配列に強制変換しようとします。たとえば、関数が同じ長さのベクトルを返す場合、sapplyはそれらを行列の列として使用します。

    sapply(1:5,function(x) rnorm(3,x))

    関数が2次元行列を返す場合、sapply基本的に同じことを行い、返された各行列を単一の長いベクトルとして扱います。

    sapply(1:5,function(x) matrix(x,2,2))

    を指定しない限りsimplify = "array"、個々の行列を使用して多次元配列を構築します。

    sapply(1:5,function(x) matrix(x,2,2), simplify = "array")

    もちろん、これらの各動作は、同じ長さまたは次元のベクトルまたは行列を返す関数に依存しています。

  • vapply - あなたが使用したいときsapplyおそらくあなたのコードのうち、いくつかのより多くの速度を絞る必要があります。

    以下のためにvapply、あなたは基本的にRにいくつかの時間の強制変換返された値は、単一の原子のベクトルに収まるように保存することができ一種の事のあなたの関数が返すものの例を与えます。

    x <- list(a = 1, b = 1:3, c = 10:100)
    #Note that since the advantage here is mainly speed, this
    # example is only for illustration. We're telling R that
    # everything returned by length() should be an integer of 
    # length 1. 
    vapply(x, FUN = length, FUN.VALUE = 0L) 
    a  b  c  
    1  3 91
  • mapply - のようにベクトル/配列に結果を強制するなど、あなたはいくつかのデータ構造(例えばベクトル、リスト)があり、それぞれの第一の要素に関数を適用したいときのために、それぞれの後、第二の要素sapply

    これは、関数が複数の引数を受け入れる必要があるという意味で多変量です。

    #Sums the 1st elements, the 2nd elements, etc. 
    mapply(sum, 1:5, 1:5, 1:5) 
    [1]  3  6  9 12 15
    #To do rep(1,4), rep(2,3), etc.
    mapply(rep, 1:4, 4:1)   
    [[1]]
    [1] 1 1 1 1
    
    [[2]]
    [1] 2 2 2
    
    [[3]]
    [1] 3 3
    
    [[4]]
    [1] 4
  • Map - with のラッパー。リストを返すことが保証されています。mapplySIMPLIFY = FALSE

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
  • rapply - あなたは、各要素に関数を適用したいときのために、ネストされたリスト再帰的、構造。

    珍しいことのいくつかの考えをあなたに与えるためにrapply、私はこの回答を最初に投稿したときにそれを忘れていました!もちろん、多くの人が使っていると思いますが、YMMV。rapply適用するユーザー定義関数で最もよく示されています:

    # Append ! to string, otherwise increment
    myFun <- function(x){
        if(is.character(x)){
          return(paste(x,"!",sep=""))
        }
        else{
          return(x + 1)
        }
    }
    
    #A nested list structure
    l <- list(a = list(a1 = "Boo", b1 = 2, c1 = "Eeek"), 
              b = 3, c = "Yikes", 
              d = list(a2 = 1, b2 = list(a3 = "Hey", b3 = 5)))
    
    
    # Result is named vector, coerced to character          
    rapply(l, myFun)
    
    # Result is a nested list like l, with values altered
    rapply(l, myFun, how="replace")
  • tapply - あなたがに関数を適用したいときのためにサブセットベクトルのサブセットとは、いくつかの他のベクター、通常の要因によって定義されています。

    ある種の* applyファミリーの黒い羊。ヘルプファイルで「不規則な配列」という語句を使用するのは少しわかりにくいかもしれませんが、実際は非常に簡単です。

    ベクトル:

    x <- 1:20

    グループを定義する(同じ長さの!)係数:

    y <- factor(rep(letters[1:5], each = 4))

    x定義された各サブグループ内の値を合計しますy

    tapply(x, y, sum)  
     a  b  c  d  e  
    10 26 42 58 74 

    より複雑な例は、サブグループがいくつかの要因のリストの一意の組み合わせによって定義される場合に処理できます。tapplyスプリット適用-組み合わせるRに共通する機能を(と精神に類似しているaggregatebyaveddplyしたがってその黒羊の状態、等)。


32
あなたがそれbyが純粋なスプリットラッププライでaggregateありtapply、彼らの核心にあることがわかると信じてください。黒羊は優れた生地を作ると思います。
IRTFM

21
素晴らしい反応!これは公式のRドキュメンテーションの一部であるべきです:) 一つの小さな提案:おそらく使用上のいくつかの箇条書きを追加aggregateしてby、同様?(説明の後でやっと理解しました!しかし、それらはかなり一般的であるため、これらの2つの関数を分離していくつかの具体的な例を示すと役立つ場合があります。)
grautur

3
@grautur(a)長すぎたり、(b)ドキュメントが書き直されたりしないように、私はこの回答から積極的に切り詰めていました。私はしばらくことを決めたaggregatebyなどはあなたがそれらを使用してアプローチの方法は、彼らが別の答えにまとめれるべきであることをユーザーの視点は異なる十分です、*関数を適用するに基づいています。時間があれば、それを試してみるか、他の誰かが私を打ち負かして私の投票を獲得するかもしれません。
joran

4
また、?Map親戚としてmapply
baptiste

3
@jsanders-私はまったくそれに同意しません。data.framesはRの中心部分であり、listオブジェクトはlapply特にを使用して頻繁に操作されます。また、従来の長方形のデータセットで多くのタイプのベクトル/因子をグループ化するためのコンテナーとしても機能します。一方でdata.tableそしてplyrいくつかは、より快適見つけるかもしれないという構文の特定のタイプを追加するかもしれない、彼らは拡張とに作用しているdata.frameそれぞれの。
thelatemail 2014

191

余談ですが、さまざまなplyr関数が基本*apply関数にどのように対応しているかを示します(イントロからplyrのWebページhttp://had.co.nz/plyr/の plyrドキュメントまで)。

Base function   Input   Output   plyr function 
---------------------------------------
aggregate        d       d       ddply + colwise 
apply            a       a/l     aaply / alply 
by               d       l       dlply 
lapply           l       l       llply  
mapply           a       a/l     maply / mlply 
replicate        r       a/l     raply / rlply 
sapply           l       a       laply 

の目標の1つは、plyr関数ごとに一貫した命名規則を提供し、関数名で入力および出力のデータ型をエンコードすることです。また、からの出力をdlply()簡単に渡してldply()有用な出力を生成できるなど、出力の一貫性も提供します。

概念的には、学習plyrは基本*apply機能を理解することと同じくらい難しいことではありません。

plyrそしてreshape機能は私の毎日の使用でこれらの機能のほとんどすべてを置き換えました。しかし、Intro to Plyrドキュメントからも:

関連する関数tapplysweepに該当する機能を持っていないplyr、かつ有用なまま。merge概要を元のデータと組み合わせる場合に便利です。


13
Rを一から学習し始めたとき、プライの*apply()ファミリよりもplyrの方がはるかに学習しやすいことに気付きました。私は、ddply()SQL集計関数に精通していたので、非常に直感的でした。ddply()は、多くの問題を解決するための私のハンマーになりました。そのいくつかは、他のコマンドで解決することができたでしょう。
JD Long

1
plyr関数の背後にある概念は関数に似て*applyいると思います。そのため、1つを実行できれば、もう1つを実行できますが、plyr関数の方が覚えやすいです。しかし、私はddply()ハンマーに完全に同意します!
JoFrhwld 2010

1
plyrパッケージには、join()マージと同様のタスクを実行する機能があります。おそらく、それはplyrのコンテキストで言及するほうがましです。
marbel 14年

貧しい人々、忘れられたものを忘れないようにしましょうeapply
JDL '25

一般的には良い答えですが、の有用性vapplyと欠点を軽視すると思いますsapply。の主な利点vapplyは、出力のタイプと長さを強制することです。そのため、正確な期待出力または情報エラーが発生します。一方、sapply常に明白であるとは限らないルールに従って出力を単純化しようとし、そうでなければリストにフォールバックします。たとえば、これにより生成される出力のタイプを予測してみますsapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1))。どうsapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)ですか?
Alexey Shiklomanov

133

http://www.slideshare.net/hadley/plyr-one-data-analytic-strategyのスライド21から:

適用、sapply、lapply、by、aggregate

(うまくいけばapply、@ Hadleyに対応し、@ Hadley aaplyなどにaggregate対応することddplyは明らかです。同じスライド共有のスライド20は、この画像から取得しない場合に明確になります。)

(左側が入力、上部が出力です)


4
スライドにタイプミスはありますか?左上のセルは上向きである必要があります
JHowIX

100

まず、ヨランの優れた答えから始めましょう。

次に、以下のニーモニックは、それぞれの違いを思い出すのに役立ちます。明白なものもあれば、それほど明確でないものもあります。これらについては、ヨランの議論で正当化されます。

ニーモニック

  • lapplyあるリストは、、リストまたはベクトルに作用し、リストを返す適用します。
  • sapplyある簡単な lapply(可能な場合、ベクトルまたは行列を返す関数へのデフォルト)
  • vapplyされて適用されます検証(リターンオブジェクトタイプを事前に指定することを可能にします)
  • rapplyある再帰リスト内のネストされたリスト、すなわちリストに適用されます
  • tapplyタグ付けされましたタグのサブセットを識別する場合適用
  • apply is generic:行列の行または列(または、より一般的には、配列の次元)に関数を適用します

適切な背景の構築

applyそれでもファミリーを使用することに少し異質感がある場合は、重要な視点が欠けている可能性があります。

これら2つの記事が役立ちます。それらは、関数applyファミリーによって提供されている関数型プログラミング手法を動機付けるために必要な背景を提供します。

Lispのユーザーはパラダイムをすぐに認識します。Lispに慣れていない場合は、FPについて理解すれば、Rで使用するための強力な視点が得られますapply


51

この投稿の(非常に優れた)回答の不足byaggregate説明に気付いたので。これが私の貢献です。

沿って

byドキュメントに記載されているように関数は、のための「ラッパー」として、しかし可能tapply。の力は、処理できないbyタスクを計算するときに発生しtapplyます。1つの例は次のコードです。

ct <- tapply(iris$Sepal.Width , iris$Species , summary )
cb <- by(iris$Sepal.Width , iris$Species , summary )

 cb
iris$Species: setosa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.300   3.200   3.400   3.428   3.675   4.400 
-------------------------------------------------------------- 
iris$Species: versicolor
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.525   2.800   2.770   3.000   3.400 
-------------------------------------------------------------- 
iris$Species: virginica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   2.800   3.000   2.974   3.175   3.800 


ct
$setosa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.300   3.200   3.400   3.428   3.675   4.400 

$versicolor
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.525   2.800   2.770   3.000   3.400 

$virginica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   2.800   3.000   2.974   3.175   3.800 

我々はこれらの2つのオブジェクトを印刷し、場合ctcb、私たちは「本質的に」同じ結果を持っており、唯一の違いはどのように示されており、さまざまでありclass、それぞれの属性byのためにcbarrayのためにct

私が言ったように、私byたちが使用できないときにパワーが発生しますtapply。次のコードは1つの例です。

 tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) : 
  arguments must have same length

Rは、引数は同じ長さでなければならない、「summaryすべての変数のをiris因子に沿って計算したい」と言いSpeciesますが、Rは処理方法がわからないため、それを行うことができません。

by関数Rを使用するdata frameと、クラスの特定のメソッドをディスパッチしsummary、最初の引数(および型も)の長さが異なる場合でも関数を機能させます。

bywork <- by(iris, iris$Species, summary )

bywork
iris$Species: setosa
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200   versicolor: 0  
 Median :5.000   Median :3.400   Median :1.500   Median :0.200   virginica : 0  
 Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246                  
 3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300                  
 Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600                  
-------------------------------------------------------------- 
iris$Species: versicolor
  Sepal.Length    Sepal.Width     Petal.Length   Petal.Width          Species  
 Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000   setosa    : 0  
 1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200   versicolor:50  
 Median :5.900   Median :2.800   Median :4.35   Median :1.300   virginica : 0  
 Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326                  
 3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500                  
 Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800                  
-------------------------------------------------------------- 
iris$Species: virginica
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400   setosa    : 0  
 1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800   versicolor: 0  
 Median :6.500   Median :3.000   Median :5.550   Median :2.000   virginica :50  
 Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026                  
 3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300                  
 Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500     

それは確かに機能し、その結果は非常に驚くべきものです。これは、クラスのオブジェクトであり、byそれに沿ってSpecies(たとえば、それぞれについて)summary各変数のを計算します。

最初の引数がのdata frame場合、ディスパッチされた関数には、そのオブジェクトクラスのメソッドが必要です。たとえば、mean関数でこのコードを使用すると、まったく意味のないこのコードが生成されます。

 by(iris, iris$Species, mean)
iris$Species: setosa
[1] NA
------------------------------------------- 
iris$Species: versicolor
[1] NA
------------------------------------------- 
iris$Species: virginica
[1] NA
Warning messages:
1: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA
2: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA
3: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA

集計

aggregatetapplyこのような使い方をすれば、別の使い方として見ることができます。

at <- tapply(iris$Sepal.Length , iris$Species , mean)
ag <- aggregate(iris$Sepal.Length , list(iris$Species), mean)

 at
    setosa versicolor  virginica 
     5.006      5.936      6.588 
 ag
     Group.1     x
1     setosa 5.006
2 versicolor 5.936
3  virginica 6.588

2つの直接的な違いは、の2番目の引数はリストでaggregate なければならないtapply (必須ではない)リストにすることができること、およびの出力aggregateはデータフレームである一方tapplyarray

の威力はaggregatesubset引数を使用してデータのサブセットを簡単に処理できることと、tsオブジェクト用のメソッドを備えていることformulaです。

これらの要素により、状況に応じaggregateて簡単に操作できるようになりtapplyます。ここにいくつかの例があります(ドキュメントで利用可能):

ag <- aggregate(len ~ ., data = ToothGrowth, mean)

 ag
  supp dose   len
1   OJ  0.5 13.23
2   VC  0.5  7.98
3   OJ  1.0 22.70
4   VC  1.0 16.77
5   OJ  2.0 26.06
6   VC  2.0 26.14

同じことを実現できtapplyますが、構文が少し難しく、出力(状況によっては)が読みにくくなります。

att <- tapply(ToothGrowth$len, list(ToothGrowth$dose, ToothGrowth$supp), mean)

 att
       OJ    VC
0.5 13.23  7.98
1   22.70 16.77
2   26.06 26.14

使用できない、byまたはtapply使用しなければならない場合もありますaggregate

 ag1 <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, mean)

 ag1
  Month    Ozone     Temp
1     5 23.61538 66.73077
2     6 29.44444 78.22222
3     7 59.11538 83.88462
4     8 59.96154 83.96154
5     9 31.44828 76.89655

tapply1回の呼び出しで前の結果を取得することはできませんが、Month各要素の平均を計算してからそれらを組み合わせる必要があります(関数na.rm = TRUEformulaメソッドにはaggregateデフォルトでがあるため、を呼び出す必要があることにも注意してくださいna.action = na.omit)。

ta1 <- tapply(airquality$Ozone, airquality$Month, mean, na.rm = TRUE)
ta2 <- tapply(airquality$Temp, airquality$Month, mean, na.rm = TRUE)

 cbind(ta1, ta2)
       ta1      ta2
5 23.61538 65.54839
6 29.44444 79.10000
7 59.11538 83.90323
8 59.96154 83.96774
9 31.44828 76.90000

しながら、by私たちは実際には次の関数呼び出しはエラーを返します(ただし、ほとんどの場合、それが供給機能に関連していることを達成することはできませんmean)。

by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)

その他の場合、結果は同じであり、違いはクラス(そして、それがどのように表示/印刷されるかだけでなく、例、それをサブセット化する方法)だけです。

byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)

前のコードは同じ目的と結果を達成しました。ある時点で、どのツールを使用するかは個人の好みとニーズの問題にすぎません。前の2つのオブジェクトには、サブセット化の点でニーズが大きく異なります。


すでに述べたように、byのパワーは、tapplyを使用できないときに発生します。次のコードは1つの例です。これは、これまでに使用した単語です。そして、要約を計算する例を示しました。まあ要約統計量は、それが清掃必要があることだけを計算することができることを言うことができます:例えば data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))、これはtapplyの使用である. With the right splitting there is nothing you cant do with tapply . The only thing is it returns a matrix. Please be careful by saying we cant use tapply`
Onyambu

35

各関数のユースケースの違いを説明する素晴らしい答えがたくさんあります。パフォーマンスの違いについては、どの回答でも説明されていません。これは合理的な原因です。さまざまな関数がさまざまな入力を期待し、さまざまな出力を生成しますが、それらのほとんどはシリーズ/グループで評価する一般的な共通の目的を持っています。私の答えはパフォーマンスに焦点を当てます。上記により、ベクトルからの入力作成はタイミングに含まれ、apply関数も測定されません。

2つの異なる機能を一度にテストsumlengthました。テストされたボリュームは、入力で50M、出力で50Kです。私はまた、広く疑問を頼まれた時に使用されていなかった2つの現在人気のパッケージが含まれ、しているdata.tabledplyr。あなたが良いパフォーマンスを目指しているなら、どちらも間違いなく見る価値があります。

library(dplyr)
library(data.table)
set.seed(123)
n = 5e7
k = 5e5
x = runif(n)
grp = sample(k, n, TRUE)

timing = list()

# sapply
timing[["sapply"]] = system.time({
    lt = split(x, grp)
    r.sapply = sapply(lt, function(x) list(sum(x), length(x)), simplify = FALSE)
})

# lapply
timing[["lapply"]] = system.time({
    lt = split(x, grp)
    r.lapply = lapply(lt, function(x) list(sum(x), length(x)))
})

# tapply
timing[["tapply"]] = system.time(
    r.tapply <- tapply(x, list(grp), function(x) list(sum(x), length(x)))
)

# by
timing[["by"]] = system.time(
    r.by <- by(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)

# aggregate
timing[["aggregate"]] = system.time(
    r.aggregate <- aggregate(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)

# dplyr
timing[["dplyr"]] = system.time({
    df = data_frame(x, grp)
    r.dplyr = summarise(group_by(df, grp), sum(x), n())
})

# data.table
timing[["data.table"]] = system.time({
    dt = setnames(setDT(list(x, grp)), c("x","grp"))
    r.data.table = dt[, .(sum(x), .N), grp]
})

# all output size match to group count
sapply(list(sapply=r.sapply, lapply=r.lapply, tapply=r.tapply, by=r.by, aggregate=r.aggregate, dplyr=r.dplyr, data.table=r.data.table), 
       function(x) (if(is.data.frame(x)) nrow else length)(x)==k)
#    sapply     lapply     tapply         by  aggregate      dplyr data.table 
#      TRUE       TRUE       TRUE       TRUE       TRUE       TRUE       TRUE 

# print timings
as.data.table(sapply(timing, `[[`, "elapsed"), keep.rownames = TRUE
              )[,.(fun = V1, elapsed = V2)
                ][order(-elapsed)]
#          fun elapsed
#1:  aggregate 109.139
#2:         by  25.738
#3:      dplyr  18.978
#4:     tapply  17.006
#5:     lapply  11.524
#6:     sapply  11.326
#7: data.table   2.686

dplyrがapplt関数よりも低いのは正常ですか?
モスタファ2016年

1
@DimitriPetrenko私はそうは思いません、なぜここにあるのかわかりません。関係する要素はたくさんあるので、自分のデータに対してテストするのが最善です。
jangorecki

28

ここでのすべての素晴らしい答えにもかかわらず、言及するに値する2つの基本機能がさらにあります。それは、便利なouter機能と不明瞭なeapply機能です。

アウター

outerありふれた機能として隠されている非常に便利な機能です。outerその説明のヘルプを読んだ場合:

The outer product of the arrays X and Y is the array A with dimension  
c(dim(X), dim(Y)) where element A[c(arrayindex.x, arrayindex.y)] =   
FUN(X[arrayindex.x], Y[arrayindex.y], ...).

これは、線形代数型のものに対してのみ有用であるように見えます。ただし、mapply2つの入力ベクトルに関数を適用するのと同じように使用できます。違いはmapply、最初の2つの要素に関数を適用し、次に2番目の2つouterに適用するなどです。一方、最初のベクトルの1つの要素と2番目の要素の1つの要素のすべての組み合わせに関数を適用します。例えば:

 A<-c(1,3,5,7,9)
 B<-c(0,3,6,9,12)

mapply(FUN=pmax, A, B)

> mapply(FUN=pmax, A, B)
[1]  1  3  6  9 12

outer(A,B, pmax)

 > outer(A,B, pmax)
      [,1] [,2] [,3] [,4] [,5]
 [1,]    1    3    6    9   12
 [2,]    3    3    6    9   12
 [3,]    5    5    6    9   12
 [4,]    7    7    7    9   12
 [5,]    9    9    9    9   12

私はこれを、値のベクトルと条件のベクトルがあり、どの値がどの条件を満たすかを確認したいときに使用しました。

当てはめる

eapplylapplyリスト内のすべての要素に関数を適用するのではなく、環境内のすべての要素に関数を適用することを除いて、似ています。たとえば、グローバル環境でユーザー定義関数のリストを検索する場合は、次のようにします。

A<-c(1,3,5,7,9)
B<-c(0,3,6,9,12)
C<-list(x=1, y=2)
D<-function(x){x+1}

> eapply(.GlobalEnv, is.function)
$A
[1] FALSE

$B
[1] FALSE

$C
[1] FALSE

$D
[1] TRUE 

率直に言って、私はこれをあまり使用しませんが、多くのパッケージを構築している場合や、多くの環境を作成している場合は、便利な場合があります。


25

それは多分言及する価値がありaveます。avetapplyフレンドリーないとこです。データフレームに直接プラグインできる形式で結果を返します。

dfr <- data.frame(a=1:20, f=rep(LETTERS[1:5], each=4))
means <- tapply(dfr$a, dfr$f, mean)
##  A    B    C    D    E 
## 2.5  6.5 10.5 14.5 18.5 

## great, but putting it back in the data frame is another line:

dfr$m <- means[dfr$f]

dfr$m2 <- ave(dfr$a, dfr$f, FUN=mean) # NB argument name FUN is needed!
dfr
##   a f    m   m2
##   1 A  2.5  2.5
##   2 A  2.5  2.5
##   3 A  2.5  2.5
##   4 A  2.5  2.5
##   5 B  6.5  6.5
##   6 B  6.5  6.5
##   7 B  6.5  6.5
##   ...

基本パッケージには何ものように動作することはありませんave(と全体のデータフレームのbyようなものですtapplyデータフレームの場合)。しかし、それをファッジすることができます:

dfr$foo <- ave(1:nrow(dfr), dfr$f, FUN=function(x) {
    x <- dfr[x,]
    sum(x$m*x$m2)
})
dfr
##     a f    m   m2    foo
## 1   1 A  2.5  2.5    25
## 2   2 A  2.5  2.5    25
## 3   3 A  2.5  2.5    25
## ...

12

私は最近、かなり便利なsweep機能を発見し、完全を期すためにここに追加しました。

掃く

基本的な考え方は、配列を行方向または列方向にスイープし、変更された配列を返すことです。例はこれを明確にします(ソース:datacamp):

マトリックスがあり、それを列ごとに標準化したいとします。

dataPoints <- matrix(4:15, nrow = 4)

# Find means per column with `apply()`
dataPoints_means <- apply(dataPoints, 2, mean)

# Find standard deviation with `apply()`
dataPoints_sdev <- apply(dataPoints, 2, sd)

# Center the points 
dataPoints_Trans1 <- sweep(dataPoints, 2, dataPoints_means,"-")
print(dataPoints_Trans1)
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5
# Return the result
dataPoints_Trans1
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5
# Normalize
dataPoints_Trans2 <- sweep(dataPoints_Trans1, 2, dataPoints_sdev, "/")

# Return the result
dataPoints_Trans2
##            [,1]       [,2]       [,3]
## [1,] -1.1618950 -1.1618950 -1.1618950
## [2,] -0.3872983 -0.3872983 -0.3872983
## [3,]  0.3872983  0.3872983  0.3872983
## [4,]  1.1618950  1.1618950  1.1618950

注意:この単純な例の場合、当然、同じ結果をより簡単に達成できます。
apply(dataPoints, 2, scale)


1
これはグループ化に関連していますか?
フランク

2
@フランク:正直なところ、この投稿のタイトルはかなり誤解を招く可能性があります。質問自体を読んだとき、それは「適用家族」に関するものです。sweepは、ここで述べた他のすべての関数と同様に高次関数です。たとえばapply、ですsapplylapplyしたがって、1,000以上の賛成票とそこに示されている例で、承認された回答について同じ質問をすることができます。applyそこに示されている例を見てください。
vonjd 2017年

2
スイープには、紛らわしい名前、紛らわしいデフォルト、および紛らわしいパラメーター名があります:)。このように理解する方が簡単です。1)STATSは、最初の入力と同じサイズの行列を形成するために繰り返されるベクトルまたは単一の値です。2)1番目の入力とこの新しい行列にFUNが適用されます。たぶん、次のように説明されますsweep(matrix(1:6,nrow=2),2,7:9,list)applywhere applyループは、sweepベクトル化された関数を使用できるため、通常より効率的です。
Moody_Mudskipper

2

CRANで最近リリースされた折りたたみパッケージでは、一般的な適用機能のほとんどを2つの関数に圧縮しようとしました。

  1. dapply(Data-Apply)は、行列とdata.framesの行または(デフォルト)列に関数を適用し、(デフォルト)同じタイプの同じ属性のオブジェクトを返します(各計算の結果がアトミックである場合を除きますdrop = TRUE)。パフォーマンスはlapplydata.frame列に匹敵しapply、マトリックスの行または列よりも約2倍高速です。並列処理はmclapply(MACの場合のみ)から利用できます。

構文:

dapply(X, FUN, ..., MARGIN = 2, parallel = FALSE, mc.cores = 1L, 
       return = c("same", "matrix", "data.frame"), drop = TRUE)

例:

# Apply to columns:
dapply(mtcars, log)
dapply(mtcars, sum)
dapply(mtcars, quantile)
# Apply to rows:
dapply(mtcars, sum, MARGIN = 1)
dapply(mtcars, quantile, MARGIN = 1)
# Return as matrix:
dapply(mtcars, quantile, return = "matrix")
dapply(mtcars, quantile, MARGIN = 1, return = "matrix")
# Same for matrices ...
  1. BYベクトル、行列、data.frameメソッドを使用したsplit-apply-combineコンピューティング用のS3ジェネリックです。これは、大幅に高速化よりもtapplybyaggregate(も速くよりplyr大規模なデータに、dplyr速いけれどもあります)。

構文:

BY(X, g, FUN, ..., use.g.names = TRUE, sort = TRUE,
   expand.wide = FALSE, parallel = FALSE, mc.cores = 1L,
   return = c("same", "matrix", "data.frame", "list"))

例:

# Vectors:
BY(iris$Sepal.Length, iris$Species, sum)
BY(iris$Sepal.Length, iris$Species, quantile)
BY(iris$Sepal.Length, iris$Species, quantile, expand.wide = TRUE) # This returns a matrix 
# Data.frames
BY(iris[-5], iris$Species, sum)
BY(iris[-5], iris$Species, quantile)
BY(iris[-5], iris$Species, quantile, expand.wide = TRUE) # This returns a wider data.frame
BY(iris[-5], iris$Species, quantile, return = "matrix") # This returns a matrix
# Same for matrices ...

グループ化変数のリストもに提供できgます。

パフォーマンスについて話す:折りたたみの主な目標は、Rでの高パフォーマンスプログラミングを促進し、split-apply-combine全体を超えることです。この目的のため、パッケージには、C ++ベースの高速な汎用的な機能のフルセットを持っています:fmeanfmedianfmodefsumfprodfsdfvarfminfmaxffirstflastfNobsfNdistinctfscalefbetweenfwithinfHDbetweenfHDwithinflagfdifffgrowth。それらは、データの単一パスでグループ化された計算を実行します(つまり、分割と再結合は行いません)。

構文:

fFUN(x, g = NULL, [w = NULL,] TRA = NULL, [na.rm = TRUE,] use.g.names = TRUE, drop = TRUE)

例:

v <- iris$Sepal.Length
f <- iris$Species

# Vectors
fmean(v)             # mean
fmean(v, f)          # grouped mean
fsd(v, f)            # grouped standard deviation
fsd(v, f, TRA = "/") # grouped scaling
fscale(v, f)         # grouped standardizing (scaling and centering)
fwithin(v, f)        # grouped demeaning

w <- abs(rnorm(nrow(iris)))
fmean(v, w = w)      # Weighted mean
fmean(v, f, w)       # Weighted grouped mean
fsd(v, f, w)         # Weighted grouped standard-deviation
fsd(v, f, w, "/")    # Weighted grouped scaling
fscale(v, f, w)      # Weighted grouped standardizing
fwithin(v, f, w)     # Weighted grouped demeaning

# Same using data.frames...
fmean(iris[-5], f)                # grouped mean
fscale(iris[-5], f)               # grouped standardizing
fwithin(iris[-5], f)              # grouped demeaning

# Same with matrices ...

パッケージビネットでは、ベンチマークを提供します。高速関数を使用したプログラミングは、dplyrまたはdata.table使用したプログラミングよりも大幅に高速です。、特に小さなデータだけでなく大きなデータの場合も、。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.