Depuis que j'ai réalisé que les (très excellentes) réponses de ce post manquent de by
y aggregate
explications. Voici ma contribution.
PAR
El by
comme indiqué dans la documentation, peut être considérée comme une "enveloppe" de la fonction tapply
. Le pouvoir de by
se présente lorsque nous voulons calculer une tâche qui tapply
ne peut pas gérer. 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
y cb
nous obtenons "essentiellement" les mêmes résultats et les seules différences résident dans la manière dont ils sont présentés et dans les différentes méthodes de calcul. class
attributs, respectivement by
para cb
y array
para ct
.
Comme je l'ai dit, le pouvoir de by
survient 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 le summary
de toutes les variables dans iris
le long du facteur Species
" : mais R ne peut pas le faire car il ne sait pas comment gérer.
Avec le by
R distribue une méthode spécifique pour data frame
puis laisser la classe summary
fonctionne même si la longueur du premier argument (et le type aussi) sont différents.
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. Il s'agit d'un objet de classe by
que le long de Species
(disons, pour chacun d'entre eux) calcule la 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 l'objet mean
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
AGGREGAT
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 l'option aggregate
doit être une liste tandis que tapply
peut (non obligatoire) soit une liste et que la sortie de aggregate
est un cadre de données tandis que celui de tapply
est un array
.
Le pouvoir de aggregate
est qu'il peut traiter facilement des sous-ensembles de données avec subset
et qu'il dispose de méthodes pour ts
les objets et formula
également.
Ces éléments font aggregate
plus facile de travailler 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
o 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 le long de Month
pour chaque élément, puis les combiner (notez également que nous devons appeler la fonction na.rm = TRUE
parce que le formula
méthodes de l aggregate
a par défaut la fonction 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
tandis qu'avec by
nous n'arrivons pas à réaliser qu'en fait l'appel de fonction suivant renvoie une erreur (mais il est fort probable qu'elle soit liée à 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 se situent uniquement au niveau de l'objet de la classe (et ensuite de la manière dont il est montré/imprimé et pas seulement -- exemple, comment le sous-ensembler) :
byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)
Le code précédent permet d'atteindre le même objectif et les mêmes résultats, à certains moments, le choix de l'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-ensembles.
35 votes
À votre question secondaire : pour beaucoup de choses, plyr est un remplacement direct de
*apply()
yby
. 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 bien des tracas.14 votes
Aussi, je recommanderais d'ajouter :
doBy
et les capacités de sélection et d'application dedata.table
.8 votes
sapply
est justelapply
avec l'ajout desimplify2array
sur la sortie.apply
se transforme en vecteur atomique, mais la sortie peut être un vecteur ou une liste.by
divise les cadres de données en sous-cadres de données, mais n'utilise pas la fonctionf
sur les colonnes séparément. Seulement s'il y a une méthode pour la classe 'data.frame'.f
sont appliqués en colonne parby
.aggregate
est générique, de sorte que différentes méthodes existent pour différentes classes du premier argument.10 votes
Mnémotechnique : l pour "liste", s pour "simplifier", t pour "par type" (chaque niveau du groupement est un type).
0 votes
Il existe également certaines fonctions dans le paquet Rfast, comme : eachcol.apply, apply.condition, et d'autres, qui sont plus rapides que les équivalents de R.