Рассчитать пользовательский сеанс из таблицы длинных форматов: проблема производительности с прямым подходом - PullRequest
0 голосов
/ 26 июня 2019

Мне нужно рассчитать продолжительность сеанса для некоторых пользователей.Чтобы упростить задачу, пользователи могут включать и выключать, и мне нужно измерить, как долго они оставались.Таблица, которую я получаю, довольно проста:

Я попробовал простой метод, упорядочив по отметкам времени, а также используя функцию, чтобы фактически вычислять различия между последовательными строками для каждого пользователя.Это работает, однако мои реальные данные содержат тысячи и тысячи строк, и в итоге получается очень медленно (уже удалось убить память моего ноутбука).Я примерно работаю с 300000 строками в этой таблице и 5000 разными именами пользователей.

Есть ли способ получить что-то быстрее, чем приведенный ниже код?

#Synthetic data
connections<-data.frame(name=c("sam","sam","bob","mike","bob","mike","sam","sam","bob","mike","bob","mike"),
                        state=c("on","off","on","on","off","off","on","off","on","on","off","off"),
                        time= c("2019-06-19 00:00:02","2019-06-19 01:11:22",
                                "2019-06-19 10:09:19","2019-06-19 02:12:06",
                                "2019-06-19 10:45:18","2019-06-19 06:24:43",
                                "2019-06-19 14:12:06","2019-06-19 15:15:43",
                                "2019-06-19 17:54:08","2019-06-19 15:17:47",
                                "2019-06-19 19:27:55","2019-06-19 21:22:36"))

#Consider time variable as a time stamp
connections$time<-as.POSIXct(connections$time)
connections<-connections%>%arrange(time)

#calculate the difference between time stamps for all user sessions (on-off transitions)
sessions<-by(connections,connections$name, duration)

#transform list back to a data frame
sessions<-rbindlist(sessions)

#keep only lines corresponding to off factor as, it corresponds to the duration people stayed on
sessions<-sessions[sessions$state=='off',]

#display the table. This result suits me, but on big tables, this code is damn slow...
sessions

Ожидаемый результат достигается прикод выше, но медленно: -)

name state                time session_duration
1:  bob   off 2019-06-19 10:45:18             2159
2:  bob   off 2019-06-19 19:27:55             5627
3: mike   off 2019-06-19 06:24:43            15157
4: mike   off 2019-06-19 21:22:36            21889
5:  sam   off 2019-06-19 01:11:22             4280
6:  sam   off 2019-06-19 15:15:43             3817

1 Ответ

0 голосов
/ 26 июня 2019

Я не могу сравнить с тем, что вы сделали, так как вы не поделились кодом для функции duration. Я могу обновить сравнение, если вы сделаете это.

Некоторые варианты приведены ниже. Я попробовал это с базой R (я использую 3.4.1), используя параллельную обработку и dplyr (0.7.7, так что я немного устарел). Несколько лет назад я читал, что dplyr может быть немного медленным при работе с большим количеством групп, но я не знаю, так ли это до сих пор. В этом конкретном примере кажется, что dplyr все еще примерно в два раза быстрее даже с 30 группами. Его запас превосходства растет через 6000 групп.

Я уверен, что есть datatable способ сделать это, но я не очень разбираюсь в этой структуре, поэтому я не буду пытаться добавить его сам.

В такой проблеме я ожидал бы, что добавление большего числа отдельных пользователей вызовет более медленное замедление, чем добавление большего количества строк на пользователя, и даже при 6000 пользователей я все еще обрабатываю это за четыре секунды, используя мой самый медленный подход. Это заставляет меня думать, что ваша duration функция не очень хорошо оптимизирована для этой проблемы. Я бы посоветовал вам поделиться этим кодом, чтобы мы могли показать вам, каких ловушек следует избегать. (Я предполагаю, что duration является функцией вашего собственного создания, поскольку ?duration ничего для меня не вызывает)

# Base R

calculate_duration <- function(df){
  df$end_time <- dplyr::lead(df$time)
  df <- df[df$state == "on", ]
  df$duration <- difftime(df$end_time, df$time, units = "secs")
  df
}

conn <- connections[order(connections$name, connections$time), ]
conn <- split(conn, conn$name)
conn <- lapply(conn, calculate_duration)
conn <- do.call("rbind", conn)
conn

# parallel

library(parallel)
cl <- makeCluster(detectCores() - 1)
clusterExport(cl, "calculate_duration")
conn <- connections[order(connections$name, connections$time), ]
conn <- split(conn, conn$name)
conn <- parLapply(cl, conn, calculate_duration)
conn <- do.call("rbind", conn)
conn

stopCluster(cl)

# dplyr

library(dplyr)

connections %>% 
  arrange(name, time) %>% 
  group_by(name) %>% 
  mutate(end_time = lead(time)) %>% 
  filter(state == "on") %>% 
  mutate(duration = difftime(end_time, time, units = "secs"))

Тесты

ndup_option <- c(1, 10, 50, 100, 500, 2000)

for (i in ndup_option){
  connections <- data.frame(name = c("sam","sam","bob","mike","bob","mike","sam","sam","bob","mike","bob","mike"),
                            state = c("on","off","on","on","off","off","on","off","on","on","off","off"),
                            time = as.POSIXct(c("2019-06-19 00:00:02","2019-06-19 01:11:22",
                                                "2019-06-19 10:09:19","2019-06-19 02:12:06",
                                                "2019-06-19 10:45:18","2019-06-19 06:24:43",
                                                "2019-06-19 14:12:06","2019-06-19 15:15:43",
                                                "2019-06-19 17:54:08","2019-06-19 15:17:47",
                                                "2019-06-19 19:27:55","2019-06-19 21:22:36")),
                            stringsAsFactors = FALSE)

  ndup <- i
  conn_list <- vector("list", ndup)
  for (j in seq_len(ndup)){
    tmp <- connections
    tmp$name <- sprintf("%s%s", connections$name, j)
    conn_list[[j]] <- tmp
  }

  connections <- do.call("rbind", conn_list)

  out <- microbenchmark(
    base = {
      conn <- connections[order(connections$name, connections$time), ]
      conn <- split(conn, conn$name)
      conn <- lapply(conn, calculate_duration)
      conn <- do.call("rbind", conn)
      conn
    },
    parallel = {
      conn <- connections[order(connections$name, connections$time), ]
      conn <- split(conn, conn$name)
      conn <- parLapply(cl, conn, calculate_duration)
      conn <- do.call("rbind", conn)
      conn
    },
    dplyr = {
      connections %>% 
        arrange(name, time) %>% 
        group_by(name) %>% 
        mutate(end_time = lead(time)) %>% 
        filter(state == "on") %>% 
        mutate(duration = difftime(end_time, time))
    },
    times = 10
  )

  message(sprintf("Benchmark for %s groups:", 
                  length(unique(connections$name))))

  print(out)
}
Benchmark for 3 groups:
Unit: milliseconds
     expr      min       lq     mean   median       uq       max neval
     base 1.387948 1.390001 1.481524 1.491905 1.564191  1.587064    10
 parallel 3.535705 4.435393 4.557561 4.734508 4.965587  5.136552    10
    dplyr 3.222515 3.272074 4.515245 3.534385 3.676465 13.978893    10

Benchmark for 30 groups:
Unit: milliseconds
     expr       min        lq      mean    median        uq       max neval
     base 10.800952 11.201530 12.020516 11.339943 11.670142 15.609064    10
 parallel  9.992464 10.990684 11.496216 11.497125 12.073653 12.782729    10
    dplyr  5.699297  6.019231  6.056093  6.092104  6.127148  6.347964    10

Benchmark for 150 groups:
Unit: milliseconds
     expr      min       lq     mean   median       uq      max neval
     base 53.52881 54.47601 57.01772 56.41424 58.62636 63.47641    10
 parallel 35.42127 37.91183 39.10031 39.05301 39.89185 42.53285    10
    dplyr 17.31812 17.52427 19.01509 17.93761 19.95384 24.30478    10

Benchmark for 300 groups:
Unit: milliseconds
     expr       min        lq      mean    median        uq       max neval
     base 111.00359 114.57917 119.35063 117.68527 123.43090 131.21256    10
 parallel  71.35337  72.95979  76.53514  76.74857  79.88487  84.47363    10
    dplyr  31.50023  32.21957  33.53007  33.71852  34.38932  36.30571    10

Benchmark for 1500 groups:
Unit: milliseconds
     expr      min       lq     mean   median       uq      max neval
     base 647.6213 677.7236 692.9642 687.5113 725.7461 741.0704    10
 parallel 423.8719 437.3221 485.8728 449.2985 506.2456 627.6593    10
    dplyr 159.6991 166.2746 171.3379 169.5461 173.5134 187.0929    10

Benchmark for 6000 groups:
Unit: milliseconds
     expr      min        lq      mean    median        uq       max neval
     base 3454.545 3517.1275 3616.9079 3579.5780 3773.7299 3789.4571    10
 parallel 2506.242 2556.1848 2601.5728 2607.1978 2639.6757 2695.3605    10
    dplyr  657.422  681.0403  704.7698  691.7343  713.3387  784.5798    10
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...