J'ai un data.frame
et je dois calculer la moyenne par groupe (c'est-à-dire par Month
, ci-dessous).
Name Month Rate1 Rate2
Aira 1 12 23
Aira 2 18 73
Aira 3 19 45
Ben 1 53 19
Ben 2 22 87
Ben 3 19 45
Cat 1 22 87
Cat 2 67 43
Cat 3 45 32
Ma sortie souhaitée est comme ci-dessous, où les valeurs de Rate1
et Rate2
sont les moyennes du groupe. S'il vous plaît ne tenez pas compte de la valeur, je l'ai inventé pour l'exemple.
Name Rate1 Rate2
Aira 23.21 12.2
Ben 45.23 43.9
Cat 33.22 32.2
Ce type d’opération correspond exactement à ce que aggregate
a été conçu pour:
d <- read.table(text=
'Name Month Rate1 Rate2
Aira 1 12 23
Aira 2 18 73
Aira 3 19 45
Ben 1 53 19
Ben 2 22 87
Ben 3 19 45
Cat 1 22 87
Cat 2 67 43
Cat 3 45 32', header=TRUE)
aggregate(d[, 3:4], list(d$Name), mean)
Group.1 Rate1 Rate2
1 Aira 16.33333 47.00000
2 Ben 31.33333 50.33333
3 Cat 44.66667 54.00000
Ici, nous agrégons les colonnes 3 et 4 de data.frame d
, en regroupant par d$Name
et en appliquant la fonction mean
.
Ou, en utilisant une interface de formule:
aggregate(. ~ Name, d[-2], mean)
Ou utilisez group_by
& summarise_at
à partir du package dplyr
:
library(dplyr)
d %>%
group_by(Name) %>%
summarise_at(vars(-Month), funs(mean(., na.rm=TRUE)))
# A tibble: 3 x 3
Name Rate1 Rate2
<fct> <dbl> <dbl>
1 Aira 16.3 47.0
2 Ben 31.3 50.3
3 Cat 44.7 54.0
Voir ?summarise_at
pour les nombreuses façons de spécifier les variables sur lesquelles agir. Ici, vars(-Month)
dit toutes les variables à l'exception de Month
.
Vous pouvez également utiliser le paquetage plyr
, qui est en quelque sorte plus polyvalent:
library(plyr)
ddply(d, .(Name), summarize, Rate1=mean(Rate1), Rate2=mean(Rate2))
Name Rate1 Rate2
1 Aira 16.33333 47.00000
2 Ben 31.33333 50.33333
3 Cat 44.66667 54.00000
Une troisième bonne alternative consiste à utiliser le package data.table
, qui contient également la classe data.frame, mais les opérations que vous recherchez sont calculées beaucoup plus rapidement.
library(data.table)
mydt <- structure(list(Name = c("Aira", "Aira", "Aira", "Ben", "Ben", "Ben", "Cat", "Cat", "Cat"), Month = c(1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L), Rate1 = c(15.6396600443877, 2.15649279424609, 6.24692918928743, 2.37658797276116, 34.7500663272292, 3.28750138697048, 29.3265553981065, 17.9821839334431, 10.8639802575958), Rate2 = c(17.1680489538369, 5.84231656330206, 8.54330866437461, 5.88415184986176, 3.02064294862551, 17.2053351400752, 16.9552950199166, 2.56058000170089, 15.7496228048122)), .Names = c("Name", "Month", "Rate1", "Rate2"), row.names = c(NA, -9L), class = c("data.table", "data.frame"))
Maintenant, prenons la moyenne de Rate1 et Rate2 pour les 3 mois, pour chaque personne (Nom): Commencez par choisir les colonnes que vous voulez utiliser comme moyenne.
colstoavg <- names(mydt)[3:4]
Maintenant nous utilisons lapply pour prendre la moyenne sur les colonnes que nous voulons avg (colstoavg)
mydt.mean <- mydt[,lapply(.SD,mean,na.rm=TRUE),by=Name,.SDcols=colstoavg]
mydt.mean
Name Rate1 Rate2
1: Aira 8.014361 10.517891
2: Ben 13.471385 8.703377
3: Cat 19.390907 11.755166
Il existe différentes manières de le faire dans la méthode R
de base, y compris une approche alternative aggregate
. Les exemples ci-dessous renvoient par mois, ce qui, je pense, est ce que vous avez demandé. Bien que la même approche puisse être utilisée pour renvoyer des moyennes par personne:
Utiliser ave
:
my.data <- read.table(text = '
Name Month Rate1 Rate2
Aira 1 12 23
Aira 2 18 73
Aira 3 19 45
Ben 1 53 19
Ben 2 22 87
Ben 3 19 45
Cat 1 22 87
Cat 2 67 43
Cat 3 45 32
', header = TRUE, stringsAsFactors = FALSE, na.strings = 'NA')
Rate1.mean <- with(my.data, ave(Rate1, Month, FUN = function(x) mean(x, na.rm = TRUE)))
Rate2.mean <- with(my.data, ave(Rate2, Month, FUN = function(x) mean(x, na.rm = TRUE)))
my.data <- data.frame(my.data, Rate1.mean, Rate2.mean)
my.data
Utiliser by
:
my.data <- read.table(text = '
Name Month Rate1 Rate2
Aira 1 12 23
Aira 2 18 73
Aira 3 19 45
Ben 1 53 19
Ben 2 22 87
Ben 3 19 45
Cat 1 22 87
Cat 2 67 43
Cat 3 45 32
', header = TRUE, stringsAsFactors = FALSE, na.strings = 'NA')
by.month <- as.data.frame(do.call("rbind", by(my.data, my.data$Month, FUN = function(x) colMeans(x[,3:4]))))
colnames(by.month) <- c('Rate1.mean', 'Rate2.mean')
by.month <- cbind(Month = rownames(by.month), by.month)
my.data <- merge(my.data, by.month, by = 'Month')
my.data
Utiliser lapply
et split
:
my.data <- read.table(text = '
Name Month Rate1 Rate2
Aira 1 12 23
Aira 2 18 73
Aira 3 19 45
Ben 1 53 19
Ben 2 22 87
Ben 3 19 45
Cat 1 22 87
Cat 2 67 43
Cat 3 45 32
', header = TRUE, stringsAsFactors = FALSE, na.strings = 'NA')
ly.mean <- lapply(split(my.data, my.data$Month), function(x) c(Mean = colMeans(x[,3:4])))
ly.mean <- as.data.frame(do.call("rbind", ly.mean))
ly.mean <- cbind(Month = rownames(ly.mean), ly.mean)
my.data <- merge(my.data, ly.mean, by = 'Month')
my.data
Utiliser sapply
et split
:
my.data <- read.table(text = '
Name Month Rate1 Rate2
Aira 1 12 23
Aira 2 18 73
Aira 3 19 45
Ben 1 53 19
Ben 2 22 87
Ben 3 19 45
Cat 1 22 87
Cat 2 67 43
Cat 3 45 32
', header = TRUE, stringsAsFactors = FALSE, na.strings = 'NA')
my.data
sy.mean <- t(sapply(split(my.data, my.data$Month), function(x) colMeans(x[,3:4])))
colnames(sy.mean) <- c('Rate1.mean', 'Rate2.mean')
sy.mean <- data.frame(Month = rownames(sy.mean), sy.mean, stringsAsFactors = FALSE)
my.data <- merge(my.data, sy.mean, by = 'Month')
my.data
Utiliser aggregate
:
my.data <- read.table(text = '
Name Month Rate1 Rate2
Aira 1 12 23
Aira 2 18 73
Aira 3 19 45
Ben 1 53 19
Ben 2 22 87
Ben 3 19 45
Cat 1 22 87
Cat 2 67 43
Cat 3 45 32
', header = TRUE, stringsAsFactors = FALSE, na.strings = 'NA')
my.summary <- with(my.data, aggregate(list(Rate1, Rate2), by = list(Month),
FUN = function(x) { mon.mean = mean(x, na.rm = TRUE) } ))
my.summary <- do.call(data.frame, my.summary)
colnames(my.summary) <- c('Month', 'Rate1.mean', 'Rate2.mean')
my.summary
my.data <- merge(my.data, my.summary, by = 'Month')
my.data
Je décris deux manières de procéder, l’une basée sur le package data.table et l’autre sur le package reshape2. La méthode data.table a déjà une réponse, mais j'ai essayé de la rendre plus propre et plus détaillée.
Les données sont comme ceci:
d <- structure(list(Name = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 3L,
3L, 3L), .Label = c("Aira", "Ben", "Cat"), class = "factor"),
Month = c(1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L), Rate1 = c(12L,
18L, 19L, 53L, 22L, 19L, 22L, 67L, 45L), Rate2 = c(23L, 73L,
45L, 19L, 87L, 45L, 87L, 43L, 32L)), .Names = c("Name", "Month",
"Rate1", "Rate2"), class = "data.frame", row.names = c(NA, -9L
))
head(d)
Name Month Rate1 Rate2
1 Aira 1 12 23
2 Aira 2 18 73
3 Aira 3 19 45
4 Ben 1 53 19
5 Ben 2 22 87
6 Ben 3 19 45
library("reshape2")
mym <- melt(d, id = c("Name"))
res <- dcast(mym, Name ~ variable, mean)
res
#Name Month Rate1 Rate2
#1 Aira 2 16.33333 47.00000
#2 Ben 2 31.33333 50.33333
#3 Cat 2 44.66667 54.00000
Utiliser data.table:
# At first, I convert the data.frame to data.table and then I group it
setDT(d)
d[, .(Rate1 = mean(Rate1), Rate2 = mean(Rate2)), by = .(Name)]
# Name Rate1 Rate2
#1: Aira 16.33333 47.00000
#2: Ben 31.33333 50.33333
#3: Cat 44.66667 54.00000
Il existe un autre moyen de le faire en évitant d’écrire plusieurs arguments pour j dans data.table en utilisant un fichier .SD.
d[, lapply(.SD, mean), by = .(Name)]
# Name Month Rate1 Rate2
#1: Aira 2 16.33333 47.00000
#2: Ben 2 31.33333 50.33333
#3: Cat 2 44.66667 54.00000
si nous voulons seulement avoir Rate1 et Rate2, alors nous pouvons utiliser le . SDcols comme suit:
d[, lapply(.SD, mean), by = .(Name), .SDcols = 3:4]
# Name Rate1 Rate2
#1: Aira 16.33333 47.00000
#2: Ben 31.33333 50.33333
#3: Cat 44.66667 54.00000
Vous pouvez également utiliser la fonction générique cbind()
et lm()
sans l'interception:
cbind(lm(d$Rate1~-1+d$Name)$coef,lm(d$Rate2~-1+d$Name)$coef)
> [,1] [,2]
>d$NameAira 16.33333 47.00000
>d$NameBen 31.33333 50.33333
>d$NameCat 44.66667 54.00000
Vous pouvez également accomplir cela en utilisant le paquetage sqldf
comme indiqué ci-dessous:
library(sqldf)
x <- read.table(text='Name Month Rate1 Rate2
Aira 1 12 23
Aira 2 18 73
Aira 3 19 45
Ben 1 53 19
Ben 2 22 87
Ben 3 19 45
Cat 1 22 87
Cat 2 67 43
Cat 3 45 32', header=TRUE)
sqldf("
select
Name
,avg(Rate1) as Rate1_float
,avg(Rate2) as Rate2_float
,avg(Rate1) as Rate1
,avg(Rate2) as Rate2
from x
group by
Name
")
# Name Rate1_float Rate2_float Rate1 Rate2
#1 Aira 16.33333 47.00000 16 47
#2 Ben 31.33333 50.33333 31 50
#3 Cat 44.66667 54.00000 44 54
Je suis un converti récent en dplyr
comme indiqué dans d'autres réponses, mais sqldf
est gentil, car la plupart des analystes/scientifiques/développeurs de données maîtrisent au moins un peu le langage SQL. De cette façon, je pense que cela tend à créer un code plus lisible par tous que dplyr
ou d’autres solutions présentées ci-dessus.
PDATE: En répondant au commentaire ci-dessous, j'ai tenté de mettre à jour le code comme indiqué ci-dessus. Cependant, le comportement n'était pas comme je m'y attendais. Il semble que la définition de colonne (c'est-à-dire int
vs float
) ne soit appliquée que lorsque l'alias de colonne correspond au nom de colonne d'origine. Lorsque vous spécifiez un nouveau nom, la colonne d'agrégat est renvoyée sans arrondi.