Base R graphics are very powerful, but the many different
possibilities and graphical parameters can be overwhelming. Package
prepplot has been developed for making it easier to
customize figure regions for base R graphics. The concept is to prepare
the figure region with prepplot, and to then add further
graphical elements as desired; this can be done with any function that
allows adding to an existing plot.
Inspired by Rahlf (2017), the default choice consists of a lightly
colored background, and axis lines, tick marks and boxes are avoided.
Orientation in the plot region can be supported by grid lines or
background stripes with reasonable defaults regarding line type and
color; customization is straightforward and, for example, allows to
modify defaults such that a ggplot2-like look is
obtained. Figure @ref(fig:ggplot) shows a scatter plot on default
prepplot background with grid lines and on a
prepplot customization similar to the ggplot
default, and compares it to a default ggplot scatter plot.
The code for this figure is shown in the examples section.
prepplot and ggplot
prepplot consists of only two user-visible
functions, prepplot and stripes. Function
prepplot creates a plotting region for an x range and a y
range, taking care of background color, and optionally specifying
various further design and annotation elements of the figure region.
Function prepplot has many arguments, most of which have
reasonable defaults and are thus optional. The documentation groups
related arguments together in order to support locating arguments. While
it is possible and convenient to specify most plot annotations within
function prepplot, it is also possible to use
prepplot for background color and stripes only and to do
all further annotation with functions like mtext,
title, axis, stripes (from
prepplot) or abline.
The only compulsory arguments xlim and ylim
specify the data limits for the two axes. They can either contain
numeric vectors of length two like usual, or longer numeric vectors from
which the length two vectors are calculated by function
range. Depending on the choices of the arguments
xaxs and yaxs (or their values in the graphics
state, as set/queried by the par function), the plot region
in the respective direction is 4% larger than specified by these limits
(setting "r") or exactly matches these limits (setting
"i").
prepplot requires numeric xlim and
ylim specifications; it currently does not work with
logarithmic axes. For a date, time or date-time axis, the user has to
take care of conversion to numeric values before using
prepplot (see the last example in the examples section).
Axes with character-valued ticks are always based on numeric tick
position specifications; see e.g. Figure @ref(fig:barplots) for a
barplot example.
The code below creates a default plot region with the default choice
for xaxs and yaxs (assuming that the default
has not been changed in par) and both choices modified to
exact axis limits. The result is shown in Figure @ref(fig:default); the
bottom margin has been reduced, because it would produce a lot of empty
space between the figure and its caption.
x <- 0:10
y <- -5:5
par(mfrow=c(1,2), mar=c(2.1, 4.1, 4.1, 2.1))
prepplot(x, y, main='default axis extension', bg="grey92", cex.axis=0.8, mgpx=c(2,0.25,0))
points(x,y, pch=16)
mtext('xaxs="r"', line=0.25, family="mono")
prepplot(x, y, xaxs="i", yaxs="i", main='limits match axis extent', bg="grey92", cex.axis=0.8, mgpx=c(2,0.25,0))
points(x,y, pch=16, xpd=TRUE)
mtext('xaxs="i"', line=0.25, family="mono")
plot regions in prepplot, depending on xaxs
and yaxs, where xlim and ylim
correspond to the data ranges. The only structural element is the light
grey background color.
prepplot obeys to most general graphical parameters as
set with function par (see ?par),
e.g. mar. Where further graphical parameters can be
modified from within prepplot (bg,
xaxs, yaxs, xaxt,
yaxt, cex), they default to the current
par settings; this is partly also true for parameters that
are set for specific components, like mgp, lwd
or col; las is an exception: it is per default
1 in prepplot, regardless of par("las").
prepplot does not actively change the graphics state (as
queried by par()).
For default plot regions,
las=1), i.e., easily readable from the reader’s
perspective (the y axis label, if specified, is parallel to the y
axis),grey20),lwd.axis=0),lwd.axis to a positive value, the default border color
becomes the annotation color specified with col.axis),xlab and ylab
are NULL),Default plot regions are therefore very unobtrusive: they are neither highlighted by a background color nor bounded by a border line nor indicated by visible axis lines or tick marks. In most cases, one will want to activate one or more structural elements:
gridx, gridy), possibly major
and minor;bg (e.g. to “grey92”, which used to be its default in
version~0.7 of the package): bg in prepplot
affects the plot region only, which is the usr area
calculated by R from the axis limits by extending them by 4% (arguments
xaxs and/or yaxs equal to "r" or
NULL, assuming unchanged par specifications)
or not at all (arguments xaxs and/or yaxs
equal to "i");stripex, stripey) as
an alternative to grid lines;prepplot by setting positive lwd.axis (box and
axis lines and ticks) or with subsequent axis and/or
box statements.xaxs and yaxs) and the
background areaIf xaxs="r", the horizontal extent of the plot region is
4% larger than the range obtained from the xlim
specification; if xaxs="i", the x extent of the plot region
exactly coincides with range(xlim). If
xaxs=NULL, xaxs is determined from the current
graphics state par("xaxs"). yaxs behaves
analogously for the vertical extent of the plot region.
The background color colors the plot region, whose coordinates can
also be queried by par("usr"), and the plot region is
framed by the color specified with the border argument. If
an axis line is not requested (default, lwd.axis=0), the
default border color equals the background color; otherwise, the default
border color equals the axis color.
Note that xaxs="i" or yaxs="i" should
usually not be specified, if data values equal to one of the axis limits
exist (see Figure @ref(fig:default)). On the other hand, if the axis
ranges have been chosen liberally, they may be a good choice, because
the orientation marks often look more beautiful.
prepplot offers stripes and / or grid lines. Respective
arguments are handled separately for x and y axes (e.g.,
gridx, stripesx). Stripes and grid lines
cannot be simultaneously requested for the same axis within function
prepplot; if both are desired, stripes should be done with
prepplot, and subsequently grid lines can be added with
function abline. The simplest use is to activate grid lines
or stripes by, e.g., gridx=TRUE or
stripesy=TRUE; in that case, axis tick positions define the
locations of grid lines or stripes. For stripes, swapping the colors
between bg and col.stripes can make a big
difference, as can be seen in Figure @ref(fig:orient) by comparing the
top and bottom chart in the middle column.
If grid lines are activated (gridx and/or
gridy set to TRUE), these are dotted, whenever
there are no minor grid lines and solid otherwise. Their default color
is darker than the default background color but lighter than the default
axis and annotation (grey75). Per default, if activated,
minor grid lines are half as wide as major grid lines, whose width is
governed by par("lwd") per default. If stripes are
activated without specifying specific stripes boundaries
(stripesx and/or stripesy set to
TRUE), the background is interrupted by stripes (default
color light grey, grey90) between tick positions. Depending
on the device’s default background color, this default may or may not be
adequate.
Apart from using stripes or grid lines for orientation w.r.t. the
axis scale, they can also be used for displaying information independent
of the tick positions, e.g. grid lines for time points of interest or
upper limits of harmful substances, or stripes for indicating time
periods of interest in time series presentations (see, e.g., the last
example). Such stripes or grid lines can be requested with function
prepplot, or subsequently with functions
abline or stripes.
Per default, prepplot displays tick position labelings
without ticks, andif axis labels are givenaxis
labels. Axis annotation color is controlled by col.axis,
which defaults to grey20, i.e. a dark grey. Tick position
labels default to tick positions, but can be modified by
xticklabs/yticklabs, e.g. for character or
date values.
If default tick positions from R’s calculations are not appropriate,
xticks can be used for specifying custom tick positions for
the horizontal axis. If xticks is not given (i.e. remains
NULL), a vector-valued gridx or (if that is
not available either) stripesx determines the x tick
positions; default R tick positions are only calculated if neither of
these is given. By giving both xticks and one of
gridx or stripesx, stripes or lines
corresponding to the horizontal axis can be specified independent of
tick mark positions, as mentioned before. yticks and
friends are completely analogous.
The distance of the tick position labels and axis labels from the
plot region (i.e. the colored background, which coincides with the
rectangle specified by par("usr") coordinates) is
controlled by the mgp graphical parameter, a three-element
vector (default c(3, 1, 0)), which gives the number of
lines the axis label, the tick mark labels and the axis line are outside
the usr rectangle (the specified line refers to the closest
position, wide tick position labels will extend into the margin much
more than the specified value). Options mgpx and
mgpy can be used to control these positionings independent
of the current par("mgp") setting and separately for the
two axes; both default to par("mgp"), and mgpy
defaults to mgpx, if that is specified. One should always
think about mgp in relation to the margin settings
(par("mar")); in the author’s opinion, both
mgp and mar settings are often too large, and
occasionally much too narrow (e.g. for the left margin of horizontal
barplots). Whether or not the margin size is appropriate also depends on
whether or not non-NULL axis labels (xlab and
ylab) are specified.
Per default, there is no axis line and ticks at the tick positions
are not drawn. If lwd.axis is set to a positive value, a
typical R axis is drawn, per default with a same color border around the
plot region, and the axis observes parameter settings like
par("tcl"); the ... argument of function
prepplot can also be used for specifying such parameters,
e.g. tcl=-0.2 specifies shorter tick marks on the outside
of the usr area than the R default (effective only for
positive lwd.axis).
Suppressing axes can also be useful, e.g. when subsequently using functions for which it is difficult to suppress axes; an example of this is given in Section 3.3.
The argument of prepplot allows to specify a title and /
or sub title (with main and / or sub), and
further arguments like col.main or font.main
can be used for their formatting. Alternatively and even additionally,
subsequent annotation with axis, mtext or
title can be used.
prepplot allows to add arrows to the plot region: option
axis.arrow takes care of that. The default axes can be
provided with an arrow by setting axis.arrow to
TRUE; this draws axes with width equal to
1.5*lwd.grid, i.e. lwd.axis=0 does not prevent
axis arrows from being drawn. With axis.arrow=TRUE, it is
usually advisable to keep the respective xaxs or
yaxs at its default "r" (or
NULL).
A two-element numeric vector axis.arrow (instead of a
logical) requests axes with arrows in positions given by the two vector
elements. prepplot keeps axis annotation at the margin of
the plot region (oberving mgp specification), regardless
where axis arrows are placed. An example with default axes with arrows
is in the prepplot help, and Section 3.5 of this vignette
has an example with a differently placed axis arrow.
The examples in this section illustrate a few uses of function
prepplot.
The following simple example uses major and minor grid lines on white background. The result is shown in Figure @ref(fig:majorminor).
par(mar=par("mar")-1, mgp=c(2,0.3,0))
prepplot(c(0,10), c(25,30), yticks=25:30, bg="white",
lwd.grid.minor=1, gridyminor=4, gridx=TRUE,
xlab="x axis label", ylab="y axis label",
main="Major/minor grid on white background")Major and minor grid lines on white background
The overview section already showed the default gridlines and
customized gridlines mimicking ggplot style
(Figure @ref(fig:ggplot)). The code for that example is shown below;
note that it uses package gridBase for placing the
grid-based ggplot figure in the third position of the base
graphics layout.
par(mfrow=c(1, 3), mgp=c(2,0.2,0), mar=c(3.1,4.1,4.1,1.6))
## prepplot grid default
prepplot(swiss$Education, swiss$Fertility,
## manage grid
gridx=TRUE, gridy=TRUE,
## annotation
xlab="Education", ylab="Fertility",
main="prepplot default grid")
points(Fertility ~ Education, swiss, pch=16, col="grey20", cex=1.3)
## prepplot ggplot like
prepplot(swiss$Education, swiss$Fertility,
## background color (like in version 0.7 of prepplot)
bg = "grey92",
## manage grid
gridx=TRUE, gridy=TRUE, col.grid="white", lty.grid="solid",
## manage axis annotation
cex=0.75, cex.lab=1.35, col.axis="black",
xlab="Education", ylab="Fertility",
## manage title
main="prepplot ggplot like",
cex.main=1.75, font.main=1, col.main="black")
points(Fertility ~ Education, swiss, pch=16, cex=1.3)
## add the default ggplot plot
## following the answer in StackOverflow question
# https://stackoverflow.com/questions/14124373/
# combine-base-and-ggplot-graphics-in-r-figure-window
plot.new()
vps <- baseViewports()
pushViewport(vps$figure) ## in the third plot region
vp1 <-plotViewport(c(0,0.5,1,0.25)) ## create new vp with margins
p <- ggplot(swiss, aes(Education, Fertility)) +
geom_point() + labs(title="ggplot default")
print(p, vp = vp1)It will often be of interest to adapt some par settings
before calling function prepplot, e.g. mar,
mgp, tcl, xaxs,
yaxs, … The following code exemplifies such parameter
settings, together with a selection of layouts; the results are shown in
Figure @ref(fig:parandmore).
par(mfrow=c(2,2), mar=c(3.1, 4.1, 3.1, 2.1), mgp=c(2,0.5,0), xaxs="i", yaxs="i")
prepplot(xlim=c(0,9), ylim=c(0,12), bg="grey92", col.grid="grey98",
lty.grid=1, lwd.grid=1.5, gridx=0:9, gridy=seq(0,12,2),
xlab="x", ylab="y", cex = 0.7, mgpx=c(1.5,0.25,0),
mgpy=c(1.75,0.5,0), main="ggplot style")
prepplot(xlim=c(0,9), ylim=c(0,12),
gridx=0:9, gridy=seq(0,12,2),
xlab="x", ylab="y", cex = 0.7,
main="default grid lines")
prepplot(xlim=c(0,9), ylim=c(0,12), gridx=0:9, gridy=seq(0,12,2),
xlab="x", ylab="y", cex = 0.7, lty.grid="solid",
mgpx=c(2,0.5,0), main="default solid grid lines")
prepplot(ylim=c(0,12), xlim=c(0,9), stripesx=0:9,
xlab="x", yaxt="n", cex = 0.7, mgpx=c(2,0.5,0),
main="default vertical stripes, no y axis")xaxs="i" and yaxs="i"” width=“672”
/>
Some examples with xaxs="i" and yaxs="i"
The following code creates barplots with transparent bars on striped
background. Function prepplot provides the plot region and
the x axis annotation, while the vertical axis annotation comes from
function barplot, which is called with the
add=TRUE argument. Figure @ref(fig:barplots) shows the
result.
tab <- c(one=10, two=5, three=8, four=7, five=12, six=10, seven=9)
par(mfrow=c(1,2), mar=c(3.1, 4.1, 3.1, 2.1))
prepplot(ylim=c(0,8.5), xlim=c(-0.2,13),
yaxs="i", stripesx=seq(0,12,2),
xlab="Frequency", yaxt="n", cex = 0.8, mgpx=c(2,0.5,0),
main="Default colors", cex.main=1.2)
barplot(tab, col=rgb(0.5, 0.5, 0.5, 0.7),
border=FALSE, add=TRUE, xaxt="n",las=2, horiz=TRUE,
cex.names=0.8)
hks51 <- rgb(0, 152/256, 161/256)
prepplot(ylim=c(0,8.5), xlim=c(-0.2,13),
yaxs="i",
xticks=seq(0,12,2), stripesx=TRUE,
xlab="Frequency",
bg=rgb(235/256, 246/256, 246/256),
col.stripes = rgb(190/256, 226/256, 226/256, 0.4),
col.axis=hks51, cex=0.8, col.main=hks51,
yaxt="n", tcl=-0.2, mgpx=c(2,0.5,0),
main="Beuth colors",
cex.main=1.2, col.main=hks51)
barplot(tab,
col=rgb(0,152/256,161/256,0.7), border=FALSE, add=TRUE,
xaxt="n",las=2, horiz=TRUE, cex.names=0.8, col.axis=rgb(0,152/256,161/256))Barplots on striped background, with slightly transparent bars
The following code creates scatter plots on various backgrounds,
illustrating various orientation strategies, which are on offer for
function prepplot. The result is shown in
Figure @ref(fig:orient). Note: The charts use the default axis extent
(ranges of Education and Fertility, extended
by 4%); a custom axis with well-chosen limits and suppressing extension
by 4% could yield constant widths for all stripes, also the outer ones.
Also remember the previous remark on the effect of swapping
bg and col.stripes.
par(mfrow=c(2,3), oma=c(2,0,0,0), mgp=c(2.25,0.5,0),
mar=c(3.1, 4.1, 3.1, 2.1))
## default axis system with grid lines
prepplot(xlim=swiss$Education, ylim=swiss$Fertility, mgpy=c(2.5,0.5,0),
xlab="Education", ylab="Fertility",
gridx=TRUE, gridy=TRUE, cex=0.8,
main="grid lines")
points(swiss$Education, swiss$Fertility, pch=16, col="grey20")
## stripes and grid lines
prepplot(xlim=swiss$Education, ylim=swiss$Fertility,
mgpy=c(2.5,0.5,0),
xlab="Education", ylab="Fertility",
stripesx=seq(-5,60,5), gridy=seq(30,100,10), cex=0.8,
xticks=seq(0,50,10), yticks = seq(40,90,10),
main="stripes and grid lines")
points(swiss$Education, swiss$Fertility, pch=16, col="grey20")
prepplot(xlim=range(swiss$Education), ylim=range(swiss$Fertility),
mgpy=c(2.5,0.5,0),
xlab="Education", ylab="Fertility",
gridx=seq(0,60,10), stripesy=seq(30,100,5), cex=0.8,
xticks=seq(0,50,10), yticks = seq(40,90,10),
main="stripes and grid lines")
points(swiss$Education, swiss$Fertility, pch=16, col="grey20")
## stripes only
prepplot(xlim=range(swiss$Education), ylim=range(swiss$Fertility),
mgpy=c(2.5,0.5,0), bg="grey90", col.stripes = "white",
xlab="Education", ylab="Fertility",
stripesx=seq(-5,60,5), stripesy=seq(30,100,5), cex=0.8,
xticks=seq(0,50,10), yticks = seq(40,90,10),
main="crossing stripes")
points(swiss$Education, swiss$Fertility, pch=16, col="grey20")
prepplot(xlim=range(swiss$Education), ylim=range(swiss$Fertility),
mgpy=c(2.5,0.5,0),
xlab="Education", ylab="Fertility",
stripesx=seq(-5,60,5), cex=0.8,
xticks=seq(0,50,10), yticks = seq(40,90,10),
main="only vertical stripes", bg="grey90", col.stripes = "white")
points(swiss$Education, swiss$Fertility, pch=16, col="grey20")
prepplot(xlim=range(swiss$Education), ylim=range(swiss$Fertility),
mgpy=c(2.5,0.5,0),
xlab="Education", ylab="Fertility",
stripesy=seq(30,100,5), cex=0.8,
yticks = seq(40,90,10), xticks=seq(0,50,10),
main="only horizontal stripes")
points(swiss$Education, swiss$Fertility, pch=16, col="grey20")
mtext(side=1, line=0.5,
'Some strategies are better than others ...',
cex=0.8, outer=TRUE, col="grey20")Scatter plots with different orientation mark strategies
This section exemplifies axis arrows (first example, Figure @ref(fig:stripes)), simultaneous use of stripes and grid lines on the same dimension and a date-time axis (second example, Figure @ref(fig:datetime)).
prepplot(0:10, -5:5, yticks=seq(-5,5,5),
stripesy=-5:5,
xlab="x", ylab="y", main="with stripes and arrow axis", cex=0.8, cex.main=1.2,
axis.arrow = c(0,NA),arrow.length = 0.5,arrow.width = 0.5,
lwd=2)Plot region with horizontal stripes and a horizontal arrow axis at y=0
It sometimes make sense to combine stripes and grid lines for the same dimension, e.g. on a time axis. For example, think of some technical equipment with down times shown as stripes and days separated by grid lines. Such an application is exemplified below.
par(mar=c(1.5, 3, 3, 1), mgp=c(3,0.2,0))
xlim <- as.numeric(as.POSIXlt(c("2017-12-28 18:09:46","2018-01-02 09:15:22")))
stripelims <- as.numeric(as.POSIXlt(
c("2017-12-29 10:30:00","2017-12-29 12:00:00",
"2017-12-30 10:00:00","2017-12-30 12:30:00",
"2017-12-31 10:00:00","2017-12-31 12:30:00",
"2018-01-01 10:30:00","2018-01-01 11:30:00",
"2018-01-01 16:30:00","2018-01-01 17:45:00")))
griddatts <- as.POSIXlt(c(
"2017-12-29 00:00:00", "2017-12-30 00:00:00",
"2017-12-31 00:00:00", "2018-01-01 00:00:00",
"2018-01-02 00:00:00"))
gridposs <- as.numeric(griddatts)
tickposs <- gridposs + 12*3600
tickposs <- tickposs[tickposs < xlim[2]]
prepplot(xlim=xlim, ylim=c(-5,10),
stripesx=stripelims, xaxt="n", cex=0.7,
gridy=TRUE, gridyminor=4,
main="Date-time data with stripes and grid lines on same axis")
abline(v=gridposs, col="grey20")
axis(side=1, at=tickposs,
labels=as.Date(as.POSIXlt(tickposs, origin="1970-01-01 00:00.00 UTC")),
lwd=0, cex.axis=0.7)A time series. Stripes show down times, vertical lines show day boundaries
Murrell, P. (2011). R graphics. CRC, Boca Raton.
Rahlf, T. (2017). Data visualisation with R. Springer, New York.