【问题标题】:Find last of several columns that is not NA (tidyverse)查找不是 NA 的几列中的最后一个(tidyverse)
【发布时间】:2020-12-29 12:50:51
【问题描述】:

不确定我做错了什么,但我正在努力获取不是 NA 的最后一列(在几列中)的每行索引。

使用 tidyverse 和cross,我得到的输出列与输入列一样多,我希望一个输出列具有相应列的索引。

dat <- data.frame(id = c(1, 2, 3),
                  x  = c(1, NA, NA),
                  y  = c(NA, NA, NA),
                  z  = c(3, 1, NA))

我尝试了以下方法(其中受此启发:Return last data frame column which is not NA):

dat %>%
  mutate(last = across(-id, ~max.col(!is.na(.x), ties.method="last")))

预期结果是:

  id  x  y  z  last
1  1  1 NA  3  3
2  2 NA NA  1  3
3  3 NA NA NA  NA

【问题讨论】:

  • max.col 需要一个 matrix(参考:?max.col),而 .x 是一个向量,所以第一个有效调用是 max.col(c(1,1,NA))(它返回一个长度为 3 的向量, c(1,1,NA).
  • 我明白了,但是将 .x 更改为 .将导致获得几个新列。
  • 或许你应该考虑dat %&gt;% cbind(last=max.col(!is.na(.[-1]), ties.method = "last"))
  • 第二,你的第三行NAmax*不一致:即使!is.na(.x)对这三个都是假的,三个假的最大值(类似于max(c(0,0,0)))仍然有效,因此它仍然会返回 3。听起来您需要特殊的逻辑,可能是自定义函数。
  • 您几乎肯定不想使用. 而不是.x

标签: r tidyverse


【解决方案1】:

您当前流程的问题:

  1. across 将一次将一个 传递给函数/表达式;您的代码需要一行或一个矩阵/框架。为此,across 不合适。

  2. 您希望最后一行的NA 输出与逻辑不一致:!is.na(.x) 应该返回c(F,F,F)仍然有一个最大值。然后,您的逻辑需要一个自定义函数,因为您需要以不同的方式处理它。

尝试将max.col 改编成自定义函数:

max.col.notna <- function (m, ties.method = c("random", "first", "last")) {
    ties.method <- match.arg(ties.method)
    tieM <- which(ties.method == eval(formals()[["ties.method"]]))
    out <- .Internal(max.col(as.matrix(m), tieM))
    m[] <- !m %in% c(0,NA) # 'm[] <-' is required to maintain the matrix shape
    replace(out, rowSums(m) == 0, NA_integer_)
}

dat %>%
  mutate(last = max.col.notna(!is.na(select(., -id)), ties.method = "last"))
#   id  x  y  z last
# 1  1  1 NA  3    3
# 2  2 NA NA  1    3
# 3  3 NA NA NA   NA

注意:我已经多次编辑/更改了函数,试图确保与此自定义函数的intent 一致的 API。就目前而言,函数名称中的notna 对我来说反映了一种“空虚”感(0NA)。使用此逻辑,该函数可用于logical(如此处)和numeric 数据。也许这有点矫枉过正,但我​​更喜欢跨输入类一致/可预测地运行的 API。

【讨论】:

  • 有趣的解决方案,感谢您的解释。我现在明白 wy cross 没有用。不过,我接受了@Ronak Shah 的解决方案,因为它简短而简单。
  • 没关系,有时rowwise 是最简单/最好的方法。意识到对于更大的数据,rowwise 的表现可怕。只要您的数据在数百行以下,那应该没问题(远不止这些,您可能会感觉到差异)。
  • 是的,完全同意。这就是为什么我通常会尽量避免 rowwise 和 c_across 并使用 apply(across(select vars), 1, my function) 的 hbyrid,但这在这里不起作用。幸运的是,即使我目前有 55k 行,解决方案也只需要 2-3 秒左右。
【解决方案2】:

tidyverse 并不真正适合按行操作。大多数情况下,将数据重新整形为长格式(如@Rui Barradas 回答所示)是一种好方法。

这是使用rowwise 保持数据范围的一种方法。

library(dplyr)

dat %>%
  rowwise() %>%
  mutate(last = {ind = which(!is.na(c_across(x:z))); 
                if(length(ind)) tail(ind, 1) else NA})

#    id     x   y        z  last
#  <dbl> <dbl> <lgl> <dbl> <int>
#1     1     1 NA        3     3
#2     2    NA NA        1     3
#3     3    NA NA       NA    NA

【讨论】:

  • 是的,在 tidyverse 中 rowwise 不是很好,但您的解决方案有效(而且速度很快)。我也通过重塑找到了解决方案,但是考虑到我的数据集的大小,它的速度很慢。
【解决方案3】:

R 基础解决方案:

dat$last = apply(dat[,2:4], 1, 
                 FUN = function(x) ifelse(max(which(is.na(x))) == length(x), NA, max(which(is.na(x)))+1 ))

dat

# id  x  y  z last
# 1  1  1 NA  3    3
# 2  2 NA NA  1    3
# 3  3 NA NA NA   NA

【讨论】:

    【解决方案4】:

    您想使用c_across()rowwise() 来执行此操作。 rowwise() 的工作方式类似于 group_by_all(),但它更明确。 c_across() 从列中创建平面向量(而 across() 创建小标题)。

    如果我们先单独定义一个函数来取出最后一个非NA的值,如果没有则返回NA

    get_last <- function(x){
      y <- c(NA,which(!is.na(x)))
      y[length(y)]
    }
    
    
    

    然后我们可以将该函数 c_across() 应用到我们需要的变量中,但只有在使用 rowwise() 转换为 rowwise_df 之后

    dat %>%
      rowwise() %>%
      mutate(last = get_last(c_across(x:z)))
    

    【讨论】:

      【解决方案5】:

      baseR

      df <- data.frame(id = c(1, 2, 3),
                              x  = c(1, NA, NA),
                              y  = c(NA, NA, NA),
                              z  = c(3, 1, NA))
      
      
      df$last <- apply(df[-1], 1, function(x) max(as.vector(!is.na(x)) * seq_len(length(x))))
      df$last[df$last == 0] <- NA
      df
      #>   id  x  y  z last
      #> 1  1  1 NA  3    3
      #> 2  2 NA NA  1    3
      #> 3  3 NA NA NA   NA
      

      reprex package (v0.3.0) 于 2020 年 12 月 29 日创建

      【讨论】:

        【解决方案6】:

        从 NA 向量开始,您可以逐步遍历每个 col,如果给定元素通过您的 check_fun 返回 TRUE,则将该 col 的索引分配给该元素。与此处其他答案的区别在于,这不会逐行检查条件或从数据创建矩阵。不确定为每列创建两个新的临时向量是否比首先将整个数据转换为矩阵更好/更差。

        library(tidyverse) # purrr and dplyr
        
        last_matching_ind <- function(dat, check_fun){
          check_fun <- as_mapper(check_fun)
          reduce2(dat, seq_along(dat), .init = NA_integer_,
                  function(prev, dat, ind) if_else(check_fun(dat), ind, prev) )
        }
        
        dat %>% 
          mutate(last = last_matching_ind(dat[-1], ~ !is.na(.x)))
        
        #   id  x  y  z last
        # 1  1  1 NA  3    3
        # 2  2 NA NA  1    3
        # 3  3 NA NA NA   NA
        

        【讨论】:

          猜你喜欢
          • 2020-06-26
          • 2018-09-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多