rggplot2color-space

How can I control the limits of the color scale with the colorspace package?


Take an sf object like this.

df <- structure(list(margin = c(26.2569832402235, 26.775956284153, 
                                20.7317073170732, -4.21052631578947, 10.9876543209877, 13.6094674556213, 
                                23.4567901234568, -13.3333333333333), geometry = structure(list(
                                  structure(list(structure(c(-88.121832, -88.157869, -88.176234, 
                                                             -88.177947, -88.177989, -88.059246, -88.0582574, -88.094754, 
                                                             -88.105617, -88.136212, -88.121832, 45.831761, 45.844797, 
                                                             45.844652, 45.804926, 45.71809, 45.713063, 45.7806288, 45.785755, 
                                                             45.800389, 45.818735, 45.831761), dim = c(11L, 2L))), 
                                            class = c("XY", "POLYGON", "sfg")), structure(list(structure(c(-88.176234, -88.179036, -88.303528, -88.303565, -88.303894, -88.177947, -88.176234, 45.844652, 45.905805, 45.908553, 45.894106, 45.807592, 45.804926, 45.844652), dim = c(7L, 2L))), 
                                                                                          class = c("XY", "POLYGON", "sfg")), structure(list(structure(c(-88.365253, -88.36627, -88.425482, -88.551359, -88.675785, -88.67598, -88.365253, 45.722149, 45.808698, 45.809419, 45.809857, 45.808946, 45.722835, 45.722149),
                                                                                                                                                       dim = c(7L, 2L))), class = c("XY", "POLYGON", "sfg")), 
                                  structure(list(structure(c(-88.303894,-88.303565, -88.425221, -88.425482, -88.36627, -88.303894, 45.807592, 45.894106, 45.895528, 45.809419, 45.808698, 45.807592),
                                                           dim = c(6L, 2L))), class = c("XY", "POLYGON", "sfg")), 
                                  structure(list(structure(c(-88.121832, -88.105196, -88.070483, 
                                                             -88.101757, -88.102461, -88.126654, -88.148128, -88.180498, 
                                                             -88.230193, -88.25498, -88.268944, -88.301378, -88.328041, 
                                                             -88.380891, -88.388957, -88.423493, -88.464731, -88.48732, 
                                                             -88.497978, -88.50748, -88.534173, -88.55937, -88.551383, 
                                                             -88.550958, -88.553837, -88.551359, -88.425482, -88.425221, 
                                                             -88.303565, -88.303528, -88.179036, -88.176234, -88.157869, 
                                                             -88.121832, 45.831761, 45.842413, 45.874329, 45.883192, 45.921546, 
                                                             45.921791, 45.937546, 45.948287, 45.947251, 45.963512, 45.956413, 
                                                             45.956459, 45.963539, 45.991813, 45.982515, 45.982072, 46.000738, 
                                                             45.991024, 45.995794, 46.018516, 46.020269, 46.014102, 45.982055, 
                                                             45.896127, 45.883736, 45.809857, 45.809419, 45.895528, 45.894106, 
                                                             45.908553, 45.905805, 45.844652, 45.844797, 45.831761), dim = c(34L, 2L))),
                                            class = c("XY", "POLYGON", "sfg")), structure(list(structure(c(-88.177989, -88.177947, -88.303894, -88.36627, -88.365253, -88.177989, 45.71809, 45.804926, 45.807592, 45.808698, 45.722149, 45.71809), dim = c(6L, 2L))),
                                                                                          class = c("XY", "POLYGON", "sfg")), structure(list(structure(c(-88.551359, -88.553837, -88.550958, -88.675489, -88.675785, -88.551359, 45.809857, 45.883736, 45.896127, 45.895958, 45.808946, 45.809857), 
                                                                                                                                                       dim = c(6L, 2L))), class = c("XY", "POLYGON", "sfg")), 
                                  structure(list(structure(c(-88.55937, -88.58827, -88.616471, 
                                                             -88.658654, -88.67445, -88.675489, -88.550958, -88.551383, 
                                                             -88.55937, 46.014102, 46.005098, 45.987732, 45.98929, 45.980868, 
                                                             45.895958, 45.896127, 45.982055, 46.014102), dim = c(9L, 2L))),
                                            class = c("XY", "POLYGON", "sfg"))), n_empty = 0L, crs = structure(list(input = "NAD83", wkt = "GEOGCRS[\"NAD83\",\n    DATUM[\"North American Datum 1983\",\n        ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n            LENGTHUNIT[\"metre\",1]]],\n    PRIMEM[\"Greenwich\",0,\n        ANGLEUNIT[\"degree\",0.0174532925199433]],\n    CS[ellipsoidal,2],\n        AXIS[\"geodetic latitude (Lat)\",north,\n            ORDER[1],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n        AXIS[\"geodetic longitude (Lon)\",east,\n            ORDER[2],\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n    ID[\"EPSG\",4269]]"), class = "crs"), class = c("sfc_POLYGON", "sfc"), 
                                  precision = 0, bbox = structure(c(xmin = -88.67598, ymin = 45.713063, xmax = -88.0582574, ymax = 46.020269), class = "bbox"))), row.names = c(NA, 8L),
                sf_column = "geometry", agr = structure(c(margin = NA_integer_), levels = c("constant", "aggregate", "identity"), class = "factor"), class = c("sf", "tbl_df", "tbl", "data.frame"))

> df
Simple feature collection with 8 features and 1 field
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -88.67598 ymin: 45.71306 xmax: -88.05826 ymax: 46.02027
Geodetic CRS:  NAD83
# A tibble: 8 × 2
  margin                                                                                     geometry
*  <dbl>                                                                                <POLYGON [°]>
1  26.3  ((-88.12183 45.83176, -88.15787 45.8448, -88.17623 45.84465, -88.17795 45.80493, -88.1779...
2  26.8  ((-88.17623 45.84465, -88.17904 45.90581, -88.30353 45.90855, -88.30357 45.89411, -88.303...
3  20.7  ((-88.36525 45.72215, -88.36627 45.8087, -88.42548 45.80942, -88.55136 45.80986, -88.6757...
4  -4.21 ((-88.30389 45.80759, -88.30357 45.89411, -88.42522 45.89553, -88.42548 45.80942, -88.366...
5  11.0  ((-88.12183 45.83176, -88.1052 45.84241, -88.07048 45.87433, -88.10176 45.88319, -88.1024...
6  13.6  ((-88.17799 45.71809, -88.17795 45.80493, -88.30389 45.80759, -88.36627 45.8087, -88.3652...
7  23.5  ((-88.55136 45.80986, -88.55384 45.88374, -88.55096 45.89613, -88.67549 45.89596, -88.675...
8 -13.3  ((-88.55937 46.0141, -88.58827 46.0051, -88.61647 45.98773, -88.65865 45.98929, -88.67445...

I want to map the margin column to a red-blue diverging color palette with the midpoint set to 0. I'm using the {colorspace} package.

library(ggplot2)
library(colorspace)

ggplot(df) +
  geom_sf(aes(fill = margin)) +
  scale_fill_continuous_divergingx(palette = "RdBu", mid = 0)

enter image description here

Right now, the limits of the palette are set by the limits of the supplied data. So the most positive value, 26.8, is given the darkest shade of blue.

I want to change the palette so that the darkest shade of blue is given to a value of 100 (and the darkest shade of red is given to a value of -100). In this version of the map, all the colors will be shades of light red and light blue.

I want the ggplot legend will still show only the range of data included in this plot, as it currently does.

Setting the limits argument does half of this.

ggplot(df) +
  geom_sf(aes(fill = margin)) +
  scale_fill_continuous_divergingx(palette = "RdBu", mid = 0, limits = c(-100, 100))

enter image description here

But now the legend shows values from -100 to 100.

In an ideal solution, the colors will be mapped as in the second graph, but the legend will only show the range of data present in the current map (as in the first graph).


Solution

  • I think you want the limits= parameter:

    ggplot(df) +
      geom_sf(aes(fill = margin)) +
      scale_fill_continuous_divergingx(palette = "RdBu", mid = 0, limits = c(-100, 100))
    

    Edit: getting the legend to show only the range covered by the data

    Warning: this is a bit hacky and I'm not sure if it will work at scale

    The best I could do here was to assign the plot above to an object p and then use ggplot_build() to extract the colours used. The create a new plot using scale_fill_gradientn() and the colours extracted from the original.

    # run the orignal plot
    original_plot <- ggplot(df) +
      geom_sf(aes(fill = margin)) +
      scale_fill_continuous_divergingx(palette = "RdBu", mid = 0, limits = c(-100, 100))
    
    # extract the colours used
    mycolours <- ggplot_build(original_plot)$data[[1]]$fill[order(df$margin)]
    # rescale the values
    myvalues <- scales::rescale(sort(df$margin), c(0, 1))
    
    # use these in a new plot with scale_fill_gradientn
    df %>%
      ggplot() + 
        geom_sf(aes(fill = margin)) + 
        scale_fill_gradientn(colours = mycolours, values = myvalues)
    
    

    resulting image