在使用`dplyr::mutate()`函数的上下文中,在`dplyr::case_when()`中使用复杂的右手边表达式

4

问题

我正在尝试使用dplyr :: mutate()dplyr :: case_when()在数据框中创建一个新的数据列,该列使用存储在另一个对象(“查找列表”)中的数据进行填充,并基于数据框中的列中的信息。

我知道答案可能与引用和非标准评估的正确和不正确使用有关,但我很难将使用 dplyr 编程手册中的信息推广到我的情况。

我希望在此发布 reprex 可以指导我找到正确的答案,并且我认为解决此问题将在很大程度上帮助我理解非标准评估。

示例数据

key_list <- list(
  "a" = list(
    foo = 1,
    bar = 2),
  "b" = list(
    foo = 3,
    bar = 4),
  "c" = list(
    foo = 5,
    bar = 6)
  )

x <- tibble(fruit = c("apple", "orange", "grape", "apple", "apple", "orange"),
            `Old Letter` = c("a", "a", "b", "c", "c", "c"),
            `Old Number` = c(9, 8, 7, 6, 5, 4)
            )

x

# # A tibble: 6 x 3
#   fruit  `Old Letter` `Old Number`
#   <chr>  <chr>               <dbl>
# 1 apple  a                       9
# 2 orange a                       8
# 3 grape  b                       7
# 4 apple  c                       6
# 5 apple  c                       5
# 6 orange c                       4

目标

具体来说,我想在 x 中创建一个新列(我将其称为 `New Number`),该列根据 x$fruitx$`Old Letter` 中的值进行填充。

以下是模拟我的实际用例中遇到困难的代码:

x %>% mutate(`New Number` = case_when(
  fruit == "apple" ~ pluck(key_list, `Old Letter`, "foo") * 10,
  fruit == "orange" ~ pluck(key_list, `Old Letter`, "foo") * 100,
  fruit == "grape" ~ pluck(key_list, `Old Letter`, "foo") * 1000
  ))

# Error: Index 1 must have length 1, not 6

预期输出

在我看来,例如第一行的x,期望的运算顺序如下:

  • fruit == "apple"为TRUE,因此计算表达式:pluck(key_list, `Old Letter`, "foo") * 10
  • 由于该行的`Old Letter`列中的值为"a",因此表达式变为pluck(key_list, "a", "foo") * 10 (应该对全局环境中的key_list对象进行操作)
  • 这简化为2 * 10,等于20
  • 将评估此表达式的结果放入`New Number`列。

将此推广到整个命令,我期望以下是输出结果:

# # A tibble: 6 x 4
#   fruit  `Old Letter` `Old Number` `New Number`
#   <chr>  <chr>               <dbl>        <dbl>
# 1 apple  a                       9           20
# 2 orange a                       8          200
# 3 grape  b                       7         4000
# 4 apple  c                       6           60
# 5 apple  c                       5           60
# 6 orange c                       4          600

我的看法:

从我收到的错误消息来看,似乎不是将`Old Letter`列中的单个值用作pluck()索引使用,而是整个`Old Letter`列被传递为向量。我猜测这是因为根据 case_when()文档的说法:

case_when()不是一个整洁的评估函数。

我尝试追踪有关此问题发生的原因,但跟踪堆栈没有指向任何有用信息的地方,将整个命令包装在rlang::qq_show()quo()中也没有展示R如何解释与NSE相关的命令,因为它们也抛出了相同的错误。

我尝试使用以下组合:

  • quo()
  • enquo()
  • !!
  • !!enquo()(缩写为{{}}),以及
  • sym()

在上面的Reprex代码中进行实验,以及将其封装到一个函数中,但都会抛出相同的错误:

get_num <- function(x, y) purrr::pluck(key_list, x, y)

x %>% mutate(`New Number` = case_when(
  fruit == "apple" ~ get_num(`Old Letter`, "foo") * 10,
  fruit == "orange" ~ get_num(`Old Letter`, "foo") * 100,
  fruit == "grape" ~ get_num(`Old Letter`, "foo") * 1000
  ))

# Error: Index 1 must have length 1, not 6
这个答案 告诉我:

我猜你对 case_when() 的误解在于参数是一次性评估的,而不是针对每一行分别评估。

但我不确定它如何适用于我的情况,所以我陷入了困境。
无论如何,感谢您能提供的任何帮助!

sessionInfo():

R version 3.6.0 (2019-04-26)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS  10.15

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] rlang_0.4.1     readxl_1.3.1    forcats_0.4.0   stringr_1.4.0   dplyr_0.8.3     purrr_0.3.3     readr_1.3.1     tidyr_1.0.0     tibble_2.1.3   
[10] ggplot2_3.2.1   tidyverse_1.2.1

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.2       cellranger_1.1.0 pillar_1.4.2     compiler_3.6.0   base64enc_0.1-3  tools_3.6.0      digest_0.6.22    zeallot_0.1.0    evaluate_0.14   
[10] lubridate_1.7.4  jsonlite_1.6     lifecycle_0.1.0  nlme_3.1-141     gtable_0.3.0     lattice_0.20-38  pkgconfig_2.0.3  cli_1.1.0        rstudioapi_0.10 
[19] yaml_2.2.0       haven_2.1.1      xfun_0.10        withr_2.1.2      xml2_1.2.2       httr_1.4.1       knitr_1.25       generics_0.0.2   vctrs_0.2.0     
[28] hms_0.5.1        grid_3.6.0       tidyselect_0.2.5 glue_1.3.1       R6_2.4.0         fansi_0.4.0      rmarkdown_1.16   modelr_0.1.5     magrittr_1.5    
[37] htmltools_0.4.0  backports_1.1.5  scales_1.0.0     rvest_0.3.4      assertthat_0.2.1 colorspace_1.4-1 utf8_1.1.4       stringi_1.4.3    lazyeval_0.2.2  
[46] munsell_0.5.0    broom_0.5.2      crayon_1.3.4 
1个回答

4
我认为问题与NSE没有多大关系,而是因为pluck不是向量化的。目前的代码中,pluck并非每行只被评估一次,而是尝试一次性运行所有行。然而,正如你发现的那样,pluck需要一个单一数字输入,而不是向量。
解决的方法之一是在行之间使用map函数,使用你的代码作为小型lambda-style函数。注意,您需要使用map_dbl来强制转换为数值,否则map将返回一个列表,一切都会崩溃 :-)
x %>% 
  mutate(`New Number` = case_when(
    fruit == "apple" ~ map_dbl(`Old Letter`, ~ pluck(key_list, ., "foo")) * 10,
    fruit == "orange" ~ map_dbl(`Old Letter`, ~ pluck(key_list, ., "foo")) * 100,
    fruit == "grape" ~ map_dbl(`Old Letter`, ~ pluck(key_list, ., "foo")) * 1000
  ))

# # A tibble: 6 x 4
#   fruit  `Old Letter` `Old Number` `New Number`
#   <chr>  <chr>               <dbl>        <dbl>
# 1 apple  a                       9           10
# 2 orange a                       8          100
# 3 grape  b                       7         3000
# 4 apple  c                       6           50
# 5 apple  c                       5           50
# 6 orange c                       4          500

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接