Расчет состояний системы между событиями - PullRequest
0 голосов
/ 16 мая 2018

Это временная шкала (от отметки времени 1 до 66) для команд (ON или OFF), которые управляют состоянием машины (ONLNIE или OFFLINE).

Timeline  ------------|-------------|--------|------|---------|------------>

Command               ON            OFF      OFF    ON        OFF

Обратите внимание, что вторая команда OFF была бесполезна.

Соответственно, состояние машины должно быть:

State     ---OFFLINE--|----ONLINE---|----OFFLINE----|--ONLINE-|--OFFLINE----

Обратите внимание, что по умолчанию установлено состояние OFFLINE.

У меня есть следующие данные для 100 000 компьютеров:

Machine Timestamp    Command
======= =========    =======
1       13           ON
1       27           OFF 
1       36           OFF
1       43           ON
1       53           OFF
...
n       ...          ...

Я хочу создать следующую таблицу:

Machine   From    To    State
=======   ====    ==    =====
1         1       13    OFFLINE 
1         13      27    ONLINE
1         27      43    OFFLINE
1         43      53    ONLINE
1         53      66    OFFLINE
...
n         ...     ...   ...

(From включительно, To эксклюзивно, благодаря @MatBailie)

Как эффективный способ сделать это в SQL (DB2) или R?

Ответы [ 3 ]

0 голосов
/ 16 мая 2018

Решение Base R, OFF / ON принимаются за 0/1. n = 500000.

занимает ~ 5 сек.
n = 2
duration = 66

# sample data    
df_base = data.frame(Machine = rep(1:n, each = 5),
    Timestamp  = c(c(13,27,36,43,53), c(1,13,27,36,66)),
    Command = c(c(1, 0, 1, 1, 0), c(0, 0, 1, 1, 1)))

# auxiliary data frames
df_start = data.frame(Machine = 1:n,
                    Timestamp = rep(1, n),
                    Command = 0)

df_end = data.frame(Machine = 1:n,
    Timestamp = rep(duration, n),
    Command = NA)

df = rbind.data.frame(df_start, df_base, df_end)
df = df[order(df$Machine, df$Timestamp),]

# remove useless commands
df = df[c(TRUE, diff(df$Command) != 0 | is.na(diff(df$Command))),]

# create to and from columns
output = data.frame(head(df, -1), To = tail(df$Timestamp, -1))

# remove rows where from and to refers to different machines
output = output[!is.na(output$Command),]

# reformat output
output = output[,c("Machine", "Timestamp", "To", "Command")]
colnames(output)[2] = "From"

# deal with rows where state is changed in time 1 or time = duration
output = output[!output$From == output$To,]

head(output, 10)

#   Machine From To Command
#1        1    1 13       0
#3        1   13 27       1
#4        1   27 36       0
#5        1   36 53       1
#7        1   53 66       0
#2        2    1 27       0
#10       2   27 66       1
0 голосов
/ 16 мая 2018

Ответ SQL ...

http://sqlfiddle.com/#!18/5606d/2 # Синтаксис QUERY должен быть одинаковым в DB2

SELECT
  machine_id,
  MAX(       command)    AS state,
  MIN(     timestamp)    AS start_timestamp,
  LEAD(MIN(timestamp))
    OVER (PARTITION BY machine_id
              ORDER BY GroupID
         )
                         AS cease_timestamp  
FROM
(
  SELECT
    *,
    SUM(CASE WHEN prev_command = command THEN 0 ELSE 1 END)
      OVER (PARTITION BY machine_id
                ORDER BY timestamp
           )
        AS GroupID
  FROM
  (
    SELECT
      *,
      LAG(command)
        OVER (PARTITION BY machine_id
                  ORDER BY timestamp
             )
               AS prev_command
    FROM
    (
      SELECT machine_id, timestamp, command FROM machine_events
      UNION ALL
      SELECT machine_id,         1,   'OFF' FROM machine_events GROUP BY machine_id HAVING MIN(timestamp) > 1
    )
      expanded_events
  )
    look_back
)
  grouped
GROUP BY
  machine_id,
  GroupID
ORDER BY
  machine_id,
  GroupID
0 голосов
/ 16 мая 2018

Если ваши данные находятся в data.table объекте в R, вы можете сделать это

Пример данных

library(data.table)
dt <- fread("
Timestamp    Command
13           ON
27           OFF 
36           OFF
43           ON
53           OFF
")
dt <- rbind(dt, dt)
dt[, Machine := rep(1:2, each = 5)][]

#     Timestamp Command Machine
#  1:        13      ON       1
#  2:        27     OFF       1
#  3:        36     OFF       1
#  4:        43      ON       1
#  5:        53     OFF       1
#  6:        13      ON       2
#  7:        27     OFF       2
#  8:        36     OFF       2
#  9:        43      ON       2
# 10:        53     OFF       2

выход

library(magrittr)

dt[,  .(From = Timestamp 
      , To   = shift(Timestamp, 1, type = 'lead') - 1
      , Command)
   , by = Machine] %>% 
  .[, .(From  = first(From)
      , To    = last(To)
      , State = first(Command))
    , by = .(Machine, rleid(Command))] %>% 
  .[, .(From  = c(1, From)
      , To    = c(From[1] - 1, To)
      , State = c(ifelse(State[1] == 'ON', 'OFF', 'ON'), State))
    , by = Machine]


#     Machine From To State
#  1:       1    1 12   OFF
#  2:       1   13 26    ON
#  3:       1   27 42   OFF
#  4:       1   43 52    ON
#  5:       1   53 NA   OFF
#  6:       2    1 12   OFF
#  7:       2   13 26    ON
#  8:       2   27 42   OFF
#  9:       2   43 52    ON
# 10:       2   53 NA   OFF

Бенчмарк показывает, что для этого примера требуется ~ 200-300 миллисекунд с 1 миллионом строк. Сделал это на ноутбуке HP.

n = 2
duration = 66

dt <- data.table(Machine = rep(1:n, each = 5),
    Timestamp  = c(c(13,27,36,43,53), c(1,13,27,36,66)),
    Command = c(c(1, 0, 1, 1, 0), c(0, 0, 1, 1, 1)))


dt <- rbindlist(replicate(1e5, dt, simplify = F))
nrow(dt) == 1e6
# TRUE

microbenchmark(useDT(dt))
# Unit: milliseconds
#       expr      min       lq     mean   median       uq      max neval
#  useDT(dt) 159.6124 171.5623 265.3555 186.8661 232.0942 1157.086   100
...