Почему "уникальный" выполняется быстрее на фрейме данных, чем матрица в 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

Результаты синхронизации расходятся еще более существенно по мере увеличения количества строк. Итак, есть две части этого вопроса.

  1. Почему это медленнее для матрицы? Кажется быстрее конвертировать в фрейм данных, запустить unique, а затем конвертировать обратно.

  2. Есть ли причина не просто обернуть unique в myUniqueЧто делает преобразования в части № 1?


Примечание 1. Учитывая, что матрица является атомарной, кажется, что unique должно быть быстрее для матрицы, а не медленнее. Возможность перебирать непрерывные блоки памяти фиксированного размера, как правило, должна выполняться быстрее, чем работать с отдельными блоками связанных списков (я предполагаю, что именно так реализованы фреймы данных...).

Примечание 2. Как показали результаты data.table, Бег unique относительно фрейма данных или матрицы - сравнительно плохая идея - см. ответ Мэтью Доула и комментарии относительно времени. Я перенес много объектов в таблицы данных, и эта производительность является еще одной причиной для этого. Таким образом, хотя пользователи должны быть хорошо приспособлены к принятию таблиц данных, по педагогическим / общественным причинам я пока оставлю открытым вопрос о том, почему это занимает больше времени для объектов матрицы. Ответы ниже указывают на то, куда уходит время и как еще мы можем улучшить производительность (например, таблицы данных). Ответ на вопрос, почему под рукой - код можно найти через unique.data.frame а также unique.matrix,:) Английское объяснение того, что он делает и почему всего этого не хватает.

3 ответа

Решение
  1. В этой реализации unique.matrix такой же как unique.array

    > identical(unique.array, unique.matrix)

    [1] TRUE

  2. unique.array должен обрабатывать многомерные массивы, которые требуют дополнительной обработки, чтобы "свернуть" дополнительные измерения (эти дополнительные вызовы paste()), которые не нужны в 2-мерном случае. Ключевой раздел кода:

    collapse <- (ndim > 1L) && (prod(dx[-MARGIN]) > 1L)

    temp <- if (collapse) apply(x, MARGIN, function(x) paste(x, collapse = "\r"))

  3. 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 это то, что вы хотите?

  1. Не уверен, но я думаю, потому что matrix один смежный вектор, R сначала копирует его в векторы столбцов (например, data.frame) так как paste нужен список векторов. Обратите внимание, что оба медленные, потому что оба используют paste,

  2. Возможно потому что 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, предположительно агрегируя результаты.

Хотя это объясняет, куда идет время, это не объясняет, почему они имеют разные реализации, а также последствия простого перехода от одного типа объекта к другому.

Другие вопросы по тегам