A part of my code for ggplot is stored in a character vector. I would like to use this code as an additional geoms for my ggplot.
Example1:
DF=data.frame(x=seq(1:10), y=seq(1:20))
a='geom_line()'# This is a string that should be converted to RCode
So far I tried:
ggplot(DF, aes(x,y))+geom_point()+a
Error: Don't know how to add a to a plot
ggplot(DF, aes(x,y))+geom_point()+as.name(a)
Error: Don't know how to add as.name(a) to a plot
ggplot(DF, aes(x,y))+geom_point()+eval(parse(text=a))
Error in geom_line() + geom_line(y = 1) :
non-numeric argument to binary operator
ggplot(DF, aes(x,y))+geom_point()+deparse(substitute(a))
Error: Don't know how to add deparse(substitute(a)) to a plot
Example 2:
DF=data.frame(x=seq(1:10), y=seq(1:20))
a='geom_line()+geom_line(y=1)'
Probable you are wondering, why I would like to do that in a first place? In a for loop, I created expressions and stored them in a list as characters. Later, I pasted together all expressions into a single string expression. Now, I would like to add this string to a ggplot command. Any suggestions?
Edit: Example 1 was successfully solved. But Example 2 stays unsolved.
the parse function has text argument you need to pass a to. Try:
ggplot(DF, aes(x,y)) + geom_point() + eval(parse(text = a))
More info here:
http://adv-r.had.co.nz/Expressions.html#parsing-and-deparsing
In case of multiple statements, it is possible to deparse the original expression, add the new and then evaluate as a whole
original <- deparse(quote(ggplot(DF, aes(x,y)) + geom_point()))
new_call <- paste(original, '+', a)
eval(parse(text = new_call))
You can also use a function to define these code into a list. Please see: https://homepage.divms.uiowa.edu/~luke/classes/STAT4580/dry.html
Here I cited the related code:
Defining a theme_slopegraph function to do the theme adjustment allows
the adjustments to be easily reused:
theme_slopechart = function(toplabels = TRUE) {
thm <- theme(...)
list(thm, ...) # add multiple codes
#...
}
p <- basic_barley_slopes ## from twonum.R
p + theme_slopechart()
Related
I was trying to generate an expression in a separate function. To prevent repetitive code. The expression is then passed onto ggplot.
This works fine (a simple expression)
ggplot(mpg, aes(x = model, y = displ)) + geom_boxplot()
This works fine too (an expression with formula and string)
x = "displ"
xVar = expr(!!ensym(x) * 2)
ggplot(mpg, aes(x = model, y = !!xVar)) + geom_boxplot()
This doesn't work (an expression generated in a formula)
makeExpression = function(varName){
return(expr(!!ensym(varName) * 2))
}
xVar = makeExpression(x)
ggplot(mpg, aes(x = model, y = !!xVar)) + geom_boxplot()
This works (an expression generated in a formula with a dirty hack)
makeExpression = function(varName){
a = varName
return(expr(!!ensym(varName) * 2))
}
xVar = makeExpression(x)
ggplot(mpg, aes(x = model, y = !!xVar)) + geom_boxplot()
The third example gives the following error:
*Error in x * 2 : non-numeric argument to binary operator*
This means that "x", which was supplied to the function is not evaluated. x holds a string of a column name. The expression should have been: displ * 2
Funnily 4th example also works after accessing varName once within the function. Note that a is never used.
I don't understand why this is the case. It looks like a dirty hack to me but it probably has something to do with the scoping. Is there any cleaner way of doing this?
If you want to pass a variable that contains a name as a character value, then you want to use sym rather than ensym. sym will evaluate the parameter to pass to it to get the value. ensym is meant only to be used in function to capture expressions passed to function. Note that this magic only works when the parameter is still in the "promise" state and hasn't been evaluated by any previous code in the function. This is why the 4th example "works" because that extra code forces the promise to be evaluated.
I have a function to generate scatter plots from data, where an argument is provided to select which column to use for coloring the points. Here is a simplified version:
library(ggplot2)
plot_gene <- function (df, gene) {
ggplot(df, aes(x, y)) +
geom_point(aes_string(col = gene)) +
scale_color_gradient()
}
where df is a data.frame with columns x, y, and then a bunch of gene names. This works fine for most gene names; however, some have dashes and these fail:
print(plot_gene(df, "Gapdh")) # great!
print(plot_gene(df, "H2-Aa")) # Error: object "H2" not found
It appears the gene variable is getting parsed ("H2-Aa" becomes H2 - Aa). How can I get around this? Is there a way to indicate that a string should not go through eval in aes_string?
Reproducible Input
If you need some input to play with, this fails like my data:
df <- data.frame(c(1,2), c(2,1), c(1,2), c(2,1))
colnames(df) <- c("x", "y", "Gapdh", "H2-Aa")
For my real data, I am using read.table(..., header=TRUE) and get column names with dashes because the raw data files have them.
Normally R tries very hard to make sure you have column names in your data.frame that can be valid variable names. Using non-standard column names (those that are not valid variable names) will lead to problems when using functions that use non-standard evaluation type syntax. When focused to use such variable names you often have to wrap them in back ticks. In the normal case
ggplot(df, aes(x, y)) +
geom_point(aes(col = H2-Aa)) +
scale_color_gradient()
# Error in FUN(X[[i]], ...) : object 'H2' not found
would return an error but
ggplot(df, aes(x, y)) +
geom_point(aes(col = `H2-Aa`)) +
scale_color_gradient()
would work.
You can paste in backticks if you really want
geom_point(aes_string(col = paste0("`", gene, "`")))
or you could treat it as a symbol from the get-go and use aes_q instread
geom_point(aes_q(col = as.name(gene)))
The latest release of ggplot support escaping via !! rather than using aes_string or aes_q so you could do
geom_point(aes(col = !!rlang::sym(gene)))
I hope I can get a contextual clue as to what may be wrong here without providing data frame, but can if necessary, but ultimately I want to utilize lapply to create multiple boxplots across multiple Ys and same X, but get the following error, but Termed is definitely in my CMrecruitdat data.frame:
Error in aes_string(x = Termed, y = RecVar, fill = Termed) :
object 'Termed' not found
RecVar <- CMrecruitdat[,c("Req.Open.To.System.Entry", "Req.Open.To.Hire", "Tenure")]
BP <- function (RecVar){
require(ggplot2)
ggplot(CMrecruitdat, aes_string(x=Termed, y=RecVar, fill=Termed))+
geom_boxplot()+
guides(fill=false)
}
lapply(RecVar, FUN=BP)
If you use aes_string, you should pass strings rather than vectors and use strings for all your fields.
RecVar <- CMrecruitdat[,c("Termed", "Req.Open.To.System.Entry", "Req.Open.To.Hire", "Tenure")]
BP <- function (RecVar){
require(ggplot2)
ggplot(RecVar, aes_string(x="Termed", y=RecVar, fill="Termed"))+
geom_boxplot()+
guides(fill=false)
}
lapply(names(RecVar), FUN=BP)
I have written a function to plot a bar graph. But when I get to facet wrap the '~' sign is making things difficult.
rf.funct <- function(dat, predictor, feature){
ggplot(get(dat), aes(get(predictor), N)) +
geom_bar(stat = 'identity') +
facet_wrap(get(~feature)) # this is where the problem is
}
I've tried the following:
facet_wrap((get(~feature))) # invalid first argument
facet_wrap(paste0("~ ", get(feature))) # object 'feature' not found
How do i make sure the '~' sign gets included with the function?
You don't need to use get. You've passed the data frame into the function using the dat argument, so just feed dat to ggplot and it will have the data from within its environment.
rf.funct <- function(dat, predictor, feature) {
ggplot(dat, aes_string(predictor, "N")) +
geom_bar(stat = 'identity') +
facet_wrap(feature)
}
The predictor and feature arguments should be entered as strings. Then you can use aes_string to specify the aesthetics. facet_wrap can now take a character vector directly, without need of a formula (as pointed out by #WeihuangWong).
I was having a similar problem and the answers & comments on here helped me fix it. However, this post is about 6 years old now, and I think the most modern solution would be along these lines:
rf.funct <- function(dat, predictor, feature){
ggplot(dat, aes({{predictor}}, N)) +
geom_bar(stat = 'identity') +
facet_wrap(enquo(feature))
}
I am trying to write a function that calls ggplot with varying arguments to the aes:
hmean <- function(data, column, Label=label){
ggplot(data,aes(column)) +
geom_histogram() +
facet_wrap(~Antibody,ncol=2) +
ggtitle(paste("Mean Antibody Counts (Log2) for ",Label," stain"))
}
hmean(Log2Means,Primary.Mean, Label="Primary")
Error in eval(expr, envir, enclos) : object 'column' not found
Primary.Mean is the varying argument (I have multiple means). Following various posts here I have tried
passing the column name quoted and unquoted (which yieds either an "unexpected string constant" or the "object not found error)
setting up a local ennvironment (foo <-environment() followed by a environment= arg in ggplot)
creating a new copy of the data set using a data2$column <- data[,column]
None of these appear to work within ggplot. How do I write a function that works?
I will be calling it with different data.frames and columns:
hmean(Log2Means, Primary.mean, Label="Primary")
hmean(Log2Means, Secondary.mean, Label="Secondary")
hmean(SomeOtherFrame, SomeColumn, Label="Pretty Label")
You example is not reproducible, but likely this will work:
hmean <- function(data, column, Label=label){
ggplot(data, do.call("aes", list(y = substitute(column))) ) +
geom_histogram() +
facet_wrap(~Antibody,ncol=2) +
ggtitle(paste("Mean Antibody Counts (Log2) for ",Label," stain"))
}
hmean(Log2Means,Primary.Mean, Label="Primary")
If you need more arguments to aes, do like this:
do.call("aes", list(y = substitute(function_parameter), x = quote(literal_parameter)))
You could try this:
hmean <- function(data, column, Label=label){
# cool trick?
data$pColumn <- data[, column]
ggplot(data,aes(pColumn)) +
geom_histogram() +
facet_wrap(~Antibody,ncol=2) +
ggtitle(paste("Mean Antibody Counts (Log2) for ",Label," stain"))
}
hmean(Log2Means,'Primary.Mean', Label="Primary")
I eventually got it to work with an aes_string() call: aes_string(x=foo, y=y, colour=color), wehre y and color were also defined externally to ggplot().