Chuyển đổi các lớp cột trong data.table


118

Tôi gặp sự cố khi sử dụng data.table: Làm cách nào để chuyển đổi các lớp cột? Đây là một ví dụ đơn giản: Với data.frame, tôi không gặp sự cố khi chuyển đổi nó, với data.table, tôi chỉ không biết làm thế nào:

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)

Tôi có bỏ lỡ điều gì đó rõ ràng ở đây không?

Cập nhật do bài đăng của Matthew: Tôi đã sử dụng phiên bản cũ hơn trước đây, nhưng ngay cả sau khi cập nhật lên 1.6.6 (phiên bản tôi sử dụng bây giờ) tôi vẫn gặp lỗi.

Cập nhật 2: Giả sử tôi muốn chuyển đổi mọi cột của lớp "nhân tố" thành cột "ký tự", nhưng không biết trước cột nào thuộc lớp nào. Với data.frame, tôi có thể làm như sau:

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

Tôi có thể làm điều gì đó tương tự với data.table không?

Cập nhật 3:

sessionInfo () R phiên bản 2.13.1 (2011-07-08) Nền tảng: x86_64-pc-mingw32 / x64 (64-bit)

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

Các "[" đối số nhà điều hành trong data.tablecác phương pháp khác nhau hơn là chodata.frame
IRTFM

1
Vui lòng dán lỗi thực tế hơn là #Produces error. Vẫn +1. Tôi không gặp bất kỳ lỗi nào, bạn có phiên bản nào? Tuy nhiên, có một vấn đề trong lĩnh vực này, nó đã được nêu ra trước đó, FR # 1224FR # 1493 được ưu tiên giải quyết. Tuy nhiên, câu trả lời của Andrie là cách tốt nhất.
Matt Dowle

Xin lỗi @MatthewDowle vì thiếu điều đó trong câu hỏi của tôi, tôi đã cập nhật bài đăng của mình.
Christoph_J

1
@Christoph_J Cảm ơn. Bạn có chắc chắn về invalid times argumentlỗi đó ? Làm việc tốt cho tôi. bạn có những phiên bản nào?
Matt Dowle,

Tôi đã cập nhật bài đăng của mình với sessionInfo (). Tuy nhiên, tôi đã kiểm tra nó trên máy làm việc của mình hôm nay. Hôm qua, trên máy chủ của tôi (Ubuntu) cũng xảy ra lỗi tương tự. Tôi sẽ cập nhật R và xem nếu sự cố vẫn còn ở đó.
Christoph_J

Câu trả lời:


104

Đối với một cột duy nhất:

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

Sử dụng 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 Vui lòng hiển thị lệnh nhóm mà bạn đang gặp khó khăn (vấn đề thực sự). Nghĩ rằng bạn có thể đã bỏ lỡ một cái gì đó đơn giản. Tại sao bạn đang cố gắng chuyển đổi các lớp cột?
Matt Dowle,

1
@Christoph_J Nếu bạn gặp khó khăn trong việc thao tác với data.tables, tại sao không chỉ đơn giản là chuyển đổi chúng tạm thời thành data.frames, làm sạch dữ liệu và sau đó chuyển đổi chúng trở lại data.tables?
Andrie

17
Cách thành ngữ để làm điều này cho một tập hợp con các cột (thay vì tất cả chúng) là gì? Tôi đã xác định một vector ký tự convcolscủa các cột. dt[,lapply(.SD,as.numeric),.SDcols=convcols]gần như ngay lập tức trong khi dt[,convcols:=lapply(.SD,as.numeric),.SDcols=convcols]gần như đóng băng R, vì vậy tôi đoán rằng tôi đang làm sai. Cảm ơn
Frank

4
@Frank Xem bình luận của Matt Dowle cho câu trả lời của Geneorama bên dưới ( stackoverflow.com/questions/7813578/… ); nó là đủ hữu ích và thành ngữ đối với tôi [bắt đầu quote] Một cách dễ dàng hơn và là sử dụng set()ví dụ for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))[cuối quote]
swihart

4
Tại sao bạn sử dụng tùy chọn by = ID?
skan

48

Thử cái này

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
bây giờ bạn có thể sử dụng Filterchức năng để xác định các cột, ví dụ: changeCols<- names(Filter(is.character, DT))
David Leal

1
IMO đây là câu trả lời tốt hơn, vì lý do tôi đã đưa ra trong câu trả lời đã chọn.
James Hirschorn

1
hoặc ngắn gọn hơn: changeCols <- names(DT)[sapply(DT, is.character)].
sindri_baldur

8

Đưa nhận xét của Matt Dowle lên câu trả lời của Geneorama ( https://stackoverflow.com/a/20808945/4241780 ) để làm cho nó rõ ràng hơn (được khuyến khích), bạn có thể sử dụng 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"

Được tạo vào 2020-02-12 bởi gói reprex (v0.3.0)

Xem các nhận xét khác của Matt tại https://stackoverflow.com/a/33000778/4241780 để biết thêm thông tin.

Biên tập.

Như đã lưu ý bởi Espen và trong help(set), jcó thể là "(Các) tên cột (ký tự) hoặc (các) số (số nguyên) được gán giá trị khi (các) cột đã tồn tại". Vì vậy, names_factors <- c(1L, 3L)cũng sẽ hoạt động.


Bạn có thể muốn thêm những gì names_factorsở đây. Tôi đoán nó được lấy từ stackoverflow.com/a/20808945/1666063 vì vậy nó names_factors = c('fac1', 'fac2')nằm trong trường hợp này - là tên các cột, nhưng nó cũng có thể là số cột, ví dụ 1; ncol (dt) sẽ chuyển đổi tất cả các cột
Espen Riskedal

@EspenRiskedal Cảm ơn điểm tốt, tôi đã chỉnh sửa bài đăng để làm cho nó rõ ràng hơn.
JWilliman

2

Đây là một cách BAD để làm điều đó! Tôi chỉ để lại câu trả lời này trong trường hợp nó giải quyết được các vấn đề kỳ lạ khác. Những phương pháp tốt hơn này có lẽ một phần là kết quả của các phiên bản data.table mới hơn ... vì vậy rất đáng để ghi lại cách khó này. Thêm vào đó, đây là một ví dụ về eval substitutecú pháp hay cho cú pháp.

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)

cái nào cho bạn

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
Một cách dễ dàng hơn và là sử dụng set()ví dụfor (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
Matt Dowle

1
Tôi nghĩ câu trả lời của tôi hoàn thành điều này trong một dòng, cho tất cả các phiên bản. Tuy nhiên, không chắc liệu setcó thích hợp hơn không.
Ben Rollert

1
Thông tin thêm về for(...)set(...)ở đây: stackoverflow.com/a/33000778/403310
Matt Dowle

1
@skan Câu hỏi hay. Nếu bạn không thể tìm thấy nó đã hỏi trước đây, vui lòng đặt một câu hỏi mới. Giúp đỡ những người khác trong tương lai.
Matt Dowle,

1
@skan đây là cách tôi đã làm điều đó: github.com/geneorama/geneorama/blob/master/R/…
geneorama

0

Tôi đã thử một số cách tiếp cận.

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

, hay nói cách khác

# 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

Tôi cung cấp một cách tổng quát hơn và an toàn hơn để thực hiện công việc này,

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

Hàm ..đảm bảo rằng chúng ta nhận được một biến ngoài phạm vi của data.table; set_colclass sẽ thiết lập các lớp của cols của bạn. Bạn có thể sử dụng nó như thế này:

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

-1

Nếu bạn có một danh sách tên cột trong data.table, bạn muốn thay đổi loại việc làm:

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

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

Câu trả lời này thực chất là một phiên bản tồi của câu trả lời của @ Nera bên dưới. Chỉ cần dt[, c(convert_to_character) := lapply(.SD, as.character), .SDcols=convert_to_character]gán bằng tham chiếu, thay vì sử dụng gán data.frame chậm hơn.
altabq

-3

thử:

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]])
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.