Overlay two alluvial plots with same dimensions

Viewed 171

I am trying to plot rectangle on top of an alluvial plot in R. my codes are:

library(alluvial)
tit <- as.data.frame(Titanic)
tit2d <- aggregate(Freq ~ Class + Survived, data=tit, sum)


alluvial(tit2d, freq = tit2d$Freq, xw = 0.0, alpha = 0.8,
         gap.width = 0.1, col = "steelblue", border = "white",
         layer = tit2d$Survived != "Yes", blocks = F)

par(new = TRUE) 
plot(c(0, 1), c(0, 1), type = "n", xlab = "", ylab = "")
rect(0, 0, 0.3, 0.4, col = "red")

But How do I make sure that the coordinates of both the plots are same, as my rectangle is based on the alluvial coordinates?

2 Answers

Usually the par("usr") command can tell you the extremes of the current plotting region. But it's a bit difficult here because the alluvial function adjusts the mar graphics parameter within the function. It changes the margins to c(2,1,1,1). At the end of the function it reverts the margins back to what they were initially. The default margins are c(5, 4, 4, 2) + 0.1 representing the number of lines (1 line = 1/5 inches) between the bottom, left, top, and right outer margins to the plotting region. Look at help page for par and scroll down until you get to mai to see a good diagram. Another adjustment made by the alluvial function is to change the default style of the axes from regular (xaxs="r", yaxs="r") to internal (xaxs="i", yaxs="i"), which causes the axes to extend a little further.

For example:

alluvial( tit2d, freq=tit2d$Freq, xw=0.0, alpha=0.8,
                gap.width=0.1, col= "steelblue", border="white",
                layer = tit2d$Survived != "Yes", blocks = F )

u <- par("usr"); u
par(new=TRUE) 
plot(0, type= "n", xlab = "", ylab = "", las=1, xaxs = "i", yaxs = "i", 
xlim=c(u[1], u[2]), ylim=c(u[3], u[4]))
rect(u[1], u[3], u[2], u[4], col = "red")

You'll get a red rectangle filling in most of the plot region. The large gaps between the rectangle extremes and the the alluvial extremes is due to the said change in the margins.

You can try to imitate the alluvial function by manually reducing the margins as follows:

op <- par(mar=c(2,1,1,1))
par(new=TRUE) 
plot(0, type= "n", xlab = "", ylab = "", las=1, 
     xlim=c(u[1], u[2]), ylim=c(u[3], u[4]))
rect(u[1], u[3], u[2], u[4], col = "green")
par(op)

Your green rectangle should now cover the whole alluvial plot.

Edit: To highlight the lower rectangles:

par(new=TRUE, mar=c(2,1,1,1)) 
u <- par("usr")
rect(u[1], temp$endpoints[[1]][1,1], u[1]+0.2, # Note: cw has a default value of 0.1
   temp$endpoints[[1]][1,2], col = "red")

rect(u[1]+1, temp$endpoints[[1]][1,1], u[1]+1.2, 
   temp$endpoints[[1]][1,2], col = "red")

rect(u[1]+2, temp$endpoints[[1]][1,1], u[1]+2.2, 
   temp$endpoints[[1]][1,2], col = "red") 

Edit: Thanks to @Darren Tsai for pointing out that low-level plotting functions such as rect() do not have a "xaxs" argument for modifying the axis style, since the axes are already there!

You can think Why does this function return only matrices of y-coordinates of endpoints?
From the document of alluvial, it says:

x-coordinates are consecutive natural numbers.

Explanation

In your example, there are three pillars in the plot, and that means their midpoints are located at 1, 2, 3 respectively. However, where are the left and right bounds of each pillar? I think you ignored a crucial argument cw of alluvial(), which controls the width of pillars. If you don't set it, it'll take its default value 0.1. The left and right bounds of each pillar will be

Now you can easily find both bounds of eanch pillar.

temp <- alluvial(tit2d, freq = tit2d$Freq, xw = 0, alpha = 0.8,
                 gap.width = 0.1, col = "steelblue", border = "white",
                 layer = tit2d$Survived != "Yes", blocks = F)

par(xpd = TRUE)
abline(v = 1:3, lwd = 3, col = "red")
abline(v = 1:3 - 0.1, lwd = 3, col = "blue")
abline(v = 1:3 + 0.1, lwd = 3, col = "green")

If you want to highlight a certain rectangle:

par(xpd = TRUE)
y <- temp$endpoints

# bottom-left rectangle
rect(1 - 0.1, y[[1]][1, 1],
     1 + 0.1, y[[1]][1, 2], col = "red")

# the second pillar, the fourth rectangle from the bottom
rect(2 - 0.1, y[[2]][4, 1],
     2 + 0.1, y[[2]][4, 2], col = "orange")


Why par(xpd = TRUE)?

par("xpd") can be set

  • FALSE(default): all plotting is clipped to the "plot" region
  • TRUE: all plotting is clipped to the "figure" region
  • NA: all plotting is clipped to the "device" region

If you don't change the default value, all the low-level plotting functions, e.g. points(), abline(), rect(), text()..., will be clipped when out of the "plot" region. You can use box() to see where the "plot" region is.


Conclusion

  1. Argument cw = 0.1 (the default). You can change it, but x-coordinates will be different.

  2. Set par(xpd = TRUE).


Supplement

If you want to change the order of drawing of the stripes, try to adjust the argument layer. It can be a logical or numeric vector. The larger the number is, the earlier the stripe is drawn, that is, it'll be lower than the stripe of smaller numbers.

In your example, you can change the order according to the rank of Freq.
Note the difference of the following:

  • rank(tit2d$Freq) map smaller values to lower ranks.
  • rank(-tit2d$Freq) map smaller values to upper ranks.
tit2d$Freq
# [1] 122 167 528 673 203 118 178 212

rank(tit2d$Freq)
# [1] 2 3 7 8 5 1 4 6

rank(-tit2d$Freq)
# [1] 7 6 2 1 4 8 5 3

Therefore, what you require in the comment is the latter.

alluvial(tit2d, freq = tit2d$Freq, xw = 0, alpha = 0.8,
         gap.width = 0.1, col = "steelblue", border = "white",
         layer = rank(-tit2d$Freq), blocks = F)

Related