Depuis que je me suis rendu compte que (les très excellentes) réponses de ce post manquent by
et d' aggregate
explications. Voici ma contribution.
PAR
La by
fonction, comme indiqué dans la documentation, peut cependant être un "wrapper" pour tapply
. La puissance de by
surgit lorsque nous voulons calculer une tâche qui tapply
ne peut pas être gérée. Un exemple est ce code:
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 nous imprimons ces deux objets, ct
et cb
, nous avons "essentiellement" les mêmes résultats et les seules différences sont dans la façon dont ils sont affichés et les différents class
attributs, respectivement by
pour cb
et array
pour ct
.
Comme je l'ai dit, le pouvoir de by
surgit lorsque nous ne pouvons pas utiliser tapply
; le code suivant en est un exemple:
tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) :
arguments must have same length
R dit que les arguments doivent avoir les mêmes longueurs, disons "nous voulons calculer la summary
variable de iris
tout le long du facteur Species
": mais R ne peut tout simplement pas faire cela car il ne sait pas comment gérer.
Avec la by
fonction R, distribuez une méthode spécifique pour la data frame
classe, puis laissez la summary
fonction fonctionner même si la longueur du premier argument (et le type aussi) est différente.
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
cela fonctionne en effet et le résultat est très surprenant. C'est un objet de classe by
qui le long Species
(disons, pour chacun d'eux) calcule le summary
de chaque variable.
Notez que si le premier argument est un data frame
, la fonction distribuée doit avoir une méthode pour cette classe d'objets. Par exemple, si nous utilisons ce code avec la mean
fonction, nous aurons ce code qui n'a aucun sens:
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
AGRÉGAT
aggregate
peut être considéré comme un autre mode d'utilisation différent tapply
si nous l'utilisons de cette manière.
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
Les deux différences immédiates sont que le deuxième argument de aggregate
doit être une liste tandis que tapply
peut (non obligatoire) être une liste et que la sortie de aggregate
est une trame de données tandis que celle de tapply
est un array
.
La puissance de aggregate
est qu'il peut gérer facilement des sous-ensembles de données avec subset
argument et qu'il a des méthodes pour les ts
objets et formula
aussi.
Ces éléments aggregate
facilitent le travail avec cela tapply
dans certaines situations. Voici quelques exemples (disponibles dans la documentation):
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
Nous pouvons obtenir la même chose avec tapply
mais la syntaxe est légèrement plus difficile et la sortie (dans certaines circonstances) moins lisible:
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
Il y a d'autres moments où nous ne pouvons pas utiliser by
ou tapply
et nous devons utiliser 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
Nous ne pouvons pas obtenir le résultat précédent avec tapply
en un seul appel mais nous devons calculer la moyenne Month
pour chaque élément et ensuite les combiner (notez également que nous devons appeler le na.rm = TRUE
, car les formula
méthodes de la aggregate
fonction ont par défaut le 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
alors qu'avec by
nous, nous ne pouvons tout simplement pas y parvenir en fait, l'appel de fonction suivant renvoie une erreur (mais il est très probablement lié à la fonction fournie mean
):
by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)
D'autres fois, les résultats sont les mêmes et les différences ne concernent que la classe (et ensuite comment elle est affichée / imprimée et pas seulement - par exemple, comment la sous-définir).
byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)
Le code précédent atteint le même objectif et les mêmes résultats, à certains moments, quel outil utiliser n'est qu'une question de goûts et de besoins personnels; les deux objets précédents ont des besoins très différents en termes de sous-ensemble.
*apply()
etby
. plyr (du moins pour moi) semble beaucoup plus cohérent dans la mesure où je sais toujours exactement quel format de données il attend et exactement ce qu'il va cracher. Cela m'évite beaucoup de tracas.