M. Beausoleil
M. Beausoleil

Reputation: 3555

How to plot histograms in base R in the margin of a plot

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)

enter image description here

Is there a way to make histograms in base R in the margin of plots?

Upvotes: 1

Views: 603

Answers (1)

r2evans
r2evans

Reputation: 160547

  1. Using layout, you can set a space to 0 so that it does not require a plot, it is empty space.

  2. 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")

enter image description here

Upvotes: 3

Related Questions