Ce document se concentre sur l'utilisation de data.table
comme dépendance dans d'autres packages R. Si vous souhaitez utiliser le code C de data.table
à partir d'une application non-R, ou appeler directement ses fonctions C, passez à la dernière section de cette vignette.
Importer data.table
n'est pas différent qu'importer d'autres packages R. Cette vignette a pour but de répondre aux questions les plus courantes à ce sujet; les indications présentées ici peuvent être appliquées à d'autres packages R.
data.table
L'une des principales caractéristiques de data.table
est sa syntaxe concise qui rend l'analyse exploratoire plus rapide et plus facile à écrire et à percevoir ; cette commodité peut pousser les auteurs de package à utiliser data.table
. Une autre raison, peut-être plus importante, est la haute performance. Lorsque vous confiez des tâches de calcul lourdes de votre package à data.table
, vous obtenez généralement de très bonnes performances sans avoir besoin de réinventer vous-même ces astuces d'optimisation numérique.
data.table
est facileIl est très facile d'utiliser data.table
comme dépendance car data.table
n'a pas de dépendances propres. Ceci s'applique à la fois au système d'exploitation et aux dépendances de R. Cela signifie que si R est installé sur votre machine, il a déjà tout ce qu'il faut pour installer data.table
. Cela signifie aussi qu'ajouter data.table
comme dépendance de votre package n'entraînera pas une chaîne d'autres dépendances récursives à installer, ce qui le rend très pratique pour une installation hors ligne.
DESCRIPTION
{#DESCRIPTION}Le premier endroit pour définir une dépendance dans un package est le fichier DESCRIPTION
. Le plus souvent, vous devrez ajouter data.table
dans le champ Imports:
. Cela nécessitera l'installation de data.table
avant que votre package ne puisse être compilé/installé. Comme mentionné ci-dessus, aucun autre package ne sera installé car data.table
n'a pas de dépendances propres. Vous pouvez aussi spécifier la version minimale requise d'une dépendance ; par exemple, si votre package utilise la fonction fwrite
, qui a été introduite dans data.table
dans la version 1.9.8, vous devriez l'incorporer comme Imports: data.table (>= 1.9.8)
. De cette façon, vous pouvez vous assurer que la version de data.table
installée est 1.9.8 ou plus récente avant que vos utilisateurs ne puissent installer votre package. En plus du champ Imports:
, vous pouvez aussi utiliser Depends: data.table
mais nous décourageons fortement cette approche (et nous pourrions l'interdire dans le futur) parce que cela charge data.table
dans l'espace de travail de votre utilisateur ; i.e. cela active la fonctionnalité data.table
dans les scripts de votre utilisateur sans qu'il ne le demande. Imports:
est la bonne façon d'utiliser data.table
dans votre package sans infliger data.table
à votre utilisateur. En fait, nous espérons que le champ Depends:
sera un jour déprécié dans R car ceci est vrai pour tous les packages.
NAMESPACE
{#NAMESPACE}La prochaine chose à faire est de définir le contenu de data.table
que votre package utilise. Cela doit être fait dans le fichier NAMESPACE
. Le plus souvent, les auteurs de package voudront utiliser import(data.table)
qui importera toutes les fonctions exportées (c'est-à-dire listées dans le fichier NAMESPACE
de data.table
) de data.table
.
Vous pouvez aussi ne vouloir utiliser qu'un sous-ensemble des fonctions de data.table
; par exemple, certains packages peuvent simplement utiliser les fonctions d'écriture et lecture CSV haute performance de data.table
, pour lesquelles vous pouvez ajouter importFrom(data.table, fread, fwrite)
dans votre fichier NAMESPACE
. Il est également possible d'importer toutes les fonctions d'un package en excluant certaines d'entre elles en utilisant import(data.table, except=c(fread, fwrite))
.
Assurez-vous de lire également la note sur l'évaluation non standard dans data.table
dans la section sur les "globales non définies"
A titre d'exemple, nous allons définir deux fonctions dans le package a.pkg
qui utilise data.table
. Une fonction, gen
, générera un simple data.table
; une autre, aggr
, en fera une simple agrégation.
gen = function (n = 100L) { dt = as.data.table(list(id = seq_len(n))) dt[, grp := ((id - 1) %% 26) + 1 ][, grp := letters[grp] ][] } aggr = function (x) { stopifnot( is.data.table(x), "grp" %in% names(x) ) x[, .N, by = grp] }
Assurez-vous d'inclure des tests dans votre package. Avant chaque version majeure de data.table
, nous vérifions les dépendances inverses. Cela signifie que si un changement dans data.table
casse votre code, nous serons capables de repérer les changements et de vous en informer avant de publier la nouvelle version. Cela suppose bien sûr que vous publiiez votre package sur CRAN ou Bioconductor. Le test le plus basique peut être un script R en clair dans le répertoire tests/test.R
de votre package :
library(a.pkg) dt = gen() stopifnot(nrow(dt) == 100) dt2 = aggr(dt) stopifnot(nrow(dt2) < 100)
Lorsque vous testez votre package, vous pouvez utiliser R CMD check --no-stop-on-test-error
, qui continuera après une erreur et exécutera tous vos tests (au lieu de s'arrêter à la première ligne de script qui a échoué) NB ceci nécessite R 3.4.0 ou plus.
testthat
Il est très courant d'utiliser le package testthat
pour effectuer des tests. Tester un package qui importe data.table
n'est pas différent de tester d'autres packages. Un exemple de script de test tests/testthat/test-pkg.R
:
context("pkg tests") test_that("generate dt", { expect_true(nrow(gen()) == 100) }) test_that("aggregate dt", { expect_true(nrow(aggr(gen())) < 100) })
Si data.table
est dans Suggests (mais pas dans Imports) alors vous devez déclarer .datatable.aware=TRUE
dans un des fichiers R/* pour éviter les erreurs "object not found" lors des tests via testthat::test_package
ou testthat::test_check
.
l'utilisation par data.table
de l'évaluation différée de R (en particulier sur le côté gauche de :=
) n'est pas bien reconnue par R CMD check
. Il en résulte des NOTE
s comme la suivante lors de la vérification du package :
* checking R code for possible problems ... NOTE aggr: no visible binding for global variable 'grp' gen: no visible binding for global variable 'grp' gen: no visible binding for global variable 'id' Undefined global functions or variables: grp id
La façon la plus simple de gérer cela est de prédéfinir ces variables dans votre package et de leur donner la valeur NULL
, en ajoutant éventuellement un commentaire (comme c'est le cas dans la version raffinée de gen
ci-dessous). Quand c'est possible, vous pouvez aussi utiliser un vecteur de caractères à la place des symboles (comme dans aggr
ci-dessous) :
gen = function (n = 100L) { id = grp = NULL # en raison des notes NSE dans la vérification CMD R dt = as.data.table(list(id = seq_len(n))) dt[, grp := ((id - 1) %% 26) + 1 ][, grp := letters[grp] ][] } aggr = function (x) { stopifnot( is.data.table(x), "grp" %in% names(x) ) x[, .N, by = "grp"] }
Le cas des symboles spéciaux de data.table
(par exemple .SD
et .N
) et de l'opérateur d'affectation (:=
) est légèrement différent (voir ?.N
pour plus d'informations, y compris une liste complète de ces symboles). Vous devriez importer n'importe laquelle de ces valeurs que vous utilisez de l'espace de noms de data.table
pour vous protéger contre tout problème provenant du scénario improbable où nous changerions la valeur exportée de ces valeurs dans le futur, par exemple, si vous voulez utiliser .N
, .I
, et :=
, un NAMESPACE
minimal devrait avoir :
importFrom(data.table, .N, .I, ':=')
Il est beaucoup plus simple d'utiliser import(data.table)
qui autorisera avidement l'utilisation dans le code de votre package de tout objet exporté de data.table
.
Si cela ne vous dérange pas d'avoir id
et grp
enregistrés comme variables globalement dans l'espace de noms de votre package, vous pouvez utiliser ?globalVariables
. Soyez conscient que ces notes n'ont aucun impact sur le code ou ses fonctionnalités ; si vous n'avez pas l'intention de publier votre package, vous pouvez simplement choisir de les ignorer.
La pratique courante des packages R est de fournir des options de personnalisation définies par options(name=val)
et récupérées en utilisant getOption("name", default)
. Les arguments des fonctions spécifient souvent un appel à getOption()
pour que l'utilisateur connaisse (grâce à ?fun
ou args(fun)
) le nom de l'option contrôlant la valeur par défaut de ce paramètre ; par exemple fun(..., verbose=getOption("datatable.verbose", FALSE))
. Toutes les options de data.table
commencent par datatable.
afin de ne pas entrer en conflit avec les options d'autres packages. Un utilisateur appelle simplement options(datatable.verbose=TRUE)
pour activer la verbosité. Cela affecte tous les appels de fonctions de data.table à moins que verbose=FALSE
ne soit fourni explicitement ; par exemple fun(..., verbose=FALSE)
.
Le mécanisme des options dans R est global. Cela signifie que si un utilisateur définit une option data.table
pour son propre usage, ce réglage affecte également le code de tout package qui utilise data.table
. Pour une option comme datable.verbose
, c'est exactement le comportement désiré puisque le but est de tracer et d'enregistrer toutes les opérations de data.table
d'où qu'elles viennent ; activer la verbosité n'affecte pas les résultats. Une autre option unique à R et excellente pour la production est options(warn=2)
qui transforme tous les avertissements en erreurs. Encore une fois, le but est d'affecter n'importe quel avertissement dans n'importe quel package afin de ne manquer aucun avertissement en production. Il y a 6 options datable.print.*
et 3 options d'optimisation qui n'affectent pas le résultat des opérations. Cependant, il y a une option data.table
qui l'affecte et qui est maintenant un problème : datatable.nomatch
. Cette option change la jointure par défaut d'externe à interne. [A côté de cela, la jointure par défaut est externe parce que outer est plus sûr ; il ne laisse pas tomber les données manquantes silencieusement ; de plus, il est cohérent avec la façon dont la base R fait correspondre les noms et les indices]. Certains utilisateurs préfèrent que la jointure interne soit la valeur par défaut et nous avons prévu cette option pour eux. Cependant, un utilisateur qui met en place cette option peut involontairement changer le comportement des jointures à l'intérieur des packages qui utilisent data.table
. En conséquence, dans la version 1.12.4 (Oct 2019), un message était affiché lorsque l'option datable.nomatch
était utilisée, et à partir de la version 1.14.2, elle est maintenant ignorée avec un avertissement. C'était la seule option datable.table
qui posait ce problème.
Si vous rencontrez des problèmes lors de la création d'un package qui utilise data.table, veuillez confirmer que le problème est reproductible dans une session R propre en utilisant la console R : R CMD check nom.package
.
Certains des problèmes les plus courants auxquels les développeurs sont confrontés sont généralement liés à des outils d'aide destinés à automatiser certaines tâches de développement de package, par exemple, l'utilisation de roxygen
pour générer votre fichier NAMESPACE
à partir des métadonnées des fichiers de code R. D'autres sont liés aux outils d'aide qui construisent et vérifient les package. D'autres sont liées aux aides qui construisent et vérifient le package. Malheureusement, ces aides ont parfois des effets secondaires inattendus/cachés qui peuvent masquer la source de vos problèmes. Ainsi, assurez-vous de faire une double vérification en utilisant la console R (lancez R sur la ligne de commande) et assurez-vous que l'importation est définie dans les fichiers DESCRIPTION
et NAMESPACE
en suivant les instructions ci-dessus.
Si vous n'êtes pas en mesure de reproduire les problèmes que vous rencontrez en utilisant la simple console R pour construire ("build") et vérifier ("check"), vous pouvez essayer d'obtenir de l'aide en vous basant sur les problèmes que nous avons rencontrés dans le passé avec data.table
interagissant avec des outils d'aide : devtools#192 ou devtools#1472.
Depuis la version 1.10.5, data.table
est sous licence Mozilla Public License (MPL). Les raisons du changement de la GPL peuvent être lues en entier ici et vous pouvez en savoir plus sur la MPL sur Wikipedia ici et ici.
data.table
: Suggests
Si vous voulez utiliser data.table
de manière conditionnelle, c'est-à-dire seulement quand il est installé, vous devriez utiliser Suggests: data.table
dans votre fichier DESCRIPTION
au lieu d'utiliser Imports: data.table
. Par défaut, cette définition ne forcera pas l'installation de data.table
lors de l'installation de votre package. Cela vous oblige aussi à utiliser conditionnellement data.table
dans le code de votre package, ce qui doit être fait en utilisant la fonction ?requireNamespace
. L'exemple ci-dessous démontre l'utilisation conditionnelle de la fonction d'écriture de CSV rapide de ?fwrite
du package data.table
. Si le package data.table
n'est pas installé, la fonction de base R ?write.table
, beaucoup plus lente, est utilisée à la place.
my.write = function (x) { if(requireNamespace("data.table", quietly=TRUE)) { data.table::fwrite(x, "data.csv") } else { write.table(x, "data.csv") } }
Une version légèrement plus étendue de cette méthode permettrait également de s'assurer que la version installée de data.table
est suffisamment récente pour que la fonction fwrite
soit disponible :
my.write = function (x) { if(requireNamespace("data.table", quietly=TRUE) && utils::packageVersion("data.table") >= "1.9.8") { data.table::fwrite(x, "data.csv") } else { write.table(x, "data.csv") } }
Lorsque vous utilisez un package comme dépendance suggérée, vous ne devez pas l'"importer" dans le fichier NAMESPACE
. Mentionnez-le simplement dans le fichier DESCRIPTION
. Lorsque vous utilisez les fonctions data.table
dans le code d'un package (fichiers R/), vous devez utiliser le préfixe data.table::
car aucune d'entre elles n'est importée. Lorsque vous utilisez data.table
dans des packages de tests (par exemple des fichiers tests/testthat/test), vous devez déclarer .datatable.aware=TRUE
dans l'un des fichiers R/*.
data.table
dans Imports
mais rien d'importéCertains utilisateurs (e.g.) peuvent préférer éviter d'utiliser importFrom
ou import
dans leur fichier NAMESPACE
et utiliser à la place la syntaxe data.table::
sur tout le code interne (en gardant bien sûr data.table
sous leurs Imports:
dans DESCRIPTION
).
Dans ce cas, la fonction non exportée [.data.table
reviendra à appeler [.data.frame
comme filet de sécurité puisque data.table
n'a aucun moyen de savoir que le package parent est conscient qu'il tente de faire des appels en utilisant la syntaxe de l'API de requête de data.table
(ce qui pourrait conduire à un comportement inattendu car la structure des appels à [.data.frame
et [.data.table
diffère fondamentalement, par exemple, ce dernier a beaucoup plus d'arguments).
Si c'est l'approche que vous préférez pour le développement de packages, définissez .datatable.aware = TRUE
n'importe où dans votre code source R (pas besoin d'exporter). Cela indique à data.table
que vous, en tant que développeur du package, avez conçu votre code pour qu'il s'appuie intentionnellement sur les fonctionnalités de data.table
, même si cela n'est pas évident en inspectant votre fichier NAMESPACE
.
data.table
détermine à la volée si la fonction appelante est consciente qu'elle puise dans data.table
avec la fonction interne cedta
(Calling Environment is Data Table Aware), qui, en plus de vérifier le ?getNamespaceImports
de votre package, vérifie également l'existence de cette variable (entre autres choses).
Pour une documentation plus canonique sur la définition de la dépendance des packages, consultez le manuel officiel : Writing R Extensions.
Certaines routines C utilisées en interne sont maintenant exportées au niveau C et peuvent donc être utilisées dans les packages R directement à partir de leur code C. Voir ?cdt
pour les détails et Writing R Extensions dans la section Linking to native routines in other packages pour l'utilisation.
Certaines petites parties du code C de data.table
ont été isolées de l'API C de R et peuvent maintenant être utilisées à partir d'applications non-R en liant les fichiers .so / .dll. Des détails plus concrets seront fournis ultérieurement ; pour l'instant, vous pouvez étudier le code C qui a été isolé de l'API C de R dans src/fread.c et src/fwrite.c.
Pour convertir une dépendance Depends
sur data.table
en une dépendance Imports
dans votre package, suivez ces étapes :
Avant :
Depends: R (>= 3.5.0), data.table Imports:
Après :
Depends: R (>= 3.5.0) Imports: data.table
R CMD check
Lancez R CMD check
pour identifier tout import ou symbole manquant. Cette étape aide à :
data.table
qui ne sont pas explicitement importés..N
, .SD
, et :=
.Note : Toutes ces utilisations ne sont pas prises en compte par R CMD check
. En particulier, R CMD check
ne tient pas compte de certains symboles/fonctions dans les formules et manquera complètement des expressions analysées comme parse(text = "data.table(a = 1)")
. Les packages auront besoin d'une bonne couverture de test pour détecter ces cas limites.
En se basant sur les résultats du R CMD check
, s'assurer que toutes les fonctions utilisées, les symboles spéciaux, les génériques S3, et les classes S4 de data.table
sont importés.
Cela signifie qu'il faut ajouter les directives importFrom(data.table, ...)
pour les symboles, les fonctions et les génériques S3, et/ou les directives importClassesFrom(data.table, ...)
pour les classes S4, selon le cas. Voir 'Writing R Extensions' pour plus de détails sur la façon de procéder.
Vous pouvez également importer toutes les fonctions de data.table
en une seule fois, bien que cela ne soit généralement pas recommandé :
import(data.table)
Justification Pour Eviter Les Importations Globales : =====1. Documentation : Le fichier NAMESPACE peut servir de bonne documentation sur la façon dont vous dépendez de certains packages.
2. Éviter Les Conflits : Les importations générales vous exposent à des ruptures subtiles. Par exemple, si vous importez deux packages avec import(pkgA)
et import(pkgB)
, mais que plus tard pkgB exporte une fonction également exportée par pkgA, cela cassera votre package à cause de conflits dans votre espace de noms, ce qui est interdit par R CMD check
et CRAN.=====
Lorsque vous déplacez un package de Depends
vers Imports
, il ne sera plus automatiquement attaché lorsque votre package sera chargé. Cela peut être important pour les exemples, les tests, les vignettes et les démos, où les packages Imports
doivent être attachés explicitement.
Avant (avec Depends
) :
# les fonctions de data.table sont directement disponibles library(MyPkgDependsDataTable) dt <- data.table(x = 1:10, y = letters[1:10]) setDT(dt) result <- merge(dt, other_dt, by = "x")
Après (avec Imports
) :
# Charger explicitement data.table dans les scripts utilisateurs ou les vignettes library(data.table) library(MyPkgDependsDataTable) dt <- data.table(x = 1:10, y = letters[1:10]) setDT(dt) result <- merge(dt, other_dt, by = "x")
Imports
Depends
modifie le chemin search()
de vos utilisateurs, éventuellement sans qu'ils le veuillent.Depends
peut conduire à des conflits et des problèmes de compatibilité au fil du temps.Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.