Reputation: 3555
I'd like to add histograms in base R to the margins of a plot, I've tried many ways, and used points and segments to plot the values calculated from a histogram. The challenge is that after plotting the data, the width of the bars have to be set manually in order to show a histogram like margin plot.
# Inspired from
# https://github.com/ChrKoenig/R_marginal_plot/blob/master/marginal_plot.R
par(mfrow = c(1,2), mar =c(4,4,3,3), cex = 1.4)
set.seed(1234)
n = 250
x.1 = rnorm(n, mean = 15, sd = 5)
y.1 = rnorm(n, mean = 5, sd = 2)
x.2 = rnorm(n, mean = 15, sd = 1)
y.2 = rnorm(n, mean = 5, sd = .5)
x = x.1
y = y.1
moreargs$xlim[1] <- range(c(-3,x.1,x.2, max(x.1,x.2)+5))[1]
moreargs$xlim[2] <- range(c(-3,x.1,x.2, max(x.1,x.2)+5))[2]
moreargs$ylim[1] <- range(c(y.1,y.2,max(y.1,y.2)+5))[1]
moreargs$ylim[2] <- range(c(y.1,y.2,max(y.1,y.2)+5))[2]
require(scales)
# plotting
data = data.frame(x = as.numeric(x), y = as.numeric(y))
group_colors = "black"
ifelse(test = !is.null(data$group),
yes = data_split <- split(data, data$group),
no = data_split <- list(data))
orig_par = par(no.readonly = T)
par(mar = c(0.25,5,1,0))
layout(matrix(1:4, nrow = 2, byrow = T), widths = c(10,3), heights = c(3,10))
histval = hist(x = data_split[[1]]$x, plot = FALSE)
# upper density plot
plot(NULL, type = "n",
ylab = "Counts",
xlim = moreargs$xlim,
ylim = c(0, max(histval$counts)),
main = NA, axes = F)
points(histval$mids,histval$counts, type= "h", lwd = 33, lend="butt", col = 'gray60')
# points(histval$mids,histval$counts-1, type= "h", lwd = 30, lend="butt", col = 'gray80')
axis(2, tck = 0.04)
# (add an empty plot in the corner)
par(mar = c(0.25,0.25,0,0))
plot.new()
# main plot
par(mar = c(4,5,0,0))
plot(y.1~x.1,
ylim = moreargs$ylim,
xlim = moreargs$xlim,
pch = 19, col = scales::alpha("black",.8), ylab = "Y",xlab = "X")
axis(3, labels = F, tck = 0.01)
axis(4, labels = F, tck = 0.01)
box()
# right density plot
par(mar = c(4,0.25,0,1))
histval.y = hist(x = data_split[[1]]$y, plot = FALSE)
plot(NULL, type = "n",
xlab = "Counts",
xlim = c(0, max(histval$counts)),
ylim = moreargs$ylim,
main = NA, axes = F)
segments(x0 = 0,x1 = histval.y$counts,
y0 = histval.y$mids,y1 = histval.y$mids,
lwd = rep(c(46,2), each = length(histval.y$counts)), col = "gray60", lend="butt")
axis(1,tck=0.04)
par(orig_par)
Is there a way to make histograms in base R in the margin of plots?
Upvotes: 1
Views: 603
Reputation: 160547
Using layout
, you can set a space to 0
so that it does not require a plot, it is empty space.
hist
does not support horizontal mode, and recommendations (example) to use barplot
are constrained in that one cannot fix where on the x-axis the bars are positioned. Yet another workaround is needed: since barplot
is a wrapper around rect
, we can write our own.
This function mimics some of barplot
but allows us to set where the bars are located. This is to ensure that the shared axis can be the same dimensions.
barplot2 <- function(xs, ys, ..., space = 0, horiz = FALSE) {
mids <- diff(xs) / 2
mids <- c(mids[1], mids) - space/2
if (horiz) {
rect(0, xs - mids, ys, xs + mids, ...)
} else {
rect(xs - mids, 0, xs + mids, ys, ...)
}
}
It should be feasible to add plot
in there as well, but then one needs to either be explicit with all arguments, or determine which from ...
-arguments go to plot
and which go to rect
. I'll dodge that discussion for now, and just explicitly call plot
myself.
layout(
matrix(c(2,0,
1,3), byrow=TRUE, nrow=2),
widths = c(4,1), heights = c(1,4)
)
# main plot
par(mar = c(4, 5, 0, 0) + 0.1)
plot(y ~ x, data = data, pch = 16)
usr <- par("usr")
# top margin
par(mar = c(0, 5, 0, 0) + 0.1)
hx <- hist(data$x, plot = FALSE)
hy <- hist(data$y, plot = FALSE)
plot(NA, type = "n", xlim = usr[1:2], ylim = c(0, max(c(hx$counts, hy$counts))),
xaxs = "i", yaxs = "i", xaxt = "n", ylab = "Counts", frame = FALSE)
barplot2(hx$mids, hx$counts, space = 0, horiz = FALSE, col = "gray")
# right margin
par(mar = c(4, 0, 0, 0) + 0.1)
plot(NA, type = "n", xlim = c(0, max(c(hx$counts, hy$counts))), ylim = usr[3:4],
xaxs = "i", yaxs = "i", yaxt = "n", xlab = "Counts", frame = FALSE)
barplot2(hy$mids, hy$counts, space = 0, horiz = TRUE, col = "gray")
Upvotes: 3