代码之家  ›  专栏  ›  技术社区  ›  Thomas

在dplyr而不是SE中使用get()是否有缺点?

  •  7
  • Thomas  · 技术社区  · 8 年前

    我一直在阅读dplyr中关于SE和NSE的内容,遇到了一个实际需要SE的问题。我有以下函数,该函数用于查找某些项匹配的行,但目标变量不匹配:

    find_dataset_inconsistencies <- function(df, target_column, cols_to_use) {
      inconsists <- df %>% 
        group_by_at(cols_to_use) %>% 
        summarise(uTargets = length(unique(get(target_column)))) %>% 
        filter(uTargets > 1)
    }
    

    这似乎对我的情况有效。然而 get(target\u列) 是一种解决方法,因为我需要变量的SE,无法对列名进行硬编码。我最初尝试使用SE版本( summarise_(.dots = ...) ),但很难找到计算target\u列的正确语法。

    我的问题如下:简单地使用 get() ? 在任何情况下,这都不会起作用吗?是否存在任何风险/减速?简单使用 get 绝对比“正确的”SE语法更具可读性。

    2 回复  |  直到 8 年前
        1
  •  5
  •   Aurèle    8 年前

    可以使用NSE,使用 rlang .

    假设您的用例是:

    find_dataset_inconsistencies(mtcars, "mpg", c("cyl", "vs", "am", "gear", "carb"))
    # # A tibble: 8 x 6
    # # Groups:   cyl, vs, am, gear [5]
    #     cyl    vs    am  gear  carb uTargets
    #   <dbl> <dbl> <dbl> <dbl> <dbl>    <int>
    # 1  4.00  1.00  0     4.00  2.00        2
    # 2  4.00  1.00  1.00  4.00  1.00        4
    # 3  4.00  1.00  1.00  4.00  2.00        2
    # 4  6.00  1.00  0     3.00  1.00        2
    # 5  6.00  1.00  0     4.00  4.00        2
    # 6  8.00  0     0     3.00  2.00        4
    # 7  8.00  0     0     3.00  3.00        3
    # 8  8.00  0     0     3.00  4.00        4
    

    您可以:

    library(dplyr)
    
    f2 <- function(df, target_column, cols_to_use) {
      group_by_at(df, cols_to_use) %>% 
        summarise(uTargets = n_distinct(!! rlang::sym(target_column))) %>% 
        filter(uTargets > 1)
    }
    
    all.equal(
      find_dataset_inconsistencies(mtcars, "mpg", c("cyl", "vs", "am", "gear", "carb")),
      f2(mtcars, "mpg", c("cyl", "vs", "am", "gear", "carb"))
    )
    # [1] TRUE
    

    关于风险问题的实际答案:

    现在想象一下 foo <- 3 在您的全球环境中。比较:

    find_dataset_inconsistencies(mtcars, "foo", c("cyl", "vs", "am", "gear", "carb"))
    # A tibble: 0 x 6
    # Groups:   cyl, vs, am, gear [0]
    # ... with 6 variables: cyl <dbl>, vs <dbl>, am <dbl>, gear <dbl>,
    #   carb <dbl>, uTargets <int>
    

    将以静默方式返回空数据帧,并且:

    f2(mtcars, "foo", c("cyl", "vs", "am", "gear", "carb"))
    # Error in summarise_impl(.data, dots) : variable 'foo' not found
    

    这将引发一个直接指向错误的错误。


    编辑

    既然你似乎在追求“tidyverse方式”,我建议你做以下几点。基本原理似乎是尽可能不鼓励使用变量名称作为字符串,而不是简单的名称:

    f3 <- function(df, target_column, ...) {
      target_column <- enquo(target_column)
      cols_to_use <- quos(...)
      group_by(df, !!! cols_to_use) %>% 
        summarise(uTargets = n_distinct(!! target_column)) %>% 
        filter(uTargets > 1)
    }
    all.equal(
      find_dataset_inconsistencies(mtcars, "mpg", c("cyl", "vs", "am", "gear", "carb")),
      f3(mtcars, target_column = mpg, cyl, vs, am, gear, carb)
    )
    # [1] TRUE
    

    f3() 的接口也被设计为类似于其他tidyverse函数的接口,并且可能更好地集成到tidyverse转换管道中。

        2
  •  2
  •   G. Grothendieck    8 年前

    @Aurele已经展示了如何使用rlang来实现它,但我想看看我们是否可以使用 get 也正如我所指出的 收到 不起作用,但经过一些实验后,这似乎如预期的那样起作用。这并不是说我在建议这一点,而是出于兴趣的考虑。

    1、获取/执行

    如果我们用 do 然后我们可以使用 get(..., .) 像这样,它将按需要工作。这可能是最简单、最直接的使用方法 收到 在内部 group by . 关键的观察结果是 做 点指的是当前组内的行,而不是 做 当在嵌套函数调用的实际参数中使用时,它引用输入的所有行。

    find_dataset_inconsistencies <- function(df, target_column, cols_to_use) {
      df %>% 
        group_by_at(cols_to_use) %>% 
        do(summarise(., uTargets = length(unique(get(target_column, .))))) %>% 
        filter(uTargets > 1)
    }
    
    # gives desired result
    find_dataset_inconsistencies(mtcars, "mpg", c("cyl", "vs", "am", "gear", "carb"))
    # ... snip correct output ...
    
    # correctly gives an error indicating it can't find `foo`
    foo <- 3
    find_dataset_inconsistencies(mtcars, "foo", c("cyl", "vs", "am", "gear", "carb"))
    

    2、与祖父母接触继承=假

    find_dataset_inconsistencies <- function(df, target_column, cols_to_use) {
      df %>% 
        group_by_at(cols_to_use) %>% 
        summarise(uTargets = length(unique(get(target_column,
           parent.env(parent.env(environment())), inherits = FALSE)))) %>% 
        filter(uTargets > 1)
    }
    
    # gives desired result
    find_dataset_inconsistencies(mtcars, "mpg", c("cyl", "vs", "am", "gear", "carb"))
    # ... snip correct output ...
    
    # correctly gives an error indicating it can't find `foo`
    foo <- 3
    find_dataset_inconsistencies(mtcars, "foo", c("cyl", "vs", "am", "gear", "carb"))
    ## Error in summarise_impl(.data, dots) : 
    ##   Evaluation error: object 'foo' not found.
    

    为了使此解决方案更加精简,我们可以定义 GET 这样地:

    GET <- function(x) {
      p <- parent.frame()
      p3 <- parent.env(parent.env(p))
      get(x, p3, inherits = FALSE)
    }
    
    find_dataset_inconsistencies <- function(df, target_column, cols_to_use) {
      df %>% 
        group_by_at(cols_to_use) %>% 
        summarise(uTargets = length(unique(GET(target_column)))) %>% 
        filter(uTargets > 1)
    }
    
    # gives expected answer    
    find_dataset_inconsistencies(mtcars, "mpg", c("cyl", "vs", "am", "gear", "carb"))
    
    # gives expected error
    foo <- 3
    find_dataset_inconsistencies(mtcars, "foo", c("cyl", "vs", "am", "gear", "carb"))
    

    3、按键列子集

    另一种可能性是通过键列进行子集。 mtcars 没有这样的列,但如果我们将行名称设置为这样的列,则会有一个:

    library(tidyr)
    find_dataset_inconsistencies <- function(df, target_column, cols_to_use) {
      df %>% 
        rownames_to_column %>%
        group_by_at(cols_to_use) %>% 
        summarise(uTargets = length(unique(
            get(target_column, .[.$rowname %in% rowname, ])))) %>% 
        filter(uTargets > 1)
    }
    
    # gives expected answer
    find_dataset_inconsistencies(mtcars, "mpg", c("cyl", "vs", "am", "gear", "carb"))
    
    # gives expected error
    find_dataset_inconsistencies(mtcars, "foo", c("cyl", "vs", "am", "gear", "carb"))
    
    推荐文章