Il n'y a pas de réponse directe / simple car les philosophies de ces deux packages diffèrent sur certains aspects. Certains compromis sont donc inévitables. Voici quelques-unes des préoccupations que vous devrez peut-être aborder / considérer.
Opérations impliquant i
(== filter()
et slice()
en dplyr)
Supposons DT
avec, disons 10 colonnes. Considérez ces expressions data.table:
DT[a > 1, .N] ## --- (1)
DT[a > 1, mean(b), by=.(c, d)] ## --- (2)
(1) donne le nombre de lignes dans la DT
colonne where a > 1
. (2) renvoie mean(b)
groupé par c,d
pour la même expression i
que (1).
Les dplyr
expressions couramment utilisées seraient:
DT %>% filter(a > 1) %>% summarise(n()) ## --- (3)
DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)
De toute évidence, les codes data.table sont plus courts. De plus, ils sont également plus efficaces en mémoire 1 . Pourquoi? Parce que dans (3) et (4), filter()
retourne d'abord les lignes pour les 10 colonnes , quand dans (3) nous avons juste besoin du nombre de lignes, et dans (4) nous avons juste besoin de colonnes b, c, d
pour les opérations successives. Pour surmonter cela, nous devons select()
colonnes apriori:
DT %>% select(a) %>% filter(a > 1) %>% summarise(n()) ## --- (5)
DT %>% select(a,b,c,d) %>% filter(a > 1) %>% group_by(c,d) %>% summarise(mean(b)) ## --- (6)
Il est essentiel de souligner une différence philosophique majeure entre les deux packages:
Dans data.table
, nous aimons garder ces opérations liées ensemble, et cela permet de regarder le j-expression
(à partir du même appel de fonction) et de réaliser qu'il n'y a pas besoin de colonnes dans (1). L'expression dans i
est calculée et .N
n'est que la somme de ce vecteur logique qui donne le nombre de lignes; le sous-ensemble entier n'est jamais réalisé. Dans (2), seules les colonnes b,c,d
sont matérialisées dans le sous-ensemble, les autres colonnes sont ignorées.
Mais dans dplyr
, la philosophie est qu'une fonction fasse précisément une chose bien . Il n'y a (au moins actuellement) aucun moyen de dire si l'opération après a filter()
besoin de toutes les colonnes que nous avons filtrées. Vous devrez penser à l'avance si vous souhaitez effectuer ces tâches efficacement. Personnellement, je trouve cela contre-intutitif dans ce cas.
Notez que dans (5) et (6), nous sous-ensembleons toujours des colonnes a
dont nous n'avons pas besoin. Mais je ne sais pas comment éviter cela. Si la filter()
fonction avait un argument pour sélectionner les colonnes à retourner, nous pourrions éviter ce problème, mais alors la fonction ne fera pas qu'une seule tâche (qui est également un choix de conception de dplyr).
Sous-attribuer par référence
dplyr ne mettra jamais à jour par référence. C'est une autre énorme différence (philosophique) entre les deux packages.
Par exemple, dans data.table, vous pouvez faire:
DT[a %in% some_vals, a := NA]
qui met à jour la colonne a
par référence uniquement sur les lignes qui satisfont à la condition. Pour le moment, dplyr copie en profondeur l'intégralité de la table data.table en interne pour ajouter une nouvelle colonne. @BrodieG l'a déjà mentionné dans sa réponse.
Mais la copie profonde peut être remplacée par une copie superficielle lorsque FR # 617 est implémenté. Aussi pertinent: dplyr: FR # 614 . Notez qu'encore, la colonne que vous modifiez sera toujours copiée (donc un peu plus lente / moins efficace en mémoire). Il n'y aura aucun moyen de mettre à jour les colonnes par référence.
Autres fonctionnalités
Dans data.table, vous pouvez agréger lors de la jointure, ce qui est plus simple à comprendre et est efficace en mémoire car le résultat de la jointure intermédiaire n'est jamais matérialisé. Consultez cet article pour un exemple. Vous ne pouvez pas (pour le moment?) Faire cela en utilisant la syntaxe data.table / data.frame de dplyr.
La fonction de jointures roulantes de data.table n'est pas non plus prise en charge dans la syntaxe de dplyr.
Nous avons récemment implémenté des jointures de chevauchement dans data.table pour joindre des plages d'intervalle ( voici un exemple ), qui est une fonction distincte foverlaps()
pour le moment, et pourrait donc être utilisée avec les opérateurs de tube (magrittr / pipeR? - je ne l'ai jamais essayé moi-même).
Mais finalement, notre objectif est de l'intégrer [.data.table
afin que nous puissions récolter les autres fonctionnalités comme le regroupement, l'agrégation en rejoignant etc. qui auront les mêmes limitations décrites ci-dessus.
Depuis 1.9.4, data.table implémente l'indexation automatique en utilisant des clés secondaires pour des sous-ensembles basés sur une recherche binaire rapide sur la syntaxe R régulière. Ex: DT[x == 1]
et DT[x %in% some_vals]
créera automatiquement un index lors de la première exécution, qui sera ensuite utilisé sur des sous-ensembles successifs de la même colonne au sous-ensemble rapide en utilisant la recherche binaire. Cette fonctionnalité continuera d'évoluer. Vérifiez cet essentiel pour un bref aperçu de cette fonctionnalité.
De la façon dont filter()
est implémenté pour data.tables, il ne tire pas parti de cette fonctionnalité.
Une fonctionnalité de dplyr est qu'il fournit également une interface aux bases de données en utilisant la même syntaxe, ce que data.table ne fait pas pour le moment.
Ainsi, vous devrez peser ces points (et probablement d'autres) et décider en fonction de si ces compromis sont acceptables pour vous.
HTH
(1) Notez que l'efficacité de la mémoire a un impact direct sur la vitesse (d'autant plus que les données grossissent), car le goulot d'étranglement dans la plupart des cas est de déplacer les données de la mémoire principale vers le cache (et d'utiliser autant que possible les données du cache - réduire les échecs de cache - afin de réduire l'accès à la mémoire principale). Ne pas entrer dans les détails ici.
dplyr
méthodes pour les tableaux de données, mais le tableau de données a également ses propres méthodes comparables