Почему "уникальный" выполняется быстрее на фрейме данных, чем матрица в R?
Я начал верить, что фреймы данных не имеют никаких преимуществ по сравнению с матрицами, за исключением удобства записи. Тем не менее, я заметил эту странность при запуске unique
на матрицах и фреймах данных: кажется, работает быстрее на фрейме данных.
a = matrix(sample(2,10^6,replace = TRUE), ncol = 10)
b = as.data.frame(a)
system.time({
u1 = unique(a)
})
user system elapsed
1.840 0.000 1.846
system.time({
u2 = unique(b)
})
user system elapsed
0.380 0.000 0.379
Результаты синхронизации расходятся еще более существенно по мере увеличения количества строк. Итак, есть две части этого вопроса.
Почему это медленнее для матрицы? Кажется быстрее конвертировать в фрейм данных, запустить
unique
, а затем конвертировать обратно.Есть ли причина не просто обернуть
unique
вmyUnique
Что делает преобразования в части № 1?
Примечание 1. Учитывая, что матрица является атомарной, кажется, что unique
должно быть быстрее для матрицы, а не медленнее. Возможность перебирать непрерывные блоки памяти фиксированного размера, как правило, должна выполняться быстрее, чем работать с отдельными блоками связанных списков (я предполагаю, что именно так реализованы фреймы данных...).
Примечание 2. Как показали результаты data.table
, Бег unique
относительно фрейма данных или матрицы - сравнительно плохая идея - см. ответ Мэтью Доула и комментарии относительно времени. Я перенес много объектов в таблицы данных, и эта производительность является еще одной причиной для этого. Таким образом, хотя пользователи должны быть хорошо приспособлены к принятию таблиц данных, по педагогическим / общественным причинам я пока оставлю открытым вопрос о том, почему это занимает больше времени для объектов матрицы. Ответы ниже указывают на то, куда уходит время и как еще мы можем улучшить производительность (например, таблицы данных). Ответ на вопрос, почему под рукой - код можно найти через unique.data.frame
а также unique.matrix
,:) Английское объяснение того, что он делает и почему всего этого не хватает.
3 ответа
В этой реализации
unique.matrix
такой же какunique.array
> identical(unique.array, unique.matrix)
[1] TRUE
unique.array
должен обрабатывать многомерные массивы, которые требуют дополнительной обработки, чтобы "свернуть" дополнительные измерения (эти дополнительные вызовыpaste()
), которые не нужны в 2-мерном случае. Ключевой раздел кода:collapse <- (ndim > 1L) && (prod(dx[-MARGIN]) > 1L)
temp <- if (collapse) apply(x, MARGIN, function(x) paste(x, collapse = "\r"))
unique.data.frame
оптимизирован для 2D-случая,unique.matrix
не является. Это может быть, как вы предполагаете, просто в текущей реализации.
Обратите внимание, что во всех случаях (уникальных. {Массив, матрица,data.table}), где существует более одного измерения, строковое представление сравнивается на предмет уникальности. Для чисел с плавающей запятой это означает 15 десятичных цифр, так
NROW(unique(a <- matrix(rep(c(1, 1+4e-15), 2), nrow = 2)))
является 1
в то время как
NROW(unique(a <- matrix(rep(c(1, 1+5e-15), 2), nrow = 2)))
а также
NROW(unique(a <- matrix(rep(c(1, 1+4e-15), 1), nrow = 2)))
оба 2
, Ты уверен unique
это то, что вы хотите?
Не уверен, но я думаю, потому что
matrix
один смежный вектор, R сначала копирует его в векторы столбцов (например,data.frame
) так какpaste
нужен список векторов. Обратите внимание, что оба медленные, потому что оба используютpaste
,Возможно потому что
unique.data.table
уже во много раз быстрее. Пожалуйста, обновитесь до v1.6.7, загрузив его из репозитория R-Forge, потому что это имеет исправлениеunique
Вы подняли в этом вопросе.data.table
не используетpaste
сделатьunique
,
a = matrix(sample(2,10^6,replace = TRUE), ncol = 10) b = as.data.frame(a) system.time(u1<-unique(a)) user system elapsed 2.98 0.00 2.99 system.time(u2<-unique(b)) user system elapsed 0.99 0.00 0.99 c = as.data.table(b) system.time(u3<-unique(c)) user system elapsed 0.03 0.02 0.05 # 60 times faster than u1, 20 times faster than u2 identical(as.data.table(u2),u3) [1] TRUE
Пытаясь ответить на мой собственный вопрос, особенно часть 1, мы можем увидеть, на что тратится время, посмотрев на результаты Rprof
, Я запустил это снова, с 5M элементами.
Вот результаты для первой уникальной операции (для матрицы):
> summaryRprof("u1.txt")
$by.self
self.time self.pct total.time total.pct
"paste" 5.70 52.58 5.96 54.98
"apply" 2.70 24.91 10.68 98.52
"FUN" 0.86 7.93 6.82 62.92
"lapply" 0.82 7.56 1.00 9.23
"list" 0.30 2.77 0.30 2.77
"!" 0.14 1.29 0.14 1.29
"c" 0.10 0.92 0.10 0.92
"unlist" 0.08 0.74 1.08 9.96
"aperm.default" 0.06 0.55 0.06 0.55
"is.null" 0.06 0.55 0.06 0.55
"duplicated.default" 0.02 0.18 0.02 0.18
$by.total
total.time total.pct self.time self.pct
"unique" 10.84 100.00 0.00 0.00
"unique.matrix" 10.84 100.00 0.00 0.00
"apply" 10.68 98.52 2.70 24.91
"FUN" 6.82 62.92 0.86 7.93
"paste" 5.96 54.98 5.70 52.58
"unlist" 1.08 9.96 0.08 0.74
"lapply" 1.00 9.23 0.82 7.56
"list" 0.30 2.77 0.30 2.77
"!" 0.14 1.29 0.14 1.29
"do.call" 0.14 1.29 0.00 0.00
"c" 0.10 0.92 0.10 0.92
"aperm.default" 0.06 0.55 0.06 0.55
"is.null" 0.06 0.55 0.06 0.55
"aperm" 0.06 0.55 0.00 0.00
"duplicated.default" 0.02 0.18 0.02 0.18
$sample.interval
[1] 0.02
$sampling.time
[1] 10.84
И для фрейма данных:
> summaryRprof("u2.txt")
$by.self
self.time self.pct total.time total.pct
"paste" 1.72 94.51 1.72 94.51
"[.data.frame" 0.06 3.30 1.82 100.00
"duplicated.default" 0.04 2.20 0.04 2.20
$by.total
total.time total.pct self.time self.pct
"[.data.frame" 1.82 100.00 0.06 3.30
"[" 1.82 100.00 0.00 0.00
"unique" 1.82 100.00 0.00 0.00
"unique.data.frame" 1.82 100.00 0.00 0.00
"duplicated" 1.76 96.70 0.00 0.00
"duplicated.data.frame" 1.76 96.70 0.00 0.00
"paste" 1.72 94.51 1.72 94.51
"do.call" 1.72 94.51 0.00 0.00
"duplicated.default" 0.04 2.20 0.04 2.20
$sample.interval
[1] 0.02
$sampling.time
[1] 1.82
Мы замечаем, что матричная версия тратит много времени на apply
, paste
, а также lapply
, Напротив, версия фрейма данных просто работает duplicated.data.frame
и большая часть времени проводится в paste
, предположительно агрегируя результаты.
Хотя это объясняет, куда идет время, это не объясняет, почему они имеют разные реализации, а также последствия простого перехода от одного типа объекта к другому.