转换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)

我想念这里明显的东西吗?

由于Matthew的帖子而导致的更新:我以前使用的是旧版本,但是即使更新为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

data.table方法中的[[]运算符参数与适用方法不同data.frame
IRTFM 2011年

1
请粘贴实际错误而不是#Produces error。还是+1。我没有任何错误,您使用哪个版本?但是,在这方面存在一个问题,之前已经提出过,FR#1224FR#1493是需要优先解决的问题。不过,安德里的答案是最好的方法。
马特·道尔

抱歉@MatthewDowle遗漏了我的问题,我更新了帖子。
Christoph_J

1
@Christoph_J谢谢。您确定该invalid times argument错误吗?对我来说很好。您有哪个版本?
马特·道尔

我用sessionInfo()更新了我的帖子。但是,我今天在工作机器上检查了它。昨天,在我的家用计算机(Ubuntu)上发生了相同的错误。我将更新R,看看问题是否仍然存在。
Christoph_J

Answers:


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 ...

使用lapplyas.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" ...

2
@Christoph_J请显示您正在使用的分组命令(真正的问题)。认为您可能错过了一些简单的事情。为什么要尝试转换列类?
马特·道尔

1
@Christoph_J如果您难以操作data.tables,为什么不简单地将它们临时转换为data.frames,先进行数据清理,然后再将其转换回data.tables?
Andrie

17
对于一列列(而不是全部列),这样做的惯用方式是什么?我已经定义了convcols列的字符向量。dt[,lapply(.SD,as.numeric),.SDcols=convcols]几乎是瞬间,却dt[,convcols:=lapply(.SD,as.numeric),.SDcols=convcols]几乎冻结了R,所以我猜我做错了。谢谢
法兰克

4
@Frank参见下面的马特·道尔(Matt Dowle)对Geneorama的回答的评论(stackoverflow.com/questions/7813578/…);这对我来说是很有帮助和习惯的[开始报价]另一个更简单的方法是使用set()例如for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]])) [结束报价]
swihart 2014年

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))
David Leal

1
IMO,这是更好的答案,因为我选择了答案。
James Hirschorn

1
或者更简洁:changeCols <- names(DT)[sapply(DT, is.character)]
sindri_baldur

8

提出马特·道尔(Matt Dowle)对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"

创建于2020-02-12,由 reprex软件包(v0.3.0)

在上查看Matt的其他评论 更多信息, https://stackoverflow.com/a/33000778/4241780

编辑。

正如Espen和中所述help(set)j可能是“当列已存在时要分配值的列名称(字符)或编号(整数)”。这样 names_factors <- c(1L, 3L)也可以。


您可能要添加names_factors此处的内容。我猜它取自stackoverflow.com/a/20808945/1666063,所以names_factors = c('fac1', 'fac2')在这种情况下-这是列名。但是也可以是例如1; ncol(dt)的列号,它将转换所有列
Espen Riskedal

@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> 

42
另一种更简单的方法是使用set()例如for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
Matt Dowle 2013年

1
对于所有版本,我认为我的回答可以在一行中完成。set虽然不确定是否更合适。
Ben Rollert 2014年

1
for(...)set(...)此处的更多信息:stackoverflow.com/a/33000778/403310
Matt Dowle

1
@skan好问题。如果您之前找不到它,请提出一个新问题。将来帮助他人。
马特·道尔


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"

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将设置cols的类。您可以像这样使用它:

dt <- data.table(i=1:3,f=3:1)
set_colclass(dt, c(i="character"))
class(dt$i)

-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]])
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.