Pourquoi courir plus vite « unique » sur une trame de données qu'une matrice R?
-
26-10-2019 - |
Question
J'ai commencé à croire que les trames de données détiennent aucun avantage sur les matrices, sauf pour la commodité notationnelle. Cependant, j'ai remarqué cette bizarrerie lors de l'exécution unique
sur des matrices et des trames de données:. Il semble courir plus vite sur une trame de données
a = matrix(sample(2,10^6,replace = TRUE), ncol = 10)
b = as.data.frame(a)
system.time({
u1 = unique(a)
})
user system elapsed
1.840 0.000 1.846
system.time({
u2 = unique(b)
})
user system elapsed
0.380 0.000 0.379
Les résultats de cadencement divergent sensiblement plus que le nombre de lignes augmente. Donc, il y a deux parties à cette question.
-
Pourquoi est-ce plus lent pour une matrice? Il semble plus rapide de se convertir à une trame de données, exécutez
unique
, puis reconvertir. -
Y at-il raison de ne pas simplement
unique
envelopper dansmyUnique
, qui fait les conversions en partie # 1?
Note 1. Etant donné qu'une matrice est atomique, il semble que unique
devrait être plus rapide pour une matrice, plutôt que plus lente. Être capable d'itérer sur de taille fixe, des blocs contigus de mémoire doit être généralement plus rapide que la course des blocs séparés sur des listes chaînées (je suppose que la façon dont les cadres de données sont mises en œuvre ...).
Note 2. Comme l'ont démontré les performances de data.table
, en cours d'exécution unique
sur une trame de données ou une matrice est une idée relativement mauvaise - voir la réponse par Matthew Dowle et les commentaires des temps relatifs. J'ai migré beaucoup d'objets à des tables de données, et cette performance est une autre raison de le faire. Ainsi, bien que les utilisateurs devraient être bien servis à adopter des tableaux de données, pour des raisons pédagogiques / communautaires, je vais laisser la question ouverte pour l'instant en ce qui concerne la pourquoi Cette mesure tient plus sur des objets de la matrice. Les réponses ci-dessous l'adresse où ne déplacement de temps, et comment les autres peut-on obtenir de meilleures performances (à savoir des tableaux de données). La réponse à pourquoi est à portée de main - le code peut être trouvé par unique.data.frame
et unique.matrix
. :) explication Une anglaise de ce qu'il fait et pourquoi est tout ce qui fait défaut.
La solution
-
Dans cette mise en œuvre,
unique.matrix
est le même queunique.array
> identical(unique.array, unique.matrix)
[1] TRUE
-
unique.array
doit gérer des tableaux multidimensionnels qui nécessite un traitement supplémentaire à « effondrement » des dimensions supplémentaires (ces appels supplémentaires àpaste()
) qui ne sont pas nécessaires dans le cas 2 dimensions. La section clé du code est:collapse <- (ndim > 1L) && (prod(dx[-MARGIN]) > 1L)
temp <- if (collapse) apply(x, MARGIN, function(x) paste(x, collapse = "\r"))
-
unique.data.frame
est optimisé pour le cas 2D,unique.matrix
est pas. Il pourrait être, comme vous le suggérez, il est tout simplement pas dans la mise en œuvre actuelle.
Notez que dans tous les cas (unique. {Tableau, matrice, data.table}) où il y a plus d'une dimension, il est la représentation de chaîne qui est comparée pour l'unicité. Pour les nombres à virgule flottante ce moyen 15 chiffres décimaux si
NROW(unique(a <- matrix(rep(c(1, 1+4e-15), 2), nrow = 2)))
est 1
tandis que
NROW(unique(a <- matrix(rep(c(1, 1+5e-15), 2), nrow = 2)))
et
NROW(unique(a <- matrix(rep(c(1, 1+4e-15), 1), nrow = 2)))
sont à la fois 2
. Êtes-vous que unique
est ce que vous voulez?
Autres conseils
-
Je ne sais pas, mais je suppose que parce
matrix
est un vecteur contigu, des copies de R dans des vecteurs de première colonne (comme undata.frame
) parce quepaste
a besoin d'une liste de vecteurs. Notez que les deux sont lents parce que les deuxpaste
d'utilisation. -
Peut-être parce que
unique.data.table
est déjà plusieurs fois plus rapide. S'il vous plaît mettre à niveau v1.6.7 en le téléchargeant à partir du référentiel R-Forge parce que le correctif a pourunique
que vous soulevez dans cette question.data.table
n'utilise paspaste
faireunique
.
a = matrix(sample(2,10^6,replace = TRUE), ncol = 10) b = as.data.frame(a) system.time(u1<-unique(a)) user system elapsed 2.98 0.00 2.99 system.time(u2<-unique(b)) user system elapsed 0.99 0.00 0.99 c = as.data.table(b) system.time(u3<-unique(c)) user system elapsed 0.03 0.02 0.05 # 60 times faster than u1, 20 times faster than u2 identical(as.data.table(u2),u3) [1] TRUE
En tentant de répondre à ma propre question, en particulier la partie 1, nous pouvons voir où le temps est passé en regardant les résultats de Rprof
. J'ai couru ce nouveau, avec des éléments 5M.
Voici les résultats pour la première opération unique, (pour la matrice):
> summaryRprof("u1.txt")
$by.self
self.time self.pct total.time total.pct
"paste" 5.70 52.58 5.96 54.98
"apply" 2.70 24.91 10.68 98.52
"FUN" 0.86 7.93 6.82 62.92
"lapply" 0.82 7.56 1.00 9.23
"list" 0.30 2.77 0.30 2.77
"!" 0.14 1.29 0.14 1.29
"c" 0.10 0.92 0.10 0.92
"unlist" 0.08 0.74 1.08 9.96
"aperm.default" 0.06 0.55 0.06 0.55
"is.null" 0.06 0.55 0.06 0.55
"duplicated.default" 0.02 0.18 0.02 0.18
$by.total
total.time total.pct self.time self.pct
"unique" 10.84 100.00 0.00 0.00
"unique.matrix" 10.84 100.00 0.00 0.00
"apply" 10.68 98.52 2.70 24.91
"FUN" 6.82 62.92 0.86 7.93
"paste" 5.96 54.98 5.70 52.58
"unlist" 1.08 9.96 0.08 0.74
"lapply" 1.00 9.23 0.82 7.56
"list" 0.30 2.77 0.30 2.77
"!" 0.14 1.29 0.14 1.29
"do.call" 0.14 1.29 0.00 0.00
"c" 0.10 0.92 0.10 0.92
"aperm.default" 0.06 0.55 0.06 0.55
"is.null" 0.06 0.55 0.06 0.55
"aperm" 0.06 0.55 0.00 0.00
"duplicated.default" 0.02 0.18 0.02 0.18
$sample.interval
[1] 0.02
$sampling.time
[1] 10.84
Et pour la trame de données:
> summaryRprof("u2.txt")
$by.self
self.time self.pct total.time total.pct
"paste" 1.72 94.51 1.72 94.51
"[.data.frame" 0.06 3.30 1.82 100.00
"duplicated.default" 0.04 2.20 0.04 2.20
$by.total
total.time total.pct self.time self.pct
"[.data.frame" 1.82 100.00 0.06 3.30
"[" 1.82 100.00 0.00 0.00
"unique" 1.82 100.00 0.00 0.00
"unique.data.frame" 1.82 100.00 0.00 0.00
"duplicated" 1.76 96.70 0.00 0.00
"duplicated.data.frame" 1.76 96.70 0.00 0.00
"paste" 1.72 94.51 1.72 94.51
"do.call" 1.72 94.51 0.00 0.00
"duplicated.default" 0.04 2.20 0.04 2.20
$sample.interval
[1] 0.02
$sampling.time
[1] 1.82
Ce que nous constatons est que la version de la matrice passe beaucoup de temps sur apply
, paste
et lapply
. En revanche, la simple version de trame de données fonctionne duplicated.data.frame
et la plupart du temps est consacré à paste
, agrégation vraisemblablement des résultats.
Bien que cela explique où le temps va, il n'explique pas pourquoi ceux-ci ont différentes implémentations, ni les effets de simple changement d'un type d'objet à un autre.