Ich mag dieses data.table-Zeug, sowohl wegen seiner Ausführungsgeschwindigkeit als auch wegen seiner sparsamen Art der Skripterstellung.
Ich benutze es auch auf kleinen Tischen.
Ich unterteile Tabellen regelmäßig auf diese Weise: DT[, .(id1, id5)]
und nicht so: DT[, c("id1", "id5")]
Heute habe ich die Geschwindigkeit der beiden gemessen und war erstaunt über den Geschwindigkeitsunterschied auf kleinen Tischen. Die sparsame Methode ist viel langsamer.
Ist dieser Unterschied gewollt?
Gibt es den Anspruch, den sparsamen Weg zu gehen, um sich in Bezug auf die Ausführungsgeschwindigkeit dem anderen anzunähern?
(Es zählt, wenn ich mehrere kleine Tabellen wiederholt unterteilen muss.)
Ubuntu 18.04
R-Version 3.5.3 (2019-03-11)
data.table 1.12.0
Arbeitsspeicher 32 GB
Intel® Core™ i7-8565U CPU @ 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
Sie können die Formatierung Ihres Beitrags korrigieren, indem Sie vor und nach dem Codeabschnitt eine einzelne Zeile mit drei Backticks verwenden:
```
code
```
Es zählt, wenn ich mehrere kleine Tabellen wiederholt unterteilen muss.
Ich denke, das wiederholte Auswählen von Spalten aus kleinen Tabellen ist etwas, das vermieden werden sollte und in den meisten Fällen vermieden werden kann ...? Da j
in DT[i, j, by]
eine so große Vielfalt an Eingaben unterstützt und optimiert, denke ich, dass es natürlich ist, dass es etwas Overhead beim Parsen gibt.
In Bezug auf andere Möglichkeiten, Ihr Problem anzugehen (und vielleicht passt dies besser zu Stack Overflow, wenn Sie mehr darüber sprechen möchten) ... Je nachdem, was Sie sonst noch mit der Tabelle machen möchten, können Sie einfach die anderen Spalten löschen , DT[, setdiff(names(DT), cols) := NULL]
und verwenden Sie DT direkt weiter.
Wenn Sie immer noch lieber die Teilmenge nehmen möchten, ist das Greifen von Spaltenzeigern viel schneller als jede der hier in Betracht gezogenen Optionen, obwohl sich Änderungen am Ergebnis auf diese Weise auf die ursprüngliche Tabelle auswirken:
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
(Ich habe die Randomisierung aus Ihrem Beispiel herausgenommen und im Benchmark # Mal reduziert, weil ich ungeduldig war.)
Ich habe nie eine Möglichkeit gefunden, die Listenteilmenge von R direkt aufzurufen (die nach den unclass
oben verwendet wird).
In Bezug auf "Bearbeitungen des Ergebnisses ändern die ursprüngliche Tabelle" meine ich:
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, ich habe heute etwas Neues und Schnelles gelernt (die Spinner), und ich habe zur Kenntnis genommen, dass es einen Kompromiss zwischen Geschwindigkeit und sparsamer Codierung gibt. Das Glas ist also halb voll! Danke!
Ich denke, #852 verwandt
Hilfreichster Kommentar
Sie können die Formatierung Ihres Beitrags korrigieren, indem Sie vor und nach dem Codeabschnitt eine einzelne Zeile mit drei Backticks verwenden:
Ich denke, das wiederholte Auswählen von Spalten aus kleinen Tabellen ist etwas, das vermieden werden sollte und in den meisten Fällen vermieden werden kann ...? Da
j
inDT[i, j, by]
eine so große Vielfalt an Eingaben unterstützt und optimiert, denke ich, dass es natürlich ist, dass es etwas Overhead beim Parsen gibt.In Bezug auf andere Möglichkeiten, Ihr Problem anzugehen (und vielleicht passt dies besser zu Stack Overflow, wenn Sie mehr darüber sprechen möchten) ... Je nachdem, was Sie sonst noch mit der Tabelle machen möchten, können Sie einfach die anderen Spalten löschen ,
DT[, setdiff(names(DT), cols) := NULL]
und verwenden Sie DT direkt weiter.Wenn Sie immer noch lieber die Teilmenge nehmen möchten, ist das Greifen von Spaltenzeigern viel schneller als jede der hier in Betracht gezogenen Optionen, obwohl sich Änderungen am Ergebnis auf diese Weise auf die ursprüngliche Tabelle auswirken:
(Ich habe die Randomisierung aus Ihrem Beispiel herausgenommen und im Benchmark # Mal reduziert, weil ich ungeduldig war.)
Ich habe nie eine Möglichkeit gefunden, die Listenteilmenge von R direkt aufzurufen (die nach den
unclass
oben verwendet wird).In Bezug auf "Bearbeitungen des Ergebnisses ändern die ursprüngliche Tabelle" meine ich: