Skip to content

Commit

Permalink
Allow extra sources of pronouns in f_eval()
Browse files Browse the repository at this point in the history
  • Loading branch information
lionel- committed Mar 19, 2016
1 parent f7c54d1 commit 1a0f79c
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 4 deletions.
33 changes: 30 additions & 3 deletions R/f-eval.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@
#' \code{.env} and \code{.data}. These are thin wrappers around \code{.data}
#' and \code{.env} that throw errors if you try to access non-existent values.
#'
#' You can also provide additional sources of data with the
#' \code{explicit} argument. It should be a named list containing one
#' or several lists. Each of them will get a pronoun equal to their
#' name. Contrarily to \code{data}, sources in \code{explicit} are not
#' directly accessible. Therefore you have to explicitly qualify them
#' with the relevant pronoun to access these data.
#'
#' @param f A one-sided formula. Any expressions wrapped in \code{ (( )) } will
#' will be "unquoted", i.e. they will be evaluated, and the results inserted
#' back into the formula. See \code{\link{f_interp}} for more details.
#' @param data A list (or data frame).
#' @param explicit A named list of lists (or data frames).
#' @export
#' @examples
#' f_eval(~ 1 + 2 + 3)
Expand All @@ -36,6 +44,13 @@
#' f_eval(~ .data$cyl, mtcars)
#' f_eval(~ .env$cyl, mtcars)
#'
#' # Additional pronouns can be added by supplying explicit:
#' f_eval(~ .iris$Species, explicit = list(.iris = iris))
#'
#' # The data contained in these sources can only be accessed
#' # explicitly, through the pronoun. Direct access will fail:
#' \dontrun{f_eval(~ Species, explicit = list(.iris = iris))}
#'
#' # Imagine you are computing the mean of a variable:
#' f_eval(~ mean(cyl), mtcars)
#' # How can you change the variable that's being computed?
Expand All @@ -49,17 +64,29 @@
#' # take one more step of explicitness. Unfortunately data$((var)) is
#' # not valid R code, so we need to use the prefix for of `$`.
#' f_eval(~ mean( `$`(.data, ((var)) )), mtcars)
f_eval <- function(f, data = NULL) {
f_eval <- function(f, data = NULL, explicit = NULL) {
expr <- f_rhs(f_interp(f))

if (!is.null(data) && !is.list(data)) {
stop("`data` must be must be NULL, a list, or a data frame.", call. = FALSE)
}

if (!is.null(explicit)) {
if (is.null(names(explicit))) {
stop("`explicit` should be named", call. = FALSE)
}
if (!all(vapply(explicit, is.list, logical(1)))) {
stop("`explicit` should contain lists or data frames", call. = FALSE)
}
}

parent_env <- environment(f)
env <- new.env(parent = parent_env)
env$.env <- complain(parent_env)
env$.data <- complain(data)

sources <- c(.env = parent_env, .data = list(data), explicit)
for (source in names(sources)) {
env[[source]] <- complain(sources[[source]])
}

eval(expr, data, env)
}
18 changes: 17 additions & 1 deletion man/f_eval.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions tests/testthat/test-f-eval.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,17 @@ test_that("f_eval does quasiquoting", {
x <- 10
expect_equal(f_eval(~ ((quote(x)))), 10)
})

test_that("look into explicit pronouns", {
expect_equal(f_eval(~ .mtcars$cyl, explicit = list(.mtcars = mtcars)), mtcars$cyl)
})

test_that("data of explicit pronouns cannot be directly accessed", {
expect_error(f_eval(~ cyl, explicit = list(.data = mtcars)))
})

test_that("fails when explicit is malformed", {
expect_error(f_eval(~ ., explicit = list(mtcars)), "should be named")
expect_error(f_eval(~ ., explicit = list(pronoun = list)), "should contain lists")
})

0 comments on commit 1a0f79c

Please sign in to comment.