【问题标题】:R: convert to factor with order of levels same with case_whenR:转换为与 case_when 相同级别顺序的因子
【发布时间】:2018-09-09 09:02:09
【问题描述】:

在进行数据分析时,有时我需要将值重新编码为因子以便进行组分析。我想保持因子的顺序与case_when 中指定的转换顺序相同。在这种情况下,订单应该是"Excellent" "Good" "Fail"。我怎样才能做到这一点而不像levels=c('Excellent', 'Good', 'Fail') 那样繁琐地再次提及它?

非常感谢。


library(dplyr, warn.conflicts = FALSE)             
                                                   
set.seed(1234)                                     
score <- runif(100, min = 0, max = 100)     
   
Performance <- function(x) {                       
  case_when(                                         
    is.na(x) ~ NA_character_,                          
    x > 80   ~ 'Excellent',                            
    x > 50   ~ 'Good',                                 
    TRUE     ~ 'Fail'                                  
  ) %>% factor(levels=c('Excellent', 'Good', 'Fail'))
}                                                  
                                                   
performance <- Performance(score)                  
levels(performance)                                
#> [1] "Excellent" "Good"      "Fail"
table(performance)                                 
#> performance
#> Excellent      Good      Fail 
#>        15        30        55

【问题讨论】:

  • 那是他不想做的事(而且已经在做)
  • 这是一个不错的解决方案!
  • 漂亮,谢谢!
  • 要允许在 RHS 上使用表达式,请在倒数第二行插入 levels = sapply(levels, FUN = eval)。这样就可以执行result = fct_case_when(x &lt; 5 ~ my_vec[3]) 而不会将“my_vec[3]”设为result
  • 请不要在问题中编辑解决方案公告。接受(即单击旁边的“勾号”)现有答案之一,如果有的话。如果现有答案尚未涵盖您的解决方案,您还可以创建自己的答案,甚至接受它。比较stackoverflow.com/help/self-answer

标签: r dplyr data-analysis tidyverse forcats


【解决方案1】:

默认情况下按字典顺序设置级别。如果您不想指定它们,可以设置它们以使字典顺序正确(Performance1),或者创建一次levels 向量,并在生成因子和设置级别时使用它(@ 987654323@)。我不知道这些中的任何一个都会为您节省多少努力或乏味,但它们就在这里。看看我的第三条建议,我认为这是最不乏味的方式。

Performance1 <- function(x) {                       
  case_when(
    is.na(x) ~ NA_character_,                          
    x > 80 ~ 'Excellent',  
    x <= 50 ~ 'Fail',
    TRUE ~ 'Good',
  ) %>% factor()
}

Performance2 <- function(x, levels = c("Excellent", "Good", "Fail")){
  case_when(
    is.na(x) ~ NA_character_,
    x > 80 ~ levels[1],
    x > 50 ~ levels[2],
    TRUE ~ levels[3]
  ) %>% factor(levels)
}
performance1 <- Performance1(score)
levels(performance1)
# [1] "Excellent" "Fail"     "Good"
table(performance1)
# performance1
# Excellent      Fail      Good 
#        15        55        30 

performance2 <- Performance2(score)
levels(performance2)
# [1] "Excellent" "Good"      "Fail"  
table(performance2)
# performance2
# Excellent      Good      Fail 
#        15        30        55 

如果我能提出一种更不乏味的方法:

performance <- cut(score, breaks = c(0, 50, 80, 100), 
                   labels = c("Fail", "Good", "Excellent"))
levels(performance)
# [1] "Fail"      "Good"      "Excellent"
table(performance)
# performance
#      Fail      Good Excellent 
#        55        30        15

【讨论】:

  • 我认为Performace2 接近我所需要的。 dplyrforcats 中是否有任何功能可以一步完成?也就是说,没有先保存关卡。此外,cut 函数可以方便地将数值转换为因子,尽管在这种情况下它颠倒了顺序(可以使用forcats::fct_rev 轻松纠正)。谢谢。
  • 我觉得Performance2的缺点是我们不能立即看到对应的转换。例如,当看到x &gt; 80 ~ levels[1]时,我们必须寻找levels向量,看看它的第一个元素是什么,以便找出x &gt; 80对应Excellent。所以它对编程很方便,但在我看来会降低可读性。如果有人能提供一个编程友好和可读的解决方案,那就太好了。
【解决方案2】:

虽然我的解决方案用一个杂乱的中间变量替换了你的管道,但这很有效:

    library(dplyr, warn.conflicts = FALSE)             

set.seed(1234)                                     
score <- runif(100, min = 0, max = 100)     

Performance <- function(x) {                       
  t <- case_when(                                         
    is.na(x) ~ NA_character_,                          
    x > 80   ~ 'Excellent',                            
    x > 50   ~ 'Good',                                 
    TRUE     ~ 'Fail'                                  
  ) 
  to <- subset(t, !duplicated(t))
  factor(t, levels=(to[order(subset(x, !duplicated(t)), decreasing=T)] ))
}                                                  
performance <- Performance(score)                
levels(performance)  

已编辑修复!

【讨论】:

  • 这不起作用。它会产生错误factor level [2] is duplicated
  • 这行得通。但看起来很复杂,并没有节省多少打字。无论如何,谢谢!
  • 我发现这通常不起作用。例如,当分数为rbinom(10, size = 9, prob = .5) 且条件更改为x %% 2 == 1 ~ 'Odd', x %% 2 == 0 ~ 'Even' 时,级别的顺序有时为Odd Even,但有时为Even Odd,与case_when 中指定的顺序并不总是相同。您正在使用order,所以我猜这种方法仅在值具有合理顺序时才有效。
  • 嗯。我认为解决此问题的更好方法可能是创建一个包含两个向量的列表,一个具有有序阈值,另一个具有描述条件的因素,然后将此列表作为函数的参数提供。如果这是您所追求的,这将允许您使函数完全通用。
【解决方案3】:

我的解决方案

最后,我想出了一个解决方案。对于那些感兴趣的人,这是我的解决方案。我写了一个函数fct_case_when(假装是forcats中的一个函数)。它只是带有因子输出的case_when 的包装器。级别的顺序与参数顺序相同。


fct_case_when <- function(...) {
  args <- as.list(match.call())
  levels <- sapply(args[-1], function(f) f[[3]])  # extract RHS of formula
  levels <- levels[!is.na(levels)]
  factor(dplyr::case_when(...), levels=levels)
}

现在,我可以使用fct_case_when 代替case_when,结果将与之前的实现相同(但不那么繁琐)。


Performance <- function(x) {                       
  fct_case_when(                                         
    is.na(x) ~ NA_character_,                          
    x > 80   ~ 'Excellent',                            
    x > 50   ~ 'Good',                                 
    TRUE     ~ 'Fail'                                  
  )
}      
performance <- Performance(score)                  
levels(performance)                       
#> [1] "Excellent" "Good"      "Fail"
table(performance)                
#> performance
#> Excellent      Good      Fail 
#>        15        30        55

【讨论】:

    【解决方案4】:

    这是我一直在使用的实现:

    library(dplyr)
    library(purrr)
    library(rlang)
    library(forcats)
    
    factored_case_when <- function(...) {
      args <- list2(...)
      rhs <- map(args, f_rhs)
      
      cases <- case_when(
        !!!args
      )
      
      exec(fct_relevel, cases, !!!rhs)
    }
    
    
    numbers <- c(2, 7, 4, 3, 8, 9, 3, 5, 2, 7, 5, 4, 1, 9, 8)
    
    factored_case_when(
      numbers <= 2 ~ "Very small",
      numbers <= 3 ~ "Small",
      numbers <= 6 ~ "Medium",
      numbers <= 8 ~ "Large",
      TRUE    ~ "Huge!"
    )
    #>  [1] Very small Large      Medium     Small      Large      Huge!     
    #>  [7] Small      Medium     Very small Large      Medium     Medium    
    #> [13] Very small Huge!      Large     
    #> Levels: Very small Small Medium Large Huge!
    

    这样做的好处是不必手动指定因子水平。

    我还为此功能向 dplyr 提交了功能请求:https://github.com/tidyverse/dplyr/issues/6029

    【讨论】:

      猜你喜欢
      • 2016-12-01
      • 1970-01-01
      • 2017-05-14
      • 2014-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-29
      相关资源
      最近更新 更多