1

I have a function that roughly follows this structure:

TestFunc <- function(dat, point) {
    if (!(point %in% c("NW", "SE", "SW"))) 
        stop("point must be one of 'SW', 'SE', 'NW'")

    point <- enquo(point)

    return(dat %>% filter(point == !!point))

The issue is that I get the following error when I include the check for values:

Error in (function (x, strict = TRUE)  : 
  the argument has already been evaluated

The error disappears when I remove the check. How can I keep both?

Lionel Henry
  • 6,652
  • 27
  • 33
Brandon Sherman
  • 673
  • 1
  • 8
  • 25
  • 2
    How exactly are you calling this function? Are you passing `point` as an unquoted expression? It's easier to help you with a [reproducible example](https://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example) with sample input data so we can try to run the code. – MrFlick Sep 19 '17 at 20:51
  • 1
    Best guess without reproducible example: Move the `enquo` to the top, and use `!!point` in your check. – Axeman Sep 19 '17 at 20:53
  • 1
    I don't think you need `enquo` here at all, just `!!`. If you didn't have a variable and a function argument with the same name I don't think you'd need any non-standard evaluation tools here at all. – aosmith Sep 19 '17 at 20:57
  • 2
    Fyi, the functionality of your example check exists in base R already; see `?match.arg`. Using it will not solve your problem here, though, I think. – Frank Sep 19 '17 at 21:14
  • @MrFlick I'm passing it in quotes. Passing without quotes results in a missing object error. – Brandon Sherman Sep 20 '17 at 17:58
  • Is `point` supposed to be both a column name _and_ a value in that column? In other words, are you trying to replace both `point`'s in `dat %>% filter(point == !!point)`? – acylam Nov 14 '17 at 16:40

3 Answers3

3

The thing to remember about the quosure framework is that it's a very clever, sophisticated piece of code that undoes another very clever, sophisticated piece of code to get you back where you started from. What you want is achievable in a very simple fashion, without going to NSE and coming back again.

TestFunc <- function(dat, point)
{
    if(!(point %in% c("NW", "SE", "SW")))
        stop("point must be one of 'SW', 'SE', 'NW'")
    dat[dat$point == point, ]
}

(The difference between this and using match.arg, as @Frank suggests in a comment, is that match.arg will use the first value as the default if no input is supplied whereas this will fail.)

If you want to call other dplyr/tidyverse verbs, just do that after filtering the rows.

Hong Ooi
  • 56,353
  • 13
  • 134
  • 187
1

Because of technical reasons that have to do with how R optimises code, you can only capture arguments that have not been evaluated yet.

So you first have to capture with enquo() and then proceed to check the value. However if you have to mix both quoting and value-based code it often indicates a design problem.

As Hong suggested it seems that in your case you can directly unquote the value without capturing it. Unquoting will ensure the right value is found (since you gave the same name to that variable as the column in your data frame).

Lionel Henry
  • 6,652
  • 27
  • 33
0

Evaluate point so filter can tell the difference between the argument and the data's column

aosmith has a good idea, so I'm putting it in answer form, along with a reproducible example:

f <- function(dat, point) {
  if (!(point %in% c("NW", "SE", "SW")))
    stop("point must be one of 'SW', 'SE', 'NW'")

  filter(dat, point == !!point)
}

tbl <- tibble(point = c('SE', 'NW'))
f(tbl, 'SE')
f(tbl, 'XX')

If you're passing point in as a string, you only need to differentiate the argument point ( = "SE", in this case) and the column dat$point (or tbl$point, depending if you're inside or outside the function). From the dplyr programming doc:

In dplyr (and in tidyeval in general) you use !! to say that you want to unquote an input so that it’s evaluated, not quoted.

You want to evaluate the argument point so that "SE" is passed to filter, that way filter can tell the difference between the column point and the argument SE.

(I also tried using filter(dat, .data$point == point), but the RHS point still refers to the column, not the f argument.)

I hope this example helps with your real code.

twedl
  • 1,588
  • 1
  • 17
  • 28