J'utilise data.table et il existe de nombreuses fonctions qui m'obligent à définir une clé (par exemple X[Y]
). En tant que tel, je souhaite comprendre ce que fait une clé afin de définir correctement les clés dans mes tables de données.
Une source que j'ai lue était ?setkey
.
setkey()
trie undata.table
et le marque comme trié. Les colonnes triées sont la clé. La clé peut être n'importe quelle colonne dans n'importe quel ordre. Les colonnes sont toujours triées par ordre croissant. Le tableau est modifié par référence. Aucune copie n'est effectuée, à l'exception de la mémoire de travail temporaire de la taille d'une colonne.
Ce que je retiens ici, c'est qu'une clé «trierait» la table data.table, ce qui aurait un effet très similaire à order()
. Cependant, cela n'explique pas le but d'avoir une clé.
La FAQ data.table 3.2 et 3.3 explique:
3.2 Je n'ai pas de clé sur une grande table, mais le regroupement est toujours très rapide. Pourquoi donc?
data.table utilise le tri par base. C'est beaucoup plus rapide que les autres algorithmes de tri. Radix est uniquement pour les entiers, voir
?base::sort.list(x,method="radix")
. C'est aussi une des raisons pour lesquellessetkey()
c'est rapide. Lorsqu'aucune clé n'est définie, ou que nous groupons dans un ordre différent de celui de la clé, nous l'appelons un par ad hoc.3.3 Pourquoi le regroupement par colonnes dans la clé est-il plus rapide qu'un ad hoc par?
Parce que chaque groupe est contigu en RAM, minimisant ainsi les récupérations de pages, et la mémoire peut être copiée en masse (
memcpy
en C) plutôt qu'en boucle en C.
À partir de là, je suppose que la définition d'une clé permet en quelque sorte à R d'utiliser le "tri par base" sur d'autres algorithmes, et c'est pourquoi il est plus rapide.
Le guide de démarrage rapide de 10 minutes contient également un guide sur les clés.
- Clés
Commençons par considérer data.frame, spécifiquement les noms de lignes (ou en anglais, les noms de lignes). Autrement dit, les noms multiples appartenant à une seule ligne. Les noms multiples appartenant à la seule ligne? Ce n'est pas ce à quoi nous sommes habitués dans un data.frame. Nous savons que chaque ligne a au plus un nom. Une personne a au moins deux noms, un premier nom et un deuxième nom. Cela est utile pour organiser un annuaire téléphonique, par exemple, qui est trié par nom, puis par premier nom. Cependant, chaque ligne d'un data.frame ne peut avoir qu'un seul nom.
Une clé se compose d'une ou plusieurs colonnes de noms de lignes, qui peuvent être des nombres entiers, des facteurs, des caractères ou une autre classe, pas simplement des caractères. De plus, les lignes sont triées par clé. Par conséquent, un data.table peut avoir au plus une clé, car il ne peut pas être trié de plusieurs manières.
L'unicité n'est pas appliquée, c'est-à-dire que les valeurs de clé en double sont autorisées. Puisque les lignes sont triées par clé, tous les doublons dans la clé apparaîtront consécutivement
L'annuaire téléphonique a été utile pour comprendre ce qu'est une clé, mais il semble qu'une clé ne soit pas différente par rapport à une colonne de facteurs. De plus, il n'explique pas pourquoi une clé est nécessaire (notamment pour utiliser certaines fonctions) et comment choisir la colonne à définir comme clé. De plus, il semble que dans une data.table avec le temps comme colonne, définir une autre colonne comme clé gâcherait probablement la colonne de temps aussi, ce qui la rend encore plus déroutante car je ne sais pas si je suis autorisé à définir une autre colonne comme clé. Quelqu'un peut-il m'éclairer s'il vous plaît?
la source
Réponses:
Mise à jour mineure: veuillez également vous référer aux nouvelles vignettes HTML . Ce numéro met en évidence les autres vignettes que nous prévoyons de réaliser.
J'ai à nouveau mis à jour cette réponse (février 2016) à la lumière de la nouvelle
on=
fonctionnalité qui permet également les jointures ad hoc . Consultez l'historique pour les réponses précédentes (obsolètes).Que fait exactement
setkey(DT, a, b)
?Il fait deux choses:
DT
par la ou les colonnes fournies ( a , b ) par référence , toujours par ordre croissant .sorted
àDT
.La réorganisation est à la fois rapide (grâce au tri de base interne de data.table ) et efficace en mémoire (une seule colonne supplémentaire de type double est allouée).
Quand est-ce
setkey()
nécessaire?Pour les opérations de regroupement, cela
setkey()
n'a jamais été une exigence absolue. Autrement dit, nous pouvons effectuer un cold by ou adhoc-by .Cependant, avant
v1.9.6
, les jointures de la formex[i]
doiventkey
être activéesx
. Avec le nouvelon=
argument de la v1.9.6 + , ce n'est plus vrai, et la définition des clés n'est donc pas non plus une exigence absolue ici.Notez que l'
on=
argument peut être spécifié explicitement même pour leskeyed
jointures.Alors, quelle est la raison de la mise en œuvre de l'
on=
argument?Il y a plusieurs raisons.
Il permet de distinguer clairement l'opération comme une opération impliquant deux tables de données . Le simple fait de faire
X[Y]
ne distingue pas cela non plus, bien que cela puisse être clair en nommant les variables de manière appropriée.Il permet également de comprendre les colonnes sur lesquelles la jointure / sous-ensemble est effectuée immédiatement en regardant cette ligne de code (et sans avoir à retracer la
setkey()
ligne correspondante ).Dans les opérations où des colonnes sont ajoutées ou mises à jour par référence , les
on=
opérations sont beaucoup plus performantes car il n'est pas nécessaire de réorganiser la totalité de la table data.table juste pour ajouter / mettre à jour des colonnes. Par exemple,Dans le second cas, nous n'avons pas eu à commander à nouveau. Il ne s'agit pas de calculer la commande qui prend du temps, mais de réorganiser physiquement la table data.table dans la RAM, et en l'évitant, nous conservons l'ordre d'origine, et il est également performant.
Même autrement, à moins que vous n'effectuiez des jointures de manière répétitive, il ne devrait y avoir aucune différence de performances notable entre une jointure à clé et une jointure ad hoc .
Cela conduit à la question, quel avantage la saisie d'une table data.table a- t -elle plus?
Y a-t-il un avantage à saisir une table data.table?
La saisie d'une table data.table la réorganise physiquement en fonction de ces colonnes dans la RAM. Le calcul de la commande n'est généralement pas la partie qui prend du temps, mais plutôt la réorganisation elle-même. Cependant, une fois les données triées en RAM, les lignes appartenant au même groupe sont toutes contiguës en RAM, et sont donc très efficaces en cache. C'est le tri qui accélère les opérations sur les tables de données à clé.
Il est donc essentiel de déterminer si le temps passé à réorganiser l'ensemble de la table data.table vaut le temps de faire une jointure / agrégation efficace du cache. Habituellement, à moins que des opérations de regroupement / jointure répétitives soient effectuées sur la même table de données à clé , il ne devrait pas y avoir de différence notable.
Question: Selon vous, quelles seraient les performances par rapport à une jointure à clé , si vous l'utilisez
setorder()
pour réorganiser la table data.table et l'utiliseron=
? Si vous avez suivi jusqu'à présent, vous devriez être en mesure de le comprendre :-).la source
DT[J(1e4:1e5)]
vraiment équivalent àDF[DF$x > 1e4 & DF$x < 1e5, ]
? Pouvez-vous m'indiquer ce que celaJ
signifie? De plus, cette recherche ne renvoie aucune ligne car ellesample(1e4, 1e7, TRUE)
n'inclut pas les nombres supérieurs à 1e4.>=
et<=
- corrigé.J
(et.
) sont des alias delist
(c'est- à -dire qu'ils sont équivalents). En interne, quandi
est une liste, elle est convertie en data.table à la suite de laquelle la recherche binaire est utilisée pour calculer les indices de ligne. Corrigé1e4
pour1e5
éviter toute confusion. Merci d'avoir repéré. Notez que nous pouvonson=
maintenant utiliser directement l' argument pour effectuer des sous-ensembles binaires plutôt que de définir la clé. En savoir plus sur les nouvelles vignettes HTML . Et gardez un œil sur cette page pour les vignettes pour les jointures.Une clé est essentiellement un index dans un ensemble de données, ce qui permet des opérations de tri, de filtrage et de jointure très rapides et efficaces. Ce sont probablement les meilleures raisons d'utiliser des tables de données au lieu de blocs de données (la syntaxe d'utilisation des tables de données est également beaucoup plus conviviale, mais cela n'a rien à voir avec les clés).
Si vous ne comprenez pas les index, considérez ceci: un annuaire téléphonique est «indexé» par nom. Donc, si je veux rechercher le numéro de téléphone de quelqu'un, c'est assez simple. Mais supposons que je veuille rechercher par numéro de téléphone (par exemple, rechercher qui a un numéro de téléphone particulier)? À moins que je ne puisse «réindexer» l'annuaire téléphonique par numéro de téléphone, cela prendra beaucoup de temps.
Prenons l'exemple suivant: supposons que j'ai une table, ZIP, de tous les codes postaux aux États-Unis (> 33 000) avec les informations associées (ville, état, population, revenu médian, etc.). Si je veux rechercher les informations pour un code postal spécifique, la recherche (filtre) est environ 1000 fois plus rapide si je suis d'
setkey(ZIP,zipcode)
abord.Un autre avantage concerne les jointures. Supposons que vous ayez une liste de personnes et leurs codes postaux dans une table de données (appelez-la "PPL"), et que je veuille ajouter des informations de la table ZIP (par exemple, ville, état, etc.). Le code suivant le fera:
Il s'agit d'une "jointure" dans le sens où je joins les informations de 2 tables basées dans un champ commun (code postal). Les jointures comme celle-ci sur de très grandes tables sont extrêmement lentes avec les trames de données et extrêmement rapides avec les tables de données. Dans un exemple réel, j'ai dû faire plus de 20 000 jointures comme celle-ci sur une table complète de codes postaux. Avec les tables de données, le script a duré environ 20 minutes. courir. Je ne l'ai même pas essayé avec des trames de données car cela aurait pris plus de 2 semaines.
À mon humble avis, vous ne devriez pas seulement lire mais étudier la FAQ et le matériel d'introduction. C'est plus facile à comprendre si vous avez un problème réel auquel appliquer cela.
[Réponse au commentaire de @ Frank]
Re: tri vs indexation - Sur la base de la réponse à cette question , il semble que
setkey(...)
réorganise en fait les colonnes de la table (par exemple, un tri physique), et ne crée pas d'index au sens de la base de données. Cela a des implications pratiques: d'une part, si vous définissez la clé dans une table avecsetkey(...)
, puis modifiez l'une des valeurs de la colonne clé, data.table déclare simplement que la table n'est plus triée (en désactivant l'sorted
attribut); il ne réindexe pas dynamiquement pour maintenir le bon ordre de tri (comme cela se produirait dans une base de données). De plus, "supprimer la clé" à l'aide desetky(DT,NULL)
ne restaure pas la table dans son ordre d'origine non trié.Re: filter vs join - la différence pratique est que le filtrage extrait un sous-ensemble d'un seul ensemble de données, tandis que join combine les données de deux ensembles de données basés sur un champ commun. Il existe de nombreux types de jointures (interne, externe, gauche). L'exemple ci-dessus est une jointure interne (seuls les enregistrements avec des clés communes aux deux tables sont renvoyés), et cela présente de nombreuses similitudes avec le filtrage.
la source
setkey
réorganise les lignes de manière irréversible. Si c'est uniquement à des fins d'affichage, comment puis-je imprimer les dix premières lignes selon le "vrai" ordre (que j'aurais vu avant setkey)? Je suis presque sûr desetkey(DT,NULL)
ne pas faire ça ... (suite)X[Y,...]
, vous devez "filtrer" les lignes de X en utilisant la clé. Certes, d'autres choses se produisent après cela (les colonnes de Y sont mises à disposition, et il y a un par-sans-par implicite), mais je ne vois toujours pas cela comme un avantage conceptuellement distinct. Je suppose que votre réponse concerne les opérations que vous voudrez peut-être faire, cependant, là où la distinction peut être utile.setkey(DT,NULL)
la clé mais n'affecte pas l'ordre de tri. A posé une question à ce sujet ici . Voyons voir.