2013-06-05 4 views
25

Я пытаюсь найти элегантный способ использования назначения := для замены многих столбцов сразу в data.table с помощью общей функции. Типичным применением этого может быть применение строковой функции (например, gsub) ко всем столбцам символов в таблице. Нетрудно расширить способ data.frame сделать это до data.table, но я ищу способ, соответствующий способу делать вещи.Элегантное назначение нескольких столбцов в data.table с помощью lapply()

Например:

library(data.table) 

m <- matrix(runif(10000), nrow = 100) 
df <- df1 <- df2 <- df3 <- as.data.frame(m) 
dt <- as.data.table(df) 
head(names(df)) 
head(names(dt)) 

## replace V20-V100 with sqrt 

# data.frame approach 
# by column numbers 
df1[20:100] <- lapply(df1[20:100], sqrt) 
# by reference to column numbers 
v <- 20:100 
df2[v] <- lapply(df2[v], sqrt) 
# by reference to column names 
n <- paste0("V", 20:100) 
df3[n] <- lapply(df3[n], sqrt) 

# data.table approach 
# by reference to column names 
n <- paste0("V", 20:100) 
dt[, n] <- lapply(dt[, n, with = FALSE], sqrt) 

Я понимаю, что это более эффективно, чтобы перебрать вектор имен столбцов, используя := для назначения:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE] 

мне не нравится это, потому что я не» t напоминает ссылку data.table в выражении j. Я также знаю, что я могу использовать := назначать с lapply учитывая, что я знаю имена столбцов: (. Вы можете расширить это путем создания выражения с неизвестными именами столбцов)

dt[, c("V20", "V30", "V40", "V50", "V60") := lapply(list(V20, V30, V40, V50, V60), sqrt)] 

Ниже приведены идеи Я пробовал это, но я не смог заставить их работать. Я ошибаюсь, или есть другой подход, который я пропускаю?

# possible data.table approaches? 
# by reference to column names; assignment works, but not lapply 
n <- paste0("V", 20:100) 
dt[, n := lapply(n, sqrt), with = FALSE] 
# by (smaller for example) list; lapply works, but not assignment 
dt[, list(list(V20, V30, V40, V50, V60)) := lapply(list(V20, V30, V40, V50, V60), sqrt)] 
# by reference to list; neither assignment nor lapply work 
l <- parse(text = paste("list(", paste(paste0("V", 20:100), collapse = ", "), ")")) 
dt[, eval(l) := lapply(eval(l), sqrt)] 

ответ

30

Да, вы правы в вопросе здесь:

Я понимаю, что более эффективно перебирать вектор имен столбцов, используя := для назначения:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

Помимо: обратите внимание, что новый способ сделать это:

for (col in paste0("V", 20:100)) 
    dt[ , (col) := sqrt(dt[[col]])] 

потому что with = FALSE не было легко читать, идет ли он в LHS или RHS из :=. Отойди.

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

Проблема с lapply на RHS := заключается в том, что сначала оценивается RHS (lapply); то есть результат для 80 столбцов. Это 80 колонок стоит новой памяти, которая должна быть распределена и заполнена. Таким образом, для достижения этой цели вам понадобится бесплатная операционная память 80 столбцов. Это использование ОЗУ доминирует против мгновенной операции присвоения (plonking) этих 80 новых столбцов в слоты указателя столбца data.table.

Как отметил @Frank, если у вас много столбцов (скажем, 10 000 или более), то небольшие накладные расходы на отправку в метод [.data.table начинают складываться). Чтобы устранить эти накладные расходы, существует data.table::set, который под ?set описан как «закодированный» :=. Я использую цикл for для этого типа операции. Это самый быстрый способ и довольно легко писать и читать.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(dt[[col]])) 

Хотя с 80 столбцами это вряд ли имеет значение. (Обратите внимание, что это может быть более распространено для цикла set для большого количества строк, чем большое количество столбцов.) Однако петля set не решает проблему повторной ссылки на имя символа dt, которое вы упомянули в вопросе:

Мне это не нравится, потому что мне не нравится ссылка data.table в выражении aj.

Согласен. Поэтому лучшее, что я могу сделать, это вернуться к вашему циклу :=, но вместо этого используйте get.

for (col in paste0("V", 20:100)) 
    dt[, (col) := sqrt(get(col))] 

Тем не менее, я боюсь, что использование get в j может быть неэффективным. Для этого необходим бенчмаркинг, #1380. Кроме того, возможно, смущает использование get() на RHS, но не на LHS. Для решения, чтобы мы могли сахара в LHS и позволяют get(), а также, #1381:

for (col in paste0("V", 20:100)) 
    dt[, get(col) := sqrt(get(col))] 

Кроме того, может быть value из set может быть запущена в рамках DT, #1382.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(get(col)) 
+1

Большое спасибо за то, что вы положили круглые скобки вокруг 'col'. Пока я не вспомнил этот трюк, я получил столбец под названием «col». – Farrel

7

Это вы что искали?

dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x)) , .SDcols=20:100] 

Я слышал, говорят, что использование .SD не так эффективен, поскольку он создает копию таблицы заранее, но если таблица не огромный (очевидно, что это родственник в зависимости от вашей системы спецификации) Я сомневаюсь это будет иметь большое значение.

+4

Мне сказали, что [ 'set' также может ускорить операции, как это] (http://stackoverflow.com/questions/16846380/how-to-apply-same-function-to-every -specified-колонки-в-данных таблицы/16846530 # 16846530). – Frank

+0

@Frank +1 за этот ответ, и я зарезервирован для дальнейшего использования. Я бы не подумал использовать здесь цикл 'for'. Умная. –

+0

@Frank, я не знал о подходе 'for' loop +' set'. Мне придется подумать об использовании этого в будущем. –

13

Они должны работать, если вы хотите обратиться к столбцам по имени строки:

n = paste0("V", 20:100) 
dt[, (n) := lapply(n, function(x) {sqrt(get(x))})] 

или

dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})] 
+2

В последней строке требуется следующее: 'dt [, (n): = lapply (n, function (x) {sqrt (dt [[x]])})] ' – HywelMJ

+0

@HywelMJ спасибо, исправлено – eddi

+0

Возможно, также добавятся опции' .SDcols', такие как 'dt [, (n): = lapply (.SD, sqrt), .SDcols = n] '? Хм ..на секунду подумал, может быть, Саймон уже сделал что-то подобное уже. –

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