require(data.table) knitr::opts_chunk$set( comment = "#", error = FALSE, tidy = FALSE, cache = FALSE, collapse = TRUE) .old.th = setDTthreads(1)
Cette vignette traite de la sémantique de référence de data.table qui permet d'ajouter, de mettre à jour ou de supprimer des colonnes d'un data.table par référence, ainsi que de les combiner avec i
et by
. Elle s'adresse à ceux qui sont déjà familiers avec la syntaxe de data.table, avec sa forme générale, avec la façon de filtrer des lignes avec i
, de sélectionner et calculer sur des colonnes, et d'effectuer des agrégations par groupe. Si vous n'êtes pas familier avec ces concepts, veuillez d'abord lire la vignette "Introduction à data.table ".
Nous utiliserons les mêmes données flights
que dans la vignette "Introduction à data.table".
options(with = 100L)
flights <- fread("../flights14.csv") flights dim(flights)
Dans cette vignette, nous allons
d’abord discuter brièvement les sémantiques de référence et examiner les deux formes différentes pour lesquelles l’opérateur :=
peut être utilisé
ensuite, voir comment ajouter/mettre à jour/supprimer des colonnes par référence dans j
en utilisant l'opérateur :=
et comment le combiner avec i
et by
.
et enfin, nous examinerons l'utilisation de :=
pour ses effets secondaires et comment nous pouvons éviter ces effets secondaires en utilisant copy()
.
Toutes les opérations que nous avons vues jusqu'à présent dans la vignette précédente ont abouti à un nouveau jeu de données. Nous allons voir comment ajouter de nouvelles colonnes, mettre à jour ou supprimer des colonnes existantes sur les données originales.
Avant d'examiner la sémantique de référence, considérons le data.frame ci-dessous :
DF = data.frame(ID = c("b", "b", "b", "a", "a", "c"), a = 1:6, b = 7:12, c = 13:18) DF
Quand nous faisions :
DF$c <- 18:13 # (1) -- remplacer toute une colonne # ou DF$c[DF$ID == "b"] <- 15:13 # (2) -- sous-assignation dans la colonne 'c'
À la fois (1) et (2) ont tous deux entraîné une copie profonde de l'ensemble du data.frame
dans les versions de R < 3.1. Ces version copiaient plus d’une fois. Pour améliorer les performances en évitant ces copies redondantes, data.table a utilisé l'opérateur :=
disponible mais inutilisé dans R.
D’importantes améliorations de performance ont été réalisées dans R v3.1
, à la suite desquelles seule une copie superficielle est faite pour (1) et non une copie profonde. Cependant, pour (2), la colonne entière est encore copiée en profondeur même dans R v3.1+
. Cela signifie que plus on effectue de sous-assignations de colonnes dans une même requête, plus R fait de copies profondes.
Une copie superficielle consiste uniquement en une copie du vecteur de pointeurs de colonnes (correspondant aux colonnes d'un data.frame ou d'un data.table). Les données réelles ne sont pas physiquement copiées en mémoire.
Une copie profonde, en revanche, copie l'intégralité des données à un autre emplacement en mémoire.
Lorsque l'on utilise i
(par exemple, DT[1:10]
) pour sélectionner des lignes dans une data.table, une copie profonde est effectuée. Cependant, lorsque i
n'est pas fourni ou est égal à TRUE
, une copie superficielle est faite.
Avec l'opérateur :=
de data.table, absolument aucune copie n'est effectuée dans les deux cas (1) et (2), quelle que soit la version de R que vous utilisez. Cela s’explique par le fait que l’opérateur :=
met à jour les colonnes de data.table en place (par référence).
:=
Il peut être utilisé dans j
de deux façons :
(a) La forme LHS := RHS
(côté gauche := côté droit)
DT[, c("colA", "colB", ...) := list(valA, valB, ...)] # lorsque vous n'avez qu'une seule colonne à assigner # vous pouvez omettre les guillemets et `list(), pour plus de commodité DT[, colA := valA]
(b) La forme fonctionnelle
DT[, `:=`(colA = valA, # valA est assigné à colA colB = valB, # valB est assigné à colB ... )]
Notez que le code ci-dessus explique comment :=
peut être utilisé. Ce ne sont pas des exemples pratiques. Nous en proposerons un premier avec le data.table flights
dans la section suivante.
Dans (a), LHS
prend un vecteur de caractères de noms de colonnes et RHS
une liste de valeurs. RHS
doit juste être un objet list
, indépendamment de la façon dont elle est générée (par exemple, en utilisant lapply()
, list()
, mget()
, mapply()
, etc.) Cette forme est généralement facile à programmer et est particulièrement utile lorsque vous ne connaissez pas à l'avance les colonnes auxquelles attribuer des valeurs.
En revanche, le point (b) est pratique si vous souhaitez commenter votre code (voir exemple sur flights
).
Le résultat est renvoyé de manière invisible.
Puisque :=
est disponible dans j
, nous pouvons le combiner avec les opérations i
et by
tout comme les opérations d'agrégation que nous avons vues dans la vignette précédente.
Dans les deux formes de :=
présentées ci-dessus, notez que nous n'assignons pas le résultat à une variable, parce que nous n'en avons pas besoin. La data.table en entrée est modifiée par référence. Prenons des exemples pour comprendre ce que nous entendons par là.
Pour la suite de cette vignette, nous travaillerons avec la data.table flights
.
flights
?flights[, `:=`(speed = distance / (air_time/60), # vitesse en mph (mi/h) delay = arr_delay + dep_delay)] # retard en minutes head(flights) ## ou alors, en utilisant la forme 'LHS := RHS' # flights[, c("speed", "delay") := list(distance/(air_time/60), arr_delay + dep_delay)]
Nous n'avons pas eu à réaffecter le résultat à flights
.
La data.table flights
contient maintenant les deux colonnes nouvellement ajoutées. C'est ce que nous entendons par ajouté par référence.
Nous avons utilisé la forme fonctionnelle pour pouvoir ajouter des commentaires sur le côté afin d'expliquer ce que fait le calcul. Vous pouvez également voir la forme LHS := RHS
(en commentaire).
Examinons toutes les heures (hours
) disponibles dans la data.table flights
:
# récupère toutes les heures 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 toutes les deux être présentes. Remplaçons 24 par 0.
hour == 24
par la valeur 0
# sous-assignation par référence flights[hour == 24L, hour := 0L]
Nous pouvons utiliser i
avec :=
dans j
de la même manière que nous l'avons déjà vu dans la vignette "Introduction à data.table ".
La colonne hour
est remplacée par 0
uniquement sur les indices de ligne où la condition hour == 24L
spécifiée dans i
est évaluée à TRUE
.
:=
renvoie le résultat de manière invisible. Parfois, il peut être nécessaire de voir le résultat après l'affectation. Nous pouvons y parvenir en ajoutant des crochets vides []
à la fin de la requête, comme indiqué ci-dessous :
r
flights[hour == 24L, hour := 0L][]
Regardons toutes les heures pour vérifier.
# vérifier à nouveau la présence de '24' flights[, sort(unique(hour))]
Quelle est la différence entre flights[hour == 24L, hour := 0L]
et flights[hour == 24L][, hour := 0L]
? Indice : le dernier a besoin d'une affectation (<-
) si vous voulez utiliser le résultat plus tard.
Si vous ne parvenez pas à le comprendre, consultez la section Note
de ?":="
.
delay
flights[, c("delay") := NULL] head(flights) ## ou en utilisant la forme fonctionnelle # flights[, `:=`(delay = NULL)]
Assigner NULL
à une colonne supprime cette colonne. Et cela se produit instantanément.
Nous pouvons également passer des numéros de colonnes au lieu de noms dans le membre de gauche (LHS
), bien qu'il soit de bonne pratique de programmation d'utiliser des noms de colonnes.
Lorsqu'il n'y a qu'une seule colonne à supprimer, nous pouvons omettre le c()
et les guillemets doubles et simplement utiliser le nom de la colonne sans guillemets, pour plus de commodité. C'est-à-dire :
r
flights[, delay := NULL]
est équivalent au code ci-dessus.
:=
avec regroupement utilisant by
{#ref-j-by}Nous avons déjà vu l'utilisation de i
avec :=
dans la [Section 2b] (#ref-i-j). Voyons maintenant comment nous pouvons utiliser :=
avec by
.
orig,dest
la vitesse maximale ?flights[, max_speed := max(speed), by = .(origin, dest)] head(flights)
Nous ajoutons une nouvelle colonne max_speed
en utilisant l'opérateur :=
par référence.
Nous fournissons les colonnes pour le regroupement de la même manière qu’indiqué dans la vignette Introduction à data.table. Pour chaque groupe, max(speed)
est calculé, ce qui renvoie une seule valeur. Cette valeur est recyclée pour s'adapter à la longueur du groupe. Encore une fois, aucune copie n'est faite. La data.table flights
est modifié directement « sur place ».
Nous aurions également pu fournir à by
un vecteur de caractères comme nous l'avons vu dans la vignette Introduction à data.table, par exemple en utilisant by = c("origin", "dest")
.
:=
max()
de dep_delay
et arr_delay
pour chaque mois, en utilisant .SD
?in_cols = c("dep_delay", "arr_delay") out_cols = c("max_dep_delay", "max_arr_delay") flights[, c(out_cols) := lapply(.SD, max), by = month, .SDcols = in_cols] head(flights)
Nous utilisons la forme LHS := RHS
. Nous stockons les noms des colonnes d'entrée et les nouvelles colonnes à ajouter dans des variables séparées, puis les fournissons à .SDcols
et à LHS
(pour une meilleure lisibilité).
Notez que puisque nous autorisons l'assignation par référence sans mettre les noms de colonnes entre guillemets lorsqu'il n'y a qu'une seule colonne comme expliqué dans la Section 2c, nous ne pouvons pas faire out_cols := lapply(.SD, max)
. Cela rajouterait une nouvelle colonne nommée out_col
. À la place, nous devrions utiliser soit c(out_cols)
, soit simplement (out_cols)
. Envelopper le nom de la variable dans des parenthèses (
est suffisant pour différencier les deux cas.
La forme LHS := RHS
nous permet d'opérer sur plusieurs colonnes. Dans le membre de droite (RHS), pour calculer le max
sur les colonnes spécifiées dans .SDcols
, nous utilisons la fonction de base lapply()
avec .SD
de la même manière que nous l'avons vu précédemment dans la vignette "Introduction to data.table ". Ceci renvoie une liste de deux éléments, contenant la valeur maximale correspondant à dep_delay
et arr_delay
pour chaque groupe.
Avant de passer à la section suivante, nettoyons les colonnes nouvellement créées speed
, max_speed
, max_dep_delay
et max_arr_delay
.
# RHS est automatiquement recyclé à la longueur de LHS flights[, c("speed", "max_speed", "max_dep_delay", "max_arr_delay") := NULL] head(flights)
.SD
?flights[, names(.SD) := lapply(.SD, as.factor), .SDcols = is.character]
Nettoyons à nouveau et convertissons nos colonnes de facteurs nouvellement créées en colonnes de caractères. Cette fois, nous allons utiliser .SDcols
qui accepte une fonction pour décider quelles colonnes inclure. Dans ce cas, is.factor()
retournera les colonnes qui sont des facteurs. Pour en savoir plus sur le Sous-ensemble des Données (Subset of the Data), il y a aussi une vignette sur l’utilisation de SD.
Parfois, il est également utile de garder une trace des colonnes que nous transformons. Ainsi, même après avoir converti nos colonnes, nous pourrons toujours appeler les colonnes spécifiques que nous avons mises à jour.
factor_cols <- sapply(flights, is.factor) flights[, names(.SD) := lapply(.SD, as.character), .SDcols = factor_cols] str(flights[, ..factor_cols])
(factor_cols)
sur le membre de gauche (LHS
) au lieu de names(.SD)
.:=
et copy()
:=
modifie l'objet d'entrée par référence. En dehors des fonctionnalités que nous avons déjà discutées, il arrive parfois que nous souhaitions utiliser la fonctionnalité de mise à jour par référence pour ses effets secondaires. À d’autres moments, il n'est pas souhaitable de modifier l'objet original, auquel cas nous pouvons utiliser la fonction copy()
, comme nous le verrons dans un instant.
:=
pour ses effets secondairesSupposons que nous voulions créer une fonction qui renvoie la vitesse maximale (maximum speed) pour chaque mois. Mais en même temps, nous aimerions aussi ajouter la colonne speed
à flights. Nous pourrions écrire une petite fonction comme suit :
foo <- function(DT) { DT[, speed := distance / (air_time/60)] DT[, .(max_speed = max(speed)), by = month] } ans = foo(flights) head(flights) head(ans)
Notez que la nouvelle colonne speed
a été ajoutée à la data.table flights
. C'est parce que :=
effectue des opérations par référence. Puisque DT
(l'argument de la fonction) et flights
font référence au même objet en mémoire, la modification de DT
se répercute également sur flights
.
Et ans
contient la vitesse maximale pour chaque mois.
copy()
Dans la section précédente, nous avons utilisé :=
pour son effet secondaire. Mais bien sûr, ce n'est pas toujours souhaitable. Parfois, nous voudrions passer un objet data.table à une fonction, et nous pourrions vouloir utiliser l'opérateur :=
, mais ne voudrions pas mettre à jour l'objet original. Nous pouvons accomplir cela en utilisant la fonction copy()
.
La fonction copy()
effectue une copie profonde de l'objet d'entrée, et donc, toutes les opérations de mise à jour par référence effectuées sur l'objet copié n'affecteront pas l'objet d'origine.
Il y a deux situations particulières où la fonction copy()
est essentielle :
Contrairement à ce que nous avons vu au point précédent, nous pouvons ne pas vouloir que les données d'entrée d'une fonction soient modifiées par référence. A titre d'exemple, considérons la tâche de la section précédente, sauf que nous ne voulons pas modifier flights
par référence.
Supprimons d'abord la colonne speed
que nous avons générée dans la section précédente.
r
flights[, vitesse := NULL]
Maintenant, nous pourrions accomplir la tâche comme suit :
r
foo <- function(DT) {
DT <- copy(DT) ## copie profonde
DT[, speed := distance / (air_time/60)] ## n'affecte pas les vols
DT[, .(max_speed = max(speed)), by = month]
}
ans <- foo(flights)
head(flights)
head(ans)
L'utilisation de la fonction copy()
n'a pas modifié la data.table flights
par référence. Elle ne contient pas la colonne speed
.
Et ans
contient la vitesse maximale correspondant à chaque mois.
Cependant, nous pourrions encore améliorer cette fonctionnalité en faisant une copie superficielle au lieu d'une copie profonde. En fait, nous aimerions beaucoup fournir cette fonctionnalité pour v1.9.8
. Nous reviendrons sur ce point dans la vignette design de data.table.
Lorsque nous stockons les noms de colonnes dans une variable, par exemple, DT_n = names(DT)
, puis que nous ajoutons/mettons à jour/supprimons une ou plusieurs colonne(s) par référence, cela modifierait également DT_n
, à moins que nous ne fassions copy(names(DT))
.
```r DT = data.table(x = 1L, y = 2L) DT_n = names(DT) DT_n
DT[, z := 3L]
DT_n
copy()
DT_n = copy(names(DT)) DT[, w := 4L]
DT_n ```
:=
Il est utilisé pour ajouter/mettre à jour/supprimer des colonnes par référence.
Nous avons aussi vu comment utiliser :=
avec i
et by
de la même manière que nous l'avons vu dans la vignette Introduction à data.table. Nous pouvons de la même manière utiliser keyby
, enchaîner des opérations, et passer des expressions à by
de la même manière. La syntaxe est consistante.
Nous pouvons utiliser :=
pour ses effets secondaires ou utiliser copy()
pour ne pas modifier l'objet original tout en mettant à jour par référence.
setDTthreads(.old.th)
Jusqu'à présent, nous avons vu beaucoup d’opérations en j
, et comment les combiner avec by
, mais peu de choses concernant i
. Tournons notre attention vers i
dans la prochaine vignette "Clés et sous-ensembles basés sur une recherche binaire rapide" pour réaliser des sous-ensembles ultra-rapides en utilisant des clés dans data.tables.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.