Як узагальнити дані по групах у 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 з процесором 2,53 ГГц Core 2 Duo та 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, це дало мені можливість випробувати цей новий пакет даних.table . Це виглядає дійсно перспективно.
mpiktas

7
+6000 для даних.table. Це дійсно набагато швидше, ніж ddply, навіть для мене на наборах даних менше 100 к (я маю лише 20 к рядків). Повинно бути щось із функціями, які я застосовую, але ddply займе хвилини та таблицю даних. Кілька секунд.
atomicules

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

На мою (в цьому випадку не скромну) думку, data.tableце найкращий спосіб узагальнення даних, і ця відповідь чудова, але все ще лише подряпини по поверхні. Окрім синтаксичного вищого рівня, він також надзвичайно гнучкий та має багато вдосконалених функцій, які передбачають з'єднання та внутрішню механіку. Перегляньте поширені запитання, сторінку github або курс для отримання додаткової інформації.
генеорама

97

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

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

дає другий стовпець бажаного результату.


1
Не посилайтеся на ваш локальний сервер довідки :-) +1, але дивіться мої коментарі до відповіді @ steffen.
chl

Зробили річ за телефоном, 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: Дякую, що ви виконали мою відповідь!
окрам

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/ magrittrтруба:

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

EDIT повне використання трубного оператора:

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

Повне використання версії трубного оператора для мене, на жаль, не працює
dagcilibili

ви завантажили dplyr чи magrittr?
Квастіа Бастіян

велике спасибі @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 в орієнтир, але виявилося, що це зовсім не займає часу.
kasterma

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

Відредаговано: За пропозиціями chl

Функція, яку ви шукаєте, називається "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")

Я дійсно пропоную опрацювати базовий навчальний посібник, де пояснюються всі часто використовувані структури даних та методи. В іншому випадку ви будете застрягати кожен дюйм під час програмування. Дивіться це питання для зібрання безкоштовних доступних ресурсів.


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

@steffen Проблема в тому, що мені потрібна саме таблиця структури, яку я описав. Немає проблем із отриманням засобів та SD. Проблема - з структурою.
Юрій Петровський

@chl: Дякую за ваш коментар, не знав про plyr :). Я додав cbind, а решту залишив недоторканою. Якщо інший може взяти кредит, ця відповідь залишиться менш оптимальним прикладом.
steffen

@ Юрій: Додано скруту. Якщо ви вже знали, як застосувати функції для кожної групи, ви можете переформулювати своє запитання (лише для наочності;)).
steffen

@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.