New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Documentation question: how does one convert a string to a quosure? #116
Comments
|
you can use select(df, !! sym("foo"))
select(df, !!! syms(c("foo", "bar")))or you can unquote them while creating quosures (which is actually the same mechanism): quo(!! sym("foo"))
quo(list(!!! syms(letters))) |
|
Thanks, just a note: that doesn't work until you explicitly import rlang (dplyr seems to not share it). library("dplyr") # installed today
packageVersion("dplyr")
# [1] ‘0.5.0.9004’
select(mtcars, !! sym("disp"))
# Error in (function (x) : could not find function "sym"
library('rlang')
packageVersion("rlang")
# [1] ‘0.0.0.9018’
select(mtcars, !! sym("disp")) |
|
You can qualify: |
|
Also I don't think library("dplyr")
library("rlang")
varName <- 'disp'
varQ = quo(sym(varName))
varQ
# <quosure: global>
# ~sym(varName)
select(mtcars, varQ)
# Error: `varQ` must resolve to integer column positions, not formula Even if that did work, it would be dangerous as it looks like it is hanging on a reference to Can you please re-open this issue until we find a working pure |
|
But you don't need to create a quosure here; just pass in a symbol (which you can also create with |
|
When you're programming with NSE functions, you are building an expression. Unquoting makes it possible to change parts of the expression and is what a programmer should focus on: varName <- "disp"
varQ <- quo(!! sym(varName)) # unquoting the symbol
select(mtcars, !! varQ) # unquoting the quosureNote that the quosure is superfluous here as Hadley mentions, since you're not referring to symbols from the contextual environment.
I don't understand what you mean. In the snippet above we build a quosure containing the value of |
|
John Mount here again (different account, sorry). I see the later solutions do work, and than you for that. I also understand answering questions is a volunteer activity, so I appreciate you working on this for me. I had a typo in my attempt to use the answer, and I apologize for that. But variations on that idea do not work even if I type it "correctly": library("dplyr")
library("rlang")
varName <- 'disp'
varQ = quo(sym(varName))
select(mtcars, !! varQ)
# Error: `sym(varName)` must resolve to integer column positions, not symbolTo answer some expressed concerns and set some context I have some follow-up, but all my questions are now answered. As far as the appearance of unbound variables and CRAN check. If I had been submitting the above code to CRAN I would have added the following line above the let-block: VARNAME <- NULL # mark variable symbol as not an unbound referenceThe reason I asked for "quosure" is: I thought that was all I do not mind importing The latest solution does work (and thank you for it): library("dplyr")
library("rlang")
varName <- 'disp'
varQ = quo(!! sym(varName))
mtcars %>%
select(!! varQ) %>%
head()
# disp
#Mazda RX4 160
#Mazda RX4 Wag 160
#Datsun 710 108
#Hornet 4 Drive 258
#Hornet Sportabout 360
#Valiant 225It also appears to not have the captured reference name issue. Since the captured reference issue is not in this variation (there is no visible reference to the variable name "varName" in "varQ") there isn't much point going more into it. But the rough idea is: if a Also I understand library("dplyr")
varName <- 'disp'
varQ = as.name(varName)
mtcars %>%
select(!! varQ) %>%
head()
# disp
#Mazda RX4 160
#Mazda RX4 Wag 160
#Datsun 710 108
#Hornet 4 Drive 258
#Hornet Sportabout 360
#Valiant 225Finally I did work on this before asking. I tried all of: varQ = as.formula(~varName)
varQ = quo(!! varName)
varQ = quote(varName)
varQ = quote(!! varName)And none of the above worked. Frankly I was guessing, but the reason I was guessing is I did not find a worked example of converting a string to something I had tried Anyway thank you very much for your solutions. |
|
You can also use the varName <- "disp"
mtcars %>% select(.data["disp"])(Well, you will be able to once tidyverse/dplyr#2718 is merged) |
It's not about reference semantics, it's about delayed evaluation. We're building an expression, sometimes in several steps, and if the value of some symbols changes before evaluation actually happens this could be a problem. To work around this, you can unquote values rather than symbols, or you could make sure the symbols are in read-only environments (e.g. by building an appropriate quosure).
tidyeval works with pure expressions. The only adjustment we make is that quosures self-evaluate within their environments (with overscoped data attached). This is why the following expressions are completely equivalent: select(mtcars, "cyl")
var <- "cyl"
select(mtcars, !! var)This doesn't work because select(mtcars, cyl)
var <- sym("cyl")
select(mtcars, !! var)Alternatively, you can also supply the values it understands (column positions): select(mtcars, 1)
var <- 1
select(mtcars, !! var) |
|
It seems that if an expression (as opposed to a column name) is passed as a string the above solutions do not work. count(mtcars, !!! rlang::syms(c("2 * cyl", "am")))
#> Error in grouped_df_impl(data, unname(vars), drop): Column `2 * cyl` is unknownWhile this works: count(mtcars, !!! rlang::syms(c("cyl", "am")))
#> # A tibble: 6 x 3
#> cyl am n
#> <dbl> <dbl> <int>
#> 1 4 0 3
#> 2 4 1 8
#> 3 6 0 4
#> 4 6 1 3
#> 5 8 0 12
#> 6 8 1 2I must be missing something but I'm not sure what. |
|
|
|
Right. It does not. But count(mtcars, 2 * cyl, am)
#> # A tibble: 6 x 3
#> `2 * cyl` am n
#> <dbl> <dbl> <int>
#> 1 8 0 3
#> 2 8 1 8
#> 3 12 0 4
#> 4 12 1 3
#> 5 16 0 12
#> 6 16 1 2This even works with count_(mtcars, c("2 * cyl", "am"))
#> # A tibble: 6 x 3
#> `2 * cyl` am n
#> <dbl> <dbl> <int>
#> 1 8 0 3
#> 2 8 1 8
#> 3 12 0 4
#> 4 12 1 3
#> 5 16 0 12
#> 6 16 1 2Can I get the same results if |
|
Yes, you need to parse it, not convert it to a string. I forget if rlang has an equivalent of |
|
There is |
|
A couple SO answers which illustrate how one can use quosures and |
|
@hadley , including references to as.name() or sym() with examples in the dplyr programming vignette would be very helpful. I must have tried just about every permutation in the (0.7.3) vignette examples trying to get a code-generated string (in a Shiny app) to be a column name before I stumbled upon this thread. FWIW. (Will happily post this in whatever venue is more appropriate.) |
|
@gwhiteford-cwt we're working on a new vignette: http://rpubs.com/lionel-/programming-draft |
|
btw if you need a symbol don't use the |
|
@lionel- Why what's the difference? For example: Where can I read about the difference? |
|
parse_expr("foo+")
#> Error in parse(text = x) : <text>:2:0: unexpected end of input
#> 1: foo+
#> ^
sym("foo+")
#> `foo+` |
|
@lionel- I think you should also include a reference to the into this |
|
note the old You are right we should document this process somewhere. |
|
hey @lionel- noticed that there's no mention of sym() or syms() in the final vignette, so has the best practice changed? my use case is similar to @gwhiteford-cwt, i.e. allowing the user to choose which column to group_by() in a shiny app... |
|
If the symbols reference data frame columns, you can safely use |
|
@lionel- thanks for clarifying! |
|
edit: @lionel- |
|
Agreed, my_filter <- function(data, col, op, value) {
op_sym <- sym(op)
col_sym <- sym(col)
cond <- expr((!!op_sym)(!!col_sym, !!value))
filter(data, !!cond)
}
my_filter(starwars, "mass", "<", 20)
my_filter(starwars, "mass", ">", 1000)This is compatible with cond <- new_quosure(cond, parent.frame())just before the |
|
Though it's better to take the environment as argument in case your function is called from another function rather than by the user. So the complete solution is: my_filter <- function(data, col, op, value, env = parent.frame()) {
op_sym <- sym(op)
col_sym <- sym(col)
cond <- expr((!!op_sym)(!!col_sym, !!value))
cond <- new_quosure(cond, env)
filter(data, !!cond)
} |
|
Ok, thanks, that's a helpful example |
|
And you can also build your expression with cond <- call(op, sym(col), value)That does make things a bit simpler: my_filter <- function(data, col, op, value, env = parent.frame()) {
cond <- call(op, sym(col), value)
cond <- new_quosure(cond, env)
filter(data, !!cond)
} |
|
yes I like that! |
|
Fixed by #1307. In particular, see:
|
|
Wow, looks like my old patterns of By the way, are |
It's been pretty much stable for a few years now. The new glue and On the other hand, the
If they are used in the wrong place they'll be treated as negation operators, see https://rlang.r-lib.org/reference/topic-inject-out-of-context.html
In case that helps, see https://rlang.r-lib.org/reference/topic-data-mask-ambiguity.html for how to use |
|
Thanks as always for the incredibly impressive work on rlang. I never would have imagined we would have such a powerful framework for safely using both NSE and SE as appropriate in any possible situation. |
I have skimmed the dplyr/tidyeval/rlang documentation and tutorials and I don't remember or see how to convert a string to a quosure easily. What I want to do (and I think it is an important use case) is take the name of a column as a string from some external source (say from colnames(), or from the yarn-control block of an R-markdown document) and then use that string as a variable name. It looks like to do that you have to promote the string up to a quosure- and that is the part I don't know how to do in pure tidyeval idiom. I've tried things like
quo(), but I am missing something.Below is a specific example with a work-around that shows the effect I want. The only question is how does one produce the variable varQ from the value stored in varName (again, assuming the value stored is a string and not known to the programmer)?
packageVersion("dplyr")The text was updated successfully, but these errors were encountered: