以下是我的数据框。它有行名和列名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
row1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0
row2 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0
我想基于连续的零推导出一个列测试(从最后一列开始,每行的列。下面是一个例子。对于第一行,有8个连续的零,所以测试行中的值应该是8.对于第二行,结果应该是1只有一个零。(我想从15开始考虑并返回到零开始的位置)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 test
row1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 8
row2 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 1
实现这一目标的最佳方法是什么?
使用rle
的解决方案:
getConsecZeroRle <- function(x) {
foo <- rle(x)
foo$lengths[tail(which(foo$values), 1)]
}
result <- apply(df[, -1] == 0, 1, function(x) getConsecZeroRle(x))
df$test <- as.numeric(result)
df$test[is.na(df$test)] <- 0
说明:
使用apply
迭代数据帧的子集。对于每一行计算连续零(rle
)的长度,并使用tail
提取最后一个值。没有零的行将产生NA
(使用is.na(df$test)
)用零替换它们。
使用sum
的解决方案:
getConsecZeroSum <- function(x) {
x[1:tail(which(!x), 1)] <- FALSE
sum(x)
}
df$test <- apply(df[, -1] == 0, 1, function(x) getConsecZeroSum(x))
说明:
提取每行中的最后一个FALSE
值,然后将所有内容转换为FALSE
(x[1:tail(which(!x), 1)] <- FALSE
),然后使用sum
从末尾计算零值。
结果:
# a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 test
# 1 row1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 8
# 2 row2 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 1
你可以简单地找到不等于0
的第一个值的索引(从最后一列开始)然后减去一个:
df$test2 <- apply(df[,ncol(df):1]==0, 1, which.min) - 1
df
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 test2
#1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 8
#2 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 1
另一个答案:
由于我很好奇如何在没有apply
-ing的情况下做到这一点,我提出了一个(公认的复杂)Reduce
解决方案。不是我推荐的解决方案,但我有兴趣看看有没有办法做到这一点:
iniCol <- setNames(df[,ncol(df)] == 0, as.numeric(df[,ncol(df)] == 0))
df$test2 <- Reduce(function(ini, add) {temp <- ifelse(pmin(as.numeric(names(ini)), add==0) == 0, ini, rowSums(cbind(ini, add == 0)))
ini <- setNames(temp, pmin(as.numeric(names(ini)), add==0))},
df[,(ncol(df)-1):1],
ini = iniCol)
这背后的想法是使用names
属性来跟踪列是否曾经0
。如果是,那么我们停止计数,否则继续计数。