0

I was trying to generate an expression in a separate function. To prevent repetitive code. The expression is then passed onto ggplot.

This works fine (a simple expression)

ggplot(mpg, aes(x = model, y = displ)) + geom_boxplot()

This works fine too (an expression with formula and string)

x = "displ"
xVar = expr(!!ensym(x) * 2)
ggplot(mpg, aes(x = model, y = !!xVar)) + geom_boxplot()

This doesn't work (an expression generated in a formula)

makeExpression = function(varName){
    return(expr(!!ensym(varName) * 2))
}
xVar = makeExpression(x)
ggplot(mpg, aes(x = model, y = !!xVar)) + geom_boxplot()

This works (an expression generated in a formula with a dirty hack)

makeExpression = function(varName){
    a = varName
    return(expr(!!ensym(varName) * 2))
}
xVar = makeExpression(x)
ggplot(mpg, aes(x = model, y = !!xVar)) + geom_boxplot()

The third example gives the following error: *Error in x * 2 : non-numeric argument to binary operator*

This means that "x", which was supplied to the function is not evaluated. x holds a string of a column name. The expression should have been: displ * 2

Funnily 4th example also works after accessing varName once within the function. Note that a is never used.

I don't understand why this is the case. It looks like a dirty hack to me but it probably has something to do with the scoping. Is there any cleaner way of doing this?

Saren Tasciyan
  • 454
  • 4
  • 15
  • It's easier to help you if you include a simple [reproducible example](https://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example) with sample input and desired output that can be used to test and verify possible solutions. Maybe simplify the functions to isolate the exact problem you are trying to solve. It's unclear exactly what you are passing to these functions or what packages you might be working with where these functions are defined. – MrFlick Oct 22 '19 at 14:56
  • @MrFlick You are absolutely right. I made a simple working example and edited my original question. – Saren Tasciyan Oct 23 '19 at 08:26
  • 1
    I'm not exactly sure what your question is here any more. If you want to pass a character value that need to be evaluated to a function, use `sym()` rather than `ensym()`. The latter is designed for when you want to pass an unquoted symbol. – MrFlick Oct 23 '19 at 15:23
  • That solves it. Apparently, I didn't get the usage of `sym` and `ensym` before. With `sym` it works. Funnily, with `ensym` this behaviour is still weird. Also, all of my code worked with `ensym` until I tried it in a function. – Saren Tasciyan Oct 23 '19 at 22:05
  • 1
    ensym is really meant to be used only in a function. But unlike other en- functions it has a fall back to accept character values which probably lead to more confusion than help in this case. – MrFlick Oct 23 '19 at 22:07
  • Well, if you would write this in an answer, I would like to accept it. – Saren Tasciyan Oct 23 '19 at 23:32

1 Answers1

1

If you want to pass a variable that contains a name as a character value, then you want to use sym rather than ensym. sym will evaluate the parameter to pass to it to get the value. ensym is meant only to be used in function to capture expressions passed to function. Note that this magic only works when the parameter is still in the "promise" state and hasn't been evaluated by any previous code in the function. This is why the 4th example "works" because that extra code forces the promise to be evaluated.

MrFlick
  • 195,160
  • 17
  • 277
  • 295