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

118

У меня проблема с использованием data.table: как преобразовать классы столбцов? Вот простой пример: с data.frame у меня нет проблем с его преобразованием, с data.table я просто не знаю как:

df <- data.frame(ID=c(rep("A", 5), rep("B",5)), Quarter=c(1:5, 1:5), value=rnorm(10))
#One way: http://stackoverflow.com/questions/2851015/r-convert-data-frame-columns-from-factors-to-characters
df <- data.frame(lapply(df, as.character), stringsAsFactors=FALSE)
#Another way
df[, "value"] <- as.numeric(df[, "value"])

library(data.table)
dt <- data.table(ID=c(rep("A", 5), rep("B",5)), Quarter=c(1:5, 1:5), value=rnorm(10))
dt <- data.table(lapply(dt, as.character), stringsAsFactors=FALSE) 
#Error in rep("", ncol(xi)) : invalid 'times' argument
#Produces error, does data.table not have the option stringsAsFactors?
dt[, "ID", with=FALSE] <- as.character(dt[, "ID", with=FALSE]) 
#Produces error: Error in `[<-.data.table`(`*tmp*`, , "ID", with = FALSE, value = "c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2)") : 
#unused argument(s) (with = FALSE)

Я упускаю здесь что-то очевидное?

Обновление из-за сообщения Мэтью: раньше я использовал старую версию, но даже после обновления до 1.6.6 (версия, которую я использую сейчас) я все равно получаю сообщение об ошибке.

Обновление 2: Допустим, я хочу преобразовать каждый столбец класса «фактор» в «символьный» столбец, но не знаю заранее, какой столбец принадлежит к какому классу. С помощью data.frame я могу делать следующее:

classes <- as.character(sapply(df, class))
colClasses <- which(classes=="factor")
df[, colClasses] <- sapply(df[, colClasses], as.character)

Могу ли я сделать что-то подобное с data.table?

Обновление 3:

sessionInfo () R версия 2.13.1 (2011-07-08) Платформа: x86_64-pc-mingw32 / x64 (64-разрядная)

locale:
[1] C

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] data.table_1.6.6

loaded via a namespace (and not attached):
[1] tools_2.13.1
Christoph_J
источник
Аргументы оператора «[» в data.tableметодах отличаются от тех, для которых они предназначеныdata.frame
IRTFM
1
Пожалуйста, вставьте фактическую ошибку, а не #Produces error. +1 в любом случае. Я не получаю ошибок, какая у вас версия? Однако в этой области есть проблема, она уже поднималась, FR № 1224 и FR № 1493 являются приоритетными для решения. Однако ответ Андри - лучший способ.
Мэтт Доул
Извините @MatthewDowle за то, что пропустил это в моем вопросе, я обновил свой пост.
Christoph_J
1
@Christoph_J Спасибо. Вы уверены в этой invalid times argumentошибке? У меня работает нормально. Какая у тебя версия?
Мэтт Доул
Я обновил свой пост с помощью sessionInfo (). Однако сегодня я проверил это на своей рабочей машине. Вчера на моей домашней машине (Ubuntu) произошла такая же ошибка. Я обновлю R и посмотрю, сохраняется ли проблема.
Christoph_J

Ответы:

104

Для одного столбца:

dtnew <- dt[, Quarter:=as.character(Quarter)]
str(dtnew)

Classes ‘data.table’ and 'data.frame':  10 obs. of  3 variables:
 $ ID     : Factor w/ 2 levels "A","B": 1 1 1 1 1 2 2 2 2 2
 $ Quarter: chr  "1" "2" "3" "4" ...
 $ value  : num  -0.838 0.146 -1.059 -1.197 0.282 ...

Использование lapplyи as.character:

dtnew <- dt[, lapply(.SD, as.character), by=ID]
str(dtnew)

Classes ‘data.table’ and 'data.frame':  10 obs. of  3 variables:
 $ ID     : Factor w/ 2 levels "A","B": 1 1 1 1 1 2 2 2 2 2
 $ Quarter: chr  "1" "2" "3" "4" ...
 $ value  : chr  "1.487145280568" "-0.827845218358881" "0.028977182770002" "1.35392750102305" ...
Andrie
источник
2
@Christoph_J Пожалуйста, покажите команду группировки, с которой вы боретесь (настоящая проблема). Думаю, вы упустили что-то простое. Почему вы пытаетесь преобразовать классы столбцов?
Мэтт Доул
1
@Christoph_J Если вам сложно манипулировать таблицами data.tables, почему бы просто не преобразовать их временно в data.frames, очистить данные и затем преобразовать их обратно в data.tables?
Андри
17
Каков идиоматический способ сделать это для подмножества столбцов (а не для всех)? Я определил вектор символов convcolsстолбцов. dt[,lapply(.SD,as.numeric),.SDcols=convcols]почти мгновенно, в то время как dt[,convcols:=lapply(.SD,as.numeric),.SDcols=convcols]R почти зависает, поэтому я предполагаю, что делаю это неправильно. Спасибо
Фрэнк
4
@Frank См. Комментарий Мэтта Доула к ответу Дженорамы ниже ( stackoverflow.com/questions/7813578/… ); это было полезно и достаточно идиоматично для меня [начало цитаты] Другой и более простой способ - использовать, set()например, for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))[конечная цитата]
swihart
4
Почему вы используете опцию by = ID?
skan
48

Попробуй это

DT <- data.table(X1 = c("a", "b"), X2 = c(1,2), X3 = c("hello", "you"))
changeCols <- colnames(DT)[which(as.vector(DT[,lapply(.SD, class)]) == "character")]

DT[,(changeCols):= lapply(.SD, as.factor), .SDcols = changeCols]
Нера
источник
7
теперь вы можете использовать Filterфункцию для идентификации столбцов, например: changeCols<- names(Filter(is.character, DT))
Дэвид Лил
1
ИМО, это лучший ответ по той причине, которую я указал в выбранном ответе.
Джеймс Хиршорн
1
или более сжато: changeCols <- names(DT)[sapply(DT, is.character)].
sindri_baldur
8

Подняв комментарий Мэтта Доула к ответу Geneorama ( https://stackoverflow.com/a/20808945/4241780 ), чтобы сделать его более очевидным (как рекомендуется), вы можете использовать for(...)set(...).


library(data.table)

DT = data.table(a = LETTERS[c(3L,1:3)], b = 4:7, c = letters[1:4])
DT1 <- copy(DT)
names_factors <- c("a", "c")

for(col in names_factors)
  set(DT, j = col, value = as.factor(DT[[col]]))

sapply(DT, class)
#>         a         b         c 
#>  "factor" "integer"  "factor"

Создано 12 февраля 2020 г. пакетом REPEX (v0.3.0)

См. Еще один комментарий Мэтта на https://stackoverflow.com/a/33000778/4241780 для получения дополнительной информации.

Редактировать.

Как отметил Эспен и в help(set), jможет быть «имя (я) столбца (символ) или номер (а) (целое число), которому будет присвоено значение, если столбец (я) уже существует»). Так names_factors <- c(1L, 3L)тоже будет работать.

JWilliman
источник
Возможно, вы захотите добавить то, что names_factorsздесь. Я предполагаю, что это взято из stackoverflow.com/a/20808945/1666063, поэтому names_factors = c('fac1', 'fac2')в данном случае это имена столбцов, но это также могут быть номера столбцов, например 1; ncol (dt), который преобразовал бы все столбцы
Эспен Рискедал,
@EspenRiskedal Спасибо, хороший замечание, я отредактировал сообщение, чтобы сделать его более очевидным.
JWilliman,
2

Это ПЛОХОЙ способ сделать это! Я оставляю этот ответ только на тот случай, если он решит другие странные проблемы. Эти лучшие методы, вероятно, частично являются результатом новых версий data.table ... поэтому стоит задокументировать этот трудный путь. Кроме того, это хороший пример eval substituteсинтаксиса для синтаксиса.

library(data.table)
dt <- data.table(ID = c(rep("A", 5), rep("B",5)), 
                 fac1 = c(1:5, 1:5), 
                 fac2 = c(1:5, 1:5) * 2, 
                 val1 = rnorm(10),
                 val2 = rnorm(10))

names_factors = c('fac1', 'fac2')
names_values = c('val1', 'val2')

for (col in names_factors){
  e = substitute(X := as.factor(X), list(X = as.symbol(col)))
  dt[ , eval(e)]
}
for (col in names_values){
  e = substitute(X := as.numeric(X), list(X = as.symbol(col)))
  dt[ , eval(e)]
}

str(dt)

что дает вам

Classes ‘data.table’ and 'data.frame':  10 obs. of  5 variables:
 $ ID  : chr  "A" "A" "A" "A" ...
 $ fac1: Factor w/ 5 levels "1","2","3","4",..: 1 2 3 4 5 1 2 3 4 5
 $ fac2: Factor w/ 5 levels "2","4","6","8",..: 1 2 3 4 5 1 2 3 4 5
 $ val1: num  0.0459 2.0113 0.5186 -0.8348 -0.2185 ...
 $ val2: num  -0.0688 0.6544 0.267 -0.1322 -0.4893 ...
 - attr(*, ".internal.selfref")=<externalptr> 
geneorama
источник
42
Другой, более простой способ - использовать, set()например,for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
Мэтт Доул
1
Я думаю, что мой ответ описывает это одной строкой для всех версий. Не уверен, что setэто более уместно.
Бен Роллерт
1
Дополнительная информация for(...)set(...)здесь: stackoverflow.com/a/33000778/403310
Мэтт Доул
1
@skan Хороший вопрос. Если вы не можете найти его ранее, задайте новый вопрос. Помогает другим в будущем.
Мэтт Доул
1
@skan, вот как я это сделал: github.com/geneorama/geneorama/blob/master/R/…
geneorama
0

Я пробовал несколько подходов.

# BY {dplyr}
data.table(ID      = c(rep("A", 5), rep("B",5)), 
           Quarter = c(1:5, 1:5), 
           value   = rnorm(10)) -> df1
df1 %<>% dplyr::mutate(ID      = as.factor(ID),
                       Quarter = as.character(Quarter))
# check classes
dplyr::glimpse(df1)
# Observations: 10
# Variables: 3
# $ ID      (fctr) A, A, A, A, A, B, B, B, B, B
# $ Quarter (chr) "1", "2", "3", "4", "5", "1", "2", "3", "4", "5"
# $ value   (dbl) -0.07676732, 0.25376110, 2.47192852, 0.84929175, -0.13567312,  -0.94224435, 0.80213218, -0.89652819...

, или иным образом

# from list to data.table using data.table::setDT
list(ID      = as.factor(c(rep("A", 5), rep("B",5))), 
     Quarter = as.character(c(1:5, 1:5)), 
     value   = rnorm(10)) %>% setDT(list.df) -> df2
class(df2)
# [1] "data.table" "data.frame"
uribo
источник
0

Я предлагаю более общий и безопасный способ сделать это,

".." <- function (x) 
{
  stopifnot(inherits(x, "character"))
  stopifnot(length(x) == 1)
  get(x, parent.frame(4))
}


set_colclass <- function(x, class){
  stopifnot(all(class %in% c("integer", "numeric", "double","factor","character")))
  for(i in intersect(names(class), names(x))){
    f <- get(paste0("as.", class[i]))
    x[, (..("i")):=..("f")(get(..("i")))]
  }
  invisible(x)
}

Функция ..гарантирует, что мы получаем переменную вне области data.table; set_colclass установит классы ваших столбцов. Вы можете использовать это так:

dt <- data.table(i=1:3,f=3:1)
set_colclass(dt, c(i="character"))
class(dt$i)
liqg3
источник
-1

Если у вас есть список имен столбцов в data.table, вы хотите изменить класс do:

convert_to_character <- c("Quarter", "value")

dt[, convert_to_character] <- dt[, lapply(.SD, as.character), .SDcols = convert_to_character]
Эмиль Ликке Йенсен
источник
Этот ответ по сути является плохой версией ответа @Nera ниже. Просто сделайте dt[, c(convert_to_character) := lapply(.SD, as.character), .SDcols=convert_to_character]назначение по ссылке, а не используйте более медленное назначение data.frame.
altabq
-3

пытаться:

dt <- data.table(A = c(1:5), 
                 B= c(11:15))

x <- ncol(dt)

for(i in 1:x) 
{
     dt[[i]] <- as.character(dt[[i]])
}
user151444
источник