【问题标题】:nested ifelse statement with programmatic number of nested levels具有编程嵌套级别数的嵌套 ifelse 语句
【发布时间】:2021-01-09 04:19:19
【问题描述】:

我有一个 3 列的矩阵。对于每一行,都要选择一个非缺失值,-如果在第1列中没有找到值,则搜索第2列,然后搜索第3列,并由用户给出顺序。

我对我复杂的嵌套 ifelse 方法感到相当满意 - 唉,这取决于给定列的相同长度。但是列的数量应该是灵活的(因此嵌套的 ifelse 语句的数量是灵活的) - 意思是,如果用户只选择一或两列,即使不想要的列包含一个值,也会产生 NA。

foo_mat <- structure(c(
  NA, 30L, 15, 0, NA, 100L, 87L, NA, 0, NA, 2L, NA,
  10, 0, NA
), .Dim = c(5L, 3L), .Dimnames = list(NULL, c(
  "a", "b", "c"
)))

foo <- function(x, preced) {
    ifelse(!is.na(x[, preced[1]]), x[, preced[1]],
      ifelse(!is.na(x[, preced[2]]), x[, preced[2]],
        x[, preced[3]]
      )
    )
}

foo_mat
#>       a   b  c
#> [1,] NA 100  2
#> [2,] 30  87 NA
#> [3,] 15  NA 10
#> [4,]  0   0  0
#> [5,] NA  NA NA

foo(foo_mat, c("a", "c", "b"))
#> [1]  2 30 15  0 NA

foo(foo_mat, preced = c("b", "a"))
#> Error in x[, preced[3]]: subscript out of bounds #(of course)

# desired output
#> [1]  100 87 15 0 NA

【问题讨论】:

  • 您的示例数据是一个好的开始,但缺少最后一部分。我认为它需要添加"b", "c"))),对吗?
  • 我的错,谢谢你看到它......但我的鼠标拖动复制/粘贴没有抓住它,所以无论如何一起编辑它可能很有用。谢谢,打扰了。

标签: r


【解决方案1】:

基础R:

apply(foo_mat[,c("a","c","b")], 1, function(z) c(na.omit(z), NA)[1])
# [1]  2 30 15  0 NA

匿名函数是一个两步过程:

  • 首先,去掉任何NAs,这样我们就可以抢到第一个非NA的值了
  • 其次,na.omit(.) 会返回integer(0) 是可行的,这不是你想要的,所以c(., NA)[1] 确保在na.omit(.) 之后,我们总是在c(.) 向量中至少有一个值,我们想要第一个;如果na.omit 什么都不返回,那么至少我们有一个NA

使用apply(foo_mat, 1, ...) 逐行执行此操作。您可以通过重新排列进入apply 数据的列来控制首选项顺序,就像我使用foo_mat[,c("a","c","b")] 一样。

作为一个函数:

foo <- function(data, preced = names(data)) apply(data[,preced,drop=FALSE], 1, function(z) c(na.omit(z), NA)[1])
foo(foo_mat, c("a", "c", "b"))
# [1]  2 30 15  0 NA

(drop=FALSE 是防御性的。Base R 默认 foo_mat[,"a"] 的行为是向量而不是 1 列矩阵。这会破坏很多东西,包括 apply。所以添加 drop=FALSE 可以防止默认减少行为。)

与其他答案一样快的替代方法:

foo <- function(data, preced) apply(data[,preced,drop=FALSE], 1, function(z) z[!is.na(z)][1])

相同的功能,更少的调用,简单的逻辑。

(署名:此替代方案是@tmfmnk、@Tjebo 和我的作品的组合。谢谢!)

【讨论】:

  • 仅供参考,@RuiBarradas 提供的答案提供了相同的结果。但是在这种大小的数据上,该函数的速度是原来的两倍。
  • 我觉得c(z[!is.na(z)], NA)[1]甚至可以写成z[!is.na(z)][1],因为integer(0)[1]就是NA
【解决方案2】:

这是一个函数foo,它可以根据发布的示例的要求工作。

foo <- function(x, preced){
  apply(x[, preced], 1, function(y){
    w <- !is.na(y)
    if(any(w)) y[w][1] else NA
  })
}

foo(foo_mat, c("a", "c", "b"))
#[1]  2 30 15  0 NA
foo(foo_mat, preced = c("b", "a"))
#[1] 100  87  15   0  NA

【讨论】:

    【解决方案3】:

    代替嵌套的ifelse,用coalesce创建一个函数可能会更好

    foo <- function(data, preced) {
            do.call(dplyr::coalesce, as.data.frame(data[, preced]))
           }
    
    
    foo(foo_mat, c("a", "c", "b"))
    #[1]  2 30 15  0 NA
    foo(foo_mat, c("b", "a"))
    #[1] 100  87  15   0  NA
    

    coalesce 根据所选数据集中的列自动为每一行选取第一个非 NA


    或者我们可以在base Rmax.col 中使用矢量化选项

    foo1 <- function(data, preced) {
             tmp <- data[, preced]
             i1 <- seq_len(nrow(tmp))
             j1 <- max.col(!is.na(tmp), "first")
             out <- tmp[cbind(i1, j1)]
             out
        }
    
    foo1(foo_mat, c("a", "c", "b"))
    #[1]  2 30 15  0 NA
    foo1(foo_mat, c("b", "a"))
    #[1] 100  87  15   0  NA
    

    【讨论】:

      猜你喜欢
      • 2013-08-03
      • 2019-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-13
      相关资源
      最近更新 更多