Лучший способ объединения данных в группу в кадре данных через равные интервалы - PullRequest
0 голосов
/ 16 января 2019

У меня есть датафрейм, для которого характерно множество разных идентификаторов. Для каждого идентификатора существует несколько событий, которые характеризуются совокупной продолжительностью времени между событиями (часы) и продолжительностью этого события (секунды). Итак, это будет выглядеть примерно так:

Id <- c(1,1,1,1,1,1,2,2,2,2,2)
cumulative_time<-c(0,3.58,8.88,11.19,21.86,29.54,0,5,14,19,23)
duration<-c(188,124,706,53,669,1506.2,335,349,395,385,175)
test = data.frame(Id,cumulative_time,duration)

> test
   Id cummulative_time duration
1   1             0.00    188.0
2   1             3.58    124.0
3   1             8.88    706.0
4   1            11.19     53.0
5   1            21.86    669.0
6   1            29.54   1506.2
7   2             0.00    335.0
8   2             5.00    349.0
9   2            14.00    395.0
10  2            19.00    385.0
11  2            23.00    175.0

Я бы хотел сгруппировать по идентификатору, а затем реструктурировать группу путем выборки по кумулятивному количеству, скажем, каждые 10 часов, и из этой суммы в 10 часов по продолжительности, которая произошла в 10-часовом интервале. Количество ячеек, которые я хочу, должно быть от 0 до 30 часов. Таким образом, было бы 3 бункера.

Я посмотрел на функцию cut и сумел взломать ее в рамках фрейма данных - даже я, как новый пользователь r, знаю, что это не красиво

test_cut = test %>% 
  mutate(bin_durations = cut(test$cummulative_time,breaks = c(0,10,20,30),labels = c("10","20","30"),include.lowest = TRUE)) %>% 
  group_by(Id,bin_durations) %>% 
  mutate(total_duration = sum(duration)) %>% 
  select(Id,bin_durations,total_duration) %>% 
  distinct()

, который дает вывод:

test_cut 
  Id time_bins duration
1  1        10   1018.0
2  1        20     53.0
3  1        30   2175.2
4  2        10    684.0
5  2        20    780.0
6  2        30    175.0

В конечном счете, я хочу, чтобы интервал и число ячеек были произвольными - если у меня есть промежуток в 5000 часов, и я хочу, чтобы ячейка производилась за 1 час. Для этого я бы использовал breaks=seq(0,5000,1) для bins Я бы сказал labels = as.character(seq(1,5000,1))

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

Было бы неплохо использовать решение dplyr, так как я применяю биннинг для каждой группы.

Полагаю, между cut и, возможно, split имеется хорошее взаимодействие для генерации желаемого результата.

Заранее спасибо.

Обновление

После тестирования я обнаружил, что даже моя текущая реализация не совсем то, что я хотел бы, как если бы я сказал:

n=3
test_cut = test %>% 
  mutate(bin_durations = cut(test$cumulative_time,breaks=seq(0,30,n),labels = as.character(seq(n,30,n)),include.lowest = TRUE)) %>% 
  group_by(Id,bin_durations) %>% 
  mutate(total_duration = sum(duration)) %>% 
  select(Id,bin_durations,total_duration) %>% 
  distinct()

Я получаю

test_cut
# A tibble: 11 x 3
# Groups:   Id, bin_durations [11]
      Id bin_durations total_duration
   <dbl> <fct>                  <dbl>
 1     1 3                       188 
 2     1 6                       124 
 3     1 9                       706 
 4     1 12                       53 
 5     1 24                      669 
 6     1 30                     1506.
 7     2 3                       335 
 8     2 6                       349 
 9     2 15                      395 
10     2 21                      385 
11     2 24                      175 

Если в последовательности ячеек нет вхождений, я просто должен получить 0 в столбце длительности. Скорее, чем упущение.

Таким образом, это должно выглядеть так:

test_cut
# A tibble: 11 x 3
# Groups:   Id, bin_durations [11]
      Id bin_durations total_duration
   <dbl> <fct>                  <dbl>
 1     1 3                       188 
 2     1 6                       124 
 3     1 9                       706 
 4     1 12                       53 
 5     1 15                        0 
 6     1 18                        0
 7     1 21                        0    
 8     1 24                      669
 9     1 27                        0 
10     1 30                     1506.
11     2 3                       335 
12     2 6                       349
13     2 9                         0
14     2 12                        0  
15     2 15                      395
16     2 18                        0 
17     2 21                      385 
18     2 24                      175
19     2 27                        0
20     2 30                        0 

Ответы [ 2 ]

0 голосов
/ 16 января 2019

Мы могли бы внести эти изменения:

  • test$cummulative_time может быть просто cumulative_time
  • breaks может быть учтено, а затем использовано в cut, как показано
  • второй mutate может быть изменен на summarize, в этом случае select и distinct не нужны
  • это всегда хорошая идея закрыть любой group_by с соответствующим ungroup
  • добавить complete, чтобы вставить 0 для уровней, которых нет

Реализуя эти изменения, мы имеем:

library(dplyr)
library(tidyr)

breaks <- seq(0, 40, 10)
test %>% 
  mutate(bin_durations = cut(cumulative_time, breaks = breaks,
   labels = breaks[-1], include.lowest = TRUE)) %>% 
  group_by(Id,bin_durations) %>% 
  summarize(total_duration = sum(duration)) %>%
  ungroup %>%
  complete(Id, bin_durations, fill = list(total_duration = 0))

дает:

# A tibble: 8 x 3
     Id bin_durations total_duration
  <dbl> <fct>                  <dbl>
1     1 10                     1018 
2     1 20                       53 
3     1 30                     2175.
4     1 40                        0 
5     2 10                      684 
6     2 20                      780 
7     2 30                      175 
8     2 40                        0 
0 голосов
/ 16 января 2019

Вот одна идея через целочисленное деление (%/%)

library(tidyverse)

test %>% 
 group_by(Id, grp = cumulative_time %/% 10) %>% 
 summarise(toatal_duration = sum(duration))

что дает,

# A tibble: 6 x 3
# Groups:   Id [?]
     Id   grp toatal_duration
  <dbl> <dbl>           <dbl>
1     1     0           1018 
2     1     1             53 
3     1     2           2175.
4     2     0            684 
5     2     1            780 
6     2     2            175 

Чтобы решить вашу обновленную проблему, мы можем использовать complete, чтобы добавить недостающие строки. Так, для того же примера, биннинг за 3 часа,

test %>%
     group_by(Id, grp = cumulative_time %/% 3) %>%
     summarise(toatal_duration = sum(duration)) %>%
     ungroup() %>%
     complete(Id, grp = seq(min(grp), max(grp)), fill = list(toatal_duration = 0))

, что дает,

     # A tibble: 20 x 3
      Id   grp toatal_duration
   <dbl> <dbl>           <dbl>
 1     1     0            188 
 2     1     1            124 
 3     1     2            706 
 4     1     3             53 
 5     1     4              0 
 6     1     5              0 
 7     1     6              0 
 8     1     7            669 
 9     1     8              0 
10     1     9           1506.
11     2     0            335 
12     2     1            349 
13     2     2              0 
14     2     3              0 
15     2     4            395 
16     2     5              0 
17     2     6            385 
18     2     7            175 
19     2     8              0 
20     2     9              0  
...