mjskay / tidybayes

Bayesian analysis + tidy data + geoms (R package)
http://mjskay.github.io/tidybayes
GNU General Public License v3.0
718 stars 59 forks source link

Passing parameters in as strings #202

Closed rgiordan closed 5 years ago

rgiordan commented 5 years ago

I would like to replace some boilerplate of my own with this wonderful package. But I'm not able to pass in array parameter arguments as strings using tidy evaluation. With apologies, I'm not sure whether this is a problem with my understand of tidy evaluation (most likely), with tidybayes, or with syms.

An example is below.

library(tidybayes)
library(tidyverse)

# Make a simple example matrix.
draw_mat <- matrix(runif(5 * 4), nrow=5)
colnames(draw_mat) <- c(sprintf("a[%d]", 1:3), "b")

# Works:
gather_draws(draw_mat, a[i], b)

gather_draws_str <- function(draw_mat, ...) {
  pars <- syms(...)
  return(gather_draws(draw_mat, !!!pars))
}

# Works:
gather_draws_str(draw_mat, "b") 

# Fails:
gather_draws_str(draw_mat, "a[i]") 
# ...with the following error message:
# Error in spread_draws_long_(tidy_draws(model), variable_names, dimension_names,  : 
# No variables found matching spec: a\[i\] 
mjskay commented 5 years ago

Yeah, it's fairly convoluted. :)

The issue is that syms() translates each string into a single symbol, whereas gather_draws() takes unevaluated code expressions (i.e., combinations of symbols). You are doing the equivalent of this:

par_str = "a[i]"
par = sym(par_str)

But that yields a single symbol whose value is "a[i]", i.e. an expression representing a single variable "a[i]" not a variable "a" being subscripted with index "i". You can inspect par to see that (sort of, it is subtle):

par
# `a[i]`

str(par)
# symbol a[i]

class(par)
# [1] "name"

as.list(par)
# [[1]]
# `a[i]`

The backticks around `a[i]` tell us it is being treated as a single atomic symbol and not an expression built up of multiple symbols. The output of as.list() confirms this.

The str2lang() function can be used to instead convert a string into an unevaluted expression. Something like this:

par_str = "a[i]"
par = str2lang(par_str)

Now when we inspect par it is an unevaluated expression:

par
# a[i]

str(par)
# language a[i]

class(par)
# [1] "call"

as.list(par)
# [[1]]
# `[`
# 
# [[2]]
# a
# 
# [[3]]
# i
#

The upshot is that syms() (which basically just applies sym() to every element of the given list) in your code needs to be replaced with something like a map() plus str2lang() to apply str2lang() to every input parameter, something like this:

gather_draws_str <- function(draw_mat, ...) {
  pars <- map(list(...), str2lang)
  return(gather_draws(draw_mat, !!!pars))
}

Which now gives the expected output with gather_draws_str(draw_mat, "a[i]").

Let me know if that helps / if I can clarify anything.

rgiordan commented 5 years ago

Fantastic, that was a great answer that solves my problem completely. Thank you!

mjskay commented 5 years ago

Wonderful, glad to help!