Используйте rle для группировки с помощью прогонов при использовании dplyr

В R я хочу обобщить свои данные после группировки на основе прогонов переменной x (так как каждая группа данных соответствует подмножеству данных, где последовательные значения x совпадают). Например, рассмотрим следующий фрейм данных, где я хочу вычислить среднее значение y в каждом прогоне x:

(dat <- data.frame(x=c(1, 1, 1, 2, 2, 1, 2), y=1:7))
#   x y
# 1 1 1
# 2 1 2
# 3 1 3
# 4 2 4
# 5 2 5
# 6 1 6
# 7 2 7

В этом примере переменная x имеет пробеги длиной 3, затем 2, затем 1 и, наконец, 1, принимая значения 1, 2, 1 и 2 в этих четырех прогонах. Соответствующим средством y в этих группах являются 2, 4.5, 6 и 7.

Легко выполнить эту сгруппированную операцию в базе R с помощью tapply, передав dat$y в качестве данных, используя rle для вычисления номера прогона из dat$x и передачи нужной итоговой функции:

tapply(dat$y, with(rle(dat$x), rep(seq_along(lengths), lengths)), mean)
#   1   2   3   4 
# 2.0 4.5 6.0 7.0 

Я решил, что смогу довольно быстро перенести эту логику в dplyr, но мои попытки до сих пор закончились ошибками:

library(dplyr)
# First attempt
dat %>%
  group_by(with(rle(x), rep(seq_along(lengths), lengths))) %>%
  summarize(mean(y))
# Error: cannot coerce type 'closure' to vector of type 'integer'

# Attempt 2 -- maybe "with" is the problem?
dat %>%
  group_by(rep(seq_along(rle(x)$lengths), rle(x)$lengths)) %>%
  summarize(mean(y))
# Error: invalid subscript type 'closure'

Для полноты я мог бы повторно реализовать идентификатор запуска rle, используя cumsum, head и tail, чтобы обойти это, но это делает код группировки более жестким для чтения и требует немного переосмысления колесо:

dat %>%
  group_by(run=cumsum(c(1, head(x, -1) != tail(x, -1)))) %>%
  summarize(mean(y))
#     run mean(y)
#   (dbl)   (dbl)
# 1     1     2.0
# 2     2     4.5
# 3     3     6.0
# 4     4     7.0

Что приводит к тому, что мой код rle на основе rle не работает в dplyr, и существует ли какое-либо решение, которое позволяет мне продолжать использовать rle при группировке по идентификатору запуска?

Ответ 1

Одним из вариантов является использование {}, как в:

dat %>%
    group_by(yy = {yy = rle(x); rep(seq_along(yy$lengths), yy$lengths)}) %>%
    summarize(mean(y))
#Source: local data frame [4 x 2]
#
#     yy mean(y)
#  (int)   (dbl)
#1     1     2.0
#2     2     4.5
#3     3     6.0
#4     4     7.0

Было бы неплохо, если бы в будущих версиях dplyr также был эквивалент функции data.table rleid.


Я заметил, что эта проблема возникает при использовании ввода data.frame или tbl_df, но не при использовании ввода tbl_dt или data.table:

dat %>% 
    tbl_df %>% 
    group_by(yy = with(rle(x), rep(seq_along(lengths), lengths))) %>%
    summarize(mean(y))
Error: cannot coerce type 'closure' to vector of type 'integer'

dat %>% 
    tbl_dt %>% 
    group_by(yy = with(rle(x), rep(seq_along(lengths), lengths))) %>%
    summarize(mean(y))
Source: local data table [4 x 2]

     yy mean(y)
  (int)   (dbl)
1     1     2.0
2     2     4.5
3     3     6.0
4     4     7.0

Я сообщил об этом как issue на странице dplyr github.

Ответ 2

Если вы явно создаете переменную группировки g, она работает более или менее:

> dat %>% transform(g=with(rle(dat$x),{ rep(seq_along(lengths), lengths)}))%>%                                   
 group_by(g) %>% summarize(mean(y))
Source: local data frame [4 x 2]

      g mean(y)
  (int)   (dbl)
1     1     2.0
2     2     4.5
3     3     6.0
4     4     7.0

Я использовал transform здесь, потому что mutate выдает ошибку.