When plotting a mixture of continuous and factor rasters with rasterVis::levelplot using print(..., more=TRUE), the height and width of the panels is inconsistent. This seems to be due to differences in the width of the colorkey (legend), and the colorkey's tick labels.
For example:
library(raster)
library(rasterVis)
r1 <- raster(matrix(runif(100), 10))
r2 <- as.factor(raster(matrix(rbinom(100, 1, 0.8), 10)))
levels(r2)[[1]]$name <- c('gray', 'lightblue')
p1 <- levelplot(r1, margin=FALSE, scales=list(draw=FALSE),
at=seq(0, 1, length.out=100))
p2 <- levelplot(r2, scales=list(draw=FALSE),
col.regions=c('gray90', 'lightblue'))
print(p1, split=c(1, 1, 1, 2), more=TRUE)
print(p2, split=c(1, 2, 1, 2))
Is there a way to modify the trellis graphical parameters (e.g., layout widths/heights?) to achieve consistently sized plots, such that it looks more like the layout used when plotting a RasterStack?
Or is there an alternative way of combining these rasters, which would scale to layouts with multiple columns as well as multiple rows? (Other plotting frameworks are fine, but base plot doesn't readily support factor rasters.)
You can use ?c.trellis function from the latticeExtra package.
library(latticeExtra)
c(p1, p2, layout = c(1, 2), merge.legends = TRUE)
However, in your case the legend overlaps slightly. If the order of the plots is not relevant you can instead use
c(p2, p1, layout = c(1, 2), merge.legends = TRUE)
Alternatively you can add some space to your first plot as follows.
p1 <- levelplot(r1, margin=FALSE, scales=list(draw=FALSE),
at=seq(0, 1, length.out=100),
par.settings = rasterTheme(layout.widths = list(key.right = 1.5)))
c(p1, p2, layout = c(1, 2), merge.legends = TRUE)
Related
I am trying to combine legends of a series of 22 plots but I simply can't make it work.
My legend represents months and each plot is different, some have info in just some of the months.
So the final legend is showing only the legend of my first plot (that only has data in 2 months), and I have no other plot with data in all the months to be used for the legend to be complete.
Any way to fix this?
Trying this:
ggarrange(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12,
p13, p14, p15, p16, p17, p18, p19, p20, p21, p22,
ncol=6, nrow=4, common.legend = TRUE, legend="bottom")
https://oregonstate.box.com/s/gxgo93mpva9wdx9lcfem26cer8ixpcs8
for a better understanding of the obstacles, it is interesting that you provide some reproducible example, but by your text I think it is possible to understand the basics of your problem.
Recently I saw something similar, and managed to solve in a not so elegant way.
Perform the organization of all your plots without legend.
From p1 top22 with show.legend = FALSE for each 'row' (nrow) of your plot set. Then transform into grob.
library(cowplot)
obj1 <- cowplot::plot_grid(p1, ..., p6, align = "hv", nrow = 1)
obj2 <- cowplot::plot_grid(p7, ..., p12, align = "hv", nrow = 1)
obj3 <- cowplot::plot_grid(p13, ..., p18, align = "hv", nrow = 1)
obj4 <- cowplot::plot_grid(p19, ..., p22, NULL, NULL, align = "hv", nrow = 1)
g1grob <- ggplot2::ggplotGrob(obj1)
g2grob <- ggplot2::ggplotGrob(obj2)
g3grob <- ggplot2::ggplotGrob(obj3)
g4grob <- ggplot2::ggplotGrob(obj4)
You can continue to use ggarrange, but not necessarily mind you need to share the legend. I suggest observing the plot_grid function of the cowplot package, using the align = "hv" argument (link).
You need to create an object (objx) so that it has the most complex caption possible, addressing all your points of interest. For from this object you should get its caption using the cowplot package with the get_legend (link) function.
objx <- ggplot2::ggplot(df) +
... +
theme(legend.position = "bottom")
legend <- cowplot::get_legend(objx)
Once this is done, it is only necessary to unite the objects of interest, the plot (obj) and the legend (legend).
lay <- rbind(c(1, 1, 1, 1, 1, 1),
c(2, 2, 2, 2, 2, 2),
c(3, 3, 3, 3, 3, 3),
c(4, 4, 4, 4, 4, 4))
plot_leila <- gridExtra::grid.arrange(g1grob, g2grob, g3grob, g4grob,
layout_matrix = lay)
lay <- rbind(c(rep(1,12)),
c(2))
plot_leila <- gridExtra::grid.arrange(plot_leila, legend,
layout_matrix = lay)
plot_leila
Eventual misalignments can occur, but with patience and testing the packages you will surely get a good result.
I am having extreme difficulty in making my axes logarithmic/have custom tick marks in plot3d using the rgl package. I've tried using the "log='xy'" command in my code just like you would in the basic plot function, and I have tried to create custom tick marks using rgl.bbox. My y axis is plotting fine but my x and z are not cooperating. I cannot get anything to work. Any ideas? Below is my data, code, and a picture of the result I'm getting. I should also add that I'm basically plotting multiple 2d scatterplots in 3d using an arbitrary z value to separate the individual 2d plots.
https://www.dropbox.com/s/wv24rmnyalm3vvc/scattertest.csv?dl=0
#!/usr/bin/env Rscript
library("rgl")
data <- read.csv("~/Desktop/scattertest.csv", header=TRUE, fill=TRUE, sep=',')
x <- names(data[2])
y <- names(data[3])
z <- names(data[4])
plot3d(data[[x]], data[[z]], data[[y]], type="s", size=0.75, lit=FALSE, axes=FALSE,
xlab="rpmn", ylab="round", zlab="rpmt", log="xz",
xmin=c(0.1, 10^6), ymin=c(1,4), zmin=c(0.1, 10^6))
rgl.bbox(color="grey50", emission="grey50",
xat = c(0.1, 1, 10, 100, 10^3, 10^4, 10^5, 10^6), yat = c(1, 2, 3, 4), zat = c(0.1, 1, 10, 100, 10^3, 10^4, 10^5, 10^6),
xlen=8, ylen=4, zlen=8)
There's no support for log="xy" in plot3d(), you'll need to do the transformation yourself.
Your code asks for logarithmic labels, but you aren't doing the logarithmic transformation, so it's not working. You need to rescale the data as well.
You didn't post a reproducible example, but it's easy to create one:
x <- rlnorm(20, 2, 6)
y <- runif(20, 1, 4)
z <- rlnorm(20, 2, 6)
xyz <- cbind(log(x), y, log(z))
plot3d(xyz, axes = FALSE)
ticks <- 10^((-1):6)
bbox3d(xat = log(ticks), xlab = ticks, yat = pretty(1:4),
zat = log(ticks), zlab = ticks,
color="grey50", emission="grey50")
Using the sample data below, how can I generate rasters and spatial points plot with the same colorkey as in the "manually" joined plot shown below?
library(rasterVis)
library(raster)
library(colorRamps)
col=colorRampPalette(matlab.like2(255))
s <- stack(replicate(2, raster(matrix(runif(100), 10))))
xy <- data.frame(coordinates(sampleRandom(s, 10, sp=TRUE)),
z1=runif(10), z2=runif(10))
levelplot(s, margin=FALSE, at=seq(0, 1, 0.05),col.regions=col)
x=xy$x;y=xy$y;z=xy$z1
levelplot(z ~ x + y,contour=F, panel = panel.levelplot.points,
margin=FALSE,col.regions=col,
par.settings=list(axis.line=list(lwd=3), strip.border=list(lwd=3)),
cex=1.4, scales=list(x=list(cex=1.7),y=list(cex=1.7)),xlab=list(label="Longitude",cex=2),
ylab=list(label="Latitude",cex=2))
Thanks to #fdestch I was able to generate the following plot using:
latticeCombineGrid(mget(rep("pp", 24)), layout = c(3, 8))
following my comments on printing multiple plots with the same colorkey.
An issue that remains to be clarified:
1) How can one decide on the order of panels? That is, which row & column to place a particular plot just as in levelplot using index.cond.
First of all, you should probably make sure that the breaks in the points plot are identical with those defined in the first levelplot.
## raster plot with colorkey disabled
pr <- levelplot(s, margin = FALSE, at = seq(0, 1, 0.05), col.regions = col,
colorkey = FALSE, xlab = list("Longitude", col = "transparent"))
## points plot
pp <- levelplot(z ~ x + y, panel = panel.levelplot.points, cex = 1.4,
contour = FALSE, margin = FALSE, col.regions = col,
colorkey = list(at = seq(0, 1, .05), width = .6, height = .6),
xlab = "Longitude", ylab = "Latitude")
Please note the definition of a transparent xlab when creating the raster plot. This little workaround comes in quite handy when using downViewport later on to ensure that the actual plot boundaries of pr and pp overlap (feel free to run grid.rect() right after print(pr, newpage = FALSE) to see what I mean).
The actual plot arrangement can then easily be achieved by using viewports from the grid package.
library(grid)
library(lattice)
## initialize new grid device
grid.newpage()
## add raster plot
vp1 <- viewport(x = 0, y = 0, width = .5, height = 1,
just = c("left", "bottom"))
pushViewport(vp1)
print(pr, newpage = FALSE)
## add points plot
downViewport(trellis.vpname("page"))
vp2 <- viewport(x = 1, y = 0, width = .75, height = 1,
just = c("left", "bottom"))
pushViewport(vp2)
print(pp, newpage = FALSE)
Here is my solution using latticeExtra::c.trellis:
library(raster)
library(rasterVis)
s <- stack(replicate(2, raster(matrix(runif(100), 10))))
xy <- data.frame(coordinates(sampleRandom(s, 10, sp=TRUE)),
z1=runif(10), z2=runif(10))
## Define theme and breaks
myTheme <- BTCTheme()
my.at <- seq(0, 1, 0.05)
Plot the Raster* object, using rasterVis::levelplot:
p1 <- levelplot(s, margin=FALSE,
at = my.at,
par.settings = myTheme)
Plot the points, using lattice::levelplot:
p2 <- levelplot(z1 ~ x + y, data = xy,
at = my.at,
panel = panel.levelplot.points,
par.settings = myTheme)
Join them with latticeExtra::c.trellis:
p3 <- c(p1, p2, layout = c(3, 1))
Unfortunately, c.trellis does not assign the strip labels correctly, so you have to define them directly:
update(p3,
strip = strip.custom(factor.levels = c(names(s), "Points")))
I'd like to do a vertical histogram. Ideally I should be able to put multiple on a single plot per day.
If this could be combined with quantmod experimental chart_Series or some other library capable of drawing bars for a time series that would be great. Please see the attached screenshot. Ideally I could plot something like this.
Is there anything built in or existing libraries that can help with this?
I wrote something a year or so ago to do vertical histograms in base graphics. Here it is, with a usage example.
VerticalHist <- function(x, xscale = NULL, xwidth, hist,
fillCol = "gray80", lineCol = "gray40") {
## x (required) is the x position to draw the histogram
## xscale (optional) is the "height" of the tallest bar (horizontally),
## it has sensible default behavior
## xwidth (required) is the horizontal spacing between histograms
## hist (required) is an object of type "histogram"
## (or a list / df with $breaks and $density)
## fillCol and lineCol... exactly what you think.
binWidth <- hist$breaks[2] - hist$breaks[1]
if (is.null(xscale)) xscale <- xwidth * 0.90 / max(hist$density)
n <- length(hist$density)
x.l <- rep(x, n)
x.r <- x.l + hist$density * xscale
y.b <- hist$breaks[1:n]
y.t <- hist$breaks[2:(n + 1)]
rect(xleft = x.l, ybottom = y.b, xright = x.r, ytop = y.t,
col = fillCol, border = lineCol)
}
## Usage example
require(plyr) ## Just needed for the round_any() in this example
n <- 1000
numberOfHists <- 4
data <- data.frame(ReleaseDOY = rnorm(n, 110, 20),
bin = as.factor(rep(c(1, 2, 3, 4), n / 4)))
binWidth <- 1
binStarts <- c(1, 2, 3, 4)
binMids <- binStarts + binWidth / 2
axisCol <- "gray80"
## Data handling
DOYrange <- range(data$ReleaseDOY)
DOYrange <- c(round_any(DOYrange[1], 15, floor),
round_any(DOYrange[2], 15, ceiling))
## Get the histogram obects
histList <- with(data, tapply(ReleaseDOY, bin, hist, plot = FALSE,
breaks = seq(DOYrange[1], DOYrange[2], by = 5)))
DOYmean <- with(data, tapply(ReleaseDOY, bin, mean))
## Plotting
par(mar = c(5, 5, 1, 1) + .1)
plot(c(0, 5), DOYrange, type = "n",
ann = FALSE, axes = FALSE, xaxs = "i", yaxs = "i")
axis(1, cex.axis = 1.2, col = axisCol)
mtext(side = 1, outer = F, line = 3, "Length at tagging (mm)",
cex = 1.2)
axis(2, cex.axis = 1.2, las = 1, line = -.7, col = "white",
at = c(75, 107, 138, 169),
labels = c("March", "April", "May", "June"), tck = 0)
mtext(side = 2, outer = F, line = 3.5, "Date tagged", cex = 1.2)
box(bty = "L", col = axisCol)
## Gridlines
abline(h = c(60, 92, 123, 154, 184), col = "gray80")
biggestDensity <- max(unlist(lapply(histList, function(h){max(h[[4]])})))
xscale <- binWidth * .9 / biggestDensity
## Plot the histograms
for (lengthBin in 1:numberOfHists) {
VerticalHist(binStarts[lengthBin], xscale = xscale,
xwidth = binWidth, histList[[lengthBin]])
}
Violin plots might be close enough to what you want. They are density plots that have been mirrored through one axis, like a hybrid of a boxplot and a density plot. (Much easier to understanding by example than description. :-) )
Here is a simple (somewhat ugly) example of the ggplot2 implementation of them:
library(ggplot2)
library(lubridate)
data(economics) #sample dataset
# calculate year to group by using lubridate's year function
economics$year<-year(economics$date)
# get a subset
subset<-economics[economics$year>2003&economics$year<2007,]
ggplot(subset,aes(x=date,y=unemploy))+
geom_line()+geom_violin(aes(group=year),alpha=0.5)
A prettier example would be:
ggplot(subset,aes(x=date,y=unemploy))+
geom_violin(aes(group=year,colour=year,fill=year),alpha=0.5,
kernel="rectangular")+ # passes to stat_density, makes violin rectangular
geom_line(size=1.5)+ # make the line (wider than normal)
xlab("Year")+ # label one axis
ylab("Unemployment")+ # label the other
theme_bw()+ # make white background on plot
theme(legend.position = "none") # suppress legend
To include ranges instead of or in addition to the line, you would use geom_linerange or geom_pointrange.
If you use grid graphics then you can create rotated viewports whereever you want them and plot to the rotated viewport. You just need a function that will plot using grid graphics into a specified viewport, I would suggest ggplot2 or possibly lattice for this.
In base graphics you could write your own function to plot the rotated histogram (modify the plot.histogram function or just write your own from scratch using rect or other tools). Then you can use the subplot function from the TeachingDemos package to place the plot wherever you want on a larger plot.
I'm trying to put multiple lattice plots in one window using levelplot by setting par(mfrow=c(2,1)) but it seems to be ignoring this.
Is there a particular function for setting multiple plots in lattice?
The 'lattice' package is built on the grid package and attaches its namespace when 'lattice' loaded. However, in order to use the grid.layout function, you need to explicitly load() pkg::grid. The other alternative, that is probably easier, is the grid.arrange function in pkg::gridExtra:
install.packages("gridExtra")
require(gridExtra) # also loads grid
require(lattice)
x <- seq(pi/4, 5 * pi, length.out = 100)
y <- seq(pi/4, 5 * pi, length.out = 100)
r <- as.vector(sqrt(outer(x^2, y^2, "+")))
grid <- expand.grid(x=x, y=y)
grid$z <- cos(r^2) * exp(-r/(pi^3))
plot1 <- levelplot(z~x*y, grid, cuts = 50, scales=list(log="e"), xlab="",
ylab="", main="Weird Function", sub="with log scales",
colorkey = FALSE, region = TRUE)
plot2 <- levelplot(z~x*y, grid, cuts = 50, scales=list(log="e"), xlab="",
ylab="", main="Weird Function", sub="with log scales",
colorkey = FALSE, region = TRUE)
grid.arrange(plot1,plot2, ncol=2)
The Lattice Package often (but not always) ignores the par command, so i just avoid using it when plotting w/ Lattice.
To place multiple lattice plots on a single page:
create (but don't plot) the lattice/trellis plot objects, then
call print once for each plot
for each print call, pass in arguments for (i) the plot; (ii)
more, set to TRUE, and which is only passed in for the initial call to print, and (iii) pos, which gives the position of each plot on the page specified as x-y coordinate pairs for the plot's lower left-hand corner and upper right-hand
corner, respectively--ie, a vector with four numbers.
much easier to show than to tell:
data(AirPassengers) # a dataset supplied with base R
AP = AirPassengers # re-bind to save some typing
# split the AP data set into two pieces
# so that we have unique data for each of the two plots
w1 = window(AP, start=c(1949, 1), end=c(1952, 1))
w2 = window(AP, start=c(1952, 1), end=c(1960, 12))
px1 = xyplot(w1)
px2 = xyplot(w2)
# arrange the two plots vertically
print(px1, position=c(0, .6, 1, 1), more=TRUE)
print(px2, position=c(0, 0, 1, .4))
This is simple to do once you read ?print.trellis. Of particular interest is the split parameter. It may seem complicated at first sight, but it's quite straightforward once you understand what it means. From the documentation:
split: a vector of 4 integers, c(x,y,nx,ny), that says to position the current plot at the x,y position in a regular array of nx by ny plots. (Note: this has origin at top left)
You can see a couple of implementations on example(print.trellis), but here's one that I prefer:
library(lattice)
# Data
w <- as.matrix(dist(Loblolly))
x <- as.matrix(dist(HairEyeColor))
y <- as.matrix(dist(rock))
z <- as.matrix(dist(women))
# Plot assignments
pw <- levelplot(w, scales = list(draw = FALSE)) # "scales..." removes axes
px <- levelplot(x, scales = list(draw = FALSE))
py <- levelplot(y, scales = list(draw = FALSE))
pz <- levelplot(z, scales = list(draw = FALSE))
# Plot prints
print(pw, split = c(1, 1, 2, 2), more = TRUE)
print(px, split = c(2, 1, 2, 2), more = TRUE)
print(py, split = c(1, 2, 2, 2), more = TRUE)
print(pz, split = c(2, 2, 2, 2), more = FALSE) # more = FALSE is redundant
The code above gives you this figure:
As you can see, split takes four parameters. The last two refer to the size of your frame (similar to what mfrow does), whereas the first two parameters position your plot into the nx by ny frame.