【问题标题】:Change zero to ones in vector if surrounded by less than five consecutive zeros如果被少于五个连续的零包围,则将向量中的零更改为一
【发布时间】:2022-02-03 15:17:16
【问题描述】:

我有一个由0s 和1s 组成的向量,并且想要识别0s 字符串被1s 包围的索引。如果1s 之间的0s 的数量小于或等于5,我想将这些零更改为1s。

这是一个例子:

> x <- c(0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1)

在位置 7,8 和 9,我只有三个零,因此需要将它们更改为 1。其他零大于 5,因此无需更改。

生成的向量应如下所示:

> x_converted <- c(0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1)

我正在使用for 循环和if else 语句执行此操作,但我确信必须有更快的方法来执行此操作。

谢谢。

【问题讨论】:

    标签: r vector boolean zero


    【解决方案1】:

    rle 的可能解决方案不会更改 x 开头或结尾处的零的短路序列:

    # create the run length encoding
    r <- rle(x)
    
    # create an index of which zero's should be changed
    i <- r$values == 0 & r$lengths < 5 & 
      c(tail(r$values, -1) == 1, FALSE) & 
      c(FALSE, head(r$values, -1) == 1)
    
    # set the appropriate values to 1
    r$values[i] <- 1
    
    # use the inverse of rle to recreate the vector
    inverse.rle(r)
    

    给出:

    [1] 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
    

    【讨论】:

    • 哦,我忘了有个inverse.rle()函数!
    • @BenBolker 这只是 rep 的包装,添加了一些测试 ;-)
    【解决方案2】:

    您可以使用rle() 获取运行。然后只需根据运行的长度更改它,通过查看cumprod() 排除第一次运行。

    x_rle <- rle(x)
    
    x_0 <- cumprod(x_rle$values == 0)
    x_rev_0 <- rev(cumprod(rev(x_rle$values) == 0))
    
    x_rle$values <- ifelse(
      x_rle$lengths > 5 | x_0 | x_rev_0,
      x_rle$values,
      1
    )
    
    rep(x_rle$values, x_rle$lengths)
    #>  [1] 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
    

    【讨论】:

    • 这个答案也失败了。例如对于这个x &lt;- c(0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,1,1,1,0),这个ifelse 将最后的0 更改为1。
    • 已编辑以提供适用于该边缘情况的解决方案。
    【解决方案3】:

    rle() (run-length-encoding) 函数使这变得非常简单。

    x <- c(0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1)
    r <- rle(x)
    ## modify values appropriately
    r$values[r$values==0 & r$lengths<=5] <- 1
    ## convert back to full vector
    x_new <- rep(r$values, r$lengths)
    ## [1] 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
    

    但是,对于文字边缘情况,这仍然需要进行一些调整——这已将初始运行的 3 个零转换为 1。也许

    n <- length(r$values)
    rv_int <- r$values[2:(n-1)]
    rl_int <- r$lengths[2:(n-1)]
    rv_int[rv_int == 0 &
           rl_int <= 5] <- 1
    x_new <- rep(c(r$values[1],  rv_int, r$values[n]),
                 c(r$lengths[1], rl_int, r$lengths[n]))
    

    【讨论】:

    • 感谢您的回答。有没有办法避免更改前三个零,因为它们 not1s 包围?
    【解决方案4】:

    使用data.table::rleidrleid 创建运行长度类型组 id,用作ave 中的分组因子。然后aver 定义的组执行一个函数。

    r <- data.table::rleid(x)
    # [1] 1 1 1 2 2 2 3 3 3 4 4 5 5 5 5 5 5 6 6 6 6
    sub <- !r %in% c(1, max(r)) 
    
    x[sub] <- ave(x[sub], r[sub], FUN = function(x) ifelse(length(x) <= 3 & x == 0, 1, x))
    # [1] 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
    

    【讨论】:

      【解决方案5】:

      另一种方法,基于将x 转换为字符串,然后再转换回数字向量:

      library(tidyverse)
      
      x <- c(0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1)
      
      x %>% str_c(collapse = "") %>% 
        str_replace_all("(?<=1)0{1,5}(?=1)", \(x) str_dup("1", nchar(x))) %>% 
        str_split("") %>% flatten %>% as.numeric
      
      #>  [1] 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
      

      或者purrr::walkrle

      library(purrr)
      
      x <- c(0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1)
      
      z <- rle(x)
      
      walk(1:(length(z$values)-3), 
        ~ if (all(z$values[.x:(.x+2)] == c(1,0,1)) & z$lengths[.x+1] <= 5)
           z$values[.x+1] <<- 1)
      
      inverse.rle(z)
      
      #>  [1] 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
      

      【讨论】:

        猜你喜欢
        • 2018-09-09
        • 2022-11-02
        • 2019-08-04
        • 2016-09-03
        • 1970-01-01
        • 2016-08-17
        • 1970-01-01
        • 2013-12-16
        • 1970-01-01
        相关资源
        最近更新 更多