Apply two transformations on one axis - r

I have found coord_trans, but I'd like to apply log10 and
reverse to my x-axis. I tried applying two transformation
ggplot(table) + aes(color=Vowel, x=F1, y=F2) + geom_point() + coord_trans(x="log10", y="log10") + coord_trans(x="reverse", y="reverse")
but only the first one was applied. So I tried linking them
ggplot(table) + aes(color=Vowel, x=F2, y=F1) + geom_point() + coord_trans(x=c("log10", "reverse"), y=c("log10", "reverse"))
Which gives me a plain error.
'c("log10_trans", "reverse_trans")' is not a function, character or symbol
How do I chain them?

You can define new transformations using trans_new.
library(scales)
log10_rev_trans <- trans_new(
"log10_rev",
function(x) log10(rev(x)),
function(x) rev(10 ^ (x)),
log_breaks(10),
domain = c(1e-100, Inf)
)
p <- ggplot(mtcars, aes(wt, mpg)) +
geom_point()
p + coord_trans(y = log10_rev_trans)

A quick and easy way is to apply one of the transformations directly to the data and use the other with the plot function.
e.g.
ggplot(iris, aes(log10(Sepal.Length), log10(Sepal.Width), colour = Species)) +
geom_point() + coord_trans(x="reverse", y="reverse")
Note: the reverse transformation does not work with the iris data but you get the idea.

I wandered in here looking for a 'composition of scales' function. I think one might be able to write such a thing as follows:
# compose transforms a and b, applying b first, then a:
`%::%` <- function(atrans,btrans) {
mytran <- scales::trans_new(name = paste(btrans$name,'then',atrans$name),
transform = function(x) { atrans$transform(btrans$transform(x)) },
inverse = function(y) { btrans$inverse(atrans$inverse(y)) },
domain = btrans$domain, # this could use improvement...
breaks = btrans$breaks, # not clear how this should work, tbh
format = btrans$format)
}
ph <- ggplot(mtcars, aes(wt, mpg)) +
geom_point() +
scale_y_continuous(trans=scales::reverse_trans() %::% scales::log10_trans())
print(ph)

Related

Pass arbitrary number of variables into facet_grid()

I'm trying to develop a function that will create a plot from just a few arguments. I need it to be flexible, so that I can input an unknown number of variables to facet the graph by. I've used all the quoting/unquoting methods I can find: {{ }}, .data[], !!!, inject, and more. I think part of the problem is that facet_grid calls it's variables within vars(), which is not cooperating with these operators. I have also tried passing the variables that I want to facet by into the function using dots, and by a named argument that takes a vector.
It seems to me that the best option here, based on this article (https://adv-r.hadley.nz/quasiquotation.html) is the unquote-splice operator. It seems to me like one of these should work, but neither does:
make_graph <- function (factor.by){
ggplot(mpg, aes(cyl, cty)) +
geom_col() +
facet_grid(cols = vars(!!!factor.by))
}
make_graph(factor.by = c("manufacturer", "model"))
make_graph_2 <- function (...){
ggplot(mpg, aes(cyl, cty)) +
geom_col() +
facet_grid(cols = vars(...))
}
make_graph_2("manufactuer", "model")
I would really appreciate any help figuring out how to make this work! I'm pretty new to programming with R, and I've been stuck on this issue for a long time. Thank you so much!
Some options:
Ellipses:
make_graph <- function(...) {
ggplot(mpg, aes(cyl, cty)) +
geom_col() +
facet_grid(cols = vars(...))
}
make_graph(manufacturer, model)
Programmatic formula:
make_graph <- function(factor.by) {
ggplot(mpg, aes(cyl, cty)) +
geom_col() +
facet_grid(rows = as.formula(paste0(". ~ ", paste(factor.by, collapse = "+"))))
}
make_graph(factor.by = c("manufacturer", "model"))
Using ensyms (thank you #stefan!), for a variadic function:
make_graph <- function(...) {
ggplot(mpg, aes(cyl, cty)) +
geom_col() +
facet_grid(cols = vars(!!!rlang::ensyms(...)))
}
make_graph("manufacturer", "model")
Using syms for a single-argument function:
make_graph <- function(factor.by) {
ggplot(mpg, aes(cyl, cty)) +
geom_col() +
facet_grid(cols = vars(!!!rlang::syms(factor.by)))
}
make_graph(c("manufacturer", "model"))

Using `facet_wrap` in a function with non-standard evaluation with vector valued arguments

I want to make a function that uses ggplot and facet_wrap and passes the function variables to facet by.
I can do the following using quoted arguments.
library(tidyverse)
my_grid_plot <- function(facet_by){
ggplot(mtcars, aes(wt, disp)) +
geom_point() +
facet_wrap(facet_by)
}
my_grid_plot(facet_by = 'am')
my_grid_plot(facet_by = 'vs')
my_grid_plot(facet_by = c('am', 'vs'))
However, I would like to do this using unquoted arguments, e.g.
# does not work yet; this is what I want
my_grid_plot(facet_by = am)
my_grid_plot(facet_by = vs)
my_grid_plot(facet_by = c(am, vs))
I know I can use vars with facet_wrap, e.g.
ggplot(mtcars, aes(wt, disp)) +
geom_point() +
facet_wrap(vars(am, vs))
And so I could do something like this
my_grid_plot2 <- function(facet_by){
ggplot(mtcars, aes(wt, disp)) +
geom_point() +
facet_wrap(vars({{facet_by}}))
}
# works fine
my_grid_plot2(am)
my_grid_plot2(vs)
But that won't work if facet_by is a vector.
# does not work; obscure error
my_grid_plot2(c(am, vs))
So is there a way to get what I want above, i.e. where facet_by takes a single variable name of vector of names?
I don't know if this is the right or best way, and it seems a bit hacky, but I think it does the job. Inspired by https://stackoverflow.com/a/57709169/1009979
my_grid_plot3 <- function(facet_by){
by_expr <- enexpr(facet_by)
if(length(by_expr) == 1) {
args_vars <- as.list(by_expr)
} else {
args_vars <- as.list(by_expr)[-1]
}
quoted_args_vars <- sapply(args_vars, quo_name)
ggplot(mtcars, aes(wt, disp)) +
geom_point() +
facet_wrap(quoted_args_vars)
}
# this works
my_grid_plot3(am)
my_grid_plot3(vs)
my_grid_plot3(c(am, vs))
One option is to let tidyselect do the heavy lifting:
my_grid_plot2 <- function(facet_by){
by <- rlang::enexpr(facet_by) %>%
tidyselect::eval_select(mtcars)
ggplot(mtcars, aes(wt, disp)) +
geom_point() +
facet_wrap(names(by))
}
## All of the following now work as expected
my_grid_plot2(am)
my_grid_plot2(vs)
my_grid_plot2(c(am, vs))
my_grid_plot2('am')
my_grid_plot2('vs')
my_grid_plot2(c('am', 'vs'))

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 do I pass multiple arguments through an R function to ggplot, both aesthetics and scale format?

How do I pass multiple arguments through to my ggplot function?
Here is an example of the plot I want to automate.
library(ggplot2)
library(scales)
p <- ggplot(diamonds, aes(x=cut, y=price) ) +
geom_boxplot() +
scale_y_continuous(labels = dollar)
p
But I want to graph multiple different variables and use the appropriate scale e.g. price, depth etc, some are in dollars.
So I made a function
myfunction <- function(var1,var2){
p <- ggplot(diamonds, aes(x=cut, y= var1) ) +
geom_boxplot() +
scale_y_continuous(labels = var2)
p
return(p)
}
When I test the function, it doesn't work. Both arguments cause different errors on their own.
myfunction("price","dollar")
For var1 I get:
Error: Discrete value supplied to continuous scale
and var2:
Error in f(..., self = self) : Breaks and labels are different lengths
Question 1. Why doesn't that work? This is the most important question for me.
I then wish to make multiple graphs, which I can do with a for loop, but I keep hearing I should do it with apply. Here's what I tried.
Question 2. How would you make the multiple graphs work with apply?
FirstPlotData <- c("price","dollar")
SecondPlotData <- c("depth", "comma")
plotMetaData <- data.frame(FirstPlotData,SecondPlotData)
lapply doesn't work for me with multiple arguments. Can it pass multiple arguments?
lapply(plotMetaData, function(avar,bvar)myfunction(avar, bvar))
Would mapply work? How?
mapply(mytestfunction,plotMetaData[1,],plotMetaDataList[2,])
Thanks in advance. I note that I could do the multiple graphs with facet, but for my more complex example, with hiding outliers, scaling, and also doing stats, then doing the multiple plots and putting in a {cowplot} grid seems easier.
Try this
library(ggplot2)
library(scales)
library(rlang) # for sym
myfunction <- function(var1,var2){
p <- ggplot(diamonds, aes(x=cut, y= !! sym(var1)) ) +
geom_boxplot() +
scale_y_continuous(labels = get(var2))
p
return(p)
}
myfunction('price','dollar')
You probably want aes_string. This function has been designed to make programming with ggplot easier (similar ideas have also been applied to dplyr commands). The following works:
library(tidyverse)
data(diamonds)
myfunction <- function(var1){
p <- ggplot(diamonds, aes_string(x="cut", y= var1) ) +
geom_boxplot()
p
return(p)
}
myfunction("price")
Why?
contrast the following:
# works
ggplot(diamonds, aes(x=cut, y= price) ) + geom_boxplot()
# these 2 are equivalent, but do not work
ggplot(diamonds, aes(x=cut, y= "price") ) + geom_boxplot()
var1 = "price"
ggplot(diamonds, aes(x=cut, y= var1) ) + geom_boxplot()
# these 2 are equivalent, both works but inputs are strings
ggplot(diamonds, aes_string(x="cut", y= "price") ) + geom_boxplot()
var1 = "price"
ggplot(diamonds, aes_string(x="cut", y= var1) ) + geom_boxplot()
Using apply?
For this purpose I would be inclined to use loops (others are welcome to disagree). If you are set on using an apply approach then you probably want apply as lapply, mapply, vapply and sapply are list-, multivariate-, vector- and simple-apply respectively.
A more ggplot way of doing this now, is using .data pronoun.
library(ggplot2)
myfunction <- function(var1, var2) {
p <- ggplot(diamonds, aes(x = cut, y = .data[[var1]])) +
geom_boxplot() +
scale_y_continuous(
labels = getFromNamespace(x = var2, ns = "scales")
)
p
return(p)
}
myfunction("price", "dollar")
myfunction("price", "comma")
Then to create multiple plots with these function by passing multiple arguments, a better and tidier approach is using map functions from the {purrr}
plots <- purrr::map2(
.x = c("price", "price"),
.y = c("dollar", "comma"),
.f = myfunction
)
So, plots[[1]] contains the 1st plot with var1 = "price" and var2 = "dollar" and plots[[2]] contains the 2nd plot with var1 = "price" and var2 = "comma".

ggplot line or point plotting conditionally

I'm using ggplot to write a bunch of plots inside a function. I want to pass another flag to the function so that I can choose while calling the function that whether to plot lines or points.
Currently I'm doing it like this:
plot2pdfHD = function(opdata, dir = 'plots'){
#... do something ...
plots <- list()
for (i in seq(strikes)){
#... do something ...
plots[[i]] <- ggplot(sset, aes(x = TIMESTAMP, y = value, col = optype)) +
geom_line() + labs(x = "Time", y = 'values') +
#... do something ...
}
pdf(paste0(dir, '/', Sys.Date(), '.pdf'), width=16, height=10)
for(i in seq(length(plots)))
tryCatch({print(plots[[i]])}, error = function(e) NULL)
dev.off()
}
I want to add a flag so that by setting appropriate value to the flag I can switch between geom_line() and geom_point() while calling the function.
Addition:
Can it be done without repeating the additional call part, i.e. #... do something ...? I am hoping for an answer that does that.
Also sset is a subset of the opdata.
Maybe this is what you're looking for? I like #arvi1000's answer---nice and clear---but you can put the if statement inside a single ggplot addition expression:
type = "line"
## also try with
# type = "point"
ggplot(mtcars, aes(x = wt, y = mpg)) + {
if(type == "point") geom_point() else geom_line()
} +
theme_bw()
For multiple layers, you could do something like this:
gg = ggplot(mtcars, aes(x = wt, y = mpg))
{
if(type == "point") {
gg + geom_point()
} else {
gg + geom_line() + stat_smooth()
}
} + theme_bw()
(Of course, adding the theme_bw() to the original gg definition would be cleaner, but this demonstrates that it can be added later.)
Plain old if block?
plot2pdfHD = function(opdata, dir = 'plots', plot_type){
plots <- list()
for (i in seq(strikes)) {
# base gg call
p <- ggplot(sset, aes(x = TIMESTAMP, y = value, col = optype))
# add geom layer based on content of argument:
if(plot_type == 'line') p <- p + geom_line()
if(plot_type == 'point') p <- p + geom_point()
# add additional params and assign into list item
plots[[i]] <- p + labs(x = "Time", y = 'values')
#...
}
# ...
}
Other notes:
I'm assuming you are doing something to make sset different before each call, otherwise you are going to get a list of identical plots
lapply might be better than a for loop here, esp since you are wanting a list object as the result anyway

Resources