เลือก / กำหนดให้กับ data.table เมื่อชื่อตัวแปรถูกเก็บไว้ในเวกเตอร์อักขระ


92

คุณอ้างถึงตัวแปรอย่างไรในdata.tableกรณีที่ชื่อตัวแปรถูกเก็บไว้ในเวกเตอร์อักขระ ตัวอย่างเช่นสิ่งนี้ใช้ได้กับdata.frame:

df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
#   col1
# 1    4
# 2    5
# 3    6

ฉันจะดำเนินการเดียวกันนี้กับ data.table ได้อย่างไรไม่ว่าจะมีหรือไม่มี:=สัญกรณ์ สิ่งที่ชัดเจนของการdt[ , list(colname)]ใช้งานไม่ได้ (และฉันไม่คาดหวัง)

คำตอบ:


133

สองวิธีในการเลือกตัวแปรโดยทางโปรแกรม:

  1. with = FALSE:

     DT = data.table(col1 = 1:3)
     colname = "col1"
     DT[, colname, with = FALSE] 
     #    col1
     # 1:    1
     # 2:    2
     # 3:    3
    
  2. 'dot dot' ( ..) คำนำหน้า:

     DT[, ..colname]    
     #    col1
     # 1:    1
     # 2:    2
     # 3:    3
    

สำหรับคำอธิบายเพิ่มเติมของ..สัญกรณ์'dot dot' ( ) โปรดดูคุณลักษณะใหม่ใน 1.10.2 (ขณะนี้ยังไม่ได้อธิบายไว้ในข้อความช่วยเหลือ)

ในการกำหนดให้กับตัวแปรให้ห่อ LHS ของ:=ในวงเล็บ:

DT[, (colname) := 4:6]    
#    col1
# 1:    4
# 2:    5
# 3:    6

ส่วนหลังนี้เรียกว่าคอลัมน์plonkเนื่องจากคุณแทนที่เวกเตอร์คอลัมน์ทั้งหมดโดยการอ้างอิง หากมีชุดย่อยiอยู่ชุดย่อยจะถูกกำหนดย่อยโดยการอ้างอิง parens รอบ(colname)เป็นชวเลขที่เปิดตัวในเวอร์ชัน v1.9.4 เมื่อ CRAN ต.ค. 2014 นี่คือรายการข่าว :

การใช้with = FALSEกับ:=ถูกเลิกใช้แล้วในทุกกรณีเนื่องจากว่าการตัด LHS :=ด้วยวงเล็บเป็นที่ต้องการในบางครั้ง

colVar = "col1"
DT[, (colVar) := 1]                             # please change to this
DT[, c("col1", "col2") := 1]                    # no change
DT[, 2:4 := 1]                                  # no change
DT[, c("col1","col2") := list(sum(a), mean(b))]  # no change
DT[, `:=`(...), by = ...]                       # no change

ดูส่วนรายละเอียดเพิ่มเติมใน?`:=`:

DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol

และเพื่อตอบคำถามเพิ่มเติมในความคิดเห็นนี่เป็นวิธีเดียว (ตามปกติมีหลายวิธี):

DT[, colname := cumsum(get(colname)), with = FALSE]
#    col1
# 1:    4
# 2:    9
# 3:   15 

หรือคุณอาจพบว่าง่ายกว่าในการอ่านเขียนและดีบักเฉพาะกับevala pasteคล้ายกับการสร้างคำสั่ง SQL แบบไดนามิกเพื่อส่งไปยังเซิร์ฟเวอร์:

expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"

eval(parse(text=expr))
#    col1
# 1:    4
# 2:   13
# 3:   28

หากคุณทำมากคุณสามารถกำหนดฟังก์ชันตัวช่วยEVAL:

EVAL = function(...)eval(parse(text=paste0(...)),envir=parent.frame(2))

EVAL("DT[,",colname,":=cumsum(",colname,")]")
#    col1
# 1:    4
# 2:   17
# 3:   45

ตอนนี้data.table1.8.2 ปรับประสิทธิภาพให้เหมาะสมโดยอัตโนมัติjแล้วจึงควรใช้evalวิธีนี้ get()ในjป้องกันไม่ให้การเพิ่มประสิทธิภาพบางอย่างเช่น

set()หรือมี ค่าใช้จ่ายต่ำรูปแบบการทำงาน:=ซึ่งจะดีที่นี่ ดู?set.

set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
#    col1
# 1:    4
# 2:   21
# 3:   66

1
ขอบคุณสำหรับคำตอบของ Matthew ด้วย = FALSE ช่วยแก้ปัญหาส่วนหนึ่งของฉันได้อย่างแน่นอน ในความเป็นจริงฉันต้องการแทนที่คอลัมน์ด้วย cumsum ของคอลัมน์ ฉันสามารถอ้างอิงชื่อคอลัมน์ตามตัวแปรทางด้านขวามือของงานได้หรือไม่
frankc

โดยสิ้นเชิงฉันเพิ่งจัดเก็บ cumsum ไว้ภายนอกด้วยชื่ออื่นที่ไม่มีอยู่ใน dt และใช้งานได้ดี
frankc

1
แต่นั่นจะเป็นบรรทัดพิเศษทั้งหมด! ไม่สวยหรูเท่าไหร่ :) แต่โอเคบางครั้งก็มีประโยชน์ ในกรณีเหล่านี้ควรเริ่มต้นชื่อตัวแปรด้วย.หรือ..เพื่อหลีกเลี่ยงการกำบังที่อาจเกิดขึ้นหากDTเคยมีสัญลักษณ์นั้นเป็นชื่อคอลัมน์ในอนาคต (และยึดตามหลักการที่ชื่อคอลัมน์ไม่ได้ขึ้นต้นด้วย.) มีบางคำขอคุณลักษณะที่จะทำให้มันมีประสิทธิภาพมากขึ้นกับปัญหาขอบเขตเช่นนั้นเช่นการเพิ่มเป็นและ.() ..()
Matt Dowle

ฉันตอบกลับไปก่อนที่ฉันจะสังเกตเห็นว่าคุณแก้ไขคำตอบของคุณ ความคิดแรกของฉันได้รับการประเมิน (parse ()) แต่ด้วยเหตุผลบางอย่างฉันมีปัญหาในการทำให้มันใช้งานได้เมื่อมันเริ่มต้นกับฉันที่จะทำจากภายนอก นี่เป็นคำตอบที่ดีพร้อมหลายสิ่งที่ฉันไม่ได้คิด ขอบคุณสำหรับ data.table โดยทั่วไปเป็นแพ็คเกจที่ยอดเยี่ยม
frankc

2
โปรดทราบว่าคุณสามารถใช้กึ่ง Perl แก้ไขประเภทสตริงของ fn$จากแพคเกจ gsubfn เพื่อปรับปรุงการอ่านของการแก้ปัญหา EVAL library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" )นี้:
G.Grothendieck

8

* นี่ไม่ใช่คำตอบจริงๆ แต่ฉันมีเครดิตทางถนนไม่เพียงพอที่จะแสดงความคิดเห็น: /

อย่างไรก็ตามสำหรับใครก็ตามที่อาจต้องการสร้างคอลัมน์ใหม่ในตารางข้อมูลที่มีชื่อเก็บไว้ในตัวแปรฉันมีสิ่งต่อไปนี้ในการทำงาน ฉันไม่มีเงื่อนงำเกี่ยวกับประสิทธิภาพของมัน ข้อเสนอแนะในการปรับปรุง? ปลอดภัยหรือไม่ที่จะถือว่าคอลัมน์ใหม่ที่ไม่มีชื่อจะได้รับชื่อ V1 เสมอ

colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))

สังเกตว่าฉันสามารถอ้างอิงได้ดีใน sum () แต่ดูเหมือนจะไม่สามารถกำหนดในขั้นตอนเดียวกันได้ BTW เหตุผลที่ฉันต้องทำคือ colname จะขึ้นอยู่กับการป้อนข้อมูลของผู้ใช้ในแอป Shiny


+1 สำหรับการทำงาน: ฉันยอมรับว่านี่จะต้องไม่ใช่ "วิธี" ในการทำสิ่งนี้ แต่เมื่อใช้เวลาเพียง 45 นาทีในการโพสต์ SO ทุกโพสต์ในหัวข้อนี้นี่เป็นทางออกเดียวที่ฉันสามารถทำได้จริง งาน - ขอบคุณที่สละเวลาชี้ให้เห็น!
neuropsych

ดีใจที่ช่วยได้! น่าเสียดายที่ฉันไม่เคยพบโซลูชันที่หรูหรากว่านี้โดยตรงโดยใช้ data.tables แม้ว่าซับ 3 ตัวนี้จะไม่น่ากลัวก็ตาม ในสถานการณ์ของฉันฉันพบว่าทางเลือกที่ง่ายกว่านั้นคือการใช้ tidyr เพื่อทำให้ข้อมูลของฉัน "ยาว" แทนที่จะเป็น "กว้าง" เนื่องจากขึ้นอยู่กับการป้อนข้อมูลของผู้ใช้ฉันจึงสามารถกรองในคอลัมน์เดียวได้เสมอแทนที่จะเลือกจากชุด ของคอลัมน์
efh0888

2
ไม่ปลอดภัยที่จะถือว่าV1เป็นชื่อใหม่ ตัวอย่างเช่นถ้าคุณอ่าน csv ด้วยfreadและมีคอลัมน์ที่ไม่มีชื่อคอลัมน์นั้นจะมีV1ชื่อ (และread.csvจะให้X) ดังนั้นจึงเป็นไปได้ว่าตารางของคุณมีไฟล์V1. อาจจะเพิ่งได้ชื่อโดยnames(DT)[length(names(DT))]
dracodoc

2

สำหรับหลายคอลัมน์และฟังก์ชันที่ใช้กับค่าคอลัมน์

เมื่อปรับปรุงค่าจากฟังก์ชั่นที่ RHS ต้องเป็นวัตถุรายการเพื่อใช้ในวง.SDที่มีlapplyจะทำเคล็ดลับ

ตัวอย่างด้านล่างแปลงคอลัมน์จำนวนเต็มเป็นคอลัมน์ตัวเลข

a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class)  # show classes of columns
#         a           b          c1 
# "integer"   "integer" "character" 

# column name character vector
nm <- c("a", "b")

# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]

sapply(a1, class)
#         a           b          c1 
# "numeric"   "numeric" "character" 

2

ดึงข้อมูลหลายคอลัมน์จาก data.table ผ่านตัวแปรหรือฟังก์ชัน:

library(data.table)

x <- data.table(this=1:2,that=1:2,whatever=1:2)

# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]

# === indirect via  variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)

x[, ..mycols]
x[, .SD, .SDcols=mycols]

# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]

ซึ่งทั้งหมดให้ผล

   that whatever
1:    1        1
2:    2        2

ฉันพบ.SDcolsวิธีที่สง่างามที่สุด


1

คุณสามารถลองสิ่งนี้

colname <- as.name ("COL_NAME")

DT2 <- DT [, รายการ (COL_SUM = ผลรวม (eval (colname ,.SD))) โดย = c (กลุ่ม)]


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