passing parameters to ggplot - 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)
}

How to pass variables from lm object to aes(x =, y = )?

Context: I have a function that takes an object created with stats::lm() as its main argument. The goal of this function is to make ggplots with only this lm object. Warning: variables used in the model are NOT arguments of the function i.e. if the model is lmobj <- lm(y ~ x, data = df) then the function only takes lmobj as an argument. Indeed it is different from question like this one. Moreover, I am not looking for "ggplot only" solutions that take the raw data and compute regression line and scatterplot (e.g. ggplot2::geom_smooth(method = "lm")).
Problem: ggplot() geom functions have a x and y argument that require unquoted variables (see reference manual); how can I recover these from lmobj?
Expected output:
library(ggplot2)
lmobj <- lm(Petal.Width ~ Petal.Length, data = iris)
myfun <- function(.lm) {
# make a scatterplot with .lm using ggplot
ggplot(data = .lm[["model"]], aes(x = Petal.Width, y = Petal.Length)) +
geom_point()
}
myfun(lmobj)
Trials and errors
I tried to grab an unquoted variable name from lmobject using cat():
> cat(names(lmobj[["model"]][2]))
Petal.Length
But it creates an error:
> myfuntest <- function(.lm) {
+ # make a scatterplot with .lm using ggplot
+ ggplot(data = .lm[["model"]], aes(x = cat(names(.lm[["model"]][2])),
+ y = cat(names(.lm[["model"]][1])))) +
+ geom_point()
+ }
> myfuntest(lmobj)
Petal.LengthPetal.WidthPetal.LengthPetal.WidthError: geom_point requires the following missing aesthetics: x and y
The following works:
myfun <- function(model) {
coefs <- names(model$model)
ggplot(data = model$model) +
aes(x = !! rlang::sym(coefs[1L]), y = !! rlang::sym(coefs[2L]))) +
geom_point()
}
The relevant point here is that aes uses ‘rlang’s tidy evaluation and as such requires the arguments to be injected via !! as names.
One way to do it is to evaluate first the arguments of aes as symbols and then call aes by wrapping it into a do.call
myfun <- function(.lm) {
ggplot(data = .lm[["model"]],
do.call(aes, list(x = sym(names(.lm[["model"]])[2]),
y = sym(names(.lm[["model"]])[1])))) +
geom_point()
}

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) , "()")))

Using ggplot2 in a function [duplicate]

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