从一行中的选定单元格中获取第一个非空值

Get the first non-null value from selected cells in a row

朋友们下午好!

我目前正在 R 中执行一些计算(df 显示在下方)。我的目标是在新列中显示每行选定单元格中的第一个非空值。

我的 df 是:

MD <- c(100, 200, 300, 400, 500)
liv <- c(0, 0, 1, 3, 4)
liv2 <- c(6, 2, 0, 4, 5)
liv3 <- c(1, 1, 1, 1, 1)
liv4 <- c(1, 0, 0, 3, 5)
liv5 <- c(0, 2, 7, 9, 10)
df <- data.frame(MD, liv, liv2, liv3, liv4, liv5)

我想显示(在名为“liv6”的列中)来自 5 个单元格的第一个非空值(给定数据,liv1 = 0、liv2 = 6、liv3 = 1、liv 4 = 1 和 liv5 = 1).结果应该是 6。并且应该对我的数据框中的每一行重复此计算..

我知道如何在 Python 中执行此操作,但在 R..

中不知道

非常感谢任何帮助!

dplyr 的一个选项可以是:

df %>%
    rowwise() %>%
    mutate(liv6 = with(rle(c_across(liv:liv5)), values[which.max(values != 0)]))

     MD   liv  liv2  liv3  liv4  liv5  liv6
  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1   100     0     6     1     1     0     6
2   200     0     2     1     0     2     2
3   300     1     0     1     0     7     1
4   400     3     4     1     3     9     3
5   500     4     5     1     5    10     4

一个简单的基本 R 选项是跨相关列应用(我在这里排除了 MD,您可以使用您想要的任何数据框子集样式),然后只取 [=14= 的第一个值] 该行的值。

df$liv6 <- apply(df[-1], 1, \(x) head(x[x > 0], 1))
df
#>    MD liv liv2 liv3 liv4 liv5 liv6
#> 1 100   0    6    1    1    0    6
#> 2 200   0    2    1    0    2    2
#> 3 300   1    0    1    0    7    1
#> 4 400   3    4    1    3    9    3
#> 5 500   4    5    1    5   10    4

基础 R 解决方案:

df$liv6 <- apply(df[-1], 1, function(x) x[min(which(x != 0))])

输出

df
   MD liv liv2 liv3 liv4 liv5 liv6
1 100   0    6    1    1    0    2
2 200   0    2    1    0    2    2
3 300   1    0    1    0    7    1
4 400   3    4    1    3    9    1
5 500   4    5    1    5   10    1

一种方法是使用 purrr::detect 检测每行的第一个 non-zero 元素。

我们定义一个函数,它接受一个数字向量(行)和 returns 一个布尔值,指示每个元素是否是 non-zero:

is_nonzero <- function(x) x != 0

我们使用此函数通过 purrr:detect

检测每行中的第一个 non-zero 元素
first_nonzero <- apply(df %>% dplyr::select(liv:liv5), 1, function(x) {
   purrr::detect(x, is_nonzero, .dir = "forward")  
})

我们终于创建了新列:

df$liv6 <- first_nonzero

因此,我们有

> df
MD liv liv2 liv3 liv4 liv5 liv6
100   0    6    1    1    0    6
200   0    2    1    0    2    2
300   1    0    1    0    7    1
400   3    4    1    3    9    3
500   4    5    1    5   10    4

另一个简单的解决方案是:

Reduce(function(x, y) ifelse(!x, y, x), df[, -1])
#[1] 6 2 1 3 4

这种方式应该非常有效,因为我们按列“扫描”,因为据推测,数据的列比行少得多。

Reduce 方法是一种更实用的简单形式,old-school,循环:

ans = df[, 2]
for(j in 3:ncol(df)) {
  i = !ans
  ans[i] = df[i, j]
}
ans
#[1] 6 2 1 3 4