Funções de agrupamento (toque, por, agregado) e a família * apply


1041

Sempre que eu quero fazer algo "mapear" py em R, geralmente tento usar uma função na applyfamília.

No entanto, nunca entendi bem as diferenças entre eles - como { sapply, lapplyetc.} aplica a função à entrada / entrada agrupada, como será a saída ou até mesmo como pode ser a entrada - por isso, muitas vezes basta passar por todos eles até conseguir o que quero.

Alguém pode explicar como usar qual quando?

Meu entendimento atual (provavelmente incorreto / incompleto) é ...

  1. sapply(vec, f): input é um vetor. output é um vetor / matriz, onde elemento ié f(vec[i]), fornecendo uma matriz se ftiver uma saída com vários elementos

  2. lapply(vec, f): o mesmo que sapply, mas a saída é uma lista?

  3. apply(matrix, 1/2, f): input é uma matriz. output é um vetor, em que o elemento ié f (linha / coli da matriz)
  4. tapply(vector, grouping, f): output é uma matriz / matriz, em que um elemento na matriz / matriz é o valor de fum agrupamento gdo vetor e gé empurrado para os nomes de linha / coluna
  5. by(dataframe, grouping, f): deixe gser um agrupamento. aplicar fa cada coluna do grupo / quadro de dados. imprima o agrupamento e o valor de fem cada coluna.
  6. aggregate(matrix, grouping, f): semelhante a by, mas em vez de imprimir bastante a saída, o agregado agrupa tudo em um quadro de dados.

Pergunta Side: Eu ainda tenho plyr ou remodelar não aprendeu - se plyrou reshapesubstituir todos estes inteiramente?


33
à sua pergunta paralela: para muitas coisas, o plyr é um substituto direto para *apply()e by. O plyr (pelo menos para mim) parece muito mais consistente, porque eu sempre sei exatamente qual formato de dados ele espera e exatamente o que ele vai cuspir. Isso me poupa muito aborrecimento.
JD Longo

12
Além disso, eu recomendo adicionar: doBye os recursos de seleção e aplicação de data.table.
Iterator

7
sapplyé apenas lapplycom a adição de simplify2arrayna saída. applycoagir ao vetor atômico, mas a saída pode ser vetorial ou lista. bydivide os quadros de dados em sub-quadros de dados, mas não é usado fnas colunas separadamente. Somente se houver um método para a classe 'data.frame' for faplicado por coluna by. aggregateé genérico, portanto existem métodos diferentes para diferentes classes do primeiro argumento.
IRTFM

8
Mnemônico: l é para 'lista', S é para 'simplificar', t é para 'por tipo' (cada nível do agrupamento é um tipo)
Lutz Prechelt

Existem também algumas funções no pacote Rfast, como: eachcol.apply, apply.condition, e mais, que são mais rápidos do que seus equivalentes de R
Stefanos

Respostas:


1330

R possui muitas funções * apply que são descritas habilmente nos arquivos de ajuda (por exemplo ?apply). Porém, existem muitos deles, para que os usuários iniciais possam ter dificuldade em decidir qual deles é apropriado para a situação ou até mesmo se lembrar de todos. Eles podem ter um senso geral de que "eu deveria estar usando uma função * apply aqui", mas pode ser difícil mantê-los corretos no início.

Apesar do fato (observado em outras respostas) de que grande parte da funcionalidade da família * apply é coberta pelo plyrpacote extremamente popular , as funções básicas permanecem úteis e vale a pena conhecer.

Esta resposta pretende atuar como uma espécie de sinalização para novos useRs para ajudar a direcioná-los para a função correta * apply para seu problema específico. Observe que isso não se destina a regurgitar ou substituir a documentação do R! A esperança é que essa resposta o ajude a decidir qual função * aplicada se adequa à sua situação e, então, cabe a você pesquisar mais. Com uma exceção, as diferenças de desempenho não serão abordadas.

  • aplicar - Quando você deseja aplicar uma função às linhas ou colunas de uma matriz (e análogos de dimensões superiores); geralmente não é aconselhável para quadros de dados, pois ele coagirá a uma matriz primeiro.

    # 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

    Se você quiser meio de linha / coluna ou somas para uma matriz 2D, certifique-se de investigar o altamente otimizado,-relâmpago colMeans, rowMeans, colSums, rowSums.

  • lapply - Quando você deseja aplicar uma função a cada elemento de uma lista e obter uma lista de volta.

    Este é o cavalo de batalha de muitas das outras funções * apply. Descasque o código e você encontrará frequentemente lapplyembaixo.

    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 - Quando você deseja aplicar uma função a cada elemento de uma lista, por sua vez, mas deseja um vetor de volta, em vez de uma lista.

    Se você estiver digitando unlist(lapply(...)), pare e considere 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 

    Em usos mais avançados, sapplyele tentará coagir o resultado a uma matriz multidimensional, se apropriado. Por exemplo, se nossa função retornar vetores do mesmo comprimento, sapplyos usará como colunas de uma matriz:

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

    Se nossa função retornar uma matriz bidimensional, sapplyfará essencialmente a mesma coisa, tratando cada matriz retornada como um único vetor longo:

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

    A menos que seja especificado simplify = "array", nesse caso, ele usará as matrizes individuais para criar uma matriz multidimensional:

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

    É claro que cada um desses comportamentos depende de nossa função retornar vetores ou matrizes do mesmo comprimento ou dimensão.

  • vapply - Quando você deseja usar, mas talvez precise extrair um pouco mais de velocidade do seu código.sapply

    Para vapply, basicamente, você fornece a R um exemplo de que tipo de coisa sua função retornará, o que pode economizar tempo coagindo os valores retornados a se ajustarem a um único vetor atômico.

    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 - Para quando você possui várias estruturas de dados (por exemplo, vetores, listas) e deseja aplicar uma função aos 1º elementos de cada um e depois aos 2º elementos de cada um, etc., coagindo o resultado a um vetor / matriz como em sapply.

    Isso é multivariado no sentido de que sua função deve aceitar vários argumentos.

    #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
  • Mapa - Um wrapper para mapplywith SIMPLIFY = FALSE, portanto, é garantido que você retorne uma lista.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
  • rapply - para quando você deseja aplicar uma função a cada elemento de uma estrutura de lista aninhada , recursivamente.

    Para ter uma idéia de como rapplyé incomum , eu esqueci disso ao postar esta resposta pela primeira vez! Obviamente, tenho certeza que muitas pessoas usam, mas YMMV. rapplyé melhor ilustrado com uma função definida pelo usuário a ser aplicada:

    # 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 - para quando você deseja aplicar uma função a subconjuntos de um vetor e os subconjuntos são definidos por algum outro vetor, geralmente um fator.

    As ovelhas negras da * aplicam família, dos tipos. O uso do arquivo de ajuda da frase "matriz irregular" pode ser um pouco confuso , mas na verdade é bastante simples.

    Um vetor:

    x <- 1:20

    Um fator (do mesmo comprimento!) Que define grupos:

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

    Adicione os valores em xcada subgrupo definido por y:

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

    Exemplos mais complexos podem ser manipulados onde os subgrupos são definidos pelas combinações exclusivas de uma lista de vários fatores. tapplyé semelhante em espírito à split-aplicar-se combinam funções que são comuns em R ( aggregate, by, ave, ddply, etc.) Daí o seu estatuto ovelha negra.


32
Acredite que você descobrirá que isso byé pura repetição e aggregateestá tapplyem seus núcleos. Eu acho que a ovelha negra faz um excelente tecido.
IRTFM 14/09/11

21
Resposta fantástica! Isso deve fazer parte da documentação oficial do R :). Uma pequena sugestão: talvez acrescente algumas balas no uso aggregatee bytambém? (Eu finalmente compreendê-los após sua descrição !, mas eles são bastante comuns, por isso pode ser útil para separar e ter alguns exemplos específicos para essas duas funções.)
grautur

3
@grautur Eu estava removendo ativamente as coisas desta resposta para evitar que ela fosse (a) muito longa e (b) uma reescrita da documentação. Decidi que aggregate, embora , byetc. sejam baseados em * funções de aplicação, a maneira como você as utiliza é diferente do ponto de vista dos usuários, devendo ser resumidos em uma resposta separada. Eu posso tentar isso se tiver tempo, ou talvez alguém me derrote e ganhe meu voto positivo.
joran

4
Também, ?Mapcomo um parente demapply
baptiste

3
@jsanders - eu não concordo com isso. data.frames são uma parte absolutamente central de R e, como listobjeto, são freqüentemente manipulados usando lapplyparticularmente. Eles também atuam como contêineres para agrupar vetores / fatores de vários tipos em um conjunto de dados retangular tradicional. Embora data.tablee plyrpossam adicionar um certo tipo de sintaxe que alguns possam achar mais confortável, eles estão estendendo e agindo em data.frames, respectivamente.
precisa saber é o seguinte

191

Na nota lateral, veja como as várias plyrfunções correspondem às *applyfunções básicas (da introdução ao documento plyr da página da web plyr http://had.co.nz/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 

Um dos objetivos de plyré fornecer convenções de nomenclatura consistentes para cada uma das funções, codificando os tipos de dados de entrada e saída no nome da função. Ele também fornece consistência na saída, na medida em que a saída de dlply()é facilmente passável ldply()para produzir uma saída útil, etc.

Conceitualmente, o aprendizado plyrnão é mais difícil do que entender as *applyfunções básicas .

plyre reshapefunções substituíram quase todas essas funções no meu uso diário. Mas, também do documento Intro to Plyr:

Funções relacionadas tapplye sweepnão possuem função correspondente plyre permanecem úteis. mergeé útil para combinar resumos com os dados originais.


13
Quando comecei a aprender R do zero, achei muito mais fácil aprender o plyr do que a *apply()família de funções. Para mim, ddply()foi muito intuitivo, pois estava familiarizado com as funções de agregação SQL. ddply()tornou-se meu martelo para resolver muitos problemas, alguns dos quais poderiam ter sido melhor resolvidos com outros comandos.
JD Longo

1
Acho que percebi que o conceito por trás de plyrfunções é semelhante a *applyfunções; portanto, se você pode fazer uma, pode fazer a outra, mas as plyrfunções são mais fáceis de lembrar. Mas concordo totalmente com o ddply()martelo!
precisa saber é o seguinte

1
O pacote plyr possui a join()função que executa tarefas semelhantes à mesclagem. Talvez seja mais importante mencioná-lo no contexto de plyr.
marbel

Não esqueçamos pobre, esquecidoeapply
JDL

Ótima resposta em geral, mas acho que diminui a utilidade vapplye as desvantagens de sapply. Uma grande vantagem vapplydisso é que ele impõe o tipo e o comprimento da saída, para que você termine com a saída exata exata ou com um erro informativo. Por outro lado, sapplytentará simplificar a saída seguindo regras que nem sempre são óbvias e, caso contrário, voltará a uma lista. Por exemplo, tentar prever o tipo de saída esta irá produzir: sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1)). Que tal sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)?
Alexey Shiklomanov


100

Primeiro comece com a excelente resposta de Joran - qualquer coisa duvidosa pode melhorar isso.

As seguintes mnemônicas podem ajudar a lembrar as distinções entre cada uma. Enquanto alguns são óbvios, outros podem ser menos - para estes você encontrará justificativa nas discussões de Joran.

Mnemônicos

  • lapplyé uma lista aplicada que atua em uma lista ou vetor e retorna uma lista.
  • sapplyé simples lapply (a função padrão é retornar um vetor ou matriz quando possível)
  • vapplyé uma aplicação verificada (permite que o tipo de objeto de retorno seja pré-especificado)
  • rapplyé uma aplicação recursiva para listas aninhadas, ou seja, listas dentro de listas
  • tapplyé uma aplicação marcada onde as tags identificam os subconjuntos
  • apply é genérico : aplica uma função às linhas ou colunas de uma matriz (ou, geralmente, às dimensões de uma matriz)

Construindo o fundo certo

Se o uso da applyfamília ainda parecer um pouco estranho para você, pode ser que você esteja perdendo um ponto de vista fundamental.

Esses dois artigos podem ajudar. Eles fornecem o fundo necessário para motivar as técnicas de programação funcional que estão sendo fornecidas pela applyfamília de funções.

Os usuários do Lisp reconhecerão o paradigma imediatamente. Se você não estiver familiarizado com o Lisp, depois de entender o FP, você ganhará um ponto de vista poderoso para uso no R - e applyfará muito mais sentido.


51

Desde que percebi que (as muito excelentes) respostas deste post não têm bye aggregateexplicações. Aqui está a minha contribuição.

DE

A byfunção, conforme declarado na documentação, pode ser um "wrapper" para tapply. O poder de bysurge quando queremos calcular uma tarefa que tapplynão pode lidar. Um exemplo é este código:

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 

Se imprimirmos esses dois objetos, cte cb"essencialmente" tivermos os mesmos resultados e as únicas diferenças existirão em como elas são mostradas e nos diferentes classatributos, respectivamente bypara cbe arraypara ct.

Como eu disse, o poder de bysurge quando não podemos usar tapply; o código a seguir é um exemplo:

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

R diz que os argumentos devem ter os mesmos comprimentos, diga "queremos calcular a summaryvariável de todas ao irislongo do fator Species": mas R simplesmente não pode fazer isso porque não sabe como lidar.

Com a byfunção R, despacha um método específico para a data frameclasse e deixa a summaryfunção funcionar mesmo que o comprimento do primeiro argumento (e o tipo também) seja diferente.

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     

funciona de fato e o resultado é muito surpreendente. É um objeto de classe byque, ao longo Species(digamos, para cada um deles), calcula o valor summaryde cada variável.

Observe que, se o primeiro argumento for a data frame, a função despachada deverá ter um método para essa classe de objetos. Por exemplo, se usarmos esse código com a meanfunção, teremos esse código que não faz sentido algum:

 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

AGREGAR

aggregatepode ser visto como outro modo de uso diferente tapplyse o usarmos dessa maneira.

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

As duas diferenças imediatas são que o segundo argumento de aggregate deve ser uma lista enquanto tapply pode (não obrigatório) ser uma lista e que a saída de aggregateé um quadro de dados enquanto o de tapplyé um array.

O poder de aggregateé que ele pode lidar facilmente com subconjuntos de dados com subsetargumento e que também possui métodos para tsobjetos formula.

Esses elementos aggregatefacilitam o trabalho com isso tapplyem algumas situações. Aqui estão alguns exemplos (disponíveis na documentação):

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

Podemos conseguir o mesmo com, tapplymas a sintaxe é um pouco mais difícil e a saída (em algumas circunstâncias) menos legível:

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

Há outros momentos em que não podemos usar byou tapplye temos que usar 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

Não podemos obter o resultado anterior com tapplyuma chamada, mas temos que calcular a média longitudinal Monthde cada elemento e combiná-los (observe também que precisamos chamar a na.rm = TRUE, porque os formulamétodos da aggregatefunção têm por padrão a 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

embora with bynão possamos conseguir isso, na verdade a seguinte chamada de função retorna um erro (mas provavelmente está relacionada à função fornecida mean):

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

Outras vezes, os resultados são os mesmos e as diferenças são apenas no objeto da classe (e depois como é mostrado / impresso e não apenas - por exemplo, como subconjunto):

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

O código anterior alcança o mesmo objetivo e resultados; em alguns momentos, qual ferramenta usar é apenas uma questão de gostos e necessidades pessoais; os dois objetos anteriores têm necessidades muito diferentes em termos de subconjunto.


Como eu disse, o poder de by surge quando não podemos usar o tapply; o código a seguir é um exemplo: ESTAS SÃO AS PALAVRAS QUE VOCÊ UTILIZOU ACIMA. E você deu um exemplo de cálculo do resumo. Bem, digamos que as estatísticas de resumo possam ser computadas apenas para a necessidade de limpeza: por exemplo, data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))este é um uso do 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

Há muitas ótimas respostas que discutem diferenças nos casos de uso de cada função. Nenhuma das respostas discute as diferenças de desempenho. Isso é razoável porque várias funções esperam várias entradas e produzem várias saídas, mas a maioria delas tem um objetivo geral comum de avaliar por séries / grupos. Minha resposta vai se concentrar no desempenho. Como acima, a criação de entrada dos vetores é incluída no tempo, também a applyfunção não é medida.

Eu testei duas funções diferentes sume lengthao mesmo tempo. O volume testado é de 50M na entrada e 50K na saída. Também incluí dois pacotes atualmente populares que não eram amplamente usados ​​no momento em que a pergunta foi feita, data.tablee dplyr. Definitivamente, vale a pena olhar para ambos se você está buscando um bom desempenho.

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

É normal que o dplyr seja menor que as funções applt?
Mostafa

1
@DimitriPetrenko Acho que não, não sei por que está aqui. É melhor testar com seus próprios dados, pois existem muitos fatores que entram em jogo.
Jangorecki

28

Apesar de todas as excelentes respostas aqui, existem mais 2 funções básicas que merecem ser mencionadas, a outerfunção útil e a eapplyfunção obscura

exterior

outeré uma função muito útil oculta como mais comum. Se você ler a ajuda para outersua descrição, diz:

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], ...).

o que faz parecer que isso só é útil para coisas do tipo álgebra linear. No entanto, pode ser usado de maneira semelhante mapplyà aplicação de uma função a dois vetores de entradas. A diferença é que mapplyaplicará a função aos dois primeiros elementos e, em seguida, aos dois segundos etc., enquanto outeraplicará a função a todas as combinações de um elemento do primeiro vetor e um do segundo. Por exemplo:

 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

Eu pessoalmente o usei quando tenho um vetor de valores e um vetor de condições e desejo ver quais valores atendem a quais condições.

ansiosamente

eapplyé como lapplyexceto que, em vez de aplicar uma função a todos os elementos de uma lista, ela aplica uma função a todos os elementos de um ambiente. Por exemplo, se você deseja encontrar uma lista de funções definidas pelo usuário no ambiente global:

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 

Francamente, eu não uso muito isso, mas se você estiver criando muitos pacotes ou criando muitos ambientes, pode ser útil.


25

Talvez valha a pena mencionar ave. aveé tapplyprimo amigável de. Ele retorna os resultados em um formulário que você pode conectar diretamente ao seu quadro de dados.

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
##   ...

Não há nada no pacote base que funcione como avepara quadros de dados inteiros (como byé tapplypara quadros de dados). Mas você pode falsificá-lo:

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

Eu descobri recentemente a sweepfunção bastante útil e a adicionei aqui por uma questão de integridade:

varrer

A ideia básica é a de varrer por meio de uma matriz ou row- direcção de coluna e retornar uma matriz modificada. Um exemplo deixará isso claro (fonte: datacamp ):

Digamos que você tenha uma matriz e queira padronizá- la em colunas:

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

NB: para este exemplo simples, obviamente, o mesmo resultado pode ser alcançado mais facilmente
apply(dataPoints, 2, scale)


1
Isso está relacionado ao agrupamento?
Frank

2
@Frank: Bem, para ser sincero com você, o título deste post é bastante enganador: quando você lê a pergunta em si, trata-se da "família de candidatos". sweepé uma função de ordem superior, como todas as outras mencionadas aqui, por exemplo apply, Portanto sapply, lapplya mesma pergunta pode ser feita sobre a resposta aceita com mais de 1.000 votos positivos e os exemplos dados nela. Basta dar uma olhada no exemplo dado por applylá.
vonjd

2
sweep tem um nome enganador, padrões enganosos e nome de parâmetro enganoso :). É mais fácil para mim entender desta maneira: 1) STATS é um vetor ou valor único que será repetido para formar uma matriz do mesmo tamanho da primeira entrada, 2) FUN será aplicado na 1ª entrada e nesta nova matriz. Talvez melhor ilustrado por: sweep(matrix(1:6,nrow=2),2,7:9,list). Geralmente é mais eficiente do que applyporque onde applyloops sweepé capaz de usar funções vetorizadas.
Moody_Mudskipper

2

No pacote recolhido recentemente lançado no CRAN, tentei compactar a maioria das funcionalidades comuns de aplicação em apenas 2 funções:

  1. dapply(Data-Apply) aplica funções a linhas ou (padrão) colunas de matrizes e data.frames e (padrão) retorna um objeto do mesmo tipo e com os mesmos atributos (a menos que o resultado de cada cálculo seja atômico e drop = TRUE). O desempenho é comparável ao lapplydas colunas data.frame e cerca de duas vezes mais rápido que applynas linhas ou colunas da matriz. O paralelismo está disponível via mclapply(apenas para MAC).

Sintaxe:

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

Exemplos:

# 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é um S3 genérico para computação de combinação e aplicação dividida com o vetor, matriz e método data.frame. É significativamente mais rápido que tapply, bye aggregate(também mais rápido que plyrem grandes dados dplyré mais rápido).

Sintaxe:

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

Exemplos:

# 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 ...

Também podem ser fornecidas listas de variáveis ​​de agrupamento para g .

Falando sobre desempenho: O principal objetivo do colapso é promover a programação de alto desempenho no R e ir além da combinação de aplicar e dividir. Para este efeito, o pacote tem um conjunto completo de C ++ com base funções rápido genéricos: fmean, fmedian, fmode, fsum, fprod, fsd, fvar, fmin, fmax, ffirst, flast, fNobs, fNdistinct, fscale, fbetween, fwithin, fHDbetween, fHDwithin, flag, fdiffefgrowth . Eles executam cálculos agrupados em uma única passagem pelos dados (ou seja, sem divisão e recombinação).

Sintaxe:

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

Exemplos:

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

Nas vinhetas da embalagem, forneço referências. A programação com as funções rápidas é significativamente mais rápida que a programação com dplyr ou data.table , especialmente em dados menores, mas também em dados grandes.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.