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


Données {#data}

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)

Introduction

Dans cette vignette, nous allons

  1. 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é

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

  3. et enfin, nous examinerons l'utilisation de := pour ses effets secondaires et comment nous pouvons éviter ces effets secondaires en utilisant copy().

1. Sémantique de référence

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.

a) Contexte

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.

Copie superficielle vs copie profonde

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

b) L'opérateur :=

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

2. Ajouter/mettre à jour/supprimer des colonnes par référence

a) Ajouter des colonnes par référence {#ref-j}

-- Comment ajouter les colonnes vitesse speed et retard total total delay de chaque vol à la data.table 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)]

Notez que

b) Mise à jour de certaines lignes de colonnes par référence - sous-assignation par référence {#ref-i-j}

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.

-- Remplacer les lignes où hour == 24 par la valeur 0

# sous-assignation par référence
flights[hour == 24L, hour := 0L]

Regardons toutes les heures pour vérifier.

# vérifier à nouveau la présence de '24'
flights[, sort(unique(hour))]

Exercice : {#update-by-reference-question}

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 ?":=".

c) Suppression de colonne par référence

-- Supprimer la colonne delay

flights[, c("delay") := NULL]
head(flights)

## ou en utilisant la forme fonctionnelle
# flights[, `:=`(delay = NULL)]

{#delete-convenience}

d) := 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.

-- Comment ajouter une nouvelle colonne qui contienne pour chaque paire orig,dest la vitesse maximale ?

flights[, max_speed := max(speed), by = .(origin, dest)]
head(flights)

e) Colonnes multiples et :=

-- Comment peut-on ajouter deux colonnes supplémentaires en calculant 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)

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)

-- Comment peut-on mettre à jour plusieurs colonnes existantes par référence en utilisant .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])

{.bs-callout .bs-callout-info}

3. := 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.

a) := pour ses effets secondaires

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

b) La fonction 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 :

  1. 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)

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

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

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

    ajouter une nouvelle colonne par référence

    DT[, z := 3L]

    DT_n est également mis à jour

    DT_n

    utiliser copy()

    DT_n = copy(names(DT)) DT[, w := 4L]

    DT_n n'est pas mis à jour

    DT_n ```

Résumé

L'opérateur :=

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.




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