Funciones de agrupación (tapply, by, aggregate) y la familia * apply

Siempre que quiero hacer algo "mapear" py en R, por lo general trato de usar una función en el apply familia.

Sin embargo, nunca he entendido bien las diferencias entre ellos. ¿Cómo {sapply, lapply, etc.} aplicar la función a la entrada / entrada agrupada, cómo se verá la salida o incluso cuál puede ser la entrada, por lo que a menudo las reviso todas hasta que obtengo lo que quiero.

¿Alguien puede explicar cómo usar cuál y cuándo?

Mi comprensión actual (probablemente incorrecta / incompleta) es ...

  1. sapply(vec, f): la entrada es un vector. la salida es un vector / matriz, donde el elemento i is f(vec[i]), dándote una matriz si f tiene una salida de varios elementos

  2. lapply(vec, f): igual que sapply, pero la salida es una lista?

  3. apply(matrix, 1/2, f): la entrada es una matriz. la salida es un vector, donde el elemento i es f (fila / columna i de la matriz)
  4. tapply(vector, grouping, f): la salida es una matriz / matriz, donde un elemento de la matriz / matriz es el valor de f en una agrupación g del vector, y g se empuja a los nombres de filas / columnas
  5. by(dataframe, grouping, f): dejar g ser una agrupación. solicitar f a cada columna del grupo / marco de datos. bonita imprime la agrupación y el valor de f en cada columna.
  6. aggregate(matrix, grouping, f): Similar a by, pero en lugar de imprimir bastante la salida, el agregado pega todo en un marco de datos.

Pregunta secundaria: todavía no he aprendido plyr o remodelar - ¿lo haría? plyr or reshape reemplazar todos estos por completo?

preguntado el 17 de agosto de 10 a las 15:08

a su pregunta paralela: para muchas cosas, plyr es un reemplazo directo de *apply() y by. plyr (al menos para mí) parece mucho más consistente en el sentido de que siempre sé exactamente qué formato de datos espera y exactamente qué escupirá. Eso me ahorra muchas molestias. -

Además, recomendaría agregar: doBy y las capacidades de selección y aplicación de data.table. -

sapply es sólo lapply con la adición de simplify2array en la salida. apply coacciona al vector atómico, pero la salida puede ser vector o lista. by divide los marcos de datos en sub-marcos de datos, pero no utiliza f en columnas por separado. Solo si hay un método para la clase 'data.frame' podría f obtener la aplicación de columnas por by. aggregate es genérico, por lo que existen diferentes métodos para diferentes clases del primer argumento. -

Mnemónico: l es para 'lista', s es para 'simplificar', t es para 'por tipo' (cada nivel de la agrupación es un tipo) -

También existen algunas funciones en el paquete Rfast, como: eachcol.apply, apply.condition y más, que son más rápidas que los equivalentes de R -

10 Respuestas

R tiene muchas * funciones de aplicación que se describen hábilmente en los archivos de ayuda (p. Ej. ?apply). Sin embargo, hay suficientes de ellos que los usuarios principiantes pueden tener dificultades para decidir cuál es apropiado para su situación o incluso recordarlos todos. Es posible que tengan la sensación general de que "debería usar una función * aplicar aquí", pero puede ser difícil mantenerlos todos correctos al principio.

A pesar del hecho (señalado en otras respuestas) de que gran parte de la funcionalidad de la familia * apply está cubierta por el extremadamente popular plyr paquete, las funciones base siguen siendo útiles y vale la pena conocerlas.

Esta respuesta pretende actuar como una especie de poste indicador para que los nuevos usuarios ayuden a dirigirlos a la función de aplicación correcta * para su problema particular. Nota, esto es no destinado a simplemente regurgitar o reemplazar la documentación R! La esperanza es que esta respuesta lo ayude a decidir qué * función de aplicación se adapta a su situación y luego depende de usted investigar más. Con una excepción, no se abordarán las diferencias de rendimiento.

  • aplicar - Cuando desee aplicar una función a las filas o columnas de una matriz (y análogos de dimensiones superiores); generalmente no es aconsejable para los marcos de datos, ya que primero coaccionará a una matriz.

    # 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
    

    Si desea promedios de fila / columna o sumas para una matriz 2D, asegúrese de investigar el altamente optimizado, ultrarrápido colMeans, rowMeans, colSums, rowSums.

  • lapear - Cuando desee aplicar una función a cada elemento de una lista por turno y obtener una lista de regreso.

    Este es el caballo de batalla de muchas de las otras * funciones de aplicación. Despegue su código y encontrará a menudo lapply debajo.

    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
    
  • aplicar savia - Cuando desea aplicar una función a cada elemento de una lista, pero desea una vector atrás, en lugar de una lista.

    Si te encuentras escribiendo unlist(lapply(...)), detente y considera 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 
    

    En usos más avanzados de sapply intentará coaccionar el resultado a una matriz multidimensional, si es apropiado. Por ejemplo, si nuestra función devuelve vectores de la misma longitud, sapply los usará como columnas de una matriz:

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

    Si nuestra función devuelve una matriz bidimensional, sapply hará esencialmente lo mismo, tratando cada matriz devuelta como un solo vector largo:

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

    A menos que especifiquemos simplify = "array", en cuyo caso utilizará las matrices individuales para construir una matriz multidimensional:

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

    Cada uno de estos comportamientos depende, por supuesto, de que nuestra función devuelva vectores o matrices de la misma longitud o dimensión.

  • vapear - Cuando quieras usar sapply pero quizás necesite exprimir un poco más la velocidad de su código.

    Para vapply, básicamente le da a R un ejemplo de qué tipo de cosas devolverá su función, lo que puede ahorrar algo de tiempo al coaccionar valores devueltos para que quepan en un solo vector 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
    
  • mapear - Para cuando tiene varias estructuras de datos (por ejemplo, vectores, listas) y desea aplicar una función a los primeros elementos de cada uno, y luego a los segundos elementos de cada uno, etc., coaccionando el resultado a un vector / matriz como en sapply.

    Esto es multivariado en el sentido de que su función debe aceptar múltiples 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 - Un envoltorio para mapply con SIMPLIFY = FALSE, por lo que está garantizado devolver una lista.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
    
  • rapear - Para cuando desee aplicar una función a cada elemento de una lista anidada estructura, recursivamente.

    Para darte una idea de lo poco común rapply es decir, ¡lo olvidé cuando publiqué esta respuesta por primera vez! Obviamente, estoy seguro de que mucha gente lo usa, pero YMMV. rapply se ilustra mejor con una función definida por el usuario para aplicar:

    # 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")
    
  • tocar - Para cuando desee aplicar una función a subconjuntos de un vector y los subconjuntos están definidos por algún otro vector, generalmente un factor.

    La oveja negra de la familia * Apply, en cierto modo. El uso de la frase "matriz irregular" en el archivo de ayuda puede ser un poco confuso, pero en realidad es bastante simple.

    Un vector:

    x <- 1:20
    

    Un factor (¡de la misma longitud!) Que define grupos:

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

    Sume los valores en x dentro de cada subgrupo definido por y:

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

    Se pueden manejar ejemplos más complejos donde los subgrupos están definidos por las combinaciones únicas de una lista de varios factores. tapply es similar en espíritu a las funciones dividir-aplicar-combinar que son comunes en R (aggregate, by, ave, ddply, etc.) De ahí su condición de oveja negra.

Respondido 03 Oct 17, 20:10

Cree que encontrarás eso by es puro solape dividido y aggregate is tapply en sus núcleos. Creo que las ovejas negras son una excelente tela. - IRTFM

¡Respuesta fantástica! Esto debería ser parte de la documentación oficial de R :). Una pequeña sugerencia: tal vez agregue algunas viñetas sobre el uso aggregate y by ¿también? (¡Finalmente los entiendo después de su descripción!, Pero son bastante comunes, por lo que podría ser útil separarlos y tener algunos ejemplos específicos para esas dos funciones). grautur

@grautur Estaba recortando activamente cosas de esta respuesta para evitar que sea (a) demasiado larga y (b) una reescritura de la documentación. Decidí que mientras aggregate, by, etc.se basan en * aplicar funciones, la forma en que aborda su uso es lo suficientemente diferente desde la perspectiva de los usuarios que deberían resumirse en una respuesta separada. Puedo intentar eso si tengo tiempo, o tal vez alguien más se me adelantará y se ganará mi voto a favor. - Jorán

además, ?Map como pariente de mapply - bautista

@jsanders - No estaría de acuerdo con eso en absoluto. data.frames son una parte absolutamente central de R y como un list objeto se manipulan con frecuencia utilizando lapply particularmente. También actúan como contenedores para agrupar vectores / factores de muchos tipos en un conjunto de datos rectangular tradicional. Tiempo data.table y plyr podría agregar un cierto tipo de sintaxis que algunos podrían encontrar más cómodos, están extendiendo y actuando sobre data.frames respectivamente. - el correo tardío

En la nota al margen, así es como los diversos plyr las funciones corresponden a la base *apply funciones (desde la introducción a plyr document desde la página web de 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 

Uno de los objetivos de plyr es proporcionar convenciones de nomenclatura coherentes para cada una de las funciones, codificando los tipos de datos de entrada y salida en el nombre de la función. También proporciona consistencia en la salida, ya que la salida de dlply() es fácilmente transitable para ldply() para producir resultados útiles, etc.

Conceptualmente, aprender plyr no es más difícil que entender la base *apply funciones.

plyr y reshape Las funciones han reemplazado a casi todas estas funciones en mi uso diario. Pero, también del documento Intro to Plyr:

Funciones relacionadas tapply y sweep no tienen una función correspondiente en plyry seguir siendo útil. merge es útil para combinar resúmenes con los datos originales.

Respondido 17 ago 10, 20:08

Cuando comencé a aprender R desde cero, encontré plyr MUCHO más fácil de aprender que el *apply() familia de funciones. Para mi, ddply() fue muy intuitivo ya que estaba familiarizado con las funciones de agregación SQL. ddply() se convirtió en mi martillo para resolver muchos problemas, algunos de los cuales podrían haberse resuelto mejor con otros comandos. - JD largo

Supongo que pensé que el concepto detrás plyr funciones es similar a *apply funciones, por lo que si puede hacer una, puede hacer la otra, pero plyr las funciones son más fáciles de recordar. Pero estoy totalmente de acuerdo con el ddply() ¡martillo! - JoFrhwld

El paquete plyr tiene la join() función que realiza tareas similares a fusionar. Quizás sea más apropiado mencionarlo en el contexto de plyr. - marbel

No olvidemos a los pobres olvidados eapply - JDL

Gran respuesta en general, pero creo que minimiza la utilidad de vapply y las desventajas de sapply. Una gran ventaja de vapply es que impone el tipo y la longitud de salida, por lo que terminará con la salida exacta esperada o con un error informativo. Por otro lado, sapply intentará simplificar la salida siguiendo reglas que no siempre son obvias, y de lo contrario recurrirá a una lista. Por ejemplo, intente predecir el tipo de salida que producirá: sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1)). Qué pasa sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)? - Alexey Shiklómanov

De la diapositiva 21 de http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy:

aplicar, aplicar, aplicar, aplicar, agregar

(Con suerte, está claro que apply corresponde a @ Hadley's aaply y aggregate corresponde a @ Hadley's ddply etc. La diapositiva 20 de la misma diapositiva compartida aclarará si no la obtiene de esta imagen).

(a la izquierda es la entrada, en la parte superior es la salida)

Respondido 15 Feb 12, 23:02

¿Hay un error tipográfico en la diapositiva? La celda superior izquierda debería ser adecuada: JHowIX

Primero comienza con La excelente respuesta de Joran - dudoso que algo pueda mejorar eso.

Entonces, los siguientes mnemónicos pueden ayudar a recordar las distinciones entre cada uno. Si bien algunos son obvios, otros pueden ser menos, para estos encontrará justificación en las discusiones de Joran.

Mnemotécnica

  • lapply es un -- aplicar que actúa sobre una lista o vector y devuelve una lista.
  • sapply es un sencillo lapply (la función por defecto devuelve un vector o matriz cuando es posible)
  • vapply es un Verificado aplicar (permite especificar previamente el tipo de objeto de retorno)
  • rapply es un recursiva solicitar listas anidadas, es decir, listas dentro de listas
  • tapply es un etiquetado aplicar donde las etiquetas identifican los subconjuntos
  • apply is genérico: aplica una función a las filas o columnas de una matriz (o, más generalmente, a las dimensiones de una matriz)

Construyendo el trasfondo adecuado

Si usa el apply La familia todavía se siente un poco extraña para ti, entonces es posible que te pierdas un punto de vista clave.

Estos dos artículos pueden ayudar. Proporcionan los antecedentes necesarios para motivar a la técnicas de programación funcional que están siendo proporcionados por el apply familia de funciones.

Los usuarios de Lisp reconocerán el paradigma de inmediato. Si no está familiarizado con Lisp, una vez que se familiarice con FP, habrá obtenido un punto de vista poderoso para usar en R - y apply tendrá mucho más sentido.

contestado el 23 de mayo de 17 a las 11:05

Desde que me di cuenta de que (las excelentes) respuestas de esta publicación carecen de by y aggregate explicaciones. Aquí está mi contribución.

BY

La by La función, como se indica en la documentación, puede ser, sin embargo, como un "contenedor" para tapply. El poder de by surge cuando queremos calcular una tarea que tapply no puedo manejar. Un ejemplo es 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 

Si imprimimos estos dos objetos, ct y cb, tenemos "esencialmente" los mismos resultados y las únicas diferencias están en cómo se muestran y las diferentes class atributos, respectivamente by para cb y array para ct.

Como he dicho, el poder de by surge cuando no podemos usar tapply; el siguiente código es un ejemplo:

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

R dice que los argumentos deben tener la misma longitud, dice "queremos calcular el summary de todas las variables en iris a lo largo del factor Species": pero R simplemente no puede hacer eso porque no sabe cómo manejarlo.

Con la by función R despacha un método específico para data frame clase y luego dejar que el summary La función funciona incluso si la longitud del primer argumento (y el tipo también) son diferentes.

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 hecho y el resultado es muy sorprendente. Es un objeto de clase by eso a lo largo Species (digamos, para cada uno de ellos) calcula el summary de cada variable.

Tenga en cuenta que si el primer argumento es un data frame, la función despachada debe tener un método para esa clase de objetos. Por ejemplo, si usamos este código con el mean función tendremos este código que no tiene ningún sentido:

 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

aggregate puede verse como otra forma de uso diferente tapply si lo usamos de esa manera.

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

Las dos diferencias inmediatas son que el segundo argumento de aggregate debe: ser una lista mientras tapply puede (no obligatorio) sea una lista y que la salida de aggregate es un marco de datos mientras que el de tapply es un array.

El poder de aggregate es que puede manejar fácilmente subconjuntos de datos con subset argumento y que tiene métodos para ts objetos y formula también.

Estos elementos hacen aggregate más fácil trabajar con eso tapply en algunas situaciones. A continuación, se muestran algunos ejemplos (disponibles en la documentación):

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 lograr lo mismo con tapply pero la sintaxis es un poco más difícil y la salida (en algunas circunstancias) menos legible:

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

Hay otras ocasiones en las que no podemos usar by or tapply y tenemos 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

No podemos obtener el resultado anterior con tapply en una llamada, pero tenemos que calcular la media a lo largo de Month para cada elemento y luego combinarlos (también tenga en cuenta que tenemos que llamar al na.rm = TRUE, Debido a que el formula métodos de la aggregate función tiene por defecto el 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

mientras que con by simplemente no podemos lograr eso, de hecho, la siguiente llamada a la función devuelve un error (pero lo más probable es que esté relacionado con la función proporcionada, mean):

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

Otras veces, los resultados son los mismos y las diferencias están solo en la clase (y luego en cómo se muestra / imprime y no solo, por ejemplo, cómo subconjunto) objeto:

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

El código anterior logra el mismo objetivo y resultados, en algunos puntos qué herramienta usar es solo una cuestión de gustos y necesidades personales; los dos objetos anteriores tienen necesidades muy diferentes en términos de subconjunto.

Respondido 28 ago 15, 11:08

Como he dicho, el poder de by surge cuando no podemos usar tapply; el siguiente código es un ejemplo: ESTAS SON LAS PALABRAS QUE HA UTILIZADO ANTERIORMENTE. Y ha dado un ejemplo de cómo calcular el resumen. Bueno, digamos que las estadísticas de resumen se pueden calcular solo que necesitarán limpieza: p. Ej. data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary)) este es un uso de tapply. With the right splitting there is nothing you cant do with tocar. The only thing is it returns a matrix. Please be careful by saying we cant use tapply' - Onyambu

Hay muchas respuestas excelentes que discuten las diferencias en los casos de uso de cada función. Ninguna de las respuestas discute las diferencias en el desempeño. Eso es razonable porque varias funciones esperan varias entradas y producen varias salidas, sin embargo, la mayoría de ellas tienen un objetivo común general para evaluar por series / grupos. Mi respuesta se centrará en el rendimiento. Debido a que la creación de entrada a partir de los vectores se incluye en el tiempo, también la apply la función no se mide.

He probado dos funciones diferentes sum y length En seguida. El volumen probado es de 50 M en la entrada y 50 K en la salida. También he incluido dos paquetes actualmente populares que no se usaban mucho en el momento en que se formuló la pregunta, data.table y dplyr. Definitivamente vale la pena ver ambos si lo que busca es un buen rendimiento.

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

Respondido el 08 de diciembre de 15 a las 22:12

¿Es normal que dplyr sea menor que las funciones de applt? - Benmoshe

@DimitriPetrenko No lo creo, no estoy seguro de por qué está aquí. Es mejor probar con sus propios datos, ya que hay muchos factores que entran en juego. - Jangorecki

A pesar de todas las excelentes respuestas aquí, hay 2 funciones básicas más que merecen ser mencionadas, la útil outer función y lo oscuro eapply función

exterior

outer es una función muy útil escondida como una más mundana. Si lees la ayuda para outer su descripción dice:

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

lo que hace que parezca que esto solo es útil para cosas de tipo álgebra lineal. Sin embargo, se puede utilizar como mapply para aplicar una función a dos vectores de entradas. La diferencia es que mapply aplicará la función a los dos primeros elementos y luego a los dos segundos, etc., mientras que outer aplicará la función a cada combinación de un elemento del primer vector y uno del segundo. Por ejemplo:

 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

Personalmente he usado esto cuando tengo un vector de valores y un vector de condiciones y deseo ver qué valores cumplen qué condiciones.

aplicar

eapply es como lapply excepto que en lugar de aplicar una función a cada elemento de una lista, aplica una función a cada elemento de un entorno. Por ejemplo, si desea encontrar una lista de funciones definidas por el usuario en el entorno 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, no lo uso mucho, pero si está creando muchos paquetes o creando muchos entornos, puede ser útil.

Respondido el 03 de junio de 16 a las 14:06

Quizás valga la pena mencionar ave. ave is tapplyprima amiga. Devuelve resultados en un formulario que puede volver a conectar directamente a su marco de datos.

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

No hay nada en el paquete base que funcione como ave para marcos de datos completos (como by es como tapply para marcos de datos). Pero puedes modificarlo:

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

respondido 06 nov., 14:00

Recientemente descubrí el bastante útil sweep función y agréguelo aquí en aras de la integridad:

barrido

La idea básica es barrido a través de una matriz por filas o columnas y devuelve una matriz modificada. Un ejemplo aclarará esto (fuente: campamento de datos):

Digamos que tienes una matriz y quieres estandarizar es columna-sabio:

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

# 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 ejemplo simple, por supuesto, el mismo resultado se puede lograr más fácilmente
apply(dataPoints, 2, scale)

Respondido 17 Jul 20, 14:07

¿Está esto relacionado con la agrupación? - Frank

@Frank: Bueno, para ser honesto contigo, el título de esta publicación es bastante engañoso: cuando lees la pregunta en sí, se trata de "la familia de la aplicación". sweep es una función de orden superior como todas las demás mencionadas aquí, p. ej. apply, sapply, lapply Entonces, se podría hacer la misma pregunta sobre la respuesta aceptada con más de 1,000 votos a favor y los ejemplos que se dan allí. Solo eche un vistazo al ejemplo dado para apply allí. - vonjd

sweep tiene un nombre engañoso, valores predeterminados engañosos y un nombre de parámetro engañoso :). Es más fácil para mí entenderlo de esta manera: 1) STATS es un vector o valor único que se repetirá para formar una matriz del mismo tamaño que la primera entrada, 2) FUN se aplicará en la primera entrada y esta nueva matriz. Quizás mejor ilustrado por: sweep(matrix(1:6,nrow=2),2,7:9,list) . Suele ser más eficiente que applyporque donde apply bucles, sweep es capaz de utilizar funciones vectorizadas. - Moody_Mudskipper

En los colapso paquete lanzado recientemente en CRAN, he intentado comprimir la mayor parte de la funcionalidad de aplicación común en solo 2 funciones:

  1. dapply (Data-Apply) aplica funciones a filas o (por defecto) columnas de matrices y data.frames y (por defecto) devuelve un objeto del mismo tipo y con los mismos atributos (a menos que el resultado de cada cálculo sea atómico y drop = TRUE). El rendimiento es comparable a lapply para columnas data.frame, y aproximadamente 2 veces más rápido que apply para filas o columnas de matriz. El paralelismo está disponible a través de mclapply (solo para MAC).

Sintaxis:

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

Ejemplos:

# 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 es un S3 genérico para la computación dividida, aplicada y combinada con el método de vector, matriz y data.frame. Es significativamente más rápido que tapply, by y aggregate (un también más rápido que plyr, en datos grandes dplyr aunque es más rápido).

Sintaxis:

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

Ejemplos:

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

También se pueden proporcionar listas de variables de agrupación g.

Hablar de rendimiento: un objetivo principal de colapso es fomentar la programación de alto rendimiento en R y avanzar más allá de dividir-aplicar-combinar todos juntos. Para este propósito, el paquete tiene un conjunto completo de funciones genéricas rápidas basadas en C ++: fmean, fmedian, fmode, fsum, fprod, fsd, fvar, fmin, fmax, ffirst, flast, fNobs, fNdistinct, fscale, fbetween, fwithin, fHDbetween, fHDwithin, flag, fdiff y fgrowth. Realizan cálculos agrupados en una sola pasada a través de los datos (es decir, sin división ni recombinación).

Sintaxis:

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

Ejemplos:

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

En las viñetas del paquete proporciono puntos de referencia. La programación con las funciones rápidas es significativamente más rápida que la programación con dplyr or tabla de datos, especialmente en datos más pequeños, pero también en datos grandes.

respondido 20 mar '20, 07:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.