Ich habe eine data.table
(~ 30 Millionen Zeilen) aus einer datetime
Säule in POSIXct
Format, eine id
Spalte und einige andere Spalten (im Beispiel, verließen ich nur eine irrelevant Spalte x
zu demonstrieren, dass es andere Spalten gibt, die beibehalten werden müssen). A dput
ist am Ende des Beitrags.Subset Beobachtungen, die von mindestens 30 Minuten Zeit unterscheiden
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
Q: Für jeden id
, ich brauche nur die Beobachtungen der Teilmenge, die von mehr als 30 Minuten Zeit abweichen. Was könnte ein effizienter data.table
Ansatz sein, um dies zu tun (wenn möglich, ohne umfangreiche Schleifen)?
Die Logik kann auch als (wie in meinem Kommentar unten) beschrieben werden:
Per-ID die erste Zeile immer gehalten wird. Die nächste Reihe, die mindestens 30 Minuten nach dem ersten ist, soll auch behalten werden. Nehmen wir an, dass Zeile gehalten werden soll Zeile 4. Dann wird die Zeitunterschiede zwischen Zeile 4 und Reihen 5 berechnen: n und halten Sie die erste, die von mehr als 30 Minuten und so auf
Im dput unterscheidet unten fügte ich eine Spalte keep
hinzu, um anzugeben, welche Zeilen in diesem Beispiel beibehalten werden sollten, da sie sich um mehr als 30 Minuten von der vorherigen Beobachtung unterscheiden, die pro ID beibehalten wird. Die Schwierigkeit besteht darin, dass es notwendig erscheint, die Zeitunterschiede iterativ zu berechnen (oder zumindest kann ich mir im Moment keinen effizienteren Ansatz vorstellen).
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]
Erläuterung der keep
Säule:
- Zeilen 2: 3 von weniger als 30 Minuten von der Reihe unterscheiden 1 ->
- Row 4 unterscheidet sich von der Reihe von mehr als 30 Minuten löschen 1 - halten>
- Reihe 5 dufferes von weniger als 30 Minuten von Zeile 4 ->
- Row 6 unterscheidet sich von der Reihe von mehr als 30 Minuten löschen 4 -> halten
- ...
gewünschte Ausgabe:
desiredDT <- DT[(keep)]
Danke für drei Experten-Antworten, die ich erhielt. Ich habe sie an 1 und 10 Millionen Datenzeilen getestet. Hier ein Auszug der Benchmarks.
a) 1 Million Zeilen
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 Millionen Zeilen
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
Offenbar @ data.table Ansatz Frank und @ Roland RCPP basierte Lösung ähnlich sind, in der Leistung mit RCPP ein mit leichter Vorteil, während @ eddi Ansätze waren immer noch schnell, aber nicht so leistungsfähig wie die anderen.
Allerdings, wenn ich für die Gleichstellung der Lösungen geprüft, fand ich, dass @ Roland Ansatz hat ein etwas anderes Ergebnis als die anderen:
a) 1 Million Zeilen
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 Millionen
Reihenall.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
Meine heutige Annahme ist, dass dieser Unterschied in Zusammenhang stehen könnte, ob die differnce> 30 Minuten oder> = 30 Minuten, obwohl ich darüber noch nicht sicher bin.
Schlussgedanke: Ich entschied mich aus zwei Gründen mit der Lösung von Frank zu gehen: 1. Es funktioniert sehr gut, fast gleich mit der Rcpp-Lösung, und 2. Es braucht kein anderes Paket, mit dem ich nicht sehr bin doch vertraut (ich verwende data.table sowieso)
Dies sind die Arten von Aufgaben, bei denen ich denke, dass eine gute 'C/C++' Lösung wertvoll ist. Es gibt keinen offensichtlichen R-vektorisierten Weg und das Schreiben der beschriebenen Bedingungen sollte in C oder C++ ziemlich einfach sein. Wenn Sie ein wenig wissen, wie man 'C/C++' Funktionen schreibt, die von R aufrufbar sind, würde ich diese Route vorschlagen. – nicola
sollte einfach mit überlappenden Join sein, bereiten Sie einfach die * von * und * bis * Daten für jede 'ID' – jangorecki
@jangorecki Ich hatte gehofft, dass eine solche Lösung existiert, aber ich weiß nicht die von und zu Daten apriori. Oder haben Sie eine Idee, wie Sie diese berechnen können? –