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