1
votes

For an R package I am designing I'd like to programmatically define a bunch of functions in a for loop. For example, I might want to do something like make new versions of base functions which always ignore NA values. Like:

for(f in c('mean', 'sd', 'median', 'quantile', 'max', 'min', 'range')) {
  cur = function() {}
  formals(cur) = formals(args(get(f)))
  body(cur) = parse(text = paste0(f, 
                                 '(', 
                                 names(formals(cur))[1], 
                                 ', na.rm = TRUE)'))
  assign(paste0(f, 'x'), cur)
}

This code creates seven new functions (meanx, sdx, medianx, quantilex, etc.) which simply call their eponymous functions (mean, sd, median, quantile, etc.) with na.rm = TRUE.

My problem is this: How can I use roxygen to document these seven functions? At very least, I'd like to @export them, but if I put a #' @export line into the loop, roxygen doesn't do anything with it. Obviously, I could write the names into the NAMESPACE file myself, but if I ever use roxygen again it will overwrite my changes.

Any ideas?

(Just to be clear, this is just an illustrative example. These seven functions are not really the functions I'm defining, so I'm not looking for advice on how to automatically use na.rm or anything. I'm interested in ideas for how to make roxygen export functions defined in a loop into the NAMESPACE file.)

2
It is not an answer yet but the documentation package will help alleviate this. It is work from the R Documentation Task Force, and will facilitate exporting dynamically generated functions. It should be ready by July 2017 for the R Users Conference. - Andrew Redd
Thanks @Andrew, I look forward to checking out the package! - user3474009

2 Answers

1
votes

I believe I've found a solution to my own problem! I should have looked through the roxygen documentation a little more thoroughly before posting the question.

It turns out you can feed a single #' @export command multiple names separated by spaces: #' @export func1 func2 func3 etc..

So my original problem can be solved as so:

for(f in c('mean', 'sd', 'median', 'quantile', 'max', 'min', 'range')) {
cur = function() {}
formals(cur) = formals(args(get(f)))
body(cur) = parse(text = paste0(f, 
                               '(', 
                               names(formals(cur))[1],
                              ', na.rm = TRUE)'))
assign(paste0(f, 'x'), cur)
}
#' @export meanx sdx medianx quantilex maxx minx rangex
NULL

(I'm pretty sure the NULL after the #' @export line is necessary.)

The only issue is that when I build the package in Rstudio, it gives a warning because it thinks things are being exported which don't exist. I don't know yet if this warning would be visible to users who load my package.

0
votes

If this is what you want to do I don't think you'll be able to easily use a loop. Unless you're using a loop in a build script to write out the functions and roxygen code. With that said you would have a lot of replicated code if you did this manually for each of your functions. So what you could do is make a function that takes a function as input and returns a function as it's output. The search term you would be interested in here if you wanted to learn more would be "closures". Here is an example from the ggplot2 codebase (GPL-2):

wrap_hmisc <- function(fun) {

  function(x, ...) {
    if (!requireNamespace("Hmisc", quietly = TRUE))
      stop("Hmisc package required for this function", call. = FALSE)

    fun <- getExportedValue("Hmisc", fun)
    result <- do.call(fun, list(x = quote(x), ...))

    plyr::rename(
      data.frame(t(result)),
      c(Median = "y", Mean = "y", Lower = "ymin", Upper = "ymax"),
      warn_missing = FALSE
    )
  }
}
#' @export
#' @rdname hmisc
mean_cl_boot <- wrap_hmisc("smean.cl.boot")
#' @export
#' @rdname hmisc
mean_cl_normal <- wrap_hmisc("smean.cl.normal")
#' @export
#' @rdname hmisc
mean_sdl <- wrap_hmisc("smean.sdl")
#' @export
#' @rdname hmisc
median_hilow <- wrap_hmisc("smedian.hilow")

Basically you create a function which returns a function and then you can use that to manually go over all the functions you want to modify and add the relevant roxygen documentation. I don't see a way around doing the manual creation and manual roxygen documentation writing unless you want a build script to take care of that for you.