【问题标题】:R - Find a sequence of row elements based on time constraints in a dataframeR - 根据数据帧中的时间限制查找一系列行元素
【发布时间】:2017-01-20 20:40:11
【问题描述】:

考虑以下数据框(按 id 和时间排序):

df <- data.frame(id = c(rep(1,7),rep(2,5)), event = c("a","b","b","b","a","b","a","a","a","b","a","a"), time = c(1,3,6,12,24,30,32,1,2,6,17,24))
df
   id event time
1   1     a    1
2   1     b    3
3   1     b    6
4   1     b   12
5   1     a   24
6   1     b   30
7   1     a   42
8   2     a    1
9   2     a    2
10  2     b    6
11  2     a   17
12  2     a   24

我想计算给定事件序列在每个“id”组中出现的次数。考虑以下有时间限制的序列:

seq <- c("a", "b", "a")
time_LB <- c(0, 2, 12)
time_UB <- c(Inf, 8, 18)

这意味着事件“a”可以在任何时候开始,事件“b”必须在事件“a”之后不早于2并且不迟于8开始,另一个事件“a”必须不早于12并且不开始事件“b”之后的 18 点之后。 创建序列的一些规则:

  1. 事件对于“时间”列不需要是连续的。例如,seq 可以从第 1、3 和 5 行构造。
  2. 要进行计数,序列必须具有不同的第一个事件。例如,如果 seq = 第 8、10 和 11 行已计入,则 seq = 第 8、10 和 12 行不得计入。
  3. 如果事件不违反第二条规则,它们可能会包含在许多构建的序列中。例如,我们计算两个序列:第 1、3、5 行和第 5、6、7 行。

预期结果:

df1
  id count
1  1     2
2  2     2

R - Identify a sequence of row elements by groups in a dataframeFinding rows in R dataframe where a column value follows a sequence有一些相关问题。

是否可以使用“dplyr”解决问题?

【问题讨论】:

  • 有点困惑 - 采取id == 2;这里a=1,b=6,a=17 是唯一满足条件的情况对吧?所以计数应该是1对吗?我答对了吗?
  • 另一个序列是 a=2,b=6,a=17 或 a=2,b=6,a=24
  • 那么我们也可以有a=24, b=6, a=17?
  • 您的意思是 a=2 而不是 24?如果是,则规则 2 规定我们只能计算其中之一
  • 我还是一头雾水。不,我的意思是 24 本身;因为a 可以有 0 和 Inf 之间的任何值?我们是否正在尝试搜索所有排列?

标签: r dataframe dplyr


【解决方案1】:

我相信这就是您正在寻找的。它为您提供所需的输出。请注意,当您在 df 中定义 time 列时,您的原始问题中有一个错字,其中您有 32 而不是 42。我说这是一个错字,因为它与您在df 定义下方的输出不匹配。我在下面的代码中将 32 更改为 42。

library(dplyr)

df <- data.frame(id = c(rep(1,7),rep(2,5)), event = c("a","b","b","b","a","b","a","a","a","b","a","a"), time = c(1,3,6,12,24,30,42,1,2,6,17,24))

seq <- c("a", "b", "a")
time_LB <- c(0, 2, 12)
time_UB <- c(Inf, 8, 18)

df %>% 
  full_join(df,by='id',suffix=c('1','2')) %>% 
  full_join(df,by='id') %>% 
  rename(event3 = event, time3 = time) %>%
  filter(event1 == seq[1] & event2 == seq[2] & event3 == seq[3]) %>% 
  filter(time1 %>% between(time_LB[1],time_UB[1])) %>% 
  filter((time2-time1) %>% between(time_LB[2],time_UB[2])) %>% 
  filter((time3-time2) %>% between(time_LB[3],time_UB[3])) %>%
  group_by(id,time1) %>%
  slice(1) %>%   # slice 1 row for each unique id and time1 (so no duplicate time1s)
  group_by(id) %>% 
  count()

这是输出:

# A tibble: 2 x 2
     id     n
  <dbl> <int>
1     1     2
2     2     2

此外,如果您省略 dplyr 管道的最后 2 部分进行计数(以查看它匹配的序列),您将获得以下序列:

Source: local data frame [4 x 7]
Groups: id, time1 [4]

     id event1 time1 event2 time2 event3 time3
  <dbl> <fctr> <dbl> <fctr> <dbl> <fctr> <dbl>
1     1      a     1      b     6      a    24
2     1      a    24      b    30      a    42
3     2      a     1      b     6      a    24
4     2      a     2      b     6      a    24

编辑以回应关于泛化此的评论: 是的,可以将其泛化为任意长度的序列,但需要一些 R 巫术。最值得注意的是,请注意Reduce 的使用,它允许您在对象列表以及foreach 上应用通用函数,我从foreach 包中借用它来执行一些任意循环。代码如下:

library(dplyr)
library(foreach)

df <- data.frame(id = c(rep(1,7),rep(2,5)), event = c("a","b","b","b","a","b","a","a","a","b","a","a"), time = c(1,3,6,12,24,30,42,1,2,6,17,24))

seq <- c("a", "b", "a")
time_LB <- c(0, 2, 12)
time_UB <- c(Inf, 8, 18)

multi_full_join = function(df1,df2) {full_join(df1,df2,by='id')}
df_list = foreach(i=1:length(seq)) %do% {df} 
df2 = Reduce(multi_full_join,df_list)

names(df2)[grep('event',names(df2))] = paste0('event',seq_along(seq))
names(df2)[grep('time',names(df2))] = paste0('time',seq_along(seq))
df2 = df2 %>% mutate_if(is.factor,as.character)

df2 = df2 %>% 
  mutate(seq_string = Reduce(paste0,df2 %>% select(grep('event',names(df2))) %>% as.list)) %>% 
  filter(seq_string == paste0(seq,collapse=''))

time_diff = df2 %>% select(grep('time',names(df2))) %>%
  t %>%
  as.data.frame() %>%
  lapply(diff) %>% 
  unlist %>%  matrix(ncol=2,byrow=TRUE) %>% 
  as.data.frame

foreach(i=seq_along(time_diff),.combine=data.frame) %do%
{
  time_diff[[i]] %>% between(time_LB[i+1],time_UB[i+1])
} %>% 
  Reduce(`&`,.) %>% 
  which %>% 
  slice(df2,.) %>% 
  filter(time1 %>% between(time_LB[1],time_UB[1])) %>% # deal with time1 bounds, which we skipped over earlier
  group_by(id,time1) %>%
  slice(1) # slice 1 row for each unique id and time1 (so no duplicate time1s)

这会输出以下内容:

Source: local data frame [4 x 8]
Groups: id, time1 [4]

     id event1 time1 event2 time2 event3 time3 seq_string
  <dbl>  <chr> <dbl>  <chr> <dbl>  <chr> <dbl>      <chr>
1     1      a     1      b     6      a    24        aba
2     1      a    24      b    30      a    42        aba
3     2      a     1      b     6      a    24        aba
4     2      a     2      b     6      a    24        aba

如果你只想要计数,你可以group_by(id) 然后count() 就像在原始代码 sn-p 中一样。

【讨论】:

  • 是的,这是一个错字...必须是 42。感谢您修复它。可以修改您的代码以使用任何长度的序列吗?实际序列有 2 到 10 个事件。
  • 是的,我编辑了上面的答案以包含一个通用版本。
  • @dmitriy873 请让我知道这是否按预期工作,或者我是否应该对代码本身添加任何进一步的说明。谢谢。
【解决方案2】:

也许将事件序列表示为字符串并使用正则表达式更容易:

df.str = lapply(split(df, df$id), function(d) {
    z = rep('-', tail(d,1)$time); z[d$time] = as.character(d$event); z })

df.str = lapply(df.str, paste, collapse='')

# > df.str
# $`1`
# [1] "a-b--b-----b-----------a-----b-----------a"
#
# $`2`
# [1] "aa---b----------a------a"


df1 = lapply(df.str, function(s) length(gregexpr('(?=a.{1,7}b.{11,17}a)', s, perl=T)[[1]]))

> data.frame(id=names(df1), count=unlist(df1))
#   id count
# 1  1     2
# 2  2     2

【讨论】:

  • 这是一个非常好的主意!但是,真实事件之间可能有数百万秒。它会增加执行时间吗?
  • @ dmitriy873 就像,我知道这里的两个答案都有效,但我真的不喜欢它们中的任何一个,因为它们看起来更像是解决问题而不是直接面对问题。从某种意义上说,我们在这里谈论的是信号检测。所以确定这些工作是在一个人为的例子上,但是当序列超级复杂,或者你正在搜索的环境非常嘈杂(很多误报要省略)时呢?这些答案不会很好地扩展。
  • 愿意分享更好的解决方案吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-02
  • 2018-12-06
  • 2020-04-30
相关资源
最近更新 更多