簡體   English   中英

查找不是 NA 的幾列中的最后一個(tidyverse)

[英]Find last of several columns that is not NA (tidyverse)

不知道我做錯了什么,但我正在努力獲取不是 NA 的最后一列(在幾列中)的每行索引。

使用 tidyverse 和cross,我得到與輸入列一樣多的 output 列,我希望一個 output 列具有相應列的索引。

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

我嘗試了以下方法(除其他外,受此啟發: 返回不是 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

您當前流程的問題:

  1. across將一次將一列傳遞給函數/表達式; 您的代碼需要一行或一個矩陣/框架。 為此, across是不合適的。

  2. 最后一行所需的NA的 output 與邏輯不一致: .is.na(.x)應該返回c(F,F,F)它仍然有一個 max 然后,您的邏輯需要自定義 function,因為您需要以不同方式處理它。

嘗試將max.col改編為自定義 function:

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

注意:我已經多次編輯/更改了 function,以確保 API 與此自定義 function 的意圖一致。 就目前而言,在我看來, notna名稱中的 notna 反映了一種“空虛”感( 0NA )。 使用此邏輯,function 可用於logical (如此處)和numeric數據。 也許這是矯枉過正,但我更喜歡跨輸入類一致/可預測地運行的 API。

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

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

您想使用c_across()rowwise()來執行此操作。 rowwise()的工作方式類似於group_by_all() ,但它更明確。 c_across()從列中創建平面向量(而cross across()創建小標題)。

如果我們首先單獨定義一個 function 以提取最后一個非NA值,如果沒有則返回NA

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


然后我們可以應用 function c_across()我們需要的變量,但只有在使用rowwise()轉換為rowwise_df之后

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

base R

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

代表 package (v0.3.0) 於 2020 年 12 月 29 日創建

從一個 NA 向量開始,您可以逐步遍歷每個 col,如果給定元素通過了返回TRUEcheck_fun ,則將該 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM