Làm thế nào để xóa một hàng bằng cách tham chiếu trong data.table?


150

Câu hỏi của tôi liên quan đến bài tập bằng cách tham chiếu so với sao chép trong data.table. Tôi muốn biết nếu một người có thể xóa các hàng bằng cách tham chiếu, tương tự như

DT[ , someCol := NULL]

Tôi muốn biết về

DT[someRow := NULL, ]

Tôi đoán có một lý do chính đáng cho lý do tại sao chức năng này không tồn tại, vì vậy có lẽ bạn có thể chỉ ra một giải pháp thay thế tốt cho phương pháp sao chép thông thường, như dưới đây. Cụ thể, đi với ví dụ yêu thích của tôi (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

Nói rằng tôi muốn xóa hàng đầu tiên khỏi dữ liệu này. Tôi biết tôi có thể làm điều này:

DT <- DT[-1, ]

nhưng thường chúng ta có thể muốn tránh điều đó, bởi vì chúng ta đang sao chép đối tượng (và điều đó đòi hỏi khoảng 3 * N bộ nhớ, nếu N object.size(DT), như được chỉ ra ở đây . Bây giờ tôi đã tìm thấy set(DT, i, j, value). Tôi biết cách đặt các giá trị cụ thể (như ở đây: đặt tất cả các giá trị trong hàng 1 và 2 và cột 2 và 3 về 0)

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

Nhưng làm thế nào tôi có thể xóa hai hàng đầu tiên, nói? Đang làm

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

đặt toàn bộ DT thành NULL.

Kiến thức SQL của tôi rất hạn chế, vì vậy các bạn nói với tôi: data data.table sử dụng công nghệ SQL, có tương đương với lệnh SQL không

DELETE FROM table_name
WHERE some_column=some_value

trong data.table?


17
Tôi không nghĩ rằng nó data.table()sử dụng công nghệ SQL nhiều đến mức người ta có thể vẽ song song giữa các hoạt động khác nhau trong SQL và các đối số khác nhau cho a data.table. Đối với tôi, tham chiếu đến "công nghệ" phần nào ngụ ý rằng việc data.tablengồi trên cơ sở dữ liệu SQL ở đâu đó, mà AFAIK không phải là trường hợp.
Đuổi theo

1
cảm ơn đuổi theo. vâng, tôi đoán rằng tương tự sql là một phỏng đoán hoang dã.
Florian Oswald

1
Thường thì nó là đủ để xác định một cờ để giữ các hàng, như DT[ , keep := .I > 1], sau đó tập hợp con cho các hoạt động sau này:, DT[(keep), ...]thậm chí có thể setindex(DT, keep)là tốc độ của tập hợp con này. Không phải là thuốc chữa bách bệnh, nhưng đáng để xem là một lựa chọn thiết kế trong quy trình làm việc của bạn - bạn có thực sự muốn xóa tất cả các hàng đó khỏi bộ nhớ , hoặc bạn muốn loại trừ chúng? Câu trả lời khác nhau tùy theo trường hợp sử dụng.
MichaelChirico

Câu trả lời:


125

Câu hỏi hay. data.tablekhông thể xóa hàng bằng cách tham khảo nào.

data.tablecó thể thêm và xóa các cột bằng cách tham chiếu vì nó phân bổ quá mức vectơ của các con trỏ cột, như bạn biết. Kế hoạch là làm một cái gì đó tương tự cho các hàng và cho phép nhanh insertdelete. Một hàng xóa sẽ sử dụng memmovetrong C để tạo ra các mục (trong mỗi và mỗi cột) sau khi các hàng bị xóa. Xóa một hàng ở giữa bảng sẽ vẫn không hiệu quả so với cơ sở dữ liệu lưu trữ hàng như SQL, phù hợp hơn để chèn và xóa nhanh các hàng bất cứ nơi nào có các hàng trong bảng. Tuy nhiên, nó sẽ nhanh hơn rất nhiều so với việc sao chép một đối tượng lớn mới mà không có các hàng bị xóa.

Mặt khác, vì các vectơ cột sẽ được phân bổ quá mức, các hàng có thể được chèn (và xóa) ở cuối , ngay lập tức; ví dụ, một chuỗi thời gian đang phát triển


Nó được coi là một vấn đề: Xóa các hàng theo tham chiếu .


1
@Matthew Dowle Có một số tin tức về điều này?
statquant

15
@statquant Tôi nghĩ mình nên sửa 37 lỗi, và hoàn thành freadtrước. Sau đó, nó khá cao.
Matt Dowle

15
@MatthewDowle chắc chắn, cảm ơn một lần nữa cho tất cả mọi thứ bạn đang làm.
statquant

1
@rbatt Đúng. DT[b<8 & a>3]trả về một dữ liệu mới. Chúng tôi muốn thêm delete(DT, b>=8 | a<=3)DT[b>=8 | a<=8, .ROW:=NULL]. Ưu điểm của cái sau sẽ là kết hợp với các tính năng []khác như số hàng i, tham gia irollhưởng lợi từ [i,j,by]việc tối ưu hóa.
Matt Dowle

2
@charliealpha Không cập nhật. Đóng góp chào mừng. Tôi sẵn sàng hướng dẫn. Nó cần kỹ năng C - một lần nữa, tôi sẵn sàng hướng dẫn.
Matt Dowle

29

Cách tiếp cận mà tôi đã thực hiện để sử dụng bộ nhớ tương tự như xóa tại chỗ là đặt một cột tại một thời điểm và xóa. không nhanh như một giải pháp memmove C thích hợp, nhưng sử dụng bộ nhớ là tất cả những gì tôi quan tâm ở đây. đại loại như thế này:

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 Cách tiếp cận hiệu quả bộ nhớ tốt. Vì vậy, lý tưởng là chúng ta cần xóa một tập hợp các hàng bằng cách tham khảo thực sự không phải chúng ta, tôi đã không nghĩ về điều đó. Nó sẽ phải là một loạt các memmoves để nhúc nhích những khoảng trống, nhưng không sao.
Matt Dowle

Điều này sẽ hoạt động như một chức năng, hoặc việc sử dụng trong một chức năng và trả lại buộc nó tạo ra các bản sao bộ nhớ?
russellpierce

1
nó sẽ hoạt động trong một hàm, vì data.tables luôn là các tham chiếu.
vc273

1
cảm ơn, một cái tốt đẹp Để tăng tốc một chút (đặc biệt là với nhiều cột), bạn thay đổi DT[, col:= NULL, with = F]trongset(DT, NULL, col, NULL)
Michele

2
Cập nhật theo cách thay đổi thành ngữ và cảnh báo "with = FALSE cùng với: = đã bị phản đối trong v1.9.4 được phát hành vào tháng 10 năm 2014. Vui lòng bọc LHS của: = bằng dấu ngoặc đơn, ví dụ: DT [, (myVar): = sum (b) , by = a] để gán cho (các) tên cột được giữ trong biến myVar. Xem? ': =' cho các ví dụ khác. Như đã cảnh báo vào năm 2014, đây hiện là một cảnh báo. "
Frank

6

Đây là chức năng hoạt động dựa trên câu trả lời của @ vc273 và phản hồi của @ 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);
}

Và ví dụ về cách sử dụng của nó:

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

Trong đó "dat" là data.table. Xóa 14k hàng khỏi 1,4M hàng mất 0,25 giây trên máy tính xách tay của tôi.

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

Tái bút Vì tôi chưa quen với SO, tôi không thể thêm nhận xét vào chủ đề của @ vc273 :-(


Tôi đã nhận xét dưới câu trả lời của vc giải thích cú pháp thay đổi cho (col): =. Loại kỳ lạ để có một chức năng tên là "xóa" nhưng một đối số liên quan đến những gì cần giữ. Btw, nói chung, nên sử dụng một ví dụ có thể lặp lại thay vì hiển thị mờ cho dữ liệu của riêng bạn. Bạn có thể sử dụng lại DT từ câu hỏi, ví dụ.
Frank

Tôi không hiểu tại sao bạn làm điều đó bằng cách tham khảo nhưng sau đó sử dụng một dữ liệu gán <-
skan

1
@skan, Nhiệm vụ đó gán "dat" để trỏ đến dữ liệu đã sửa đổi. Bản thân nó đã được tạo bằng cách đặt lại dữ liệu gốc.table. <- assingment không thực hiện sao chép dữ liệu trả về, chỉ gán tên mới cho nó. liên kết
Jarno P.

@Frank, tôi đã cập nhật chức năng cho sự kỳ lạ mà bạn đã chỉ ra.
Jarno P.

Được rồi cảm ơn. Tôi đang để lại nhận xét vì tôi vẫn nghĩ rằng đáng chú ý rằng việc hiển thị đầu ra của bàn điều khiển thay vì một ví dụ có thể lặp lại không được khuyến khích ở đây. Ngoài ra, một điểm chuẩn duy nhất không có nhiều thông tin. Nếu bạn cũng đã đo thời gian cho việc đặt lại, thì nó sẽ có nhiều thông tin hơn (vì hầu hết chúng ta không biết bằng trực giác mất bao lâu, mất bao lâu cho comp của bạn). Dù sao, tôi không có ý đề nghị đây là một câu trả lời tồi; Tôi là một trong những người ủng hộ nó.
Frank

4

Thay vào đó hoặc cố gắng đặt thành NULL, hãy thử đặt thành NA (khớp với loại NA cho cột đầu tiên)

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

3
Vâng, đó là công việc tôi đoán. Vấn đề của tôi là tôi có rất nhiều dữ liệu và tôi muốn loại bỏ chính xác các hàng đó bằng NA, có thể mà không phải sao chép DT để thoát khỏi các hàng đó. cảm ơn bình luận của bạn nào!
Florian Oswald

4

Chủ đề vẫn còn nhiều người thú vị (bao gồm cả tôi).

Thế còn cái đó? Tôi đã sử dụng assignđể thay thế glovalenvvà mã được mô tả trước đây. Sẽ tốt hơn nếu chụp môi trường ban đầu nhưng ít nhất trong globalenvđó là bộ nhớ hiệu quả và hoạt động như một sự thay đổi của ref.

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)

Để rõ ràng, điều này không xóa bằng tham chiếu (dựa trên address(DT); delete(DT, 3); address(DT)), mặc dù nó có thể có hiệu quả trong một số ý nghĩa.
Frank

1
Không nó không. Nó mô phỏng hành vi và là bộ nhớ hiệu quả. Đó là lý do tại sao tôi nói: nó hoạt động như thế . Nhưng nói đúng ra bạn đúng địa chỉ đã thay đổi.
JRR

3

Dưới đây là một số chiến lược tôi đã sử dụng. Tôi tin rằng một chức năng .law có thể sẽ đến. Không có cách tiếp cận nào dưới đây là nhanh. Đây là một số chiến lược vượt ra ngoài tập hợp con hoặc lọc. Tôi đã cố gắng nghĩ như dba chỉ cố gắng để làm sạch dữ liệu. Như đã lưu ý ở trên, bạn có thể chọn hoặc xóa các hàng trong 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

Lưu ý: .SD tạo một tập hợp con của dữ liệu gốc và cho phép bạn thực hiện khá nhiều công việc trong j hoặc data.table tiếp theo. Xem https://stackoverflow.com/a/47406952/305675 . Ở đây tôi đã ra lệnh cho tròng mắt của mình theo Độ dài Sepal, lấy tối thiểu Sepal.Lipse được chỉ định là tối thiểu, chọn ba phần trên cùng (theo Độ dài Sepal) của tất cả các Loài và trả về tất cả dữ liệu đi kèm:

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

Các cách tiếp cận trên tất cả sắp xếp lại một data.table tuần tự khi loại bỏ các hàng. Bạn có thể hoán chuyển một data.table và loại bỏ hoặc thay thế các hàng cũ hiện là các cột được hoán chuyển. Khi sử dụng ': = NULL' để xóa hàng được chuyển, tên cột tiếp theo cũng bị xóa:

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

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

Khi bạn chuyển đổi data.frame trở lại data.table, bạn có thể muốn đổi tên từ data.table gốc và khôi phục các thuộc tính lớp trong trường hợp xóa. Áp dụng ": = NULL" cho dữ liệu đã được chuyển đổi. Có thể tạo tất cả các lớp ký tự.

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

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

Bạn có thể chỉ muốn xóa các hàng trùng lặp mà bạn có thể thực hiện có hoặc không có Khóa:

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

Cũng có thể thêm bộ đếm tăng dần với '.I'. Sau đó, bạn có thể tìm kiếm các khóa hoặc trường trùng lặp và xóa chúng bằng cách xóa bản ghi với bộ đếm. Đây là tính toán đắt tiền, nhưng có một số lợi thế vì bạn có thể in các dòng cần loại bỏ.

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.

Bạn cũng có thể chỉ cần điền một hàng bằng 0 hoặc NA và sau đó sử dụng truy vấn i để xóa chúng:

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

Điều này không thực sự trả lời câu hỏi (về việc loại bỏ bằng cách tham chiếu) và sử dụng ttrên data.frame thường không phải là một ý tưởng hay; kiểm tra str(m_iris)xem tất cả dữ liệu đã trở thành chuỗi / ký tự. Btw, bạn cũng có thể lấy số hàng bằng cách sử dụng d_iris[duplicated(Key), which = TRUE]mà không cần tạo cột truy cập.
Frank

1
Vâng, bạn đúng. Tôi không trả lời câu hỏi cụ thể. Nhưng việc xóa một hàng bằng tham chiếu chưa có chức năng hoặc tài liệu chính thức và nhiều người sẽ đến bài đăng này để tìm chức năng chung để thực hiện chính xác điều đó. Chúng tôi có thể tạo một bài đăng để chỉ trả lời câu hỏi về cách xóa một hàng. Tràn ngăn xếp rất hữu ích và tôi thực sự hiểu sự cần thiết để giữ câu trả lời chính xác cho câu hỏi. Đôi khi, tôi nghĩ rằng SO có thể chỉ là một kẻ phát xít nhỏ trong vấn đề này ... nhưng có lẽ có một lý do chính đáng cho điều đó.
rferrisx

Ok, cảm ơn đã giải thích. Tôi nghĩ bây giờ cuộc thảo luận của chúng tôi ở đây là đủ một biển chỉ dẫn cho bất cứ ai bị nhầm lẫn trong trường hợp này.
Frank
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.