Ewen
Ewen

Reputation: 1381

Passing weights to glm() using rlang

I want to pass weights to glm() via a function without having to use the eval(substitute()) or do.call() methods, but using rlang.

This describes a more complicated underlying function.

# Toy data
mydata = dplyr::tibble(outcome = c(0,0,0,0,0,0,0,0,1,1,1,1,1,1),
                                group = c(0,1,0,1,0,1,0,1,0,1,0,1,0,1),
                                wgts = c(1,1,1,1,1,1,1,1,1,1,1,1,1,1)
)

# This works
glm(outcome ~ group, data = mydata)                             

# This works
glm(outcome ~ group, data = mydata, weights = wgts)                             

library(rlang)
# Function not passing weights
myglm <- function(.data, y, x){
    glm(expr(!! enexpr(y) ~ !! enexpr(x)), data = .data)
}

# This works
myglm(mydata, outcome, group)

# Function passing weights
myglm2 <- function(.data, y, x, weights){
    glm(expr(!! enexpr(y) ~ !! enexpr(x)), `weights = !! enexpr(weights)`, data = .data)
}

# This doesn't work
myglm2(mydata, outcome, group, wgts)

(Ticks are to highlight).

I know the weights argument here is wrong, I have tried many different ways of doing this all unsuccessfully. The actual function will be passed to a version of purrr:map() or purrr:invoke(), which is why I want to avoid a simple do.call(). Thoughts greatly appreciated.

Upvotes: 3

Views: 846

Answers (1)

Artem Sokolov
Artem Sokolov

Reputation: 13691

The issue is that glm() can recognize an expression being provided to its weights argument, but doesn't support quasiquotation, because it uses the base quote() / substitute() / eval() mechanisms instead of rlang. This causes problems for nested expression arithmetic.

One way to get around it is to compose the entire glm expression, then evaluate it. You can use ... to supply optional arguments.

myglm2 <- function( .data, y, x, weights, ... ) {
  myglm <- expr( glm(!!enexpr(y) ~ !!enexpr(x), data=.data, 
                      weights = !!enexpr(weights), ...) )
  eval(myglm)
}

myglm2(mydata, outcome, group)
# Call:  glm(formula = outcome ~ group, data = .data)

myglm2(mydata, outcome, group, wgts)
# Call:  glm(formula = outcome ~ group, data = .data, weights = wgts)

myglm2(mydata, outcome, group, wgts, subset=7:10)
# Call:  glm(formula = outcome ~ group, data = .data, weights = wgts, 
#     subset = ..1)
# While masked as ..1, the 7:10 is nevertheless correctly passed to glm()

To follow @lionel's suggestion, you can encapsulate the expression composition / evaluation into a standalone function:

value <- function( e ) {eval(enexpr(e), caller_env())}

myglm2 <- function( .data, y, x, weights, ... ) {
  value( glm(!!enexpr(y) ~ !!enexpr(x), data=.data, 
              weights = !!enexpr(weights), ...) )
}

Upvotes: 3

Related Questions