Почему «уникальный» выполняется быстрее на фрейме данных, чем матрица в R? - PullRequest
26 голосов
/ 18 октября 2011

Я начал верить, что кадры данных не имеют преимуществ перед матрицами, за исключением удобства обозначения.Однако я заметил эту странность при запуске 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 ]

13 голосов
/ 19 октября 2011
  1. В этой реализации unique.matrix совпадает с unique.array

    > identical(unique.array, unique.matrix)

    [1] TRUE

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

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

12 голосов
/ 18 октября 2011
  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
9 голосов
/ 18 октября 2011

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

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

...