Alex
Alex

Reputation: 2780

Parsing a formula

I have the following function, gigl, where I am trying to capture the variables on the left and right of |. Currently my code only captures the variables if it is named exactly s or n.

How can I generalize the following to evaluate any variable regardless of the name?

gigl <- function(form,data){

  s <- rlang::f_lhs(f_lhs(form))
  n <- rlang::f_rhs(f_lhs(form))
  s <- eval_tidy(data$s) # don't like that I have to use the same name as inputed. Make more general.
  n <- eval_tidy(data$n) # don't like that I have to use the same name as inputed. Make more general.
  output <- tibble(n,s) %>% data.matrix()
  output
  }

fit <- gigl(s | n ~ 1 , data=df)

Here is some toy data

library(tidyverse)
df <- tribble(
  ~n, ~s,
  10, 6,
  8, 7,
  6, 5
)

The following should work as above, but is currently not working

df2 <- tribble(
  ~total, ~positive,
  10, 6,
  8, 7,
  6, 5
)

fit <- gigl(total | positive ~ 1 , data=df2)

The output should be

      total  positive
[1,] 10       6
[2,]  8       7
[3,]  6       5

Upvotes: 0

Views: 131

Answers (1)

Mikko Marttila
Mikko Marttila

Reputation: 11878

Here's one way to do it. However, there's a lot of ways to break this implementation, and you would need to think about what you want to do if the input formula is not exactly in the format you expect. For example, I added a little check to make sure there actually is a | on the left hand side.

library(tidyverse)
library(rlang)

gigl <- function(form, data) {

  # Get the left hand side expression from the formula
  lhs <- f_lhs(form)

  # Check that the lhs actually has a `|`
  stopifnot(is.call(lhs), as_string(lhs[[1]]) == "|")

  # Get the expressions from either side of `|` in a list of length 2
  exprs <- as.list(lhs[-1])

  # Create names from them
  names <- map(exprs, expr_name)

  # Evaluate them to get the values
  values <- map(exprs, eval_tidy, data)

  # Construct the data frame
  df <- invoke(data.frame, set_names(values, names))

  # Return
  data.matrix(df)
}

Check that it works:

df2 <- tribble(
  ~total, ~positive,
  10, 6,
  8, 7,
  6, 5
)

gigl(total | positive ~ 1 , data = df2)
#>      total positive
#> [1,]    10        6
#> [2,]     8        7
#> [3,]     6        5

Or you could have a more succint solution using !!! with select:

gigl2 <- function(form, data) {
  lhs <- f_lhs(form)

  stopifnot(is.call(lhs))
  stopifnot(as_string(lhs[[1]]) == "|")

  exprs <- as.list(lhs[-1])
  df <- select(data, !!!exprs)

  data.matrix(df)
}

gigl2(total | positive ~ 1 , data = df2)
#>      total positive
#> [1,]    10        6
#> [2,]     8        7
#> [3,]     6        5

Created on 2018-02-19 by the reprex package (v0.2.0).

Upvotes: 1

Related Questions