J'aime ce truc data.table, à la fois pour sa vitesse d'exécution et pour sa manière parcimonieuse de scripter.
Je l'utilise même sur de petites tables également.
Je sous-ensemble régulièrement des tables de cette façon : DT[, .(id1, id5)]
et pas de cette façon : DT[, c("id1", "id5")]
Aujourd'hui j'ai mesuré la vitesse des deux et j'ai été étonné de la différence de vitesse sur les petites tables. La méthode parcimonieuse est beaucoup plus lente.
Cette différence est-elle intentionnelle ?
Aspire-t-on à faire converger la voie parcimonieuse en termes de rapidité d'exécution vers l'autre ?
(Cela compte quand je dois sous-ensemble plusieurs petites tables de manière répétitive.)
Ubuntu 18.04
R version 3.5.3 (2019-03-11)
data.table 1.12.0
RAM 32 Go
Processeur Intel® Core™ i7-8565U à 1,80 GHz × 8
library(data.table)
library(microbenchmark)
N <- 2e8
K <- 100
set.seed(1)
DT <- data.table(
id1 = sample(sprintf("id%03d", 1:K), N, TRUE), # large groups (char)
id2 = sample(sprintf("id%03d", 1:K), N, TRUE), # large groups (char)
id3 = sample(sprintf("id%010d", 1:(N/K)), N, TRUE), # small groups (char)
id4 = sample(K, N, TRUE), # large groups (int)
id5 = sample(K, N, TRUE), # large groups (int)
id6 = sample(N/K, N, TRUE), # small groups (int)
v1 = sample(5, N, TRUE), # int in range [1,5]
v2 = sample(5, N, TRUE), # int in range [1,5]
v3 = sample(round(runif(100, max = 100), 4), N, TRUE) # numeric e.g. 23.5749
)
microbenchmark(
DT[, .(id1, id5)],
DT[, c("id1", "id5")]
)
Unit: seconds
expr min lq mean median uq max neval
DT[, .(id1, id5)] 1.588367 1.614645 1.929348 1.626847 1.659698 12.33872 100
DT[, c("id1", "id5")] 1.592154 1.613800 1.937548 1.628082 2.184456 11.74581 100
N <- 2e5
DT2 <- data.table(
id1 = sample(sprintf("id%03d", 1:K), N, TRUE), # large groups (char)
id2 = sample(sprintf("id%03d", 1:K), N, TRUE), # large groups (char)
id3 = sample(sprintf("id%010d", 1:(N/K)), N, TRUE), # small groups (char)
id4 = sample(K, N, TRUE), # large groups (int)
id5 = sample(K, N, TRUE), # large groups (int)
id6 = sample(N/K, N, TRUE), # small groups (int)
v1 = sample(5, N, TRUE), # int in range [1,5]
v2 = sample(5, N, TRUE), # int in range [1,5]
v3 = sample(round(runif(100, max = 100), 4), N, TRUE) # numeric e.g. 23.5749
)
microbenchmark(
DT2[, .(id1, id5)],
DT2[, c("id1", "id5")]
)
Unit: microseconds
expr min lq mean median uq max neval
DT2[, .(id1, id5)] 1405.042 1461.561 1525.5314 1491.7885 1527.8955 2220.860 100
DT2[, c("id1", "id5")] 614.624 640.617 666.2426 659.0175 676.9355 906.966 100
Vous pouvez corriger la mise en forme de votre message en utilisant une seule ligne de trois backticks avant et après le morceau de code :
```
code
```
Ça compte quand je dois subdiviser plusieurs petites tables de manière répétitive.
Je suppose que la sélection répétée de colonnes à partir de petites tables est quelque chose qui devrait, et dans la plupart des cas, peut être évité... ? Étant donné que j
dans DT[i, j, by]
prend en charge et optimise une telle variété d'entrées, je pense qu'il est naturel qu'il y ait une surcharge lors de son analyse.
En ce qui concerne les autres façons d'aborder votre problème (et peut-être que cela conviendrait mieux à Stack Overflow si vous voulez en parler davantage) ... En fonction de ce que vous voulez faire d'autre avec la table, vous pouvez simplement supprimer les autres cols , DT[, setdiff(names(DT), cols) := NULL]
et continuez à utiliser DT directement.
Si vous préférez toujours prendre le sous-ensemble, la saisie des pointeurs de colonne est beaucoup plus rapide que l'une ou l'autre des options que vous avez envisagées ici, bien que de cette façon, les modifications apportées au résultat affectent la table d'origine :
library(data.table)
library(microbenchmark)
N <- 2e8
K <- 100
set.seed(1)
DT <- data.table(
id1 = sprintf("id%03d", 1:K), # large groups (char)
id2 = sprintf("id%03d", 1:K), # large groups (char)
id3 = sprintf("id%010d", 1:(N/K)), # small groups (char)
id4 = sample(K), # large groups (int)
id5 = sample(K), # large groups (int)
id6 = sample(N/K), # small groups (int)
v1 = sample(5), # int in range [1,5]
v2 = sample(5), # int in range [1,5]
v3 = round(runif(100, max = 100), 4), # numeric e.g. 23.5749
row = seq_len(N)
)
cols = c("id1", "id5")
microbenchmark(times = 3,
expression = DT[, .(id1, id5)],
index = DT[, c("id1", "id5")],
dotdot = DT[, ..cols],
oddball = setDT(lapply(setNames(cols, cols), function(x) DT[[x]]))[],
oddball2 = setDT(unclass(DT)[cols])[]
)
Unit: microseconds
expr min lq mean median uq max neval
expression 1249753.580 1304355.3415 1417166.9297 1358957.103 1500873.6045 1642790.106 3
index 1184056.302 1191334.4835 1396372.3483 1198612.665 1502530.3715 1806448.078 3
dotdot 1084521.234 1240062.2370 1439680.6980 1395603.240 1617260.4300 1838917.620 3
oddball 92.659 171.8635 568.5317 251.068 806.4680 1361.868 3
oddball2 66.582 125.9505 150.7337 185.319 192.8095 200.300 3
(J'ai retiré la randomisation de votre exemple et réduit # fois dans le benchmark parce que j'étais impatient.)
Je n'ai jamais trouvé de moyen d'appeler directement le sous-ensemble de liste de R (qui est utilisé après le unclass
ci-dessus).
En ce qui concerne "les modifications apportées au résultat modifieront la table d'origine", je veux dire :
myDT = data.table(a = 1:2, b = 3:4)
# standard way
res <- myDT[, "a"]
res[, a := 0]
myDT
# a b
# 1: 1 3
# 2: 2 4
# oddball, grabbing pointers
res2 <- setDT(unclass(myDT)["a"])
res2[, a := 0]
myDT
# a b
# 1: 0 3
# 2: 0 4
Ok, j'ai appris quelque chose de nouveau et de rapide (les excentriques) aujourd'hui et j'ai pris note qu'il y a un compromis entre la vitesse et le codage parcimonieux. Le verre est donc à moitié plein ! Merci!
Je suppose que # 852 est lié
Commentaire le plus utile
Vous pouvez corriger la mise en forme de votre message en utilisant une seule ligne de trois backticks avant et après le morceau de code :
Je suppose que la sélection répétée de colonnes à partir de petites tables est quelque chose qui devrait, et dans la plupart des cas, peut être évité... ? Étant donné que
j
dansDT[i, j, by]
prend en charge et optimise une telle variété d'entrées, je pense qu'il est naturel qu'il y ait une surcharge lors de son analyse.En ce qui concerne les autres façons d'aborder votre problème (et peut-être que cela conviendrait mieux à Stack Overflow si vous voulez en parler davantage) ... En fonction de ce que vous voulez faire d'autre avec la table, vous pouvez simplement supprimer les autres cols ,
DT[, setdiff(names(DT), cols) := NULL]
et continuez à utiliser DT directement.Si vous préférez toujours prendre le sous-ensemble, la saisie des pointeurs de colonne est beaucoup plus rapide que l'une ou l'autre des options que vous avez envisagées ici, bien que de cette façon, les modifications apportées au résultat affectent la table d'origine :
(J'ai retiré la randomisation de votre exemple et réduit # fois dans le benchmark parce que j'étais impatient.)
Je n'ai jamais trouvé de moyen d'appeler directement le sous-ensemble de liste de R (qui est utilisé après le
unclass
ci-dessus).En ce qui concerne "les modifications apportées au résultat modifieront la table d'origine", je veux dire :