Преобразование * некоторых * классов столбцов в data.table

Я хочу преобразовать подмножество data.table cols в новый класс. Здесь есть популярный вопрос (Преобразование классов столбцов в data.table), но ответ создает новый объект, а не работает на стартовом объекте.

Возьмем этот пример:

dat <- data.frame(ID=c(rep("A", 5), rep("B",5)), Quarter=c(1:5, 1:5), value=rnorm(10))
cols <- c('ID', 'Quarter')

Как лучше всего преобразовать только столбцы cols в (например) фактор? В нормальном data.frame вы можете сделать это:

dat[, cols] <- lapply(dat[, cols], factor)

но это не работает для таблицы data.table, и это также не работает

dat[, .SD := lapply(.SD, factor), .SDcols = cols]

Комментарий в связанном вопросе от Matt Dowle (с декабря 2013 года) предлагает следующее, которое прекрасно работает, но кажется немного менее элегантным.

for (j in cols) set(dat, j = j, value = factor(dat[[j]]))

Есть ли в настоящее время лучший ответ data.table(т.е. более короткий + не генерирует переменную счетчика), или я должен просто использовать выше + rm(j)?

Ответ 1

Помимо использования опции, предложенной Мэттом Доуле, другой способ изменения классов столбцов выглядит следующим образом:

dat[, (cols) := lapply(.SD, factor), .SDcols=cols]

Используя оператор :=, вы обновляете данные по ссылке. Проверьте, работает ли это:

> sapply(dat,class)
       ID   Quarter     value 
 "factor"  "factor" "numeric" 

Как было предложено @MattDowle в комментариях, вы также можете использовать комбинацию for(...) set(...) следующим образом:

for (col in cols) set(dat, j = col, value = factor(dat[[col]]))

который даст тот же результат. Третий вариант:

for (col in cols) dat[, (col) := factor(dat[[col]])]

В меньших наборах данных параметр for(...) set(...) примерно в три раза быстрее, чем параметр lapply (но это не имеет особого значения, поскольку это небольшой набор данных). В больших наборах данных (например, 2 миллиона строк) каждый из этих подходов занимает примерно столько же времени. Для тестирования на более крупном наборе данных я использовал:

dat <- data.table(ID=c(rep("A", 1e6), rep("B",1e6)),
                  Quarter=c(1:1e6, 1:1e6),
                  value=rnorm(10))

Иногда вам придется делать это по-другому (например, когда числовые значения сохраняются как фактор). Тогда вы должны использовать что-то вроде этого:

dat[, (cols) := lapply(.SD, function(x) as.integer(as.character(x))), .SDcols=cols]


ПРЕДУПРЕЖДЕНИЕ: Следующее объяснение - не data.table - способ выполнения действий. Данному не обновляется по ссылке, потому что копия создается и сохраняется в памяти (как указано в @Frank), что увеличивает использование памяти. Это больше подходит для объяснения работы with=FALSE.

Если вы хотите изменить классы столбцов так же, как и с фреймворком данных, вы должны добавить with = FALSE следующим образом:

dat[, cols] <- lapply(dat[, cols, with = FALSE], factor)

Проверьте, работает ли это:

> sapply(dat,class)
       ID   Quarter     value 
 "factor"  "factor" "numeric" 

Если вы не добавите with = FALSE, datatable будет оценивать dat[, cols] как вектор. Проверьте разницу в выходе между dat[, cols] и dat[, cols, with=FALSE]:

> dat[, cols]
[1] "ID"      "Quarter"

> dat[, cols, with=FALSE]
    ID Quarter
 1:  A       1
 2:  A       2
 3:  A       3
 4:  A       4
 5:  A       5
 6:  B       1
 7:  B       2
 8:  B       3
 9:  B       4
10:  B       5

Ответ 2

Вы можете использовать .SDcols:

dat[, cols] <- dat[, lapply(.SD, factor), .SDcols=cols]