Ответ 1
Обновление:
@Roland делает несколько замечательных замечаний в разделе комментариев, и сообщение лучше для них. Хотя я изначально сосредоточился на проблемах переполнения памяти, он отметил, что даже если этого не происходит, управление памятью разных копий занимает значительное время, что является более распространенной повседневной заботой. Также были добавлены примеры обеих проблем.
Мне нравится этот вопрос в stackoverflow, потому что я думаю, что это действительно предотвращение в R при работе с большими наборами данных. 😊 Те, кто не знаком с data.table
семейством операций set
, могут воспользоваться этим обсуждением!
При работе с большими наборами данных следует использовать setDT()
, которые занимают значительное количество ОЗУ, поскольку операция будет модифицировать каждый объект на месте, сохраняя память. Для данных, которые являются очень небольшим процентом ОЗУ, использование data.tables copy-and-modify прекрасное.
Создание функции setDT
было фактически вдохновлено следующим потоком, который заключается в работе с большим набором данных (несколько ГБ). Вы увидите мелодию Мэтта Доула, предложив имя 'setDT'.
Преобразование фрейма данных в таблицу данных без копирования
Немного больше:
С R данные хранятся в памяти. Это значительно ускоряет работу, поскольку оперативная память намного быстрее, чем устройства хранения данных. Однако проблема может возникнуть, когда один набор данных является большой частью ОЗУ. Зачем? Поскольку база R имеет тенденцию делать копии каждого data.frame
, когда к ним применяются некоторые операции. Это улучшилось после версии 3.1, но адресация выходит за рамки этой публикации. Если вы вытаскиваете несколько data.frame
или list
в один data.frame
или data.table
, использование вашей памяти будет расширяться довольно быстро, потому что в какой-то момент во время работы в ОЗУ имеется несколько копий ваших данных. Если набор данных достаточно велик, у вас может закончиться нехватка памяти, когда будут созданы все копии, и ваш стек будет переполняться. См. Пример ниже. Мы получаем ошибку, и исходный адрес памяти и класс объекта не изменяются.
> N <- 1e8
> P <- 1e2
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
>
> pryr::object_size(data)
800 MB
>
> tracemem(data)
[1] "<0000000006D2DF18>"
>
> data <- data.table(data)
Error: cannot allocate vector of size 762.9 Mb
>
> tracemem(data)
[1] "<0000000006D2DF18>"
> class(data)
[1] "data.frame"
>
Возможность просто изменить объект на месте без копирования - большое дело. Это то, что setDT
делает, когда требуется list
или data.frame
и возвращает data.table
. Тот же пример, что и выше, используя setDT
, теперь работает нормально и без ошибок. Изменение адреса класса и памяти и отсутствие копий.
> tracemem(data)
[1] "<0000000006D2DF18>"
> class(data)
[1] "data.frame"
>
> setDT(data)
>
> tracemem(data)
[1] "<0000000006A8C758>"
> class(data)
[1] "data.table" "data.frame"
@Roland указывает, что для большинства людей большая проблема - это скорость, которая страдает как побочный эффект такого интенсивного использования управления памятью. Ниже приведен пример с меньшими данными, которые не приводят к сбою процессора и иллюстрируют, насколько быстрее setDT
для этого задания. Обратите внимание на результаты "tracemem" после data <- data.table(data)
, делая копии data
. Сравните это с setDT(data)
, который не печатает одну копию. Затем мы вызываем tracemem(data)
, чтобы увидеть новый адрес памяти.
> N <- 1e5
> P <- 1e2
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
> pryr::object_size(data)
808 kB
> # data.table method
> tracemem(data)
[1] "<0000000019098438>"
> data <- data.table(data)
tracemem[0x0000000019098438 -> 0x0000000007aad7d8]: data.table
tracemem[0x0000000007aad7d8 -> 0x0000000007c518b8]: copy as.data.table.data.frame as.data.table data.table
tracemem[0x0000000007aad7d8 -> 0x0000000018e454c8]: as.list.data.frame as.list vapply copy as.data.table.data.frame as.data.table data.table
> class(data)
[1] "data.table" "data.frame"
>
> # setDT method
> # back to data.frame
> data <- as.data.frame(data)
> class(data)
[1] "data.frame"
> tracemem(data)
[1] "<00000000125BE1A0>"
> setDT(data)
> tracemem(data)
[1] "<00000000125C2840>"
> class(data)
[1] "data.table" "data.frame"
>
Как это влияет на время? Как мы видим, setDT
для него намного быстрее.
> # timing example
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
> microbenchmark(setDT(data), data <- data.table(data))
Unit: microseconds
expr min lq mean median max neval uq
setDT(data) 49.948 55.7635 69.66017 73.553 100.238 100 79.198
data <- data.table(data) 54594.289 61238.8830 81545.64432 64179.131 611632.427 100 68647.917
Устанавливаемые функции могут использоваться во многих областях, а не только при преобразовании объектов в data.tables. Вы можете найти более подробную информацию о ссылочной семантике и о том, как применять ее в другом месте, вызывая виньетку на эту тему.
library(data.table)
vignette("datatable-reference-semantics")
Это отличный вопрос, и те, кто думает об использовании R с большими наборами данных или просто хотят ускорить активацию данных, могут извлечь выгоду из понимания значительных улучшений производительности data.table
эталонной семантики.