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:

enter image description here

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:

enter image description here

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.

+3


source to share


3 answers


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.

+2


source


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

      

enter image description here

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,...)
  }

      

enter image description here

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))))

      

enter image description here

+3


source


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))
  }
}

      

Plot with shared margins

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.

+2


source







All Articles