funciones ejemplos datos consultas avg agrupar agrupadas agregado agregacion r lapply sapply tapply r-faq

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



sum sql (9)

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

exterior

outer es una función muy útil oculta 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 usar de manera muy similar a 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 una 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 lo he usado 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 en 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 uso mucho esto, pero si está construyendo muchos paquetes o creando muchos entornos, puede ser útil.

Cada vez que quiero hacer algo "map" en R, normalmente trato de usar una función en la familia de apply .

Sin embargo, nunca he entendido muy bien las diferencias entre ellos: cómo { sapply , lapply , etc.} aplican la función a la entrada de entrada / agrupada, cómo se verá la salida, o incluso cómo puede ser la entrada, así que A menudo solo paso por todos ellos hasta que obtengo lo que quiero.

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

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

  1. sapply(vec, f) : entrada es un vector. la salida es un vector / matriz, donde el elemento i es f(vec[i]) , que le da una matriz si f tiene una salida de elementos múltiples

  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, g es empujado a los nombres de fila / columna
  5. by(dataframe, grouping, f) : sea g una agrupación. aplicar f a cada columna del grupo / marco de datos. Imprima bastante 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: ¿ plyr o reshape todo esto por completo?


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

(Esperamos que quede claro que apply corresponde a @ Hadley''s aaply y el aggregate corresponde a @ Hadley''s ddply etc. La diapositiva 20 del mismo slideshare aclarará si no lo obtiene de esta imagen).

(a la izquierda está la entrada, en la parte superior está la salida)


Desde que me di cuenta de que (el excelente) respuestas de este post carecen de explicaciones aggregate . Aquí está mi contribución.

POR

Sin embargo, la función by , como se indica en la documentación, puede ser una "envoltura" para tapply . El poder de by surge cuando queremos calcular una tarea que tapply no puede 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 , "esencialmente" tenemos los mismos resultados y las únicas diferencias están en cómo se muestran y los diferentes atributos de class , respectivamente, 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, diga "queremos calcular el summary de todas las variables en el iris largo del factor Species ": pero R simplemente no puede hacerlo porque no sabe cómo manejarlas.

Con la función by R despacha un método específico para data frame clase de data frame y luego deje que la función de summary funcione incluso si la longitud del primer argumento (y el tipo también) es 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 verdad y el resultado es muy sorprendente. Es un objeto de clase by que a lo largo de Species (por ejemplo, para cada una de ellas) se calcula el summary de cada variable.

Tenga en cuenta que si el primer argumento es un data frame , la función distribuida debe tener un método para esa clase de objetos. Por ejemplo, si usamos este código con la función mean 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 diferente de uso de 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 del aggregate debe ser una lista, mientras que tapply puede (no obligatorio) ser una lista y que la salida del aggregate es un marco de datos, mientras que el de tapply es una array .

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

Estos elementos hacen que el aggregate sea ​​más fácil de trabajar con ese tapply en algunas situaciones. Aquí hay 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) es 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 otros momentos en los que no podemos usar by tapply y tenemos que usar el 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 del Month para cada elemento y luego combinarlos (también tenga en cuenta que debemos llamar a na.rm = TRUE , porque los métodos de formula de la función aggregate 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 que, de hecho, la siguiente llamada de función devuelva un error (pero lo más probable es que esté relacionado con la función suministrada, mean ):

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

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

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.


En la nota al margen, aquí es cómo las diversas funciones plyr corresponden a las funciones base *apply (desde la introducción al documento plyr de la página 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

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() se puede ldply() fácilmente a ldply() para producir una salida útil, etc.

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

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

Las funciones relacionadas tapply y sweep no tienen ninguna función correspondiente en plyr , y siguen siendo útiles. merge es útil para combinar resúmenes con los datos originales.


Hay muchas respuestas excelentes que discuten las diferencias en los casos de uso para cada función. Ninguna de las respuestas discute las diferencias en el rendimiento. Esto es razonable porque varias funciones esperan varias entradas y producen varias salidas, aunque 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 lo anterior, la creación de entrada a partir de los vectores se incluye en el tiempo, además, la función de apply no se mide.

He probado dos funciones diferentes sum y length a la vez. El volumen probado es de 50M en la entrada y 50K en la salida. También he incluido dos paquetes actualmente populares que no se utilizaron ampliamente en el momento en que se hizo la pregunta, data.table y dplyr . Definitivamente vale la pena mirar ambos si desea obtener un buen desempeño.

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


Primero comience con la excelente respuesta de Joran : algo dudoso puede mejorar eso.

Luego, las siguientes mnemotécnicas pueden ayudar a recordar las distinciones entre cada una. Mientras que algunos son obvios, otros pueden ser menos, por lo que encontrará justificación en las discusiones de Joran.

Mnemotécnica

  • lapply es una aplicación de lista que actúa sobre una lista o vector y devuelve una lista.
  • sapply es un simple lapply (la función predeterminada es devolver un vector o una matriz cuando sea posible)
  • vapply es una aplicación verificada (permite que el tipo de objeto de retorno sea preespecificado)
  • rapply es una solicitud recursiva para listas anidadas, es decir, listas dentro de listas
  • tapply es una aplicación etiquetada donde las etiquetas identifican los subconjuntos
  • apply es 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 Fondo Correcto

Si el uso de la familia de apply todavía te parece un poco extraño, entonces es posible que te falte un punto de vista clave.

Estos dos artículos pueden ayudar. Proporcionan los antecedentes necesarios para motivar las técnicas de programación funcional que proporciona la familia de funciones de aplicación.

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


R tiene muchas * funciones de aplicación que se describen con habilidad en los archivos de ayuda (p ?apply Ej. ?apply ). Sin embargo, hay suficientes de ellos para que los ReR comienzos tengan dificultades para decidir cuál es el adecuado para su situación o incluso para recordarlos a todos. Pueden tener la sensación general de que "debería estar usando una función de * aplicación aquí", pero puede ser difícil mantenerlos a todos 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 paquete plyr extremadamente popular, las funciones básicas siguen siendo útiles y vale la pena conocerlas.

El objetivo de esta respuesta es actuar como una señal para los nuevos useRs para ayudarlos a dirigir la función de aplicación correcta * para su problema particular. Tenga en cuenta que esto no pretende simplemente regurgitar o reemplazar la documentación de R! La esperanza es que esta respuesta le ayude a decidir cuál * función de aplicación se adapta a su situación y luego depende de usted investigar más a fondo. 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 recomendable para marcos de datos, ya que primero se forzará 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 quiere medias o sumas de fila / columna para una matriz 2D, asegúrese de investigar los colMeans , las rowMeans , las colSums , las rowSums y las rowSums altamente optimizadas y colSums rowSums .

  • lapply : cuando desee aplicar una función a cada elemento de una lista y volver a obtener una lista.

    Este es el caballo de batalla de muchas de las otras * funciones de aplicación. Despegue su código y, a menudo, encontrará una 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

  • sapply : cuando desea aplicar una función a cada elemento de una lista, a su vez, pero desea un vector de vuelta, en lugar de una lista.

    Si se encuentra escribiendo unlist(lapply(...)) , deténgase y considere la posibilidad de 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á forzar 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.

  • vapply : cuando desea utilizar sapply pero quizás necesite sapply poco más de 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 ahorrarle tiempo al forzar los 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

  • mapply : 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., forzando 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 : una envoltura para mapply con mapply SIMPLIFY = FALSE , por lo que se garantiza que devuelva 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

  • rapply : para cuando desea aplicar una función a cada elemento de una estructura de lista anidada , recursivamente.

    Para darle una idea de lo poco rapply es rapply , ¡me olvidé de eso cuando rapply 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")

  • tapply : para cuando desea 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 * aplicar, de tipo. El uso del archivo de ayuda de la frase "matriz irregular" puede ser un poco confusing , 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 split-apply-combine que son comunes en R ( aggregate , by , ave , ddply , etc.) De ahí su estado de oveja negra.


Recientemente descubrí la función de sweep bastante útil y la agregué aquí para completarla:

barrer

La idea básica es barrer a través de una fila o columna en forma de columna y devolver una matriz modificada. Un ejemplo lo aclarará (fuente: datacamp ):

Digamos que tienes una matriz y quieres standardize en columnas:

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


Tal vez vale la pena mencionar ave . ave es la prima amiga de tapply . Devuelve los 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 es como tapply para marcos de datos). Pero puedes falsearlo:

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