Create multiple scatter with same axes in R
I am trying to plot four scatter plots in a 2 x 2 circuit in R (I am actually drawing through rpy2). I would like each to have an aspect ratio of 1, but also be in the same scale, so the same X and Y ticks for all subplots so that they can be compared. I tried to do it with par
:
par(mfrow=c(2,2))
# scatter 1
plot(x, y, "p", asp=1)
# scatter 2
plot(a, b, "p", asp=1)
# ...
Edit:
Here's a straightforward example of what I have now:
> par(mfrow=c(2,2))
> for (n in 1:4) { plot(iris$Petal.Width, rnorm(length(iris$Petal.Width)), "p", asp=1) }
which creates the correct type of scatter, but with different scales. Setting ylim
and the xlim
same in every call plot
above does not fix the problem. You still get different labels and ticks on each axis, making it an unnecessarily difficult nudge to interpret. I want the X and Y axes to be the same. For example, this:
for (n in 1:4) { plot(iris$Petal.Width, rnorm(length(iris$Petal.Width)), "p", asp=1, xlim=c(-4, 6), ylim=c(-2, 4)) }
Creates an incorrect result:
What's the best way to ensure that the same axes are used across all subplots?
All I was looking for was a parameter, for example axis=same
or something par(mfrow=...)
like that, which sounds like the default behavior for lattice
to make the axes common and identical in every subheading.
lgautier gave good code with ggplot, but it requires the axes to be known in advance. I want to clarify that I wanted to avoid repeating the data in each subheading and calculating my own correct ticks for plotting. If you need to know this in advance, then ggplot's solution is much more complicated than just plotting with plot
and explicitly
agstudy gave a lattice solution. This looks closest to what I want, because you don't have to explicitly precompose the tick positions for each scatter, but as a new user, I can't figure out how to make the grid look like a regular plot. The closest I got is this:
> xyplot(y~x|group, data =dat, type='p',
between =list(y=2,x=2),
layout=c(2,2), aspect=1,
scales =list(y = list(relation='same'), alternating=FALSE))
which gives:
How can I make it look like an R base? I don't need those subtitles group
at the top of each subtitle, or unmarked checkboxes at the top and right of each scatter, I just want each x and y of the scatter to be marked. I'm also not looking for a common label for X and Y - each subhead gets its own X and Y labels. And the axis labels should be the same in every scatter, although with the data selected here it doesn't make sense.
If there is no easy way to make the lattice look like a base R, it looks like the answer is that there is no way to do what I am trying to do in R (surprisingly) without first calculating the exact locations of each tick in each subplot, which requires iteration through the data in advance.
source to share
ggplot2 might have the highest pretty / easy ratio when starting out.
Example with rpy2:
from rpy2.robjects.lib import ggplot2
from rpy2.robjects import r, Formula
iris = r('iris')
p = ggplot2.ggplot(iris) + \
ggplot2.geom_point(ggplot2.aes_string(x="Sepal.Length", y="Sepal.Width")) + \
ggplot2.facet_wrap(Formula('~ Species'), ncol=2, nrow = 2) + \
ggplot2.GBaseObject(r('ggplot2::coord_fixed')()) # aspect ratio
# coord_fixed() missing from the interface,
# therefore the hack. This should be fixed in rpy2-2.3.3
p.plot()
After reading the comments on the previous answer, I see that you can mean completely separate sites. With the default construction system for R, par(mfrow(c(2,2))
or par(mfcol(c(2,2)))
will be the easiest way, and also keep the aspect ratio, ranges for the axes, and the conventionally agreed markings that have been fixed.
The most flexible system to build in R might be grid
. It's not as bad as it sounds, think of it as a scene graph. With rpy2, ggplot2 and grid:
from rpy2.robjects.vectors import FloatVector
from rpy2.robjects.lib import grid
grid.newpage()
lt = grid.layout(2,2) # 2x2 layout
vp = grid.viewport(layout = lt)
vp.push()
# limits for axes and tickmarks have to be known or computed beforehand
xlims = FloatVector((4, 9))
xbreaks = FloatVector((4,6,8))
ylims = FloatVector((-3, 3))
ybreaks = FloatVector((-2, 0, 2))
# first panel
vp_p = grid.viewport(**{'layout.pos.col':1, 'layout.pos.row': 1})
p = ggplot2.ggplot(iris) + \
ggplot2.geom_point(ggplot2.aes_string(x="Sepal.Length",
y="rnorm(nrow(iris))")) + \
ggplot2.GBaseObject(r('ggplot2::coord_fixed')()) + \
ggplot2.scale_x_continuous(limits = xlims, breaks = xbreaks) + \
ggplot2.scale_y_continuous(limits = ylims, breaks = ybreaks)
p.plot(vp = vp_p)
# third panel
vp_p = grid.viewport(**{'layout.pos.col':2, 'layout.pos.row': 2})
p = ggplot2.ggplot(iris) + \
ggplot2.geom_point(ggplot2.aes_string(x="Sepal.Length",
y="rnorm(nrow(iris))")) + \
ggplot2.GBaseObject(r('ggplot2::coord_fixed')()) + \
ggplot2.scale_x_continuous(limits = xlims, breaks = xbreaks) + \
ggplot2.scale_y_continuous(limits = ylims, breaks = ybreaks)
p.plot(vp = vp_p)
More documentation in rpy2 documentation about plotting and then in ggplot2 and grid docs.
source to share
From lattice
and ggplot2
you need to change the data. For example:
- create 4 data.frame (x = x1, y = y1) ...
- add group column for each data.frame, group = 1,2, ...
- rbind 4 data.frame in one go
Here's an example using lattice
dat <- data.frame(x = rep(sample(1:100,size=10),4),
y = rep(rnorm(40)),
group = rep(1:4,each =10))
xyplot(y~x|group, ## conditional formula to get 4 panels
data =dat, ## data
type='l', ## line type for plot
groups=group, ## group ti get differents colors
layout=c(2,2)) ## equivalent to par or layout
PS: no need to install bundles. To xyplot
the default settings for clicks same
(the same links for all panels). You can change it like:
xyplot(y~x|group, data =dat, type='l',groups=group,
layout=c(2,2), scales =list(y = list(relation='free')))
EDIT
There are a large number of arguments for building a grid function, allowing you to control many plot details, here, for example, I configure:
- Text used for labels and headings for stripes
- Size and placement of axis tick marks,
-
The size of the gaps between columns and rows of panels.
xyplot(y~x|group, data =dat, type='l',groups=group, between =list(y=2,x=2), layout=c(2,2), strip = myStrip, scales =list(y = list(relation='same',alternating= c(3,3))))
Where
myStrip <- function(var.name,which.panel, which.given,...) {
var.name <- paste(var.name ,which.panel)
strip.default(which.given,which.panel,var.name,...)
}
EDIT . To get graphic plots with graphic plot, you can try the following:
xyplot(y~x|group, data =dat, type='l',groups=group,
between=list(y=2,x=2),
layout=c(2,2),
strip =FALSE,
xlab=c('a','a'),
xlab.top=c('a','a'),
ylab=c('b','b'),
ylab.right = c('b','b'),
main=c('plot1','plot2'),
sub=c('plot3','plot4'),
scales =list(y = list(alternating= c(3,3)),
x = list(alternating= c(3,3))))
source to share
Although the answer has already been selected, this answer uses ggplot
, not the R base, which is what the OP wants. While it's ggplot
really nice to plot quickly, you often need finer control over the plots for publishing than ggplot
. This is where the basic plot excels.
I would suggest reading Sean Anderson 's explanation of magic that can be used cleverly par
, as well as a few other nice tricks like using layout()
and split.screen()
.
Using his explanation, I came up with the following:
# Assume that you are starting with some data,
# rather than generating it on the fly
data_mat <- matrix(rnorm(600), nrow=4, ncol=150)
x_val <- iris$Petal.Width
Ylim <- c(-3, 3)
Xlim <- c(0, 2.5)
# You'll need to make the ylimits the same if you want to share axes
par(mfrow=c(2,2))
par(mar=c(0,0,0,0), oma=c(4,4,0.5,0.5))
par(mgp=c(1, 0.6, 0.5))
for (n in 1:4) {
plot(x_val, data_mat[n,], "p", asp=1, axes=FALSE, ylim=Ylim, xlim=Xlim)
box()
if(n %in% c(1,3)){
axis(2, at=seq(Ylim[1]+0.5, Ylim[2]-0.5, by=0.5))
}
if(n %in% c(3,4)){
axis(1, at=seq(min(x_val), max(x_val), by=0.1))
}
}
There is one more job here. As in the OP, the data appears squashed in the middle. It would of course be good to adjust the situation to use the entire build area.
source to share