【问题标题】:How to populate Yes and No when applying conditions accros 3 columns only with dplyr?仅使用 dplyr 跨 3 列应用条件时如何填充是和否?
【发布时间】:2021-09-28 05:20:45
【问题描述】:

我正在尝试创建一个新列,例如测试,其中包含基于 3 列的多个条件。我正在尝试仅通过 tidyverse 来实现这一目标。这是我的条件:

  • 如果我在一列中有 Yes,而不管其他类别 (No/Unknown/NA) 跨列的同一 ID:previous_cabg, previous_pci, previous_ami 然后在测试变量中分配 Yes
  • 如果我在所有列中都有相同 ID 的 No,则为测试变量分配 NO
  • 如果我有一列有NO,另一列有NA/Unknown,则在测试变量中分配No
  • 如果我在同一 id 的所有列中都有 Yes,则在测试变量中分配 Yes
  • 如果我在测试变量中有Yes in one column and NA/Unknownfor the same id in each column then assignYes`

这是我拥有的数据集类型:

structure(list(id = c(112139L, 43919L, 92430L, 87137L, 95417L, 
66955L, 16293L, 61396L, 25379L, 79229L, 27107L, 63243L, 50627L, 
17968L, 83015L, 96549L, 7332L, 4873L, 98131L, 93506L, 52894L, 
59327L, 85003L, 96623L, 82999L, 65769L, 67063L, 21744L, 62961L, 
2229L, 103673L, 9367L, 60215L, 74044L, 58422L, 57530L, 100399L, 
46483L, 108690L, 62017L, 46467L, 79562L, 4800L, 119158L, 103222L, 
32908L, 14491L, 30293L, 52558L, 122304L, 42281L, 1553L, 111771L, 
23087L, 30147L, 37842L, 51552L, 20148L, 28L, 7477L), previous_cabg = structure(c(1L, 
1L, 1L, NA, 1L, NA, NA, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, NA, 1L, 1L, NA, 1L, NA, 1L, 1L, 1L, 1L, 1L, NA, 1L, 1L, 3L, 
1L, 1L, NA, 1L, 1L, 1L, 1L, 3L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, NA, 1L, 1L, 1L, 1L, 1L), .Label = c("No", 
"Unknown", "Yes"), class = "factor"), previous_pci = structure(c(1L, 
1L, 2L, NA, 1L, NA, NA, 2L, 2L, 1L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 
2L, NA, 2L, 1L, NA, 2L, NA, 1L, 2L, 1L, 1L, 1L, NA, 2L, 1L, 1L, 
2L, 2L, NA, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, NA, 1L, 1L, 2L, 1L, 1L), .Label = c("No", 
"Yes", "Unknown"), class = "factor"), previous_ami = structure(c(2L, 
2L, 1L, 2L, 2L, NA, 2L, 1L, 2L, 2L, NA, 1L, 2L, 2L, 2L, 2L, 2L, 
1L, NA, 1L, 2L, NA, 1L, NA, 2L, 1L, 2L, 2L, 2L, NA, 1L, 1L, 1L, 
2L, 1L, NA, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 3L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, NA, 2L, 2L, 2L, 1L, 2L), .Label = c("Yes", 
"No", "Unknown"), class = "factor")), row.names = c(NA, -60L), problems = structure(list(
    row = c(34136L, 121773L, 121779L), col = c("1.01 Hospital identifier", 
    "1.01 Hospital identifier", "1.01 Hospital identifier"), 
    expected = c("value in level set", "value in level set", 
    "value in level set"), actual = c("CMH", "CMH", "CMH"), file = c("'../../data/changed/minap_2020_2021_second.csv'", 
    "'../../data/changed/minap_2020_2021_second.csv'", "'../../data/changed/minap_2020_2021_second.csv'"
    )), row.names = c(NA, -3L), class = c("tbl_df", "tbl", "data.frame"
)), class = c("tbl_df", "tbl", "data.frame"))

这就是它的外观,但只有前 10 行,如果你仔细看,我在 3 列中有不同的匹配组

# A tibble: 60 x 4
       id previous_cabg previous_pci previous_ami
    <int> <fct>         <fct>        <fct>       
 1 112139 No            No           No          
 2  43919 No            No           No          
 3  92430 No            Yes          Yes         
 4  87137 NA            NA           No          
 5  95417 No            No           No          
 6  66955 NA            NA           NA          
 7  16293 NA            NA           No          
 8  61396 No            Yes          Yes         
 9  25379 No            Yes          No          
10  79229 No            No           No        

我希望仅使用 tidyverse 或 tidyverse 和 r base 的组合来解决这个问题。

这是我尝试过的,但我觉得它不是那么明智。我认为这是不明智的,因为此代码将成为自动化过程的一部分,如果我将获得其他类别,而不是 Yes and No,例如 Unknown,因为它稍后出现在下一个数据集提取中,那么我希望代码能够避免我上面给出的条件中的所有其他情况。

dplyr::mutate(first_attack = 
                  dplyr::case_when(previous_cabg == 'No'  | previous_pci == 'No'  | previous_ami == 'Yes' ~ 'Yes',
                                   previous_cabg == 'No'  | previous_pci == 'Yes' | previous_ami == 'Yes' ~ 'Yes',
                                   previous_cabg == 'Yes' | previous_pci == 'No'  | previous_ami == 'Yes' ~ 'Yes', 
                                   previous_cabg == 'Yes' | previous_pci == 'Yes' | previous_ami == 'No' ~  'Yes', 
                                   previous_cabg == 'No'  | previous_pci == 'No'  | previous_ami == 'Yes' ~ 'Yes',
                                   previous_cabg == 'No'  | previous_pci == 'Yes' | previous_ami == 'Yes' ~ 'Yes',
                                   previous_cabg == 'Yes' | previous_pci == 'No'  | previous_ami == 'Yes' ~ 'Yes', 
                                   previous_cabg == 'Yes' | previous_pci == 'Yes' | previous_ami == 'No' ~  'Yes'
                                   
                                   # deal with the unknown category
                                   previous_cabg == 'Unknown'  | previous_pci == 'Yes' | previous_ami == 'Yes' ~ 'Yes',
                                   previous_cabg == 'Yes' | previous_pci == 'Unknown'  | previous_ami == 'Yes' ~ 'Yes', 
                                   previous_cabg == 'Yes' | previous_pci == 'Yes' | previous_ami == 'No' ~  'Yes', 
                                   previous_cabg == 'Unknown'  | previous_pci == 'Unknown'  | previous_ami == 'Yes' ~ 'Yes',
                                   previous_cabg == 'Unknown'  | previous_pci == 'Yes' | previous_ami == 'Yes' ~ 'Yes',
                                   previous_cabg == 'Yes' | previous_pci == 'Unknown' | previous_ami == 'Yes' ~ 'Yes', 
                                   previous_cabg == 'Yes' | previous_pci == 'Yes' | previous_ami == 'Unknown' ~  'Yes', 
                                   
                                   
                                   previous_cabg == 'Yes' |  previous_pci == 'No'  | previous_ami == 'Yes' ~  'Yes', 
                                   previous_cabg == 'Yes' |  previous_pci == 'No'  | previous_ami == 'No'  ~  'Yes',
                                   previous_cabg == 'No'  |  previous_pci == 'No'  | previous_ami == 'Yes' ~  'Yes',
                                   previous_cabg == 'No'  | previous_pci == 'Yes'  | previous_ami == 'No'  ~ 'Yes', 
                                   
                                   
                                   previous_cabg == 'Yes' |  previous_pci == 'Unknown'   | previous_ami == 'Yes' ~  'Yes', 
                                   previous_cabg == 'Yes' |  previous_pci == 'Unknown'   | previous_ami == 'Unknown'   ~  'Yes',
                                   previous_cabg == 'Unknown'   |  previous_pci == 'Unknown'   | previous_ami == 'Yes' ~  'Yes',
                                   previous_cabg == 'Unknown'   | previous_pci == 'Yes'  | previous_ami == 'Unknown'   ~ 'Yes', 
                                   
                                   
                                   previous_cabg == 'Yes' | previous_pci == 'Unknown' | previous_ami == 'Unknown' ~ 'Yes', 
                                   previous_cabg == 'Unknown'  | previous_pci == 'Yes'| previous_ami == 'Unknown' ~ 'Yes', 
                                   previous_cabg == 'Yes' | previous_pci == 'No' | previous_ami == 'Yes' ~ 'Yes', 
                                   previous_cabg == 'Unknown'  | previous_pci == 'Yes'| previous_ami == 'Yes' ~ 'Yes', 
                                   
                                   previous_cabg == 'Yes' | previous_pci == 'No' | previous_ami == 'No' ~ 'Yes', 
                                   previous_cabg == 'No'  | previous_pci == 'Yes'| previous_ami == 'No' ~ 'Yes', 
                                   previous_cabg == 'Yes' | previous_pci == 'No' | previous_ami == 'Yes' ~ 'Yes', 
                                   previous_cabg == 'No'  | previous_pci == 'Yes'| previous_ami == 'Yes' ~ 'Yes', 
                                   
                                   previous_cabg == 'Yes' | previous_pci == 'Unknown' | previous_ami == 'Unknown' ~ 'Yes', 
                                   previous_cabg == 'Unknown'  | previous_pci == 'Yes'| previous_ami == 'Unknown' ~ 'Yes', 
                                   previous_cabg == 'Yes' | previous_pci == 'Unknown' | previous_ami == 'Yes' ~ 'Yes', 
                                   previous_cabg == 'Unknown' | previous_pci == 'Yes'| previous_ami == 'Yes' ~ 'Yes', 
                                   
                                   
                                   previous_cabg == 'No'  | previous_pci == 'No'  |  previous_ami == 'No' ~ 'No', 
                                   previous_cabg == 'Yes' | previous_pci == 'Yes' |  previous_ami == 'Yes' ~'Yes'
                                   
                  ))

【问题讨论】:

  • 如您所见,对所有这些逻辑进行编码很尴尬。将三个变量的所有组合映射到 first_attack 的真值表怎么样?然后你可以离开加入主数据集和真值表。 first_attack 的 NA 值表示您可以手动查看的三个值的新组合。
  • 你告诉我的对我来说是陌生的。如果你能证明它是一个帖子,它会更有帮助吗?
  • 你列出的三个条件相当混乱。你说“如果我在所有行中都有 Yes / 或 Yes 并且每列中同一行的 NA = Yes”是什么意思。在所有行中都是是什么意思?最后的“= Yes”是否意味着“Test”变量应该被分配“Yes”?
  • 元音 - 帖子更好吗?
  • 是的,它好多了!我发布了一个解决方案。我认为对于您未来的提取,您仍然想简化您的条件标准。

标签: r tidyverse multiple-conditions


【解决方案1】:

这些操作是rowwise(),所以它们不是很有效,但是tidyverse 中的这个解决方案应该可以完全实现你想要的。

让我们将您的示例数据集命名为 dataset。然后是下面的工作流程

library(tidyverse)


# ...
# Code to generate your 'dataset'.
# ...


# Define custom logic across a single row.
get_first_attack <- function(values_across_row) {
  # "Yes" overrides all other values.
  if(isTRUE(any(values_across_row == "Yes"))){
    return("Yes")
  }
  # "No" overrides all missing values: 'NA' and "Unknown".
  else if(isTRUE(any(values_across_row == "No"))) {
    return("No")
  }
  # "Unknown" overrides all other missing values: 'NA'.
  else if(isTRUE(any(values_across_row == "Unknown"))) {
    return("Unknown")
  }
  # All values are missing: 'NA'.
  else {
    return(as.character(NA))
  }
}


dataset %>%
  # Examine row by row.
  dplyr::rowwise() %>%
  # Compare values across each row according to the logic in 'get_first_attack()'.
  dplyr::mutate(first_attack = get_first_attack(across(previous_cabg:previous_ami))) %>%
  # Exit row-wise approach, to restore efficiency.
  dplyr::ungroup() %>%
  # Factor 'first_attack' exactly like its neighboring column.
  dplyr::mutate(first_attack = factor(first_attack, levels = levels(previous_ami)))

应该给你这些结果

# A tibble: 60 x 5
       id previous_cabg previous_pci previous_ami first_attack
    <int> <fct>         <fct>        <fct>        <fct>       
 1 112139 No            No           No           No          
 2  43919 No            No           No           No          
 3  92430 No            Yes          Yes          Yes         
 4  87137 NA            NA           No           No          
 5  95417 No            No           No           No          
 6  66955 NA            NA           NA           NA          
 7  16293 NA            NA           No           No          
 8  61396 No            Yes          Yes          Yes         
 9  25379 No            Yes          No           Yes         
10  79229 No            No           No           No          
# ... with 50 more rows

first_attack 列恰当地定义为具有三个级别的 factor"Yes""No""Unknown"

【讨论】:

  • 这个解决方案非常优雅。如果我将所有变量都放在一个列表中,我什至可以使用 purrr::map 传递 get_first_attack 函数,只保留 id 作为列。这是哇!!非常感谢!
  • 很高兴为您提供帮助,@GaB!
  • 你也是新人。我敢打赌,如果您提供帮助,您将在 stackoverflow 中名列前茅!谢谢!
  • 你太客气了,@GaB!为了记录在案,你明智地想到dplyr::case_when()。在您必须将逻辑条件应用于数据集中的多个列的情况下,它几乎总是正确(最有效)的调用。这种情况在信息的结构上稍微复杂一点……所以我采取了额外的步骤来简化get_first_attack() 的逻辑。祝你的项目好运!
【解决方案2】:

所以总的来说,你的情况是:

  • 对于每一行,如果任何一列为“是”,则输出“是”
  • 对于每一行,如果所有列都是NA,则输出NA
  • 对于每一行,如果所有列都是“未知”,则输出“未知”
  • 否则输出“否”

如果是这种情况,你可以这样做:

# Convert your data structure into a data.frame
dat <- as.data.frame(dat)

# Remove id col
id <- dat$id
dat <- subset(dat, select = -c(id))

# For each row, check if there is a 'Yes' under any column. If so, return 'Yes'; otherwise return 'No'
output <- apply(dat, 1, function(x) ifelse('Yes' %in% x, 'Yes', 'No'))

# For each row, check if NA under all column. If so, return TRUE; otherwise return FALSE.
isNA <- apply(dat, 1, function(x) ifelse(all(is.na(x)), TRUE, FALSE))

# Now merge output and isNA
output[isNA] <- NA

# For each row, check if 'Unknown' under all column. If so, return TRUE; otherwise return FALSE.
isUK <- apply(dat, 1, function(x) ifelse(all('Unknown' == x), TRUE, FALSE))

# Now merge output and isUK
output[isUK] <- 'Unknown'

# Append the output character vector to a new col of the data frame
dat$id <- id
dat$test <- output

【讨论】:

  • 谢谢。然而,当我应用它时,当我有 NA 时,我得到 No
  • 哎呀哈哈。那么,当您拥有 NA 时,您会期待什么?您是指所有 3 列下的 NA 吗?
  • 是的,我确实希望所有 3 列都为 NA。另外,为了让您知道,在我的数据集中我还有其他变量,我希望解决方案能够考虑到所有其他变量。如果有 tidyverse 的解决方案,那就更好了。非常感谢您的努力。此外,我也将未知作为一个类别。
  • 是的,但是你看,如果它在一列中有 NA,在另一列中有 Yes,在第三列中有 Unknown,那么我希望它是 Yes。在我上面的代码中,我确实用 case_when 甚至第一点指定了这一点。 Dang man :) 虽然 case_when 不是在这种情况下使用的正确函数。
  • 再次,给出的解决方案不起作用。我有一个案例,我有 Yes, Unknown, NA,并且您的解决方案的输出是 Unknown。然而,根据我的帖子应该是肯定的。帕姆,帕姆
猜你喜欢
  • 2018-04-13
  • 2021-09-20
  • 2022-07-23
  • 2021-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-03
  • 2017-02-14
相关资源
最近更新 更多