Desde que me di cuenta de que (los muy excelentes) respuestas de esta entrada falta de by
y aggregate
explicaciones. Aquí está mi contribución.
POR
Sin by
embargo, la función, como se indica en la documentación, puede ser un "contenedor" 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 class
atributos, respectivamente by
para cb
y array
para ct
.
Como he dicho, el poder de by
surge cuando no podemos usarlo 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 las mismas longitudes, digamos "queremos calcular el summary
de todas las variables a lo iris
largo del factor Species
": pero R simplemente no puede hacer eso porque no sabe cómo manejarlo.
Con la by
función R, despache un método específico para la data frame
clase y luego deje que la summary
función funcione 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
que 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 a data frame
, la función despachada debe tener un método para esa clase de objetos. Por ejemplo, si usamos este código con la 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 diferente de uso tapply
si la 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 que tapply
puede (no obligatorio) ser una lista y que la salida de aggregate
es un marco de datos mientras que el de tapply
es un array
.
El poder de esto aggregate
es que puede manejar fácilmente subconjuntos de datos con subset
argumentos y que también tiene métodos para ts
objetos formula
.
Estos elementos hacen que aggregate
sea 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
una sola llamada, pero tenemos que calcular la media Month
para cada elemento y luego combinarlos (también tenga en cuenta que tenemos que llamar a na.rm = TRUE
, porque los formula
métodos de la aggregate
función tienen 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 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 el objeto de clase (y luego cómo se muestra / imprime y no solo - por ejemplo, cómo subconjunto):
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.
*apply()
yby
. plyr (al menos para mí) parece mucho más coherente en el sentido de que siempre sé exactamente qué formato de datos espera y qué es lo que escupirá. Eso me ahorra mucha molestia.