6

I need to use black and white color for my boxplots in R. I would like to colorfill the boxplot with lines and dots. For an example:

enter image description here

I imagine ggplot2 could do that but I can't find any way to do it.

Thank you in advance for your help!

Thomas
  • 43,637
  • 12
  • 109
  • 140
user2427949
  • 75
  • 1
  • 4
  • Some googling turned up [this discussion from r-help](http://r.789695.n4.nabble.com/How-to-fill-bar-plot-with-textile-rather-than-color-td847003.html), which makes it sound pretty difficult. – Thomas Jul 02 '13 at 10:39

2 Answers2

6

I thought this was a great question and pondered if it was possible to do this in base R and to obtain the checkered look. So I put together some code that relies on boxplot.stats and polygon (which can draw angled lines). Here's the solution, which is really not ready for primetime, but is a solution that could be tinkered with to make more general.

boxpattern <- 
function(y, xcenter, boxwidth, angle=NULL, angle.density=10, ...) {
    # draw an individual box
    bstats <- boxplot.stats(y)
    bxmin <- bstats$stats[1]
    bxq2 <- bstats$stats[2]
    bxmedian <- bstats$stats[3]
    bxq4 <- bstats$stats[4]
    bxmax <- bstats$stats[5]
    bleft <- xcenter-(boxwidth/2)
    bright <- xcenter+(boxwidth/2)
    # boxplot
    polygon(c(bleft,bright,bright,bleft,bleft),
        c(bxq2,bxq2,bxq4,bxq4,bxq2), angle=angle[1], density=angle.density)
    polygon(c(bleft,bright,bright,bleft,bleft),
        c(bxq2,bxq2,bxq4,bxq4,bxq2), angle=angle[2], density=angle.density)
    # lines
    segments(bleft,bxmedian,bright,bxmedian,lwd=3) # median
    segments(bleft,bxmin,bright,bxmin,lwd=1) # min
    segments(xcenter,bxmin,xcenter,bxq2,lwd=1)
    segments(bleft,bxmax,bright,bxmax,lwd=1) # max
    segments(xcenter,bxq4,xcenter,bxmax,lwd=1)
    # outliers
    if(length(bstats$out)>0){
        for(i in 1:length(bstats$out))
            points(xcenter,bstats$out[i])
    }
}

drawboxplots <- function(y, x, boxwidth=1, angle=NULL, ...){
    # figure out all the boxes and start the plot
    groups <- split(y,as.factor(x))
    len <- length(groups)
    bxylim <- c((min(y)-0.04*abs(min(y))),(max(y)+0.04*max(y)))
    xcenters <- seq(1,max(2,(len*(1.4))),length.out=len)
    if(is.null(angle)){
        angle <- seq(-90,75,length.out=len)
        angle <- lapply(angle,function(x) c(x,x))
    }
    else if(!length(angle)==len)
        stop("angle must be a vector or list of two-element vectors")
    else if(!is.list(angle))
        angle <- lapply(angle,function(x) c(x,x))
    # draw plot area
    plot(0, xlim=c(.97*(min(xcenters)-1), 1.04*(max(xcenters)+1)),
        ylim=bxylim, 
        xlab="", xaxt="n",
        ylab=names(y), 
        col="white", las=1)

    axis(1, at=xcenters, labels=names(groups))
    # draw boxplots
    plots <- mapply(boxpattern, y=groups, xcenter=xcenters,
        boxwidth=boxwidth, angle=angle, ...)
}

Some examples in action:

mydat <- data.frame(y=c(rnorm(200,1,4),rnorm(200,2,2)),
                    x=sort(rep(1:2,200)))
drawboxplots(mydat$y, mydat$x)

enter image description here

mydat <- data.frame(y=c(rnorm(200,1,4),rnorm(200,2,2),
                        rnorm(200,3,3),rnorm(400,-2,8)),
                    x=sort(rep(1:5,200)))
drawboxplots(mydat$y, mydat$x)

enter image description here

drawboxplots(mydat$y, mydat$x, boxwidth=.5, angle.density=30)

enter image description here

drawboxplots(mydat$y, mydat$x, # specify list of two-element angle parameters
             angle=list(c(0,0),c(90,90),c(45,45),c(45,-45),c(0,90)))

enter image description here

EDIT: I wanted to add that one could also obtain dots as a fill by basically drawing a pattern of dots, then covering them a "donut"-shaped polygon, like so:

x <- rep(1:10,10)
y <- sort(x)
plot(y~x, xlim=c(0,11), ylim=c(0,11), pch=20)
outerbox.x <- c(2.5,0.5,10.5,10.5,0.5,0.5,2.5,7.5,7.5,2.5)
outerbox.y <- c(2.5,0.5,0.5,10.5,10.5,0.5,2.5,2.5,7.5,7.5)
polygon(outerbox.x,outerbox.y, col="white", border="white") # donut
polygon(c(2.5,2.5,7.5,7.5,2.5),c(2.5,2.5,2.5,7.5,7.5)) # inner box

enter image description here

But mixing that with angled lines in a single plotting function would be a bit difficult, and is generally a bit more challenging, but it starts to get you there.

Thomas
  • 43,637
  • 12
  • 109
  • 140
4

I think it is hard to do this with ggplot2 since it dont use shading polygon(gris limitatipn). But you can use shading line feature in base plot, paramtered by density and angle arguments in some plot functions ( ploygon, barplot,..).

The problem that boxplot don't use this feature. So I hack it , or rather I hack bxp internally used by boxplot. The hack consist in adding 2 arguments (angle and density) to bxp function and add them internally in the call of xypolygon function ( This occurs in 2 lines).

my.bxp <- function (all.bxp.argument,angle,density, ...) {
    .....#### bxp code
    xypolygon(xx, yy, lty = boxlty[i], lwd = boxlwd[i], 
    border = boxcol[i],angle[i],density[i])  
    .......## bxp code after
    xypolygon(xx, yy, lty = "blank", col = boxfill[i],angle[i],density[i])      
    ......

}

Here an example. It should be noted that it is entirely the responsibility of the user to ensure that the legend corresponds to the plot. So I add some code to rearrange the legend an the boxplot code.

enter image description here

require(stats)
set.seed(753)
(bx.p <- boxplot(split(rt(100, 4), gl(5, 20))))
layout(matrix(c(1,2),nrow=1),
       width=c(4,1)) 
angles=c(60,30,40,50,60)
densities=c(50,30,40,50,30)
par(mar=c(5,4,4,0)) #Get rid of the margin on the right side
my.bxp(bx.p,angle=angles,density=densities)
par(mar=c(5,0,4,2)) #No margin on the left side
plot(c(0,1),type="n", axes=F, xlab="", ylab="")
legend("top", paste("region", 1:5),
       angle=angles,density=densities)
agstudy
  • 119,832
  • 17
  • 199
  • 261