Ich verwende data.table und es gibt viele Funktionen, bei denen ich einen Schlüssel setzen muss (z X[Y]
. B. ). Daher möchte ich verstehen, was ein Schlüssel tut, um Schlüssel in meinen Datentabellen richtig festzulegen.
Eine Quelle, die ich las, war ?setkey
.
setkey()
sortiert adata.table
und markiert es als sortiert. Die sortierten Spalten sind der Schlüssel. Der Schlüssel kann eine beliebige Spalte in beliebiger Reihenfolge sein. Die Spalten sind immer in aufsteigender Reihenfolge sortiert. Die Tabelle wird als Referenz geändert. Es wird überhaupt keine Kopie erstellt, außer dem temporären Arbeitsspeicher, der so groß wie eine Spalte ist.
Meine Erkenntnis hier ist, dass ein Schlüssel die data.table "sortieren" würde, was zu einem sehr ähnlichen Effekt führen würde wie order()
. Es erklärt jedoch nicht den Zweck eines Schlüssels.
In den häufig gestellten Fragen 3.2 und 3.3 der Datentabelle wird Folgendes erläutert:
3.2 Ich habe keinen Schlüssel auf einem großen Tisch, aber die Gruppierung ist immer noch sehr schnell. Warum ist das so?
data.table verwendet die Radix-Sortierung. Dies ist deutlich schneller als andere Sortieralgorithmen. Radix ist speziell nur für ganze Zahlen, siehe
?base::sort.list(x,method="radix")
. Dies ist auch ein Grund, warumsetkey()
es schnell geht. Wenn kein Schlüssel festgelegt ist oder wir in einer anderen Reihenfolge als der Schlüssel gruppieren, nennen wir ihn Ad-hoc von.3.3 Warum ist die Gruppierung nach Spalten im Schlüssel schneller als eine Ad-hoc-Gruppierung?
Da jede Gruppe im RAM zusammenhängend ist, wodurch Seitenabrufe minimiert werden und der Speicher in großen Mengen (
memcpy
in C) kopiert werden kann, anstatt in C eine Schleife zu bilden.
Von hier aus denke ich, dass das Setzen eines Schlüssels es R irgendwie ermöglicht, die "Radix-Sortierung" gegenüber anderen Algorithmen zu verwenden, und deshalb ist es schneller.
Die 10-minütige Kurzanleitung enthält auch eine Anleitung zu den Tasten.
- Schlüssel
Beginnen wir mit der Betrachtung von data.frame, insbesondere von Rownamen (oder in Englisch von Zeilennamen). Das heißt, die mehreren Namen, die zu einer einzelnen Zeile gehören. Die mehreren Namen, die zur einzelnen Zeile gehören? Das ist nicht das, was wir in einem data.frame gewohnt sind. Wir wissen, dass jede Zeile höchstens einen Namen hat. Eine Person hat mindestens zwei Namen, einen ersten und einen zweiten Namen. Dies ist nützlich, um beispielsweise ein Telefonverzeichnis zu organisieren, das nach Nachname und Vorname sortiert ist. Jede Zeile in einem data.frame kann jedoch nur einen Namen haben.
Ein Schlüssel besteht aus einer oder mehreren Spalten von Rownamen, die eine Ganzzahl, ein Faktor, ein Zeichen oder eine andere Klasse sein können, nicht nur ein Zeichen. Außerdem werden die Zeilen nach dem Schlüssel sortiert. Daher kann eine data.table höchstens einen Schlüssel haben, da sie nicht auf mehrere Arten sortiert werden kann.
Die Eindeutigkeit wird nicht erzwungen, dh doppelte Schlüsselwerte sind zulässig. Da die Zeilen nach dem Schlüssel sortiert sind, werden alle Duplikate im Schlüssel nacheinander angezeigt
Das Telefonverzeichnis war hilfreich, um zu verstehen, was ein Schlüssel ist, aber es scheint, dass ein Schlüssel nicht anders ist als eine Faktorspalte. Darüber hinaus wird nicht erklärt, warum ein Schlüssel benötigt wird (insbesondere um bestimmte Funktionen zu verwenden) und wie die Spalte ausgewählt wird, die als Schlüssel festgelegt werden soll. Außerdem scheint es, dass in einer data.table mit der Zeit als Spalte das Festlegen einer anderen Spalte als Schlüssel wahrscheinlich auch die Zeitspalte durcheinander bringt, was es noch verwirrender macht, da ich nicht weiß, ob ich eine andere Spalte als festlegen darf Schlüssel. Kann mich bitte jemand aufklären?
quelle
Antworten:
Kleinere Aktualisierung: Bitte beachten Sie auch die neuen HTML-Vignetten . In dieser Ausgabe werden die anderen Vignetten vorgestellt, die wir planen.
Ich habe diese Antwort (Februar 2016) angesichts der neuen
on=
Funktion, die auch Ad-hoc- Joins ermöglicht, erneut aktualisiert . Frühere (veraltete) Antworten finden Sie im Verlauf.Was genau macht
setkey(DT, a, b)
das?Es macht zwei Dinge:
DT
nach den bereitgestellten Spalten neu an ( a , b ) als Referenz neu an , immer in aufsteigender Reihenfolge.sorted
zu dem aufgerufen wirdDT
.Die Neuordnung ist sowohl schnell (aufgrund der internen Radix-Sortierung von data.table ) als auch speichereffizient (nur eine zusätzliche Spalte vom Typ double wird zugewiesen).
Wann ist
setkey()
erforderlich?Für Gruppierungsvorgänge
setkey()
war dies nie eine absolute Voraussetzung. Das heißt, wir können eine Erkältung durchführen oder By Ad-Hoc-By durchführen .Vorher
v1.9.6
müssen jedoch Verknüpfungen des Formularsx[i]
aktiviertkey
werdenx
. Mit dem neuenon=
Argument aus Version 1.9.6 + ist dies nicht mehr der Fall, und das Setzen von Schlüsseln ist daher auch hier keine absolute Voraussetzung.Beachten Sie, dass das
on=
Argument auch fürkeyed
Joins explizit angegeben werden kann .Was ist der Grund für die Implementierung von
on=
Argumenten?Es gibt einige Gründe.
Es ermöglicht eine klare Unterscheidung der Operation als eine Operation mit zwei Datentabellen . Nur dies zu tun,
X[Y]
unterscheidet dies auch nicht, obwohl dies durch die entsprechende Benennung der Variablen deutlich werden könnte.Sie können auch die Spalten verstehen, für die der Join / die Teilmenge sofort ausgeführt wird, indem Sie sich diese Codezeile ansehen (und nicht zur entsprechenden
setkey()
Zeile zurückverfolgen müssen ).Bei Operationen, bei denen Spalten durch Referenz hinzugefügt oder aktualisiert werden , sind
on=
Operationen viel leistungsfähiger, da nicht die gesamte Datentabelle neu angeordnet werden muss, nur um Spalten hinzuzufügen / zu aktualisieren. Beispielsweise,Im zweiten Fall mussten wir nicht nachbestellen. Es geht nicht darum, die Reihenfolge zu berechnen, die zeitaufwändig ist, sondern die Datentabelle im RAM physisch neu zu ordnen. Indem wir sie vermeiden, behalten wir die ursprüngliche Reihenfolge bei und sie ist auch performant.
Selbst ansonsten sollte es keinen merklichen Leistungsunterschied zwischen verschlüsselten und Ad-hoc- Verknüpfungen geben , es sei denn, Sie führen Joins wiederholt durch .
Dies führt zu der Frage, welchen Vorteil das Eingeben einer data.table mehr hat.
Gibt es einen Vorteil beim Eingeben einer Datentabelle?
Wenn Sie eine data.table eingeben, wird sie basierend auf den Spalten im RAM physisch neu angeordnet. Die Berechnung der Bestellung ist normalerweise nicht der zeitaufwändige Teil, sondern die Neuordnung selbst. Sobald wir die Daten im RAM sortiert haben, sind die Zeilen, die zu derselben Gruppe gehören, alle im RAM zusammenhängend und daher sehr cacheeffizient. Es ist die Sortierung, die Operationen an verschlüsselten Datentabellen beschleunigt.
Es ist daher wichtig herauszufinden, ob die Zeit, die für die Neuordnung der gesamten Daten aufgewendet wird, die Zeit für eine cacheeffiziente Verknüpfung / Aggregation wert ist. Normalerweise, es sei denn es sind sich wiederholende Gruppierung / Join - Operationen auf demselben ausgeführt werden verkeilten data.table, sollte es nicht einen spürbaren Unterschied.
Frage: Wie würde Ihrer Meinung nach die Leistung im Vergleich zu einem verschlüsselten Join aussehen , wenn Sie
setorder()
die data.table neu verwenden und verwendenon=
? Wenn Sie bisher gefolgt sind, sollten Sie es herausfinden können :-).quelle
DT[J(1e4:1e5)]
wirklich gleichbedeutend mitDF[DF$x > 1e4 & DF$x < 1e5, ]
? Könnten Sie mich darauf hinweisen, wasJ
bedeutet? Außerdem würde diese Suche keine Zeilen zurückgeben, dasample(1e4, 1e7, TRUE)
sie keine Zahlen über 1e4 enthält.>=
und<=
- behoben sein.J
(und.
) sind Aliase zulist
(dh sie sind äquivalent). Wenni
es sich um eine Liste handelt, wird sie intern in eine data.table konvertiert, nach der die binäre Suche zum Berechnen von Zeilenindizes verwendet wird. Feste1e4
auf ,1e5
um Verwechslungen zu vermeiden. Danke fürs Erkennen. Beachten Sie, dass wiron=
jetzt direkt Argumente verwenden können, um binäre Teilmengen auszuführen, anstatt den Schlüssel zu setzen. Lesen Sie mehr aus den neuen HTML-Vignetten . Und behalten Sie diese Seite im Auge, um Vignetten für Verknüpfungen zu erhalten.Ein Schlüssel ist im Grunde ein Index in einem Dataset, der sehr schnelle und effiziente Sortier-, Filter- und Verknüpfungsvorgänge ermöglicht. Dies sind wahrscheinlich die besten Gründe, Datentabellen anstelle von Datenrahmen zu verwenden (die Syntax für die Verwendung von Datentabellen ist auch viel benutzerfreundlicher, hat aber nichts mit Schlüsseln zu tun).
Wenn Sie Indizes nicht verstehen, beachten Sie Folgendes: Ein Telefonbuch wird nach Namen "indiziert". Wenn ich also die Telefonnummer von jemandem nachschlagen möchte, ist das ziemlich einfach. Angenommen, ich möchte nach Telefonnummer suchen (z. B. nachschlagen, wer eine bestimmte Telefonnummer hat). Wenn ich das Telefonbuch nicht nach Telefonnummer "neu indizieren" kann, dauert es sehr lange.
Betrachten Sie das folgende Beispiel: Angenommen, ich habe eine Tabelle mit der Postleitzahl aller Postleitzahlen in den USA (> 33.000) sowie die zugehörigen Informationen (Stadt, Bundesland, Bevölkerung, Durchschnittseinkommen usw.). Wenn ich die Informationen für eine bestimmte Postleitzahl nachschlagen möchte, ist die Suche (Filter) etwa 1000-mal schneller, wenn ich sie
setkey(ZIP,zipcode)
zuerst mache .Ein weiterer Vorteil betrifft Joins. Angenommen, Sie haben eine Liste mit Personen und deren Postleitzahlen in einer Datentabelle (nennen Sie sie "PPL"), und ich möchte Informationen aus der Postleitzahlentabelle anhängen (z. B. Stadt, Bundesland usw.). Der folgende Code wird es tun:
Dies ist ein "Join" in dem Sinne, dass ich die Informationen aus 2 Tabellen in einem gemeinsamen Feld (Postleitzahl) verbinde. Solche Verknüpfungen in sehr großen Tabellen sind bei Datenrahmen extrem langsam und bei Datentabellen extrem schnell. In einem Beispiel aus dem wirklichen Leben musste ich mehr als 20.000 Verknüpfungen wie diese auf einer vollständigen Tabelle mit Postleitzahlen durchführen. Bei Datentabellen dauerte das Skript ca. 20 Minuten. laufen. Ich habe es nicht einmal mit Datenrahmen versucht, da es mehr als 2 Wochen gedauert hätte.
IMHO sollten Sie nicht nur die FAQ und das Intro-Material lesen, sondern auch studieren . Es ist einfacher zu verstehen, ob Sie ein tatsächliches Problem haben, auf das Sie dies anwenden können.
[Antwort auf @ Franks Kommentar]
Betreff: Sortieren vs. Indizieren - Basierend auf der Antwort auf diese Frage scheint es, dass
setkey(...)
die Spalten in der Tabelle tatsächlich neu angeordnet werden (z. B. eine physische Sortierung) und kein Index im Sinne der Datenbank erstellt wird. Dies hat einige praktische Auswirkungen: Zum einen, wenn Sie den Schlüssel in einer Tabelle mit festlegensetkey(...)
und dann einen der Werte in der Schlüsselspalte ändern, deklariert data.table lediglich, dass die Tabelle nicht mehr sortiert ist (indem Sie dassorted
Attribut deaktivieren ). Es wird nicht dynamisch neu indiziert, um die richtige Sortierreihenfolge beizubehalten (wie dies in einer Datenbank der Fall wäre). Auch „Entfernen Sie den Schlüssel“ verwendetsetky(DT,NULL)
wird nicht in der Tabelle wieder her , um es den ursprünglichen, unsortiert.Betreff: Filter vs. Join - Der praktische Unterschied besteht darin, dass beim Filtern eine Teilmenge aus einem einzelnen Dataset extrahiert wird, während beim Join Daten aus zwei Datasets basierend auf einem gemeinsamen Feld kombiniert werden. Es gibt viele verschiedene Arten von Verknüpfungen (innen, außen, links). Das obige Beispiel ist eine innere Verknüpfung (es werden nur Datensätze mit Schlüsseln zurückgegeben, die beiden Tabellen gemeinsam sind), und dies hat viele Ähnlichkeiten mit der Filterung.
quelle
setkey
die Zeilen tatsächlich irreversibel neu angeordnet werden. Wenn es nur zu Anzeigezwecken dient, wie drucke ich dann die ersten zehn Zeilen gemäß der "wahren" Reihenfolge (die ich vor setkey gesehen hätte)? Ich bin mir ziemlich sicher,setkey(DT,NULL)
dass ich das nicht tue ... (Forts.)X[Y,...]
, müssen Sie die Zeilen von X mit dem Schlüssel "filtern". Zugegeben, danach passieren andere Dinge (Ys Spalten werden zur Verfügung gestellt, und es gibt ein implizites By-Without-By), aber ich sehe das immer noch nicht als konzeptionell eindeutigen Vorteil. Ich denke, Ihre Antwort bezieht sich auf Operationen, die Sie möglicherweise ausführen möchten, wobei die Unterscheidung hilfreich sein kann.setkey(DT,NULL)
den Schlüssel, hat jedoch keinen Einfluss auf die Sortierreihenfolge. Gestellt eine Frage zu diesem hier . Mal schauen.