根据相邻元素有效地更改数据中的元素

cof*_*nky 14 r

让我深入研究.想象一下,你有这样的数据:

 df <- data.frame(one = c(1, 1, NA, 13), 
                  two = c(2, NA,10, 14), 
                three = c(NA,NA,11, NA), 
                 four = c(4, 9, 12, NA))
Run Code Online (Sandbox Code Playgroud)

这给了我们:

df
#   one two three four
# 1   1   2    NA    4
# 2   1  NA    NA    9
# 3  NA  10    11   12
# 4  13  14    NA   NA
Run Code Online (Sandbox Code Playgroud)

每行分别在第1,2,3和4周进行测量.假设数字代表自上次测量发生以来的一些累积测量值.例如,在第1行中,"4"列中的"4"表示第3周和第4周的累积值.

现在我想通过在测量前几周将测量值均匀分布到前几周没有进行测量的情况下"均匀"这些数字(在这里可以自由纠正我的术语).例如,第1行应该是读取的

 1 2 2 2 
Run Code Online (Sandbox Code Playgroud)

因为原始数据中的4表示2周的累积值(周"三"和"四"),并且4/2是2.

最终的最终结果应如下所示:

df
#  one two three four
# 1   1   2    2    2
# 2   1   3    3    3
# 3   5   5   11   12
# 4  13  14   NA   NA
Run Code Online (Sandbox Code Playgroud)

我对如何最好地接近这一点感到困惑.一个候选解决方案是获取所有缺失值的索引,然后计算运行的长度(发生多次的NA),并使用它以某种方式填充值.但是,我的真实数据很大,我认为这样的策略可能很耗时.有更简单,更有效的方法吗?

jos*_*ber 14

基本R解决方案是首先确定需要替换的索引,然后确定这些索引的分组,最后使用ave函数分配分组值:

clean <- function(x) {
  to.rep <- which(is.na(x) | c(FALSE, head(is.na(x), -1)))
  groups <- cumsum(c(TRUE, head(!is.na(x[to.rep]), -1)))
  x[to.rep] <- ave(x[to.rep], groups, FUN=function(y) {
    rep(tail(y, 1) / length(y), length(y))
  })
  return(x)
}
t(apply(df, 1, clean))
#      one two three four
# [1,]   1   2     2    2
# [2,]   1   3     3    3
# [3,]   5   5    11   12
# [4,]  13  14    NA   NA
Run Code Online (Sandbox Code Playgroud)

如果效率很重要(你的问题暗示它是),那么Rcpp解决方案可能是一个不错的选择:

library(Rcpp)
cppFunction(
"NumericVector cleanRcpp(NumericVector x) {
  const int n = x.size();
  NumericVector y(x);
  int consecNA = 0;
  for (int i=0; i < n; ++i) {
    if (R_IsNA(x[i])) {
      ++consecNA;
    } else if (consecNA > 0) {
      const double replacement = x[i] / (consecNA + 1);
      for (int j=i-consecNA; j <= i; ++j) {
        y[j] = replacement;
      }
      consecNA = 0;
    } else {
      consecNA = 0;
    }
  }
  return y;
}")
t(apply(df, 1, cleanRcpp))
#      one two three four
# [1,]   1   2     2    2
# [2,]   1   3     3    3
# [3,]   5   5    11   12
# [4,]  13  14    NA   NA
Run Code Online (Sandbox Code Playgroud)

我们可以比较更大的实例(10000 x 100矩阵)的性能:

set.seed(144)
mat <- matrix(sample(c(1:3, NA), 1000000, replace=TRUE), nrow=10000)
all.equal(apply(mat, 1, clean), apply(mat, 1, cleanRcpp))
# [1] TRUE
system.time(apply(mat, 1, clean))
#    user  system elapsed 
#   4.918   0.035   4.992 
system.time(apply(mat, 1, cleanRcpp))
#    user  system elapsed 
#   0.093   0.016   0.120 
Run Code Online (Sandbox Code Playgroud)

在这种情况下,与基本R实现相比,Rcpp解决方案提供大约40倍的加速.

  • 我很惊讶这个问题有多难.似乎应该有一个简单的情侣回答,但我不能为我的生活弄清楚. (4认同)
  • 看起来不错.不要以为我能做得更好.10k x 10k的5秒仍然很快.尽管如此,它可能会因巨大的表而失控.:-) (2认同)

Jos*_*ien 11

这是一个基本的R解决方案,几乎和josilber的Rcpp功能一样快:

spread_left <- function(df) {
    nc <- ncol(df)
    x <- rev(as.vector(t(as.matrix(cbind(df, -Inf)))))
    ii <- cumsum(!is.na(x))
    f <- tabulate(ii)
    v <- x[!duplicated(ii)]
    xx <- v[ii]/f[ii]
    xx[xx == -Inf] <- NA
    m <- matrix(rev(xx), ncol=nc+1, byrow=TRUE)[,seq_len(nc)]
    as.data.frame(m)
}
spread_left(df)
#   one two three four
# 1   1   2     2    2
# 2   1   3     3    3
# 3   5   5    11   12
# 4  13  14    NA   NA
Run Code Online (Sandbox Code Playgroud)

它通过矢量化所有内容并完全避免耗时的昂贵调用来设法相对快速apply().(缺点是它也相对混淆;要看它是如何工作的,做debug(spread_left),然后将它应用到dfOP中的小数据框架.

以下是所有当前发布的解决方案的基准:

library(rbenchmark)
set.seed(144)
mat <- matrix(sample(c(1:3, NA), 1000000, replace=TRUE), nrow=10000)
df <- as.data.frame(mat)

## First confirm that it produces the same results
identical(spread_left(df), as.data.frame(t(apply(mat, 1, clean)))) 
# [1] TRUE

## Then compare its speed
benchmark(josilberR     = t(apply(mat, 1, clean)),
          josilberRcpp  = t(apply(mat, 1, cleanRcpp)),
          Josh          = spread_left(df),
          Henrik        = t(apply(df, 1, fn)),
          replications = 10)
#           test replications elapsed relative user.self sys.self
# 4       Henrik           10   38.81   25.201     38.74     0.08
# 3         Josh           10    2.07    1.344      1.67     0.41
# 1    josilberR           10   57.42   37.286     57.37     0.05
# 2 josilberRcpp           10    1.54    1.000      1.44     0.11
Run Code Online (Sandbox Code Playgroud)


Hen*_*rik 7

另一种base可能性 我首先创建一个分组变量(grp),然后在其上进行"传播" ave.

fn <- function(x){
  grp <- rev(cumsum(!is.na(rev(x))))
  res <- ave(x, grp, FUN = function(y) sum(y, na.rm = TRUE) / length(y))
  res[grp == 0] <- NA
  res
}

t(apply(df, 1, fn))
#      one two three four
# [1,]   1   2     2    2
# [2,]   1   3     3    3
# [3,]   5   5    11   12
# [4,]  13  14    NA   NA
Run Code Online (Sandbox Code Playgroud)