match.fun with a parameter, for use with scales

Hi all,

I have a program with many graph-making functions that encapsulate a lot of complexity. This makes it easier to loop over a large number of graphs that need generated. I pass to these functions (among other things) the name of a scale function. The scale function is one of three from the scales package: comma, dollar, or percent. I use match.fun to convert the string to the needed function.

This mostly works well, but the default behavior of the accuracy parameter of the scales functions is not behaving like I want, and I would like to set a value for it. Is there a simple way to pass a parameter value while using something like match.fun?

A simplified version of what I'm doing looks like this:

scale_fn_name <- 'percent'  # actually is passed as a function parameter
scale_fn <- match.fun(scale_fn_name)  # somewhere in the function body

And unfortunately this doesn't work because match.fun doesn't have an ... argument:

scale_fn <- match.fun(scale_fn_name, accuracy = 1)

The workaround I have now is to redefine the functions with the desired defaults, e.g.:

percent <- function(x, ...) {
  scales::percent(x, accuracy = 1, ...)
}

Is there a better way?

Does the code below cover your use case?

library(scales)

fnc = function(x, scale_fn_name, accuracy=1) {
  scale_fn = match.fun(scale_fn_name)
  scale_fn(x, accuracy=accuracy)
}

fnc(c(0.026, 1, 0.0105, 0.3107), percent)
#> [1] "3%"   "100%" "1%"   "31%"
fnc(c(0.026, 1, 0.0105, 0.3107)*1000, comma, 0.1)
#> [1] "26.0"    "1,000.0" "10.5"    "310.7"

Created on 2020-12-09 by the reprex package (v0.3.0)

The scale_fn gets passed to the labels argument of scale_y_continuous. Here's an example:

library(scales)
library(ggplot2)

my_dat <- as.data.frame(state.x77)

my_graph_maker <- function(dat, measure, scale_fn_name) {
  # If I understood quasiquotation better I wouldn't need this step
  dat$meas <- subset(dat, select = measure, drop = TRUE)

  scale_fn <- match.fun(scale_fn_name)
  
  ggplot(dat, aes(x = Population,
                  y = meas)) +
    geom_point() +
    scale_y_continuous(name = measure, labels = scale_fn)
}

my_graph_maker(my_dat, 'Income', 'dollar')

I suppose I could do:

scale_y_continuous(name = measure, labels = function(x) scale_fn(x, accuracy = 1))

but that's pretty kludgy too.

You could use the purrr::partial function to generate scale_fn with accuracy=1 as the default. I've also switched to tidyeval:

library(scales)
library(tidyverse)

my_dat <- as.data.frame(state.x77)

my_graph_maker <- function(dat, measure, scale_fn_name, accuracy=1) {
  
  scale_fn <- purrr::partial(match.fun(scale_fn_name), accuracy=accuracy)
  
  ggplot(dat, aes(x = Population,
                  y = {{measure}})) +
    geom_point() +
    scale_y_continuous(name = as_label(enquo(measure)), labels = scale_fn)
}

p1 = my_graph_maker(my_dat, Income, dollar) 
p2 = my_graph_maker(my_dat, Income, dollar, 0.1) 
p3 = my_graph_maker(my_dat, Illiteracy/100, percent, 0.1)

purrr::partial looks like exactly what I was looking for, thanks!

The tidyeval part doesn't quite match what I'm doing, I'm passing the parameters as strings, not object names (because I can loop over a vector of strings more easily, say for example if read in from a CSV specifying the job).

It took me a minute to figure out why scale_fn_name was working unquoted - it is because match.fun also accepts functions and simply returns them - but I need it to be able to work with quoted names. The function calls look more like this:

my_graph_maker(my_dat, 'Income', 'dollar')

Although I am not actually literally typing those strings, they come from the vectors I'm looping over.

But, the tidyeval part is beyond the scope for this question, I can climb that hill another day. Thank you for pointing out purrr::partial, I wasn't searching the right things to find that one!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.