How can I pass a ggplot2 aesthetic from a variable? - r

I'm having trouble passing a POSIXct stored in a variable as the xmin/xmax for geom_rect. I've tried to construct a free-standing example without trivializing what I'm trying to do...
The idea is to take a ggplot2 plot object, whose x is a POSIXt, and "zoom in" on a particular range in time. The zoom is in the top 80% and the entire series is in the bottom 20% with an indicator as to what part is zoom'd on top.
My problem is that I can't seem to get the xmin/xmax passed into geom_rect -- each thing I've tried (other than assembling the plot by hand instead of function) gives me a different error. I've tries using aes(), aes_string(), passing as parameters instead of aesthetics, passing just strings, etc.
The example below tells me:
Error in eval(expr, envir, enclos) : object 'lims' not found
I think my problem is that the variables I'm using to set the aesthetics aren't in scope when the aesthetics get processed, but I can't figure out how else to go about it. Help.
library(ggplot2)
subplot <- function(x, y) viewport(layout.pos.col=x, layout.pos.row=y)
vplayout <- function(x, y) {
grid.newpage()
pushViewport(viewport(layout=grid.layout(y,x)))
}
anm_zoom <- function(limits, p) {
lims <- as.POSIXct(limits)
limlab <- paste(lims, collapse=" to ")
top <- p + scale_x_datetime(limlab, limits=lims, expand=c(0,0))
bottom <- p;
bottom <- bottom + opts(title="")
bottom <- bottom + opts(legend.position="none")
bottom <- bottom + opts(axis.title.y=theme_blank())
bottom <- bottom + scale_x_datetime("", expand=c(0,0))
bottom <- bottom + geom_rect(aes(xmin=lims[1], xmax=lims[2]),
ymin=-Inf, ymax=Inf, fill="grey80", alpha=0.01)
## Render the plots
vplayout(1,5)
print(top, vp=subplot(1,c(1,2,3,4)))
print(bottom, vp=subplot(1,5))
}
pdate <- seq.POSIXt(from=as.POSIXct("2010-09-09 00:00"),
to=as.POSIXct("2010-09-10 23:59"), by="2 mins")
var1 <- rnorm(length(pdate))
var2 <- rnorm(length(pdate))
df1 <- data.frame(pdate, var1, var2)
dm <- melt(df1, id="pdate")
p <- ggplot(dm) + aes(x=pdate, y=value) + stat_summary(fun.y="sum", geom="line")
anm_zoom(c("2010-09-09 12:15", "2010-09-09 12:30"), p)

Hmmmm, I think you need a new aes function that is a bit like aes (in that it doesn't try to parse its arguments) and a bit like aes_string (in that it evaluates its arguments immediately in the local environment):
aes_now <- function(...) {
structure(list(...), class = "uneval")
}
Then
bottom <- bottom + geom_rect(aes_now(xmin=lims[1], xmax=lims[2]),
ymin=-Inf, ymax=Inf, fill="grey80", alpha=0.01)
gives you what you want.

Rewrite: from hadley's answer
Owing to the updates of newer version ggplot2 by hadley,
a more intuitive way dealing with non-std evaluation of ggplot() in function is using aes_q() as following:
xminName <- substitute(lims[1]); xmaxName <- substitute(lims[2])
bottom <- bottom +
geom_rect(aes_q(xmin=xminName, xmax=xmaxName),
ymin=-Inf, ymax=Inf, fill="grey80", alpha=0.01)

you just need to change the name of your function argument limits as i think it is creating a scoping conflict. i just changed it to limits1 and also the first line in your code to read lims = as.POSIXct(limits1) and it works perfectly. check it out!!

Related

Looping ggplot to create multiple plots (issue with geom_errorbar)

I'm using summarySE() to create the mean and standard error of my data set. This data is then plotted using ggplot und geom_errorbar. I'm trying to create plots for >20 different measurements. The loop works all right until I am adding geom_errorbar. It works allright when removing the loop and changing "i" with "pH" or whatever the heading is. Could someone please point me into the right direction?
colNames <- names(results)[5:11]
for(i in colNames){
results_summary <- summarySE(results, measurevar = i, groupvars=c("Plot","Treatment"), na.rm="TRUE")
results_summary$Plot <- factor(results_summary$Plot, levels = c("A","B","C"))
plt <- ggplot(results_summary, aes_string(x="Treatment", y = i, fill = "Plot")) +
geom_bar(position=position_dodge(), stat="identity", colour='black') +
facet_wrap(~Plot) +
geom_errorbar(aes(ymin=i-se, ymax=i+se), width=.2,position=position_dodge(.9))
print(plt)
Sys.sleep(2)
}
The error message is:
Error in i - se : non-numeric argument to binary operator

R geom_line not plotting as expected

I am using the following code to plot a stacked area graph and I get the expected plot.
P <- ggplot(DATA2, aes(x=bucket,y=volume, group=model, fill=model,label=volume)) + #ggplot initial parameters
geom_ribbon(position='fill', aes(ymin=0, ymax=1))
but then when I add lines which are reading the same data source I get misaligned results towards the right side of the graph
P + geom_line(position='fill', aes(group=model, ymax=1))
does anyone know why this may be? Both plots are reading the same data source so I can't figure out what the problem is.
Actually, if all you wanted to do was draw an outline around the areas, then you could do the same using the colour aesthetic.
ggplot(DATA2, aes(x=bucket,y=volume, group=model, fill=model,label=volume)) +
geom_ribbon(position='fill', aes(ymin=0, ymax=1), colour = "black")
I have an answer, I hope it works for you, it looks good but very different from your original graph:
library(ggplot2)
DATA2 <- read.csv("C:/Users/corcoranbarriosd/Downloads/porsche model volumes.csv", header = TRUE, stringsAsFactors = FALSE)
In my experience you want to have X as a numeric variable and you have it as a string, if that is not the case I can Change that, but this will transform your bucket into a numeric vector:
bucket.list <- strsplit(unlist(DATA2$bucket), "[^0-9]+")
x=numeric()
for (i in 1:length(bucket.list)) {
x[i] <- bucket.list[[i]][2]
}
DATA2$bucket <- as.numeric(x)
P <- ggplot(DATA2, aes(x=bucket,y=volume, group=model, fill=model,label=volume)) +
geom_ribbon(aes(ymin=0, ymax=volume))+ geom_line(aes(group=model, ymax=volume))
It gives me the area and the line tracking each other, hope that's what you needed
If you switch to using geom_path in place of geom_line, it all seems to work as expected. I don't think the ordering of geom_line is behaving the same as geom_ribbon (and suspect that geom_line -- like geom_area -- assumes a zero base y value)
ggplot(DATA2, aes(x=bucket, y=volume, ymin=0, ymax=1,
group=model, fill=model, label=volume)) +
geom_ribbon(position='fill') +
geom_path(position='fill')
Should give you

Ability to specify ggplot aesthetics to function that should return a ggplot object

I'm trying to contstuct a function that behaves like a ggplot function, and also returns a ggplot object that other functions may work on further (add facets, apply a theme, etc.).
The hurdle I am facing now is that I cannot get argument passning to the function to work like I would expect it to.
data(iris)
te <- function(data,x,y){
g <- ggplot(data,aes_q(x=quote(x),y=quote(y))) + scale_x_continuous() +
scale_y_continuous() + geom_point()
return(g)
}
te(iris,x=Species,y=Petal.Length)
What I get then is:
Error: geom_point requires the following missing aesthetics: x, y
I hoped that this would enable me to pass the arguments not as strings, but obviously I am doing something wrong here. The odd thing, for me, is that geom_point is the function that is complaining. How come?
Inside a function you need to use substitute instead of quote:
data(iris)
te <- function(data,x,y){
x <- substitute(x)
y <- substitute(y)
g <- ggplot(data,aes_q(x=x,y=y)) + scale_x_discrete() +
scale_y_continuous() + geom_point()
return(g)
}
te(iris,x=Species,y=Petal.Length)
This will work perfect.
P.S. I changed scale_x_continuous to scale_x_discrete because Species is discrete
Try quote when calling the function:
data(iris)
library(ggplot2)
te <- function(data,x,y){
g <- ggplot(data,aes_q(x,y)) + scale_x_continuous() +
scale_y_continuous() + geom_point()
return(g)
}
te(iris,x=quote(Species),y=quote(Petal.Length))

How to deal with "data of class uneval" error from ggplot2?

While trying to overlay a new line to a existing ggplot, I am getting the following error:
Error: ggplot2 doesn't know how to deal with data of class uneval
The first part of my code works fine. Below is an image of "recent" hourly wind generation data from a Midwestern United States electric power market.
Now I want to overlay the last two days worth of observations in Red. It should be easy but I cant figure out why I am getting a error.
Any assistance would be greatly appreciated.
Below is a reproducible example:
# Read in Wind data
fname <- "https://www.midwestiso.org/Library/Repository/Market%20Reports/20130510_hwd_HIST.csv"
df <- read.csv(fname, header=TRUE, sep="," , skip=7)
df <- df[1:(length(df$MKTHOUR)-5),]
# format variables
df$MWh <- as.numeric(df$MWh)
df$Datetime <- strptime(df$MKTHOUR, "%m/%d/%y %I:%M %p")
# Create some variables
df$Date <- as.Date(df$Datetime)
df$HrEnd <- df$Datetime$hour+1
# Subset recent and last data
last.obs <- range(df$Date)[2]
df.recent <- subset(df, Date %in% seq(last.obs-30, last.obs-2, by=1))
df.last <- subset(df, Date %in% seq(last.obs-2, last.obs, by=1))
# plot recent in Grey
p <- ggplot(df.recent, aes(HrEnd, MWh, group=factor(Date))) +
geom_line(color="grey") +
scale_y_continuous(labels = comma) +
scale_x_continuous(breaks = seq(1,24,1)) +
labs(y="MWh") +
labs(x="Hour Ending") +
labs(title="Hourly Wind Generation")
p
# plot last two days in Red
p <- p + geom_line(df.last, aes(HrEnd, MWh, group=factor(Date)), color="red")
p
when you add a new data set to a geom you need to use the data= argument. Or put the arguments in the proper order mapping=..., data=.... Take a look at the arguments for ?geom_line.
Thus:
p + geom_line(data=df.last, aes(HrEnd, MWh, group=factor(Date)), color="red")
Or:
p + geom_line(aes(HrEnd, MWh, group=factor(Date)), df.last, color="red")
Another cause is accidentally putting the data=... inside the aes(...) instead of outside:
RIGHT:
ggplot(data=df[df$var7=='9-06',], aes(x=lifetime,y=rep_rate,group=mdcp,color=mdcp) ...)
WRONG:
ggplot(aes(data=df[df$var7=='9-06',],x=lifetime,y=rep_rate,group=mdcp,color=mdcp) ...)
In particular this can happen when you prototype your plot command with qplot(), which doesn't use an explicit aes(), then edit/copy-and-paste it into a ggplot()
qplot(data=..., x=...,y=..., ...)
ggplot(data=..., aes(x=...,y=...,...))
It's a pity ggplot's error message isn't Missing 'data' argument! instead of this cryptic nonsense, because that's what this message often means.
This could also occur if you refer to a variable in the data.frame that doesn't exist. For example, recently I forgot to tell ddply to summarize by one of my variables that I used in geom_line to specify line color. Then, ggplot didn't know where to find the variable I hadn't created in the summary table, and I got this error.

Use of ggplot() within another function in R

I'm trying to write a simple plot function, using the ggplot2 library. But the call to ggplot doesn't find the function argument.
Consider a data.frame called means that stores two conditions and two mean values that I want to plot (condition will appear on the X axis, means on the Y).
library(ggplot2)
m <- c(13.8, 14.8)
cond <- c(1, 2)
means <- data.frame(means=m, condition=cond)
means
# The output should be:
# means condition
# 1 13.8 1
# 2 14.8 2
testplot <- function(meansdf)
{
p <- ggplot(meansdf, aes(fill=meansdf$condition, y=meansdf$means, x = meansdf$condition))
p + geom_bar(position="dodge", stat="identity")
}
testplot(means)
# This will output the following error:
# Error in eval(expr, envir, enclos) : object 'meansdf' not found
So it seems that ggplot is calling eval, which can't find the argument meansdf. Does anyone know how I can successfully pass the function argument to ggplot?
(Note: Yes I could just call the ggplot function directly, but in the end I hope to make my plot function do more complicated stuff! :) )
The "proper" way to use ggplot programmatically is to use aes_string() instead of aes() and use the names of the columns as characters rather than as objects:
For more programmatic uses, for example if you wanted users to be able to specify column names for various aesthetics as arguments, or if this function is going in a package that needs to pass R CMD CHECK without warnings about variable names without definitions, you can use aes_string(), with the columns needed as characters.
testplot <- function(meansdf, xvar = "condition", yvar = "means",
fillvar = "condition") {
p <- ggplot(meansdf,
aes_string(x = xvar, y= yvar, fill = fillvar)) +
geom_bar(position="dodge", stat="identity")
}
As Joris and Chase have already correctly answered, standard best practice is to simply omit the meansdf$ part and directly refer to the data frame columns.
testplot <- function(meansdf)
{
p <- ggplot(meansdf,
aes(fill = condition,
y = means,
x = condition))
p + geom_bar(position = "dodge", stat = "identity")
}
This works, because the variables referred to in aes are looked for either in the global environment or in the data frame passed to ggplot. That is also the reason why your example code - using meansdf$condition etc. - did not work: meansdf is neither available in the global environment, nor is it available inside the data frame passed to ggplot, which is meansdf itself.
The fact that the variables are looked for in the global environment instead of in the calling environment is actually a known bug in ggplot2 that Hadley does not consider fixable at the moment.
This leads to problems, if one wishes to use a local variable, say, scale, to influence the data used for the plot:
testplot <- function(meansdf)
{
scale <- 0.5
p <- ggplot(meansdf,
aes(fill = condition,
y = means * scale, # does not work, since scale is not found
x = condition))
p + geom_bar(position = "dodge", stat = "identity")
}
A very nice workaround for this case is provided by Winston Chang in the referenced GitHub issue: Explicitly setting the environment parameter to the current environment during the call to ggplot.
Here's what that would look like for the above example:
testplot <- function(meansdf)
{
scale <- 0.5
p <- ggplot(meansdf,
aes(fill = condition,
y = means * scale,
x = condition),
environment = environment()) # This is the only line changed / added
p + geom_bar(position = "dodge", stat = "identity")
}
## Now, the following works
testplot(means)
Here is a simple trick I use a lot to define my variables in my functions environment (second line):
FUN <- function(fun.data, fun.y) {
fun.data$fun.y <- fun.data[, fun.y]
ggplot(fun.data, aes(x, fun.y)) +
geom_point() +
scale_y_continuous(fun.y)
}
datas <- data.frame(x = rnorm(100, 0, 1),
y = x + rnorm(100, 2, 2),
z = x + rnorm(100, 5, 10))
FUN(datas, "y")
FUN(datas, "z")
Note how the y-axis label also changes when different variables or data-sets are used.
I don't think you need to include the meansdf$ part in your function call itself. This seems to work on my machine:
meansdf <- data.frame(means = c(13.8, 14.8), condition = 1:2)
testplot <- function(meansdf)
{
p <- ggplot(meansdf, aes(fill=condition, y=means, x = condition))
p + geom_bar(position="dodge", stat="identity")
}
testplot(meansdf)
to produce:
This is an example of a problem that is discussed earlier. Basically, it comes down to ggplot2 being coded for use in the global environment mainly. In the aes() call, the variables are looked for either in the global environment or within the specified dataframe.
library(ggplot2)
means <- data.frame(means=c(13.8,14.8),condition=1:2)
testplot <- function(meansdf)
{
p <- ggplot(meansdf, aes(fill=condition,
y=means, x = condition))
p + geom_bar(position="dodge", stat="identity")
}
EDIT:
update: After seeing the other answer and updating the ggplot2 package, the code above works. Reason is, as explained in the comments, that ggplot will look for the variables in aes in either the global environment (when the dataframe is specifically added as meandf$... ) or within the mentioned environment.
For this, be sure you work with the latest version of ggplot2.
If is important to pass the variables (column names) to the custom plotting function unquoted, while different variable names are used within the function, then another workaround that I tried, was to make use of match.call() and eval (like here as well):
library(ggplot2)
meansdf <- data.frame(means = c(13.8, 14.8), condition = 1:2)
testplot <- function(df, x, y) {
arg <- match.call()
scale <- 0.5
p <- ggplot(df, aes(x = eval(arg$x),
y = eval(arg$y) * scale,
fill = eval(arg$x)))
p + geom_bar(position = "dodge", stat = "identity")
}
testplot(meansdf, condition, means)
Created on 2019-01-10 by the reprex package (v0.2.1)
Another workaround, but with passing quoted variables to the custom plotting function is using get():
meansdf <- data.frame(means = c(13.8, 14.8), condition = 1:2)
testplot <- function(df, x, y) {
scale <- 0.5
p <- ggplot(df, aes(x = get(x),
y = get(y) * scale,
fill = get(x)))
p + geom_bar(position = "dodge", stat = "identity")
}
testplot(meansdf, "condition", "means")
Created on 2019-01-10 by the reprex package (v0.2.1)
This frustrated me for some time. I wanted to send different data frames with different variable names and I wanted the ability to plot different columns from the data frame. I finally got a work around by creating some dummy (global) variables to handle plotting and forcing assignment inside the function
plotgraph function(df,df.x,df.y) {
dummy.df <<- df
dummy.x <<- df.x
dummy.y <<- df.y
p = ggplot(dummy.df,aes(x=dummy.x,y=dummy.y,.....)
print(p)
}
then in the main code I can just call the function
plotgraph(data,data$time,data$Y1)
plotgraph(data,data$time,data$Y2)
Short answer: Use qplot
Long answer:
In essence you want something like this:
my.barplot <- function(x=this.is.a.data.frame.typically) {
# R code doing the magic comes here
...
}
But that lacks flexibility because you must stick to consistent column naming to avoid the annoying R scope idiosyncrasies. Of course the next logic step is:
my.barplot <- function(data=data.frame(), x=..., y....) {
# R code doing something really really magical here
...
}
But then that starts looking suspiciously like a call to qplot(), right?
qplot(data=my.data.frame, x=some.column, y=some.other column,
geom="bar", stat="identity",...)
Of course now you'd like to change things like scale titles but for that a function comes handy... the good news is that scoping issues are mostly gone.
my.plot <- qplot(data=my.data.frame, x=some.column, y=some.other column,...)
set.scales(p, xscale=scale_X_continuous, xtitle=NULL,
yscale=scale_y_continuous(), title=NULL) {
return(p + xscale(title=xtitle) + yscale(title=ytitle))
}
my.plot.prettier <- set.scale(my.plot, scale_x_discrete, 'Days',
scale_y_discrete, 'Count')
Another workaround is to define the aes(...) as a variable of your function :
func<-function(meansdf, aes(...)){}
This just worked fine for me on a similar topic
You don't need anything fancy. Not even dummy variables. You only need to add a print() inside your function, is like using cat() when you want something to show in the console.
myplot <- ggplot(......) + Whatever you want here
print(myplot)
It worked for me more than one time inside the same function
I just generate new data frame variables with the desired names inside the function:
testplot <- function(df, xVar, yVar, fillVar) {
df$xVar = df[,which(names(df)==xVar)]
df$yVar = df[,which(names(df)==yVar)]
df$fillVar = df[,which(names(df)==fillVar)]
p <- ggplot(df,
aes(x=xvar, y=yvar, fill=fillvar)) +
geom_bar(position="dodge", stat="identity")
}

Resources