Ho una data.table
(~ 30 milioni di righe) costituiti da una colonna datetime
in formato POSIXct
, una colonna id
e poche altre colonne (nell'esempio, ho appena lasciato un irrilevante colonna x
per dimostrare che ci sono altre colonne presenti che devono essere mantenute). A dput
si trova nella parte inferiore del post.osservazioni sottoinsieme che differiscono di tempo di almeno 30 minuti
head(DT)
# datetime x id
#1: 2016-04-28 16:20:18 0.02461368 1
#2: 2016-04-28 16:41:34 0.88953932 1
#3: 2016-04-28 16:46:07 0.31818101 1
#4: 2016-04-28 17:00:56 0.14711365 1
#5: 2016-04-28 17:09:11 0.54406602 1
#6: 2016-04-28 17:39:09 0.69280341 1
D: Per ogni id
, ho bisogno di sottoinsieme solo quelle osservazioni che differiscono di oltre il 30 minuti di tempo. Quale potrebbe essere un approccio efficace data.table
per farlo (se possibile, senza un loop esteso)?
La logica può anche essere descritta come (come nel mio commento qui sotto):
Per id la prima fila è sempre tenuto. La riga successiva che è almeno 30 minuti dopo la prima deve essere conservata. Supponiamo che riga essere mantenuto è fila 4. Quindi, calcolare differenze di tempo tra la riga 4 e righe 5: n e mantenere il primo che differisce di più di 30 minuti e così su
Nel dput di seguito, ho aggiunto una colonna keep
per indicare quali righe devono essere mantenute in questo esempio perché differiscono di oltre 30 minuti dall'osservazione precedente che viene mantenuta per id. La difficoltà è che sembra necessario calcolare le differenze temporali in modo iterativo (o almeno, al momento non riesco a pensare ad un approccio più efficiente).
library(data.table)
DT <- structure(list(
datetime = structure(c(1461853218.81561, 1461854494.81561,
1461854767.81561, 1461855656.81561, 1461856151.81561, 1461857949.81561,
1461858601.81561, 1461858706.81561, 1461859078.81561, 1461859103.81561,
1461852799.81561, 1461852824.81561, 1461854204.81561, 1461855331.81561,
1461855633.81561, 1461856311.81561, 1461856454.81561, 1461857177.81561,
1461858662.81561, 1461858996.81561), class = c("POSIXct", "POSIXt")),
x = c(0.0246136845089495, 0.889539316063747, 0.318181007634848,
0.147113647311926, 0.544066024711356, 0.6928034061566, 0.994269776623696,
0.477795971091837, 0.231625785352662, 0.963024232536554, 0.216407935833558,
0.708530468167737, 0.758459537522867, 0.640506813768297, 0.902299045119435,
0.28915973729454, 0.795467417687178, 0.690705278422683, 0.59414202044718,
0.655705799115822),
id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L),
keep = c(TRUE, FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE,
FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, TRUE)),
.Names = c("datetime", "x", "id", "keep"),
row.names = c(NA, -20L),
class = c("data.table", "data.frame"))
setkey(DT, id, datetime)
DT[, difftime := difftime(datetime, shift(datetime, 1L, NA,type="lag"), units = "mins"),
by = id]
DT[is.na(difftime), difftime := 0]
DT[, difftime := cumsum(as.numeric(difftime)), by = id]
Spiegazione della colonna keep
:
- file 2: 3 differiscono di meno di 30 minuti dalla fila 1 -> elimina
- fila 4 differisce da più di 30 minuti dalla fila 1 - > mantenere
- fila 5 dufferes di meno di 30 minuti dalla fila 4 -> cancella
- fila 6 differisce di più di 30 minuti dalla fila 4 -> mantenere
- ...
output desiderato:
desiredDT <- DT[(keep)]
Grazie per tre risposte di esperti che ho ricevuto. Li ho testati su 1 e 10 milioni di righe di dati. Ecco un estratto dei parametri di riferimento.
a) 1 milione di righe
microbenchmark(frank(DT_Frank), roland(DT_Roland), eddi1(DT_Eddi1), eddi2(DT_Eddi2),
times = 3L, unit = "relative")
#Unit: relative
# expr min lq mean median uq max neval
# frank(DT_Frank) 1.286647 1.277104 1.185216 1.267769 1.140614 1.036749 3
# roland(DT_Roland) 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 3
# eddi1(DT_Eddi1) 11.748622 11.697409 10.941792 11.647320 10.587002 9.720901 3
# eddi2(DT_Eddi2) 9.966078 9.915651 9.210168 9.866330 8.877769 8.070281 3
b) 10 milioni di righe
microbenchmark(frank(DT_Frank), roland(DT_Roland), eddi1(DT_Eddi1), eddi2(DT_Eddi2),
times = 3L, unit = "relative")
#Unit: relative
# expr min lq mean median uq max neval
# frank(DT_Frank) 1.019561 1.025427 1.026681 1.031061 1.030028 1.029037 3
# roland(DT_Roland) 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 3
# eddi1(DT_Eddi1) 11.567302 11.443146 11.301487 11.323914 11.176515 11.035143 3
# eddi2(DT_Eddi2) 9.796800 9.693823 9.526193 9.594931 9.398969 9.211019 3
A quanto pare, @ approccio data.table di Frank e @ soluzione basata Roland Rcpp sono simili in prestazioni con Rcpp avere un lieve vantaggio, mentre gli approcci di @ eddi erano ancora veloci ma non altrettanto performanti degli altri.
Tuttavia, quando ho controllato per l'uguaglianza delle soluzioni, ho scoperto che @ approccio di Roland ha un risultato leggermente diverso rispetto agli altri:
a) 1 milione di righe
all.equal(frank(DT_Frank), roland(DT_Roland))
#[1] "Component “datetime”: Numeric: lengths (982228, 982224) differ"
#[2] "Component “id”: Numeric: lengths (982228, 982224) differ"
#[3] "Component “x”: Numeric: lengths (982228, 982224) differ"
all.equal(frank(DT_Frank), eddi1(DT_Eddi1))
#[1] TRUE
all.equal(frank(DT_Frank), eddi2(DT_Eddi2))
#[1] TRUE
b) 10 milioni di filari
all.equal(frank(DT_Frank), roland(DT_Roland))
#[1] "Component “datetime”: Numeric: lengths (9981898, 9981891) differ"
#[2] "Component “id”: Numeric: lengths (9981898, 9981891) differ"
#[3] "Component “x”: Numeric: lengths (9981898, 9981891) differ"
all.equal(frank(DT_Frank), eddi1(DT_Eddi1))
#[1] TRUE
all.equal(frank(DT_Frank), eddi2(DT_Eddi2))
#[1] TRUE
mia ipotesi attuale è che questa differenza potrebbe essere correlato al fatto che il differnce è> 30 minuti o> = 30 minuti anche se non sono ancora sicuro di questo.
pensiero finale: ho deciso di andare con @ soluzione di Frank per due motivi: 1. si comporta molto bene, quasi uguale alla soluzione Rcpp, e 2. non richiede un altro pacchetto con il quale io non sono molto ancora familiarità (sto usando data.table comunque)
Questi sono i tipi di compiti in cui penso che una buona soluzione 'C/C++' sia preziosa. Non c'è un modo ovvio di R-vectorize e scrivere le condizioni che hai descritto dovrebbe essere piuttosto semplice in C o C++. Se si conosce un po 'come scrivere le funzioni 'C/C++' richiamabili da R, suggerirei quella rotta. – nicola
dovrebbe essere facile con join sovrapposti, basta preparare * da * e * a * le date per ciascun 'id' – jangorecki
@jangorecki Speravo che una soluzione del genere esista ma non conosco il da e alle date apriori. O hai un'idea di come calcolarli? –