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


Données {#data}

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)

Introduction

Dans cette vignette, nous allons

1. Indices secondaires

a) Qu'est-ce qu'un indice secondaire ?

Les indices secondaires sont similaires aux clés dans data.table, à l'exception de deux différences majeures :

b) Définir et obtenir des indices secondaires

-- Comment définir la colonne 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))

-- Comment obtenir tous les indices secondaires définis jusqu'à présent dans flights ?

indices(flights)

setindex(flights, origin, dest)
indices(flights)

c) Pourquoi avons-nous besoin d'indices secondaires ?

-- La réorganisation d'une table de données peut être coûteuse et n'est pas toujours idéale

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.

-- Il ne peut y avoir qu'une seule clé au maximum

Maintenant, 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 !

-- Les indices secondaires peuvent être réutilisés

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à.

-- Le nouvel argument on permet une syntaxe plus propre ainsi que la création et la réutilisation automatiques d'indices secondaires

Comme nous le verrons dans la section suivante, l'argument on présente plusieurs avantages :

Argument on

2. Sous-ensemble rapide utilisant l'argument on et les indices secondaires

a) Sous-ensembles rapides dans i

-- Sous-ensemble de toutes les lignes où l'aéroport d'origine correspond à "JFK" en utilisant on

flights["JFK", on = "origin"]

## ou alors
# flights[.("JFK"), on = "origin"] (or)
# flights[list("JFK"), on = "origin"]

-- Comment puis-je faire un sous-ensemble basé sur les colonnes 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]

b) Sélection dans 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.

-- Retourner la colonne arr_delay seule en tant que data.table correspondant à origin = "LGA" et dest = "TPA"

flights[.("LGA", "TPA"), .(arr_delay), on = c("origin", "dest")]

c) Chaînage

-- Sur la base du résultat obtenu ci-dessus, utilisez le chaînage pour classer la colonne par ordre décroissant.

flights[.("LGA", "TPA"), .(arr_delay), on = c("origin", "dest")][order(-arr_delay)]

d) Calculer ou do dans j

-- Trouvez le délai d'arrivée maximal correspondant à origin = "LGA" et dest = "TPA".

flights[.("LGA", "TPA"), max(arr_delay), on = c("origin", "dest")]

e) sous-assignation par référence en utilisant := 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))]

f) Agrégation à l'aide de by

-- Obtenir le retard maximum au départ pour chaque mois correspondant à origine = "JFK". Classer les résultats par mois

ans <- flights["JFK", max(dep_delay), keyby = month, on = "origin"]
head(ans)

g) L'argument mult

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.

-- Sous-ensemble contenant uniquement la première ligne correspondante où dest correspond à "BOS" et "DAY"

flights[c("BOS", "DAY"), on = "dest", mult = "first"]

-- Sous-ensemble contenant uniquement la dernière ligne correspondante où origin correspond à "LGA", "JFK", "EWR" et dest correspond à "XNA"

flights[.(c("LGA", "JFK", "EWR"), "XNA"), on = c("origin", "dest"), mult = "last"]

h) L'argument nomatch

Nous pouvons choisir si les requêtes qui ne correspondent pas doivent retourner NA ou être ignorées en utilisant l'argument nomatch.

-- D'après l'exemple précédent, le sous-ensemble de toutes les lignes n'est pris en compte que s'il y a une correspondance

flights[.(c("LGA", "JFK", "EWR"), "XNA"), mult = "last", on = c("origin", "dest"), nomatch = NULL]

3. Indexation automatique

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

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)


Rdatatable/data.table documentation built on Nov. 20, 2024, 6:44 p.m.