Использование таблицы данных R для расчета частоты забастовок за кумулятивные даты - PullRequest
5 голосов
/ 28 апреля 2020

У меня есть структура таблицы данных, состоящая из приблизительно 1,5 млн строк и сотен столбцов, представляющих даты с результатами скачек - это нужно использовать для прогнозирующей модели, но сначала необходимо сконструировать особенности, чтобы рассчитать частоту ударов различных объектов в условия создания предыдущего рекорда, входящего в каждую гонку за каждый предыдущий день.

«Скорость удара» может быть определена по-разному, но простой способ - это соотношение выигрышей и времени для каждой конкретной лошади, тренера, жокея и т. Д. c. Конечно, это должно учитывать все предыдущие запуски и победы, но не включать результаты «сегодня», так как это было бы бессмыслицей для построения модели.

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

Создайте данные следующим образом:


n <- 90
dt <- data.table(
  date=rep(seq(as.Date('2010-01-01'), as.Date('2015-01-01'), by='year'), n/6), 
  finish=c(1:5),
  trainer=sort(rep(letters[1:5], n/5))
)

Представьте себе, что в эти даты у каждого тренера есть бегун, чья конечная позиция sh в гонке представлена ​​как "fini sh". Для новой даты в последовательности (но не в этих данных) соотношение выигранных на данный момент времен можно рассчитать следующим образом:

dt[order(trainer, date), .(strike_rate = sum(finish==1)/.N), by=trainer]

Однако полученная переменная strike_rate показана для каждый тренер будет действителен только для новой даты в последовательности, которой нет в этом наборе данных, скажем, «2015-01-02» или в нашем наборе из выборки.

Чтобы построить модель, нам нужен страйк ставки в очереди для каждого дня и каждого тренера (и многих других сущностей, но давайте сейчас придерживаться тренера).

Я поиграл с функцией shift и конструкциями таблицы данных, но не могу получить ее работать для этой конкретной проблемы - однако, в контексте oop все работает нормально, хотя это невероятно показательно.

Чтобы проиллюстрировать требуемый вывод, этот пример кода (хотя я уверен, что он не элегантный!) Работает нормально:

#order dates most recent to oldest so that the loop works backwards in time:
dt <- dt[order(-date)]  

#find unique dates (converting to character as something weird with date)
dates = as.character(unique(dt$date))

for (d in dates) {

  #find unique trainers on this date
  trainers = unique(dt$trainer[dt$date==d])

                    for (t in trainers) {

                    trainer_past_form = dt[trainer==t & date < d]

                    strike_rate = sum(trainer_past_form$finish==1)/nrow(trainer_past_form)

                    # save this strike rate for this day and this trainer
                    dt$strike_rate[dt$trainer==t & dt$date==d] <- strike_rate
                    }

}

и дает желаемый вывод:

          date finish trainer strike_rate
 1: 2015-01-01      1       a   0.2000000
 2: 2015-01-01      2       a   0.2000000
 3: 2015-01-01      3       a   0.2000000
 4: 2015-01-01      4       b   0.2000000
 5: 2015-01-01      5       b   0.2000000
 6: 2015-01-01      1       b   0.2000000
 7: 2015-01-01      2       c   0.2000000
 8: 2015-01-01      3       c   0.2000000
 9: 2015-01-01      4       c   0.2000000
10: 2015-01-01      5       d   0.2000000
11: 2015-01-01      1       d   0.2000000
12: 2015-01-01      2       d   0.2000000
13: 2015-01-01      3       e   0.2000000
14: 2015-01-01      4       e   0.2000000
15: 2015-01-01      5       e   0.2000000
16: 2014-01-01      5       a   0.1666667
17: 2014-01-01      1       a   0.1666667
18: 2014-01-01      2       a   0.1666667
19: 2014-01-01      3       b   0.2500000
20: 2014-01-01      4       b   0.2500000
21: 2014-01-01      5       b   0.2500000
22: 2014-01-01      1       c   0.1666667
23: 2014-01-01      2       c   0.1666667
24: 2014-01-01      3       c   0.1666667
25: 2014-01-01      4       d   0.1666667
26: 2014-01-01      5       d   0.1666667
27: 2014-01-01      1       d   0.1666667
28: 2014-01-01      2       e   0.2500000
29: 2014-01-01      3       e   0.2500000
30: 2014-01-01      4       e   0.2500000
31: 2013-01-01      4       a   0.1111111
32: 2013-01-01      5       a   0.1111111
33: 2013-01-01      1       a   0.1111111
34: 2013-01-01      2       b   0.3333333
35: 2013-01-01      3       b   0.3333333
36: 2013-01-01      4       b   0.3333333
37: 2013-01-01      5       c   0.1111111
38: 2013-01-01      1       c   0.1111111
39: 2013-01-01      2       c   0.1111111
40: 2013-01-01      3       d   0.2222222
41: 2013-01-01      4       d   0.2222222
42: 2013-01-01      5       d   0.2222222
43: 2013-01-01      1       e   0.2222222
44: 2013-01-01      2       e   0.2222222
45: 2013-01-01      3       e   0.2222222
46: 2012-01-01      3       a   0.1666667
47: 2012-01-01      4       a   0.1666667
48: 2012-01-01      5       a   0.1666667
49: 2012-01-01      1       b   0.3333333
50: 2012-01-01      2       b   0.3333333
51: 2012-01-01      3       b   0.3333333
52: 2012-01-01      4       c   0.0000000
53: 2012-01-01      5       c   0.0000000
54: 2012-01-01      1       c   0.0000000
55: 2012-01-01      2       d   0.3333333
56: 2012-01-01      3       d   0.3333333
57: 2012-01-01      4       d   0.3333333
58: 2012-01-01      5       e   0.1666667
59: 2012-01-01      1       e   0.1666667
60: 2012-01-01      2       e   0.1666667
61: 2011-01-01      2       a   0.3333333
62: 2011-01-01      3       a   0.3333333
63: 2011-01-01      4       a   0.3333333
64: 2011-01-01      5       b   0.3333333
65: 2011-01-01      1       b   0.3333333
66: 2011-01-01      2       b   0.3333333
67: 2011-01-01      3       c   0.0000000
68: 2011-01-01      4       c   0.0000000
69: 2011-01-01      5       c   0.0000000
70: 2011-01-01      1       d   0.3333333
71: 2011-01-01      2       d   0.3333333
72: 2011-01-01      3       d   0.3333333
73: 2011-01-01      4       e   0.0000000
74: 2011-01-01      5       e   0.0000000
75: 2011-01-01      1       e   0.0000000
76: 2010-01-01      1       a         NaN
77: 2010-01-01      2       a         NaN
78: 2010-01-01      3       a         NaN
79: 2010-01-01      4       b         NaN
80: 2010-01-01      5       b         NaN
81: 2010-01-01      1       b         NaN
82: 2010-01-01      2       c         NaN
83: 2010-01-01      3       c         NaN
84: 2010-01-01      4       c         NaN
85: 2010-01-01      5       d         NaN
86: 2010-01-01      1       d         NaN
87: 2010-01-01      2       d         NaN
88: 2010-01-01      3       e         NaN
89: 2010-01-01      4       e         NaN
90: 2010-01-01      5       e         NaN

Любая помощь по выполнению этого "должным образом" в таблице данных будет высоко ценится. Как видно, я начал использовать библиотеку, но столкнулся с препятствиями на этом типе проблемы. Я понимаю логику c из l oop, но это просто неэффективно для 1,5M строк с большим количеством этого типа cal c для всех переменных.

Ответы [ 3 ]

2 голосов
/ 29 апреля 2020

Вот несколько вариантов.

1) с использованием неэквивалентного соединения:

dt[, strike_rate :=
    .SD[.SD, on=.(trainer, date<date), by=.EACHI, sum(finish==1L)/.N]$V1
]

2) Другой вариант, который должен быть быстрее:

dt[order(trainer, date), strike_rate := {
    ri <- rleid(date)
    firstd <- which(diff(ri) != 0) + 1L

    cs <- replace(rep(NA_real_, .N), firstd, cumsum(finish==1L)[firstd - 1L])
    k <- replace(rep(NA_real_, .N), firstd, as.double(1:.N)[firstd - 1L])

    nafill(cs, "locf") / nafill(k, "locf")
}, trainer]

вывод setorder(dt, -date, trainer, finish)[]:

          date finish trainer strike_rate
 1: 2015-01-01      1       a   0.2000000
 2: 2015-01-01      2       a   0.2000000
 3: 2015-01-01      3       a   0.2000000
 4: 2015-01-01      1       b   0.2000000
 5: 2015-01-01      4       b   0.2000000
 6: 2015-01-01      5       b   0.2000000
 7: 2015-01-01      2       c   0.2000000
 8: 2015-01-01      3       c   0.2000000
 9: 2015-01-01      4       c   0.2000000
10: 2015-01-01      1       d   0.2000000
11: 2015-01-01      2       d   0.2000000
12: 2015-01-01      5       d   0.2000000
13: 2015-01-01      3       e   0.2000000
14: 2015-01-01      4       e   0.2000000
15: 2015-01-01      5       e   0.2000000
16: 2014-01-01      1       a   0.1666667
17: 2014-01-01      2       a   0.1666667
18: 2014-01-01      5       a   0.1666667
19: 2014-01-01      3       b   0.2500000
20: 2014-01-01      4       b   0.2500000
21: 2014-01-01      5       b   0.2500000
22: 2014-01-01      1       c   0.1666667
23: 2014-01-01      2       c   0.1666667
24: 2014-01-01      3       c   0.1666667
25: 2014-01-01      1       d   0.1666667
26: 2014-01-01      4       d   0.1666667
27: 2014-01-01      5       d   0.1666667
28: 2014-01-01      2       e   0.2500000
29: 2014-01-01      3       e   0.2500000
30: 2014-01-01      4       e   0.2500000
31: 2013-01-01      1       a   0.1111111
32: 2013-01-01      4       a   0.1111111
33: 2013-01-01      5       a   0.1111111
34: 2013-01-01      2       b   0.3333333
35: 2013-01-01      3       b   0.3333333
36: 2013-01-01      4       b   0.3333333
37: 2013-01-01      1       c   0.1111111
38: 2013-01-01      2       c   0.1111111
39: 2013-01-01      5       c   0.1111111
40: 2013-01-01      3       d   0.2222222
41: 2013-01-01      4       d   0.2222222
42: 2013-01-01      5       d   0.2222222
43: 2013-01-01      1       e   0.2222222
44: 2013-01-01      2       e   0.2222222
45: 2013-01-01      3       e   0.2222222
46: 2012-01-01      3       a   0.1666667
47: 2012-01-01      4       a   0.1666667
48: 2012-01-01      5       a   0.1666667
49: 2012-01-01      1       b   0.3333333
50: 2012-01-01      2       b   0.3333333
51: 2012-01-01      3       b   0.3333333
52: 2012-01-01      1       c   0.0000000
53: 2012-01-01      4       c   0.0000000
54: 2012-01-01      5       c   0.0000000
55: 2012-01-01      2       d   0.3333333
56: 2012-01-01      3       d   0.3333333
57: 2012-01-01      4       d   0.3333333
58: 2012-01-01      1       e   0.1666667
59: 2012-01-01      2       e   0.1666667
60: 2012-01-01      5       e   0.1666667
61: 2011-01-01      2       a   0.3333333
62: 2011-01-01      3       a   0.3333333
63: 2011-01-01      4       a   0.3333333
64: 2011-01-01      1       b   0.3333333
65: 2011-01-01      2       b   0.3333333
66: 2011-01-01      5       b   0.3333333
67: 2011-01-01      3       c   0.0000000
68: 2011-01-01      4       c   0.0000000
69: 2011-01-01      5       c   0.0000000
70: 2011-01-01      1       d   0.3333333
71: 2011-01-01      2       d   0.3333333
72: 2011-01-01      3       d   0.3333333
73: 2011-01-01      1       e   0.0000000
74: 2011-01-01      4       e   0.0000000
75: 2011-01-01      5       e   0.0000000
76: 2010-01-01      1       a          NA
77: 2010-01-01      2       a          NA
78: 2010-01-01      3       a          NA
79: 2010-01-01      1       b          NA
80: 2010-01-01      4       b          NA
81: 2010-01-01      5       b          NA
82: 2010-01-01      2       c          NA
83: 2010-01-01      3       c          NA
84: 2010-01-01      4       c          NA
85: 2010-01-01      1       d          NA
86: 2010-01-01      2       d          NA
87: 2010-01-01      5       d          NA
88: 2010-01-01      3       e          NA
89: 2010-01-01      4       e          NA
90: 2010-01-01      5       e          NA
          date finish trainer strike_rate

3) И если OP может справиться со вторым подходом, вот тот, который приносит by=trainer в j:)

dt[order(trainer, date), strike_rate := {

    ri <- rleid(date)
    firstd <- which(diff(ri) != 0) + 1L

    cs <- cumsum(finish==1L)

    cumfinishes <- replace(rep(NA_real_, .N), firstd, cs[firstd - 1L])
    k <- replace(rep(NA_real_, .N), firstd, rowid(trainer)[firstd - 1L])

    newt <- which(trainer != shift(trainer))
    prevTrainer <- replace(rep(NA_real_, .N), newt, cs[newt - 1L])

    finishes <- cumfinishes - nafill(replace(prevTrainer, 1L, 0), "locf")
    finishes <- replace(finishes, newt, NaN)

    nafill(finishes, "locf") / nafill(k, "locf")
}]

4) И та же идея, используя Rcpp, который должен быть самым быстрым а также более читабельно:

library(Rcpp)
cppFunction("
NumericVector strike(IntegerVector date, IntegerVector finish, IntegerVector trainer) {
    int i, sz = date.size();
    double cumstrikes = 0, prevcs = NA_REAL, days = 1, prevdays = 1;
    NumericVector strikes(sz), ndays(sz);

    for (i = 0; i < sz; i++) {
        strikes[i] = NA_REAL;
    }

    if (finish[0] == 1)
        cumstrikes = 1;
    for (i = 1; i < sz; i++) {
        if (trainer[i-1] != trainer[i]) {
            cumstrikes = 0;
            days = 0;

        } else if (date[i-1] != date[i]) {
            strikes[i] = cumstrikes;
            ndays[i] = days;

        } else {
            strikes[i] = strikes[i-1];
            ndays[i] = ndays[i-1];
        }

        if (finish[i] == 1) {
            cumstrikes++;
        }

        days++;
    }

    for (i = 0; i < sz; i++) {
        strikes[i] /= ndays[i];
    }

    return strikes;
}")

dt[order(trainer, date), strike_rate := strike(date, finish, rleid(trainer))]
1 голос
/ 29 апреля 2020

Поскольку вам по существу требуется функциональность сгруппированных окон, рассмотрите split.data.table (не путать с base::split), чтобы обрабатывать поднаборы даты / тренера в одном l oop:

setindex(dt, date, trainer)                                       # ADD FOR OTHER GROUPS
strike_rates_dt <- split(dt, by=c("date", "trainer"))             # ADD FOR OTHER GROUPS

strike_rates_dt <- lapply(strike_rates_dt, function(sub) {
  t <- sub$trainer[[1]]                                           # ADD FOR OTHER GROUPS
  d <- sub$date[[1]]

  trainer_past_form <- dt[trainer==t & date < d]                  # ADD FOR OTHER GROUPS
  sr <- sum(trainer_past_form$finish==1)/nrow(trainer_past_form)

  sub[, strike_rate := sr]                                        # SAVE AS NEW COLUMN
})


final_dt <- rbindlist(strike_rates_dt)[order(-date)]

Временные значения указывают заметные различия с вложенным for l oop подходами:

подходами

op_proc <- function() {
  dt <- dt[order(-date)]  

  dates = as.character(unique(dt$date))

  for (d in dates) {
    trainers = unique(dt$trainer[dt$date==d])

    for (t in trainers) {
      trainer_past_form = dt[trainer==t & date < d]
      strike_rate = sum(trainer_past_form$finish==1)/nrow(trainer_past_form)

      # save this strike rate for this day and this trainer
      dt$strike_rate[dt$trainer==t & dt$date==d] <- strike_rate
    }
  }

  return(dt)
}

my_proc <- function() {
  strike_rates_dt <- split(dt, by=c("date", "trainer"))

  strike_rates_dt <- lapply(strike_rates_dt, function(sub) {
    t <- sub$trainer[[1]]
    d <- sub$date[[1]]

    trainer_past_form <- dt[trainer==t & date < d]
    sr <- sum(trainer_past_form$finish==1)/nrow(trainer_past_form)
    sub[, strike_rate := sr]
  })

  final_dt <- rbindlist(strike_rates_dt)[order(-date)]
}

n = 90 Сроки

# Unit: milliseconds
#                expr      min       lq     mean   median       uq      max neval
#  op_dt <- op_proc() 57.02562 59.13524 60.13463 59.73631 60.56061 77.34649   100
# Unit: milliseconds
#                expr      min       lq   mean   median       uq      max neval
#  my_dt <- my_proc() 46.11871 46.67702 48.891 48.67245 49.64088 59.61806   100

n = 900 Время

# Unit: milliseconds
#                expr      min       lq     mean   median       uq      max neval
#  op_dt <- op_proc() 58.07979 59.83595 62.24291 60.26232 60.73125 229.4492   100
# Unit: milliseconds
#               expr      min       lq     mean   median       uq     max neval
#  my_dt <- my_proc() 45.06198 47.09655 48.00078 47.40018 47.93625 53.7639   100

n = 9000 Время

# Unit: milliseconds
#                expr      min       lq     mean   median       uq      max neval
#  op_dt <- op_proc() 66.31556 67.07828 68.20643 67.32226 68.23552 82.22218   100
# Unit: milliseconds
#                expr      min       lq     mean   median       uq      max neval
#  my_dt <- my_proc() 50.05955 51.42313 52.81052 51.73318 54.23603 61.34065   100

n = 90000 Время

# Unit: milliseconds
#                expr      min       lq     mean   median       uq      max neval
#  op_dt <- op_proc() 134.3456 137.7812 148.0204 139.4907 142.4315 356.7175   100
# Unit: milliseconds
#                expr      min       lq     mean   median       uq     max neval
#  my_dt <- my_proc() 87.33779 91.21512 105.1705 92.20642 94.82666 269.798   100
1 голос
/ 28 апреля 2020

Я думаю, что нет for - петли не требуются. Я использую magrittr::%>% здесь прежде всего потому, что я думаю, что это помогает вырвать поток операций; это не требуется, и это может быть легко преобразовано в data.table -пайп или аналогичное предпочтение.

library(data.table)
library(magrittr)
dt %>%
  .[ order(date), ] %>%
  .[, c("rate", "n") := .(cumsum(finish == 1), seq_len(.N)), by = .(trainer) ] %>%
  .[, .(rate = max(rate) / max(n)), by = .(date, trainer) ] %>%
  .[, date := shift(date, type = "lead"), by = .(trainer) ] %>%
  merge(dt, ., by = c("trainer", "date"), all.x = TRUE) %>%
  .[ order(-date), ]
#     trainer       date finish      rate
#  1:       a 2015-01-01      1 0.2000000
#  2:       a 2015-01-01      2 0.2000000
#  3:       a 2015-01-01      3 0.2000000
#  4:       b 2015-01-01      4 0.2000000
#  5:       b 2015-01-01      5 0.2000000
#  6:       b 2015-01-01      1 0.2000000
#  7:       c 2015-01-01      2 0.2000000
#  8:       c 2015-01-01      3 0.2000000
#  9:       c 2015-01-01      4 0.2000000
# 10:       d 2015-01-01      5 0.2000000
# 11:       d 2015-01-01      1 0.2000000
# 12:       d 2015-01-01      2 0.2000000
# 13:       e 2015-01-01      3 0.2000000
# 14:       e 2015-01-01      4 0.2000000
# 15:       e 2015-01-01      5 0.2000000
# 16:       a 2014-01-01      5 0.1666667
# 17:       a 2014-01-01      1 0.1666667
# 18:       a 2014-01-01      2 0.1666667
# 19:       b 2014-01-01      3 0.2500000
# 20:       b 2014-01-01      4 0.2500000
# 21:       b 2014-01-01      5 0.2500000
# 22:       c 2014-01-01      1 0.1666667
# 23:       c 2014-01-01      2 0.1666667
# 24:       c 2014-01-01      3 0.1666667
# 25:       d 2014-01-01      4 0.1666667
# 26:       d 2014-01-01      5 0.1666667
# 27:       d 2014-01-01      1 0.1666667
# 28:       e 2014-01-01      2 0.2500000
# 29:       e 2014-01-01      3 0.2500000
# 30:       e 2014-01-01      4 0.2500000
# 31:       a 2013-01-01      4 0.1111111
# 32:       a 2013-01-01      5 0.1111111
# 33:       a 2013-01-01      1 0.1111111
# 34:       b 2013-01-01      2 0.3333333
# 35:       b 2013-01-01      3 0.3333333
# 36:       b 2013-01-01      4 0.3333333
# 37:       c 2013-01-01      5 0.1111111
# 38:       c 2013-01-01      1 0.1111111
# 39:       c 2013-01-01      2 0.1111111
# 40:       d 2013-01-01      3 0.2222222
# 41:       d 2013-01-01      4 0.2222222
# 42:       d 2013-01-01      5 0.2222222
# 43:       e 2013-01-01      1 0.2222222
# 44:       e 2013-01-01      2 0.2222222
# 45:       e 2013-01-01      3 0.2222222
# 46:       a 2012-01-01      3 0.1666667
# 47:       a 2012-01-01      4 0.1666667
# 48:       a 2012-01-01      5 0.1666667
# 49:       b 2012-01-01      1 0.3333333
# 50:       b 2012-01-01      2 0.3333333
# 51:       b 2012-01-01      3 0.3333333
# 52:       c 2012-01-01      4 0.0000000
# 53:       c 2012-01-01      5 0.0000000
# 54:       c 2012-01-01      1 0.0000000
# 55:       d 2012-01-01      2 0.3333333
# 56:       d 2012-01-01      3 0.3333333
# 57:       d 2012-01-01      4 0.3333333
# 58:       e 2012-01-01      5 0.1666667
# 59:       e 2012-01-01      1 0.1666667
# 60:       e 2012-01-01      2 0.1666667
# 61:       a 2011-01-01      2 0.3333333
# 62:       a 2011-01-01      3 0.3333333
# 63:       a 2011-01-01      4 0.3333333
# 64:       b 2011-01-01      5 0.3333333
# 65:       b 2011-01-01      1 0.3333333
# 66:       b 2011-01-01      2 0.3333333
# 67:       c 2011-01-01      3 0.0000000
# 68:       c 2011-01-01      4 0.0000000
# 69:       c 2011-01-01      5 0.0000000
# 70:       d 2011-01-01      1 0.3333333
# 71:       d 2011-01-01      2 0.3333333
# 72:       d 2011-01-01      3 0.3333333
# 73:       e 2011-01-01      4 0.0000000
# 74:       e 2011-01-01      5 0.0000000
# 75:       e 2011-01-01      1 0.0000000
# 76:       a 2010-01-01      1        NA
# 77:       a 2010-01-01      2        NA
# 78:       a 2010-01-01      3        NA
# 79:       b 2010-01-01      4        NA
# 80:       b 2010-01-01      5        NA
# 81:       b 2010-01-01      1        NA
# 82:       c 2010-01-01      2        NA
# 83:       c 2010-01-01      3        NA
# 84:       c 2010-01-01      4        NA
# 85:       d 2010-01-01      5        NA
# 86:       d 2010-01-01      1        NA
# 87:       d 2010-01-01      2        NA
# 88:       e 2010-01-01      3        NA
# 89:       e 2010-01-01      4        NA
# 90:       e 2010-01-01      5        NA
#     trainer       date finish      rate

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

  1. Группировка по trainer, сбор количества попыток (seq_len(.N)) и количества выигрышей (cumsum(finish == 1));
  2. Группировка по date, trainer , суммируйте каждую группу с отношением максимальных побед к максимальным попыткам, гарантируя, что у нас есть «конец последнего дня»;
  3. Сдвиньте date, чтобы мы могли в конечном итоге ...
  4. merge (объединить) обратно в исходные данные, перенося данные «последней известной даты» на сегодняшний день, поэтому сегодняшние гонки не влияют на сегодняшний уровень страйков

Промежуточный (до * 1025) *) может быть проницательным, и показывает prevdate (смещенная дата) вместо его замены, как указано выше. Знайте, что prevdate вот то, что объединено с исходными данными date:

dt %>%
  .[ order(date), ] %>%
  .[, c("rate", "n") := .(cumsum(finish == 1), seq_len(.N)), by = .(trainer) ] %>%
  # .[, c("rate", "n") := .(cumsum(finish == 1), .I), by = .(trainer) ] %>%
  .[, .(rate = max(rate) / max(n)), by = .(date, trainer) ] %>%
  .[, prevdate := shift(date, type = "lead"), by = .(trainer) ]
#           date trainer      rate   prevdate
#  1: 2010-01-01       a 0.3333333 2011-01-01
#  2: 2010-01-01       b 0.3333333 2011-01-01
#  3: 2010-01-01       c 0.0000000 2011-01-01
#  4: 2010-01-01       d 0.3333333 2011-01-01
#  5: 2010-01-01       e 0.0000000 2011-01-01
#  6: 2011-01-01       a 0.1666667 2012-01-01
#  7: 2011-01-01       b 0.3333333 2012-01-01
#  8: 2011-01-01       c 0.0000000 2012-01-01
#  9: 2011-01-01       d 0.3333333 2012-01-01
# 10: 2011-01-01       e 0.1666667 2012-01-01
# 11: 2012-01-01       a 0.1111111 2013-01-01
# 12: 2012-01-01       b 0.3333333 2013-01-01
# 13: 2012-01-01       c 0.1111111 2013-01-01
# 14: 2012-01-01       d 0.2222222 2013-01-01
# 15: 2012-01-01       e 0.2222222 2013-01-01
# 16: 2013-01-01       a 0.1666667 2014-01-01
# 17: 2013-01-01       b 0.2500000 2014-01-01
# 18: 2013-01-01       c 0.1666667 2014-01-01
# 19: 2013-01-01       d 0.1666667 2014-01-01
# 20: 2013-01-01       e 0.2500000 2014-01-01
# 21: 2014-01-01       a 0.2000000 2015-01-01
# 22: 2014-01-01       b 0.2000000 2015-01-01
# 23: 2014-01-01       c 0.2000000 2015-01-01
# 24: 2014-01-01       d 0.2000000 2015-01-01
# 25: 2014-01-01       e 0.2000000 2015-01-01
# 26: 2015-01-01       a 0.2222222       <NA> ### data this point and below are "lost"
# 27: 2015-01-01       b 0.2222222       <NA> ### when merged, because there are no
# 28: 2015-01-01       c 0.1666667       <NA> ### dates after it to join onto
# 29: 2015-01-01       d 0.2222222       <NA>
# 30: 2015-01-01       e 0.1666667       <NA>
#           date trainer      rate   prevdate
...