【问题标题】:How to replace consecutive zeros in a data frame row with NA without using a for loop?如何在不使用 for 循环的情况下用 NA 替换数据框行中的连续零?
【发布时间】:2020-08-30 16:42:20
【问题描述】:

给定下面的数据框:

df <- data.frame(v1 = c(3, 0, 5, 1, 0),
                 v2 = c(2, 0, 0, 0, 0),
                 v3 = c(0, 0, 3, 0, 0),
                 v4 = c(0, 0, 0, 2, 0),
                 v5 = c(0, 0, 0, 0, 0),
                 v6 = c(0, 0, 0, 0, 7))
df
  v1 v2 v3 v4 v5 v6
1  3  2  0  0  0  0
2  0  0  0  0  0  0
3  5  0  3  0  0  0
4  1  0  0  2  0  0
5  0  0  0  0  0  7

想要的结果是以下数据框:

  v1 v2 v3 v4 v5 v6
1  3  2 NA NA NA NA
2 NA NA NA NA NA NA
3  5  0  3 NA NA NA
4  1  0  0  2 NA NA
5  0  0  0  0  0  7 

我想用NA 替换每一行中所有连续的零,条件是从左到右查看每一行,该行的下方不存在非零数。

我已经编写了一个 for 循环来实现这个结果,但是对于更大的数据帧来说这真的很慢:

for(i in 1:nrow(df)) {
  for (j in 1:ncol(df)){
    if ((df[i,j] == 0) & (apply(df[j:ncol(df)], 1, sum)[i] == 0)){
      df[i,j] <- NA
    }
  }
}     

我想要一个更有效的解决方案。

【问题讨论】:

    标签: r dataframe for-loop replace


    【解决方案1】:

    此解决方案使用 apply 对行进行循环,但仅在每行内使用矢量化函数,因此它有效地删除了一个非矢量化循环。

    trailing_zeros_to_NA=function(df){
        l=ncol(df)
        t(apply(df,1,function(row) {
            row=rev(row)
            row[seq_len(c(which(row!=0)-1,l)[1])]=NA
            rev(row)
        }))
    }     
    

    加速大约是 20 倍。

    original=function(df){
        for(i in 1:nrow(df)) {
            for (j in 1:ncol(df)){
                if ((df[i,j] == 0) & (apply(df[j:ncol(df)], 1, sum)[i] == 0)){
                df[i,j] <- NA
                }
            }
        }
        df
    }
    microbenchmark(original(df),trailing_zeros_to_NA(df))
    # Unit: microseconds
    #                      expr      min        lq      mean    median       uq       max neval
    #              original(df) 4672.029 5003.6895 5857.7659 5422.5345 6318.148 10676.192   100
    #  trailing_zeros_to_NA(df)  218.482  241.5945  312.7013  267.5695  315.370  2545.555   100
    
    

    【讨论】:

    • 谢谢多米尼克。一个优雅的解决方案:)。我试图找出哪个解决方案更好:你的还是 Ronak 的。它们都达到了相同的结果并且速度很快,但方式不同。选择哪种作为最终解决方案可能只是个人喜好问题。
    【解决方案2】:

    应该有一种更有效的方法来做到这一点,但这里是使用apply 的一次尝试:

    t(apply(df, 1, function(x) {
       inds <- suppressWarnings(pmax(max(which(x != 0)), 0))
       if(inds < length(x))  x[(inds + 1):length(x)] <- NA
       x
    }))
    

    另一种解决方案可能是:

    #Get the last non-zero column index for each row using `max.col`
    #cbind it with row index
    inds <- cbind(1:nrow(df), max.col(df != 0, ties.method = 'last'))
    #Remove rows where the non-zero values is at last column
    inds <- inds[!(inds[, 2] == ncol(df) & rowSums(df != 0) > 0), ]
    inds[inds[, 2] == 6, 2] <- 0
    #Create a sequence between last non-zero value to last value in column
    #for each row and replace it with NA
    df[do.call(rbind, Map(function(x, y) 
              cbind(x, y:ncol(df)), inds[, 1], inds[, 2] + 1))] <- NA
    

    【讨论】:

    • 谢谢罗纳克。您的代码完成了这项工作,尽管它返回一个矩阵而不是数据框。不过,这很容易解决。
    • 是的,我添加了另一种冗长的方法,将数据保存为数据框,并且在大型数据集上应该比apply 更快。
    • 有趣的是,microbenchmark 似乎表明“另一种解决方案”相当慢,包括大数据集。这可能是因为 Map 函数只是另一个 apply 循环的包装器...?
    【解决方案3】:

    这里也一样,必须有更优雅的方式,但您可以使用 data.table 并为每一行运行一个函数。

    require(data.table)
    dt <- data.table(ID = seq(1,5,1), # add unique ID
                     v1 = c(3, 0, 5, 1, 0),
                     v2 = c(2, 0, 0, 0, 0),
                     v3 = c(0, 0, 3, 0, 0),
                     v4 = c(0, 0, 0, 2, 0),
                     v5 = c(0, 0, 0, 0, 0),
                     v6 = c(0, 0, 0, 0, 7))
    
    dt[, v1:= ifelse(v1==0 & sum(v2,v3,v4,v5,v6)==0,NA,v1),by=ID]
    dt[, v2:= ifelse(v2==0 & sum(v3,v4,v5,v6)==0,   NA,v2),by=ID]
    dt[, v3:= ifelse(v3==0 & sum(v4,v5,v6)==0,      NA,v3),by=ID]
    dt[, v4:= ifelse(v4==0 & sum(v5,v6)==0,         NA,v4),by=ID]
    dt[, v5:= ifelse(v5==0 & sum(v6)==0,            NA,v5),by=ID]
    dt[, v6:= ifelse(v5==0 ,                        NA,v6),by=ID]
    

    【讨论】:

    • 感谢 Simone 的意见。不幸的是,对于超过 6 列的数据框,您的解决方案需要大量说明,这在我的实际问题中就是这种情况。
    • 是的,我同意。我确信有更好的方法可以使用 data.table 对右侧列的总和进行编码,但我无法做到。
    猜你喜欢
    • 2020-10-21
    • 2017-07-07
    • 2021-07-08
    • 2015-11-05
    • 2020-11-07
    • 1970-01-01
    • 2023-03-19
    • 2011-12-31
    • 2017-08-03
    相关资源
    最近更新 更多