require(data.table) knitr::opts_chunk$set( comment = "#", error = FALSE, tidy = FALSE, cache = FALSE, collapse = TRUE) .old.th = setDTthreads(1)
Cette vignette suppose que le lecteur est familier avec la syntaxe [i, j, by]
de data.table, et sur la façon d’effectuer des sous-ensembles basés sur des clés rapides. Si vous n'êtes pas familier avec ces concepts, veuillez d'abord lire les vignettes "Introduction à data.table", "Sémantique de référence" et "Sous-ensembles basés sur les clés et la recherche binaire rapide".
Nous utiliserons les mêmes données flights
que dans la vignette "Introduction à data.table".
options(width = 100L)
flights <- fread("../flights14.csv") head(flights) dim(flights)
Dans cette vignette, nous allons
discuter des indices secondaires et justifie leur nécessité en citant des cas où l'établissement de clés n'est pas nécessairement idéal,
effectuer un sous-ensemble rapide, une fois de plus, mais en utilisant le nouvel argument on
, qui calcule des indices secondaires en interne pour la tâche (temporairement), et les réutilise s'il en existe déjà un,
et enfin, explorer l’auto-indexation qui va plus loin et crée des indices secondaires automatiquement, mais en utilisant la syntaxe native de R pour le sous-ensemble.
Les indices secondaires sont similaires aux clés
dans data.table, à l'exception de deux différences majeures :
Il ne réorganise pas physiquement l'ensemble de la table de données en RAM. Au lieu de cela, il calcule uniquement l'ordre pour l'ensemble des colonnes fournies et stocke ce vecteur d'ordre dans un attribut supplémentaire appelé index
.
Il peut y avoir plus d'un index secondaire pour une table de données (comme nous le verrons plus loin).
origin
comme index secondaire dans l’objet data.table flights
?setindex(flights, origin) head(flights) ## nous pouvons aussi fournir des chaînes de caractères à la fonction ‘setindexv()’ # setindexv(flights, "origin") # utile en programmation # attribut 'index' ajouté names(attributes(flights))
setindex
et setindexv()
permettent d'ajouter un index secondaire à data.table.
Notez que flights
n'est pas physiquement réordonné dans l'ordre croissant de origin
, comme cela aurait été le cas avec setkey()
.
Notez également que l'attribut index
a été ajouté à flights
.
setindex(flights, NULL)
supprimerait tous les indices secondaires.
flights
?indices(flights) setindex(flights, origin, dest) indices(flights)
La fonction indices()
renvoie tous les indices secondaires actuels dans la table data.table. Si aucun n'existe, NULL
est retourné.
Notez qu'en créant un autre index sur les colonnes origin, dest
, nous ne perdons pas le premier index créé sur la colonne origin
, c'est-à-dire que nous pouvons avoir plusieurs index secondaires.
Considérons le cas où vous voudriez effectuer un sous-ensemble basé sur une clé rapide sur la colonne origin
pour la valeur "JFK". Nous ferions cela comme suit :
## pas exécuté setkey(flights, origin) flights["JFK"] # or flights[.("JFK")]
setkey()
nécessite de :a) calculer le vecteur d'ordre pour la (les) colonne(s) fournie(s), ici, origin
, et
b) réordonner l'ensemble du tableau de données, par référence, sur la base du vecteur d'ordre calculé.
Le calcul de l'ordre n'est pas la partie qui prend le plus de temps, puisque data.table utilise un vrai tri radix sur les vecteurs d'entiers, de caractères et de nombres. Cependant, réordonner le tableau data.table peut prendre du temps (en fonction du nombre de lignes et de colonnes).
À moins que notre tâche n'implique un sous-ensemble répété sur la même colonne, le sous-ensemble basé sur une clé rapide pourrait effectivement être annulé par le temps nécessaire pour réorganiser, en fonction des dimensions de notre data.table.
clé
au maximumMaintenant, si nous voulons répéter la même opération mais sur la colonne dest
à la place, pour la valeur "LAX", alors nous devons utiliser setkey()
, une fois de plus.
## pas exécuté setkey(flights, dest) flights["LAX"]
Et cela réordonne les vols
par dest
, encore une fois. Ce que nous aimerions vraiment, c'est pouvoir effectuer le sous-ensemble rapidement en éliminant l'étape de réorganisation.
Et c'est précisément ce que permettent les indices secondaires !
Comme il peut y avoir plusieurs indices secondaires et que la création d'un indice est aussi simple que le stockage du vecteur d'ordre en tant qu'attribut, cela nous permet même d'éliminer le temps nécessaire pour recalculer le vecteur d'ordre si un indice existe déjà.
on
permet une syntaxe plus propre ainsi que la création et la réutilisation automatiques d'indices secondairesComme nous le verrons dans la section suivante, l'argument on
présente plusieurs avantages :
on
permet d’effectuer des sous-ensembles en calculant les indices secondaires à la volée. Cela évite d'avoir à faire setindex()
à chaque fois.
permet de réutiliser facilement les indices existants en vérifiant simplement les attributs.
permet une syntaxe plus propre en intégrant dans la syntaxe les colonnes sur lesquelles le sous-ensemble est effectué. Le code est ainsi plus facile à suivre lorsqu'on le consulte ultérieurement.
Notez que l'argument on
peut également être utilisé pour les sous-ensembles à clés. En fait, nous encourageons à fournir l'argument on
même lorsque le sous-ensemble utilise des clés pour une meilleure lisibilité.
on
et les indices secondairesi
on
flights["JFK", on = "origin"] ## ou alors # flights[.("JFK"), on = "origin"] (or) # flights[list("JFK"), on = "origin"]
Cette instruction effectue également une recherche binaire rapide basée sur le sous-ensemble, en calculant l'index à la volée. Cependant, notez qu'elle n'enregistre pas automatiquement l'index en tant qu'attribut. Cela pourrait changer à l'avenir.
Si nous avions déjà créé un index secondaire en utilisant setindex()
, alors on
le réutiliserait au lieu de le (re)calculer. Nous pouvons le voir en utilisant verbose = TRUE
:
r
setindex(flights, origin)
flights["JFK", on = "origin", verbose = TRUE][1:5]
origin
et dest
?Par exemple, si nous voulons un sous-ensemble combinant c("JFK", "LAX")
, alors :
flights[.("JFK", "LAX"), on = c("origin", "dest")][1:5]
l’argument on
accepte un vecteur de caractères de noms de colonnes correspondant à l'ordre fourni à i-argument
.
Comme le temps de calcul de l'index secondaire est assez faible, nous n'avons pas besoin d'utiliser setindex()
, sauf si, une fois de plus, la tâche implique un sous-ensemble répété sur la même colonne.
j
Toutes les opérations que nous allons discuter ci-dessous ne sont pas différentes de celles que nous avons déjà vues dans la vignette Clé et recherche binaire rapide basée sur un sous-ensemble. Sauf que nous utiliserons l'argument on
au lieu de définir des clés.
arr_delay
seule en tant que data.table correspondant à origin = "LGA"
et dest = "TPA"
flights[.("LGA", "TPA"), .(arr_delay), on = c("origin", "dest")]
flights[.("LGA", "TPA"), .(arr_delay), on = c("origin", "dest")][order(-arr_delay)]
j
origin = "LGA"
et dest = "TPA"
.flights[.("LGA", "TPA"), max(arr_delay), on = c("origin", "dest")]
:=
dans j
Nous avons déjà vu cet exemple dans les vignettes Sémantique des références et Clé et sous-ensemble basé sur la recherche binaire rapide. Regardons toutes les heures
disponibles dans le data.table flights
:
# récupère toutes les 'hours' de flights flights[, sort(unique(hour))]
Nous constatons qu'il y a au total 25
valeurs uniques dans les données. Les heures 0 et 24 semblent être présentes. Remplaçons 24 par 0, mais cette fois-ci en utilisant on
au lieu de définir des clés.
flights[.(24L), hour := 0L, on = "hour"]
Maintenant, vérifions si 24
est remplacé par 0
dans la colonne hour
.
flights[, sort(unique(hour))]
hour
, nous devions utiliser setkey()
sur celui-ci, ce qui réorganisait inévitablement l'ensemble de la data.table. Avec on
, l'ordre est préservé, et l'opération est beaucoup plus rapide ! En inspectant le code, la tâche que nous voulions effectuer est également assez claire.by
mois
correspondant à origine = "JFK"
. Classer les résultats par mois
ans <- flights["JFK", max(dep_delay), keyby = month, on = "origin"] head(ans)
key
à origin, dest
, si nous n'avions pas utilisé on
qui construit en interne des index secondaires à la volée.Les autres arguments, y compris mult
, fonctionnent exactement de la même manière que nous l'avons vu dans la vignette Keys and fast binary search based subset. La valeur par défaut de mult
est "all". Nous pouvons choisir de ne renvoyer que les "premières" ou "dernières" lignes correspondantes.
dest
correspond à "BOS" et "DAY"flights[c("BOS", "DAY"), on = "dest", mult = "first"]
origin
correspond à "LGA", "JFK", "EWR" et dest
correspond à "XNA"flights[.(c("LGA", "JFK", "EWR"), "XNA"), on = c("origin", "dest"), mult = "last"]
Nous pouvons choisir si les requêtes qui ne correspondent pas doivent retourner NA
ou être ignorées en utilisant l'argument nomatch
.
flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult = "last", on = c("origin", "dest"), nomatch = NULL]
Dans un premier temps, nous avons étudié comment effectuer un sous-ensemble rapide à l'aide d'une recherche binaire en utilisant des clés. Ensuite, nous avons découvert que nous pouvions améliorer encore les performances et avoir une syntaxe plus propre en utilisant des indices secondaires.
C'est ce que fait l'indexation automatique. Pour l'instant, il n'est implémenté que pour les opérateurs binaires ==
et %in%
. Un indice est automatiquement créé et sauvegardé en tant qu'attribut. C'est-à-dire que contrairement à l'argument on
qui calcule l'indice à la volée à chaque fois (à moins qu'il n'en existe déjà un), un indice secondaire est créé ici.
Commençons par créer un tableau data.table suffisamment grand pour mettre en évidence l'avantage.
set.seed(1L) dt = data.table(x = sample(1e5L, 1e7L, TRUE), y = runif(100L)) print(object.size(dt), units = "Mb")
Lorsque nous utilisons ==
ou %in%
sur une seule colonne pour la première fois, un indice secondaire est créé automatiquement, et il est utilisé pour effectuer le sous-ensemble.
## inspection de tous les noms d’attributs names(attributes(dt)) ## première exécution (t1 <- system.time(ans <- dt[x == 989L])) head(ans) ## indice secondaire créé names(attributes(dt)) indices(dt)
Le temps nécessaire pour créer un sous-ensemble la première fois est égal au temps nécessaire pour créer l'indice + le temps nécessaire pour créer un sous-ensemble. Étant donné que la création d'un indice secondaire n'implique que la création du vecteur d'ordre, cette opération combinée est plus rapide que les balayages vectoriels dans de nombreux cas. Mais le véritable avantage réside dans les sous-ensembles successifs. Ils sont extrêmement rapides.
## sous-ensembles successifs (t2 <- system.time(dt[x == 989L])) system.time(dt[x %in% 1989:2012])
L'exécution la première fois a pris r sprintf("%.3f", t1["elapsed"])
secondes tandis que la deuxième fois, elle a pris r sprintf("%.3f", t2["elapsed"])
secondes.
L'indexation automatique peut être désactivée en définissant l'argument global options(datatable.auto.index = FALSE)
.
Désactiver l'indexation automatique permet toujours d'utiliser les index créés explicitement avec setindex
ou setindexv
. Vous pouvez désactiver complètement les index en définissant l'argument global options(datatable.use.index = FALSE)
.
Dans la version récente, nous avons étendu l'indexation automatique aux expressions impliquant plus d'une colonne (combinées avec l'opérateur &
). Dans le futur, nous prévoyons d'étendre la recherche binaire à d'autres opérateurs binaires comme <
, <=
, >
et >=
.
Nous aborderons les sous-ensembles rapides utilisant des clés et des indices secondaires pour les joints dans la prochaine vignette, "Joints et jointures roulantes".
setDTthreads(.old.th)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.