Media por grupo en un data.frame

Tengo un data.frame y necesito calcular la media por grupo (es decir, por Month , a continuación).

 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 

Mi salida deseada es como abajo, donde los valores para Rate1 y Rate2 son los medios del grupo. Por favor, ignore el valor, lo inventé para el ejemplo.

 Name Rate1 Rate2 Aira 23.21 12.2 Ben 45.23 43.9 Cat 33.22 32.2 

Este tipo de operación es exactamente para lo que se diseñó el aggregate :

 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 

Aquí agregamos las columnas 3 y 4 de data.frame d , agrupando por d$Name y aplicando la función mean .


O, usando una interfaz de fórmula:

 aggregate(. ~ Name, d[-2], mean) 

O use group_by & summarise_at del paquete dplyr :

 library(dplyr) d %>% group_by(Name) %>% summarise_at(vars(-Month), funs(mean(., na.rm=TRUE))) # A tibble: 3 x 3 Name Rate1 Rate2    1 Aira 16.3 47.0 2 Ben 31.3 50.3 3 Cat 44.7 54.0 

Consulte ?summarise_at para conocer las diversas formas de especificar las variables para actuar. Aquí, vars(-Month) dice todas las variables excepto Month .

También puede usar el paquete plyr , que de alguna manera es más versátil:

 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 

Una tercera gran alternativa es usar el paquete data.table , que también tiene la clase data.frame, pero las operaciones que usted busca se computan mucho más rápido.

 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")) 

Ahora tome la media de Rate1 y Rate2 para los 3 meses, para cada persona (Nombre): Primero, decida qué columnas quiere tomar con el promedio de

 colstoavg <- names(mydt)[3:4] 

Ahora utilizamos lapply para tomar la media sobre las columnas que queremos prom (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 

Describo dos formas de hacer esto, una basada en data.table y otra basada en el paquete reshape2 . La forma de datos ya tiene una respuesta, pero he tratado de hacerlo más limpio y más detallado.

La información es así:

  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 

Usando 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 

Hay otra forma de hacerlo evitando escribir muchos argumentos para j en data.table usando un .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 solo queremos tener Rate1 y Rate2, entonces podemos usar los .SDcols de la siguiente manera:

 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 

Aquí hay una variedad de maneras de hacer esto en la base R incluye un enfoque aggregate alternativo. Los ejemplos que figuran a continuación significan por mes, que creo que es lo que solicitó. Aunque, el mismo enfoque podría usarse para devolver los medios por persona:

Usando 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 

Usando 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 

Usando lapply y 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 

Usando sapply y 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 

Usando 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 

También podría usar la función genérica cbind() y lm() sin la intercepción:

 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 

También puede lograr esto usando el paquete sqldf como se muestra a continuación:

 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 

Soy un converso reciente a dplyr como se muestra en otras respuestas, pero sqldf es bueno ya que la mayoría de los analistas de datos / científicos / desarrolladores de datos tienen al menos algo de fluidez en SQL. De esta manera, creo que tiende a dplyr código más legible universalmente que dplyr u otras soluciones presentadas anteriormente.

ACTUALIZACIÓN: Al responder al comentario a continuación, intenté actualizar el código como se muestra arriba. Sin embargo, el comportamiento no fue el esperado. Parece que la definición de la columna (es decir, int vs float ) solo se lleva a cabo cuando el alias de la columna coincide con el nombre de la columna original. Cuando especifica un nuevo nombre, la columna agregada se devuelve sin redondeo.