แปลงคลาสคอลัมน์ใน 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: สมมติว่าฉันต้องการแปลงทุกคอลัมน์ของคลาส "factor" เป็นคอลัมน์ "character" แต่ไม่รู้ล่วงหน้าว่าคอลัมน์ใดเป็นคลาสใด ด้วย 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

1
#Produces errorโปรดวางข้อผิดพลาดที่เกิดขึ้นจริงมากกว่า +1 ต่อไป ฉันไม่ได้รับข้อผิดพลาดใด ๆ คุณมีเวอร์ชันใด มีปัญหาในพื้นที่นี้แม้ว่าจะมีการยกมาก่อนFR # 1224และFR # 1493มีความสำคัญสูงสำหรับที่อยู่ คำตอบของ Andrie เป็นวิธีที่ดีที่สุดแม้ว่า
Matt Dowle

ขอโทษ @MatthewDowle ที่หายไปในคำถามของฉันฉันอัปเดตโพสต์ของฉัน
Christoph_J

1
@Christoph_J ขอบคุณ คุณแน่ใจเกี่ยวกับinvalid times argumentข้อผิดพลาดนั้นหรือไม่? ทำงานได้ดีสำหรับฉัน คุณมีเวอร์ชันใด
Matt Dowle

ฉันอัปเดตโพสต์ของฉันด้วย 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" ...

2
@Christoph_J โปรดแสดงคำสั่งการจัดกลุ่มที่คุณกำลังดิ้นรน (ปัญหาที่แท้จริง) คิดว่าคุณอาจพลาดอะไรง่ายๆ ทำไมคุณถึงพยายามแปลงคลาสคอลัมน์
Matt Dowle

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/… ); มันมีประโยชน์และเป็นสำนวนมากพอสำหรับฉัน [start quote] อีกวิธีที่ง่ายกว่าคือใช้set()เช่นfor (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))[end quote]
swihart

4
ทำไมคุณถึงใช้ตัวเลือก by = ID
กัน

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อาจจะเป็น "ชื่อคอลัมน์ (s) (ตัวอักษร) หรือหมายเลข (s) (จำนวนเต็ม) จะได้รับมอบหมายค่าเมื่อคอลัมน์ (s) มีอยู่แล้ว" ดังนั้น 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

1
ฉันคิดว่าคำตอบของฉันทำได้ในหนึ่งบรรทัดสำหรับทุกเวอร์ชัน ไม่แน่ใจว่าsetเหมาะสมกว่าหรือไม่
Ben Rollert

1
ข้อมูลเพิ่มเติมที่for(...)set(...)นี่: stackoverflow.com/a/33000778/403310
Matt Dowle

1
@skan คำถามดีๆ. หากคุณไม่พบข้อความที่ถามก่อนหน้านี้โปรดถามคำถามใหม่ ช่วยเหลือผู้อื่นในอนาคต
Matt Dowle

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"

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.