【问题标题】:Nested ifelse() or case_when() for unknown number of queries in R嵌套 ifelse() 或 case_when() 用于 R 中未知数量的查询
【发布时间】:2022-01-27 04:02:53
【问题描述】:

我有一个数据框,我想根据数据框的给定行和列中的值对其进行分组

my_data <- data.frame(matrix(ncol = 3, nrow = 4))
colnames(my_data) <- c('Position', 'Group', 'Data')
                      
my_data[,1] <- c('A1','B1','C1','D1')
my_data[,3] <- c(1,2,3,4)

grps <- list(c('A1','B1'),
             
             c('C1','D1'))

grp.names = c("Control", "Exp1", "EMPTY")


my_data$Group <- case_when(
  my_data$Position %in% grps[[1]] ~ grp.names[1],
  my_data$Position %in% grps[[2]] ~ grp.names[2]
)

my_data$Group <- with(my_data, ifelse(Position %in% grps[[1]], grp.names[1],
                                    ifelse(Position %in% grps[[2]], grp.names[2], 
                                    grp.names[3])))

这些示例可以工作并生成一个带有适当标签的 Group 列,但是我需要灵活地将 grps 列表的长度从 1 调整到大约 25。

我看不到在 for 循环中迭代 case_withifelse 的方法,例如。

my_data$Group <- for (i in 1:length(grps)){
  case_when(
    my_data$Well %in% grps[[i]] ~ grp.names[i])
}

这个例子只是删除了 Group 列

处理变量grps 长度的最合适方法是什么?

【问题讨论】:

    标签: r dataframe for-loop if-statement


    【解决方案1】:

    我相信您的问题暗示 grps 变量是一个列表,并且该列表中的每个元素本身就是一个数组,其中包含属于该组的所有位置。

    具体来说,在下面的grps 变量中,如果位置是“A1”或“B1”,则它属于您的第一个条目是grp.names。同样,如果位置是“C1”或“D1”,则它属于您的第二个条目在grp.names

    > grps
    [[1]]
    [1] "A1" "B1"
    
    [[2]]
    [1] "C1" "D1"
    

    假设是这种情况,您可以执行以下操作:

    matching_group_df <- sapply(grps, function(x){ my_data$Position %in% x})
    selected_group <- apply(matching_group_df, 1, function(x){which(x == TRUE)})
    my_data$Group <- grp.names[selected_group]
    
      Position   Group Data
    1       A1 Control    1
    2       B1 Control    2
    3       C1    Exp1    3
    4       D1    Exp1    4
    

    其工作方式如下:

    1. matching_group_df 是一个 True/False 矩阵(通过 sapply 函数创建),指定该位置所属的组索引:
    > matching_group_df
          [,1]  [,2]
    [1,]  TRUE FALSE
    [2,]  TRUE FALSE
    [3,] FALSE  TRUE
    [4,] FALSE  TRUE
    
    1. 然后使用应用命令逐行选择具有TRUE 值的列:
    selected_group <- apply(matching_group_df, 1, function(x){which(x == TRUE)})
    
    > selected_group
    [1] 1 1 2 2
    
    1. 最后,您将这些索引传递给您的 grp.names 列表以选择合适的索引并将它们设置到您的原始数据框中。
    grp.names[selected_group]
    [1] "Control" "Control" "Exp1"    "Exp1"   
    

    如果这对你很重要的话,这还有一个小好处,那就是只使用基本 R 函数。

    【讨论】:

    • 这与添加此行 selected_group &lt;- as.numeric(selected_group) 来处理 NULL 值完美配合
    【解决方案2】:

    方法一:哈希表

    我会在这里选择不同的方法,因为组构成可能会在分析过程中发生变化,特别是键值对的查找表,并编写一个小的访问器函数。

    library(tidyverse)
    
    # First, a small adjustment to `grps` to reflect an empty group.
    grps <- list(c('A1','B1'),
                 c('C1','D1'),
                 NULL)
    names <- unlist(grps, use.names = F)
    values <- rep(grp.names, map_dbl(grps, length))
    
    h = as.list(values) %>%
      set_names(names) %>%
      list2env()
    
    # find x in h
    f <- Vectorize(function(x) h[[x]], c("x")) # scoping here
    

    这需要一些时间来设置,但使用起来很方便:

    my_data %>%
      mutate(Groups = f(Position))
    
      Position   Group Data
    1       A1 Control    1
    2       B1 Control    2
    3       C1    Exp1    3
    4       D1    Exp1    4
    

    这避免了在多个地方更改代码,并且可以采用任意长度的组。

    方法二:动态切换

    或者,我们可以创建任意长度的switch 表达式,根据组名及其唯一值构建它。

    constructor <- function(ids, names){
      purrr::imap_chr(as.character(ids), ~paste(paste0("\"", .x ,"\""),
                                                paste0("\"", names[.y], "\""),
                                                sep = "=")) %>%
        paste0(collapse = ", ") %>%
        paste0("Vectorize(function(x) switch(as.character(x), ", ., ", NA))", collapse = "") %>%
        str2expression()
    }
    
    my_data %>%
      mutate(Group = eval(constructor(names, values)))
    

    在这种情况下,它会计算表达式

    expression(Vectorize(function(x) switch(as.character(x), A1 = "Control", 
        B1 = "Control", C1 = "Exp1", D1 = "Exp1", 
        NA)))
    

    【讨论】:

      【解决方案3】:

      对于 my_data$Position 中的每个项目,您希望遍历每个 grps 并查找匹配项并分配 grp.names,如果是的话。如果在任何 grp 中都找不到匹配项,请分配 grp.names[3]:

      my_data$Group <- lapply(my_data$Position, function(position){ # Goes through each my_data$Position
        for(i in 1:length(grps)){
          if(position %in% grps[[i]]){
            return(grp.names[i]) # Give matching index of grp.names to grps
          } else if (i == length(grps)){ # if no matches assign grp.names[3]
            return(grp.names[3])
          }
        }
      }) %>% unlist() # Put the list into a vector
      

      【讨论】:

        猜你喜欢
        • 2022-12-07
        • 1970-01-01
        • 2021-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-28
        • 1970-01-01
        相关资源
        最近更新 更多