Как суммировать данные по группам в R? [закрыто]


181

У меня есть фрейм данных R, как это:

        age group
1   23.0883     1
2   25.8344     1
3   29.4648     1
4   32.7858     2
5   33.6372     1
6   34.9350     1
7   35.2115     2
8   35.2115     2
9   35.2115     2
10  36.7803     1
...

Мне нужно получить фрейм данных в следующем виде:

group mean     sd
1     34.5     5.6
2     32.3     4.2
...

Номер группы может отличаться, но их имена и количество можно узнать по телефону levels(factor(data$group))

Какие манипуляции нужно выполнить с данными, чтобы получить результат?


запятые в результирующем фрейме данных означают что-то особенное, или это просто десятичная точка?
mpiktas

@mpiktas Спасибо, что заметили. Исправленный. Это были проблемы с локалью (я русский) - мы используем запятую для десятичного разделения.
Юрий Петровский

3
Я подозревал это. Вся Европа использует запятую, кроме англичан.
mpiktas

4
Несмотря на то, что я не британец, я предпочитаю точку для десятичного разделителя.
Роман Луштрик

1
См. aggregate, А tapplyзатем stackoverflow.com для любых последующих вопросов кодирования этого типа.
конъюнктурный

Ответы:


140

Вот вариант plyr с одной строкой, использующий ddply :

dt <- data.frame(age=rchisq(20,10),group=sample(1:2,20,rep=T))
ddply(dt,~group,summarise,mean=mean(age),sd=sd(age))

Вот еще один однострочный вариант с использованием нового пакета data.table .

dtf <- data.frame(age=rchisq(100000,10),group=factor(sample(1:10,100000,rep=T)))
dt <- data.table(dtf)
dt[,list(mean=mean(age),sd=sd(age)),by=group]

Это быстрее, хотя это заметно только в таблице с 100k строк. Времена на моем Macbook Pro с процессором Core 2 Duo 2,53 ГГц и R 2.11.1:

> system.time(aa <- ddply(dtf,~group,summarise,mean=mean(age),sd=sd(age)))
utilisateur     système      écoulé 
      0.513       0.180       0.692 
> system.time(aa <- dt[,list(mean=mean(age),sd=sd(age)),by=group])
utilisateur     système      écoulé 
      0.087       0.018       0.103 

Дальнейшая экономия возможна, если мы используем setkey:

> setkey(dt,group)
> system.time(dt[,list(mean=mean(age),sd=sd(age)),by=group])
utilisateur     système      écoulé 
      0.040       0.007       0.048 

2
@chl, это дало мне возможность опробовать этот новый пакет data.table . Это выглядит действительно многообещающе.
mpiktas

7
+6000 для data.table. Это действительно намного быстрее, чем ddply, даже для меня на наборах данных, меньших 100 КБ (у меня есть только с 20 КБ строк). Должно быть что-то делать с функциями, которые я применяю, но ddply займет несколько минут, а data.table - несколько секунд.
атомные атомы

Простая опечатка: я думаю, вы имели в виду dt <- data.table(dtf)не dt <- data.table(dt)во втором блоке кода. Таким образом, вы создаете таблицу данных из фрейма данных, а не из dtфункции из statsпакета. Я пытался редактировать его, но я не могу редактировать под шестью символами.
Кристофер Боттомс,

По моему (не скромному в данном случае) мнению, data.tableэто лучший способ для агрегирования данных, и этот ответ великолепен, но все же только царапает поверхность. Помимо превосходства по синтаксису, он также чрезвычайно гибок и обладает множеством дополнительных функций, которые включают соединения и внутреннюю механику. Проверьте FAQ, страницу GitHub или курс для получения дополнительной информации.
Geneorama

98

Одной из возможностей является использование агрегатной функции . Например,

aggregate(data$age, by=list(data$group), FUN=mean)[2]

дает вам второй столбец желаемого результата.


1
Не связывайтесь с вашим локальным сервером справки :-) +1, но смотрите мои комментарии к ответу @ steffen.
ЧЛ

Сделал вещь, позвонив, data.frame(group=levels(factor(data$group)),mean=(aggregate(data$age, by=list(data$group), FUN=mean)$x),sd=(aggregate(data$age, by=list(data$group), FUN=sd)$x))но я не уверен, что это правильный путь. Я не уверен, что произойдет, тогда результаты связанных столбцов будут в другом порядке (я думаю, что это возможно). Какое у тебя мнение?
Юрий Петровский

9
@Yuriy Строки не должны быть не в порядке, но вот способ сделать это одним вызовом aggregate():aggregate(age ~ group, data=dat, FUN = function(x) c(M=mean(x), SD=sd(x)))
lockedoff

@lockedoff: Спасибо, что завершили мой ответ!
ocram

27

Поскольку вы манипулируете фреймом данных, dplyrпакет, вероятно, является более быстрым способом сделать это.

library(dplyr)
dt <- data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T))
grp <- group_by(dt, group)
summarise(grp, mean=mean(age), sd=sd(age))

или эквивалентно, используя оператор dplyr/ magrittrpipe:

library(dplyr)
dt <- data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T))
group_by(dt, group) %>%
 summarise(mean=mean(age), sd=sd(age))

РЕДАКТИРОВАТЬ полное использование оператора трубы:

library(dplyr)
data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T)) %>%
  group_by(group) %>%
  summarise(mean=mean(age), sd=sd(age))

3
+1 за dplyr. Это сделало так много задач R простыми и многие из этих методов устарели.
gregmacfarlane

К сожалению, полное использование версии оператора pipe не работает для меня
dagcilibili

ты загрузил dplyr или magrittr?
Бастиан Quast

большое спасибо @bquast за указание на решение, plyrиз dplyrкоторого была вызвана функция суммирования, а не вызывающая проблему.
dagcilibili

12

Отлично, спасибо bquast за добавление решения dplyr!

Оказывается, что тогда, dplyr и data.table очень близки:

library(plyr)
library(dplyr)
library(data.table)
library(rbenchmark)

dtf <- data.frame(age=rchisq(100000,10),group=factor(sample(1:10,100000,rep=T)))
dt <- data.table(dtf)

setkey(dt,group)

a<-benchmark(ddply(dtf,~group,plyr:::summarise,mean=mean(age),sd=sd(age)),
         dt[,list(mean=mean(age),sd=sd(age)),by=group],
         group_by(dt, group) %>% summarise(mean=mean(age),sd=sd(age) ),
         group_by(dtf, group) %>% summarise(mean=mean(age),sd=sd(age) )
)

a[, c(1,3,4)]

data.table по-прежнему самый быстрый, за ним очень тесно следует dplyr (), что интересно выглядит на data.frame быстрее, чем data.table:

                                                              test elapsed relative
1 ddply(dtf, ~group, plyr:::summarise, mean = mean(age), sd = sd(age))   1.689    4.867
2               dt[, list(mean = mean(age), sd = sd(age)), by = group]   0.347    1.000
4   group_by(dtf, group) %>% summarise(mean = mean(age), sd = sd(age))   0.369    1.063
3    group_by(dt, group) %>% summarise(mean = mean(age), sd = sd(age))   0.580    1.671

Сначала я подумал, что вам нужно переместить setkey в тест, но оказалось, что это занимает совсем немного времени.
Кастерма

10

В дополнение к существующим предложениям, вы можете проверить describe.byфункцию в psychпакете.

Он предоставляет ряд описательных статистических данных, включая среднее значение и стандартное отклонение на основе группирующей переменной.


его приятно, но несколько сложно экспортировать в LaTeX IME.
richiemorrisroe

10

Я нашел функцию summaryByв пакете doBy наиболее удобной для этого:

library(doBy)

age    = c(23.0883, 25.8344, 29.4648, 32.7858, 33.6372,
           34.935,  35.2115, 35.2115,  5.2115, 36.7803)
group  = c(1, 1, 1, 2, 1, 1, 2, 2, 2, 1)
dframe = data.frame(age=age, group=group)

summaryBy(age~group, data=dframe, FUN=c(mean, sd))
# 
#   group age.mean    age.sd
# 1     1 30.62333  5.415439
# 2     2 27.10507 14.640441

9

Используйте sqldfпакет. Это позволяет теперь использовать SQL для обобщения данных. После загрузки вы можете написать что-то вроде -

sqldf('  select group,avg(age) from data group by group  ')

8

Отредактировано: в соответствии с предложениями ЧЛ

Функция, которую вы ищете, называется «tapply», которая применяет функцию для каждой группы, определенной фактором.

# create some artificial data
set.seed(42)
groups <- 5

agedat <- c()
groupdat <- c()

for(group in 1:groups){
    agedat <- c(agedat,rnorm(100,mean=0 + group,1/group))
    groupdat <- c(groupdat,rep(group,100))
}
dat <- data.frame("age"=agedat,"group"=factor(groupdat))

# calculate mean and stdev age per group
res <- rbind.data.frame(group=1:5, with(dat, tapply(age, group, function(x) c(mean(x), sd(x)))))
names(res) <- paste("group",1:5)
row.names(res)[2:3] <- c("mean","sd")

Я действительно предлагаю проработать базовый учебник по R, объясняющий все часто используемые структуры данных и методы. В противном случае вы застрянете каждый дюйм во время программирования. Посмотрите этот вопрос для коллекции бесплатных доступных ресурсов.


2
@steffen +1, но здесь нет необходимости в forцикле, вы можете создать свой встроенный кадр данных, IMO. Для tapplyзвонка используйте function(x) c(mean(x),sd(x)))и cbindрезультат, так как ОП запрашивает обе статистики. Также ddplyиз пакета plyr можно было сделать это плавно.
ЧЛ

@steffen Проблема в том, что мне нужна именно та структура таблицы, которую я описал. Нет проблем с получением средств и сд. Проблема со структурой.
Юрий Петровский

@chl: Спасибо за ваш комментарий, не знал о plyr :). Я добавил cbind, но остальное оставил нетронутым. Если кто-то другой возьмёт кредит, этот ответ останется менее оптимальным примером.
Штеффен

@Yuriy: добавлен cbind. Если вы уже знали, как применять функции для каждой группы, вы можете переформулировать свой вопрос (просто для ясности;)).
Штеффен

@steffen cbind("mean"=mperage,"stdev"=stperage) gives no 'group' column. Will be joining by cbind (группа = уровни (фактор (данные $ группа)), "среднее" = mperage, "stdev" = stperage) `правильно?
Юрий Петровский

7

Вот пример с функцией, которую aggregates()я сделал сам некоторое время назад:

# simulates data
set.seed(666)
( dat <- data.frame(group=gl(3,6), level=factor(rep(c("A","B","C"), 6)), 
                    y=round(rnorm(18,10),1)) )

> dat
   group level    y
1      1     A 10.8
2      1     B 12.0
3      1     C  9.6
4      1     A 12.0
5      1     B  7.8
6      1     C 10.8
7      2     A  8.7
8      2     B  9.2
9      2     C  8.2
10     2     A 10.0
11     2     B 12.2
12     2     C  8.2
13     3     A 10.9
14     3     B  8.3
15     3     C 10.1
16     3     A  9.9
17     3     B 10.9
18     3     C 10.3

# aggregates() function
aggregates <- function(formula, data=NULL, FUNS){ 
    if(class(FUNS)=="list"){ 
        f <- function(x) sapply(FUNS, function(fun) fun(x)) 
    }else{f <- FUNS} 
    temp <- aggregate(formula, data, f) 
    out <- data.frame(temp[,-ncol(temp)], temp[,ncol(temp)]) 
    colnames(out)[1] <- colnames(temp)[1] 
return(out) 
} 

# example 
FUNS <- function(x) c(mean=round(mean(x),0), sd=round(sd(x), 0)) 
( ag <- aggregates(y~group:level, data=dat, FUNS=FUNS) ) 

Это дает следующий результат:

> ag
  group level mean sd
1     1     A   11  1
2     2     A    9  1
3     3     A   10  1
4     1     B   10  3
5     2     B   11  2
6     3     B   10  2
7     1     C   10  1
8     2     C    8  0
9     3     C   10  0

Может быть, вы можете получить тот же результат, начиная с функции R split ():

> with(dat, sapply( split(y, group:level), FUNS ) )
     1:A 1:B 1:C 2:A 2:B 2:C 3:A 3:B 3:C
mean  11  10  10   9  11   8  10  10  10
sd     1   3   1   1   2   0   1   2   0

Позвольте мне вернуться к выводу aggregatesфункции. Вы можете превратить его в красивую таблицу , используя reshape(), xtabs()и ftable():

rag <- reshape(ag, varying=list(3:4), direction="long", v.names="y") 
rag$time <- factor(rag$time) 
ft <- ftable(xtabs(y~group+level+time, data=rag)) 
attributes(ft)$col.vars <- list(c("mean","sd")) 

Это дает:

> ft 
             mean sd
group level         
1     A        11  1
      B        10  3
      C        10  1
2     A         9  1
      B        11  2
      C         8  0
3     A        10  1
      B        10  2
      C        10  0

Красиво, не правда ли? Вы можете экспортировать эту таблицу в pdf с textplot()функцией gplotsпакета.

Смотрите здесь для других решений.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.