Подтвердить что ты не робот

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

4b9b3361

Ответ 1

  • В этой реализации unique.matrix совпадает с unique.array

    > identical(unique.array, unique.matrix)

    [1] TRUE

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

    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 - нет. Это может быть, по вашему мнению, просто не в текущей реализации.

Обратите внимание, что во всех случаях (уникальный. {array, 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 - это то, что вы хотите?

Ответ 2

  • Не уверен, но я предполагаю, что, поскольку matrix - один непрерывный вектор, R сначала копирует его в векторы столбцов (например, a 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

Ответ 3

При попытке ответить на мой собственный вопрос, особенно в части 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, предположительно на агрегацию результатов.

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