require(data.table) knitr::opts_chunk$set( comment = "#", error = FALSE, tidy = FALSE, cache = FALSE, collapse = TRUE ) .old.th = setDTthreads(1)
Cette vignette présente la syntaxe de data.table
, sa forme générale, comment extraire les lignes, sélectionner et faire des opérations sur les colonnes, et réaliser des agrégations par groupe. Il est avantageux d'être familiarisé avec la structure de données data.frame
de base du R, mais cela n'est pas essentiel pour suivre cette vignette.
data.table
Les opérations concernant le traitement des données telles que subset, group, update, join, etc. sont toutes intimement liées. En regroupant ces opérations apparentées cela nous permet :
syntaxe concise et cohérente quel que soit l'ensemble des opérations que vous souhaitez effectuer pour atteindre votre objectif final.
effectuer une analyse fluide sans la charge cognitive de devoir faire correspondre chaque opération à une fonction particulière à partir d'un ensemble potentiellement énorme de fonctions disponibles avant d'effectuer l'analyse.
automatiquement optimiser les opérations en interne et de manière très efficace en connaissant précisément les données requises pour chaque opération, ce qui permet d'obtenir un code très rapide et efficace sur le plan de la mémoire.
En résumé, si vous souhaitez réduire drastiquement le temps de programmation et de compilation, alors ce package est fait pour vous. C'est la philosophie suivie par data.table
pour rendre cela possible. Notre but est d'illustrer ceci au travers de cette série de vignettes.
Dans cette vignette, nous utiliseront les données NYC-flights14 obtenues du package flights (disponible sur GitHub seulement). Il contient les horaires des vols d'avions du Bureau of Transportation Statistics à propos de tous les vols partant des aéroports de New York City en 2014 (inspiré de nycflights13). Les données ne concernent que les mois de janvier à octobre 2014.
Vous pouvez utiliser le lecteur de fichiers rapide et convivial 'fread' de 'data.table' pour charger 'flights' ditectement ainsi :
options(width = 100L)
input <- if (file.exists("../flights14.csv")) { "../flights14.csv" } else { "https://raw.githubusercontent.com/Rdatatable/data.table/master/vignettes/flights14.csv" } flights <- fread(input) flights dim(flights)
A noter : 'fread' accepte directement les URLS 'http' et 'https', ainsi que les commandes système opérationnelles telles que les sorties de 'sed' et 'awk'. Voir '?fread' pour les exemples.
Dans cette vignette, nous allons
Commencez par les bases - qu'est-ce qu'un data.table
, sa forme générale, comment réaliser un sous-ensemble des lignes, comment sélectionner et effectuer des calculs sur les colonnes;
Nous verrons ensuite comment effectuer des agrégations de données par groupe
'data.table' est un package R qui fournit une version étendue d'un 'data.frame', qui est la structure de données standard pour stocker des données dans la 'base' R. Dans la Data section ci-dessus, nous avons vu comment créer une 'data.table' avec 'fread()', mais on peut aussi en créer une en utilisant la fonction 'data.table()' . Voici un exemple :
DT = data.table( ID = c("b","b","b","a","a","c"), a = 1:6, b = 7:12, c = 13:18 ) DT class(DT$ID)
Vous pouvez aussi convertir des objets existants en une data.table
en utilisant setDT()
(pour les structures data.frame
et list
) ou as.data.table()
(pour les autres structures). Pour les autres détails concernant les différences (ce qui est hors du champ de cette vignette), voir ?setDT
et ?as.data.table
.
Les numéros de ligne sont imprimés avec un :
afin de séparer visuellement le numéro de ligne de la première colonne.
Lorsque le nombre de lignes à imprimer dépasse l'option globale datatable.print.nrows
(défaut = r getOption("datatable.print.nrows")
), il n'imprime automatiquement que les 5 premières et les 5 dernières lignes (comme on peut le voir dans la section Data). Pour un grand data.frame
, vous avez pu vous retrouver à attendre que des tables plus grandes s'impriment et se mettent en page, parfois sans fin. Cette restriction permet d'y remédier, et vous pouvez demander le nombre par défaut de la façon suivante :
{.r}
getOption("datatable.print.nrows")
data.table
ne définit ni n'utilise jamais de nom de ligne. Nous verrons pourquoi dans la vignette "Sous-ensemble basé sur des clés et recherche binaire rapide".
Par rapport à un data.frame
, vous pouvez faire beaucoup plus de choses qu'extraire des lignes et sélectionner des colonnes dans la structure d'une data.table
, par exemple, avec [ ... ]
(Notez bien : nous pourrions aussi faire référence à écrire quelque chose dans DT[...]
comme "interroger DT
", par analogie ou similairement à SQL). Pour le comprendre il faut d'abord que nous regardions la forme générale de la syntaxe data.table
, comme indiqué ci-dessous :
DT[i, j, by] ## R: i j by ## SQL: where | order by select | update group by
Les utilisateurs ayant des connaissances SQL feront peut être directement le lien avec cette syntaxe.
Utiliser DT
, extraire ou trier les lignes en utilisant i
, puis calculer j
, grouper avec by
.
Commençons par voir 'i' et 'j' d'abord - en indiçant les lignes et en travaillant sur les colonnes.
ans <- flights[origin == "JFK" & month == 6L] head(ans)
Dans le cadre d'un data.table
, on peut se référer aux colonnes comme s'il s'agissait de variables, un peu comme dans SQL ou Stata. Par conséquent, nous nous référons simplement à origin
et month
comme s'il s'agissait de variables. Nous n'avons pas besoin d'ajouter le préfixe vol$
à chaque fois. Néanmoins, l'utilisation de flights$origin
et flights$month
fonctionnerait parfaitement.
Les indices de ligne qui satisfont la condition origin == "JFK" & month == 6L
sont calculés, et puisqu'il n'y a rien d'autre à faire, toutes les colonnes de flights
aux lignes correspondant à ces indices de ligne sont simplement renvoyées sous forme d’un data.table
.
Une virgule après la condition dans i
n'est pas nécessaire. Mais flights[origin == "JFK" & month == 6L, ]
fonctionnerait parfaitement. Avec un data.frame
, cependant, la virgule est indispensable.
flights
. {#subset-rows-integer}ans <- flights[1:2] ans
i
. Nous retournons donc un data.table
avec toutes les colonnes de flights
aux lignes pour ces index de ligne.flights
d'abord sur la colonne origin
dans l'ordre ascending, puis par dest
dans l'ordre descendant :Nous pouvons utiliser la fonction R 'order()' pour faire cela.
ans <- flights[order(origin, -dest)] head(ans)
order()
est optimisé en interneNous pouvons utiliser "-" sur les colonnes character
dans le cadre d'un data.table
pour trier par ordre décroissant.
De plus, order(...)
dans le cadre d'un data.table
utilise l'ordre radix rapide interne de data.table
forder()
. Ce tri a apporté une telle amélioration par rapport à base::order
de R que le projet R a adopté l'algorithme data.table
comme tri par défaut en 2016 pour R 3.3.0 (pour référence, voir ?sort
et les R Release NEWS).
Nous discuterons de l'ordonnancement rapide de la data.table
plus en détails dans la vignette fonctionnement interne de data.table
internals.
j
{#select-j-1d}arr_delay
, mais la renvoyer en tant que vector.ans <- flights[, arr_delay] head(ans)
Puisque les colonnes peuvent être appelées comme si elles étaient des variables dans le cadre d'un data.table
, nous nous référons directement à la variable dont nous voulons créer un sous-ensemble. Puisque nous voulons toutes les lignes, nous sautons simplement i
.
Il renvoie toutes les lignes de la colonne arr_delay
.
arr_delay
, mais la renvoyer en tant que data.table
.ans <- flights[, list(arr_delay)] head(ans)
Nous enveloppons les variables (noms de colonnes) dans list()
, ce qui assure qu'un data.table
est retourné. Dans le cas d'un seul nom de colonne, le fait de ne pas utiliser list()
renvoie un vecteur, comme on peut le voir dans l'exemple précédent](#select-j-1d).
data.table
permet aussi d'envelopper les colonnes avec .()
au lieu de list()
. C'est un alias de list()
; les deux signifient la même chose. N'hésitez pas à utiliser ce que vous préférez ; nous avons remarqué que la plupart des utilisateurs semblent préférer .()
pour la concision, donc nous continuerons à utiliser .()
par la suite.
Un data.table
(et également un data.frame
) est aussi en interne une list
, avec la caractéristique que chaque élément a la même longueur et que la list
possède un attribut class
. En permettant à j
de renvoyer une list
cela permet de convertir et de renvoyer des data.table
très efficacement.
Tant que j-expression
renvoie une list
, chaque élément de la liste sera converti en colonne dans la data.table
résultante. Ce qui fait que j
est très puissant, comme nous le verrons bientôt. Il est aussi très important de comprendre cela dans le cas où vous auriez à faire des requêtes plus compliquées !!
arr_delay
et dep_delay
.ans <- flights[, .(arr_delay, dep_delay)] head(ans) ## forme alternative # ans <- flights[, list(arr_delay, dep_delay)]
.()
, ou list()
. C'est tout.arr_delay
et dep_delay
et les renommer en delay_arr
et delay_dep
.Comme .()
est juste un alias pour list()
, nous pouvons donner un nom quelconque aux colonnes comme si on créait une list
.
ans <- flights[, .(delay_arr = arr_delay, delay_dep = dep_delay)] head(ans)
ans <- flights[, sum( (arr_delay + dep_delay) < 0 )] ans
j
de data.table
peut gérer plus que la sélection de colonnes - il peut gérer des expressions, c'est-à-dire calculer sur des colonnes. Cela ne devrait pas être surprenant, car on peut se référer aux colonnes comme si elles étaient des variables. Nous devrions donc pouvoir calculer en appelant des fonctions sur ces variables. Et c'est précisément ce qui se passe ici.i
et do dans j
ans <- flights[origin == "JFK" & month == 6L, .(m_arr = mean(arr_delay), m_dep = mean(dep_delay))] ans
Nous commençons par effectuer un sous-ensemble dans i
pour trouver les indices de ligne correspondants à origin
égal à l’aéroport "JFK"
, et où le mois
est égal à 6L
. Nous n'effectuons pas encore le sous-ensemble de toutes les data.table
correspondant à ces lignes.
Maintenant, nous regardons j
et nous constatons qu'il n'utilise que deux colonnes. Et ce que nous devons faire, c'est calculer leur moyenne avec mean()
. Par conséquent, nous regroupons uniquement les colonnes d’intérêt aux lignes correspondantes, et nous calculons leurs moyennes.
Parce que les trois composants principaux de la requête (i
, j
et by
) figurent ensemble dans [...]
, data.table
peut les voir tous trois et optimiser la requête dans sa totalité avant l'évaluation, plutôt que d'optimiser chacun séparément. Par conséquent nous pouvons éviter le sous-ensemble complet (par exemple trier les colonnes annexes arr_delay
et dep_delay
), pour la rapidité et l'efficacité de la mémoire.
ans <- flights[origin == "JFK" & month == 6L, length(dest)] ans
La fonction length()
nécessite un argument d'entrée. Il suffit juste de calculer le nombre de lignes du sous-ensemble. On aurait pu utiliser n'importe quelle colonne comme argument d'entrée de length()
. Cette approche est une réminiscence de SELECT COUNT(dest) FROM flights WHERE origin = 'JFK' AND month = 6
en SQL.
Ce type d'opération arrive assez fréquement, particulièrement lors des regroupements (comme nous le verrons dans la section suivante), au point que data.table
fournit un symbole spécial .N
pour cela.
i
Lorsque vous interrogez une data.table
pour des éléments qui n'existent pas, le comportement dépend de la méthode utilisée.
setkeyv(flights, "origin")
dt["d"]
Ceci réalise une jointure parfaite sur la colonne clé x
, fournissant une rangée avec d
et NA
pour les colonnes absentes. En utilisant setkeyv
, la table est triée en fonction des clés fournies et un index interne est créé, permettant une recherche binaire et des performances optimisées.
flights["XYZ"] # Retourne: # origin year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum ... # 1: XYZ NA NA NA NA NA NA NA NA NA NA NA NA ...
dt[x == "d"]
Ceci réalise une opération standard de sous-ensemble qui ne trouve aucune correspondance de lignes et donc renvoie une data.table
vide.
flights[origin == "XYZ"] # Retourne: # Empty data.table (0 rows and 19 cols): year,month,day,dep_time,sched_dep_time,dep_delay,arr_time,sched_arr_time,arr_delay,...
nomatch=NULL
Pour une correspondance stricte sans NA
pour les éléments absents, utiliser nomatch=NULL
:
flights["XYZ", nomatch=NULL] # Retourne: # Empty data.table (0 rows and 19 cols): year,month,day,dep_time,sched_dep_time,dep_delay,arr_time,sched_arr_time,arr_delay,...
En assimilant ces comportements, cela vous ôtera toute confusion lorsque vous trouverez des éléments absents parmi vos données.
.N
: {#special-N}.N
est une variable interne spéciale qui contient le nombre d'observations dans le groupe actuel. Elle est particulièrement utile combinée avec by
comme nous le verrons dans la prochaine section. S'il n'y a pas de groupe pour les opérations, le nombre de lignes dans le sous-ensemble sera simplement renvoyé.
Maintenant que nous savons, nous pouvons accomplir la même tâche en utilisant .N
ainsi :
ans <- flights[origin == "JFK" & month == 6L, .N] ans
Une fois de plus, nous introduisons i
pour obtenir les indices de lignes pour lesquels l'aéroport origin
est "JFK", et le mois
est 6.
Nous voyons que j
n'utilise que .N
et aucune autre colonne. Par conséquent, le sous-ensemble complet n'est pas matérialisé. Nous renvoyons simplement le nombre de lignes dans le sous-ensemble (qui est juste la longueur des indices de ligne).
Notez que nous n'avons pas enveloppé .N
avec list()
ou .()
. Par conséquent, un vecteur est retourné.
On aurait pu faire la même opération en écrivant nrow(flights[origin == "JFK" & month == 6L])
. Néanmoins il aurait fallu d'abord dissocier la data.table
entière en fonction des indices de lignes dans i
puis renvoyer les lignes en utilisant nrow()
, ce qui est inutile et pas efficace. Nous aborderons en détails ce sujet et d'autres aspects de l'optimisation dans la vignette architecture de data.table
.
j
(comme avec un data.frame
) ? {#refer_j}Si vous imprimez le nom des colonnes explicitement, il n'y a pas de différence avec un data.frame
(depuis v1.9.8).
arr_delay
et dep_delay
à la manière d'un data.frame
.ans <- flights[, c("arr_delay", "dep_delay")] head(ans)
Si vous avez stocké les colonnes souhaitées dans un vecteur de caractères, il y a deux options : utiliser le préfixe ..
, ou utiliser l'argument with
.
..
select_cols = c("arr_delay", "dep_delay") flights[ , ..select_cols]
Pour les habitués du terminal Unix, le préfixe ..
devrait rappeler la commande de "remontée d'un niveau", qui est analogue à ce qui se passe ici -- le ..
demande à data.table
de chercher la variable select_cols
"un nivau au-dessus", c'est à dire dans ce cas, dans l'envronnement global.
with = FALSE
flights[ , select_cols, with = FALSE]
L'argument s'appelle with
d'après la fonction R with()
à cause de la fonctionnalité similaire. Supposez que vous ayiez une data.frame
DF
et que vous vouliez dissocier toutes les lignes où x > 1
. Dans la base
R vous pouvez écrire :
DF = data.frame(x = c(1,1,1,2,2,3,3,3), y = 1:8) ## (1) méthode classique DF[DF$x > 1, ] # data.frame needs that ',' as well ## (2) en utilisant with DF[with(DF, x > 1), ]
L'utilisation de with()
dans (2) permet d'utiliser la colonne x
de DF
comme s'il s'agissait d'une variable.
D'où le nom de l'argument with
dans data.table
. Mettre with = FALSE
désactive la possibilité de se référer aux colonnes comme si elles étaient des variables, restaurant ainsi le « mode data.frame
».
Nous pouvons également désélectionner des colonnes en utilisant -
ou !
. Par exemple :
```r
ans <- flights[, !c("arr_delay", "dep_delay")]
ans <- flights[, -c("arr_delay", "dep_delay")] ```
A partir de la v1.9.5+
, on peut aussi sélectionner en spécifiant les noms des colonnes de début et de fin, par exemple, year:day
pour sélectionner les trois premières colonnes.
```r
ans <- flights[, year:day]
ans <- flights[, day:year]
ans <- flights[, -(year:day)] ans <- flights[, !(year:day)] ```
Ceci est particulièrement pratique lorsque l'on travaille de manière interactive.
with = TRUE
est la valeur par défaut dans data.table
car nous pouvons faire plus en permettant à j
de gérer des expressions - particulièrement en combinant avec by
, comme nous le verrons dans un instant.
Nous avons déjà vu i
et j
dans la forme générale d'une data.table
dans la secton précédente. Dans cette section, nous allons voir comment ils peuvent être combinés ensemble avec by
pour réaliser des opérations par groupe. Voyons quelques exemples.
by
ans <- flights[, .(.N), by = .(origin)] ans ## ou résultat identique en utilisant un vecteur de chaînes de caractères dans 'by' # ans <- flights[, .(.N), by = "origin"]
Nous savons que .N
est une variable spéciale qui contient le nombre de lignes dans le groupe courant. En groupant par origine
, on obtient le nombre de lignes, .N
, pour chaque groupe.
En faisant head(flights)
vous pouvez voir que les aéroports d'origine sont dans l'ordre "JFK", "LGA", et "EWR". L'ordre original de regroupement des variables est préservé dans le résultat. Il est important de garder cela à l'esprit!
Comme nous n'avons pas fourni de nom pour la colonne retournée dans j
, elle a été nommée N
automatiquement en reconnaissant le symbole spécial .N
.
by
accepte également un vecteur de caractères de noms de colonnes. Ceci est particulièrement utile pour le codage par programmation, par exemple pour concevoir une fonction avec les colonnes de regroupement (sous la forme d'un vecteur character
) comme argument de la fonction.
Lorsqu'il n'y a qu'une seule colonne ou expression à laquelle se référer dans j
et by
, nous pouvons abandonner la notation .()
. Ceci est purement pratique. Nous pourrions plutôt faire :
r
ans <- flights[, .N, by = origin]
ans
Nous utiliserons cette forme pratique chaque fois que cela sera possible.
"AA"
? {#origin-.N}Le code unique de transporteur "AA"
correspond à American Airlines Inc.
ans <- flights[carrier == "AA", .N, by = origin] ans
Nous obtenons d'abord les indices de ligne pour l'expression carrier == "AA"
à partir de i
.
En utilisant ces index de ligne, nous obtenons le nombre de lignes groupées par origine
. Une fois de plus, aucune colonne n'est matérialisée ici, car l'expression `j' ne nécessite aucune colonne pour définir le sous-ensemble et le calcul est donc rapide et peu gourmand en mémoire.
origin, dest
du transporteur ayant pour code "AA"
? {#origin-dest-.N}ans <- flights[carrier == "AA", .N, by = .(origin, dest)] head(ans) ## ou résultat identique en utilisant une chaîne de caractères dans 'by' # ans <- flights[carrier == "AA", .N, by = c("origin", "dest")]
by
accepte plusieurs colonnes. Nous fournissons simplement toutes les colonnes par lesquelles il faut grouper. Notez l'utilisation de .()
dans by
-- encore une fois, c'est juste un raccourci pour list()
, et list()
peut être utilisé ici aussi. Nous nous en tiendrons à nouveau à .()
dans cette vignette.orig,dest
pour le transporteur ayant le code "AA"
? {#origin-dest-month}ans <- flights[carrier == "AA", .(mean(arr_delay), mean(dep_delay)), by = .(origin, dest, month)] ans
Comme nous n'avons pas fourni de noms de colonnes pour les expressions dans j
, elles ont été automatiquement générées en tant que V1
et V2
.
Une fois de plus, notez que l'ordre d'entrée des colonnes de regroupement est préservé dans le résultat.
Maintenant qu'adviendrait-il si nous voulions trier les résultats en groupant les colonnes origin
, dest
et month
?
by
: keyby
data.table
conserve l'ordre original des groupes; c'est intentionnel et défini à la conception. Il existe des cas où conserver l'ordre original est essentiel. Mais à certains moments, nous aimerions trier automatiquement par variables dans notre regroupement.
ans <- flights[carrier == "AA", .(mean(arr_delay), mean(dep_delay)), keyby = .(origin, dest, month)] ans
by
par keyby
. Cela ordonne automatiquement le résultat par ordre croissant des variables de regroupement. En fait, à cause de l'implémentation interne de by
qui nécessite d'abord un tri avant de récupérer l'ordre de la table originale, keyby
est typiquement plus rapide que by
parce qu'il ne nécessite pas cette seconde étape.Clés : actuellement keyby
en fait un peu plus que simplement trier. Il définit une clé également après le tri en initialisant un attribute
appelé sorted
.
Nous en apprendrons plus au sujet des clés
dans la vignette Clés et sous-ensembles basés sur la recherche binaire rapide; pour l'instant, tout ce que vous devez savoir est que vous pouvez utiliser keyby
pour trier automatiquement le résultat selon les colonnes spécifiées dans by
.
Considérons la tâche consistant à récupérer le nombre total de voyages pour chaque couple origin, dest
du transporteur "AA".
ans <- flights[carrier == "AA", .N, by = .(origin, dest)]
ans
en utilisant la colonne origin
en mode croissant, et la colonne dest
en mode décroissant ?On peut stocker le résultat intermédiaire dans une variable, puis passer order(origin, -dest)
sur cette variable. Cela semble plus direct.
ans <- ans[order(origin, -dest)] head(ans)
Rappelons que nous pouvons utiliser -
sur une colonne character
dans order()
dans le cadre d'un data.table
. Ceci est possible grâce à l'optimisation interne des requêtes de data.table
.
Rappelez-vous aussi que order(...)
dans le contexte d'un data.table
est automatiquement optimisé pour utiliser l’algorithme de tri radix rapide interne de data.table
forder()
pour plus de rapidité.
Mais ceci nécessite d'avoir assigné le résultat intermédiaire et de réécrire ce résultat. On peut faire mieux et éviter cette assignation intermédiaire à une variable temporaire en chaînant les expressions ensemble.
ans <- flights[carrier == "AA", .N, by = .(origin, dest)][order(origin, -dest)] head(ans, 10)
Nous pouvons ajouter des expressions l'une après l'autre, formant une chaîne d'opérations, c'est-à-dire DT[ ... ][ ... ][ ... ]
.
Vous pouvez également les enchaîner verticalement :
r
DT[ ...
][ ...
][ ...
]
by
by
accepte-t-il également expressions, ou simplement des colonnes ?Oui, il le fait. Par exemple, si nous avions voulu chercher combien de vols sont partis en retard mais sont arrivés plus tôt (ou à l'heure), ou parts à l'heure mais arrivés en retard, etc...
ans <- flights[, .N, .(dep_delay>0, arr_delay>0)] ans
La dernière ligne correspond à dep_delay > 0 = TRUE
et arr_delay > 0 = FALSE
. Nous pouvons voir que les vols r flights[!is.na(arr_delay) & !is.na(dep_delay), .N, .(dep_delay>0, arr_delay>0)][, N[4L]]
ont commencé en retard mais sont arrivés en avance (ou à l'heure).
Notez que nous n'avons pas fourni de noms à by-expression
. Par conséquent, les noms ont été automatiquement assignés dans le résultat. Comme pour j
, vous pouvez nommer ces expressions comme vous le feriez pour des éléments de n'importe quelle liste, comme par exemple DT[, .N, .(dep_delayed = dep_delay>0, arr_delayed = arr_delay>0)]
.
Vous pouvez fournir d'autres colonnes avec des expressions, par exemple : DT[, .N, by = .(a, b>0)]
.
j
- .SD
mean()
pour chaque colonne individuellement ?Bien sûr il n'est pas pratique de devoir entrer mean(myCol)
pour chaque colonne, une par une. Et s'il fallait faire la moyenne mean()
sur 100 colonnes ?
Comment faire cela de manière efficace et concise ? Pour y arriver, relisons ce conseil - "Tant que la j
-expression renvoie une list
, chaque élément de cette list
sera converti en une colonne de la data.table
résultat". Si nous pouvons adresser le sous-ensemble de données de chaque groupe comme une variable de regroupement, nous pourrons ensuite boucler sur toutes les colonnes de cette variables en utilisant la fonction de base familière (ou en passe de le devenir) lapply()
. Il n'y a pas de nouveaux noms à apprendre particuliers pour data.table
.
.SD
: {#special-SD}data.table
fournit le symbole spécial .SD
. Il tire son nom de Sous-ensemble de Données. C'est une data.table
qui contient les données du groupe actuel tel qu'il a été défini avec by
.
Souvenez-vous qu'une data.table
est représentée en interne comme une list
dont toutes les colonnes ont la même longueur.
Utilisons la data.table
DT
précédente pour avoir un aperçu de ce à quoi ressemble .SD
.
DT DT[, print(.SD), by = ID]
.SD
contient toutes les colonnes à l'exception des colonnes de regroupement par défaut.
Il est également généré en conservant l'ordre original - les données correspondant à ID = "b"
, puis ID = "a"
, et enfin ID = "c"
.
Pour calculer sur uneou plusieurs colonnes vous pouvez utiliser simplement la fonction de base R lapply()
.
DT[, lapply(.SD, mean), by = ID]
.SD
contient les lignes correspondant aux colonnes a
, b
et c
pour ce groupe. Nous calculons la moyenne avec mean()
sur chacune de ces colonnes en utilisant la fonction de base déjà familière lapply()
.
Chaque groupe renvoie une liste de trois éléments contenant la valeur moyenne qui deviendra les colonnes du data.table
résultant.
Puisque lapply()
renvoie une liste, il n'est pas nécessaire de l'entourer d'un .()
supplémentaire (si nécessaire, référez-vous à cette astuce).
Nous y sommes presque. Il reste encore une petite chose à régler. Dans notre data.table
flights
, nous avons voulu calculer seulement la mean()
des deux colonnes arr_delay
et dep_delay
. Mais .SD
contiendrait par défaut toutes les colonnes autres que les variables de groupement.
mean()
?En utilisant l'argument .SDcols
. Il accepte soit des noms soit des indices de colonnes. Par exemple, .SDcols = c("arr_delay", "dep_delay")
permet que .SD
ne comporte que ces deux colonnes pour chaque groupe.
De la même manière que part g), vous pouvez également spécifier les colonnes à supprimer au lieu des colonnes à garder en utilisant le -
ou !
. De plus, vous pouvez sélectionner des colonnes consécutives avec colA:colB
et les désélectionner avec !(colA:colB)
ou -(colA:colB)
.
Maintenant essayons d'utiliser .SD
avec .SDcols
pour obtenir la moyenne mean()
des colonnes arr_delay
et dep_delay
groupées par origin
, dest
et month
.
flights[carrier == "AA", ## Seulement les vols sur porteurs "AA" lapply(.SD, mean), ## calcule la moyenne by = .(origin, dest, month), ## pour chaque 'origin,dest,month' .SDcols = c("arr_delay", "dep_delay")] ## pour seulement ceux spécifiés dans .SDcols
.SD
pour chaque groupe :ans <- flights[, head(.SD, 2), by = month] head(ans)
.SD
est un data.table
qui contient toutes les lignes de ce groupe. Nous allons simplement subdiviser les deux premières lignes comme nous l'avons déjà vu ici.
Pour chaque groupe, head(.SD, 2)
renvoie les deux premières lignes sous forme de data.table
, qui est également une liste, ce qui nous évite de l'entourer de .()
.
j
si flexible ?Ainsi nous avons une syntaxe cohérente et continuons l'utilisation de fonctions de base déja existantes (et familières) au lieu d'apprendre de nouvelles fonctions. Pour illustrer cela utilisons la data.table
DT
que nous avons créée tout au début dans la section Qu'est-ce qu'une data.table ?.
a
et b
pour chaque groupe de ID
?DT[, .(val = c(a,b)), by = ID]
c()
qui concatène des vecteurs, ainsi que l'astuce de tout à l'heure.a
et b
concaténées, mais renvoyées en tant que colonne de liste ?DT[, .(val = list(c(a,b))), by = ID]
Ici, nous concaténons d'abord les valeurs avec c(a,b)
pour chaque groupe, et nous les enveloppons avec list()
. Ainsi, pour chaque groupe, nous renvoyons une liste de toutes les valeurs concaténées.
Notez que ces virgules ne servent qu'à l'affichage. Une colonne de liste peut contenir n'importe quel objet dans chaque cellule et, dans cet exemple, chaque cellule est elle-même un vecteur et certaines cellules contiennent des vecteurs plus longs que d'autres.
Une fois que vous commencerez à utiliser j
, vous découvrirez la puissance de sa syntaxe. Une manière pratique de l'aborder est de la tester en utilisant print()
.
Par exemple :
## inspectez la différence entre DT[, print(c(a,b)), by = ID] # (1) ## et DT[, print(list(c(a,b))), by = ID] # (2)
Dans (1), pour chaque groupe, un vecteur est renvoyé, de longueur = 6,4,2 ici. Néanmoins, (2) renvoie une liste de longueur 1 pour chaque groupe, dont chaque premier élément contient des vecteurs de longueur 6,4,2. C'est pourquoi, (1) a pour longueur totale 6+4+2 =
r 6+4+2, alors que (2) renvoie `1+1+1=`r 1+1+1
.
La forme générale de la syntaxe de data.table
est :
DT[i, j, by]
Jusqu'ici nous avons vu que,
i
:Nous pouvons subdiviser les lignes comme dans un data.frame
- sauf que vous n'avez pas besoin d'utiliser DT$
de façon répétitive puisque les colonnes dans le contexte d'un data.table
sont vues comme si elles étaient des variables.
Nous pouvons également trier un data.table
en utilisant order()
, qui utilise en interne l’algorithme de tri rapide de data.table pour de meilleures performances.
Nous pouvons faire beaucoup plus dans i
en créant une data.table
avec clés, ce qui permet de réaliser rapidement les sous-ensembles et les jointures. Nous verrons cela dans les vignettes "Clés et sous-ensembles basés sur la recherche binaire rapide" et "Jointures et jointures liées au temps".
j
:Sélectionner les colonnes à la manière de data.table
: DT[, .(colA, colB)]
.
Sélectionner les colonnes à la manière de data.frame
: DT[, c("colA", "colB")]
.
Effectuer des calculs sur les colonnes : DT[, .(sum(colA), mean(colB))]
.
Indiquer les noms si nécessaire : DT[, .(sA =sum(colA), mB = mean(colB))]
.
Combiner avec i
: DT[colA > valeur, sum(colB)]
.
by
:En utilisant by
, nous pouvons grouper par colonnes en spécifiant une liste de colonnes ou un vecteur de caractères de noms de colonnes ou même des expressions. La flexibilité de j
, combinée à by
et i
, en fait une syntaxe très puissante.
by
peut gérer plusieurs colonnes ainsi que des expressions.
Nous pouvons regrouper les colonnes par 'keyby' pour trier automatiquement les résultats groupés.
Nous pouvons utiliser .SD
et .SDcols
dans j
pour opérer sur plusieurs colonnes en utilisant des fonctions de base déjà connues. Voici quelques exemples:
DT[, lapply(.SD, fun), by = ..., .SDcols = ...]
- applique fun
à toutes les colonnes spécifiées dans .SDcols
tout en groupant par les colonnes spécifiées dans by
.
DT[, head(.SD, 2), by = ...]
- renvoie les deux premières lignes pour chaque groupe.
DT[col > val, head(.SD, 1), by = ...]
- combine i
avec j
et by
.
Tant que j
renvoie un objet list
, chaque élément de la liste va devenir une colonne du data.table
résultant.
Nous verrons dans la vignette suivante comment ajouter / mettre à jour / supprimer des colonnes par référence et comment les combiner avec i
et by
.
setDTthreads(.old.th)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.