为连续序列和拆分向量创建分组变量

Create grouping variable for consecutive sequences and split vector

提问人:letsrock 提问时间:3/8/2011 最后编辑:Henrikletsrock 更新时间:3/26/2021 访问量:5859

问:

我有一个向量,例如,我想将形成一个规则的、连续序列的“相邻”元素组合在一起,即增加 1,在一个参差不齐的向量中,结果是:c(1, 3, 4, 5, 9, 10, 17, 29, 30)

L1:1 L2:3,4,5 L3:9,10 L4:17



L5:29,30

朴素代码(前 C 程序员):

partition.neighbors <- function(v)
{
    result <<- list() #jagged array
    currentList <<- v[1] #current series

    for(i in 2:length(v))
    {
        if(v[i] - v [i-1] == 1)
        {
            currentList <<- c(currentList, v[i])
        }
        else
        {
            result <<- c(result, list(currentList))
            currentList <<- v[i] #next series
        }       
    }

    return(result)  
}

现在我明白了

a) R 不是 C(尽管有大括号) b) 全局变量是纯粹的邪恶
c)
这是一种实现结果的非常低效的方法

,因此欢迎任何更好的解决方案。

R 向量 序列 数据分区

评论


答:

4赞 daroczig 3/8/2011 #1

您可以轻松定义切割点:

which(diff(v) != 1)

基于该尝试:

v <- c(1,3,4,5,9,10,17,29,30)
cutpoints <- c(0, which(diff(v) != 1), length(v))
ragged.vector <- vector("list", length(cutpoints)-1)
for (i in 2:length(cutpoints)) ragged.vector[[i-1]] <- v[(cutpoints[i-1]+1):cutpoints[i]]

其结果是:

> ragged.vector
[[1]]
[1] 1

[[2]]
[1] 3 4 5

[[3]]
[1]  9 10

[[4]]
[1] 17

[[5]]
[1] 29 30

这个算法不是一个好算法,但你可以根据:)编写更整洁的代码祝你好运!diff

12赞 Aaron left Stack Overflow 3/8/2011 #2

Daroczig 写道:“你可以根据”......diff

这是一种方法:

split(v, cumsum(diff(c(-Inf, v)) != 1))

编辑(添加时间):

Tommy 发现,通过谨慎使用类型,这可以更快;它变得更快的原因是它在整数上更快,实际上在因子上更快。split

这是约书亚的解决方案;的结果是一个数字,因为它是 'd 和 ,所以它是最慢的。cumsumc1

system.time({
a <- cumsum(c(1, diff(v) != 1))
split(v, a)
})
#   user  system elapsed 
#  1.839   0.004   1.848 

只是这样,结果是一个整数,大大加快了它的速度。c1L

system.time({
a <- cumsum(c(1L, diff(v) != 1))
split(v, a)
})
#   user  system elapsed 
#  0.744   0.000   0.746 

这是 Tommy 的解决方案,供参考;它也在整数上分裂。

> system.time({
a <- cumsum(c(TRUE, diff(v) != 1L))
split(v, a)
})
#   user  system elapsed 
#  0.742   0.000   0.746 

这是我的原始解决方案;它还在整数上进行拆分。

system.time({
a <- cumsum(diff(c(-Inf, v)) != 1)
split(v, a)
})
#   user  system elapsed 
#  0.750   0.000   0.754 

这是 Joshua 的,结果转换为 .split

system.time({
a <- cumsum(c(1, diff(v) != 1))
a <- as.integer(a)
split(v, a)
})
#   user  system elapsed 
#  0.736   0.002   0.740 

整数向量上的所有版本都大致相同;如果该整数向量已经是一个因子,它可能会更快,因为从整数到因子的转换实际上需要大约一半的时间。在这里,我直接把它变成一个因素;通常不建议这样做,因为它取决于因子类的结构。此处仅用于比较目的。split

system.time({
a <- cumsum(c(1L, diff(v) != 1))
a <- structure(a, class = "factor", levels = 1L:a[length(a)])
split(v,a)
})
#   user  system elapsed 
#  0.356   0.000   0.357 

评论

0赞 daroczig 3/8/2011
是的,这是更整洁的方式!:)我不知道,谢谢你把我的注意力引向这个有用的功能。split
0赞 Aaron left Stack Overflow 5/14/2011
我应该注意,使用时应该小心,因为它返回截断的值,当使用浮点算术创建数字时,这可能不是您想要的,例如,返回 .as.integeras.integer(0.3*3+0.1)0
0赞 OnlyDean 6/21/2018
你能解释一下 diff() 函数在做什么以及它是如何工作的吗?官方文档根本没有帮助我理解它。
0赞 Aaron left Stack Overflow 6/21/2018
它只是计算术语之间的差异。这种帮助可能令人困惑,因为它比这更普遍,因为它允许不同的滞后,并且允许重复该过程,用于双重差异(差异的差异)等等。
24赞 Joshua Ulrich 3/8/2011 #3

大量使用一些 R 习语:

> split(v, cumsum(c(1, diff(v) != 1)))
$`1`
[1] 1

$`2`
[1] 3 4 5

$`3`
[1]  9 10

$`4`
[1] 17

$`5`
[1] 29 30
4赞 James 3/8/2011 #4

您可以使用 和 创建元素并将其分配给组,然后使用以下命令进行聚合:data.framediffifelsecumsumtapply

v.df <- data.frame(v = v)
v.df$group <- cumsum(ifelse(c(1, diff(v) - 1), 1, 0))
tapply(v.df$v, v.df$group, function(x) x)

$`1`
[1] 1

$`2`
[1] 3 4 5

$`3`
[1]  9 10

$`4`
[1] 17

$`5`
[1] 29 30
8赞 Tommy 4/8/2011 #5

约书亚和亚伦很到位。但是,通过谨慎使用正确的类型、整数和逻辑,它们的代码仍然可以提高两倍以上的速度:

split(v, cumsum(c(TRUE, diff(v) != 1L)))

v <- rep(c(1:5, 19), len = 1e6) # Huge vector...
system.time( split(v, cumsum(c(1, diff(v) != 1))) ) # Joshua's code
# user  system elapsed 
#   2.64    0.00    2.64 

system.time( split(v, cumsum(c(TRUE, diff(v) != 1L))) ) # Modified code
# user  system elapsed 
# 1.09    0.00    1.12 

评论

0赞 Aaron left Stack Overflow 5/5/2011
哇!我不会猜到它会产生如此大的差异。
0赞 Aaron left Stack Overflow 5/6/2011
汤米,我弄清楚了为什么它更快,并编辑了你的帖子以添加它。我不确定这是否是正确的礼仪;希望你不介意。(此外,它必须经过同行评审,所以如果你没有立即看到它,这就是原因。
0赞 Aaron left Stack Overflow 5/14/2011
显然我的编辑被拒绝了;我已经在我的答案中添加了时间。