Follow-up to this question. I am trying to plot 4 different rasters with a common scale (legend). I have managed to do it but now I want to plot them in the same graphic device, with the scale showing just once, at the right side of the device, and another legend for some elements I have plotted over the rasters placed at the bottom of the device.
I was sure this would be no problem with layout(), but turns out there is some incompatibility between the methods in plot.rasterand layout. Following the answer to the linked question, I plotted my rasters using image instead of plot, which seems to fix the problem. In this way, I was able to use layout() to get the points legend below my four rasters. However, now I can't seem to find a way of plotting just the scale to the right of my rasters images.
Example code:
library(raster)
# generate example rasters
set.seed(123)
ras1 <- raster(ncol = 10, nrow= 10)
values(ras1) <- runif(100, 1, 10)
ras2 <- raster(ncol = 10, nrow = 10)
values(ras2) <- runif(100, 5, 50)
ras3 <- raster(ncol = 10, nrow = 10)
values(ras3) <- runif(100, 10, 100)
ras4 <- raster(ncol = 10, nrow = 10)
values(ras4) <- runif(100, 8, 80)
# stack them
rasStack <- stack(ras1, ras2, ras3, ras4)
# obtain max and min values
maxv <- max(maxValue(rasStack))+1
minv <- min(minValue(rasStack))
# set the breaks between min and max values
brks <- seq(minv,maxv,by=0.1)
nbrks <- length(brks)-1
r.range <- c(minv, maxv)
# generate palette
colfunc<-colorRampPalette(c("springgreen", "royalblue", "yellow", "red"))
# add up the 4 layers for the common legend
rasTot <- ras1 + ras2 + ras3 + ras4
# plot in a loop with a common legend, using legend.only = T
par(mfrow=c(2,2))
for(i in seq_len(nlayers(rasStack))){
tmp <- rasStack[[i]]
plot(tmp, breaks=brks,col=colfunc(nbrks), legend = F, zlim=c(minv,maxv),
main = names(tmp))
plot(rasTot, legend.only=TRUE, col=colfunc(nbrks),
legend.width=1, legend.shrink=0.75,
legend.args=list(text='value', side=4, font=2, line=2.5, cex=0.8))
points(x = rnorm(5, 0, 100), y = rnorm(5, 0, 10), col = seq(1:10), pch = 16)
}
# so far so good. But when trying to add legends...
# setting layout parameters
m <- matrix(c(1,2,6,3,4,6,5,5,5),nrow = 3,ncol = 3,byrow = TRUE)
png("example_in_png_device.png")
layout(mat = m,heights = c(0.46,0.46,0.08), widths = c(0.45, 0.45, 0.1))
par(mar=c(4,2,4,2))
# plotting the images
for(i in seq_len(nlayers(rasStack))){
tmp <- rasStack[[i]]
image(tmp, breaks=brks,col=colfunc(nbrks), zlim=c(minv,maxv),
main = names(tmp))
points(x = rnorm(5, 0, 100), y = rnorm(5, 0, 10), col = seq(1:10), pch = 16)
}
# adding legend at the bottom
par(mar=c(0,2,0,2)+0.1)
plot(1, type = "n", axes = F, frame.plot = F)
legend("topleft",
c("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"),
fill = seq(1:10), border = "black", ncol=10,
bg = "black", bty = "n",
y.intersp = 0.8, cex = 1.2)
dev.off()
All this generates an image that looks like this:
Which is pretty much what I wanted so that's great, but I can't figure out how to add the scale (common for all 4 raster layers) in the space number 6 in this layout.
Any ideas will be greatly appreciated and put to use immediately!
Related
My example data is as follows:
df <- data.frame(study = c("Hodaie","Kerrigan","Lee","Andrade","Lim"), SR = c(0.5460, 0.2270, 0.7540, 0.6420, 0.5000), SE = c(12.30, 15.70, 12.80, 13.80, 9.00), Patients = c(5, 5, 3, 6, 4))
I want to conduct the meta-analysis with SR(single group percentage), SE (standard error that I can compute based on sample size and percentage), and patients(sample size for each study), and I hope I could get the following forest plot (I found this example in an article, and they also have one group percentage data, but I can't find which R statement or argument they used):
Could anyone tell me which R statement or argument that I could use to conduct the meta-analysis and generate the forest plot above? Thank you!
I am sure there are plenty of ways to do this using packages but it can be accomplished in base R (and there are likely more elegant solutions using base R). The way I do it is to first build a blank plot much larger than the needed graphing portion, then overlay the relevant elements on it. I find one has more control over it this way. A basic example that could get you started is below. If you are new to R (based on your name NewRUser), I suggest running it line-by-line to see how it all works. Again, this is only one way and there are likely better approaches. Good luck!
Sample Data
#### Sample Data (modified from OP)
df <- data.frame(Study = c("Hodaie","Kerrigan","Lee","Andrade","Lim"),
SR = c(0.5460, 0.2270, 0.7540, 0.6420, 0.5000),
SE = c(12.30, 15.70, 12.80, 13.80, 9.00),
Patients = c(5, 5, 3, 6, 4),
ci_lo = c(30, -8.0, 50, 37, 32),
ci_hi = c(78, 53, 100, 91, 67))
### Set up plotting elements
n.studies <- nrow(df)
yy <- n.studies:1
seqx <- seq(-100, 100, 50)
## blank plot much larger than needed
plot(range(-550, 200), range(0, n.studies), type = 'n', axes = F, xlab = '', ylab = '') #blank plot, much bigger than plotting portion needed
# Set up axes
axis(side = 1, at = seqx, labels = seqx, cex.axis = 1, mgp = c(2, 1.5, 1)) # add axis and label (bottom)
mtext(side = 1, at = 0, 'Seizure Reduction', line = 2.5, cex = 0.85, padj = 1)
axis(side = 3, at = seqx, labels = seqx, cex.axis = 1, mgp = c(2, 1.5, 1)) # add axis and label (top)
mtext(side = 3, at = 0, 'Seizure Reduction', line = 2.5, cex = 0.85, padj = -1)
## add lines and dots
segments(df[, "ci_lo"], yy, df[,"ci_hi"], yy) # add lines
points(df[,"SR"]*100, yy, pch = 19) # add points
segments(x0 = 0, y0 = max(yy), y1 = 0, lty = 3, lwd = 0.75) #vertical line # 0
### Add text information
par(xpd = TRUE)
text(x = -550, y = yy, df[,"Study"], pos = 4)
text(x = -450, y = yy, df[,"SR"]*100, pos = 4)
text(x = -350, y = yy, df[,"SE"], pos = 4)
text(x = -250, y = yy, df[,"Patients"], pos = 4)
text(x = 150, y = yy, paste0(df[,"ci_lo"], "-", df[,"ci_hi"]), pos = 4)
text(x = c(seq(-550, -250, 100), 150), y = max(yy)+0.75,
c(colnames(df)[1:4], "CI"), pos = 4, font = 2)
# Add legend
legend(x = 50, y = 0.5, c("Point estimate", "95% Confidence interval"),
pch = c(19, NA), lty = c(NA, 19), bty = "n", cex = 0.65)
I have the following code (Thanks to an answer from #Rawr in this question):
labes1 <- c("P(LNG)","","Volume(LNG)","","P(oil)","","Can.GDP","","US GDP","")
titles <- c("Levels","","","","","Log Difference","","","","")
par(mfrow = c(5, 2), mar = c(0.3, 6, 0, 2), oma = c(5, 0, 3, 2))
lapply(1:10, function(ii) {
x <- plotdata1[, ii, drop = FALSE]
plot(x, xlab = "Quarter", ylab = labes1[ii], axes = FALSE)
axis(2, las = 1)
box()
if (ii %in% 9:10) {
axis(1)
title(xlab = 'Quarter', xpd = NA)
}
if (ii %in% 1:2)
title(main = c('Levels', 'Log Difference')[ii], xpd = NA, line = 1)
})
This produces the following plot:
The obvious issue is the overlaying of the y-axis labels with the y-axis values. I have tried playing around with the mar() and oma() but these just change the margins around, I was hoping this would move things out of the way. How can I move the y-axis labels as separate from the plot? I will also be moving the margins a bit so that the white space between the two columns of plots will be closer together.
You can define the ylab separately, like what you're doing for the xlab, and set the line parameter to define its distance from the plot (as stated in this post).
I got a running example from combining your code and #rawr's from your previous question.
set.seed(1)
z <- ts(matrix(rt(200 * 10, df = 3), 200, 10), start = c(1961, 1), frequency = 12)
z <- z * 1e5 # to make "wide" y-axis labels
## vectors of x, y, and main labels
xl <- sprintf('x label %s', 1:10)
yl <- sprintf('y label %s', 1:10)
ml <- sprintf('main label %s', 1:10)
labes1 <- c("P(LNG)","","Volume(LNG)","","P(oil)","","Can.GDP","","US GDP","")
titles <- c("Levels","","","","","Log Difference","","","","")
par(mfrow = c(5, 2), mar = c(0.3, 6, 0, 2), oma = c(5, 0, 3, 2))
lapply(1:10, function(ii) {
x <- z[, ii, drop = FALSE]
plot(x, xlab = "Quarter", ylab = "", axes = FALSE) # set ylab to ""
axis(2, las = 1)
title(ylab = labes1[ii], line = 4) # set the line at an appropriate distance
box()
if (ii %in% 9:10) {
axis(1)
title(xlab = 'Quarter', xpd = NA)
}
if (ii %in% 1:2)
title(main = c('Levels', 'Log Difference')[ii], xpd = NA, line = 1)
})
The code above outputs the following graph for line = 4 :
and this plot for line = 3 :
I am looking for advice for plotting 2 similar wave forms with different y axes scales (one is mmHg and another is m/s) in the same plot. However, I would like to stagger the plots with respect to each other.
For example, using the below:
set.seed(123)
y <- sin(2*pi*x)
g <- sin(2*pi*x)+ rnorm(200, sd=0.1)
plot(y,type="l",
ann = F,
axes = F)
axis(side = 2)
par(new = T)
plot(g,type="l",
ann = F,
axes = F)
axis(side = 4)
Gives:
I would like to achieve something like this (see link below):
How to achieve this?
Here's a slightly cheaty solution:
x <- seq(from = 1, to = 3, by = 0.01)
y <- sin(2*pi*x)
set.seed(123)
g <- sin(2*pi*x)+ rnorm(length(x), sd=0.1)
stagger <- 2
glabels <- c(-1, 0, 1)
plot(c(min(y),max(y)+stagger) ~ c(1,length(y)), type="n", axes=FALSE, ann=FALSE)
lines(y)
axis(side = 2, at = min(y):max(y))
par(new = T)
lines(g+stagger)
axis(side = 4, at = glabels + stagger, labels = glabels)
Results in:
There's probably a better way to generate the positions and labels for the y-axis for g.
I am having a 3D plot in which the points are colored acording to some extra vector. My problem is to add a color gradient legend. This is my code:
x = matrix(NA,100,6)
#x value
x[,1] = runif(100, 0, 10)
#y value
x[,2] = runif(100, 0, 10)
#z value
x[,3] = x[,1]+x[,2]
#additional value
x[,4] = runif(100, 0, 1)
#find out in which interval each additional value is
intervals = seq(0,1,1/10)
x[,5] = findInterval(x[,4], intervals)
colours = topo.colors(length(intervals))
x[,6] = colours[x[,5]]
library(rgl)
plot3d(as.numeric(x[,1]),as.numeric(x.stab.in[,2]), as.numeric(x[,3]),
type="p", col=x[,6], size=2, xlab = "x(t)", ylab = "y(t)",
zlab = "z(t)")
decorate3d(xlab = "x", ylab = "y", zlab = "z")
legend3d("topright", legend = intervals, pch = 16, col = colours, cex=1, inset=c(0.02))
grid3d(c("x", "y+", "z"),col = "gray")
The plot looks like this
but I want the legend in a gradient form. That means I don't want separate points for each color but one box in which the colors fade into each other.
Here is a possible solution if you are okay with using scatterplot3d package instead of rgl. It is basically same but non-interactive. Here is your code modified to produce your expected result.
x = matrix(NA,100,6)
#x value
x[,1] = runif(100, 0, 10)
#y value
x[,2] = runif(100, 0, 10)
#z value
x[,3] = x[,1]+x[,2]
#additional value
x[,4] = runif(100, 0, 1)
#find out in which interval each additional value is
intervals = seq(0,1,1/10)
x[,5] = findInterval(x[,4], intervals)
#produce gradient of colors
#you can define different colors (two or more)
gradient <- colorRampPalette(colors = c("yellow", "green", "blue"))
colours <- gradient(length(intervals))
x[,6] = colours[x[,5]]
library(scatterplot3d)
png('3d.png', width = 600, height = 400)
layout(matrix(1:2, ncol=2), width = c(3, 1), height = c(1, 1))
scatterplot3d(as.numeric(x[,1]),as.numeric(x[,2]), as.numeric(x[,3]), type = 'p',
cex.symbols = 1.25, color=x[,6], pch = 16, xlab = "x(t)", ylab = "y(t)", zlab = "z(t)")
plot(x = rep(1, 100), y = seq_along(x[,6]),
pch = 15, cex = 2.5,
col = gradient(length(x[,6])),
ann = F, axes = F, xlim = c(1, 2))
axis(side = 2, at = seq(1, nrow(x), length.out = 11),
labels = 1:11,
line = 0.15)
dev.off()
This will plot the following graph
Here is another solution if you want to plot a gradient on an interactive 3d plot, such as if you needed to animate the plot into a movie.
require(car)
require(rgl)
require(RColorBrewer)
require(mgcv)
require(magick) #Only for creating the animation of the plot as a gif
#Creating mock dataset
Example_Data <- data.frame(Axis1 = rnorm(100),
Axis2 = rnorm(100),
Axis3 = rnorm(100))
Example_Data$Value <- Example_Data$Axis1+Example_Data$Axis2
#Defining function that takes a vector of numeric values and converts them to
#a spectrum of rgb colors to help color my scatter3d plot
get_colors <- function(values){
v <- (values - min(values))/diff(range(values))
x <- colorRamp(rev(brewer.pal(11, "Spectral")))(v)
rgb(x[,1], x[,2], x[,3], maxColorValue = 255)
}
#Writing function that takes a vector of numeric values and a title and creates
#a gradient legend based on those values and the title and suitable for addition
#to a scatter3d plot via a call to bgplot3d()
#Note, I didn't have time to make this automatically adjust text position/size for different size
#plot windows, so values may need to be adjusted manually depending on the size of the plot window.
gradient_legend_3d <- function(values, title){
min_val <- min(values)
max_val <- max(values)
x <- colorRamp(brewer.pal(11, "Spectral"))((0:20)/20)
colors <- rgb(x[,1], x[,2], x[,3], maxColorValue = 255)
legend_image <- as.raster(matrix(colors, ncol=1))
plot(c(0,1),c(0,1),type = 'n', axes = F,xlab = '', ylab = '', main = '') #Generates a blank plot
text(x=0.92, y = seq(0.5, 1,l=5), labels = signif(seq(min_val, max_val,l=5), 2), cex = 1.5) #Creates the numeric labels on the scale
text(x = 0.85, y = 1, labels = title, adj = 1, srt = 90, cex = 1.5) #Determines where the title is placed
rasterImage(legend_image, 0.87, 0.5, 0.9,1) #Values can be modified here to alter where and how wide/tall the gradient is drawn in the plotting area
}
#Creating scatter3d plot
scatter3d(x = Example_Data$Axis1, y = Example_Data$Axis2, z = Example_Data$Axis3, xlab = "Axis1", ylab = "Axis2", zlab = "Axis3", surface = F, grid = F, ellipsoid = F, fogtype = "none", point.col = get_colors(Example_Data$Value))
#Changing size of plotting window and orientation to optimize for addition of static legend
#This may not work on another machine, so the window may need to be adjusted manually
par3d(windowRect = c(0,23,1536,824))
par3d(userMatrix = matrix(c(-0.98181450, -0.02413967, 0.18830180, 0, -0.03652956, 0.99736959, -0.06260729, 0, -0.18629514, -0.06834736, -0.98011345, 0, 0, 0, 0, 1), nrow = 4, ncol = 4, byrow = T))
#Adding legend
bgplot3d(gradient_legend_3d(Example_Data$Value, "Point Value"))
#Animating plot and saving as gif
movie3d(spin3d(axis = c(0,1,0), rpm = 5), duration = 12, dir = getwd(), fps = 5, convert = FALSE, clean = FALSE)
frames <- NULL
for(j in 0:60){
if(j == 1){
frames <- image_read(sprintf("%s%03d.png", "movie", j))
} else {
frames <- c(frames, image_read(sprintf("%s%03d.png", "movie", j)))
}
}
animation <- image_animate(frames, fps = 10, optimize = TRUE)
image_write(animation, path = "Example.gif")
for(j in 0:60){
unlink(sprintf("%s%03d.png", "movie", j))
}
See link to view 3d plot generated by this code:
gif of 3d plot with gradient color scale
I came across this interesting website, with an idea of a way to visualize a clustering algorithm called "Clustergram":
(source: schonlau.net)
I am not sure how useful this really is, but in order to play with it I would like to reproduce it with R, but am not sure how to go about doing it.
How would you create a line for each item so it would stay consistent throughout the different number of clusters?
Here is an example code/data to play with for potential answer:
hc <- hclust(dist(USArrests), "ave")
plot(hc)
Update: I posted a solution with a lengthy example and discussion here. (it is based on the code I gave bellow). Also, Hadley was very kind and offered a ggplot2 implementation of the code.
Here is a basic solution (for a better one, look at the "update" above):
set.seed(100)
Data <- rbind(matrix(rnorm(100, sd = 0.3), ncol = 2),
matrix(rnorm(100, mean = 1, sd = 0.3), ncol = 2))
colnames(Data) <- c("x", "y")
# noise <- runif(100,0,.05)
line.width <- rep(.004, dim(Data)[1])
Y <- NULL
X <- NULL
k.range <- 2:10
plot(0, 0, col = "white", xlim = c(1,10), ylim = c(-.5,1.6),
xlab = "Number of clusters", ylab = "Clusters means",
main = "(Basic) Clustergram")
axis(side =1, at = k.range)
abline(v = k.range, col = "grey")
centers.points <- list()
for(k in k.range){
cl <- kmeans(Data, k)
clusters.vec <- cl$cluster
the.centers <- apply(cl$centers,1, mean)
noise <- unlist(tapply(line.width, clusters.vec,
cumsum))[order(seq_along(clusters.vec)[order(clusters.vec)])]
noise <- noise - mean(range(noise))
y <- the.centers[clusters.vec] + noise
Y <- cbind(Y, y)
x <- rep(k, length(y))
X <- cbind(X, x)
centers.points[[k]] <- data.frame(y = the.centers , x = rep(k , k))
# points(the.centers ~ rep(k , k), pch = 19, col = "red", cex = 1.5)
}
require(colorspace)
COL <- rainbow_hcl(100)
matlines(t(X), t(Y), pch = 19, col = COL, lty = 1, lwd = 1.5)
# add points
lapply(centers.points,
function(xx){ with(xx,points(y~x, pch = 19, col = "red", cex = 1.3)) })