R - 按数据帧中的组标识行元素序列

dmi*_*873 4 r dataframe dplyr

请考虑以下示例数据框:

> df
   id name time
1   1    b   10
2   1    b   12
3   1    a    0
4   2    a    5
5   2    b   11
6   2    a    9
7   2    b    7
8   1    a   15
9   2    b    1
10  1    a    3

df = structure(list(id = c(1L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 1L), 
    name = c("b", "b", "a", "a", "b", "a", "b", "a", "b", "a"
    ), time = c(10L, 12L, 0L, 5L, 11L, 9L, 7L, 15L, 1L, 3L)), .Names = c("id", 
"name", "time"), row.names = c(NA, -10L), class = "data.frame")
Run Code Online (Sandbox Code Playgroud)

我需要识别并记录所有序列seq <- c("a","b"),其中"a"在"b"之前,基于"时间"列,对于每个id."a"和"b"之间不允许使用其他名称.实序列长度至少为5.样本数据的预期结果为

  a  b
1 3 10
2 5  7
3 9 11
Run Code Online (Sandbox Code Playgroud)

有一个类似的问题在R数据帧中查找行,其中列值跟随序列.但是,在我的案例中,我不清楚如何处理"id"列.这是使用"dplyr"解决问题的方法吗?

Psi*_*dom 7

library(dplyr); library(tidyr)

# sort data frame by id and time
df %>% arrange(id, time) %>% group_by(id) %>% 

       # get logical vector indicating rows of a followed by b and mark each pair as unique
       # by cumsum
       mutate(ab = name == "a" & lead(name) == "b", g = cumsum(ab)) %>% 

       # subset rows where conditions are met
       filter(ab | lag(ab)) %>% 

       # reshape your data frame to wide format
       select(-ab) %>% spread(name, time)


#Source: local data frame [3 x 4]
#Groups: id [2]

#     id     g     a     b
#* <int> <int> <int> <int>
#1     1     1     3    10
#2     2     1     5     7
#3     2     2     9    11
Run Code Online (Sandbox Code Playgroud)

如果序列的长度大于2,那么你需要检查多个滞后,其中一个选项是使用shift函数(它接受一个向量作为滞后/超前步骤)data.table结合起来Reduce,比如我们需要检查模式abb:

library(dplyr); library(tidyr); library(data.table)
pattern = c("a", "b", "b")
len_pattern = length(pattern)

df %>% arrange(id, time) %>% group_by(id) %>% 

       # same logic as before but use Reduce function to check multiple lags condition
       mutate(ab = Reduce("&", Map("==", shift(name, n = 0:(len_pattern - 1), type = "lead"), pattern)), 
              g = cumsum(ab)) %>% 

       # use reduce or to subset sequence rows having the same length as the pattern
       filter(Reduce("|", shift(ab, n = 0:(len_pattern - 1), type = "lag"))) %>% 

       # make unique names
       group_by(g, add = TRUE) %>% mutate(name = paste(name, 1:n(), sep = "_")) %>% 

       # pivoting the table to wide format
       select(-ab) %>% spread(name, time) 

#Source: local data frame [1 x 5]
#Groups: id, g [1]

#     id     g   a_1   b_2   b_3
#* <int> <int> <int> <int> <int>
#1     1     1     3    10    12
Run Code Online (Sandbox Code Playgroud)


ali*_*ire 6

您可以使用ifelsefilterlaglead,然后tidyr::spread重塑宽:

library(tidyverse)

df %>% arrange(id, time) %>% group_by(id) %>% 
    filter(ifelse(name == 'b',    # if name is b...
                  lag(name) == 'a',    # is the previous name a?
                  lead(name) == 'b')) %>%    # else if name is not b, is next name b?
    ungroup() %>% mutate(i = rep(seq(n() / 2), each = 2)) %>%    # create indices to spread by
    spread(name, time) %>% select(a, b)    # spread to wide and clean up

## # A tibble: 3 × 2
##       a     b
## * <int> <int>
## 1     3    10
## 2     5     7
## 3     9    11
Run Code Online (Sandbox Code Playgroud)

根据下面的评论,这里有一个版本,用于gregexpr查找匹配模式的第一个索引,虽然更复杂,但更容易扩展到更长的模式,如"aabb":

df %>% group_by(pattern = 'aabb', id) %>%    # add pattern as column, group
    arrange(time) %>%
    # collapse each group to a string for name and a list column for time
    summarise(name = paste(name, collapse = ''), time = list(time)) %>% 
    # group and add list-column of start indices for each match
    rowwise() %>% mutate(i = gregexpr(pattern, name)) %>% 
    unnest(i, .drop = FALSE) %>%    # expand, keeping other list columns
    filter(i != -1) %>%    # chop out rows with no match from gregexpr
    rowwise() %>%    # regroup
    # subset with sequence from index through pattern length 
    mutate(time = list(time[i + 0:(nchar(pattern) - 1)]), 
           pattern = strsplit(pattern, '')) %>%    # expand pattern to list column
    rownames_to_column('match') %>%    # add rownames as match index column
    unnest(pattern, time) %>%    # expand matches in parallel
    # paste sequence onto each letter (important for spreading if repeated letters)
    group_by(match) %>% mutate(pattern = paste0(pattern, seq(n()))) %>% 
    spread(pattern, time)    # spread to wide form

## Source: local data frame [1 x 8]
## Groups: match [1]
## 
##   match    id  name     i    a1    a2    b3    b4
## * <chr> <int> <chr> <int> <int> <int> <int> <int>
## 1     1     1 aabba     1     0     3    10    12
Run Code Online (Sandbox Code Playgroud)

请注意,如果模式不是按字母顺序排列,则结果列不会按其索引排序.但是,由于索引被保留,您可以使用类似的东西进行排序select(1:4, parse_number(names(.)[-1:-4]) + 4).


Fra*_*ank 5

这有点令人费解,但滚动加入怎么样?

library(data.table)
setorder(setDT(df), id, time)

df[ name == "b" ][
    df[, if(name == "a") .(time = last(time)), by=.(id, name, r = rleid(id,name))],
    on = .(id, time),
    roll = -Inf,
    nomatch = 0,
    .(a = i.time, b = x.time)
]

   a  b
1: 3 10
2: 5  7
3: 9 11
Run Code Online (Sandbox Code Playgroud)