识别和计算法术(每组中的独特事件)

Posted

技术标签:

【中文标题】识别和计算法术(每组中的独特事件)【英文标题】:Identify and count spells (Distinctive events within each group) 【发布时间】:2019-08-23 02:28:09 【问题描述】:

我正在寻找一种有效的方法来识别时间序列中的法术/跑步。在下图中,前三列是我所拥有的,第四列 spell 是我要计算的。我试过使用dplyrleadlag,但这太复杂了。我试过rle,但一无所获。

ReprEx

df <- structure(list(time = structure(c(1538876340, 1538876400, 
1538876460,1538876520, 1538876580, 1538876640, 1538876700, 1538876760, 1526824800, 
1526824860, 1526824920, 1526824980, 1526825040, 1526825100), class = c("POSIXct", 
"POSIXt"), tzone = "UTC"), group = c("A", "A", "A", "A", "A", "A", "A", "A", "B", 
"B", "B", "B", "B", "B"), is.5 = c(0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1)), 
class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA, -14L))

我更喜欢tidyverse 解决方案。

假设

    数据按group排序,然后按time排序

    每个组内time 没有空格

更新

感谢您的贡献。我已经对完整数据 (n=2,583,360) 的一些建议方法进行了计时

    @markus 的 rle 方法耗时 0.53 秒 @M-M 的 cumsum 方法耗时 2.85 秒 @MrFlick 的函数方法耗时 0.66 秒 @tmfmnk 的 rledense_rank 花了 0.89

我最终选择了@markus 的 (1),因为它速度快而且仍然有点直观(主观)。 (2) @M-M 最能满足我对 dplyr 解决方案的渴望,尽管它的计算效率很低。

【问题讨论】:

对于不熟悉spell如何计算的人,您能分享一个公式或说明吗? @nsinghs 我认为他们的意思是“住院咒语” 【参考方案1】:

一个使用rle的选项

library(dplyr)
df %>% 
  group_by(group) %>% 
  mutate(
    spell = 
      r <- rle(is.5)
      r$values <- cumsum(r$values) * r$values
      inverse.rle(r) 
      
  )
# A tibble: 14 x 4
# Groups:   group [2]
#   time                group  is.5 spell
#   <dttm>              <chr> <dbl> <dbl>
# 1 2018-10-07 01:39:00 A         0     0
# 2 2018-10-07 01:40:00 A         1     1
# 3 2018-10-07 01:41:00 A         1     1
# 4 2018-10-07 01:42:00 A         0     0
# 5 2018-10-07 01:43:00 A         1     2
# 6 2018-10-07 01:44:00 A         0     0
# 7 2018-10-07 01:45:00 A         0     0
# 8 2018-10-07 01:46:00 A         1     3
# 9 2018-05-20 14:00:00 B         0     0
#10 2018-05-20 14:01:00 B         0     0
#11 2018-05-20 14:02:00 B         1     1
#12 2018-05-20 14:03:00 B         1     1
#13 2018-05-20 14:04:00 B         0     0
#14 2018-05-20 14:05:00 B         1     2

您要求tidyverse 解决方案,但如果您关心速度,您可以使用data.table。语法很相似

library(data.table)
setDT(df)[, spell := 
  r <- rle(is.5)
  r$values <- cumsum(r$values) * r$values
  inverse.rle(r) 
  , by = group][] # the [] at the end prints the data.table

解释

当我们打电话时

r <- rle(df$is.5)

我们得到的结果是

r
#Run Length Encoding
#  lengths: int [1:10] 1 2 1 1 2 1 2 2 1 1
#  values : num [1:10] 0 1 0 1 0 1 0 1 0 1

我们需要将values 替换为累积和,其中values == 1values 应保持为零。

当我们将cumsum(r$values)r$values 相乘时,我们可以实现这一点;其中后者是0s 和1s 的向量。

r$values <- cumsum(r$values) * r$values
r$values
# [1] 0 1 0 2 0 3 0 4 0 5

最后我们调用inverse.rle 来取回一个与is.5 长度相同的向量。

inverse.rle(r)
# [1] 0 1 1 0 2 0 0 3 0 0 4 4 0 5

我们为每个group 执行此操作。

【讨论】:

【参考方案2】:

这是一个帮助函数,可以返回你所追求的内容

spell_index <- function(time, flag) 
  change <- time-lag(time)==1 & flag==1 & lag(flag)!=1
  cumsum(change) * (flag==1)+0

您可以将它与您的数据一起使用,例如

library(dplyr)
df %>% 
  group_by(group) %>% 
  mutate(
    spell = spell_index(time, is.5)
  )

基本上,辅助函数使用lag() 来查找更改。我们使用cumsum() 来增加更改的数量。然后我们乘以一个布尔值,将你想要清零的值清零。

【讨论】:

【参考方案3】:

这是rleid 来自data.table 的一个选项。将 'data.frame' 转换为 'data.table' (setDT(df)),按 'group' 分组,得到 'is.5' 的 run-length-id (rleid) 并乘以 ' is.5' 以便将is.5中的0s对应的id替换为0,赋值给'spell',然后用逻辑向量指定i,选择'spell'值不为零的行,@ 987654326@ 'spell' 的那些值与unique 'spell' 并将其分配给'spell'

library(data.table)
setDT(df)[, spell := rleid(is.5) * as.integer(is.5), group
       ][!!spell, spell := match(spell, unique(spell))][]
#                   time group is.5 spell
# 1: 2018-10-07 01:39:00     A    0     0
# 2: 2018-10-07 01:40:00     A    1     1
# 3: 2018-10-07 01:41:00     A    1     1
# 4: 2018-10-07 01:42:00     A    0     0
# 5: 2018-10-07 01:43:00     A    1     2
# 6: 2018-10-07 01:44:00     A    0     0
# 7: 2018-10-07 01:45:00     A    0     0
# 8: 2018-10-07 01:46:00     A    1     3
# 9: 2018-05-20 14:00:00     B    0     0
#10: 2018-05-20 14:01:00     B    0     0
#11: 2018-05-20 14:02:00     B    1     1
#12: 2018-05-20 14:03:00     B    1     1
#13: 2018-05-20 14:04:00     B    0     0
#14: 2018-05-20 14:05:00     B    1     2

或者在第一步之后,使用.GRP

df[!!spell, spell := .GRP, spell]

【讨论】:

【参考方案4】:

这行得通,

数据,

df <- structure(list(time = structure(c(1538876340, 1538876400, 1538876460,1538876520, 1538876580, 1538876640, 1538876700, 1538876760, 1526824800, 1526824860, 1526824920, 1526824980, 1526825040, 1526825100), class = c("POSIXct", "POSIXt"), tzone = "UTC"), group = c("A", "A", "A", "A", "A", "A", "A", "A", "B", "B", "B", "B", "B", "B"), is.5 = c(0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1)), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA, -14L))

我们按组拆分数据,

df2 <- split(df, df$group)

构建一个我们可以应用于列表的函数,

my_func <- function(dat)
  rst <- dat %>% 
    mutate(change = diff(c(0,is.5))) %>% 
    mutate(flag = change*abs(is.5)) %>% 
    mutate(spell = ifelse(is.5 == 0 | change == -1, 0, cumsum(flag))) %>% 
    dplyr::select(time, group, is.5, spell)
  return(rst)

然后应用它,

l <- lapply(df2, my_func)

我们现在可以转这个list back into a data frame:

do.call(rbind.data.frame, l)

【讨论】:

【参考方案5】:

一个选项是使用cumsum

library(dplyr)
df %>% group_by(group) %>%  arrange(group, time) %>% 
   mutate(spell = is.5 * cumsum( c(0,lag(is.5)[-1]) != is.5 & is.5!=0) )


# # A tibble: 14 x 4
# # Groups:   group [2]
#   time                  group     is.5   spell
#   <dttm>                <chr>     <dbl>  <dbl>
# 1 2018-10-07 01:39:00   A         0      0
# 2 2018-10-07 01:40:00   A         1      1
# 3 2018-10-07 01:41:00   A         1      1
# 4 2018-10-07 01:42:00   A         0      0
# 5 2018-10-07 01:43:00   A         1      2
# 6 2018-10-07 01:44:00   A         0      0
# 7 2018-10-07 01:45:00   A         0      0
# 8 2018-10-07 01:46:00   A         1      3
# 9 2018-05-20 14:00:00   B         0      0
# 10 2018-05-20 14:01:00  B         0      0
# 11 2018-05-20 14:02:00  B         1      1
# 12 2018-05-20 14:03:00  B         1      1
# 13 2018-05-20 14:04:00  B         0      0
# 14 2018-05-20 14:05:00  B         1      2

c(0,lag(is.5)[-1]) != is.5is.5 发生变化时,它负责分配一个新的ID(即spell);但我们希望避免将新的行分配给is.5 等于0 的行,这就是为什么我在cumsum 函数中有第二条规则(即(is.5!=0))。

但是,第二条规则仅阻止分配新 id(将 1 添加到前一个 id),但不会将 id 设置为 0。这就是为什么我将答案乘以is.5

【讨论】:

【参考方案6】:

一种不同的可能性(不涉及cumsum())可能是:

df %>%
 group_by(group) %>%
 mutate(spell = with(rle(is.5), rep(seq_along(lengths), lengths))) %>%
 group_by(group, is.5) %>%
 mutate(spell = dense_rank(spell)) %>%
 ungroup() %>%
 mutate(spell = ifelse(is.5 == 0, 0, spell))

   time                group  is.5 spell
   <dttm>              <chr> <dbl> <dbl>
 1 2018-10-07 01:39:00 A         0     0
 2 2018-10-07 01:40:00 A         1     1
 3 2018-10-07 01:41:00 A         1     1
 4 2018-10-07 01:42:00 A         0     0
 5 2018-10-07 01:43:00 A         1     2
 6 2018-10-07 01:44:00 A         0     0
 7 2018-10-07 01:45:00 A         0     0
 8 2018-10-07 01:46:00 A         1     3
 9 2018-05-20 14:00:00 B         0     0
10 2018-05-20 14:01:00 B         0     0
11 2018-05-20 14:02:00 B         1     1
12 2018-05-20 14:03:00 B         1     1
13 2018-05-20 14:04:00 B         0     0
14 2018-05-20 14:05:00 B         1     2

在这里,首先,按“组”分组,然后获取“is.5”的运行长度 ID。其次,它按“group”和“is.5”分组,并对运行长度 ID 上的值进行排名。最后,它将 0 分配给 "is.5" == 0 的行。

【讨论】:

以上是关于识别和计算法术(每组中的独特事件)的主要内容,如果未能解决你的问题,请参考以下文章

图像识别智能化趋势中,软件算法厂商如何保护独特软件优势

识别 Haskell 元组中的重复项

《皇室战争》克隆法术卡组分享

从一个开始枚举每组中的行

AI烟火检测识别算法在视频监控场景中的预警应用分析

独特的设备识别