识别和计算法术(每组中的独特事件)
Posted
技术标签:
【中文标题】识别和计算法术(每组中的独特事件)【英文标题】:Identify and count spells (Distinctive events within each group) 【发布时间】:2019-08-23 02:28:09 【问题描述】:我正在寻找一种有效的方法来识别时间序列中的法术/跑步。在下图中,前三列是我所拥有的,第四列 spell
是我要计算的。我试过使用dplyr
的lead
和lag
,但这太复杂了。我试过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 的 rle
和 dense_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 == 1
而values
应保持为零。
当我们将cumsum(r$values)
与r$values
相乘时,我们可以实现这一点;其中后者是0
s 和1
s 的向量。
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.5
当is.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 的行。
【讨论】:
以上是关于识别和计算法术(每组中的独特事件)的主要内容,如果未能解决你的问题,请参考以下文章