3

I'm struggling to understand this.

The below lets me filter my data.frame in a "tidy" way, and draw a plot using plotly. In this case, I'm using plotly's formula-based API to say which columns of a data frame to use:

library(plotly)

tidy_filter = function(data, x) {
  x = enquo(x)
  filter(data, !!x > 5)
}

mtcars %>% 
  tidy_filter(wt) %>% 
  plot_ly(x = ~wt, y = ~wt)

I can wrap this in a single function to get the same result:

tidy_ply = function(data, x) {
  x = enquo(x)
  data = filter(data, !!x > 5)
  plot_ly(data, x = x, y = x)
}

tidy_ply(mtcars, wt)

Now:

  1. I assume that enquo(x) in this case is at least in part equivalent to ~wt since that's how it seems to work. But they are two different things (quosure VS formula). What is the relationship between them, and why does the above work?

  2. The advantage of plotly's formula API is that if I want to manipulate the input value, I can do things like ~wt/2. But in the above, doing plot_ly(data, x = x, y = x/2) produces an error. Is there a way to make this work?

I guess the general question is how to best combine the tidy eval approach with plotly's formula approach?

jakub
  • 4,774
  • 4
  • 29
  • 46

1 Answers1

5

From this answer by @alistaire:

The plotly R package was created a little before rlang, and has its own non-standard evaluation (NSE) system, that as far as I can tell is mostly only documented in the examples.

When NSE systems go sideways, the quickest way to get it to work is to rewrite all the code dynamically and then evaluate it. Here, wrap the whole plotly pipeline in quo with !! substitution wherever you like, then call quo_squash on it to collapse it to a single expression (instead of nested quosures), and then call eval_tidy on the whole lot to actually run it.

In plotly, ~ is used to refer to a column in the dataset to be visualized (@cpsievert).

In your example, x is a quosure thus you have to first unquote it before applying any base operator. That's what the error message told you:

Error: Base operators are not defined for quosures.
Do you need to unquote the quosure?

  # Bad:
  myquosure / rhs

  # Good:
  !!myquosure / rhs

Here is a working solution:

library(rlang)
library(plotly)

tidy_ply2 <- function(data, x) {
  x = enquo(x)
  print(x)
  
  data = filter(data, !!x > 5)
  
  # https://rlang.r-lib.org/reference/quasiquotation.html
  cat('\nUse qq_show() to debug the effect of unquoting operators\n')
  qq_show(plot_ly(data, x = ~!!x, y = ~!!x/2))
  
  # `base::eval` works too
  eval_tidy(
    quo_squash(
      quo({
        plot_ly(data, x = ~!!x, y = ~!!x/2)
      })
    )
  )
}

tidy_ply2(mtcars, wt)
#> <quosure>
#> expr: ^wt
#> env:  global
#> 
#> Use qq_show() to debug the effect of unquoting operators
#> plot_ly(data, x = ~^wt, y = ~(^wt) / 2)
#> 

Created on 2019-04-03 by the reprex package (v0.2.1.9000)

Community
  • 1
  • 1
Tung
  • 26,371
  • 7
  • 91
  • 115
  • Makes sense. How "dangerous" do you think this approach is? From `?rlang::quo_squash`: "...if the squashed quosure is evaluated, all expressions of the flattened quosures are resolved in a single environment. This is a source of bugs so it is good practice to set warn to TRUE..." Is there a more robust approach? – jakub Apr 04 '19 at 07:07
  • @jakub: you got everything contain within a function so I wouldn't worry too much – Tung Apr 04 '19 at 14:07