【问题标题】:Mutating based on multiple columns in a data frame基于数据框中的多个列进行变异
【发布时间】:2018-02-28 01:36:57
【问题描述】:

好的,所以我的数据框看起来像这样让我们调用 if df

KEY   A1  A2  A3 A4  B1 B2 B3 B4 C1  C2  C3  C4
1     120 100 NA 110 1  1  NA  1  NA NA NA NA
2     100 NA 115 NA  NA NA NA NA  Y  N   Y N

所以我想要做的是,当 A 列的值为 100 并且相应的 B 或 C 列的值分别为 1 或“Y”时,会生成一个带有 X 的新列值为 1。在第 1 行中为 A2 和 B2,在行中为 A1 和 C1。

我尝试做三组收集,然后使用 case_when 使用 mutate 函数。像这样

df<- df %>%
  gather(key="A",value="code",dx)%>%
  gather(key="B",value="number",dxadm)%>%
  gather(key="C",value="character",dxpoa) %>%
  mutate(X=case_when(
    code == 100 & present >0 ~ 1,
    code ==100 & character == "Y"~1)
  )

除了这些行的扩展函数返回所有行和我的 X 不合适的位置。

或者,我考虑过类似的东西

df <- df %>%
mutate(X=case_when(
    A1 == 100 & B1 >0 ~ 1,
    A1 ==100 & C1 == "Y"~1,
    A2 == 100 & B2 >0 ~ 1,
    A2 ==100 & C2 == "Y"~1,) 

以此类推,适用于所有排列。这样做的两个问题是我有很多列,我想为多个不同的 A 值这样做。

谁能推荐一种替代方案或至少一种方法,将第二种解决方案变成只需要一段烦人的长代码,我可以将其变成更通用的函数吗?谢谢!

【问题讨论】:

  • 另外,顺便说一句,我曾经被告知(实际上只是最近)不建议在数据框中使用诸如“df”之类的函数名称

标签: r dataframe dplyr tidyr


【解决方案1】:

一个建议

require(read.so) #awesome package to read from Stackoverflow, 
# available on GitHub [https://alistaire47.github.io/read.so/][1]
require(tidyr)
require(reshape2)
require(dplyr)

dat <- read.so()    

dat %>% gather(var, value, 2:13) %>% #make it long
  mutate(var = gsub('([A-Z])', '\\1_', .[['var']])) %>% #add underscore
  separate(var, c('var', 'number') ) %>% #separate your column 
  dcast(KEY+number ~ var) %>% #dcast is a bit complex but quite powerful
  group_by(KEY) %>% 
  filter(A == 100) 

# A tibble: 2 x 5
# Groups:   KEY [2]
    KEY number A     B     C    
  <int> <chr>  <chr> <chr> <chr>
1     1 2      100   1     <NA> 
2     2 1      100   <NA>  Y  

【讨论】:

    【解决方案2】:

    使用 和 的解决方案。我们可以gather 除了KEY、separate 字母和数字之外的所有列,然后spread 字母,这样我们就可以在不指定数字的情况下创建X 列。请注意,我假设如果条件不满足,X 将是0,并且根据您的描述,我使用any(A %in% 100 &amp; (B %in% 1 | C %in% "Y")) 来测试条件,因为任何给定的数字都满足条件,X 将是@987654332 @。

    library(dplyr)
    library(tidyr)
    
    df2 <- df %>%
      gather(Column, Value, -KEY) %>%
      separate(Column, into = c("Letter", "Number"), sep = 1) %>%
      spread(Letter, Value, convert = TRUE) %>%
      group_by(KEY) %>%
      mutate(X = ifelse(any(A %in% 100 & (B %in% 1 | C %in% "Y")), 1L, 0L)) 
    
    df2 %>% as.data.frame()
    #   KEY Number   A  B    C X
    # 1   1      1 120  1 <NA> 1
    # 2   1      2 100  1 <NA> 1
    # 3   1      3  NA NA <NA> 1
    # 4   1      4 110  1 <NA> 1
    # 5   2      1 100 NA    Y 1
    # 6   2      2  NA NA    N 1
    # 7   2      3 115 NA    Y 1
    # 8   2      4  NA NA    N 1
    

    我觉得df2的结构不错,但是如果你真的想要原来的结构,我们可以做如下。

    df3 <- df2 %>%
      gather(Letter, Value, A:C) %>%
      unite(Column,  Letter, Number, sep = "") %>%
      spread(Column, Value) %>%
      select(names(df), X)
    df3 %>% as.data.frame()
    #   KEY  A1   A2   A3   A4   B1   B2   B3   B4   C1   C2   C3   C4 X
    # 1   1 120  100 <NA>  110    1    1 <NA>    1 <NA> <NA> <NA> <NA> 1
    # 2   2 100 <NA>  115 <NA> <NA> <NA> <NA> <NA>    Y    N    Y    N 1
    

    df3 是最终输出。

    数据

    df <- read.table(text = "KEY   A1  A2  A3 A4  B1 B2 B3 B4 C1  C2  C3  C4
    1     120 100 NA 110 1  1  NA  1  NA NA NA NA
                     2     100 NA 115 NA  NA NA NA NA  Y  N   Y N",
                     header = TRUE, stringsAsFactors = FALSE)
    

    【讨论】:

    • 我想我明白了,但是为什么您的 df2 将所有 X 都设为 1?大多数条件应该返回零。抱歉,如果我遗漏了一些明显的东西。 group_by 还有什么目的?我假设在 df3 的形成中 select 只是用于重新排序列?谢谢!
    • 我只是按照你的逻辑。你的第二个代码块的输出看起来和我的 df3 一样,所以我认为规则适用于每个键,如果任何一对 A、B 或 C 满足条件,X 将为 1。如果你想评估每一对,然后只需删除 group_by 和任何,但创建 df3 没有意义。
    • 虽然想法相同,但我同意@www 的代码比我的要短,这要归功于separate 和spread 的巧妙使用。学习……也:)
    • @Tjebo 谢谢。但我有一个问题。为什么您和 Nettle 的回答没有生成 OP 想要的列 X?您的最终输出看起来不像 OP 想要的输出。
    • 我同意您的两个答案都非常有帮助,并且都帮助我学到了很多东西!你说得对,我确实想要你用单个 X 列所做的。我也同意,通常最好将其保留为 df2,但其他人编写的另一段代码要求数据看起来像 df3。非常感谢你们的帮助!
    【解决方案3】:

    与 Tjebo 相同的想法,但坚持 tidyverse....

    library(tidyverse)
    
    dat <- data.frame(stringsAsFactors=FALSE,
             KEY = c(1L, 2L),
              A1 = c(120L, 100L),
              A2 = c(100L, NA),
              A3 = c(NA, 115L),
              A4 = c(110L, NA),
              B1 = c(1L, NA),
              B2 = c(1L, NA),
              B3 = c(NA, NA),
              B4 = c(1L, NA),
              C1 = c(NA, "Y"),
              C2 = c(NA, "N"),
              C3 = c(NA, "Y"),
              C4 = c(NA, "N"))
    
    dat %>% 
      gather(var, value, -KEY) %>% #make it long
      extract(var, regex = "(.)(.)", into = c("var", "number") ) %>% 
      spread(var, value) %>% 
      filter( A %in% 100 )
    
    #>   KEY number   A    B    C
    #> 1   1      2 100    1 <NA>
    #> 2   2      1 100 <NA>    Y
    

    由reprex package (v0.2.0) 于 2018 年 2 月 27 日创建。

    【讨论】:

    • 感谢您的参考 :) 当然,您分隔列的方式比我的更优雅,并且很好地使用了传播。
    猜你喜欢
    • 2023-01-07
    • 2013-02-10
    • 2018-12-03
    • 1970-01-01
    • 2019-04-21
    • 2020-08-13
    • 1970-01-01
    • 1970-01-01
    • 2022-07-18
    相关资源
    最近更新 更多