Группировка кадра данных R по связанным значениям

Я не нашел решения этой распространенной проблемы группировки в R:

Это мой исходный набор данных

ID  State
1   A
2   A
3   B
4   B
5   B
6   A
7   A
8   A
9   C
10  C

Это должен быть мой сгруппированный результирующий набор данных

State   min(ID) max(ID)
A       1       2
B       3       5
A       6       8
C       9       10

Итак, идея состоит в том, чтобы сначала отсортировать набор данных по столбцу идентификатора (или столбцу отметки времени). Затем все связанные состояния без пропусков должны быть сгруппированы вместе и должны быть возвращены минимальное и максимальное значение идентификатора. Это связано с методом rle, но не позволяет вычислять минимальные и максимальные значения для групп.

Любые идеи?


person HansHupe    schedule 15.09.2016    source источник
comment
Связанное сообщение: stackoverflow.com/questions/37809094 /   -  person zx8754    schedule 15.09.2016


Ответы (4)


Вы можете попробовать:

library(dplyr)
df %>%
  mutate(rleid = cumsum(State != lag(State, default = ""))) %>%
  group_by(rleid) %>%
  summarise(State = first(State), min = min(ID), max = max(ID)) %>%
  select(-rleid)

Или, как указано в комментариях @alistaire, вы можете изменять внутри group_by() с тем же синтаксисом, комбинируя первые два шага. Воровство data.table::rleid() и использование summarise_all() для упрощения:

df %>% 
  group_by(State, rleid = data.table::rleid(State)) %>% 
  summarise_all(funs(min, max)) %>% 
  select(-rleid)

Который дает:

## A tibble: 4 × 3
#   State   min   max
#  <fctr> <int> <int>
#1      A     1     2
#2      B     3     5
#3      A     6     8
#4      C     9    10
person Steven Beaupré    schedule 15.09.2016
comment
Фактически вы можете mutate в group_by с тем же синтаксисом, комбинируя первые два шага. Воровство data.table::rleid и использование summarise_all для упрощения: df %>% group_by(State, rleid = data.table::rleid(State)) %>% summarise_all(funs(min, max)) %>% select(-rleid) - person alistaire; 15.09.2016
comment
@alistaire Не думал об использовании там summarise_all(). Хороший. Я обновил ответ вашим предложением. - person Steven Beaupré; 15.09.2016

Вот метод, который использует функцию rle в базе R для предоставленного вами набора данных.

# get the run length encoding
temp <- rle(df$State)

# construct the data.frame
newDF <- data.frame(State=temp$values,
                    min.ID=c(1, head(cumsum(temp$lengths) + 1, -1)),
                    max.ID=cumsum(temp$lengths))

который возвращается

newDF
  State min.ID max.ID
1     A      1      2
2     B      3      5
3     A      6      8
4     C      9     10

Обратите внимание, что rle требует вектора символов, а не множителя, поэтому я использую аргумент as.is ниже.


Как отмечает @ cryo111 в комментариях ниже, набор данных может представлять собой неупорядоченные временные метки, которые не соответствуют длинам, вычисленным в rle. Чтобы этот метод работал, вам нужно сначала преобразовать метки времени в формат даты и времени с помощью функции типа as.POSIXct, использовать df <- df[order(df$ID),], а затем применить небольшое изменение метода выше:

# get the run length encoding
temp <- rle(df$State)

# construct the data.frame
newDF <- data.frame(State=temp$values,
                    min.ID=df$ID[c(1, head(cumsum(temp$lengths) + 1, -1))],
                    max.ID=df$ID[cumsum(temp$lengths)])

данные

df <- read.table(header=TRUE, as.is=TRUE, text="ID  State
1   A
2   A
3   B
4   B
5   B
6   A
7   A
8   A
9   C
10  C")
person lmo    schedule 15.09.2016
comment
Будет ли это работать, если ID является столбцом с отметкой времени, как указано в OP? - person cryo111; 15.09.2016
comment
В вашем решении min.ID и max.ID рассчитываются через rle длины. Что, если столбец ID теперь содержит (неупорядоченные) временные метки? Я предполагаю, что OP затем хочет минимальную и максимальную временную метку соответствующих групп. - person cryo111; 15.09.2016
comment
Теперь я понимаю вашу точку зрения. Я внес правку, которая более или менее решает эту проблему. - person lmo; 15.09.2016

Идея с data.table:

require(data.table)

dt <- fread("ID  State
1   A
            2   A
            3   B
            4   B
            5   B
            6   A
            7   A
            8   A
            9   C
            10  C")

dt[,rle := rleid(State)]
dt2<-dt[,list(min=min(ID),max=max(ID)),by=c("rle","State")]

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

   rle State min max
1:   1     A   1   2
2:   2     B   3   5
3:   3     A   6   8
4:   4     C   9  10

Идея состоит в том, чтобы идентифицировать последовательности с помощью rleid, а затем получить min и max из ID по кортежу rle и State.

вы можете удалить столбец rle с помощью

dt2[,rle:=NULL]

Прикованный:

 dt2<-dt[,list(min=min(ID),max=max(ID)),by=c("rle","State")][,rle:=NULL]

Вы можете еще больше сократить приведенный выше код, напрямую используя rleid внутри by:

dt2 <- dt[, .(min=min(ID),max=max(ID)), by=.(State, rleid(State))][, rleid:=NULL]
person Tensibai    schedule 15.09.2016
comment
Спасибо за объяснение, я не знал о функции rleid - person HansHupe; 15.09.2016
comment
@HansHupe это часть пакета data.table, он упрощает многие подобные вещи - person Tensibai; 15.09.2016
comment
dt[, .(min = min(ID), max = max(ID)), by = .(State, rl = rleid(State))][, rl := NULL][] еще короче - person Jaap; 15.09.2016
comment
@pro Я не хотел углубляться в синтаксис DT, не уверен, что смогу это объяснить. Так что не стесняйтесь редактировать, чтобы добавить его, или добавить его в качестве другого ответа;) - person Tensibai; 15.09.2016
comment
добавил, его недостаточно, чтобы оправдать отдельный ответ - person Jaap; 15.09.2016

Вот еще одна попытка использования rle и aggregate из базы R:

rl <- rle(df$State)
newdf <- data.frame(ID=df$ID, State=rep(1:length(rl$lengths),rl$lengths))
newdf <- aggregate(ID~State, newdf, FUN = function(x) c(minID=min(x), maxID=max(x)))
newdf$State <- rl$values

  # State ID.minID ID.maxID
# 1     A        1        2
# 2     B        3        5
# 3     A        6        8
# 4     C        9       10

данные

df <- structure(list(ID = 1:10, State = c("A", "A", "B", "B", "B", 
"A", "A", "A", "C", "C")), .Names = c("ID", "State"), class = "data.frame", 
row.names = c(NA, 
    -10L))
person 989    schedule 15.09.2016