Funciones de agrupación (tapply, by, aggregate) y la familia * apply
Frecuentes
Visto 413,080 veces
1087
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 ...
sapply(vec, f)
: la entrada es un vector. la salida es un vector / matriz, donde el elementoi
isf(vec[i])
, dándote una matriz sif
tiene una salida de varios elementoslapply(vec, f)
: igual quesapply
, pero la salida es una lista?apply(matrix, 1/2, f)
: la entrada es una matriz. la salida es un vector, donde el elementoi
es f (fila / columna i de la matriz)tapply(vector, grouping, f)
: la salida es una matriz / matriz, donde un elemento de la matriz / matriz es el valor def
en una agrupacióng
del vector, yg
se empuja a los nombres de filas / columnasby(dataframe, grouping, f)
: dejarg
ser una agrupación. solicitarf
a cada columna del grupo / marco de datos. bonita imprime la agrupación y el valor def
en cada columna.aggregate(matrix, grouping, f)
: Similar aby
, 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?
10 Respuestas
1377
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 considerasapply
.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
conSIMPLIFY = 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 pory
: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.frame
s 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.frame
s respectivamente. - el correo tardío
198
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
ysweep
no tienen una función correspondiente enplyr
y 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
139
De la diapositiva 21 de http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy:
(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
104
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 sencillolapply
(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 listastapply
es un etiquetado aplicar donde las etiquetas identifican los subconjuntosapply
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.
- Advanced R: Programación funcional, por Hadley Wickham
- Programación funcional simple en R, por Michael Barton
contestado el 23 de mayo de 17 a las 11:05
55
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
38
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
30
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
27
Quizás valga la pena mencionar ave
. ave
is tapply
prima 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
14
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 apply
porque donde apply
bucles, sweep
es capaz de utilizar funciones vectorizadas. - Moody_Mudskipper
6
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:
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 ydrop = TRUE
). El rendimiento es comparable alapply
para columnas data.frame, y aproximadamente 2 veces más rápido queapply
para filas o columnas de matriz. El paralelismo está disponible a través demclapply
(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 ...
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 quetapply
,by
yaggregate
(un también más rápido queplyr
, en datos grandesdplyr
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
a su pregunta paralela: para muchas cosas, plyr es un reemplazo directo de
*apply()
yby
. 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. - JD LongAdemás, recomendaría agregar:
doBy
y las capacidades de selección y aplicación dedata.table
. - Iteratorsapply
es sólolapply
con la adición desimplify2array
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 utilizaf
en columnas por separado. Solo si hay un método para la clase 'data.frame' podríaf
obtener la aplicación de columnas porby
.aggregate
es genérico, por lo que existen diferentes métodos para diferentes clases del primer argumento. - IRTFMMnemónico: l es para 'lista', s es para 'simplificar', t es para 'por tipo' (cada nivel de la agrupación es un tipo) - Lutz Prechelt
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 - Stefanos