【问题标题】:Create grouping variable for consecutive sequences and split vector为连续序列和分割向量创建分组变量
【发布时间】:2011-07-10 11:21:59
【问题描述】:

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

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 vector sequence data-partitioning


    【解决方案1】:

    大量使用一些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
    

    【讨论】:

      【解决方案2】:

      daroczig 写道:“你可以基于 diff 编写很多更简洁的代码”...

      这是一种方法:

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

      编辑(添加时间):

      Tommy 发现注意类型可能会更快;它变得更快的原因是split 在整数上更快,实际上在因子上更快。

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

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

      只需 cing 和 1L,因此结果是一个整数会大大加快速度。

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

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

      > 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 
      

      【讨论】:

      • 是的,这是一种更简洁的方式! :) 我不知道split,感谢您关注这个有用的功能。
      • 我应该注意,在使用 as.integer 时应该小心,因为它返回截断的值,当使用浮点运算创建数字时,这可能不是您想要的,例如,@987654339 @返回0
      • 你能解释一下 diff() 函数在做什么以及它是如何工作的吗?官方文档根本没有帮助我理解它。
      • 它只是计算术语之间的差异。帮助可能会令人困惑,因为它比这更通用,因为它允许不同的滞后,并允许重复该过程,以获得双重差异(差异的差异)等等。
      【解决方案3】:

      约书亚和亚伦在​​现场。但是,通过仔细使用正确的类型、整数和逻辑,他们的代码仍然可以快两倍以上:

      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 
      

      【讨论】:

      • 哇!我没想到会产生如此大的不同。
      • Tommy,我知道为什么它更快,并编辑了您的帖子以添加它。我不确定这是否是适当的礼仪;希望你不介意。 (此外,它必须经过同行评审,所以如果你没有立即看到它,这就是原因。)
      • 显然我的编辑被拒绝了;我已将时间添加到我的答案中。
      【解决方案4】:

      您可以轻松定义切点:

      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 写出更简洁的代码:) 祝你好运!

      【讨论】:

        【解决方案5】:

        您可以创建data.frame 并使用diffifelsecumsum 将元素分配给组,然后使用tapply 进行聚合:

        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
        

        【讨论】:

          猜你喜欢
          • 2014-07-09
          • 2022-08-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-06-28
          相关资源
          最近更新 更多