【问题标题】:Move through rows in R在 R 中移动行
【发布时间】:2021-12-28 21:22:53
【问题描述】:
value1 value2
1 1
0 1
0 1
0 1
2 2
0 2
0 2
3 3
4 4
0 4

我在数据框中有一列“value1”,我想重复相同的值直到下一个数字 > 0。结果我想要的是列“value2”。这是大量数据,所以循环代码会很棒。

感谢社区!!

【问题讨论】:

  • 如果非零值总是增加,就像你的例子一样,使用cummax。如果不是,请将 0 替换为 NA,并使用众多 LOCF 方法之一。
  • 请说明您是否可以有多个连续的非零值。如果是这样,基于cumsum 的答案将不起作用。

标签: r


【解决方案1】:

我们可以使用cumsum 的条件:

library(dplyr)

df %>% 
  mutate(value2 = cumsum(value1>0))
   value1 value2
1       1      1
2       0      1
3       0      1
4       0      1
5       2      2
6       0      2
7       0      2
8       3      3
9       4      4
10      0      4

数据:

df <- structure(list(value1 = c(1L, 0L, 0L, 0L, 2L, 0L, 0L, 3L, 4L, 
0L)), class = "data.frame", row.names = c(NA, -10L))

【讨论】:

  • 谢谢!!它工作得很好
【解决方案2】:

使用 data.table 并与 base 和 dplyr 进行比较

data.table 方法

这是一个基于 data.table 的答案版本,比基本版本和 dplyr 版本都快。

set.seed(65L)
df <- data.table(v1 = sample(0:4, 1000, replace = TRUE), v2 = 0)
df[, v2 := cumsum(v1 > 0)]
head(df, 12)
    v1 v2
 1:  2  1
 2:  1  2
 3:  3  3
 4:  0  3
 5:  0  3
 6:  4  4
 7:  2  5
 8:  4  6
 9:  4  7
10:  0  7
11:  4  8
12:  2  9

三法比较:等价

set.seed(65L)
df <- data.frame(v1 = sample(0:4, 1000, replace = TRUE), v2 = 0)
df2 <- df
dt <- as.data.table(df)

# data.table
dt[, v2 := cumsum(v1 > 0)]

# base R
if (df$v1[1L] > 0) {df$v2[1L] <- 1}
for (i in 2:length(df$v1)) {
  df$v2[i] <- df$v2[i - 1] + if (df$v1[i] > 0) {1} else {0}
}

# dplyr
if (df2$v1[1L] > 0) {df2$v2[1L] <- 1}
df2 <- df2 %>% mutate(v2 = cumsum(v1>0))

all.equal(dt, df, check.attributes = FALSE)
[1] TRUE
all.equal(dt, df2, check.attributes = FALSE)
[1] TRUE
all.equal(df, df2, check.attributes = FALSE)
[1] TRUE

三法对比:速度

library(microbenchmark)
microbenchmark(DT = dt[, v2 := cumsum(v1 > 0)],
Base = {if (df$v1[1L] > 0) {df$v2[1L] <- 1};for (i in 2:length(df$v1)) {df$v2[i] <- df$v2[i - 1] + if (df$v1[i] > 0) {1} else {0}}},
DP = {if (df2$v1[1L] > 0) {df2$v2[1L] <- 1};df2 <- df2 %>% mutate(v2 = cumsum(v1>0))},
setup = 'set.seed(65L);df <- data.table(v1 = sample(0:4, 1000, replace = TRUE), v2 = 0); df2 <- df; dt <- as.data.table(df)',
control = list(order = 'block'), times = 1000L)

Unit: microseconds
 expr    min      lq      mean median      uq     max neval cld
   DT  204.1  210.20  216.6067  212.0  216.80   382.9  1000 a  
 Base 7956.1 8322.85 8936.3439 8457.6 8702.25 22219.4  1000   c
   DP  916.0  930.50  994.4782  939.8  977.60  6157.4  1000  b

所以dplyr 方法比基本循环快约 9 倍,data.table 方法比 dplyr 快约 4.5 倍,比基本循环快 40 倍以上!

【讨论】:

    【解决方案3】:

    对于这些情况,我通常将 0 替换为 NA 值,并使用 tidyr::fill() 向前复制最后一个非缺失(即非零)值。

    这是一个例子:

    df <- data.frame(
      value1 = c(1, 0, 0, 0, 2, 0, 0, 3, 4, 0)
    ) 
    
    
    library(dplyr)
    
    df %>% 
      mutate(
        value2 = ifelse(value1 == 0, NA_real_, value1)
      ) %>% 
      tidyr::fill(value2, .direction = "down")
    

    结果:

       value1 value2
    1       1      1
    2       0      1
    3       0      1
    4       0      1
    5       2      2
    6       0      2
    7       0      2
    8       3      3
    9       4      4
    10      0      4
    

    即使值的增加大于/小于 1,这也有效,例如,情况并非如此。 cumsum().

    【讨论】:

    • 我不知道这种方法。谢谢!
    【解决方案4】:

    无需使用循环。使用base R 的一种选择是:

    df <- data.frame(value1 = c(1,0,0,0,2,0,0,3,4,0))
    df$value2 <- cumsum(ifelse(df$value1 > 0, 1, 0))
    

    产量:

    > df
       value1 value2
    1       1      1
    2       0      1
    3       0      1
    4       0      1
    5       2      2
    6       0      2
    7       0      2
    8       3      3
    9       4      4
    10      0      4
    

    【讨论】:

      【解决方案5】:

      使用基础 R

      可能有更优雅的方法可以做到这一点,但假设“value2”列已经在数据框中,您可以执行以下操作。这个答案完全依赖于基数 R,而且 v1 是增加还是减少都无关紧要,只是它不为零。我将创建一个数据框作为示例。

      set.seed(65L)
      df <- data.frame(v1 = sample(0:4, 1000, replace = TRUE), v2 = 0)
       
      head(df, 12)
         v1 v2
      1   2  0
      2   1  0
      3   3  0
      4   0  0
      5   0  0
      6   4  0
      7   2  0
      8   4  0
      9   4  0
      10  0  0
      11  4  0
      12  2  0
       
      # Handle the first row seperately to get rid of i - 1 headaches
      if (df$v1[1L] > 0) {df$v2[1L] <- 1}
       
      # Now the loop. Safer to do seq_len(length(df$v1) - 1) + 1 but that's more confusing
       
      for (i in 2:length(df$v1)) {
          df$v2[i] <- df$v2[i - 1] + if (df$v1[i] > 0) {1} else {0}
      }
       
      head(df, 12)
         v1 v2
      1   2  1
      2   1  2
      3   3  3
      4   0  3
      5   0  3
      6   4  4
      7   2  5
      8   4  6
      9   4  7
      10  0  7
      11  4  8
      12  2  9
      

      【讨论】:

      • 使用for循环遍历行在某种意义上是低效的,因为它相对耗时,尤其是对于具有许多行的大型数据集。
      • 是的,但它比以前快得多,并且 dplyr 没有针对速度进行优化。在我的笔记本电脑上,这在 1000 行上花费了 9 毫秒。我无法真正与其他方法进行比较,因为如果第一个条目为 0,它们将失败。
      • 没错,这是使用base R 的原因之一。但是如果您必须迭代超过 10,000,000 多行并执行比 OP 更复杂的事情怎么办?
      • 当然,这就是为什么我说它不优雅,但它可以完成工作。如果我们真的需要优化速度,我认为data.table 滚动求和方法是最好的。
      • @Dion 我根据此处的最佳答案在新答案中发布了 data.table 版本:meta.stackexchange.com/questions/25209/… 并在下面提供了速度统计信息。 dplyr 比 base 快 9 倍,data.table 快 40 多倍。
      猜你喜欢
      • 1970-01-01
      • 2021-12-26
      • 2020-07-13
      • 2014-07-31
      • 1970-01-01
      • 2017-12-04
      • 1970-01-01
      • 2021-03-19
      • 2011-12-17
      相关资源
      最近更新 更多