转换 data.table 中的列类

我在使用 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: 假设我想把类“ factor”的每一列都转换成一个字符列,但是事先不知道哪一列属于哪个类。使用 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-ming w32/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
135691 次浏览

对于一个专栏:

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

这是一个坏的方式做到这一点!我留下这个答案只是为了以防它能解决其他奇怪的问题。这些更好的方法可能部分归功于更新的 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>

我尝试了几种方法。

# 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"

试试这个

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]

尝试:

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]])
}

我提供了一个更通用更安全的方法来做这件事,

".." <- 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 将设置您的 protocol 的类。 你可以这样使用它:

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

如果 data.table 中有一个列名列表,则需要更改 do 的类:

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


dt[, convert_to_character] <- dt[, lapply(.SD, as.character), .SDcols = convert_to_character]

将 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"

Reprex 软件包于2020-02-12创作(v0.3.0)

有关更多信息,请参阅马特在 https://stackoverflow.com/a/33000778/4241780上的另一条评论。

剪辑。

正如 Espen 和 help(set)中所指出的,j可能是“列名(字符)或数字(整数) ,当列已经存在时将被赋值”。所以 names_factors <- c(1L, 3L)也可以工作。

这与@Nera 建议的首先检查类的方法相同,但不使用 .SD,而是使用带有 set的 data.table 的快速循环作为@Matt Dowle 解决方案,并添加类检查。

for (j in seq_len(ncol(DT))){
if(class(DT[[j]]) == 'factor')
set(DT, j = j, value = as.character(DT[[j]]))
}