Bagaimana cara menghapus baris dengan referensi di data.table?


150

Pertanyaan saya terkait dengan penugasan dengan referensi versus menyalin data.table. Saya ingin tahu apakah seseorang dapat menghapus baris dengan referensi, mirip dengan

DT[ , someCol := NULL]

Saya ingin tahu tentang

DT[someRow := NULL, ]

Saya kira ada alasan bagus mengapa fungsi ini tidak ada, jadi mungkin Anda bisa menunjukkan alternatif yang baik untuk pendekatan penyalinan biasa, seperti di bawah ini. Secara khusus, pergi dengan favorit saya dari contoh (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

Katakanlah saya ingin menghapus baris pertama dari data ini. Saya tahu saya bisa melakukan ini:

DT <- DT[-1, ]

tetapi seringkali kita mungkin ingin menghindarinya, karena kita menyalin objek (dan itu membutuhkan sekitar 3 * N memori, jika N object.size(DT), seperti yang ditunjukkan di sini . Sekarang saya temukan set(DT, i, j, value). Saya tahu cara mengatur nilai tertentu (seperti di sini: atur semua nilai dalam baris 1 dan 2 dan kolom 2 dan 3 ke nol)

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

Tapi bagaimana saya bisa menghapus dua baris pertama, katakan? Perbuatan

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

mengatur seluruh DT ke NULL.

Pengetahuan SQL saya sangat terbatas, jadi kalian beri tahu saya: diberikan data. Tabel menggunakan teknologi SQL, apakah ada yang setara dengan perintah SQL

DELETE FROM table_name
WHERE some_column=some_value

dalam data.table?


17
Saya tidak berpikir itu yang data.table()menggunakan teknologi SQL sebanyak yang dapat menarik paralel antara operasi yang berbeda dalam SQL dan berbagai argumen ke data.table. Bagi saya, referensi ke "teknologi" agak menyiratkan yang data.tableduduk di atas database SQL di suatu tempat, yang AFAIK tidak demikian.
Mengejar

1
terima kasih kejar. ya, saya kira analogi sql itu tebakan liar.
Florian Oswald

1
Seringkali itu harus cukup untuk menentukan bendera untuk menjaga baris, seperti DT[ , keep := .I > 1], kemudian subset untuk operasi selanjutnya :, DT[(keep), ...]bahkan mungkin setindex(DT, keep)kecepatan subset ini. Bukan obat mujarab, tetapi layak untuk dipertimbangkan sebagai pilihan desain dalam alur kerja Anda - apakah Anda benar-benar ingin menghapus semua baris itu dari memori , atau apakah Anda lebih suka mengecualikannya? Jawabannya berbeda dengan use case.
MichaelChirico

Jawaban:


125

Pertanyaan bagus. data.tablebelum dapat menghapus baris dengan referensi.

data.tabledapat menambah dan menghapus kolom dengan referensi karena terlalu mengalokasikan vektor pointer kolom, seperti yang Anda tahu. Rencananya adalah melakukan sesuatu yang serupa untuk baris dan memungkinkan cepat insertdan delete. Hapus baris akan digunakan memmovedalam C untuk menggerakkan item (di setiap kolom) setelah baris yang dihapus. Menghapus baris di tengah-tengah tabel masih akan sangat tidak efisien dibandingkan dengan database toko baris seperti SQL, yang lebih cocok untuk memasukkan dan menghapus baris dengan cepat di mana pun baris tersebut berada di dalam tabel. Tapi tetap saja, itu akan jauh lebih cepat daripada menyalin objek besar baru tanpa baris yang dihapus.

Di sisi lain, karena vektor kolom akan dialokasikan secara berlebihan, baris dapat disisipkan (dan dihapus) di bagian akhir , secara instan; misalnya, deret waktu yang berkembang.


Itu diajukan sebagai masalah: Hapus baris dengan referensi .


1
@Matthew Dowle Apakah ada berita tentang ini?
statquant

15
@statquant Saya pikir saya harus memperbaiki 37 bug, dan selesai freaddulu. Setelah itu cukup tinggi.
Matt Dowle

15
@ MatthewDowle yakin, terima kasih lagi untuk semua yang Anda lakukan.
statquant

1
@ rbatt Benar. DT[b<8 & a>3]mengembalikan data.table baru. Kami ingin menambahkan delete(DT, b>=8 | a<=3)dan DT[b>=8 | a<=8, .ROW:=NULL]. Keuntungan dari yang terakhir adalah menggabungkan dengan fitur lain []seperti nomor baris i, bergabung idan rollmendapat manfaat dari [i,j,by]optimasi.
Matt Dowle

2
@charliealpha Tidak ada pembaruan. Kontribusi diterima. Saya bersedia membimbing. Perlu keterampilan C - sekali lagi, saya bersedia membimbing.
Matt Dowle

29

pendekatan yang telah saya lakukan untuk membuat penggunaan memori mirip dengan penghapusan di tempat adalah dengan mengelompokkan kolom pada suatu waktu dan menghapus. tidak secepat solusi memmove C yang tepat, tetapi hanya menggunakan memori yang saya pedulikan di sini. sesuatu seperti ini:

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 Pendekatan efisiensi memori yang bagus. Jadi idealnya kita perlu menghapus satu set baris dengan referensi sebenarnya bukan, saya belum memikirkan itu. Ini harus menjadi serangkaian memmoves untuk menggerakkan kesenjangan, tapi tidak apa-apa.
Matt Dowle

Apakah ini berfungsi sebagai fungsi, atau apakah penggunaan dalam fungsi dan mengembalikannya untuk membuat salinan memori?
russellpierce

1
itu akan berfungsi dalam suatu fungsi, karena data.tabel selalu referensi.
vc273

1
terima kasih bagus Untuk mempercepat sedikit (terutama dengan banyak kolom) Anda mengubah DT[, col:= NULL, with = F]diset(DT, NULL, col, NULL)
Michele

2
Memperbarui sehubungan dengan perubahan idiom dan peringatan "dengan = FALSE bersama dengan: = sudah tidak digunakan lagi dalam v1.9.4 yang dirilis Oktober 2014. Harap bungkus LHS dari: = dengan tanda kurung; misalnya, DT [, (myVar): = jumlah (b) , dengan = a] untuk menetapkan nama kolom yang disimpan dalam variabel myVar. Lihat? ': =' untuk contoh lainnya. Seperti yang diperingatkan pada tahun 2014, ini sekarang merupakan peringatan. "
Frank

6

Berikut adalah fungsi yang bekerja berdasarkan jawaban @ vc273 dan umpan balik @ 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);
}

Dan contoh penggunaannya:

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

Di mana "dat" adalah data.table. Menghapus 14k baris dari 1.4M baris membutuhkan 0,25 detik pada laptop saya.

> 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. Karena saya baru mengenal SO, saya tidak bisa menambahkan komentar ke utas @ vc273 :-(


Saya berkomentar di bawah jawaban vc yang menjelaskan sintaks yang diubah untuk (col): =. Agak aneh memiliki fungsi bernama "delete" tetapi argumen yang terkait dengan apa yang harus disimpan. Btw, umumnya lebih disukai menggunakan contoh yang dapat direproduksi daripada menunjukkan redup untuk data Anda sendiri. Anda dapat menggunakan kembali DT dari pertanyaan, misalnya.
Frank

Saya tidak mengerti mengapa Anda melakukannya dengan referensi tetapi kemudian menggunakan tugas data <-
skan

1
@skan, Penugasan itu memberikan "dat" untuk menunjuk ke data yang dimodifikasi. tabel itu sendiri telah dibuat dengan menseting data asli. tabel. <- Assingment tidak melakukan copy data pengembalian, hanya memberikan nama baru untuk itu. tautan
Jarno P.

@ Sejujurnya, saya telah memperbarui fungsi untuk keanehan yang Anda tunjukkan.
Jarno P.

Ok terima kasih. Saya meninggalkan komentar karena saya masih berpikir bahwa mencatat keluaran konsol bukan contoh yang dapat direproduksi tidak dianjurkan di sini. Juga, satu patokan tidak terlalu informatif. Jika Anda juga mengukur waktu yang diperlukan untuk berlangganan, itu akan lebih informatif (karena sebagian besar dari kita tidak secara intuitif tahu berapa lama, apalagi berapa lama waktu yang diperlukan untuk comp Anda). Bagaimanapun, saya tidak bermaksud menyarankan ini adalah jawaban yang buruk; Saya salah satu dari para upvoternya.
Frank

4

Alih-alih atau mencoba mengatur ke NULL, coba atur ke NA (cocokkan tipe-NA untuk kolom pertama)

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

3
ya, kurasa itu berhasil. Masalah saya adalah bahwa saya memiliki banyak data dan saya ingin menyingkirkan persis baris-baris dengan NA, mungkin tanpa harus menyalin DT untuk menyingkirkan baris-baris itu. Terima kasih atas komentar Anda!
Florian Oswald

4

Topiknya masih menarik banyak orang (termasuk saya).

Bagaimana dengan itu? Saya dulu assignmengganti glovalenvdan kode yang dijelaskan sebelumnya. Akan lebih baik untuk menangkap lingkungan asli tetapi setidaknya di globalenvdalamnya adalah memori yang efisien dan bertindak seperti perubahan oleh 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)

Untuk lebih jelasnya, ini tidak menghapus dengan referensi (berdasarkan address(DT); delete(DT, 3); address(DT)), meskipun mungkin efisien dalam beberapa hal.
Frank

1
Tidak. Ini mengemulasi perilaku dan memori efisien. Itu sebabnya saya katakan: itu bertindak seperti . Tapi sebenarnya Anda benar alamatnya berubah.
JRR

3

Berikut adalah beberapa strategi yang saya gunakan. Saya percaya fungsi .ROW mungkin akan datang. Tidak satu pun dari pendekatan di bawah ini yang cepat. Ini adalah beberapa strategi yang sedikit melampaui himpunan bagian atau penyaringan. Saya mencoba berpikir seperti dba hanya mencoba untuk membersihkan data. Seperti disebutkan di atas, Anda dapat memilih atau menghapus baris dalam data.tabel:

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

Catatan: .SD membuat subset dari data asli dan memungkinkan Anda untuk melakukan sedikit pekerjaan di j atau data berikutnya. Lihat https://stackoverflow.com/a/47406952/305675 . Di sini saya memesan iris saya dengan Sepal Length, ambil Sepal yang ditentukan. Panjang minimum, pilih tiga teratas (dengan Sepal Length) dari semua Spesies dan kembalikan semua data yang menyertainya:

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

Pendekatan di atas semua menyusun ulang data.tabel berurutan saat menghapus baris. Anda dapat mengubah posisi suatu data. Tabel dan menghapus atau mengganti baris lama yang sekarang kolom ditransposisikan. Saat menggunakan ': = NULL' untuk menghapus baris yang dialihkan, nama kolom selanjutnya juga dihapus:

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

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

Ketika Anda mengubah data.frame kembali ke data.table, Anda mungkin ingin mengubah nama dari data asli.tabel dan mengembalikan atribut kelas dalam kasus penghapusan. Menerapkan ": = NULL" ke data.table yang sekarang diubah membuat semua kelas karakter.

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

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

Anda mungkin hanya ingin menghapus duplikat baris yang dapat Anda lakukan dengan atau tanpa Kunci:

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

Dimungkinkan juga untuk menambahkan penghitung tambahan dengan '.I'. Anda kemudian dapat mencari kunci atau bidang yang digandakan dan menghapusnya dengan menghapus catatan dengan penghitung. Ini mahal secara komputasi, tetapi memiliki beberapa keuntungan karena Anda dapat mencetak garis yang akan dihapus.

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.

Anda juga bisa mengisi baris dengan 0s atau NAS dan kemudian menggunakan kueri untuk menghapusnya:

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

Ini tidak benar-benar menjawab pertanyaan (tentang penghapusan dengan referensi) dan menggunakan tpada data.frame biasanya bukan ide yang baik; periksa str(m_iris)untuk melihat bahwa semua data telah menjadi string / karakter. Btw, Anda juga bisa mendapatkan nomor baris dengan menggunakan d_iris[duplicated(Key), which = TRUE]tanpa membuat kolom penghitung.
Frank

1
Ya kamu benar. Saya tidak menjawab pertanyaan secara khusus. Tetapi menghapus satu baris dengan referensi belum memiliki fungsi atau dokumentasi resmi dan banyak orang akan datang ke posting ini mencari fungsionalitas umum untuk melakukan hal itu. Kami dapat membuat posting untuk hanya menjawab pertanyaan tentang cara menghapus baris. Stack overflow sangat berguna dan saya benar-benar memahami perlunya menjaga jawaban tepat untuk pertanyaan itu. Namun terkadang, saya pikir SO bisa menjadi sedikit fasis dalam hal ini ... tapi mungkin ada alasan bagus untuk itu.
rferrisx

Ok, terima kasih sudah menjelaskan. Saya pikir untuk saat ini diskusi kita di sini sudah cukup menjadi rambu bagi siapa saja yang bingung dalam hal ini.
Frank
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.