2

I'm doing a comparison chart of two different estimates of the same time series data. I'm filling the area between the two series in green if the original estimate is more than the latest estimate, and red otherwise.

I've got that part working, but I'd like to add a legend for the fill colors. I tried scale_fill_manual towards the bottom of the code, but it doesn't seem to be doing anything?

Here's the code:

library(ggplot2)
library(scales)
library(colorspace)

# Return a polygon that only plots between yLower and yUpper when yLower is
# less than yUpper.
getLowerPolygon = function(x, yLower, yUpper) {
    # Create the table of coordinates
    poly = data.frame(
        x = numeric(),
        y = numeric())

    lastReversed = (yUpper[1] < yLower[1])
    for (r in 1:length(x)) {
        reversed = (yUpper[r] < yLower[r])
        if (reversed != lastReversed) {
            # Between points r-1 and r, the series intersected, so we need to
            # change the polygon from visible to invisible or v.v.  In either
            # case, just add the intersection between those two segments to the
            # polygon.  Algorithm from:
            # https://en.wikipedia.org/wiki/Line-line_intersection
            # First line: x1,y1 - x2,y2
            x1 = x[r-1]
            y1 = yLower[r-1]
            x2 = x[r]
            y2 = yLower[r]
            # Second line: x3,y3 - x4,y4
            x3 = x[r-1]
            y3 = yUpper[r-1]
            x4 = x[r]
            y4 = yUpper[r]
            # Calculate determinants
            xy12 = det(matrix(c(x1, y1, x2, y2), ncol = 2))
            xy34 = det(matrix(c(x3, y3, x4, y4), ncol = 2))
            x12  = det(matrix(c(x1,  1, x2,  1), ncol = 2))
            x34  = det(matrix(c(x3,  1, x4,  1), ncol = 2))
            y12  = det(matrix(c(y1,  1, y2,  1), ncol = 2))
            y34  = det(matrix(c(y3,  1, y4,  1), ncol = 2))
            # Calculate fraction pieces
            xn = det(matrix(c(xy12, x12, xy34, x34), ncol = 2))
            yn = det(matrix(c(xy12, y12, xy34, y34), ncol = 2))
            d  = det(matrix(c(x12 , y12,  x34, y34), ncol = 2))
            # Calculate intersection
            xi = xn / d
            yi = yn / d
            # Add the point
            poly[nrow(poly)+1,] = c(xi, yi)
        }
        lastReversed = reversed
        # http://stackoverflow.com/questions/2563824
        poly[nrow(poly)+1,] = c(x[r], min(yLower[r], yUpper[r]))
    }

    poly = rbind(poly, data.frame(
        x = rev(x),
        y = rev(yUpper)))

    return(poly)
}

getComparisonPlot = function(data, title, lower_name, upper_name,
                         x_label, y_label, legend_title = '') {

    lightGreen = '#b0dd8d'
    lightRed   = '#fdba9a'

    darkGray = RGB(.8, .8, .8)
    midGray  = RGB(.5, .5, .5)

    plot = ggplot(data, aes(x = x))

    plot = plot + geom_polygon(
        aes(x = x, y = y),
        data = data.frame(
            x = c(data$x, rev(data$x)),
            y = c(data$yLower, rev(data$yUpper))
        ),
        fill = lightRed)

    coords = getLowerPolygon(data$x, data$yLower, data$yUpper)

    plot = plot + geom_polygon(
        aes(x = x, y = y),
        data = coords,
        fill = lightGreen)

    plot = plot + geom_line(
        aes(y = yUpper, color = 'upper'),
        size = 0.5)

    plot = plot + geom_line(
        aes(y = yLower, color = 'lower'),
        size = 0.5)

    plot = plot +
        ggtitle(paste(title, '\n', sep='')) +
        xlab(x_label) +
        ylab(y_label) +
        scale_y_continuous(labels = comma)

    # http://stackoverflow.com/a/10355844/106302
    plot = plot + scale_color_manual(
        name   = legend_title,
        breaks = c('upper' , 'lower'),
        values = c('gray20', 'gray50'),
        labels = c(upper_name, lower_name))

    plot = plot + scale_fill_manual(
        name   = 'Margin',
        breaks = c('upper', 'lower'),
        values = c(lightGreen, lightRed),
        labels = c('Over', 'Under'))

    return(plot)
}

print(getComparisonPlot(
    data = data.frame(
        x = 1:20,
        yLower = 1:20 %% 5 + 2,
        yUpper = 1:20 %% 7
    ),
    title = 'Comparison Chart',
    lower_name = 'Latest',
    upper_name = 'Original',
    x_label = 'X axis',
    y_label = 'Y axis',
    legend_title = 'Thing'
))

Here's an image of the chart, I think it is a cool technique:

Comparison Chart

I'm also open to any other suggestions for improving my ggplot code.

We Are All Monica
  • 13,000
  • 8
  • 46
  • 72
  • 2
    Right now you are setting `fill` to a constant instead of mapping something to it so you aren't getting a legend. Move `fill` inside the `aes` of `geom_polygon`, much like you did with `color` in `geom_line`. – aosmith Sep 26 '14 at 03:00

1 Answers1

7

GGplot need you to map polygons fill aesthetic to some variable. OR, in this case, it need just you to "label" the types of polygons (i.e. 'upper' and 'lower'). You do this by passing a string with the respective label for the fill aesthetic of geom_polygon(). What you are doing is passing a giving colour for each polygon and not mapping to anything that the ggplot will understand. It's kind of a "hard coded colour" =P.

Well, here are the changes inside getComparisonPlot:

plot = plot + geom_polygon(
    aes(x = x, y = y, fill = "upper"),
    data = coords)

plot = plot + geom_polygon(
    aes(x = x, y = y, fill = "lower"),
    data = data.frame(
      x = c(data$x, rev(data$x)),
      y = c(data$yLower, rev(data$yUpper))
    ))

One more thing. Note that the strings passed to fill aesthetic coincides with the breaks passed to the scale_fill_manual. It is necessary to make the legend map things right.

plot = plot + scale_fill_manual(
    name   = 'Margin',
    breaks = c('upper', 'lower'), # <<< corresponds to fill aesthetic labels
    values = c(lightGreen, lightRed),
    labels = c('Over', 'Under'))

Result:

enter image description here

hope it helps.

Athos
  • 650
  • 4
  • 10
  • So close! For me, the Margin legend shows before the Thing legend, and I want to be able to control the order. I saw http://stackoverflow.com/questions/11393123/controlling-ggplot2-legend-display-order but I'm not sure where to put the `order = 1` option. I'm using ggplot2 v1.0.0. – We Are All Monica Sep 26 '14 at 14:28
  • Got it! Need to specify **both** `guide = guide_legend(order = 1)` for the first legend **and** `guide = guide_legend(order = 2)` for the second legend. – We Are All Monica Sep 26 '14 at 14:48
  • Oh, and a minor issue that was easily fixed, your green and red areas are reversed. – We Are All Monica Sep 26 '14 at 15:00