【问题标题】:difference between enquo() and toString() within a function函数中 enquo() 和 toString() 的区别
【发布时间】:2021-02-17 21:18:34
【问题描述】:

我仍然对 enquo 和 toString 的使用感到有些困惑。在下面的示例中,我基本上只是尝试过滤数据框并将最后的行求和。我真的不明白为什么 enquo 和 toString 对我想做的第一件事做同样的事情(过滤器 --> 选项 1 和 2 给出相同的结果)但不是我想做的第二件事(总和 -->选项 1 有效,但选项 2 给了我一个错误)。仅仅是因为我在 dplyr 管道中使用它吗?

library(dplyr)
library(tidyverse)


### define dataframe
dataframe_test <- data.frame(
  column_test = c(100,99,99,90,89,50),
  month_test = c("2020-09-01", "2020-09-01","2020-09-01", "2020-09-01","2020-10-01","2020-10-01")
)



test_function <- function(df, df_col_indicator, df_col_month, char_month) {
  
  
  ### define variables for enquo, ensym, toString
  df_col_indicator_enquo <- enquo(df_col_indicator)
  df_col_indicator_ensym <- ensym(df_col_indicator)
  df_col_indicator_toString <- toString(df_col_indicator)
  
  df_col_month_ensym <- ensym(df_col_month)

  
  dataframe2 <- df %>%
    filter(!!df_col_month_ensym == char_month) %>% # filter for month
    slice_max(!!df_col_indicator_ensym, n = 3) %>% # slice top 3 observations
  
    ## two options for filter
    # option 1
    filter(!!df_col_indicator_ensym == df[2, df_col_indicator_toString]) # filter for observations with same observation as second row
    # option 2
    #filter(!!df_col_indicator_ensym == df[2, !!df_col_indicator_enquo])
  
  
  ## two options for sum
  # option 1
  bb <- sum(dataframe2[ , df_col_indicator_toString]) # sum up observations
  
  # option 2
  #bb <- sum(dataframe2[ , !!df_col_indicator_enquo])
  
  return(bb)
  
}


test_function(df = dataframe_test, df_col_indicator = "column_test", df_col_month = "month_test" , char_month = "2020-09-01")

编辑:

谢谢大家的回答。呵呵,好吧我不得不承认这个例子有点愚蠢,但我在这里尽量保持简单。我最初的问题实际上是这个(见下文)。我基本上尝试选择一列的前 5 个数字。有三种不同的结果。 1)如果超过 5 个 ==100,那么我想将 5 个观察值随机存储在列表(指标)中,其他观察值在列表(星号)中。 2)如果不是所有的观察结果都是 ==100 但有关系(第 5、第 6 位具有相同的值),我想随机选择那些有关系的,然后再次将一些放入列表(指标)中,将其他观察放入列表(星号)中。 3)如果没有关系,只需选择前 5 个观察值。我现在的主要问题是,如果我想在最底部的循环(包含所有列)上运行我的函数。不知何故,我总是将第一行作为结果...我想我不明白如何在循环中正确设置函数的变量名...?

library(dplyr)
library(tidyverse)

remove(list = ls())


dataframe_test <- data.frame(
  county_name = c("a", "b","c", "d","e", "f", "g", "h"),
  column_test1 = c(100,100,100,100,100,100,50,50),
  column_test2 = c(40,90,50,40,40,100,13,14),
  column_test3 = c(100,90,50,40,30,40,100,50),
  month = c("2020-09-01", "2020-09-01" ,"2020-09-01" ,"2020-09-01" ,"2020-09-01" ,"2020-09-01" ,"2020-08-01","2020-08-01"))


choose_top_5 <- function(df, df_col_indicator, df_col_month, char_month, numb_top, df_col_county) {

  ### enquo / ensym / deparse
  df_col_indicator_enquo <- enquo(df_col_indicator)
  df_col_indicator_ensym <- ensym(df_col_indicator)

  df_col_month_ensym <- ensym(df_col_month)
  df_col_month_enquo = enquo(df_col_month)


  ### filter month and top 5 observations
  df_top <- df %>%
    filter(!!df_col_month_ensym == char_month) %>%
    slice_max(!!df_col_indicator_ensym, n = numb_top) %>%
    select(!!df_col_county, !!df_col_month_ensym, !!df_col_indicator_ensym)


  ### if there are more than "numb_top" values and all equals to 100 --> randomly pick "numb_top"
  if (nrow(df_top) > numb_top &
      sum(df_top[ , df_col_indicator  ]) == 100*nrow(df_top)  ) {

    ## randomly pick "numb_top" out of all
    random_shuffle <- df_top[sample(nrow(df_top)),]
    indicator <- random_shuffle[1:numb_top,]
    asterisk <- random_shuffle[(numb_top+1):nrow(random_shuffle),]

    ## return "numb_top" and put names of others in asterisk
    return_list <- list(indicator, asterisk)


    ### if there are more than "numb_top" values but not all 100 (e.g. 100, 100, 100, 99, 99, 99)
    ## --> pick randomly 99 values
  } else if (nrow(df_top) > numb_top) {

    ### filter for all observations that have the same value as "numb_top"
    df_treshold <- df_top %>%
      filter(!!df_col_indicator_ensym == df_top[numb_top, df_col_indicator])

    ## randomly shuffle the observations
    random_shuffle <- df_treshold[sample(nrow(df_treshold)),]

    ## combine observations again an pick "numb_top"
    combine <- rbind(df_top[1:(nrow(df_top)-nrow(df_treshold)), ], random_shuffle)
    indicator <- combine[1:numb_top,]
    asterisk <- combine[(numb_top+1):nrow(combine),]

    ## return "numb_top" and put names of others in asterisk
    return_list <- list(indicator, asterisk)

    ### if there are not more than "numb_top" values
  } else {


    indicator <- df_top
    asterisk <- NA

    ## return "numb_top", asterisk is NA
    return_list <- list(indicator, asterisk)
  }

  return(return_list)


}



### function for 1 column
a=choose_top_5(df = dataframe_test, df_col_indicator = "column_test3",
             df_col_month = "month", char_month = "2020-09-01", numb_top = 5,
             df_col_county = "county_name")
a



### function over all columns and store in list

all_indicators <- c("column_test1","column_test2","column_test3")

my_list <- list()

for (i in all_indicators) {

  my_list[[i]] <- choose_top_5(df = dataframe_test, df_col_indicator = i,
                                        df_col_month = "month", char_month = "2020-09-01", numb_top = 5,
                                        df_col_county = "county_name")
}

my_list

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    要添加到上面的 Allans 答案:

    通常,您不能在不支持准引用的函数中使用强制运算符!!。然而,正如 Lionel Henry 指出的herehere,即将推出的{rlang} 版本可能包含一个名为blast() 的函数,用于准引用并立即进行评估。下面我用你的例子。在最后一行中,您可以看到 !! 如何在本身不支持 quasiquoation 的基本 R 函数中使用。

    library(tidyverse)
    library(rlang)
    
    ### define dataframe
    dataframe_test <- data.frame(
      column_test = c(100,99,99,90,89,50),
      month_test = c("2020-09-01", "2020-09-01","2020-09-01", "2020-09-01","2020-10-01","2020-10-01")
    )
    
    blast <- function(expr, env = caller_env()) {
      eval_bare(enexpr(expr), env)
    }
    
    test_function <- function(df, df_col_indicator, df_col_month, char_month) {
      
      df_col_indicator_enquo <- enquo(df_col_indicator)
      df_col_indicator_ensym <- ensym(df_col_indicator)
      df_col_month_enquo     <- enquo(df_col_month)
      
      temp <- df %>%
        filter(!!df_col_month_enquo == char_month) %>% 
        slice_max(!!df_col_indicator_enquo, n = 3) %>% 
        filter(!!df_col_indicator_enquo == df %>% 
                 select(!!df_col_indicator_enquo) %>% 
                 pluck(1, 2)) 
      
      # with `blast()` we can use the forcing operator !! inside sum(`$`...)
      blast(sum(`$`(temp, !! df_col_indicator_ensym)))
    }
    
    test_function(df = dataframe_test, column_test, month_test , "2020-09-01")
    #> [1] 198
    

    reprex package (v0.3.0) 于 2020-11-05 创建

    【讨论】:

      【解决方案2】:

      值得指出的是,toString 在这里并没有真正做任何事情。如果您将一个字符传递给toString,而没有其他参数,则它保持不变:

      test_toString <- function(x) identical(x, toString(x))
      
      test_toString("hello")
      #> [1] TRUE
      

      所以df_col_indicator_toString 可以从你的函数中取出并替换为df_col_indicator

      还值得指出的是,当你 enquo 一个字符串变量,然后用双 bang (!!) 运算符取消引用它时,你会得到原始字符串,所以使用 @ 没有意义987654328@ 在你的函数中,如果你传递一个字符串。

      这意味着我们可以通过以下方式简化您的功能:

      test_function <- function(df, df_col_indicator, df_col_month, char_month) {
      
        df_col_indicator_ensym <- ensym(df_col_indicator)
        df_col_month_ensym     <- ensym(df_col_month)
      
        dataframe2 <- df %>%
          filter(!!df_col_month_ensym == char_month) %>% 
          slice_max(!!df_col_indicator_ensym, n = 3) %>% 
          filter(!!df_col_indicator_ensym == df[2, df_col_indicator]) 
      
        sum(dataframe2[ , df_col_indicator])
      }
      
      test_function(df = dataframe_test, "column_test", "month_test" , "2020-09-01")
      #> [1] 198
      

      在用户定义的函数中使用enquo 和双键(统称为准引号)更常用来传递列,而无需在列名周围加上引号。例如,如果您将函数更改为:

      test_function <- function(df, df_col_indicator, df_col_month, char_month) {
      
        df_col_indicator_enquo <- enquo(df_col_indicator)
        df_col_month_enquo     <- enquo(df_col_month)
      
        df %>%
          filter(!!df_col_month_enquo == char_month) %>% 
          slice_max(!!df_col_indicator_enquo, n = 3) %>% 
          filter(!!df_col_indicator_enquo == df %>% 
                                              select(!!df_col_indicator_enquo) %>% 
                                              pluck(1, 2)) %>% 
          summarize(total = sum(!!df_col_indicator_enquo)) %>%
          pluck(1, 1)
      }
      

      那么你可以这样做:

      test_function(df = dataframe_test, column_test, month_test , "2020-09-01")
      #> [1] 198
      

      要回答有关您为什么会收到错误的更广泛的问题,那么答案是您只能在 quasiquotation 上下文 中使用此语法。不要将此与管道混淆;在某些 tidyverse 函数中可以使用准引用,但在基本 R 函数中则不能,这就是方法 2 不起作用的原因。本质上,使用!! 运算符的函数必须专门编写以处理准引用; base R 不是这样写的。

      最后一点,你的函数逻辑似乎有点不寻常,你应该检查一下这是否真的是你想要做的。您正在过滤月份,选择前三个条目,然后根据前三个条目是否等于 原始 数据第二行中的值来过滤 that帧,在求和之前。这似乎是一件奇怪的事情。

      【讨论】:

      • 非常感谢。是的,我知道,奇怪的例子,但这只是试图找出我最初问题的答案的一个例子。我现在在上面编辑了我的实际问题..
      猜你喜欢
      • 1970-01-01
      • 2022-12-22
      • 2011-02-19
      • 2012-04-24
      • 1970-01-01
      • 2014-07-14
      • 2014-03-27
      • 2011-01-07
      • 2018-07-17
      相关资源
      最近更新 更多