簡體   English   中英

基於嵌套 if else 條件創建新列並比較 R 中列表值的有效方法

[英]Efficient way to create new column based on nested if else conditions and comparing values from lists in R

輸入dt -

dt <- data.frame(a_check=c(1,2,1,1,2),
                 b_check=c(0,1,NA,1,15),
                 c_check=c(1,0,0,1,NA),
                 d_check=c(1,1,1,0,0),
                 e_check=c(1,NA,0,1,1))

驗證列表-

valid_values <- list(a_check= c(1,2,3), b_check= c(0,1),c_check=c(0,1,2),d_check="possitive integer",e_check="positive integer")
required_list <- list(a_check= 1, b_check= 1,c_check=0,d_check=1,e_check=0)
col_type_list <- list(a_check= "factor", b_check= "factor",c_check="continuous",d_check="continuous",e_check="continuous")

題-

我試圖通過使用以下多個ifelse條件來獲得低於所需的輸出 -

  1. 如果 required_list 中required_list variable並且dt包含該列的NA它應該給出error (變量不能是 NA 因為它是必需的)。
  2. 如果variablecol_type_listcontinuouscol_type_list它應該只包含dt else 中的正值(變量必須是正整數)
  3. 如果variablecol_type_list中的factorcol_type_list它應該匹配valid_value列表中的值,否則(變量必須是以下值之一)。

我能夠使用nested for loops獲得結果nested for loops但對於大數據集根本沒有效率。

我的代碼-

param_names <- colnames(dt)

error_msg <- list()
error <- list()

for(i in 1:nrow(dt)){

  for(j in 1:length(param_names))
  { 
    if(get(param_names[j],required_list) %in% 1 & is.na(as.numeric(unlist(dt[param_names[j]]))[i]) == TRUE)
    {

      error_msg[j] <- paste0(toupper(param_names[j]), " cannot be NA because it is required")

    }

    ## continuous variable check
    else if(get(param_names[j],col_type_list)=="continuous"){

      if (is.na(as.numeric(unlist(dt[param_names[j]]))[i]) | as.numeric(unlist(dt[param_names[j]]))[i] < 0) {
        error_msg[j] <- paste0(toupper(param_names[j]), " must be a positive integer")
      } else {

        error_msg[j] <- NA
      }


    } else {
      ## factor variable check

      if(!(as.numeric(unlist(dt[param_names[j]]))[i] %in% get(param_names[j],valid_values))){
        error_msg[j] <- paste0(toupper(param_names[j]), " must be one of the following values ", paste(get(param_names[j],valid_values), collapse = '-'))

      } else {

        error_msg[j] <- NA

      }
    }

  } ## end of inner for loop

  error[i] <- paste(unlist(error_msg),collapse = " & ")

}## end of inner f

final_error <- unlist(error)
setDT(dt)
dt[,error := final_error]
dt[,error := gsub("NA & | NA \\s+ &", "\\1", error)]
dt[,error := gsub("& \\s+ NA | & NA", "\\1", error)]

輸出-

> dt

    a_check b_check c_check d_check e_check                                                                                error
1:       1       0       1       1       1                                                                                   NA
2:       2       1       0       1      NA                                                   E_CHECK must be a positive integer
3:       1      NA       0       1       0                                                                 B_CHECK cannot be NA
4:       1       1       1       0       1                                                                                   NA
5:       2      15      NA       0       1 B_CHECK must be one of the following values 0-1 & C_CHECK must be a positive integer

注意- 我知道它可以通過@Jav 使用類似的解決方案來實現

dt[, error := lapply(param_names, function(x) {
  ((get(x, dt) %in% get(x, valid_values))) %>%
    ifelse(., " ", paste(x, "should have valid values like -", paste(get(x, valid_values), collapse = " ")))
}) %>% Reduce(paste, .)]

但是,我正在努力使用上述解決方案使用多個ifelse條件。 我正在尋找高效和干凈的解決方案來避免for loops 任何其他方法也可以。

您可以擺脫嵌套循環,但仍然需要編寫大量代碼。 在我看來,最簡潔的方法是編寫一個自定義函數來定義如何應用邏輯:

library(tidyverse)

check_col_validity <- function(col, name) {
  r_error <- rep(NA, length(col))

  # is required?
  if (required_list[name] == 1) {
    msg <- paste(toupper(name), "is required")
    r_error <- ifelse(is.na(col), msg, NA)
  }

  # is continuous?
  if (col_type_list[name] == "continuous") {
    msg <- paste(toupper(name), "must be positive")
    new_error <- ifelse(col < 0 | is.na(col), msg, NA)
    error <- ifelse(is.na(r_error), new_error, paste(r_error, new_error, sep = " & "))
  }

  # is in valid range?
  if (col_type_list[name] == "factor") {
    valid_range <- valid_values[[name]]
    msg <- paste(toupper(name), "must be one of", paste(valid_range, collapse = ", "))
    new_error <- ifelse(col %in% valid_range, NA, msg)
    error <- ifelse(is.na(r_error), new_error, r_error)
  }

  return(error)
}

這很像你的邏輯。 不同之處在於它如何應用於數據:

dt$error <- dt[, 1:5] %>%
  purrr::imap_dfc(check_col_validity) %>%
  t() %>%
  as_tibble() %>%
  purrr::map_chr(paste, collapse = " & ") %>%
  stringr::str_remove_all("NA & ") %>%
  stringr::str_remove_all(" & NA")

該函數使用purrr::imap應用於每一列。 結果被轉置並粘貼在一起,之后最后一步是刪除丑陋的 NA 字符串。 它提供了預期的結果,我希望代碼更清晰。

這個過程的主要部分是imap工作原理。 它是對列表的應用類型操作,但它將列表元素的名稱作為第二個參數傳遞給函數。 這意味着您可以編寫一個應用於數據框每一列的自定義函數,並將第二個參數添加到imap將向其傳遞列名稱的函數。 一旦您在函數中同時擁有列的數據和名稱,該函數就會變得更容易編寫。

自定義函數返回適用於該列的錯誤消息。 這意味着您將獲得與原始數據集具有相同維度的數據框。 然后,您可以轉置此數據框並將每列的結果粘貼在一起,以獲得每行 1 條消息。

暫無
暫無

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

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