วิธีการลบแถวโดยการอ้างอิงใน data.table?


150

data.tableคำถามของฉันจะเกี่ยวข้องกับการที่ได้รับมอบหมายโดยอ้างอิงกับการคัดลอกใน ฉันต้องการทราบว่าใครสามารถลบแถวโดยการอ้างอิงคล้ายกับ

DT[ , someCol := NULL]

ฉันต้องการรู้เกี่ยวกับ

DT[someRow := NULL, ]

ฉันคิดว่ามันมีเหตุผลที่ดีที่ฟังก์ชั่นนี้ไม่มีอยู่ดังนั้นบางทีคุณอาจชี้ให้เห็นถึงทางเลือกที่ดีสำหรับวิธีการคัดลอกตามปกติดังนี้ โดยเฉพาะอย่างยิ่งไปกับชื่นชอบจากตัวอย่าง (data.table)

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
#      x y v
# [1,] a 1 1
# [2,] a 3 2
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

ว่าฉันต้องการลบแถวแรกจาก data.table นี้ ฉันรู้ว่าฉันสามารถทำสิ่งนี้:

DT <- DT[-1, ]

แต่บ่อยครั้งที่เราอาจต้องการที่จะหลีกเลี่ยงเพราะเรากำลังคัดลอกวัตถุ (และที่ต้องใช้ประมาณ 3 * หน่วยความจำ N ถ้า N object.size(DT), เป็นแหลมออกจากที่นี่ตอนนี้ผมพบว่า. set(DT, i, j, value)ฉันรู้วิธีการค่าเฉพาะชุด (เช่นที่นี่:. การตั้งค่าทั้งหมด ค่าในแถว 1 และ 2 และคอลัมน์ 2 และ 3 ถึงศูนย์)

set(DT, 1:2, 2:3, 0) 
DT
#      x y v
# [1,] a 0 0
# [2,] a 0 0
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

แต่ฉันจะลบสองแถวแรกได้อย่างไรพูด? การทำ

set(DT, 1:2, 1:3, NULL)

ตั้งค่า DT ทั้งหมดเป็น NULL

ความรู้เกี่ยวกับ SQL ของฉันมี จำกัด มากดังนั้นคุณบอกฉัน: data.table ที่กำหนดใช้เทคโนโลยี SQL มีคำสั่ง SQL เทียบเท่าหรือไม่

DELETE FROM table_name
WHERE some_column=some_value

ใน data.table?


17
ฉันไม่คิดว่ามันคือการที่data.table()ใช้เทคโนโลยี SQL มากเป็นหนึ่งสามารถวาดขนานระหว่างการดำเนินงานที่แตกต่างกันใน SQL และข้อโต้แย้งต่าง ๆ data.tableไปยัง สำหรับฉันแล้วการอ้างอิงถึง "เทคโนโลยี" ค่อนข้างบอกเป็นนัยว่าdata.tableมันนั่งอยู่ด้านบนของฐานข้อมูล SQL ที่ไหนสักแห่งซึ่ง AFAIK ไม่ได้เป็นเช่นนั้น
Chase

1
ขอบคุณการไล่ล่า ใช่ฉันเดาว่าการเปรียบเทียบ sql เป็นการคาดเดาที่ยาก
Florian Oswald

1
บ่อยครั้งที่มันควรจะเพียงพอที่จะกำหนดธงสำหรับการเก็บรักษาแถวเช่นDT[ , keep := .I > 1]จากนั้นชุดย่อยสำหรับการดำเนินงานในภายหลัง: DT[(keep), ...]บางทีแม้แต่setindex(DT, keep)ความเร็วของการย่อยนี้ ไม่ใช่ยาครอบจักรวาล แต่คุ้มค่าที่จะพิจารณาว่าเป็นตัวเลือกการออกแบบในเวิร์กโฟลว์ของคุณ - คุณต้องการลบแถวเหล่านั้นทั้งหมดออกจากหน่วยความจำหรือคุณต้องการแยกออกจากกันหรือไม่? คำตอบนั้นแตกต่างกันไปตามกรณีการใช้งาน
MichaelChirico

คำตอบ:


125

คำถามที่ดี. data.tableยังไม่สามารถลบแถวโดยการอ้างอิงได้

data.tableสามารถเพิ่มและลบคอลัมน์ได้โดยการอ้างอิงเนื่องจากมีการจัดสรรเวกเตอร์ของตัวชี้คอลัมน์มากเกินไปดังที่คุณทราบ แผนคือการทำอะไรบางอย่างที่คล้ายกันสำหรับแถวและช่วยให้ได้อย่างรวดเร็วและinsert deleteการลบแถวจะใช้memmoveใน C เพื่อเคลื่อนย้ายรายการ (ในแต่ละคอลัมน์) หลังแถวที่ถูกลบ การลบแถวที่อยู่ตรงกลางของตารางจะยังคงไม่มีประสิทธิภาพมากเมื่อเทียบกับฐานข้อมูลที่เก็บแถวเช่น SQL ซึ่งเหมาะสำหรับการแทรกและลบแถวอย่างรวดเร็วไม่ว่าแถวเหล่านั้นจะอยู่ในตารางใด แต่ก็ยังเร็วกว่าการคัดลอกวัตถุขนาดใหญ่ใหม่โดยไม่ต้องลบแถว

ในทางตรงกันข้ามเนื่องจากคอลัมน์เวกเตอร์จะมีการจัดสรรมากเกินไปสามารถแทรกแถว (และลบ) ในตอนท้ายได้ทันที เช่นอนุกรมเวลาที่กำลังเติบโต


มันฟ้องเป็นปัญหา: แถวลบโดยการอ้างอิง


1
@ Matthew Dowle มีข่าวเกี่ยวกับเรื่องนี้ไหม?
statquant

15
@statant ฉันคิดว่าฉันควรแก้ไขข้อบกพร่อง 37 ข้อและจบfreadก่อน หลังจากนั้นมันก็ค่อนข้างสูง
Matt Dowle

15
@ Matewew แน่ใจว่าขอบคุณอีกครั้งสำหรับทุกสิ่งที่คุณทำ
statquant

1
@rbatt ถูกต้อง DT[b<8 & a>3]ส่งคืน data.table ใหม่ เราต้องการที่จะเพิ่มและdelete(DT, b>=8 | a<=3) DT[b>=8 | a<=8, .ROW:=NULL]ข้อได้เปรียบของหลังจะรวมกับคุณสมบัติอื่น ๆ ของ[]เช่นหมายเลขแถวiเข้าร่วมiและrollได้ประโยชน์จาก[i,j,by]การเพิ่มประสิทธิภาพ
Matt Dowle

2
@charliealpha ไม่มีการอัปเดต ผลงานยินดีต้อนรับ ฉันยินดีที่จะให้คำแนะนำ มันต้องการทักษะ C - อีกครั้งฉันยินดีที่จะให้คำแนะนำ
Matt Dowle

29

วิธีการที่ฉันใช้เพื่อทำให้การใช้หน่วยความจำคล้ายกับการลบแบบ in-place คือการเซ็ตย่อยคอลัมน์ในแต่ละครั้งและลบ ไม่เร็วเท่ากับโซลูชัน C memmove ที่เหมาะสม แต่การใช้หน่วยความจำคือทั้งหมดที่ฉันสนใจที่นี่ บางสิ่งเช่นนี้

DT = data.table(col1 = 1:1e6)
cols = paste0('col', 2:100)
for (col in cols){ DT[, (col) := 1:1e6] }
keep.idxs = sample(1e6, 9e5, FALSE) # keep 90% of entries
DT.subset = data.table(col1 = DT[['col1']][keep.idxs]) # this is the subsetted table
for (col in cols){
  DT.subset[, (col) := DT[[col]][keep.idxs]]
  DT[, (col) := NULL] #delete
}

5
+1 วิธีที่มีประสิทธิภาพของหน่วยความจำที่ดี ดังนั้นเราจึงจำเป็นต้องลบชุดของแถวโดยอ้างอิงจริง ๆ แล้วเราไม่ได้คิดแบบนั้น มันจะต้องเป็นชุดของmemmoveการขยับเขยื่อนช่องว่าง แต่ก็ไม่เป็นไร
Matt Dowle

มันจะทำงานเป็นฟังก์ชั่นหรือใช้ในฟังก์ชั่นแล้วส่งคืนเพื่อบังคับให้ทำสำเนาหน่วยความจำหรือไม่
russellpierce

1
มันจะทำงานในฟังก์ชั่นเนื่องจาก data.tables มักจะอ้างอิง
vc273

1
ขอบคุณดี ๆ เพื่อเพิ่มความเร็วเล็กน้อย (โดยเฉพาะกับหลายคอลัมน์) คุณเปลี่ยนDT[, col:= NULL, with = F]ในset(DT, NULL, col, NULL)
มิเคเล่

2
การอัปเดตในแง่ของการเปลี่ยนสำนวนและคำเตือน "with = FALSE พร้อมกับ: = ถูกคัดค้านใน v1.9.4 ที่ออกในเดือนตุลาคม 2014 โปรดห่อ LHS ของ: = ด้วยวงเล็บเช่น DT [, (myVar): = sum (b) , โดย = a] เพื่อกำหนดให้กับชื่อคอลัมน์ที่จัดขึ้นในตัวแปร myVar ดู? ': =' สำหรับตัวอย่างอื่น ๆ ตามที่ได้รับคำเตือนในปี 2014 นี่คือคำเตือน "
แฟรงค์

6

นี่คือฟังก์ชั่นการทำงานตามคำตอบของ @ vc273 และข้อเสนอแนะของ @ Frank

delete <- function(DT, del.idxs) {           # pls note 'del.idxs' vs. 'keep.idxs'
  keep.idxs <- setdiff(DT[, .I], del.idxs);  # select row indexes to keep
  cols = names(DT);
  DT.subset <- data.table(DT[[1]][keep.idxs]); # this is the subsetted table
  setnames(DT.subset, cols[1]);
  for (col in cols[2:length(cols)]) {
    DT.subset[, (col) := DT[[col]][keep.idxs]];
    DT[, (col) := NULL];  # delete
  }
   return(DT.subset);
}

และตัวอย่างของการใช้งาน:

dat <- delete(dat,del.idxs)   ## Pls note 'del.idxs' instead of 'keep.idxs'

โดยที่ "dat" เป็น data.table การลบแถว 14k จาก 1.4M แถวใช้เวลา 0.25 วินาทีบนแล็ปท็อปของฉัน

> dim(dat)
[1] 1419393      25
> system.time(dat <- delete(dat,del.idxs))
   user  system elapsed 
   0.23    0.02    0.25 
> dim(dat)
[1] 1404715      25
> 

PS ตั้งแต่ฉันใหม่ดังนั้นฉันจึงไม่สามารถเพิ่มความคิดเห็นในกระทู้ของ @ vc273 :-(


ฉันแสดงความคิดเห็นภายใต้คำตอบของ vc อธิบายไวยากรณ์ที่เปลี่ยนแปลงสำหรับ (col): = ชนิดของคี่ที่จะมีฟังก์ชั่นชื่อ "ลบ" แต่หาเรื่องเกี่ยวข้องกับสิ่งที่จะเก็บ Btw โดยทั่วไปแล้วเราต้องการใช้ตัวอย่างที่ทำซ้ำได้แทนที่จะแสดงสลัวสำหรับข้อมูลของคุณเอง ตัวอย่างเช่นคุณสามารถนำ DT กลับมาใช้ใหม่ได้
แฟรงค์

ฉันไม่เข้าใจว่าทำไมคุณถึงทำมันโดยอ้างอิง แต่ภายหลังใช้งานที่ได้รับมอบหมาย<-
skan

1
@skan การมอบหมายนั้นกำหนด "dat" ให้ชี้ไปที่ data.table ที่แก้ไขซึ่งถูกสร้างขึ้นเองโดยการเซ็ตย่อย data.table ดั้งเดิม <- assingment ไม่ได้คัดลอกข้อมูลส่งคืนเพียงแค่กำหนดชื่อใหม่ให้ ลิงก์
Jarno P.

@ Frank ฉันได้อัปเดตฟังก์ชั่นสำหรับความแปลกประหลาดที่คุณชี้ให้เห็น
Jarno P.

โอเคขอบคุณ. ฉันแสดงความคิดเห็นเนื่องจากฉันยังคิดว่ามันคุ้มค่าที่จะสังเกตว่าการแสดงคอนโซลออกแทนตัวอย่างที่ทำซ้ำไม่ได้รับการสนับสนุนที่นี่ นอกจากนี้เกณฑ์มาตรฐานเดียวยังไม่มีข้อมูล หากคุณวัดเวลาที่ใช้ในการย่อยก็จะเป็นข้อมูลมากขึ้น (เนื่องจากเราส่วนใหญ่ไม่ทราบว่าจะใช้เวลานานแค่ไหน อย่างไรก็ตามฉันไม่ได้ตั้งใจจะแนะนำว่านี่เป็นคำตอบที่ไม่ดี ฉันเป็นหนึ่งในผู้ต่อต้าน
แฟรงค์

4

หรือพยายามตั้งค่าเป็น NULL ให้ลองตั้งค่าเป็น NA (ตรงกับประเภท NA สำหรับคอลัมน์แรก)

set(DT,1:2, 1:3 ,NA_character_)

3
ใช่ฉันเดาได้ ปัญหาของฉันคือฉันมีข้อมูลจำนวนมากและฉันต้องการกำจัดแถวเหล่านั้นด้วย NA อย่างแน่นอนอาจเป็นไปได้โดยไม่ต้องคัดลอก DT เพื่อกำจัดแถวเหล่านั้น ขอบคุณสำหรับความคิดเห็นของคุณต่อไป!
Florian Oswald

4

หัวข้อยังคงเป็นที่น่าสนใจหลายคน (รวมอยู่ด้วย)

แล้วมันล่ะ ฉันใช้assignเพื่อแทนที่glovalenvและรหัสที่อธิบายไว้ก่อนหน้านี้ มันจะเป็นการดีกว่าถ้าคุณจะจับภาพสภาพแวดล้อมดั้งเดิม แต่อย่างน้อยglobalenvมันก็เป็นหน่วยความจำที่มีประสิทธิภาพและทำหน้าที่เหมือนการเปลี่ยนแปลงโดยอ้างอิง

delete <- function(DT, del.idxs) 
{ 
  varname = deparse(substitute(DT))

  keep.idxs <- setdiff(DT[, .I], del.idxs)
  cols = names(DT);
  DT.subset <- data.table(DT[[1]][keep.idxs])
  setnames(DT.subset, cols[1])

  for (col in cols[2:length(cols)]) 
  {
    DT.subset[, (col) := DT[[col]][keep.idxs]]
    DT[, (col) := NULL];  # delete
  }

  assign(varname, DT.subset, envir = globalenv())
  return(invisible())
}

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
delete(DT, 3)

เพื่อให้ชัดเจนสิ่งนี้ไม่ได้ลบโดยการอ้างอิง (อิงaddress(DT); delete(DT, 3); address(DT)) แม้ว่ามันอาจจะมีประสิทธิภาพในบางกรณี
แฟรงค์

1
ไม่มันทำไม่ได้ มันเลียนแบบพฤติกรรมและหน่วยความจำที่มีประสิทธิภาพ นั่นเป็นเหตุผลที่ผมบอกว่า: มันทำหน้าที่เหมือน แต่การพูดอย่างเคร่งครัดคุณเปลี่ยนที่อยู่อย่างถูกต้อง
JRR

3

นี่คือกลยุทธ์บางอย่างที่ฉันใช้ ฉันเชื่อว่าอาจมีฟังก์ชั่น. ROW วิธีการด้านล่างเหล่านี้ไม่รวดเร็ว นี่เป็นกลยุทธ์บางอย่างที่นอกเหนือจากเซ็ตย่อยหรือการกรองเล็กน้อย ฉันพยายามคิดเหมือน dba เพียงพยายามล้างข้อมูล ตามที่ระบุไว้ข้างต้นคุณสามารถเลือกหรือลบแถวใน data.table:

data(iris)
iris <- data.table(iris)

iris[3] # Select row three

iris[-3] # Remove row three

You can also use .SD to select or remove rows:

iris[,.SD[3]] # Select row three

iris[,.SD[3:6],by=,.(Species)] # Select row 3 - 6 for each Species

iris[,.SD[-3]] # Remove row three

iris[,.SD[-3:-6],by=,.(Species)] # Remove row 3 - 6 for each Species

หมายเหตุ: .SD สร้างชุดย่อยของข้อมูลต้นฉบับและช่วยให้คุณทำงานได้ค่อนข้างมากใน j หรือ data.table ที่ตามมา ดูhttps://stackoverflow.com/a/47406952/305675 ที่นี่ฉันสั่งไอริสของฉันด้วยความยาวของ Sepal ใช้ Sepal ที่ระบุความยาวเป็นขั้นต่ำเลือกสามอันดับแรก (ตามความยาวของ Sepal) ของสปีชี่ทั้งหมดและส่งคืนข้อมูลที่มาพร้อมกันทั้งหมด:

iris[order(-Sepal.Length)][Sepal.Length > 3,.SD[1:3],by=,.(Species)]

วิธีการข้างต้นทั้งหมดจัดลำดับ data.table ตามลำดับเมื่อลบแถว คุณสามารถโอนย้าย data.table และลบหรือแทนที่แถวเก่าซึ่งตอนนี้เปลี่ยนเป็นคอลัมน์ เมื่อใช้ ': = NULL' เพื่อลบแถวที่ถูกย้ายชื่อคอลัมน์ที่ตามมาจะถูกลบเช่นกัน:

m_iris <- data.table(t(iris))[,V3:=NULL] # V3 column removed

d_iris <- data.table(t(iris))[,V3:=V2] # V3 column replaced with V2

เมื่อคุณโอนย้าย data.frame กลับไปยัง data.table คุณอาจต้องการเปลี่ยนชื่อจาก data.table ดั้งเดิมและเรียกคืนแอตทริบิวต์คลาสในกรณีที่ถูกลบ การใช้ ": = NULL" กับ data.table ที่ถูกเปลี่ยนตอนนี้จะสร้างคลาสอักขระทั้งหมด

m_iris <- data.table(t(d_iris));
setnames(d_iris,names(iris))

d_iris <- data.table(t(m_iris));
setnames(m_iris,names(iris))

คุณอาจต้องการลบแถวที่ซ้ำกันซึ่งคุณสามารถทำได้โดยใช้หรือไม่ใช้คีย์:

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]     

d_iris[!duplicated(Key),]

d_iris[!duplicated(paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)),]  

นอกจากนี้ยังเป็นไปได้ที่จะเพิ่มตัวนับที่เพิ่มขึ้นด้วย '.I' จากนั้นคุณสามารถค้นหาคีย์หรือเขตข้อมูลที่ซ้ำกันและลบออกโดยลบระเบียนด้วยตัวนับ การคำนวณนี้มีราคาแพง แต่มีข้อดีเนื่องจากคุณสามารถพิมพ์บรรทัดที่จะลบออกได้

d_iris[,I:=.I,] # add a counter field

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]

for(i in d_iris[duplicated(Key),I]) {print(i)} # See lines with duplicated Key or Field

for(i in d_iris[duplicated(Key),I]) {d_iris <- d_iris[!I == i,]} # Remove lines with duplicated Key or any particular field.

คุณยังสามารถเติมแถวด้วย 0s หรือ NAs แล้วใช้คิวรี i เพื่อลบพวกเขา:

 X 
   x v foo
1: c 8   4
2: b 7   2

X[1] <- c(0)

X
   x v foo
1: 0 0   0
2: b 7   2

X[2] <- c(NA)
X
    x  v foo
1:  0  0   0
2: NA NA  NA

X <- X[x != 0,]
X <- X[!is.na(x),]

สิ่งนี้ไม่ได้ตอบคำถาม (เกี่ยวกับการลบโดยการอ้างอิง) และการใช้tdata.frame มักไม่ใช่ความคิดที่ดี ตรวจสอบstr(m_iris)เพื่อดูว่าข้อมูลทั้งหมดเป็นสตริง / ตัวละคร แต่คุณยังสามารถรับหมายเลขแถวโดยใช้d_iris[duplicated(Key), which = TRUE]โดยไม่ต้องสร้างคอลัมน์ตัวนับ
แฟรงก์

1
ใช่คุณถูก. ฉันไม่ตอบคำถามโดยเฉพาะ แต่การลบแถวโดยการอ้างอิงยังไม่มีฟังก์ชั่นหรือเอกสารอย่างเป็นทางการและหลายคนกำลังจะมาที่โพสต์นี้กำลังมองหาฟังก์ชั่นทั่วไปที่จะทำอย่างนั้น เราสามารถสร้างโพสต์เพื่อตอบคำถามเกี่ยวกับวิธีลบแถว Stack overflow มีประโยชน์มากและฉันเข้าใจถึงความจำเป็นในการตอบคำถามอย่างแม่นยำ บางครั้งฉันคิดว่าดังนั้นอาจเป็นเพียงลัทธิฟาสซิสต์เล็กน้อยในเรื่องนี้ ... แต่อาจมีเหตุผลที่ดีสำหรับสิ่งนั้น
rferrisx

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