Apa tujuan menyetel kunci dalam data.table?

113

Saya menggunakan data.table dan ada banyak fungsi yang mengharuskan saya untuk menyetel kunci (misalnya X[Y]). Karena itu, saya ingin memahami apa yang dilakukan kunci untuk mengatur kunci dengan benar dalam tabel data saya.


Satu sumber yang saya baca adalah ?setkey.

setkey()mengurutkan data.tabledan menandainya sebagai diurutkan. Kolom yang diurutkan adalah kuncinya. Kuncinya dapat berupa kolom apa pun dalam urutan apa pun. Kolom selalu diurutkan dalam urutan menaik. Tabel diubah dengan referensi. Tidak ada salinan yang dibuat sama sekali, selain memori kerja sementara sebesar satu kolom.

Kesimpulan saya di sini adalah bahwa kunci akan "menyortir" data.table, menghasilkan efek yang sangat mirip dengan order(). Namun, itu tidak menjelaskan tujuan memiliki kunci.


FAQ data.table 3.2 dan 3.3 menjelaskan:

3.2 Saya tidak memiliki kunci di meja besar, tetapi pengelompokan masih sangat cepat. Mengapa demikian?

data.table menggunakan penyortiran radix. Ini secara signifikan lebih cepat daripada algoritma pengurutan lainnya. Radix secara khusus hanya untuk bilangan bulat, lihat ?base::sort.list(x,method="radix"). Ini juga salah satu alasan mengapa setkey()cepat. Jika tidak ada kunci yang disetel, atau kami mengelompokkan dalam urutan yang berbeda dari kunci tersebut, kami menyebutnya secara ad hoc oleh.

3.3 Mengapa pengelompokan berdasarkan kolom dalam kunci lebih cepat daripada secara ad hoc?

Karena setiap grup berdekatan dalam RAM, sehingga meminimalkan pengambilan halaman, dan memori dapat disalin secara massal ( memcpydalam C) daripada mengulang dalam C.

Dari sini, saya rasa bahwa pengaturan kunci memungkinkan R untuk menggunakan "penyortiran radix" di atas algoritma lain, dan itulah mengapa ini lebih cepat.


Panduan mulai cepat 10 menit juga memiliki panduan tentang kunci.

  1. Kunci

Mari kita mulai dengan mempertimbangkan data.frame, khususnya nama rown (atau dalam bahasa Inggris, nama baris). Artinya, banyak nama yang menjadi satu baris. Beberapa nama yang termasuk dalam satu baris? Bukan itu yang biasa kita lakukan dalam data.frame. Kita tahu bahwa setiap baris paling banyak memiliki satu nama. Seseorang memiliki setidaknya dua nama, nama pertama dan nama kedua. Itu berguna untuk mengatur direktori telepon, misalnya, yang diurutkan berdasarkan nama belakang, lalu nama pertama. Namun, setiap baris dalam data.frame hanya dapat memiliki satu nama.

Kunci terdiri dari satu atau lebih kolom nama baris, yang mungkin berupa bilangan bulat, faktor, karakter atau kelas lain, bukan hanya karakter. Selanjutnya, baris tersebut diurutkan berdasarkan kunci. Oleh karena itu, data.table dapat memiliki paling banyak satu kunci, karena tidak dapat diurutkan dengan lebih dari satu cara.

Keunikan tidak diberlakukan, yaitu nilai kunci duplikat diperbolehkan. Karena baris diurutkan berdasarkan kunci, setiap duplikat dalam kunci akan muncul secara berurutan

Direktori telepon sangat membantu dalam memahami apa itu kunci, tetapi tampaknya kunci tidak berbeda jika dibandingkan dengan memiliki kolom faktor. Lebih lanjut, tidak dijelaskan mengapa sebuah key dibutuhkan (terutama untuk menggunakan fungsi tertentu) dan bagaimana memilih kolom yang akan dijadikan key. Juga, tampaknya dalam data.table dengan waktu sebagai kolom, mengatur kolom lain sebagai kunci mungkin akan mengacaukan kolom waktu juga, yang membuatnya semakin membingungkan karena saya tidak tahu apakah saya diizinkan mengatur kolom lain sebagai kunci. Bisakah seseorang mencerahkan saya?

Kaki Basah
sumber
"Saya rasa bahwa pengaturan kunci memungkinkan R untuk menggunakan" penyortiran radix "di atas algoritme lain" --Saya tidak mendapatkannya dari bantuan sama sekali. Bacaan saya adalah bahwa pengaturan kunci diurutkan berdasarkan kunci. Anda dapat melakukan penyortiran "ad hoc" berdasarkan kolom lain selain kunci, dan ini cepat, tetapi tidak secepat jika Anda telah menyortirnya.
Ari B. Friedman
Menurut saya, pencarian biner lebih cepat daripada pemindaian vektor saat memilih baris. Saya bukan ilmuwan komputer, jadi saya tidak tahu apa artinya itu. Selain FAQ, lihat pengantar .
Frank

Jawaban:

125

Pembaruan kecil: Harap lihat juga vinyet HTML baru . Masalah ini menyoroti sketsa lain yang kami rencanakan.


Saya telah memperbarui jawaban ini lagi (Feb 2016) sehubungan dengan on=fitur baru yang memungkinkan ad-hoc bergabung juga. Lihat riwayat untuk jawaban sebelumnya (usang).

Apa sebenarnya yang setkey(DT, a, b)dilakukannya?

Itu melakukan dua hal:

  1. mengurutkan ulang baris data.table DT dengan kolom yang disediakan ( a , b ) dengan referensi , selalu dalam urutan yang meningkat .
  2. menandai kolom tersebut sebagai kolom kunci dengan menyetel atribut yang dipanggil sortedke DT.

Pengubahan urutannya cepat (karena penyortiran radix internal data.table ) dan efisien memori (hanya satu kolom tambahan tipe ganda dialokasikan).

Kapan setkey()dibutuhkan?

Untuk operasi pengelompokan, setkey()tidak pernah menjadi persyaratan mutlak. Artinya, kita bisa melakukan cold-by atau adhoc-by .

## "cold" by
require(data.table)
DT <- data.table(x=rep(1:5, each=2), y=1:10)
DT[, mean(y), by=x] # no key is set, order of groups preserved in result

Namun, sebelum v1.9.6, bergabung dari bentuk x[i]yang diperlukan keyuntuk diatur pada x. Dengan on=argumen baru dari v1.9.6 + , ini tidak berlaku lagi, dan oleh karena itu pengaturan kunci juga bukan persyaratan mutlak di sini.

## joins using < v1.9.6 
setkey(X, a) # absolutely required
setkey(Y, a) # not absolutely required as long as 'a' is the first column
X[Y]

## joins using v1.9.6+
X[Y, on="a"]
# or if the column names are x_a and y_a respectively
X[Y, on=c("x_a" = "y_a")]

Perhatikan bahwa on=argumen dapat ditentukan secara eksplisit bahkan untuk keyedgabungan juga.

Satu-satunya operasi yang perlu keydisetel secara mutlak adalah fungsi foverlaps () . Tetapi kami sedang mengerjakan beberapa fitur lagi yang bila selesai akan menghapus persyaratan ini.

  • Jadi apa alasan untuk menerapkan on=argumen?

    Ada beberapa alasan.

    1. Ini memungkinkan untuk secara jelas membedakan operasi sebagai operasi yang melibatkan dua data.tables . Hanya melakukan X[Y]tidak membedakan ini juga, meskipun dapat diperjelas dengan menamai variabel dengan tepat.

    2. Ini juga memungkinkan untuk memahami kolom di mana gabungan / subset dilakukan segera dengan melihat baris kode itu (dan tidak harus menelusuri kembali ke setkey()baris yang sesuai ).

    3. Dalam operasi di mana kolom ditambahkan atau diperbarui dengan referensi , on=operasi jauh lebih berkinerja karena tidak memerlukan seluruh data.table untuk diurutkan ulang hanya untuk menambah / memperbarui kolom. Sebagai contoh,

      ## compare 
      setkey(X, a, b) # why physically reorder X to just add/update a column?
      X[Y, col := i.val]
      
      ## to
      X[Y, col := i.val, on=c("a", "b")]
      

      Dalam kasus kedua, kami tidak perlu menyusun ulang. Ini bukan menghitung urutan yang memakan waktu, tetapi secara fisik menyusun ulang data.table dalam RAM, dan dengan menghindarinya, kami mempertahankan urutan asli, dan juga berfungsi.

    4. Bahkan sebaliknya, kecuali Anda melakukan join berulang-ulang, seharusnya tidak ada perbedaan performa yang terlihat antara gabungan keyed dan ad-hoc .

Hal ini mengarah pada pertanyaan, keuntungan apa lagi yang dimiliki keying data.table ?

  • Apakah ada keuntungan memasukkan data.table?

    Keying sebuah data.table fisik menata ulang itu berdasarkan pada kolom (s) di RAM. Menghitung urutan biasanya bukan bagian yang memakan waktu, melainkan penyusunan ulang itu sendiri. Namun, setelah data diurutkan dalam RAM, baris yang termasuk dalam grup yang sama semuanya bersebelahan dalam RAM, dan oleh karena itu sangat efisien dalam cache. Urutan inilah yang mempercepat operasi pada data.tables yang dikunci.

    Oleh karena itu, penting untuk mengetahui apakah waktu yang dihabiskan untuk menyusun ulang seluruh data.table sepadan dengan waktu untuk melakukan gabungan / agregasi yang efisien-cache. Biasanya, kecuali ada operasi pengelompokan / penggabungan berulang yang dilakukan pada keyed data.table yang sama , seharusnya tidak ada perbedaan yang mencolok.

Oleh karena itu, dalam banyak kasus, tidak perlu lagi menyetel kunci. Kami merekomendasikan penggunaan on=jika memungkinkan, kecuali tombol pengaturan memiliki peningkatan kinerja yang dramatis yang ingin Anda manfaatkan.

Pertanyaan: Menurut Anda, seperti apa kinerja dibandingkan dengan gabungan kunci , jika Anda menggunakan setorder()untuk menyusun ulang data.table dan menggunakan on=? Jika Anda telah mengikuti sejauh ini, Anda seharusnya dapat menemukannya :-).

Arun
sumber
3
Keren Terimakasih! Sampai sekarang, saya belum memikirkan tentang apa sebenarnya arti "pencarian biner", dan juga tidak benar-benar memahami alasan mengapa pencarian itu digunakan sebagai pengganti hash.
Frank
@Arun, apakah DT[J(1e4:1e5)]benar - benar setara dengan DF[DF$x > 1e4 & DF$x < 1e5, ]? Bisakah Anda menunjukkan kepada saya apa Jartinya? Selain itu, penelusuran tidak akan sample(1e4, 1e7, TRUE)menghasilkan baris apa pun karena tidak menyertakan angka di atas 1e4.
fishtank
@ fishtank, dalam hal ini, harus >=dan <=- diperbaiki. J(dan .) adalah alias list(yaitu, setara). Secara internal, when iis list, itu diubah menjadi data.table yang mengikuti pencarian biner yang digunakan untuk menghitung indeks baris. Diperbaiki 1e4untuk 1e5menghindari kebingungan. Terima kasih sudah bercak. Perhatikan bahwa kita sekarang bisa langsung menggunakan on=argumen untuk melakukan subset biner daripada menyetel kunci. Baca lebih lanjut dari sketsa HTML baru . Dan perhatikan halaman itu untuk sketsa untuk gabungan.
Arun
mungkin ini bisa menjadi pembaruan yang lebih menyeluruh? bagian "bila diperlukan" tampaknya sudah usang, misalnya
MichaelChirico
Fungsi apa yang memberi tahu Anda kunci yang digunakan?
skan
20

Kunci pada dasarnya adalah indeks ke dalam kumpulan data, yang memungkinkan operasi pengurutan, filter, dan penggabungan yang sangat cepat dan efisien. Ini mungkin alasan terbaik untuk menggunakan tabel data daripada bingkai data (sintaks untuk menggunakan tabel data juga jauh lebih ramah pengguna, tetapi itu tidak ada hubungannya dengan kunci).

Jika Anda tidak memahami indeks, pertimbangkan ini: nama buku telepon "diindeks". Jadi jika saya ingin mencari nomor telepon seseorang, itu sangat mudah. Tetapi bagaimana jika saya ingin mencari berdasarkan nomor telepon (misalnya, mencari siapa yang memiliki nomor telepon tertentu)? Kecuali saya dapat "mengindeks ulang" buku telepon dengan nomor telepon, ini akan memakan waktu yang sangat lama.

Pertimbangkan contoh berikut: misalkan saya memiliki tabel, ZIP, dari semua kode pos di AS (> 33.000) bersama dengan informasi terkait (kota, negara bagian, populasi, pendapatan median, dll.). Jika saya ingin mencari informasi untuk kode pos tertentu, pencarian (filter) sekitar 1000 kali lebih cepat jika saya setkey(ZIP,zipcode)pertama kali.

Manfaat lain berkaitan dengan bergabung. Misalkan seseorang memiliki daftar orang dan kode posnya dalam tabel data (sebut saja "PPL"), dan saya ingin menambahkan informasi dari tabel ZIP (misalnya kota, negara bagian, dan sebagainya). Kode berikut akan melakukannya:

setkey(ZIP,zipcode)
setkey(PPL,zipcode)
full.info <- PPL[ZIP, nomatch=F]

Ini adalah "gabung" dalam arti bahwa saya bergabung dengan informasi dari 2 tabel berdasarkan bidang umum (kode pos). Gabungan seperti ini pada tabel yang sangat besar sangat lambat dengan bingkai data, dan sangat cepat dengan tabel data. Dalam contoh kehidupan nyata saya harus melakukan lebih dari 20.000 penggabungan seperti ini pada tabel lengkap kode pos. Dengan tabel data, skrip membutuhkan waktu sekitar 20 menit. untuk berlari. Saya bahkan tidak mencobanya dengan bingkai data karena akan memakan waktu lebih dari 2 minggu.

IMHO Anda tidak hanya harus membaca tetapi mempelajari materi FAQ dan Intro. Lebih mudah untuk memahami jika Anda memiliki masalah sebenarnya untuk menerapkan ini.

[Tanggapan untuk komentar @ Frank]

Perihal: pengurutan vs. pengindeksan - Berdasarkan jawaban atas pertanyaan ini , tampaknya setkey(...)sebenarnya mengatur ulang kolom dalam tabel (misalnya, pengurutan fisik), dan tidak membuat indeks dalam pengertian database. Ini memiliki beberapa implikasi praktis: untuk satu hal jika Anda menetapkan kunci dalam tabel dengan setkey(...)dan kemudian mengubah salah satu nilai di kolom kunci, data.table hanya mendeklarasikan tabel tidak lagi diurutkan (dengan mematikan sortedatribut); itu tidak secara dinamis mengindeks ulang untuk mempertahankan urutan yang benar (seperti yang akan terjadi dalam database). Juga, "menghapus kunci" menggunakan setky(DT,NULL)tidak tidak mengembalikan meja untuk itu asli, agar tidak dipisahkan.

Re: filter vs. join - perbedaan praktisnya adalah filtering mengekstrak subset dari satu dataset, sedangkan join menggabungkan data dari dua dataset berdasarkan field umum. Ada banyak jenis gabungan (dalam, luar, kiri). Contoh di atas adalah gabungan dalam (hanya rekaman dengan kunci yang sama untuk kedua tabel yang dikembalikan), dan ini memiliki banyak kesamaan dengan pemfilteran.

jlhoward.dll
sumber
1
+1. Mengenai kalimat pertamamu ... udah urut kan? Dan bukankah gabungan merupakan kasus khusus dari filter (atau operasi yang menggunakan pemfilteran sebagai langkah pertamanya)? Sepertinya "pemfilteran yang lebih baik" merangkum seluruh manfaat.
Frank
1
Atau pemindaian yang lebih baik, kurasa.
Kaki Basah
1
@jlhow Terima kasih. Keyakinan saya sebelumnya adalah bahwa penyortiran bukanlah salah satu manfaat dari pengaturan kunci (karena jika Anda ingin mengurutkan, Anda harus menyortir saja), dan itu juga setkeybenar - benar menyusun ulang baris secara permanen. Jika hanya untuk keperluan tampilan, lalu bagaimana cara mencetak sepuluh baris pertama sesuai dengan urutan "benar" (yang akan saya lihat sebelum setkey)? Saya cukup yakin setkey(DT,NULL)tidak melakukan ini ... (lanjutan)
Frank
... (lanjutan) Selain itu, saya belum melihat kode untuk paket tersebut, tetapi untuk bergabung X[Y,...], Anda perlu "memfilter" baris X menggunakan kunci. Memang, hal-hal lain terjadi setelah itu (kolom Y tersedia, dan ada implisit oleh-tanpa-oleh), tetapi saya masih tidak melihatnya sebagai manfaat yang berbeda secara konseptual. Saya kira jawaban Anda diletakkan dalam istilah operasi yang mungkin ingin Anda lakukan, di mana perbedaannya mungkin berguna.
Frank
1
@Frank - Jadi setkey(DT,NULL)menghapus kunci tetapi tidak mempengaruhi urutan. Mengajukan pertanyaan tentang ini di sini . Ayo lihat.
jlhoward