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

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

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

теория

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

метод

В поисках доказательств этого я подумал, что стоит потратить время на некоторые исследования. Так что я сделал тест с имитацией данных разного размера, которые все больше и больше исчерпать RAM сравнить 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

Если ваши реальные данные столь же регулярны, как и ваши образцы данных, мы можем быть весьма эффективными, заметив, что изменение формы матрицы на самом деле просто меняет ее атрибут 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