Si j'utilise la syntaxe de dplyr au-dessus d'un datatable , est-ce que j'obtiens tous les avantages de vitesse de datatable tout en utilisant toujours la syntaxe de dplyr? En d'autres termes, est-ce que j'utilise mal la table de données si je l'interroge avec la syntaxe dplyr? Ou dois-je utiliser une pure syntaxe datable pour exploiter toute sa puissance.
Merci d'avance pour tout conseil. Exemple de code:
library(data.table)
library(dplyr)
diamondsDT <- data.table(ggplot2::diamonds)
setkey(diamondsDT, cut)
diamondsDT %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()) %>%
arrange(desc(Count))
Résultats:
# cut AvgPrice MedianPrice Count
# 1 Ideal 3457.542 1810.0 21551
# 2 Premium 4584.258 3185.0 13791
# 3 Very Good 3981.760 2648.0 12082
# 4 Good 3928.864 3050.5 4906
Voici l'équivalence datable que j'ai trouvée. Je ne sais pas s'il est conforme aux bonnes pratiques de DT. Mais je me demande si le code est vraiment plus efficace que la syntaxe de dplyr dans les coulisses:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
r
data.table
dplyr
Polymérase
la source
la source
dplyr
méthodes pour les tableaux de données, mais le tableau de données a également ses propres méthodes comparablesdplyr
est utilisé surdata.frame
s etdata.table
s correspondant , voir ici (et les références qui y sont).Réponses:
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()
etslice()
en dplyr)Supposons
DT
avec, disons 10 colonnes. Considérez ces expressions data.table:(1) donne le nombre de lignes dans la
DT
colonne wherea > 1
. (2) renvoiemean(b)
groupé parc,d
pour la même expressioni
que (1).Les
dplyr
expressions couramment utilisées seraient: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 colonnesb, c, d
pour les opérations successives. Pour surmonter cela, nous devonsselect()
colonnes apriori: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 lafilter()
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
Par exemple, dans data.table, vous pouvez faire:
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]
etDT[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.
la source
filter()
plus efficace ensummarise()
utilisant la même approche que dplyr utilise pour SQL - c'est-à-dire créer une expression et ne l'exécuter qu'une seule fois à la demande. Il est peu probable que cela soit implémenté dans un proche avenir car dplyr est assez rapide pour moi et la mise en œuvre d'un planificateur / optimiseur de requêtes est relativement difficile.Essayez-le.
Sur ce problème, il semble que data.table soit 2,4 fois plus rapide que dplyr en utilisant data.table:
Révisé sur la base du commentaire de Polymerase.
la source
microbenchmark
package, j'ai constaté que l'exécution dudplyr
code de l'OP sur la version originale (trame de données) dediamonds
prenait un temps médian de 0,012 seconde, alors que cela prenait un temps médian de 0,024 seconde après la conversiondiamonds
en table de données. L'exécution dudata.table
code de G. Grothendieck a pris 0,013 seconde. Au moins sur mon système, il ressembledplyr
etdata.table
a à peu près les mêmes performances. Mais pourquoi serait-dplyr
il plus lent lorsque la trame de données est d'abord convertie en table de données?Pour répondre à tes questions:
data.table
data.table
syntaxe pureDans de nombreux cas, ce sera un compromis acceptable pour ceux qui veulent la
dplyr
syntaxe, bien qu'il soit peut-être plus lent qu'avecdplyr
des trames de données simples.Un facteur important semble être que
dplyr
copier ledata.table
par défaut lors du regroupement. Considérez (en utilisant microbenchmark):Le filtrage est d'une vitesse comparable, mais le regroupement ne l'est pas. Je crois que le coupable est cette ligne dans
dplyr:::grouped_dt
:où la valeur par
copy
défaut estTRUE
(et ne peut pas facilement être changé en FALSE que je peux voir). Cela ne représente probablement pas 100% de la différence, mais les frais généraux généraux à eux seuls sur quelque chose de la taille ladiamonds
plus probable ne sont pas la différence complète.Le problème est que pour avoir une grammaire cohérente,
dplyr
le regroupement s'effectue en deux étapes. Il définit d'abord des clés sur une copie de la table de données d'origine qui correspondent aux groupes, et ce n'est que plus tard qu'il regroupe.data.table
alloue simplement de la mémoire pour le plus grand groupe de résultats, qui dans ce cas n'est qu'une ligne, ce qui fait une grande différence dans la quantité de mémoire à allouer.FYI, si quelqu'un s'en soucie, j'ai trouvé ceci en utilisant
treeprof
(install_github("brodieg/treeprof")
), un visualiseur d'arborescence expérimental (et toujours très alpha) pour laRprof
sortie:Notez que ce qui précède ne fonctionne actuellement que sur les macs AFAIK. Aussi, malheureusement,
Rprof
enregistre les appels du typepackagename::funname
comme anonymes, donc il pourrait en fait s'agir de tous lesdatatable::
appels à l'intérieurgrouped_dt
qui sont responsables, mais d'après des tests rapides, il semblait que c'étaitdatatable::copy
le plus gros.Cela dit, vous pouvez rapidement voir à quel point il n'y a pas beaucoup de frais généraux autour de l'
[.data.table
appel, mais il existe également une branche complètement séparée pour le regroupement.EDIT : pour confirmer la copie:
la source
Vous pouvez utiliser dtplyr maintenant, qui fait partie du tidyverse . Il vous permet d'utiliser les déclarations de style dplyr comme d'habitude, mais utilise une évaluation paresseuse et traduit vos déclarations en code data.table sous le capot. La surcharge de traduction est minime, mais vous tirez tous, sinon la plupart des avantages de data.table. Plus de détails sur le repo officiel de git ici et sur la page tidyverse .
la source