Pregunta Funciones de agrupamiento (por ejemplo, por, agregado) y la familia * apply


Cada vez que quiero hacer algo "mapa" py en R, generalmente trato de usar una función en el apply familia.

Sin embargo, nunca he entendido las diferencias entre ellos, cómo {sapply, lapply, etc.} aplique la función a la entrada / entrada agrupada, cómo se verá la salida, o incluso cuál será la entrada, así que a menudo los repaso todos hasta que obtengo lo que quiero.

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

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

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

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

  3. apply(matrix, 1/2, f): entrada es una matriz. salida es un vector, donde el elemento i es f (fila / col i de la matriz)
  4. tapply(vector, grouping, f): salida es una matriz / matriz, donde un elemento en 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 un grupo. aplicar f a cada columna del grupo / marco de datos. imprimir bonita 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 el resultado, el agregado pega todo en un marco de datos.

Pregunta complementaria: todavía no he aprendido plyr o remodelado - lo haría plyr o reshape reemplazar todos estos por completo?


915
2017-08-17 18:31


origen


Respuestas:


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

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

Esta respuesta está destinada a actuar como una especie de señalizar para nuevos usuarios para ayudarlos a dirigirlos a la función correcta * apply 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 adapte a su situación y luego depende de usted investigar más a fondo. Con una excepción, las diferencias de rendimiento no se abordarán.

  • 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 marcos de datos, ya que forzará a una matriz primero.

    # 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 el altamente optimizado, rapidísimo colMeans, rowMeans, colSums, rowSums.

  • lapply - Cuando desee aplicar una función a cada elemento de un enumere a su vez y obtenga una lista de vuelta.

    Este es el caballo de batalla de muchas de las otras * funciones de aplicación. Pelar respaldar su código y a menudo encontrará 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 desee aplicar una función a cada elemento de un lista a su vez, pero quieres un vector volver, 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 al resultado a una matriz multidimensional, si corresponde. 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 de 2 dimensiones, 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 usará las matrices individuales para construir una matriz multidimensional:

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

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

  • vapply - Cuando quieras usar sapply pero tal vez necesites exprime un poco más de velocidad de tu código.

    por vapply, básicamente le das a R un ejemplo de qué tipo de cosa su función volverá, lo que puede ahorrar algo de tiempo coaccionando devuelto valores para encajar 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 tienes varias estructuras de datos (p. vectores, listas) y desea aplicar una función a los primeros elementos de cada uno, y luego los segundos elementos de cada uno, etc., coaccionando el resultado a un vector / matriz como en sapply.

    Esto es multivariante en el sentido de que su función debe aceptar argumentos múltiples

    #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 SIMPLIFY = FALSE, por lo que se garantiza que 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
    
  • rapply - Para cuando desea aplicar una función a cada elemento de un lista anidada estructura, recursivamente

    Para darte una idea de cuán poco común rapply es, 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")
    
  • tapply - Para cuando quieres aplicar una función a subconjuntos de un vector y los subconjuntos están definidos por algún otro vector, generalmente un factor.

    Las ovejas negras de la * familia de aplicación, de tipo. El uso del archivo de ayuda de la frase "matriz desigual" puede ser un poco confuso, pero en realidad es bastante sencillo.

    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 se definen los subgrupos por las combinaciones únicas de una lista de varios factores. tapply es similar en espíritu a las funciones de dividir-aplicar-combinar que son común en R (aggregate, by, ave, ddply, etc.) De ahí su estado de oveja negra.


1204
2017-08-21 22:50



En la nota lateral, aquí está cómo los diversos plyr las funciones corresponden a la base *apply funciones (desde la introducción al documento plyr desde 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 consistentes 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 el resultado, en ese resultado de dlply() es fácilmente pasable a ldply() para producir resultados útiles, etc.

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

plyr y reshape las funciones han reemplazado casi todas estas funciones en mi uso diario. Pero, también desde el documento de introducción a Plyr:

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


171
2017-08-17 19:20



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

apply, sapply, lapply, by, aggregate

(Espero que esté claro que apply corresponde a @ Hadley's aaply y aggregate corresponde a @ Hadley's ddply etc. La diapositiva 20 del mismo slideshare aclarará si no la obtiene de esta imagen).

(a la izquierda se ingresa, en la parte superior sale)


118
2017-10-09 05:29



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

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

Mnemotécnica

  • lapply es un lista aplicar que actúa en una lista o vector y devuelve una lista.
  • sapply es un sencillo  lapply (la función predeterminada es devolver un vector o matriz cuando sea posible)
  • vapply es un verificado aplica (permite que el tipo de objeto de retorno sea preespecificado)
  • rapply es un recursivo solicitar listas anidadas, es decir, listas dentro de listas
  • tapply es un etiquetado aplicar 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 usa el apply la familia todavía se siente un poco extraña para ti, entonces es posible que te falte un punto de vista clave.

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

Los usuarios de Lisp reconocerán el paradigma inmediatamente. Si no está familiarizado con Lisp, una vez que tenga su cabeza alrededor de FP, habrá ganado un poderoso punto de vista para usar en R - y apply tendrá mucho más sentido.


88
2018-04-25 00:20



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

POR

los by función, como se indica en la documentación puede ser, como un "contenedor" para tapply. El poder de by surge cuando queremos calcular una tarea que tapplyno 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, "esencialmente" tenemos los mismos resultados y las únicas diferencias están en cómo se muestran y en los 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 duración, decir "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 el by función R despachar 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 verdad y el resultado es muy sorprendente. Es un objeto de clase by eso a lo largo Species (por ejemplo, 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, utilizamos este código con el mean función tendremos este código que no tiene ningún sentido en absoluto:

 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 se puede ver como otra forma de uso diferente tapply si lo usamos de tal 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  poder (no obligatorio) ser una lista y que el resultado 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 los 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 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) 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 que no podemos usar by o 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 Month para cada elemento y luego combinarlos (también tenga en cuenta que tenemos que llamar al na.rm = TRUE, porque el formula métodos del 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 con by simplemente no podemos lograr eso, de hecho, la siguiente llamada de función devuelve 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 la clase (y luego 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.


39
2017-08-28 02:28



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. Eso es razonable porque varias funciones esperan varias entradas y producen varios resultados, 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 lo anterior, la creación de entrada de los vectores se incluye en el tiempo, también el applyla función no se mide

He probado dos funciones diferentes sum y length En seguida. El volumen probado es 50M en la entrada y 50K en la salida. También incluí dos paquetes actualmente populares que no se usaban ampliamente en el momento en que se formulaba la pregunta, data.table y dplyr. Ambos definitivamente valen la pena si buscas 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

30
2017-12-08 22:42



Tal vez vale la pena mencionar ave. ave es tapplyprimo amistoso. Devuelve resultados en una forma 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 cambiarlo:

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

20
2017-11-06 00:00



A pesar de todas las excelentes respuestas aquí, hay 2 funciones base más que merecen ser mencionadas, las útiles 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 del tipo de álgebra lineal. Sin embargo, se puede usar de manera muy similar mapply para aplicar una función a dos vectores de entradas. La diferencia es que mapply aplicará la función a los primeros dos elementos y luego a los segundos dos, etc. 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 con qué condiciones.

eapply

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 uso mucho esto, pero si construyes muchos paquetes o creas muchos entornos, puede ser útil.


20
2018-05-16 03:59