Какие методы мы можем использовать, чтобы изменить ОЧЕНЬ большие наборы данных? - PullRequest
33 голосов
/ 09 марта 2019

Когда из-за очень больших данных расчеты занимают много времени и, следовательно, мы не хотим, чтобы они зависали, было бы полезно заранее узнать, какой метод изменения формы использовать.

В последнее время методыдля преобразования данных были дополнительно разработаны данные о производительности, например, data.table::dcast и tidyr::spread.Особенно dcast.data.table, кажется, устанавливает тон [1] , [2] , [3] , [4] .Это делает другие методы, поскольку базовые R reshape в тестах кажутся устаревшими и почти бесполезными [5] .

Теория

Однако , я слышал, что reshape по-прежнему непобедим, когда дело доходит до очень больших наборов данных (вероятно, превышающих объем ОЗУ), потому что это единственный метод, который может обрабатывать их, и, следовательно, он по-прежнему имеет правосуществовать.Соответствующий отчет о сбое с использованием reshape2::dcast поддерживает эту точку [6] .По крайней мере, одна ссылка дает подсказку, что reshape() действительно может иметь преимущества перед reshape2::dcast для действительно "больших вещей" [7] .

Метод

В поисках доказательств этого я подумал, что стоит потратить время на некоторые исследования.Поэтому я провел тест с имитированными данными разного размера, которые все больше расходуют ОЗУ для сравнения reshape, dcast, dcast.data.table и spread.Я посмотрел на простые наборы данных с тремя столбцами, с различным количеством строк, чтобы получить разные размеры (см. Код в самом низу).

> head(df1, 3)
  id                 tms         y
1  1 1970-01-01 01:00:01 0.7463622
2  2 1970-01-01 01:00:01 0.1417795
3  3 1970-01-01 01:00:01 0.6993089

Размер оперативной памяти составлял всего 8 ГБ, что было моим порогоммоделировать "очень большие" наборы данных.Чтобы сохранить разумное время для расчетов, я сделал только 3 измерения для каждого метода и сосредоточился на изменении формы с длинного на широкий.

Результаты

unit: seconds
       expr       min        lq      mean    median        uq       max neval size.gb size.ram
1  dcast.DT        NA        NA        NA        NA        NA        NA     3    8.00    1.000
2     dcast        NA        NA        NA        NA        NA        NA     3    8.00    1.000
3     tidyr        NA        NA        NA        NA        NA        NA     3    8.00    1.000
4   reshape 490988.37 492843.94 494699.51 495153.48 497236.03 499772.56     3    8.00    1.000
5  dcast.DT   3288.04   4445.77   5279.91   5466.31   6375.63  10485.21     3    4.00    0.500
6     dcast   5151.06   5888.20   6625.35   6237.78   6781.14   6936.93     3    4.00    0.500
7     tidyr   5757.26   6398.54   7039.83   6653.28   7101.28   7162.74     3    4.00    0.500
8   reshape  85982.58  87583.60  89184.62  88817.98  90235.68  91286.74     3    4.00    0.500
9  dcast.DT      2.18      2.18      2.18      2.18      2.18      2.18     3    0.20    0.025
10    tidyr      3.19      3.24      3.37      3.29      3.46      3.63     3    0.20    0.025
11    dcast      3.46      3.49      3.57      3.52      3.63      3.74     3    0.20    0.025
12  reshape    277.01    277.53    277.83    278.05    278.24    278.42     3    0.20    0.025
13 dcast.DT      0.18      0.18      0.18      0.18      0.18      0.18     3    0.02    0.002
14    dcast      0.34      0.34      0.35      0.34      0.36      0.37     3    0.02    0.002
15    tidyr      0.37      0.39      0.42      0.41      0.44      0.48     3    0.02    0.002
16  reshape     29.22     29.37     29.49     29.53     29.63     29.74     3    0.02    0.002

enter image description here

( Примечание: Тесты проводились на вторичном MacBook Pro с Intel Core i5 2,5 ГГц, 8 ГБ оперативной памяти DDR3 1600 МГц.)

Очевидно, dcast.data.table кажется всегда самым быстрым.Как и ожидалось, все упакованные подходы потерпели неудачу с очень большими наборами данных, возможно, потому что вычисления тогда превысили объем оперативной памяти:

Error: vector memory exhausted (limit reached?)
Timing stopped at: 1.597e+04 1.864e+04 5.254e+04

Только reshape обрабатывал все размеры данных, хотя и очень медленно.

Заключение

Методы пакета, такие как dcast и spread, неоценимы для наборов данных, которые меньше ОЗУ или чьи вычисления не исчерпывают ОЗУ.Если набор данных больше, чем объем оперативной памяти, методы пакета не будут выполнены, и мы должны использовать reshape.

Вопрос

Можно ли сделать такой вывод?Может ли кто-нибудь немного разъяснить, почему методы data.table/reshape и tidyr терпят неудачу и каковы их методологические отличия от reshape?Является ли единственная альтернатива для больших данных надежной, но медленной лошадью reshape?Что мы можем ожидать от методов, которые здесь не тестировались как tapply, unstack и xtabs подходы [8] , [9] ?

Или, короче говоря: Какая более быстрая альтернатива, если что-либо, кроме reshape, выходит из строя?


Данные / Код

# 8GB version
n <- 1e3      
t1 <- 2.15e5  # approx. 8GB, vary to increasingly exceed RAM

df1 <- expand.grid(id=1:n, tms=as.POSIXct(1:t1, origin="1970-01-01"))
df1$y <- rnorm(nrow(df1))

dim(df1)
# [1] 450000000         3

> head(df1, 3)
id                 tms         y
1  1 1970-01-01 01:00:01 0.7463622
2  2 1970-01-01 01:00:01 0.1417795
3  3 1970-01-01 01:00:01 0.6993089

object.size(df1)
# 9039666760 bytes

library(data.table)
DT1 <- as.data.table(df1)

library(microbenchmark)
library(tidyr)
# NOTE: this runs for quite a while!
mbk <- microbenchmark(reshape=reshape(df1, idvar="tms", timevar="id", direction="wide"),
                      dcast=dcast(df1, tms ~ id, value.var="y"),
                      dcast.dt=dcast(DT1, tms ~ id, value.var="y"),
                      tidyr=spread(df1, id, y),
                      times=3L)

1 Ответ

7 голосов
/ 03 мая 2019

Если ваши реальные данные столь же регулярны, как и ваши примерные данные, мы можем быть весьма эффективными, заметив, что изменение формы матрицы на самом деле просто меняет ее атрибут dim.

1-е место на очень маленьких данных

library(data.table)
library(microbenchmark)
library(tidyr)

matrix_spread <- function(df1, key, value){
  unique_ids <-  unique(df1[[key]])
  mat <- matrix( df1[[value]], ncol= length(unique_ids),byrow = TRUE)
  df2 <- data.frame(unique(df1["tms"]),mat)
  names(df2)[-1] <- paste0(value,".",unique_ids)
  df2
}

n <- 3      
t1 <- 4
df1 <- expand.grid(id=1:n, tms=as.POSIXct(1:t1, origin="1970-01-01"))
df1$y <- rnorm(nrow(df1))

reshape(df1, idvar="tms", timevar="id", direction="wide")
#                    tms        y.1        y.2       y.3
# 1  1970-01-01 01:00:01  0.3518667  0.6350398 0.1624978
# 4  1970-01-01 01:00:02  0.3404974 -1.1023521 0.5699476
# 7  1970-01-01 01:00:03 -0.4142585  0.8194931 1.3857788
# 10 1970-01-01 01:00:04  0.3651138 -0.9867506 1.0920621

matrix_spread(df1, "id", "y")
#                    tms        y.1        y.2       y.3
# 1  1970-01-01 01:00:01  0.3518667  0.6350398 0.1624978
# 4  1970-01-01 01:00:02  0.3404974 -1.1023521 0.5699476
# 7  1970-01-01 01:00:03 -0.4142585  0.8194931 1.3857788
# 10 1970-01-01 01:00:04  0.3651138 -0.9867506 1.0920621

all.equal(check.attributes = FALSE,
          reshape(df1, idvar="tms", timevar="id", direction="wide"),
          matrix_spread (df1, "id", "y"))
# TRUE

Тогда на больших данных

(извините, я не могу позволить себе сейчас делать огромные вычисления)

n <- 100      
t1 <- 5000

df1 <- expand.grid(id=1:n, tms=as.POSIXct(1:t1, origin="1970-01-01"))
df1$y <- rnorm(nrow(df1))

DT1 <- as.data.table(df1)

microbenchmark(reshape=reshape(df1, idvar="tms", timevar="id", direction="wide"),
               dcast=dcast(df1, tms ~ id, value.var="y"),
               dcast.dt=dcast(DT1, tms ~ id, value.var="y"),
               tidyr=spread(df1, id, y),
               matrix_spread = matrix_spread(df1, "id", "y"),
               times=3L)

# Unit: milliseconds
# expr                 min         lq       mean     median         uq        max neval
# reshape       4197.08012 4240.59316 4260.58806 4284.10620 4292.34203 4300.57786     3
# dcast           57.31247   78.16116   86.93874   99.00986  101.75189  104.49391     3
# dcast.dt       114.66574  120.19246  127.51567  125.71919  133.94064  142.16209     3
# tidyr           55.12626   63.91142   72.52421   72.69658   81.22319   89.74980     3
# matrix_spread   15.00522   15.42655   17.45283   15.84788   18.67664   21.50539     3 

Не так уж плохо!

Об использовании памяти, я думаю, если reshape справится с этим, моё решение поможет, если вы можете работать с моими предположениями или предварительно обработать данные, чтобы удовлетворить их:

  • данные отсортированы
  • у нас есть только 3 столбца
  • для всех значений id мы находим все значения tms
...