R: как выполнить повторную выборку внутридневных данных на уровне группы?

#r #data.table #dplyr #lubridate

#r #data.table #dplyr #lubridate

Вопрос:

Рассмотрим следующий фрейм данных

 time <-c('2016-04-13 23:07:45','2016-04-13 23:07:50','2016-04-13 23:08:45','2016-04-13 23:08:45'
         ,'2016-04-13 23:08:45','2016-04-13 23:07:50','2016-04-13 23:07:51')
group <-c('A','A','A','B','B','B','B')
value<- c(5,10,2,2,NA,1,4)
df<-data.frame(time,group,value)

> df
                 time group value
1 2016-04-13 23:07:45     A     5
2 2016-04-13 23:07:50     A    10
3 2016-04-13 23:08:45     A     2
4 2016-04-13 23:08:45     B     2
5 2016-04-13 23:08:45     B    NA
6 2016-04-13 23:07:50     B     1
7 2016-04-13 23:07:51     B     4
  

Я хотел бы выполнить повторную выборку этого фрейма данных в 5 seconds level group level и вычислить сумму value для каждого time-interval group value .

Интервал должен быть закрыт слева и открыт справа. Например, первая строка вывода должна быть

2016-04-13 23:07:45 A 5 поскольку первый 5-секундный интервал равен [2016-04-13 23:07:45, 2016-04-13 23:07:50[

Как я могу это сделать в любом dplyr или data.table ? Нужно ли импортировать lubridate временные метки?

Комментарии:

1. исправлены опечатки. большое спасибо!

2. Я думаю foverlaps data.table , что в этом случае может быть полезно. Я посмотрю, смогу ли я выполнить asnwer

3.спасибо, или, может быть, с помощью a dplyr group_by ? Понятия не имею

4. @Noobie, просто для пояснения, я предполагаю, что интервал открыт справа … (вот почему вы не включаете 23:07:50 значение в сумму первой строки вывода)? Кроме того, что вы делаете с группами, если они разные, но происходят в один и тот же интервал времени (например 7 2016-04-13 23:07:51 B 4 , и, допустим, следующая строка была 8 2016-04-13 23:07:53 A 12 )? Мы игнорируем разницу в группах и просто суммируем 1, 4 и 12?

5. Привет, Джозеф, да, закрыто слева и открыто справа. Для вторых точек агрегирование выполняется на уровне временной группы, поэтому две точные временные метки для двух разных групп никогда не будут перепутаны

Ответ №1:

С последней версией (1.9.8 ) data.table :

 library(data.table)

# convert to data.table, fix time, add future time
setDT(df)
df[, time := as.POSIXct(time)][, time.5s := time   5]

# use non-equi join to filter on the required intervals and sum
df[, newval := df[df, on = .(group, time < time.5s, time >= time),
                  sum(value, na.rm = T), by = .EACHI]$V1]
df
#                  time group value             time.5s newval
#1: 2016-04-13 23:07:45     A     5 2016-04-13 23:07:50      5
#2: 2016-04-13 23:07:50     A    10 2016-04-13 23:07:55     10
#3: 2016-04-13 23:08:45     A     2 2016-04-13 23:08:50      2
#4: 2016-04-13 23:08:45     B     2 2016-04-13 23:08:50      2
#5: 2016-04-13 23:08:45     B    NA 2016-04-13 23:08:50      2
#6: 2016-04-13 23:07:50     B     1 2016-04-13 23:07:55      5
#7: 2016-04-13 23:07:51     B     4 2016-04-13 23:07:56      4
  

Комментарии:

1. Просто измените неравенства on со строгих на нестрогие по мере необходимости.

2. разве 6 и 7 не должны быть сгруппированы вместе? Они находятся в одном и том же 5-секундном интервале. Похоже, вы просто добавляете 5 секунд к каждому результату.

3. верно. 6 и 7 следует суммировать вместе. хороший улов @JosephWood

4. # 7 находится в будущем интервале # 6, но # 6 не находится в будущем интервале # 7 .. если вы ищете совпадение future past, добавьте time.neg.5s := time - 5 столбец и сравните с ним

5. У меня такое чувство, что время от времени.5s могут быть созданы в одном вызове, но я не уверен, что это в любом случае улучшит скорость

Ответ №2:

Как насчет этого:

 library(dplyr)
Group5 <- function(myDf) {
    myDf$time <- ymd_hms(myDf$time)
    myDf$timeGroup <- floor_date(myDf$time, unit = "5 seconds")
    summarise(myDf %>% group_by(group, timeGroup), sum(value, na.rm = TRUE))
}

Group5(df)
Source: local data frame [5 x 3]
Groups: group [?]

   group           timeGroup `sum(value, na.rm = TRUE)`
  <fctr>              <dttm>                      <dbl>
1      A 2016-04-13 23:07:45                          5
2      A 2016-04-13 23:07:50                         10
3      A 2016-04-13 23:08:45                          2
4      B 2016-04-13 23:07:50                          5
5      B 2016-04-13 23:08:45                          2
  

Он использует преимущества floor_date и ymd_hms из lubridate , чтобы поместить время каждой даты в правильное групповое время.

Вот более экзотический пример:

 set.seed(500)
time <- ymd_hms('2016-04-13 23:07:45')   sample(-10^3:10^3, 10^5, replace=TRUE)
group <- rep(LETTERS[1:20], each = 5000)
value <- rep(NA, 10^5)
value[sample(10^5, 95000)] <- sample(100, 95000, replace=TRUE)
df2 <- data.frame(time,group,value)

head(df2)
                 time group value
1 2016-04-13 23:18:53     A    53
2 2016-04-13 23:15:15     A    NA
3 2016-04-13 23:23:36     A    40
4 2016-04-13 23:06:40     A    23
5 2016-04-13 23:18:10     A    74
6 2016-04-13 22:57:56     A    65
  

Вызывая его, мы имеем:

 Group5(df2)
Source: local data frame [8,020 x 3]
Groups: group [?]

    group           timeGroup `sum(value, na.rm = TRUE)`
   <fctr>              <dttm>                      <int>
1       A 2016-04-13 22:51:05                        379
2       A 2016-04-13 22:51:10                        646
3       A 2016-04-13 22:51:15                        391
4       A 2016-04-13 22:51:20                       1118
5       A 2016-04-13 22:51:25                        745
6       A 2016-04-13 22:51:30                        546
7       A 2016-04-13 22:51:35                        884
8       A 2016-04-13 22:51:40                        711
9       A 2016-04-13 22:51:45                        526
10      A 2016-04-13 22:51:50                        484
# ... with 8,010 more rows
  

Комментарии:

1. Хорошая догадка из вопроса Op, не думал о округлении с интервалом в 5 секунд.

Ответ №3:

Если вы хотите иметь отдельные объекты данных для каждой группы, вы могли бы использовать xts для решения вашей проблемы вместо data.table , для каждого группового объекта. xts period.apply автоматически обработает ваш интервал, если он закрыт с левой стороны, но открыт и справа (что действительно полезно для агрегирования финансовых тиковых данных с частотой баров. Вы не получите двойного подсчета тиков на краях интервала для последовательных баров / интервалов):

 time <-c('2016-04-13 23:07:45','2016-04-13 23:07:55','2016-04-13 23:08:45','2016-04-13 23:08:45'
         ,'2016-04-13 23:08:45','2016-04-13 23:07:50','2016-04-13 23:07:51')
group <-c('A','A','A','B','B','B','B')

value<- c(5,10,2,2,NA,1,4)
df=data.frame(time,group,value)

library(quantmod)
library(lubridate)
df$time = ymd_hms(df$time)

# In this example, model group B object: (You can easily generalise this with a loop or lapply over multiple groups)
df_grp <- df[df$group == "B", ]
x.df_grp <- xts(df_grp$value, order.by = df_grp$time) 
ep <- endpoints(x.df_grp, on = "seconds", k = 5)
# You can replace sum by any useful function.  Pass in extra arguments to period.apply that correspond to FUN, here na.rm = T, to avoid having sum returning NA in your group B row:
x.df_grp_5sec <- period.apply(x.df_grp, ep, FUN = sum, na.rm = TRUE)
# Align timestamps to end of each 5 sec interval by default (helps avoid lookforward bias when merging time series data on different time frequencies):
x.df_grp_5sec <- align.time(x.df_grp_5sec, 5)
# Now record timestamps at start of each 5 sec interval:
.index(x.df_grp_5sec) <- .index(x.df_grp_5sec) - 5

#result:
> x.df_grp_5sec
                    [,1]
2016-04-13 23:07:50    5
2016-04-13 23:08:45    2
  

Ответ №4:

Лучшая идея, к которой я пришел data.table :

 library(data.table)
setDT(df)
df[, result:={lv=df$group==group; dt=difftime( df$time, time, units="sec"); print(dt); sum(df$value[lv amp; dt >= 0 amp; dt < 5],na.rm=TRUE)},by=1:nrow(df)]
  

Вывод:

                   time group value result
1: 2016-04-13 23:07:45     A     5      5
2: 2016-04-13 23:07:50     A    10     10
3: 2016-04-13 23:08:45     A     2      2
4: 2016-04-13 23:08:45     B     2      2
5: 2016-04-13 23:08:45     B    NA      2
6: 2016-04-13 23:07:50     B     1      5
7: 2016-04-13 23:07:51     B     4      4
  

j Часть в деталях:

 lv=df$group==group # Create a logical vector to filter at end
dt=abs( difftime( df$time, time, units="sec")) # compute the time difference in seconds between current row and all others
 sum(df$value[lv amp; dt >= 0 amp; dt < 5]) # Sum the values where in same group and the difference in seconds is between 0 and 5 secs, 0 included, 5 excluded 
  

это result:={} позволяет нам создавать результат в виде вызова функции. это by=1:nrow(df) позволяет работать строка за строкой.

И отфильтровать полученный результат, чтобы получить только начальную точку:

 > df[,.SD[!duplicated(result)],by=group]
   group                time value result
1:     A 2016-04-13 23:07:45     5      5
2:     A 2016-04-13 23:07:50    10     10
3:     A 2016-04-13 23:08:45     2      2
4:     B 2016-04-13 23:08:45     2      2
5:     B 2016-04-13 23:07:50     1      5
6:     B 2016-04-13 23:07:51     4      4
  

Комментарии:

1. Почему у вас result для строки 7 равно 5? Предыдущая строка не входит в диапазон [time, time 5) .

2. Строка @eddi 6 находится в диапазоне в пределах 5 секунд, если не требуется, просто удалите вызов abs в разное время

3. @Eddie, я экстраполировал вопрос Op о диапазоне, действительно, вызов abs является излишним для ответа в этом случае

4. @Tensibai, не уверен, вопрос это или предложение… должны ли мы удалять NAs при суммировании?

5. Кроме того, вам необходимо изменить data.frame на data.table .. мне потребовалось пару раз получать ошибки, прежде чем я понял, что мне нужно data.table .