Get current ggplot object - r

Suppose I plot something like this:
ggplot(iris, aes(x=Sepal.Length, y=Petal.Length)) + geom_point()
Then I realise that I forgot to store the result (i.e. the ggplot object).
How can I retrieve the ggplot object corresponding to the current device?
Is there some ggplot function I can feed cur.dev() into to retrieve the associated plot object, or is it gone forever?
(Note - in this case I could do p <- .Last.value, but let's assume I've typed a few commands since then so that this is not available.
Motivation - adding a hook to knitr to automagically set fig.cap to the title of the plot (if any)).

You are after last_plot
It retrieves the last plot to be modified or created and is used by ggsave
Note that it is the last plot modified or created
set_last_plot is the relevant code (see the source)
It is important note that creating modifying or rendering a ggplot object will set the last plot.
ggplot(iris, aes(x=Sepal.Length, y=Petal.Length)) + geom_point()
f <- last_plot()
# will return the iris plot
p <- ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()
last_plot()
# will return p
f
last_plot()
# is now f
It will also not count any modifications / manipulation using grid or gridExtra (such as grid.arrange / grid.text

The last object assigned (and it does not need to be a plot object) can be recovered with .Last.value
>require(ggplot2)
#Loading required package: ggplot2
ggplot(iris, aes(x=Sepal.Length, y=Petal.Length)) + geom_point()
gp <- .Last.value
gp
This should return plot objects that have been modified by grid functions as long as there was an assignment. I'm not sure it this is true for actions that were mediated through print calls.

Related

ggplot - global scale_color_manual [duplicate]

I would like to define a color palette for every plot built in a markdown document. In essence this would overwrite the default choices.
There are several very old answers -- thanks for the links here and here suggested by #dww -- that solve for old versions (specifically calling out a solution on 0.8.2 when the modern release several major releases ahead, currently at 3.2.x).
I'll illustrate the closest use case, setting themes. In the case of general purpose themes, this is trivial: rather than appending + theme_minimal() on every plot, I can instead set the theme which persists across all plots.
library(ggplot2)
d <- diamonds[sample(1:nrow(diamonds), 1000), ]
## without theming
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
theme_minimal() # must set this theme call for every plot
## setting theme
theme_set(theme_minimal())
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() # plot in theme, for all subsequent plots
Is there a similar, modification that exists to set the color palette throughout? For example, a theme-based replacement for calling,
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
scale_color_brewer(palette='Set2') # requesting a global option to set this for all plots
The linked solution that does not depend on old versions instead overloads the entire ggplot function. That seems risky.
ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer(palette = 'Set1')
There is a ggplot_global environment which is used internally within ggplot2 but isn't exported. You can see its structure by temporarily unlocking the bindings of a ggplot function and modifying it to return the contents of the environment as a list. You can do this non-destructively like this:
library(ggplot2)
get_ggplot_global <- function()
{
unlockBinding("theme_set", as.environment("package:ggplot2"))
backup <- body(theme_set)[[5]]
body(theme_set)[[5]] <- substitute(return(as.list(ggplot_global)))
global_list <- theme_set(theme_bw())
body(theme_set)[[5]] <- backup
lockBinding("theme_set", as.environment("package:ggplot2"))
return(global_list)
}
global_list <- get_ggplot_global()
names(global_list)
#> [1] "date_origin" "element_tree" "base_to_ggplot" "all_aesthetics"
#> [5] "theme_current" "time_origin"
By examining this you can see that ggplot global environment has an object called theme_current, which is why you can set the various line, text and axis elements globally including their colours.
When you are talking about a colour scheme in your question, you are referring to the colours defined in a scale object. This is not part of the ggplot_global environment, and you can't change the default scale object because there isn't one. When you create a new ggplot(), it has an empty slot for "scales".
You therefore have a few options:
Wrap ggplot with my_ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer()
Overwrite ggplot with the above function (as suggested by #Phil)
Create your own theme object that you add on with standard ggplot syntax
The best thing might be to just write a wrapper around ggplot. However, the third option is also quite clean and idiomatic. You could achieve it like this:
set_scale <- function(...)
{
if(!exists("doc_env", where = globalenv()))
assign("doc_env", new.env(parent = globalenv()), envir = globalenv())
doc_env$scale <- (ggplot() + eval(substitute(...)))$scales$scales[[1]]
}
my_scale <- function() if(exists("doc_env", where = globalenv())) return(doc_env$scale)
You would use this by doing (for example)
set_scale(scale_color_brewer(palette = "Set2"))
At the start of your document.
So now you can just do + my_scale() for each plot:
d <- diamonds[sample(1:nrow(diamonds), 1000), ]
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
my_scale()

Set a document-persistent ggplot2 color theme

I would like to define a color palette for every plot built in a markdown document. In essence this would overwrite the default choices.
There are several very old answers -- thanks for the links here and here suggested by #dww -- that solve for old versions (specifically calling out a solution on 0.8.2 when the modern release several major releases ahead, currently at 3.2.x).
I'll illustrate the closest use case, setting themes. In the case of general purpose themes, this is trivial: rather than appending + theme_minimal() on every plot, I can instead set the theme which persists across all plots.
library(ggplot2)
d <- diamonds[sample(1:nrow(diamonds), 1000), ]
## without theming
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
theme_minimal() # must set this theme call for every plot
## setting theme
theme_set(theme_minimal())
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() # plot in theme, for all subsequent plots
Is there a similar, modification that exists to set the color palette throughout? For example, a theme-based replacement for calling,
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
scale_color_brewer(palette='Set2') # requesting a global option to set this for all plots
The linked solution that does not depend on old versions instead overloads the entire ggplot function. That seems risky.
ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer(palette = 'Set1')
There is a ggplot_global environment which is used internally within ggplot2 but isn't exported. You can see its structure by temporarily unlocking the bindings of a ggplot function and modifying it to return the contents of the environment as a list. You can do this non-destructively like this:
library(ggplot2)
get_ggplot_global <- function()
{
unlockBinding("theme_set", as.environment("package:ggplot2"))
backup <- body(theme_set)[[5]]
body(theme_set)[[5]] <- substitute(return(as.list(ggplot_global)))
global_list <- theme_set(theme_bw())
body(theme_set)[[5]] <- backup
lockBinding("theme_set", as.environment("package:ggplot2"))
return(global_list)
}
global_list <- get_ggplot_global()
names(global_list)
#> [1] "date_origin" "element_tree" "base_to_ggplot" "all_aesthetics"
#> [5] "theme_current" "time_origin"
By examining this you can see that ggplot global environment has an object called theme_current, which is why you can set the various line, text and axis elements globally including their colours.
When you are talking about a colour scheme in your question, you are referring to the colours defined in a scale object. This is not part of the ggplot_global environment, and you can't change the default scale object because there isn't one. When you create a new ggplot(), it has an empty slot for "scales".
You therefore have a few options:
Wrap ggplot with my_ggplot <- function(...) ggplot2::ggplot(...) + scale_color_brewer()
Overwrite ggplot with the above function (as suggested by #Phil)
Create your own theme object that you add on with standard ggplot syntax
The best thing might be to just write a wrapper around ggplot. However, the third option is also quite clean and idiomatic. You could achieve it like this:
set_scale <- function(...)
{
if(!exists("doc_env", where = globalenv()))
assign("doc_env", new.env(parent = globalenv()), envir = globalenv())
doc_env$scale <- (ggplot() + eval(substitute(...)))$scales$scales[[1]]
}
my_scale <- function() if(exists("doc_env", where = globalenv())) return(doc_env$scale)
You would use this by doing (for example)
set_scale(scale_color_brewer(palette = "Set2"))
At the start of your document.
So now you can just do + my_scale() for each plot:
d <- diamonds[sample(1:nrow(diamonds), 1000), ]
ggplot(d, aes(x=carat, y=price, color=clarity)) +
geom_point() +
my_scale()

How to add geom_point() to autolayer() line?

Trying to add geom_points to an autolayer() line ("fitted" in pic), which is a wrapper part of autoplot() for ggplot2 in Rob Hyndmans forecast package (there's a base autoplot/autolayer in ggplot2 too so same likely applies there).
Problem is (I'm no ggplot2 expert, and autoplot wrapper makes it trickier) the geom_point() applies fine to the main call, but how do I apply similar to the autolayer (fitted values)?
Tried type="b" like normal geom_line() but it's not an object param in autolayer().
require(fpp2)
model.ses <- ets(mdeaths, model="ANN", alpha=0.4)
model.ses.fc <- forecast(model.ses, h=5)
forecast::autoplot(mdeaths) +
forecast::autolayer(model.ses.fc$fitted, series="Fitted") + # cannot set to show points, and type="b" not allowed
geom_point() # this works fine against the main autoplot call
This seems to work:
library(forecast)
library(fpp2)
model.ses <- ets(mdeaths, model="ANN", alpha=0.4)
model.ses.fc <- forecast(model.ses, h=5)
# Pre-compute the fitted layer so we can extract the data out of it with
# layer_data()
fitted_layer <- forecast::autolayer(model.ses.fc$fitted, series="Fitted")
fitted_values <- fitted_layer$layer_data()
plt <- forecast::autoplot(mdeaths) +
fitted_layer +
geom_point() +
geom_point(data = fitted_values, aes(x = timeVal, y = seriesVal))
There might be a way to make forecast::autolayer do what you want directly but this solution works. If you want the legend to look right, you'll want to merge the input data and fitted values into a single data.frame.

R if ggplot not last in function, it won't plot

I discovered that if ggplot is the last thing in a function, then calling the function will result in ggplot creating a plot as expected.
But if ggplot is not the last thing in a function -- say it is followed by an assignment (like x <- t) or a return statement (like return(x)) then ggplot will not create a plot.
What is the work around for this?
P.S. please throw in a comment explaining how to create an inline grey background used to indicate code :-)
Use plot for your ggplot object.
func1 <- function() {
require(ggplot2)
ggplot(mtcars, aes(mpg, wt)) + geom_point()
}
func1() # this creates plot
func2 <- function() {
require(ggplot2)
ggplot(mtcars, aes(mpg, wt)) + geom_point()
cat("hey\n")
}
func2() # this does not create plot
func3 <- function() {
require(ggplot2)
plot(ggplot(mtcars, aes(mpg, wt)) + geom_point())
cat("hey\n")
}
func3() # use plot to make sure your plot is displayed
By the way, func1 creates plot not because ggplot is the last thing to do in the function. It creates plot because the function returns the ggplot object and the code func1() invokes the print method of the object.
To see this, If you do a <- func1(), then the plot is not created, but stored in the a variable.
Note: You can use print insted. print and plot are equivalent for the ggplot object.

change font size of labels without knowing labels in ggplot2

I would like to change the font size of the labels in this plot:
library(ggplot2)
p <- ggplot(mtcars, aes(x=wt, y=mpg)) +
geom_text(label=rownames(mtcars))
p
My problem: I do not know what the labels are. (I stored a plot in which I used different data.frame()s to add geom_text(). I now only loaded the plot (p in this example), but do not want to also load the data.frame()s with which I created the labels).
As I do not know what the labels are, I cannot use this solution:
p + geom_text(label=rownames(mtcars), size=2)
(Another problem with this solution would be that I still needed to delete the original geom_text() with the larger font-size).
I can change the size of all text in the plot with this solution:
library(grid)
grid.force()
grid.gedit("GRID.text", grep=TRUE, gp=gpar(fontsize=4.5))
However, now also my axes changed, which is not what I wanted.
I believe there are several options to achieve what I want, at least two of which should be fairly simply to implement:
Save the object from grid.gedit() to p1 and then p1 + theme(text = element_text(size=2)). My problem here: I do not know how to save the object from grid.gedit(). This would be my preferred option.
Go to the right viewport before applying grid.gedit(). I tried this, but still change both the labels (which I want) and the axes text (which I do not want).
Somehow extract the data.frame for the labels from the stored plot (p in this example) to apply the solution that I provided first.
You can inspect (/modify) the plot after building it,
library(ggplot2)
p <- ggplot(mtcars, aes(x=wt, y=mpg)) +
geom_text(label=rownames(mtcars))
g <- ggplot_build(p)
# original data is in str(g$plot$data)
# but it's easier to process the data for rendering
g[["data"]][[1]][["size"]] <- 5
g[["data"]][[1]][["colour"]] <- "red"
gg <- ggplot_gtable(g)
grid.newpage()
grid.draw(gg)
Your grid.gedit command was close. You need to set up a gPath so that the edit command finds just those labels in the plot panel. grid.ls(grid.force()) returns a hierarchy of grobs. Find the 'panel', then the 'text'. (Note: the 'g' in 'gedit' stands for 'grep = TRUE, global = TRUE')
library(ggplot2)
p <- ggplot(mtcars, aes(x=wt, y=mpg)) +
geom_text(label=rownames(mtcars))
p
library(grid)
grid.ls(grid.force()) # Locate the path to the labels in the panel
grid.gedit(gPath("panel","GRID.text"), gp=gpar(fontsize=4.5))
If you prefer, with a few more lines of code, the plot object can be edited rather than editing on screen.
g = ggplotGrob(p)
g = editGrob(grid.force(g), gPath("panel", "GRID.text"), grep=TRUE, gp=gpar(fontsize=4.5))
grid.newpage()
grid.draw(g)

Resources