Я рассчитываю получить самое близкое предыдущее значение для каждого 15-минутного интервала (то есть 12:00:00, 12:15:00, 12:30:00 AM) для произвольного числа показаний между интервалами.

Например, я ищу df:

Timestamp   Value (kW)
8/12/2018 23:00:06  51
8/13/2018 0:00:16   52
8/13/2018 0:10:26   53
8/13/2018 0:14:36   54
8/13/2018 0:15:00   55
8/13/2018 0:19:57   56
8/13/2018 0:29:09   57
8/13/2018 0:38:17   58
8/13/2018 0:44:59   59
8/13/2018 0:45:00   60
8/13/2018 0:58:47   61
8/13/2018 1:01:57   62


structure(list(Timestamp = c("8/12/2018 23:00:00", "8/13/2018 0:00:00", 
"8/13/2018 0:10:00", "8/13/2018 0:14:00", "8/13/2018 0:15:00", 
"8/13/2018 0:19:00", "8/13/2018 0:29:00", "8/13/2018 0:38:00", 
"8/13/2018 0:44:00", "8/13/2018 0:45:00", "8/13/2018 0:58:00", 
"8/13/2018 1:01:00"), Value..kW. = 51:62), .Names = c("Timestamp", 
"Value..kW."), class = "data.frame", row.names = c(NA, -12L))

Посмотрите что-нибудь ближе к df2:

Interval    Value
8/13/2018 0:00:00   51
8/13/2018 0:15:00   55
8/13/2018 0:30:00   57
8/13/2018 0:45:00   60
8/13/2018 1:00:00   61

Обратите также внимание на seconds. Я думаю, что nalocf функция из zoo и dplyr или data.table может привести меня туда-сюда. Открыто для других пакетов.

1
longlivebrew 20 Авг 2018 в 22:23

3 ответа

Лучший ответ

Это может быть хорошим приложением для data.table переходящих объединений с опцией «ближайший».

Первым шагом является получение данных в объект типа data.table с правильно отформатированной отметкой времени POSIXct.

library(data.table)

DT <- structure(list(Timestamp = c("8/12/2018 23:00:00", "8/13/2018 0:00:00", 
                             "8/13/2018 0:10:00", "8/13/2018 0:14:00", "8/13/2018 0:15:00", 
                             "8/13/2018 0:19:00", "8/13/2018 0:29:00", "8/13/2018 0:38:00", 
                             "8/13/2018 0:44:00", "8/13/2018 0:45:00", "8/13/2018 0:58:00", 
                             "8/13/2018 1:01:00"), Value..kW. = 51:62), .Names = c("Timestamp", 
                                                                                   "Value..kW."), class = "data.frame", row.names = c(NA, -12L))
## Convert from data.frame to data.table
setDT(DT)

## Convert to POSIXct
DT[,Timestamp := as.POSIXct(Timestamp, format = "%m/%d/%Y %H:%M:%S", tz = "UTC")]

Получив это, вы можете сгенерировать другую таблицу с 15-минутным интервалом.

## Get Start and Ends
Start <- min(as.POSIXct(cut.POSIXt(DT[,Timestamp],breaks = c("15 min")), tz = "UTC"))
End <- max(as.POSIXct(cut.POSIXt(DT[,Timestamp],breaks = c("15 min")), tz = "UTC"))
## Generate data.table with a sequence
SummaryDT <- data.table(TimeStamp15 = seq.POSIXt(from = Start, to = End, by = "15 min"))

print(SummaryDT)
#            TimeStamp15
# 1: 2018-08-12 23:00:00
# 2: 2018-08-12 23:15:00
# 3: 2018-08-12 23:30:00
# 4: 2018-08-12 23:45:00
# 5: 2018-08-13 00:00:00
# 6: 2018-08-13 00:15:00
# 7: 2018-08-13 00:30:00
# 8: 2018-08-13 00:45:00
# 9: 2018-08-13 01:00:00

Затем вы можете установить ключи и получить самое близкое значение для каждых 15 минут, используя обновление по скользящему соединению.

## Set keys
setkey(SummaryDT,TimeStamp15)
setkey(DT,Timestamp)

## Create a new column in SummaryDT with the closest measurement
SummaryDT[DT, Closest_Value_kW := `i.Value..kW.` , roll = "nearest"]
print(SummaryDT)
#            TimeStamp15 Closest_Value_kW
# 1: 2018-08-12 23:00:00               51
# 2: 2018-08-12 23:15:00               NA
# 3: 2018-08-12 23:30:00               NA
# 4: 2018-08-12 23:45:00               NA
# 5: 2018-08-13 00:00:00               52
# 6: 2018-08-13 00:15:00               56
# 7: 2018-08-13 00:30:00               57
# 8: 2018-08-13 00:45:00               60
# 9: 2018-08-13 01:00:00               62

Если вы новичок в data.table, это может быть совсем немного для понимания, этот пример находится на продвинутом конце спектра - Страница" Начало работы " на сайте data.table может быть хорошим местом для начала, если вы вообще не использовали data.table перед.

Выполнение help("data.table") даст вам краткую рецензию, но есть один хороший пример некоторых возможностей, написанных Беном Горманом в его блоге - Анализ Гормана: R - переходящие объединения таблиц Data.Table и другие работы Робера Норберга в его блоге bRogramming: Понимание data.table Rolling Joins, которое может помочь лучше понять.

Обновление. Похоже, что вы, возможно, захотите переносить только наблюдения, вместо того, чтобы обязательно указывать «самое близкое» значение - в этом случае вариант будет следующим:

(Используя тот же DT как отправную точку)

## Get Start and Ends
Start <- min(as.POSIXct(cut.POSIXt(DT[,Timestamp],breaks = c("15 min")), tz = "UTC"))
End <- max(as.POSIXct(cut.POSIXt(DT[,Timestamp],breaks = c("15 min"),), tz = "UTC"))
## Generate data.table with a sequence
SummaryDT <-data.table(TimeStamp15 = seq.POSIXt(from = Start, to = End, by = "15 min"))

## Set keys
setkey(SummaryDT,TimeStamp15)
setkey(DT,Timestamp)
## Do a rolling join
FinalDT <- DT[SummaryDT, roll = +Inf]

print(FinalDT)
#              Timestamp Value..kW.
# 1: 2018-08-12 23:00:00         51
# 2: 2018-08-12 23:15:00         51
# 3: 2018-08-12 23:30:00         51
# 4: 2018-08-12 23:45:00         51
# 5: 2018-08-13 00:00:00         52
# 6: 2018-08-13 00:15:00         55
# 7: 2018-08-13 00:30:00         57
# 8: 2018-08-13 00:45:00         60
# 9: 2018-08-13 01:00:00         61
4
Matt Summersgill 20 Авг 2018 в 21:00

Это может немного отличаться от вашего примера. Я не уверен, что ваш пример вывод на 100% правильно. например, как насчет данных от 12/8?

Библиотека lubridate имеет много полезных функций даты и времени. Это преобразует символ в дату и округляет до ближайшего периода. (Есть также функции floor_date и ceiling_date, которые округляются вниз или вверх соответственно).

library(dplyr) 
library(lubridate)
df %>% 
  # ensure timestamp is a date type and round to the nearest fifteen minutes
  mutate(ts = mdy_hm(Timestamp),
         period = round_date(ts, unit = "15 minutes")) %>%
  # group into periods 
  group_by(period) %>%
  # grab the first row in each period, orderd by the timestamp (use -1 for last)
  top_n(-1, ts) %>%
  # order the reuslt
  arrange(period)

#   Timestamp       Value..kW. ts                  period             
#   <chr>                <int> <dttm>              <dttm>             
# 1 8/12/2018 23:00         51 2018-08-12 23:00:00 2018-08-12 23:00:00
# 2 8/13/2018 0:00          52 2018-08-13 00:00:00 2018-08-13 00:00:00
# 3 8/13/2018 0:10          53 2018-08-13 00:10:00 2018-08-13 00:15:00
# 4 8/13/2018 0:29          57 2018-08-13 00:29:00 2018-08-13 00:30:00
# 5 8/13/2018 0:38          58 2018-08-13 00:38:00 2018-08-13 00:45:00
1
Andrew Lavers 20 Авг 2018 в 20:15

В зависимости от структуры входных данных и ожидаемого результата у ОП есть несколько вариантов.

Из вопроса и примера набора данных не совсем понятно, как должен выглядеть ожидаемый результат, если входные данные содержат промежутки , то есть интервалы, превышающие 15 минут, когда данные не были записаны. Как ОП хочет, чтобы пропуски во входных данных были отражены в результате?

РЕДАКТИРОВАТЬ: ОП предоставляет два немного разных набора данных. Оба используются ниже, чтобы продемонстрировать влияние входных данных на результат.

Варианты ниже будут использовать lubridate и data.table. Предполагается, что df уже упорядочен Timesstamp.

Подготовка

Это необходимо для всех вариантов:

library(lubridate)
library(data.table)
setDT(df)[, Timestamp := mdy_hms(Timestamp)]

Агрегирование до следующего 15-минутного интервала (с пробелами в результате)

Самое простое решение - агрегировать до следующего 15-минутного интервала:

df[, .SD[.N], by = .(Interval = ceiling_date(Timestamp, "15 min"))]
              Interval Value..kW.
1: 2018-08-12 23:00:00         51
2: 2018-08-13 00:00:00         52
3: 2018-08-13 00:15:00         55
4: 2018-08-13 00:30:00         57
5: 2018-08-13 00:45:00         60
6: 2018-08-13 01:00:00         61
7: 2018-08-13 01:15:00         62

Обратите внимание, что между строками 1 и 2 есть промежуток в 1 час, когда отсутствуют 3 интервала.

Для полноты, вот вариант, который работает также с неупорядоченными данными.

df[, .SD[which.max(Timestamp)], keyby = .(Interval = ceiling_date(Timestamp, "15 min"))]

EDIT: С другим набором данных (без усеченных секунд) мы получаем

df0[, .SD[.N], by = .(Interval = ceiling_date(Timestamp, "15 min"))]
1: 2018-08-12 23:15:00         51
2: 2018-08-13 00:15:00         55
3: 2018-08-13 00:30:00         57
4: 2018-08-13 00:45:00         60
5: 2018-08-13 01:00:00         61
6: 2018-08-13 01:15:00         62

Обратите внимание, что без усеченных секунд значения перемещаются на следующий интервал.

Агрегировать до следующего 15-минутного интервала без разрывов в результате

step <- "15 min"
df[, .SD[.N], by = .(Interval = ceiling_date(Timestamp, step))][
  .(seq(min(Interval), max(Interval), step)), on = .(Interval = V1)]

Здесь мы присоединяемся к последовательности временных меток, чтобы завершить пропущенные интервалы:

               Interval Value..kW.
 1: 2018-08-12 23:00:00         51
 2: 2018-08-12 23:15:00         NA
 3: 2018-08-12 23:30:00         NA
 4: 2018-08-12 23:45:00         NA
 5: 2018-08-13 00:00:00         52
 6: 2018-08-13 00:15:00         55
 7: 2018-08-13 00:30:00         57
 8: 2018-08-13 00:45:00         60
 9: 2018-08-13 01:00:00         61
10: 2018-08-13 01:15:00         62

Теперь разрыв становится видимым в результате значений NA.

EDIT: С другим набором данных (без усеченных секунд) мы получаем

df0[, .SD[.N], by = .(Interval = ceiling_date(Timestamp, step))][
  .(seq(min(Interval), max(Interval), step)), on = .(Interval = V1)]
              Interval Value..kW.
1: 2018-08-12 23:15:00         51
2: 2018-08-12 23:30:00         NA
3: 2018-08-12 23:45:00         NA
4: 2018-08-13 00:00:00         NA
5: 2018-08-13 00:15:00         55
6: 2018-08-13 00:30:00         57
7: 2018-08-13 00:45:00         60
8: 2018-08-13 01:00:00         61
9: 2018-08-13 01:15:00         62

Скользящее соединение (пробелы, заполненные данными в результате)

Это упрощенная версия подхода Мэтта

step = "15 min"
df[.(seq(floor_date(min(Timestamp), step), ceiling_date(max(Timestamp), step),by = step)), 
   on = .(Timestamp = V1), roll = TRUE]
              Timestamp Value..kW.
 1: 2018-08-12 23:00:00         51
 2: 2018-08-12 23:15:00         51
 3: 2018-08-12 23:30:00         51
 4: 2018-08-12 23:45:00         51
 5: 2018-08-13 00:00:00         52
 6: 2018-08-13 00:15:00         55
 7: 2018-08-13 00:30:00         57
 8: 2018-08-13 00:45:00         60
 9: 2018-08-13 01:00:00         61
10: 2018-08-13 01:15:00         62

Здесь пробел заполняется данными, которые были скопированы из последней доступной величины. Из анализа результата больше не видно, что во входных данных был пробел.

EDIT: С другим набором данных (без усеченных секунд) мы получаем

df0[.(seq(floor_date(min(Timestamp), step), ceiling_date(max(Timestamp), step),by = step)), 
   on = .(Timestamp = V1), roll = TRUE]
              Timestamp Value..kW.
 1: 2018-08-12 23:00:00         NA
 2: 2018-08-12 23:15:00         51
 3: 2018-08-12 23:30:00         51
 4: 2018-08-12 23:45:00         51
 5: 2018-08-13 00:00:00         51
 6: 2018-08-13 00:15:00         55
 7: 2018-08-13 00:30:00         57
 8: 2018-08-13 00:45:00         60
 9: 2018-08-13 01:00:00         61
10: 2018-08-13 01:15:00         62

Здесь у нас есть незаполненный пробел в первом ряду. Это вызвано тем, как составляется последовательность интервалов. Можно избежать небольшой модификации

df0[.(seq(ceiling_date(min(Timestamp), step), ceiling_date(max(Timestamp), step),by = step)), 
    on = .(Timestamp = V1), roll = TRUE]

             Timestamp Value..kW.
1: 2018-08-12 23:15:00         51
2: 2018-08-12 23:30:00         51
3: 2018-08-12 23:45:00         51
4: 2018-08-13 00:00:00         51
5: 2018-08-13 00:15:00         55
6: 2018-08-13 00:30:00         57
7: 2018-08-13 00:45:00         60
8: 2018-08-13 01:00:00         61
9: 2018-08-13 01:15:00         62

Данные

ОП предоставил данные в виде dput()

df <-
structure(list(Timestamp = c("8/12/2018 23:00:00", "8/13/2018 0:00:00", 
"8/13/2018 0:10:00", "8/13/2018 0:14:00", "8/13/2018 0:15:00", 
"8/13/2018 0:19:00", "8/13/2018 0:29:00", "8/13/2018 0:38:00", 
"8/13/2018 0:44:00", "8/13/2018 0:45:00", "8/13/2018 0:58:00", 
"8/13/2018 1:01:00"), Value..kW. = 51:62), .Names = c("Timestamp", 
"Value..kW."), class = "data.frame", row.names = c(NA, -12L))

РЕДАКТИРОВАТЬ: ОП предоставляет два немного разных набора данных:

  1. как dput() с усеченными секундами (df в этом ответе)
  2. как напечатано df в вопросе без усеченных секунд (df0 в этом ответе)

Эта тонкая разница влияет на результаты. Итак, вот набор данных как напечатано:

df0 <- data.frame(
readr::read_table("        Timestamp   Value.(kW)
8/12/2018 23:00:06  51
8/13/2018 0:00:16   52
8/13/2018 0:10:26   53
8/13/2018 0:14:36   54
8/13/2018 0:15:00   55
8/13/2018 0:19:57   56
8/13/2018 0:29:09   57
8/13/2018 0:38:17   58
8/13/2018 0:44:59   59
8/13/2018 0:45:00   60
8/13/2018 0:58:47   61
8/13/2018 1:01:57   62
"))
# prepare
library(lubridate)
library(data.table)
setDT(df0)[, Timestamp := mdy_hms(Timestamp)]
1
Uwe 21 Авг 2018 в 12:17
51937270