81

I would like to generate a figure that has a combination of base and ggplot graphics. The following code shows my figure using the base plotting functions of R:

t <- c(1:(24*14)) 
P <- 24 
A <- 10 
y <- A*sin(2*pi*t/P)+20

par(mfrow=c(2,2))
plot(y,type = "l",xlab = "Time (hours)",ylab = "Amplitude",main = "Time series")
acf(y,main = "Autocorrelation",xlab = "Lag (hours)", ylab = "ACF")
spectrum(y,method = "ar",main = "Spectral density function", 
         xlab = "Frequency (cycles per hour)",ylab = "Spectrum")
require(biwavelet)
t1 <- cbind(t, y)
wt.t1=wt(t1)
plot(wt.t1, plot.cb=FALSE, plot.phase=FALSE,main = "Continuous wavelet transform",
     ylab = "Period (hours)",xlab = "Time (hours)")

Which generates enter image description here

Most of these panels look sufficient for me to include in my report. However, the plot showing the autocorrelation needs to be improved. This looks much better by using ggplot:

require(ggplot2)
acz <- acf(y, plot=F)
acd <- data.frame(lag=acz$lag, acf=acz$acf)
ggplot(acd, aes(lag, acf)) + geom_area(fill="grey") +
  geom_hline(yintercept=c(0.05, -0.05), linetype="dashed") +
  theme_bw()

enter image description here

However, seeing as ggplot is not a base graphic, we cannot combine ggplot with layout or par(mfrow). How could I replace the autocorrelation plot generated from the base graphics with the one generated by ggplot? I know I can use grid.arrange if all of my figures were made with ggplot but how do I do this if only one of the plots are generated in ggplot?

MichaelChirico
  • 33,841
  • 14
  • 113
  • 198
KatyB
  • 3,920
  • 7
  • 42
  • 72
  • 3
    it might be almost as easy, and look more consistent, to use `polygon` with the output of `acf()` to construct a base-graphics plot that resembles the `ggplot` one. – Ben Bolker Jan 02 '13 at 15:18
  • 1
    Thanks for our response. This question is really aimed at learning how to combine ggplot and base graphics in a figure window, I realize that there may be more efficient ways of generating the figure shown, but for future purposes I would like to learn the method specified. – KatyB Jan 02 '13 at 15:21
  • 4
    check out the `gridBase` package ... – Ben Bolker Jan 02 '13 at 15:24
  • You might like to take a look at the `gridGraphics` package, which "[Redraws] base graphics as grid graphics". – maj Aug 15 '15 at 19:56
  • Although it is marked as duplicated, this answer worked extremely well for me: http://stackoverflow.com/a/21857177/1436851 – Antoni Mar 09 '16 at 09:54
  • Would be interesting to consider the case where we have a need of going from base to ggplot and back again. – Jason Whyte Dec 01 '22 at 04:07

5 Answers5

59

Using gridBase package, you can do it just by adding 2 lines. I think if you want to do funny plot with the grid you need just to understand and master viewports. It is really the basic object of the grid package.

vps <- baseViewports()
pushViewport(vps$figure) ##   I am in the space of the autocorrelation plot

The baseViewports() function returns a list of three grid viewports. I use here figure Viewport A viewport corresponding to the figure region of the current plot.

Here how it looks the final solution:

enter image description here

library(gridBase)
library(grid)

par(mfrow=c(2, 2))
plot(y,type = "l",xlab = "Time (hours)",ylab = "Amplitude",main = "Time series")
plot(wt.t1, plot.cb=FALSE, plot.phase=FALSE,main = "Continuous wavelet transform",
     ylab = "Period (hours)",xlab = "Time (hours)")
spectrum(y,method = "ar",main = "Spectral density function", 
         xlab = "Frequency (cycles per hour)",ylab = "Spectrum")
## the last one is the current plot
plot.new()              ## suggested by @Josh
vps <- baseViewports()
pushViewport(vps$figure) ##   I am in the space of the autocorrelation plot
vp1 <-plotViewport(c(1.8,1,0,1)) ## create new vp with margins, you play with this values 
require(ggplot2)
acz <- acf(y, plot=F)
acd <- data.frame(lag=acz$lag, acf=acz$acf)
p <- ggplot(acd, aes(lag, acf)) + geom_area(fill="grey") +
  geom_hline(yintercept=c(0.05, -0.05), linetype="dashed") +
  theme_bw()+labs(title= "Autocorrelation\n")+
  ## some setting in the title to get something near to the other plots
  theme(plot.title = element_text(size = rel(1.4),face ='bold'))
print(p,vp = vp1)        ## suggested by @bpatiste
Edward
  • 10,360
  • 2
  • 11
  • 26
agstudy
  • 119,832
  • 17
  • 199
  • 261
  • Yes I have tried this, the problem with this is that the plot generated with ggplot is much bigger than the other panels (as you can see above). Is there a method for changing this? – KatyB Jan 02 '13 at 16:31
  • +1 Very nice. If you replaced the call to `acf(...)` with a call to `plot.new()`, you'd avoid having to call `grid.rect()` to 'white-out' the acf plot. – Josh O'Brien Jan 02 '13 at 17:42
  • 1
    Just b/c I came back and saw that you'd added this much simpler and cleverer answer. – Josh O'Brien Jan 02 '13 at 17:51
  • 1
    instead of `grid.draw(ggplotGrob(p))` I'd use the `vp` argument in the `print` method for ggplot objects. – baptiste Jan 02 '13 at 21:52
  • Thanks. I used it in order to print a few plots to a pdf in different pages. But for some reason the margins get bigger in every new page and at some point plots disappear. How can I correct that? Thanks – AMM Jun 09 '15 at 16:02
  • It's not obvious how to extend this solution to the case where (say) the left column is base plots and the right column is ggplot. When I've tried the layout is disrupted as the fig in the bottom right corner becomes an inset of the top right corner. Is there a fairly straightforward way to generalise the solution? (Or, is this a separate question as a whole other method is needed?) – Jason Whyte Dec 01 '22 at 04:08
16

You can use the print command with a grob and viewport.
First plot your base graphics then add the ggplot

library(grid)

# Let's say that P is your plot
P <- ggplot(acd, # etc... )

# create an apporpriate viewport.  Modify the dimensions and coordinates as needed
vp.BottomRight <- viewport(height=unit(.5, "npc"), width=unit(0.5, "npc"), 
                           just=c("left","top"), 
                           y=0.5, x=0.5)

# plot your base graphics 
par(mfrow=c(2,2))
plot(y,type #etc .... )

# plot the ggplot using the print command
print(P, vp=vp.BottomRight)
Ricardo Saporta
  • 54,400
  • 17
  • 144
  • 178
  • Hello Ricardo. Do you know how to control the widths of the plots with your method ? For instance I want a base graphic and a ggplot2 graphic side by side, but with a larger width for the base graphic. – Stéphane Laurent Jan 16 '13 at 11:27
  • Hi Stéphane, you can accomplish this adjust the settings in the `viewport()` line. Specifically, you would want to adjust `width` and the `y` value, experimenting with different values until you get your desired results. – Ricardo Saporta Jan 18 '13 at 06:01
  • Thank you Ricardo. Actually I have open a question here http://stackoverflow.com/questions/14358526/controlling-column-widths-for-side-by-side-base-graphic-and-ggplot2-graphic/14358752 – Stéphane Laurent Jan 18 '13 at 08:12
12

cowplot package has recordPlot() function for capturing base R plots so that they can be put together in plot_grid() function.

library(biwavelet)
library(ggplot2)
library(cowplot)
library(gridGraphics)

t <- c(1:(24*14)) 
P <- 24 
A <- 10 
y <- A*sin(2*pi*t/P)+20

plot(y,type = "l",xlab = "Time (hours)",ylab = "Amplitude",main = "Time series")
### record the previous plot
p1 <- recordPlot()  

spectrum(y,method = "ar",main = "Spectral density function", 
         xlab = "Frequency (cycles per hour)",ylab = "Spectrum")
p2 <- recordPlot()

t1 <- cbind(t, y)
wt.t1=wt(t1)
plot(wt.t1, plot.cb=FALSE, plot.phase=FALSE,main = "Continuous wavelet transform",
     ylab = "Period (hours)",xlab = "Time (hours)")
p3 <- recordPlot()

acz <- acf(y, plot=F)
acd <- data.frame(lag=acz$lag, acf=acz$acf)
p4 <- ggplot(acd, aes(lag, acf)) + geom_area(fill="grey") +
  geom_hline(yintercept=c(0.05, -0.05), linetype="dashed") +
  theme_bw()

### combine all plots together
plot_grid(p1, p4, p2, p3,
          labels = 'AUTO',
          hjust = 0, vjust = 1)

Created on 2019-03-17 by the reprex package (v0.2.1.9000)

Edward
  • 10,360
  • 2
  • 11
  • 26
Tung
  • 26,371
  • 7
  • 91
  • 115
9

I'm a fan of the gridGraphics package. For some reason I had trouble with gridBase.

library(ggplot2)
library(gridGraphics)
data.frame(x = 2:10, y = 12:20) -> dat
plot(dat$x, dat$y)
grid.echo()
grid.grab() -> mapgrob
ggplot(data = dat) + geom_point(aes(x = x, y = y)) 
pushViewport(viewport(x = .8, y = .4, height = .2, width = .2))    
grid.draw(mapgrob)

enter image description here

Chris
  • 1,575
  • 13
  • 20
0

Another option is using the ggplotify package with the as.ggplot function, which convert the base R graph to a ggplot object. Then you can combine that plot with a real ggplot using patchwork layout structure. Here is a reproducible example:

set.seed(1) # Reproducible
df <- data.frame(x = runif(10,0,1),
                 y = runif(10,0,1))
library(ggplot2)
library(ggplotify)
library(patchwork)
# ggplot
p_ggplot <- ggplot(df, aes(x = x, y = y)) +
  geom_point()

# Combine base graph and ggplot 
as.ggplot(~plot(df$x, df$y)) + p_ggplot

Created on 2022-08-22 with reprex v2.0.2

As you can see the base graph and ggplot graph are combined in one figure window.

It is possible to combine more different kind of graph objects as described in the documentation:

Convert plot function call (using expression or formula) to ‘grob’ or ‘ggplot’ object that compatible to the ‘grid’ and ‘ggplot2’ ecosystem. With this package, we are able to e.g. using ‘cowplot’ to align plots produced by ‘base’ graphics, ‘grid’, ‘lattice’, ‘vcd’ etc. by converting them to ‘ggplot’ objects.

Quinten
  • 35,235
  • 5
  • 20
  • 53