วัตถุประสงค์ของการตั้งค่าคีย์ใน data.table คืออะไร?


113

ฉันใช้ data.table และมีฟังก์ชั่นมากมายที่ทำให้ฉันต้องตั้งค่าคีย์ (เช่นX[Y]) ด้วยเหตุนี้ฉันจึงต้องการทำความเข้าใจว่าคีย์ทำอะไรเพื่อที่จะตั้งค่าคีย์ในตารางข้อมูลของฉันได้อย่างถูกต้อง


?setkeyแหล่งข่าวคนหนึ่งผมอ่านเป็น

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

Takeaway ของฉันที่นี่คือคีย์จะ "เรียงลำดับ" data.table ซึ่งส่งผลให้เกิดผลคล้ายกับorder(). อย่างไรก็ตามมันไม่ได้อธิบายวัตถุประสงค์ของการมีคีย์


คำถามที่พบบ่อย data.table 3.2 และ 3.3 อธิบาย:

3.2 ฉันไม่มีคีย์บนโต๊ะขนาดใหญ่ แต่การจัดกลุ่มยังเร็วมาก ทำไมถึงเป็นเช่นนั้น?

data.table ใช้การเรียงลำดับเลขฐาน ซึ่งเร็วกว่าอัลกอริทึมการจัดเรียงอื่น ๆ อย่างเห็นได้ชัด Radix เป็น specically ?base::sort.list(x,method="radix")สำหรับจำนวนเต็มเท่านั้นดู นี่เป็นสาเหตุหนึ่งที่ทำให้ setkey()รวดเร็ว เมื่อไม่มีการตั้งค่าคีย์หรือเราจัดกลุ่มในลำดับที่แตกต่างจากคีย์เราเรียกว่าคีย์เฉพาะกิจโดย

3.3 เหตุใดการจัดกลุ่มตามคอลัมน์ในคีย์จึงเร็วกว่าเฉพาะกิจโดย?

เนื่องจากแต่ละกลุ่มอยู่ติดกันใน RAM จึงลดการดึงข้อมูลเพจและสามารถคัดลอกหน่วยความจำเป็นกลุ่ม ( memcpyใน C) แทนที่จะวนซ้ำใน C

จากตรงนี้ฉันเดาว่าการตั้งค่าคีย์ทำให้ R สามารถใช้ "การเรียงลำดับเรดิกซ์" เหนืออัลกอริทึมอื่น ๆ ได้และนั่นคือสาเหตุที่เร็วกว่า


คู่มือเริ่มใช้งานฉบับย่อ 10 นาทียังมีคำแนะนำเกี่ยวกับปุ่มต่างๆ

  1. คีย์

เริ่มต้นด้วยการพิจารณา data.frame ชื่อแถวเฉพาะ (หรือในภาษาอังกฤษชื่อแถว) นั่นคือหลายชื่อที่อยู่ในแถวเดียว หลายชื่อที่อยู่ในแถวเดียว? นั่นไม่ใช่สิ่งที่เราคุ้นเคยใน data.frame เรารู้ว่าแต่ละแถวมีชื่อมากที่สุดหนึ่งชื่อ บุคคลมีชื่ออย่างน้อยสองชื่อชื่อ rst และชื่อที่สอง ซึ่งมีประโยชน์ในการจัดระเบียบสมุดโทรศัพท์เช่นจัดเรียงตามนามสกุลแล้วตามด้วยชื่อ rst อย่างไรก็ตามแต่ละแถวใน data.frame สามารถมีได้เพียงชื่อเดียว

คีย์ประกอบด้วยคอลัมน์อย่างน้อยหนึ่งคอลัมน์ของชื่อแถวซึ่งอาจเป็นจำนวนเต็มตัวประกอบอักขระหรือคลาสอื่น ๆ ไม่ใช่แค่อักขระ นอกจากนี้แถวจะเรียงตามคีย์ ดังนั้น data.table สามารถมีได้ไม่เกินหนึ่งคีย์เนื่องจากไม่สามารถจัดเรียงได้มากกว่าหนึ่งวิธี

ไม่มีการบังคับใช้ความเป็นเอกลักษณ์กล่าวคืออนุญาตให้มีค่าคีย์ที่ซ้ำกัน เนื่องจากแถวเรียงตามคีย์รายการที่ซ้ำกันในคีย์จะปรากฏขึ้นตามลำดับ

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


"ฉันเดาว่าการตั้งค่าคีย์จะทำให้ R สามารถใช้" การเรียงลำดับเรดิกซ์ "เหนืออัลกอริทึมอื่น ๆ " - ฉันไม่ได้รับความช่วยเหลือเลย การอ่านของฉันคือการตั้งค่าคีย์เรียงตามคีย์ คุณสามารถจัดเรียง "เฉพาะกิจ" ตามคอลัมน์อื่นที่ไม่ใช่คีย์ได้และทำได้เร็ว แต่ไม่เร็วเท่าที่คุณจัดเรียงไว้แล้ว
Ari B. Friedman

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

คำตอบ:


125

การอัปเดตเล็กน้อย:โปรดดูที่สะเปะสะปะของ HTML ใหม่ด้วย ปัญหานี้เน้นถึงความสะเปะสะปะอื่น ๆ ที่เราวางแผนไว้


ฉันได้อัปเดตคำตอบนี้อีกครั้ง (กุมภาพันธ์ 2016) โดยคำนึงถึงon=คุณลักษณะใหม่ที่อนุญาตให้ใช้เฉพาะกิจเข้าร่วมเช่นกัน ดูประวัติสำหรับคำตอบก่อนหน้านี้ (ล้าสมัย)

อะไรกันแน่ setkey(DT, a, b) ?

มันทำสองสิ่ง:

  1. reorders แถวของdata.table DTตามคอลัมน์ (s) มีให้บริการ ( , ) โดยการอ้างอิงเสมอในการเพิ่มการสั่งซื้อ
  2. เครื่องหมายคอลัมน์ที่เป็นคีย์คอลัมน์โดยการตั้งค่าแอตทริบิวต์ที่เรียกว่าการsortedDT

การเรียงลำดับใหม่ทำได้รวดเร็ว (เนื่องจากการเรียงลำดับเรดิกซ์ภายในของdata.table ) และหน่วยความจำมีประสิทธิภาพ (คอลัมน์พิเศษเพียงคอลัมน์เดียวประเภทdoubleมีการจัดสรร )

เมื่อเป็น setkey()จำเป็น?

สำหรับการดำเนินการจัดกลุ่มsetkey()ไม่เคยเป็นข้อกำหนดที่แน่นอน นั่นก็คือเราสามารถดำเนินการเย็นโดยหรือเฉพาะกิจโดย

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

อย่างไรก็ตามก่อนที่จะv1.9.6ร่วมของแบบฟอร์มx[i]ที่จำเป็นจะต้องตั้งอยู่บนkey ด้วยอาร์กิวเมนต์ใหม่จาก v1.9.6 +สิ่งนี้ไม่เป็นความจริงอีกต่อไปและการตั้งค่าคีย์จึงไม่ใช่ข้อกำหนดที่สมบูรณ์เช่นกันxon=

## 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")]

โปรดทราบว่าon=อาร์กิวเมนต์สามารถระบุได้อย่างชัดเจนแม้กระทั่งการkeyedรวมเช่นกัน

การดำเนินการเท่านั้นที่ต้องkeyมีการกำหนดอย่างเป็นfoverlaps ()ฟังก์ชั่น แต่เรากำลังดำเนินการกับคุณสมบัติเพิ่มเติมบางอย่างซึ่งเมื่อทำเสร็จแล้วจะลบข้อกำหนดนี้

  • แล้วเหตุผลในการใช้on=อาร์กิวเมนต์คืออะไร?

    มีไม่กี่เหตุผล

    1. ช่วยให้สามารถแยกแยะการดำเนินการได้อย่างชัดเจนว่าเป็นการดำเนินการที่เกี่ยวข้องกับdata.tablesสองรายการ การทำเพียงอย่างเดียวX[Y]ก็ไม่สามารถแยกแยะสิ่งนี้ได้เช่นกันแม้ว่าจะชัดเจนโดยการตั้งชื่อตัวแปรให้เหมาะสม

    2. นอกจากนี้ยังช่วยให้เข้าใจคอลัมน์ที่กำลังดำเนินการjoin / subsetทันทีโดยดูที่บรรทัดของโค้ดนั้น (และไม่ต้องย้อนกลับไปยังsetkey()บรรทัดที่เกี่ยวข้อง)

    3. ในการดำเนินงานที่คอลัมน์ที่มีการเพิ่มหรือปรับปรุงโดยการอ้างอิง , on=การดำเนินงานมีมาก performant ขณะที่มันไม่จำเป็นต้องทั้ง data.table จะได้รับการจัดลำดับใหม่เพียงเพื่อเพิ่ม / คอลัมน์ปรับปรุง (s) ตัวอย่างเช่น,

      ## 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")]

      ในกรณีที่สองเราไม่จำเป็นต้องเรียงลำดับใหม่ ไม่ใช่การคำนวณลำดับที่ใช้เวลานาน แต่เป็นการจัดเรียงข้อมูลใหม่ตารางใน RAM และการหลีกเลี่ยงเรายังคงรักษาคำสั่งเดิมและยังมีประสิทธิภาพอีกด้วย

    4. แม้จะเป็นอย่างอื่นเว้นแต่ว่าคุณจะทำการเข้าร่วมซ้ำ ๆ กันก็ตามไม่ควรมีความแตกต่างด้านประสิทธิภาพที่เห็นได้ชัดเจนระหว่างการรวมคีย์และการรวมเฉพาะกิจ

สิ่งนี้นำไปสู่คำถามว่าการคีย์data.tableมีประโยชน์อะไรอีกต่อไป?

  • การคีย์ data.table มีข้อดีหรือไม่?

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

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

ในกรณีส่วนใหญ่จึงไม่จำเป็นต้องตั้งค่าคีย์อีกต่อไป เราขอแนะนำให้ใช้on=ทุกที่ที่เป็นไปได้เว้นแต่คีย์การตั้งค่าจะมีการปรับปรุงประสิทธิภาพอย่างมากที่คุณต้องการใช้ประโยชน์

คำถาม:คุณคิดว่าประสิทธิภาพจะเป็นอย่างไรเมื่อเทียบกับการเข้าร่วมแบบคีย์ถ้าคุณใช้setorder()เพื่อเรียงลำดับข้อมูลใหม่ตารางและใช้on=? หากคุณติดตามจนถึงตอนนี้คุณน่าจะเข้าใจได้ :-)


3
เจ๋งขอบคุณ! จนถึงตอนนี้ฉันไม่ได้คิดว่าจริงๆแล้ว "การค้นหาแบบไบนารี" หมายถึงอะไรและไม่เข้าใจเหตุผลว่าทำไมจึงใช้แทนแฮช
Frank

@Arun DT[J(1e4:1e5)]เทียบเท่าจริงDF[DF$x > 1e4 & DF$x < 1e5, ]หรือ? คุณช่วยชี้ให้ฉันเห็นความJหมายได้ไหม นอกจากนี้การค้นหาจะไม่ส่งคืนแถวใด ๆ เนื่องจากsample(1e4, 1e7, TRUE)ไม่มีตัวเลขที่สูงกว่า 1e4
fishtank

@fishtank ในกรณีนี้มันควรจะเป็น>=และ<=- คงที่ J(และ.) เป็นนามแฝงถึงlist(กล่าวคือเทียบเท่า) ภายในiรายการจะถูกแปลงเป็น data.table ตามหลังการค้นหาไบนารีที่ใช้ในการคำนวณดัชนีแถว แก้ไข1e4เพื่อ1e5หลีกเลี่ยงความสับสน ขอบคุณสำหรับการจำ โปรดทราบว่าon=ตอนนี้เราสามารถใช้อาร์กิวเมนต์โดยตรงเพื่อดำเนินการย่อยไบนารีแทนการตั้งค่าคีย์ อ่านเพิ่มเติมจากHTML สะเปะสะปะใหม่ และจับตาดูหน้านั้นเพื่อหาการเชื่อมต่อแบบสะเปะสะปะ
อรุณ

บางทีนี่อาจเป็นการอัปเดตอย่างละเอียดมากขึ้น? ส่วน "เมื่อจำเป็น" ดูเหมือนจะล้าสมัยเช่น
MichaelChirico

ฟังก์ชั่นใดที่บอกคุณว่ากำลังใช้คีย์
กัน

20

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

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

ลองพิจารณาตัวอย่างต่อไปนี้: สมมติว่าฉันมีตาราง ZIP ของรหัสไปรษณีย์ทั้งหมดในสหรัฐอเมริกา (> 33,000) พร้อมกับข้อมูลที่เกี่ยวข้อง (เมืองรัฐประชากรรายได้เฉลี่ย ฯลฯ ) หากฉันต้องการค้นหาข้อมูลสำหรับรหัสไปรษณีย์ที่เฉพาะเจาะจงการค้นหา (ตัวกรอง) จะเร็วขึ้นประมาณ 1000 เท่าหากsetkey(ZIP,zipcode)เป็นครั้งแรก

ประโยชน์อีกอย่างที่เกี่ยวข้องกับการรวม สมมติว่ามีรายชื่อบุคคลและรหัสไปรษณีย์ในตารางข้อมูล (เรียกว่า "PPL") และฉันต้องการต่อท้ายข้อมูลจากตาราง ZIP (เช่นเมืองรัฐและอื่น ๆ ) รหัสต่อไปนี้จะทำ:

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

นี่คือ "เข้าร่วม" ในแง่ที่ว่าฉันกำลังเข้าร่วมข้อมูลจาก 2 ตารางที่อยู่ในช่องทั่วไป (รหัสไปรษณีย์) การเข้าร่วมแบบนี้บนตารางขนาดใหญ่จะช้ามากกับ data frames และเร็วมากกับตารางข้อมูล ในตัวอย่างชีวิตจริงฉันต้องทำการรวมมากกว่า 20,000 รายการแบบนี้บนตารางรหัสไปรษณีย์ทั้งหมด ด้วยตารางข้อมูลสคริปต์ใช้เวลาประมาณ 20 นาที วิ่ง. ฉันไม่ได้ลองใช้ data frames ด้วยซ้ำเพราะจะต้องใช้เวลานานกว่า 2 สัปดาห์

IMHO คุณไม่ควรอ่าน แต่ศึกษา FAQ และ Intro material ง่ายกว่าที่จะเข้าใจหากคุณมีปัญหาจริงที่จะใช้กับสิ่งนี้

[ตอบกลับความคิดเห็นของ @ Frank]

เรื่องการเรียงลำดับเทียบกับการจัดทำดัชนี - จากคำตอบของคำถามนี้ดูเหมือนว่าsetkey(...)จะจัดเรียงคอลัมน์ในตารางใหม่ (เช่นการเรียงลำดับทางกายภาพ) และไม่ได้สร้างดัชนีในฐานข้อมูล สิ่งนี้มีผลในทางปฏิบัติ: ประการหนึ่งถ้าคุณตั้งค่าคีย์ในตารางด้วยsetkey(...)แล้วเปลี่ยนค่าใด ๆ ในคอลัมน์คีย์ data.table เพียงประกาศว่าตารางจะไม่เรียงลำดับอีกต่อไป (โดยการปิดsortedแอ็ตทริบิวต์) มันไม่ได้จัดทำดัชนีใหม่แบบไดนามิกเพื่อรักษาลำดับการจัดเรียงที่เหมาะสม (เช่นเดียวกับที่เกิดขึ้นในฐานข้อมูล) นอกจากนี้การ "ลบคีย์" โดยใช้setky(DT,NULL)จะไม่คืนค่าตารางให้กลับเป็นแบบเดิมและไม่เรียงลำดับ

Re: filter vs. join - ข้อแตกต่างในทางปฏิบัติคือการกรองแยกชุดย่อยจากชุดข้อมูลเดียวในขณะที่ join จะรวมข้อมูลจากชุดข้อมูลสองชุดโดยยึดตามช่องทั่วไป การเข้าร่วมมีหลายประเภท (ด้านในด้านนอกด้านซ้าย) ตัวอย่างด้านบนคือการรวมภายใน (เฉพาะระเบียนที่มีคีย์ทั่วไปของทั้งสองตารางเท่านั้นที่จะถูกส่งคืน) และสิ่งนี้มีความคล้ายคลึงกันมากกับการกรอง


1
+1 เกี่ยวกับประโยคแรกของคุณ ... มันเรียงลำดับแล้วใช่มั้ย? และไม่ใช่การเข้าร่วมกรณีพิเศษของตัวกรอง (หรือการดำเนินการที่ใช้การกรองเป็นขั้นตอนแรก)? ดูเหมือนว่า "การกรองที่ดีกว่า" จะสรุปผลประโยชน์ทั้งหมด
Frank

1
หรือการสแกนที่ดีกว่าฉันคิดว่า
Wet Feet

1
@jlhoward ขอบคุณ ความเชื่อเดิมของฉันคือการเรียงลำดับไม่ได้อยู่ในประโยชน์ของการตั้งค่าคีย์ (เนื่องจากหากคุณต้องการจัดเรียงคุณควรเรียงลำดับ) และการsetkeyเรียงลำดับแถวใหม่กลับไม่ได้ หากเป็นเพียงเพื่อการแสดงผลเท่านั้นฉันจะพิมพ์สิบแถวแรกตามลำดับ "จริง" ได้อย่างไร (ที่ฉันเคยเห็นก่อน setkey) ฉันค่อนข้างมั่นใจว่าsetkey(DT,NULL)จะไม่ทำแบบนี้ ... (ต่อ)
แฟรงค์

... (ต่อ) นอกจากนี้ฉันยังไม่ได้ดูรหัสสำหรับแพ็คเกจ แต่ในการเข้าร่วมX[Y,...]คุณต้อง "กรอง" แถวของ X โดยใช้คีย์ จริงอยู่สิ่งอื่น ๆ เกิดขึ้นหลังจากนั้น (คอลัมน์ของ Y พร้อมใช้งานและมีโดยนัยโดยไม่ต้องใช้โดย) แต่ฉันยังไม่เห็นว่าเป็นประโยชน์ที่แตกต่างกันในเชิงแนวคิด ฉันเดาว่าคำตอบของคุณอยู่ในแง่ของการดำเนินการที่คุณอาจต้องการทำซึ่งความแตกต่างอาจเป็นประโยชน์
Frank

1
@Frank - ดังนั้นsetkey(DT,NULL)ลบคีย์ แต่ไม่มีผลต่อลำดับการจัดเรียง โพสต์คำถามเกี่ยวกับเรื่องนี้ที่นี่ มาดูกัน.
jlhoward
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.