Add sp.points key to levelplot colorkey - r

Is it possible to add the key for, e.g., an sp.points layer, to the colorkey generated by levelplot?
Take the following example:
library(rasterVis)
library(latticeExtra)
library(sp)
r <- as.factor(raster(matrix(rbinom(100, 1, 0.5), 10)))
levels(r)[[1]] <- data.frame(ID=0:1, z=c('a', 'b'))
p <- SpatialPoints(matrix(runif(20), 10))
levelplot(r, margin=list(draw=FALSE), scales=list(draw=FALSE),
col.regions=c('white', 'gray90')) +
latticeExtra::layer(sp.points(p, pch=20, col=1))
I would like to add a key entry for the points, below the existing colorkey.
A kludgy solution is to add a key to the levelplot call as follows, adjusting the x and y values until it's in the desired location, but (1) finding the right x and y values is a pain, requiring interaction, (2) the right padding doesn't resize to accommodate the key, and (3) the font size is not automatically scaled to be consistent with the colorkey.
k <- list(x = 1.02, y = 0.4, corner = c(0, 0), points=list(pch=20, col=1),
text=list('foo', cex=0.9))
levelplot(r, margin=list(draw=FALSE), scales=list(draw=FALSE),
col.regions=c('white', 'gray90'), key=k) +
latticeExtra::layer(sp.points(p, pch=20, col=1))
Assuming I need to stick with lattice graphics, what's the best way to overcome the issues I listed above?

Although it does not solve all the issues you raised, maybe the latticeExtra::mergedTrellisLegendGrob function is useful for you:
p1 <- levelplot(r, scales=list(draw=FALSE),
col.regions=c('white', 'gray90'))
myPoints <- SpatialPoints(matrix(runif(20), 10))
p2 <- spplot(myPoints, pch=20)
## Merge graphics
p <- p1 + p2
## Merge legends
l1 <- p1$legend$right
l2 <- p2$legend$bottom
ll <- mergedTrellisLegendGrob(l1, l2)
p$legend$right$fun <- ll
p

Related

How to add miniature to plot and repeat this for multiple plots side by side?

There's a nice answer around to plot a miniature plot within a plot. I wrapped it in a function which works fine for a single plot.
myPlot <- function(x, y) {
# main plot
plot(x)
# calculate position of inset
pp <- par("plt")
x0 <- pp[2] - (pp[2] - pp[1]) * 0.225
x1 <- pp[2] - .01
y0 <- pp[4] - (pp[4] - pp[3]) * 0.225
y1 <- pp[4] - .01
# set position for inset
op <- par(fig=c(x0, x1, y0, y1), mar=c(0, 0, 0, 0), new=TRUE)
# add inset grey background
plot.new()
u <- par("usr")
rect(u[1], u[2], u[4], u[3], col="grey80")
# add inset
par(new=TRUE)
plot(y, col=2)
par(op)
}
myPlot(x, y)
However, when I useMap to loop over several data lists, in order to make multiple plots of this type side by side, there seems to be a mess with the pars. The miniature appears as a new plot and not within the main plot. Also a new device is opened after one iteration (i.e. old plot gets overwritten).
op1 <- par(mfrow=c(1, 2))
Map(function(x, y) myPlot(x, y), list(d0, d0), list(d0_inset, d0_inset))
par(op1)
When I use Map(function(x, y) myPlot(x, y), list(d0, d0), list(d0_inset, d0_inset)) alone, though, there are two perfect plots in the plot queue (of RStudio). Thus the plot.new() and par(new=TRUE) might not be the issue here.
What I actually want is this:
myPlot() should throw a number of main plots with miniatures inside corresponding to the length of the data lists when using Map and fit it into the par(mfrow=...).
Does anyone have a clue how to solve this using base R functionalities?
Data:
x <- data.frame(x = rnorm(150, sd=5), y = rnorm(150, sd=5))
y <- data.frame(x = rnorm(1500, sd=5), y = rnorm(1500, sd=5))
There's a couple of points here Jay. The first is that if you want to continue to use mfrow then it's best to stay away from using par(fig = x) to control your plot locations, since fig changes depending on mfrow and also forces a new plot (though you can override that, as per your question). You can use plt instead, which makes all co-ordinates relative to the space within the fig co-ordinates.
The second point is that you can plot the rectangle without calling plot.new()
The third, and maybe most important, is that you only need to write to par twice: once to change plt to the new plotting co-ordinates (including a new = TRUE to plot it in the same window) and once to reset plt (since new will reset itself). This means the function is well behaved and leaves the par as they were.
Note I have added a parameter, at, that allows you to specify the position and size of the little plot within the larger plot. It uses normalized co-ordinates, so for example c(0, 0.5, 0, 0.5) would be the bottom left quarter of the plotting area. I have set it to default at somewhere near your version's location.
myPlot <- function(x, y, at = c(0.7, 0.95, 0.7, 0.95))
{
# Helper function to simplify co-ordinate conversions
space_convert <- function(vec1, vec2)
{
vec1[1:2] <- vec1[1:2] * diff(vec2)[1] + vec2[1]
vec1[3:4] <- vec1[3:4] * diff(vec2)[3] + vec2[3]
vec1
}
# Main plot
plot(x)
# Gray rectangle
u <- space_convert(at, par("usr"))
rect(u[1], u[3], u[2], u[4], col="grey80")
# Only write to par once for drawing insert plot: change back afterwards
plt <- par("plt")
plt_space <- space_convert(at, plt)
par(plt = plt_space, new = TRUE)
plot(y, col = 2)
par(plt = plt)
}
So we can test it with:
x <- data.frame(x = rnorm(150, sd = 5), y = rnorm(150, sd = 5))
y <- data.frame(x = rnorm(1500, sd = 5), y = rnorm(1500, sd = 5))
myPlot(x, y)
par(mfrow = c(1, 2))
myPlot(x, y)
myPlot(x, y)
par(mfrow = c(2, 2))
for(i in 1:4) myPlot(x, y)

How to do a 3D plot using R?

I want to plot a 3D plot using R. My data set is independent, which means the values of x, y, and z are not dependent on each other. The plot I want is given in this picture:
This plot was drawn by someone using MATLAB. How can I can do the same kind of Plot using R?
Since you posted your image file, it appears you are not trying to make a 3d scatterplot, rather a 2d scatterplot with a continuous color scale to indicate the value of a third variable.
Option 1: For this approach I would use ggplot2
# make data
mydata <- data.frame(x = rnorm(100, 10, 3),
y = rnorm(100, 5, 10),
z = rpois(100, 20))
ggplot(mydata, aes(x,y)) + geom_point(aes(color = z)) + theme_bw()
Which produces:
Option 2: To make a 3d scatterplot, use the cloud function from the lattice package.
library(lattice)
# make some data
x <- runif(20)
y <- rnorm(20)
z <- rpois(20, 5) / 5
cloud(z ~ x * y)
I usually do these kinds of plots with the base plotting functions and some helper functions for the color levels and color legend from the sinkr package (you need the devtools package to install from GitHib).
Example:
#library(devtools)
#install_github("marchtaylor/sinkr")
library(sinkr)
# example data
grd <- expand.grid(
x=seq(nrow(volcano)),
y=seq(ncol(volcano))
)
grd$z <- c(volcano)
# plot
COL <- val2col(grd$z, col=jetPal(100))
op <- par(no.readonly = TRUE)
layout(matrix(1:2,1,2), widths=c(4,1), heights=4)
par(mar=c(4,4,1,1))
plot(grd$x, grd$y, col=COL, pch=20)
par(mar=c(4,1,1,4))
imageScale(grd$z, col=jetPal(100), axis.pos=4)
mtext("z", side=4, line=3)
par(op)
Result:

Plotting marginal histograms (as factors) and scatterplot (as numeric) from the same variable in R

I'm trying to create a scatterplot with marginal histograms as in this question.
My data are two (numeric) variables which share seven discrete (somewhat) logarithmically-spaced levels.
I've successfully done this with the help of ggMarginal in the ggExtra package, however I'm not happy with the outcome as when plotting the marginal histograms using the same data as for the scatterplots, things don't line up.
As can be seen below, the histogram bars are biased a little to the right or left of the datapoints themselves.
library(ggMarginal)
library(ggplot2)
x <- rep(log10(c(1,2,3,4,5,6,7)), times=c(3,7,12,18,12,7,3))
y <- rep(log10(c(1,2,3,4,5,6,7)), times=c(3,1,13,28,13,1,3))
d <- data.frame("x" = x,"y" = y)
p1 <- ggMarginal(ggplot(d, aes(x,y)) + geom_point() + theme_bw(), type = "histogram")
A possible solution for this may be change the variables used in the histograms into factors, so they are nicely aligned with the scatterplot axes.
This works well when creating histograms using ggplot:
p2 <- ggplot(data.frame(lapply(d, as.factor)), aes(x = x)) + geom_histogram()
However, when I try to do this using ggMarginal, I do not get the desired result - it appears that the ggMarginal histogram is still treating my variables as numeric.
p3 <- ggMarginal(ggplot(d, aes(x,y)) + geom_point() + theme_bw(),
x = as.factor(x), y = as.factor(y), type = "histogram")
How can I ensure my histogram bars are centred over the data points?
I'm absolutely willing to accept an answer which does not involve use of ggMarginal.
Not sure if it is a good idea to replicate here the answer I gave to the question you mentioned but I have no rights to comment still, please let me know otherwise.
I've found the package (ggpubr) that seems to work very well for this problem and it considers several possibilities to display the data.
The link to the package is here, and in this link you will find a nice tutorial to use it. For completeness, I attach one of the examples I reproduced.
I first installed the package (it requires devtools)
if(!require(devtools)) install.packages("devtools")
devtools::install_github("kassambara/ggpubr")
For the particular example of displaying different histograms for different groups, it mentions in relation with ggExtra: "One limitation of ggExtra is that it can’t cope with multiple groups in the scatter plot and the marginal plots. In the R code below, we provide a solution using the cowplot package." In my case, I had to install the latter package:
install.packages("cowplot")
And I followed this piece of code:
# Scatter plot colored by groups ("Species")
sp <- ggscatter(iris, x = "Sepal.Length", y = "Sepal.Width",
color = "Species", palette = "jco",
size = 3, alpha = 0.6)+
border()
# Marginal density plot of x (top panel) and y (right panel)
xplot <- ggdensity(iris, "Sepal.Length", fill = "Species",
palette = "jco")
yplot <- ggdensity(iris, "Sepal.Width", fill = "Species",
palette = "jco")+
rotate()
# Cleaning the plots
sp <- sp + rremove("legend")
yplot <- yplot + clean_theme() + rremove("legend")
xplot <- xplot + clean_theme() + rremove("legend")
# Arranging the plot using cowplot
library(cowplot)
plot_grid(xplot, NULL, sp, yplot, ncol = 2, align = "hv",
rel_widths = c(2, 1), rel_heights = c(1, 2))
Which worked fine for me:
If you are willing to give baseplotting a try, here is a function:
plots$scatterWithHists <- function(x, y, histCols=c("lightblue","lightblue"), lhist=20, xlim=range(x), ylim=range(y), ...){
## set up layout and graphical parameters
layMat <- matrix(c(1,4,3,2), ncol=2)
layout(layMat, widths=c(5/7, 2/7), heights=c(2/7, 5/7))
ospc <- 0.5 # outer space
pext <- 4 # par extension down and to the left
bspc <- 1 # space between scatter plot and bar plots
par. <- par(mar=c(pext, pext, bspc, bspc), oma=rep(ospc, 4)) # plot parameters
## barplot and line for x (top)
xhist <- hist(x, breaks=seq(xlim[1], xlim[2], length.out=lhist), plot=FALSE)
par(mar=c(0, pext, 0, 0))
barplot(xhist$density, axes=FALSE, ylim=c(0, max(xhist$density)), space=0, col=histCols[1])
## barplot and line for y (right)
yhist <- hist(y, breaks=seq(ylim[1], ylim[2], length.out=lhist), plot=FALSE)
par(mar=c(pext, 0, 0, 0))
barplot(yhist$density, axes=FALSE, xlim=c(0, max(yhist$density)), space=0, col=histCols[2], horiz=TRUE)
## overlap
dx <- density(x)
dy <- density(y)
par(mar=c(0, 0, 0, 0))
plot(dx, col=histCols[1], xlim=range(c(dx$x, dy$x)), ylim=range(c(dx$y, dy$y)),
lwd=4, type="l", main="", xlab="", ylab="", yaxt="n", xaxt="n", bty="n"
)
points(dy, col=histCols[2], type="l", lwd=3)
## scatter plot
par(mar=c(pext, pext, 0, 0))
plot(x, y, xlim=xlim, ylim=ylim, ...)
}
Just do:
scatterWithHists(x,y, histCols=c("lightblue","orange"))
And you get:
If you absolutely want to use ggMargins then look up xparams and yparams. It says you can send additional arguments to x-margin and y-margin using those. I was only successful in sending trivial things like color. But maybe sending something like xlim would help.

Adding stippling to image/contour plot

have some data that I would like to add "stippling" to show where it is "important", as they do in the IPCC plots
At the moment I am really struggling with trying to do this in R.
If I make up some test data and plot it:
data <- array(runif(12*6), dim=c(12,6) )
over <- ifelse(data > 0.5, 1, 0 )
image(1:12, 1:6, data)
What I would like to finally do is over-plot some points based on the array "over" on top of the current image.
Any suggestions!??
This should help - I had do do a similar thing before and wrote a function that I posted here.
#required function from www.menugget.blogspot.com
matrix.poly <- function(x, y, z=mat, n=NULL){
if(missing(z)) stop("Must define matrix 'z'")
if(missing(n)) stop("Must define at least 1 grid location 'n'")
if(missing(x)) x <- seq(0,1,,dim(z)[1])
if(missing(y)) y <- seq(0,1,,dim(z)[2])
poly <- vector(mode="list", length(n))
for(i in seq(length(n))){
ROW <- ((n[i]-1) %% dim(z)[1]) +1
COL <- ((n[i]-1) %/% dim(z)[1]) +1
dist.left <- (x[ROW]-x[ROW-1])/2
dist.right <- (x[ROW+1]-x[ROW])/2
if(ROW==1) dist.left <- dist.right
if(ROW==dim(z)[1]) dist.right <- dist.left
dist.down <- (y[COL]-y[COL-1])/2
dist.up <- (y[COL+1]-y[COL])/2
if(COL==1) dist.down <- dist.up
if(COL==dim(z)[2]) dist.up <- dist.down
xs <- c(x[ROW]-dist.left, x[ROW]-dist.left, x[ROW]+dist.right, x[ROW]+dist.right)
ys <- c(y[COL]-dist.down, y[COL]+dist.up, y[COL]+dist.up, y[COL]-dist.down)
poly[[i]] <- data.frame(x=xs, y=ys)
}
return(poly)
}
#make vector of grids for hatching
incl <- which(over==1)
#make polygons for each grid for hatching
polys <- matrix.poly(1:12, 1:6, z=over, n=incl)
#plot
png("hatched_image.png")
image(1:12, 1:6, data)
for(i in seq(polys)){
polygon(polys[[i]], density=10, angle=45, border=NA)
polygon(polys[[i]], density=10, angle=-45, border=NA)
}
box()
dev.off()
Or, and alternative with "stipples":
png("hatched_image2.png")
image(1:12, 1:6, data)
for(i in seq(polys)){
xran <- range(polys[[i]]$x)
yran <- range(polys[[i]]$y)
xs <- seq(xran[1], xran[2],,5)
ys <- seq(yran[1], yran[2],,5)
grd <- expand.grid(xs,ys)
points(grd, pch=19, cex=0.5)
}
box()
dev.off()
Update:
In (very late) response to Paul Hiemstra's comment, here are two more examples with a matrix of higher resolution. The hatching maintains a nice regular pattern, but it is not nice to look at when broken up. The stippled example is much nicer:
n <- 100
x <- 1:n
y <- 1:n
M <- list(x=x, y=y, z=outer(x, y, FUN = function(x,y){x^2 * y * rlnorm(n^2,0,0.2)}))
image(M)
range(M$z)
incl <- which(M$z>5e5)
polys <- matrix.poly(M$x, M$y, z=M$z, n=incl)
png("hatched_image.png", height=5, width=5, units="in", res=400)
op <- par(mar=c(3,3,1,1))
image(M)
for(i in seq(polys)){
polygon(polys[[i]], density=10, angle=45, border=NA, lwd=0.5)
polygon(polys[[i]], density=10, angle=-45, border=NA, lwd=0.5)
}
box()
par(op)
dev.off()
png("stippled_image.png", height=5, width=5, units="in", res=400)
op <- par(mar=c(3,3,1,1))
image(M)
grd <- expand.grid(x=x, y=y)
points(grd$x[incl], grd$y[incl], pch=".", cex=1.5)
box()
par(op)
dev.off()
Do it using the coordinate positioning mechanism of ?image [1].
data(volcano)
m <- volcano
dimx <- nrow(m)
dimy <- ncol(m)
d1 <- list(x = seq(0, 1, length = dimx), y = seq(0, 1, length = dimy), z = m)
With your 'image' constructed that way you keep the structure with the object, and its
coordinates intact. You can collect multiple matrices into a 3D array or as multiple
elements, but you need to augment image() in order to handle that, so I keep them
separate here.
Make a copy of the data to specify an interesting area.
d2 <- d1
d2$z <- d2$z > 155
Use the coordinates to specify which cells are interesting. This is expensive if you have a very big raster, but it's super easy to do.
pts <- expand.grid(x = d2$x, y = d2$y)
pts$over <- as.vector(d2$z)
Set up the plot.
op <- par(mfcol = c(2, 1))
image(d1)
image(d1)
points(pts$x[pts$over], pts$y[pts$over], cex = 0.7)
par(op)
Don't forget to modify the plotting of points to get different effects, in particular a very dense grid with lots of points will take ages to draw all those little circles. pch = "." is a good choice.
Now, do you have some real data to plot on that nice projection? See examples here for some of the options: http://spatial-analyst.net/wiki/index.php?title=Global_datasets
[1] R has classes for more sophisticated handling of raster data, see package sp and raster
for two different approaches.
This is a solution in the spirit of #mdsummer's comment using ggplot2. I first draw the grid, and then draw +'es at the locations where a certain value has been exceeded. Note that ggplot2 works with data.frame's, not with multi-dimensional arrays or matrices. You can use melt from the reshape package to convert from an array / marix to a data.frame flat structure.
Here is a concrete example using the example data from the geom_tile documentation:
pp <- function (n,r=4) {
x <- seq(-r*pi, r*pi, len=n)
df <- expand.grid(x=x, y=x)
df$r <- sqrt(df$x^2 + df$y^2)
df$z <- cos(df$r^2)*exp(-df$r/6)
df
}
require(ggplot2)
dat = pp(200)
over = dat[,c("x","y")]
over$value = with(dat, ifelse(z > 0.5, 1, 0))
ggplot(aes(x = x, y = y), data = dat) +
geom_raster(aes(fill = z)) +
scale_fill_gradient2() +
geom_point(data = subset(over, value == 1), shape = "+", size = 1)
This is probably coming too late, but I'd like to post my answer as a reference too.
One nice option for spatial data is to use the rasterVis package. Once you have a "base" raster object, and the "mask" object, which you will use to draw the stippling, you can do something like:
require(raster)
require(rasterVis)
# Scratch raster objects
data(volcano)
r1 <- raster(volcano)
# Here we are selecting only values from 160 to 180.
# This will be our "mask" layer.
over <- ifelse(volcano >=160 & volcano <=180, 1, NA)
r2 <- raster(over)
# And this is the key step:
# Converting the "mask" raster to spatial points
r.mask <- rasterToPoints(r2, spatial=TRUE)
# Plot
levelplot(r1, margin=F) +
layer(sp.points(r.mask, pch=20, cex=0.3, alpha=0.8))
which resembles the map that the OP was looking for. Parameters of the points such as color, size and type can be fine tuned. ?sp.points provides all the arguments that can be used to do that.

plotting and coloring data on irregular grid

I have data in the form (x, y, z) where x and y are not on a regular grid. I wish to display a 2D colormap of these data, with intensity (say, grey scale) mapped to the z variable. An obvious solution is to interpolate (see below) on a regular grid,
d <- data.frame(x=runif(1e3, 0, 30), y=runif(1e3, 0, 30))
d$z = (d$x - 15)^2 + (d$y - 15)^2
library(akima)
d2 <- with(d, interp(x, y, z, xo=seq(0, 30, length = 30),
yo=seq(0, 30, length = 50), duplicate="mean"))
pal1 <- grey(seq(0,1,leng=500))
with(d2, image(sort(x), sort(y), z, useRaster=TRUE, col = pal1))
points(d$x, d$y, col="white", bg=grey(d$z/max(d$z)), pch=21, cex=1,lwd=0.1)
However, this loses the information of the initial mesh (position of the points with actual data), which could be very fine or very rough at certain locations. My preference would be for a delaunay tiling with triangles, which accurately represents the actual location and density of the original data points.
Ideally the solution would
compute the tesselation outside of the plotting function, so that the resulting polygons may be plotted with either ggplot2, lattice, or base graphics
be fast. In my real-life example (~1e5 points), the calculation of the tesselation via deldir can be really slow.
By "tesselation" I mean either Delaunay triangles or Voronoi diagrams, although my preference would be for the former. However it bring the additional complexity of interpolating the colour of each triangle based on the original data points.
Here's a solution based on dirichlet from the maptools package,
d <- data.frame(x=runif(1e3, 0, 30), y=runif(1e3, 0, 30))
d$z = (d$x - 15)^2 + (d$y - 15)^2
library(spatstat)
library(maptools)
W <- ripras(df, shape="rectangle")
W <- owin(c(0, 30), c(0, 30))
X <- as.ppp(d, W=W)
Y <- dirichlet(X)
Z <- as(Y, "SpatialPolygons")
plot(Z, col=grey(d$z/max(d$z)))
I'm still unsure of the way to extract the polygons from this SpatialPolygons class.
Also if there's an easy way to produce the "correct" colors for the associated delaunay tesselation I'd like to hear it.
Here is a lattice solution using deldir
d <- data.frame(x=runif(1e3, 0, 30), y=runif(1e3, 0, 30))
d$z = (d$x - 15)^2 + (d$y - 15)^2
pal1 <- grey(seq(0,1,leng=500))
library(latticeExtra)
levelplot(z~x*y, data=d,
panel = function(...) panel.voronoi(..., points=FALSE),
interpolate=TRUE,
col.regions = colorRampPalette(pal1)(1e3), cut=1e3)

Resources