У меня есть data.table
(~ 30 миллионов строк), состоящий из столбца datetime
в формате POSIXct
, столбца id
и нескольких других столбцов (в примере я просто оставил один нерелевантный столбец x
, чтобы продемонстрировать наличие других столбцов, которые необходимо сохранить). A dput
находится в нижней части сообщения.
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: Для каждого id
мне нужно подмножество только тех наблюдений, которые отличаются более чем на 30 минут. Каким может быть эффективный подход data.table
для этого (если возможно, без широкого цикла)?
Логику можно также описать как (как в моем комментарии ниже):
На id всегда сохраняется первая строка. Следующая строка не менее 30 через несколько минут после первого. Предположим, что строка следует сохранить строку 4. Затем вычислить временные разницы между строками 4 и строки 5: n и сохранить первый, который отличается более чем на 30 минут и так далее. на
В нижеприведенной таблице я добавил colum keep
, чтобы указать, какие строки должны храниться в этом примере, потому что они отличаются более чем на 30 минут от предыдущего наблюдения, которое хранится на id. Трудность состоит в том, что, по-видимому, необходимо вычислить временные различия итеративно (или, по крайней мере, я не могу думать о более эффективном подходе на данный момент).
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]
Объяснение столбца keep
:
- Строки 2: 3 отличаются менее чем на 30 минут от строки 1 → delete
- Строка 4 отличается более чем на 30 минут от строки 1 → сохранить
- Строка 5 dufferes менее чем за 30 минут от строки 4 → delete
- Строка 6 отличается более чем на 30 минут от строки 4 → сохранить
- ...
Требуемый вывод:
desiredDT <- DT[(keep)]
Спасибо за три экспертных ответа, которые я получил. Я тестировал их на 1 и 10 миллионов строк данных. Здесь выдержка из тестов.
a) 1 миллион строк
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 миллионов строк
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
По-видимому, подход @Frank data.table и решение на основе @Roland Rcpp аналогичны по производительности с Rcpp, имеющим небольшое преимущество, в то время как подходы @eddi все еще были быстрыми, но не такими эффективными, как другие.
Однако, когда я проверил равенство решений, я обнаружил, что подход @Roland имеет несколько иной результат, чем другие:
a) 1 миллион строк
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 миллионов строк
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
Мое нынешнее предположение состоит в том, что это различие может быть связано с тем, отличается ли разницa > 30 минут или >= 30 минут, хотя я пока не уверен в этом.
Заключительная мысль: я решил пойти с решением @Frank по двум причинам: 1. он работает очень хорошо, почти равен решению Rcpp, и 2. он не требует другого пакета, с которым я не очень хорошо знаком еще (я все равно использую data.table)