Рассчитать среднее значение для каждой группы (среднее по группе)

У меня есть большой кадр данных, похожий на этот:

df <- data.frame(dive=factor(sample(c("dive1","dive2"),10,replace=TRUE)),speed=runif(10))
> df
    dive      speed
1  dive1 0.80668490
2  dive1 0.53349584
3  dive2 0.07571784
4  dive2 0.39518628
5  dive1 0.84557955
6  dive1 0.69121443
7  dive1 0.38124950
8  dive2 0.22536126
9  dive1 0.04704750
10 dive2 0.93561651

Моя цель - усреднить значения одного столбца, когда другой столбец равен определенному значению, и повторите это для всех значений. то есть в приведенном выше примере я хотел бы вернуть среднее значение для столбца speed для каждого уникального значения столбца dive. Поэтому, когда dive==dive1, среднее значение для speed - это и так далее для каждого значения dive.

Ответы

Ответ 1

Существует много способов сделать это в R. В частности, by, aggregate, split и plyr, cast, tapply, data.table, dplyr и так д.

В широком смысле эти проблемы имеют форму split-apply-comb. Хэдли Уикхэм написал статью красивая статья, которая даст вам более глубокое понимание всей категории проблем, и это стоит того. Его пакет plyr реализует стратегию для общих структур данных, а dplyr - это более новая производительность реализации, настроенная для кадров данных. Они позволяют решать проблемы одной и той же формы, но имеют еще большую сложность, чем эта. Они хорошо изучают как общий инструмент для решения проблем с манипулированием данными.

Производительность - это проблема с очень большими наборами данных, и для этого трудно выполнить решения на основе data.table. Однако, если вы имеете дело только со средними наборами данных или меньше, то потратить время на изучение data.table, скорее всего, не стоит усилий. dplyr также может быть быстрым, поэтому это хороший выбор, если вы хотите ускорить процесс, но не нуждаетесь в масштабируемости data.table.

Многие другие решения, приведенные ниже, не требуют дополнительных пакетов. Некоторые из них даже довольно быстр на средних наборах данных. Их основным недостатком является либо метафора, либо гибкость. По метафоре я имею в виду, что это инструмент, предназначенный для чего-то другого, которого принуждают к решению этого конкретного типа проблемы "умным" способом. Благодаря гибкости я имею в виду, что им не хватает возможности решать широкий спектр подобных проблем или легко производить аккуратный вывод.


Примеры

base функции

tapply:

tapply(df$speed, df$dive, mean)
#     dive1     dive2 
# 0.5419921 0.5103974

aggregate

aggregate принимает в data.frames, выводит data.frames и использует интерфейс формулы.

aggregate( speed ~ dive, df, mean )
#    dive     speed
# 1 dive1 0.5790946
# 2 dive2 0.4864489

by

В самой удобной для пользователя форме он принимает в векторы и применяет к ним функцию. Однако его выход не находится в очень манипулируемой форме.:

res.by <- by(df$speed, df$dive, mean)
res.by
# df$dive: dive1
# [1] 0.5790946
# ---------------------------------------
# df$dive: dive2
# [1] 0.4864489

Чтобы обойти это, для простого использования by метод as.data.frame в библиотеке taRifx работает:

library(taRifx)
as.data.frame(res.by)
#    IDX1     value
# 1 dive1 0.6736807
# 2 dive2 0.4051447

split

Как следует из названия, он выполняет только "разделенную" часть стратегии split-apply-comb. Чтобы сделать все остальное, я напишу небольшую функцию, которая использует sapply для apply-comb. sapply автоматически упрощает результат в максимально возможной степени. В нашем случае это означает вектор, а не data.frame, так как у нас есть только 1 размерность результатов.

splitmean <- function(df) {
  s <- split( df, df$dive)
  sapply( s, function(x) mean(x$speed) )
}
splitmean(df)
#     dive1     dive2 
# 0.5790946 0.4864489 

Внешние пакеты

data.table

library(data.table)
setDT(df)[ , .(mean_speed = mean(speed)), by = dive]
#    dive mean_speed
# 1: dive1  0.5419921
# 2: dive2  0.5103974

dplyr

library(dplyr)
group_by(df, dive) %>% summarize(m = mean(speed))

plyr (предварительный указатель dplyr)

Здесь, что официальная страница говорит о plyr:

Его уже можно сделать с помощью base R-функций (например, split и семейство функций apply), но plyr упрощает все это с:

  • полностью согласованные имена, аргументы и выходы
  • удобная параллелизация через пакет foreach
  • ввод и вывод в data.frames, matrices и lists
  • индикаторы выполнения для отслеживания длительных операций
  • встроенное исправление ошибок и информативные сообщения об ошибках
  • которые поддерживаются во всех преобразованиях

Другими словами, если вы изучите один инструмент для манипуляции split-apply-comb, он должен быть plyr.

library(plyr)
res.plyr <- ddply( df, .(dive), function(x) mean(x$speed) )
res.plyr
#    dive        V1
# 1 dive1 0.5790946
# 2 dive2 0.4864489

reshape2

Библиотека reshape2 не сконструирована с использованием split-apply-comb в качестве основного фокуса. Вместо этого он использует двухкомпонентную стратегию таяния/литья для perfor m множество разнообразных задач по перестройке данных. Однако, поскольку он позволяет функцию агрегации, он может быть использован для этой проблемы. Это был не мой первый выбор для операций split-apply-comb, но его возможности по перестройке мощные, и поэтому вы также должны изучить этот пакет.

library(reshape2)
dcast( melt(df), variable ~ dive, mean)
# Using dive as id variables
#   variable     dive1     dive2
# 1    speed 0.5790946 0.4864489

Бенчмарки

10 строк, 2 группы

library(microbenchmark)
m1 <- microbenchmark(
  by( df$speed, df$dive, mean),
  aggregate( speed ~ dive, df, mean ),
  splitmean(df),
  ddply( df, .(dive), function(x) mean(x$speed) ),
  dcast( melt(df), variable ~ dive, mean),
  dt[, mean(speed), by = dive],
  summarize( group_by(df, dive), m = mean(speed) ),
  summarize( group_by(dt, dive), m = mean(speed) )
)

> print(m1, signif = 3)
Unit: microseconds
                                           expr  min   lq   mean median   uq  max neval      cld
                    by(df$speed, df$dive, mean)  302  325  343.9    342  362  396   100  b      
              aggregate(speed ~ dive, df, mean)  904  966 1012.1   1020 1060 1130   100     e   
                                  splitmean(df)  191  206  249.9    220  232 1670   100 a       
  ddply(df, .(dive), function(x) mean(x$speed)) 1220 1310 1358.1   1340 1380 2740   100      f  
         dcast(melt(df), variable ~ dive, mean) 2150 2330 2440.7   2430 2490 4010   100        h
                   dt[, mean(speed), by = dive]  599  629  667.1    659  704  771   100   c     
 summarize(group_by(df, dive), m = mean(speed))  663  710  774.6    744  782 2140   100    d    
 summarize(group_by(dt, dive), m = mean(speed)) 1860 1960 2051.0   2020 2090 3430   100       g 

autoplot(m1)

benchmark 10 rows

Как обычно, data.table имеет немного больше накладных расходов, поэтому примерно средний размер для небольших наборов данных. Тем не менее, это микросекунды, поэтому различия тривиальны. Любой из подходов отлично работает здесь, и вы должны выбрать на основе:

  • То, что вы уже знакомы или хотите знать (plyr всегда заслуживает изучения своей гибкости; data.table стоит изучить, если вы планируете анализировать огромные наборы данных; by и aggregate и split - все базовые R-функции и, следовательно, универсально доступны)
  • Результат вывода (числовой, data.frame или data.table - последний из которых наследуется от data.frame)

10 миллионов строк, 10 групп

Но что, если у нас есть большой набор данных? Попробуйте 10 ^ 7 строк, разбитых на десять групп.

df <- data.frame(dive=factor(sample(letters[1:10],10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)

m2 <- microbenchmark(
  by( df$speed, df$dive, mean),
  aggregate( speed ~ dive, df, mean ),
  splitmean(df),
  ddply( df, .(dive), function(x) mean(x$speed) ),
  dcast( melt(df), variable ~ dive, mean),
  dt[,mean(speed),by=dive],
  times=2
)

> print(m2, signif = 3)
Unit: milliseconds
                                           expr   min    lq    mean median    uq   max neval      cld
                    by(df$speed, df$dive, mean)   720   770   799.1    791   816   958   100    d    
              aggregate(speed ~ dive, df, mean) 10900 11000 11027.0  11000 11100 11300   100        h
                                  splitmean(df)   974  1040  1074.1   1060  1100  1280   100     e   
  ddply(df, .(dive), function(x) mean(x$speed))  1050  1080  1110.4   1100  1130  1260   100      f  
         dcast(melt(df), variable ~ dive, mean)  2360  2450  2492.8   2490  2520  2620   100       g 
                   dt[, mean(speed), by = dive]   119   120   126.2    120   122   212   100 a       
 summarize(group_by(df, dive), m = mean(speed))   517   521   531.0    522   532   620   100   c     
 summarize(group_by(dt, dive), m = mean(speed))   154   155   174.0    156   189   321   100  b      

autoplot(m2)

benchmark 1e7 rows, 10 groups

Тогда data.table или dplyr с использованием функции data.table - это, безусловно, путь. Некоторые подходы (aggregate и dcast) начинают выглядеть очень медленными.

10 миллионов строк, 1000 групп

Если у вас больше групп, разница становится более выраженной. С 1000 групп и теми же 10 ^ 7 строками:

df <- data.frame(dive=factor(sample(seq(1000),10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)

# then run the same microbenchmark as above
print(m3, signif = 3)
Unit: milliseconds
                                           expr   min    lq    mean median    uq   max neval    cld
                    by(df$speed, df$dive, mean)   776   791   816.2    810   828   925   100  b    
              aggregate(speed ~ dive, df, mean) 11200 11400 11460.2  11400 11500 12000   100      f
                                  splitmean(df)  5940  6450  7562.4   7470  8370 11200   100     e 
  ddply(df, .(dive), function(x) mean(x$speed))  1220  1250  1279.1   1280  1300  1440   100   c   
         dcast(melt(df), variable ~ dive, mean)  2110  2190  2267.8   2250  2290  2750   100    d  
                   dt[, mean(speed), by = dive]   110   111   113.5    111   113   143   100 a     
 summarize(group_by(df, dive), m = mean(speed))   625   630   637.1    633   644   701   100  b    
 summarize(group_by(dt, dive), m = mean(speed))   129   130   137.3    131   142   213   100 a     

autoplot(m3)

enter image description here

Итак, data.table продолжает хорошо масштабироваться, а dplyr, работающий на data.table, также хорошо работает, причем dplyr on data.frame приближается к порядку медленнее. Стратегия split/sapply, по-видимому, плохо масштабируется в количестве групп (это означает, что split(), вероятно, медленный, а sapply - быстрый). by продолжает быть относительно эффективным - через 5 секунд он определенно заметен для пользователя, но для набора данных этот большой все еще не является необоснованным. Тем не менее, если вы регулярно работаете с наборами данных такого размера, data.table - это, безусловно, путь - 100% data.table для лучшей производительности или dplyr с dplyr с использованием data.table в качестве жизнеспособной альтернативы.

Ответ 2

aggregate(speed~dive,data=df,FUN=mean)
   dive     speed
1 dive1 0.7059729
2 dive2 0.5473777

Ответ 3

Обновление с 2015 года с помощью dplyr:

df %>% group_by(dive) %>% summarise(percentage = mean(speed))
Source: local data frame [2 x 2]

   dive percentage
1 dive1  0.4777462
2 dive2  0.6726483