Using ggplot2 in a function [duplicate] - r

would like to create a function that generates graphs using ggplot. For the sake of simplicity, the typical graph may be
ggplot(car, aes(x=speed, y=dist)) + geom_point()
The function I would like to create is of the type
f <- function(DS, x, y) ggplot(DS, aes(x=x, y=y)) + geom_point()
This however won't work, since x and y are not strings. This problem has been noted in previous SO questions (e.g., this one), but without providing, in my view, a satisfactory answer. How would one modify the function above to make it work with arbitrary data frames?

One solution would be to pass x and y as string names of columns in data frame DS.
f <- function(DS, x, y) {
ggplot(DS, aes_string(x = x, y = y)) + geom_point()
}
And then call the function as:
f(cars, "speed", "dist")
However, it seems that you don't want that? Can you provide an example why you would need different functionality? Is it because you don't want to have the arguments in the same data frame?

I think it's possible the following type of codes, which only build the aes component.
require(ggplot2)
DS <- data.frame(speed=rnorm(10), dist=rnorm(10))
f <- function(DS, x, y, geom, opts=NULL) {
aes <- eval(substitute(aes(x, y),
list(x = substitute(x), y = substitute(y))))
p <- ggplot(DS, aes) + geom + opts
}
p <- f(DS, speed, dist, geom_point())
p
However, it seems to be complicated approach.

Another option is to use do.call. Here is a one line copy paste from a working code:
gg <- gg + geom_rect( do.call(aes, args=list(xmin=xValues-0.5, xmax=xValues+0.5, ymin=yValues, ymax=rep(Inf, length(yValues))) ), alpha=0.2, fill=colors )

One approach that I can think of is using match.call() to reach the variable names contained by the parameters/arguments passed to the custom plotting function and then use eval() on them. In this way you avoid passing them as quoted to your custom function, if you do not like that.
library(ggplot2)
fun <- function(df, x, y) {
arg <- match.call()
ggplot(df, aes(x = eval(arg$x), y = eval(arg$y))) + geom_point()
}
fun(mpg, cty, hwy) # no need to pass the variables (column names) as quoted / as strings

Related

Use tidy evaluation when passing an expression as a character to ggplot2::aes()

I am trying to convert my use of ggplot2 in functions to using tidy evaluation so as to avoid the warning messages that are evaluated. I particular, I have extensively used aes_string() and these need to be converted to aes(). I can handle cases when just the name of a column is passed as a characater. However, I have been unable to work how to deal with the case when the character is a mathematical expression.
Here is a small reproducible example of the problem that I an trying to solve.
library(ggplot2)
set.seed(1)
dat <- data.frame(x=rnorm(100),y=rnorm(100))
xvar <- 'x+100'
yvar <- 'y'
#This works but uses the deprecated aes_string
ggplot(dat,aes_string(x=xvar,y=yvar))
#This works
ggplot(dat, ggplot2::aes(x=x+100, y=.data[[yvar]])) + geom_point()
#This does not work
ggplot(dat, aes(x={{xvar}}, y=.data[[yvar]])) + geom_point()
My question is what tidy evaluation techniques do I need to employ to use xvar to specify the x variable as is possible with aes_string()?
You could use eval(str2expression()):
library(ggplot2)
ggplot(dat, aes(x = eval(str2expression(xvar)), y = eval(str2expression(yvar)))) +
geom_point() +
labs(x = xvar, y = yvar)
Or using the analogous rlang functions:
library(rlang)
ggplot(dat, aes(x = eval_tidy(parse_expr(xvar)), y = eval_tidy(parse_expr(yvar)))) +
geom_point() +
labs(x = xvar, y = yvar)
Note you’ll want to manually set the axis labels using labs(); otherwise you’ll end up with e.g. "eval(str2expression(xvar))" for the x axis.
It may need parse_expr/eval
library(ggplot2)
ggplot(dat, aes(x=eval(rlang::parse_expr(xvar)), y=.data[[yvar]])) +
geom_point() +
xlab(xvar)
-output
Or another option would be to interpolate and do the eval/parse
eval(parse(text = glue::glue("ggplot(dat, aes(x = {xvar}, y = {yvar}))",
"+ geom_point()")))

Dynamically change ggplot depending on the arguments passed to a function in R

I am currently writing a function which ultimately returns a ggplot. I would like to offer the user the option to change different aspects of that ggplot, such as the xlab by specifying it in the function. Right now I am using a code like this.
library(tidyverse)
d <- sample_n(diamonds,500)
plot_something <- function(data,x,y,x_axis_name=NULL){
if(is.null(x_axis_name)){
p<-ggplot(d,aes_string(x=x,y=y))+
geom_point()
} else{
p<-ggplot(d,aes_string(x=x,y=y))+
geom_point()+
xlab(x_axis_name)
}
return(p)
}
plot_something(data=d, x="depth", y="price",x_axis_name = "random_name")
This works fine, but as you can see a lot of the code is duplicated, the only difference is the xlab argument. In this case it is not too bad, but my actual function is much more complicated and things get more difficult if I would also allow the user to for example modify the ylab.
So my question is, if there is a more elegant way to modify ggplots inside of a function depending on arguments passed by the user.
Any help is much appreciated!
There is no need for the duplicated code. You could conditionally add layers to a base plot as desired like so:
library(tidyverse)
d <- sample_n(diamonds, 500)
plot_something <- function(data, x, y, x_axis_name = NULL) {
x_lab <- if (!is.null(x_axis_name)) xlab(x_axis_name)
p <- ggplot(d, aes_string(x = x, y = y)) +
geom_point() +
x_lab
return(p)
}
plot_something(data = d, x = "depth", y = "price", x_axis_name = "random_name")
Or using ggplot2s built-in mechanisms to choose defaults you don't even need an if condition but could do:
plot_something <- function(data, x, y, x_axis_name = ggplot2::waiver()) {
p <- ggplot(d, aes_string(x = x, y = y)) +
geom_point() +
xlab(x_axis_name)
return(p)
}

Select ggtheme randomly

I would like to draw a ggplot with a random theme (In fact, I want to draw many plots, each with a different theme). Consider the following reproducible example:
# Exmple data
df <- data.frame(x = 1:10, y = 1:10)
# Select theme randomly
random_theme <<- sample(c("theme_gray", "theme_bw", "theme_light", "theme_dark", "theme_minimal", "theme_classic"), 1)
# Draw ggplot
ggplot(df, aes(x, y)) +
geom_line() +
random_theme # This line does not work
Question: How can I select a ggtheme randomly?
Sample from the functions and not the names of the functions. Also, sample returns a list when sampling from anything more complex than a scalar, so you need the first list element. Eg:
> sample(c(sqrt, sqrt),2)
[[1]]
function (x) .Primitive("sqrt")
[[2]]
function (x) .Primitive("sqrt")
So get a random theme function with:
random_theme <- sample(c(theme_gray, theme_bw, theme_light, theme_dark, theme_minimal, theme_classic), 1)[[1]]
and call it when you plot:
ggplot(df, aes(x, y)) +geom_line() + random_theme()
Resample random_theme and plot again to update.
Also, you probably don't need the <<- which I guess is a hangover from desperately trying to make something work...
You could do this with match.fun():
random_theme = match.fun(sample(c("theme_gray", "theme_bw", "theme_light", "theme_dark", "theme_minimal", "theme_classic"), 1))
ggplot(df, aes(x, y)) +
geom_line() +
random_theme()
Sice your random_theme is a character vector, you can use eval and then parse to parse your theme.
library(tidyverse)
ggplot(df, aes(x, y)) +
geom_line() +
eval(parse(text = paste0(random_theme, "()")))
Or more directly:
ggplot(df, aes(x, y)) +
geom_line() +
eval(parse(text = paste0(sample(c("theme_gray",
"theme_bw",
"theme_light",
"theme_dark",
"theme_minimal",
"theme_classic"), 1) , "()")))

How to pass a vector to the dots (...) argument of a function

Several R functions have and arguement ... that allows you to pass an arbitrary number of arguments. A example of this is the paste function, to which you can provide an arbitrary number of arguements. But sometimes, you don't know ahead of time how many arguements you want to pass.
For example, say I want to produce a plot in ggplot, where I want to color points by the combination of two columns:
df <- data.frame(x=rnorm(100),
y=rnorm(100),
cat1=sample(c(TRUE, FALSE), 100),
cat2=sample(c(TRUE, FALSE), 100),
cat3=sample(c(TRUE, FALSE), 100))
ggplot(df) + aes(x=x, y=y, col=paste(cat1,cat2) + geom_point()
But now consider that I want to the list of columns to be colour by to be determined at run-time. I would like to write a function that did something like:
library(rlang)
color_plot <- function(df, color_by) {
color_by = lapply(color_by, sym)
ggplot(df) + aes(x=x, y=y, col=paste(...=color_by)) + geom_point()
}
color_plot(df, list("cat1"))
color_plot(df, list("cat2", "cat3"))
color_plot(df, list("cat1", "cat2", "cat3"))
I guess i'm look for something equivalent to pythons *args as in:
args =[1,2,3]
my_fun(*args)
Use syms:
color_plot <- function(df, color_by) {
color_by <- syms(color_by)
ggplot(df) + aes(x=x, y=y, col=paste(!!!color_by)) + geom_point()
}
Another method would be to use quos if you prefer passing in unquoted column names instead of a list:
library(ggplot2)
library(rlang)
color_plot <- function(df, ...) {
color_by = quos(...)
ggplot(df) + aes(x=x, y=y, col=paste(!!!color_by)) + geom_point()
}
color_plot(df, cat1, cat2, cat3)

passing parameters to ggplot

would like to create a function that generates graphs using ggplot. For the sake of simplicity, the typical graph may be
ggplot(car, aes(x=speed, y=dist)) + geom_point()
The function I would like to create is of the type
f <- function(DS, x, y) ggplot(DS, aes(x=x, y=y)) + geom_point()
This however won't work, since x and y are not strings. This problem has been noted in previous SO questions (e.g., this one), but without providing, in my view, a satisfactory answer. How would one modify the function above to make it work with arbitrary data frames?
One solution would be to pass x and y as string names of columns in data frame DS.
f <- function(DS, x, y) {
ggplot(DS, aes_string(x = x, y = y)) + geom_point()
}
And then call the function as:
f(cars, "speed", "dist")
However, it seems that you don't want that? Can you provide an example why you would need different functionality? Is it because you don't want to have the arguments in the same data frame?
I think it's possible the following type of codes, which only build the aes component.
require(ggplot2)
DS <- data.frame(speed=rnorm(10), dist=rnorm(10))
f <- function(DS, x, y, geom, opts=NULL) {
aes <- eval(substitute(aes(x, y),
list(x = substitute(x), y = substitute(y))))
p <- ggplot(DS, aes) + geom + opts
}
p <- f(DS, speed, dist, geom_point())
p
However, it seems to be complicated approach.
Another option is to use do.call. Here is a one line copy paste from a working code:
gg <- gg + geom_rect( do.call(aes, args=list(xmin=xValues-0.5, xmax=xValues+0.5, ymin=yValues, ymax=rep(Inf, length(yValues))) ), alpha=0.2, fill=colors )
One approach that I can think of is using match.call() to reach the variable names contained by the parameters/arguments passed to the custom plotting function and then use eval() on them. In this way you avoid passing them as quoted to your custom function, if you do not like that.
library(ggplot2)
fun <- function(df, x, y) {
arg <- match.call()
ggplot(df, aes(x = eval(arg$x), y = eval(arg$y))) + geom_point()
}
fun(mpg, cty, hwy) # no need to pass the variables (column names) as quoted / as strings

Resources