2017-01-29 1 views
19

У меня возникли трудности с пониманием сути функции setDT(). Когда я читаю код на SO, я часто сталкиваюсь с использованием setDT() для создания таблицы данных. Конечно, использование data.table() вездесущ. Я чувствую, что я твердо понимаю природу data.table(), но актуальность setDT() ускользает от меня. ?setDT говорит мне это:Когда я должен использовать setDT() вместо data.table() для создания таблицы данных?

setDT преобразует списки (как по имени и безымянный) и data.frames в data.tables по ссылке.

, а также:

В data.table просторечии, все установленные * функции изменяют свой вклад в виде ссылки. То есть никакой копии не делается вообще, кроме временной рабочей памяти, которая равна одному столбцу.

Так что это заставляет меня думать, что я должен использовать только setDT(), чтобы сделать data.table, правильно? Есть setDT() просто список для data.table конвертер?

library(data.table) 

a <- letters[c(19,20,1,3,11,15,22,5,18,6,12,15,23)] 
b <- seq(1,41,pi) 
ab <- data.frame(a,b) 
d <- data.table(ab) 
e <- setDT(ab) 

str(d) 
#Classes ‘data.table’ and 'data.frame': 13 obs. of 2 variables: 
# $ a: Factor w/ 12 levels "a","c","e","f",..: 9 10 1 2 5 7 11 3 8 4 ... 
# $ b: num 1 4.14 7.28 10.42 13.57 ... 
# - attr(*, ".internal.selfref")=<externalptr> 

str(e) 
#Classes ‘data.table’ and 'data.frame': 13 obs. of 2 variables: 
# $ a: Factor w/ 12 levels "a","c","e","f",..: 9 10 1 2 5 7 11 3 8 4 ... 
# $ b: num 1 4.14 7.28 10.42 13.57 ... 
# - attr(*, ".internal.selfref")=<externalptr> 

Похоже, нет разницы в этом случае. В другом случае разница очевидна:

ba <- list(a,b) 
f <- data.table(ba) 
g <- setDT(ba) 

str(f) 
#Classes ‘data.table’ and 'data.frame': 2 obs. of 1 variable: 
# $ ba:List of 2 
# ..$ : chr "s" "t" "a" "c" ... 
# ..$ : num 1 4.14 7.28 10.42 13.57 ... 
# - attr(*, ".internal.selfref")=<externalptr> 

str(g) 
#Classes ‘data.table’ and 'data.frame': 13 obs. of 2 variables: 
# $ V1: chr "s" "t" "a" "c" ... 
# $ V2: num 1 4.14 7.28 10.42 13.57 ... 
# - attr(*, ".internal.selfref")=<externalptr> 

Когда я должен использовать setDT()? Что делает setDT() релевантным? Почему бы просто не сделать оригинальную функцию data.table(), способную делать то, что может сделать setDT()?

+6

Если данные уже существуют в data.frame, и вы с удовольствием преобразуете этот файл data.frame (или список) в таблицу data.table, затем используйте setDT(). Если вы хотите построить data.table из векторов или чего-то еще, используйте data.table(). Вы никогда не должны делать 'x <- setDT (y)' - Я думаю, вы неправильно понимаете, что это означает, что объект должен быть изменен ссылкой. Возможно, вам нужно пройти через виньетки: http://r-datatable.com/Getting-Started – Frank

+5

И посмотрите, что происходит с таблицей 'ab' data.table после присвоения новых столбцов' d' и 'e': 'd [, newCol: = 1]; абы; e [, newCol: = 1]; ab'. – SymbolixAU

+1

@Frank Я вернусь в литературе. Как вы, возможно, знаете, требуется много итераций для вещей, которые нужно щелкнуть для некоторых людей - как и я. Спасибо, что указал мне в правильном направлении. –

ответ

13

Update:

@Roland делает несколько хороших точек в разделе комментариев, и пост лучше для них. Хотя я изначально сосредоточился на проблемах переполнения памяти, он отметил, что даже если этого не происходит, управление памятью разных копий занимает значительное время, что является более распространенной повседневной заботой. В настоящее время также добавлены примеры обеих проблем.

Мне нравится этот вопрос в stackoverflow, потому что я думаю, что это действительно об избежании переполнения стека в R при работе с большими наборами данных. Те, кто не знаком с data.table семейством set, могут извлечь пользу из этой дискуссии!

Следует использовать setDT() при работе с большими наборами данных, которые занимают значительное количество ОЗУ, поскольку операция будет модифицировать каждый объект на месте, сохраняя память. Для данных, которые являются очень небольшим процентом ОЗУ, использование копирования и модификации data.table в порядке.

Создание функции setDT было фактически вдохновлено следующим потоком переполнения стека, который заключается в работе с большим набором данных (несколько ГБ). Вы увидите мелодию Мэтта Доула, предложив имя 'setDT'.

Convert a data frame to a data.table without copy

Немного больше глубина:

С R, данные хранятся в памяти. Это значительно ускоряет работу, поскольку оперативная память намного быстрее, чем устройства хранения данных. Однако проблема может возникнуть, когда один из наборов данных является большой частью ОЗУ. Зачем? Поскольку база R имеет тенденцию делать копии каждого data.frame, когда к ним применяются некоторые операции. Это улучшилось после версии 3.1, но адресация выходит за рамки этой публикации. Если вы вытягиваете несколько data.frame или list s в один 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 ссылочных семантики.

+3

Вы слишком много внимания уделяете памяти. Изменения по ссылке или копией также имеют огромные последствия для скорости, что более актуально для большинства людей. Хороший ответ должен содержать некоторые ориентиры. Кроме того, вы не должны слишком полагаться на информацию из древних сообщений по этим вопросам. Управление памятью базы R развилось с 2009 года. – Roland

+0

@Roland Спасибо за комментарии, и я всегда стараюсь принять скромный подход по этим вопросам. Однако, глядя глубже на это, я должен сказать, что мой ответ соответствует тому, что авторы data.table пишут в своей документации. В частности, они создали 'setDT' для преобразования data.frames в data.tables *** без копирования ***. См. '? SetDT'. Фактически, они даже указывают на одно и то же сообщение переполнения стека, указанное выше. Спасибо – Justin

+1

Я думаю, что вам не хватает моей точки. Копии не только требуют дополнительной памяти, но и управления памятью занимает значительное время. Вот почему избежать копирования не только эффективно, но и эффективно. – Roland

10

setDT() не является заменой data.table(). Это более эффективная замена для as.data.table(), которая может использоваться с определенными типами объектов.

  • mydata <- as.data.table(mydata) будет копировать объект за mydata, преобразовать копию в data.table, а затем изменить mydata символ, чтобы указать на копии.
  • setDT(mydata) изменит объект позади mydata на data.table.Никакое копирование не выполняется.

Итак, какова реалистичная ситуация для использования setDT()? Если вы не можете управлять классом исходных данных. Например, большинство пакетов для работы с базами данных дают выход data.frame. В этом случае ваш код будет что-то вроде

mydata <- dbGetQuery(conn, "SELECT * FROM mytable") # Returns a data.frame 
setDT(mydata)          # Make it a data.table 

Когда следует использовать as.data.table(x)? Когда x не является list или data.frame. Наиболее часто используется матрица.

+3

Маленькая ниточка: в вашей первой маркерной точке вы ссылаетесь на переменную, если вы имеете в виду mydata [символ] (https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Symbol -объекты). – Roland

+0

Последний вопрос не будет дан, как есть. Лучше сделать отдельный пример. мои 2 цента – amonk

Смежные вопросы