In his paper A Layered Grammar of Graphics, Hadley Wickham re-builded Minard’s inforgraphic of Napoleon’s march on Russia to illustrate the layers mechanism in ggplot2. Since I’m the learning-by-doing type of person, in this post, I will reproduce Hadley’s example from start to finish.
Prepare data with dplyr package
The dataset is made available at this link.
minard <- read.csv("minard.csv", fileEncoding="UTF-8-BOM")
Let’s take a look at the all the original dataset. First, some column names are not good variable names, for example surviv = number of survivors, direc = direction, lonc, lont and lonp are different names for longitude. Second, the dataset is actually 3 tables merged together, the first 3 columns make up one table, the last 5 colums form another table. I’ll use those 2 tables to reproduce Minard’s graph.
minard
There is no need to reshape or tidy the dataset in this situation so the dplyr package is all we need to segment the original dataset.
library(dplyr)
#select relevant columns, rename columns's names and remove NA values
troops <- select(minard, long = lonp, lat = latp, survivors = surviv, direction = direc, division)
cities <- na.omit(select(minard, long = lonc, lat = latc, city))
#display tables
troops
cities
Plotting using ggplot2 package
ggplot2‘s layers mechanisim enables its users to “divide and conquer” a wide range of graphics. As a user, you simply layer your way to the final graphic. Subsequent layers inherent previous layers’ settings and can override those settings if needed.
First, we use ggplot() to create the “base layer”, the troops dataset will be passed to subsequent layers, similarly, long variable will be mapped to x-axis and lat varible will be mapped to y-axis in latter layers unless overrided.
library(ggplot2)
layer1 <- ggplot(troops, aes(long, lat))
layer1

This is the default code of geom_path() which makes up the second layer:
geom_path(mapping = NULL, data = NULL, stat = "identity", position = "identity", ..., lineend = "butt", linejoin = "round", linemitre = 1, arrow = NULL, na.rm = FALSE, show.legend = NA, inherit.aes = TRUE)
- Let’s see how some important settings apply to our case:
- data = NULL:
layer2 inherits the dataset (troops) specified in the call to ggplot() in layer1
- inherit.aes = TRUE: inherits the aesthetics in ggplot(), you don’t have to change this to
FALSE to override any aesthetic, FALSE means ignore all aesthetics in ggplot(), use it if you want to start a new layer from from scratch or there are too many aesthetics to override, TRUE means keep aesthetics in ggplot(), combined them with new aesthetics or override them. In our case, what are inheritted from ggplot() are: map long variable to x-axis and lat variable to y-axis.
- show.legend = NA: includes legend if any aesthetics are mapped, here, path sizes are mapped to number of survivors, color to direction, so there will be two legend corressponding to those two varibles.
- na.rm = FALSE: removes missing values with a warning, since I deleted mising values at data preparation step, there won’t be any warning showing up, if set to
TRUE, missing values will be removed without any warning. Unless I know the dataset extremely well, I will never set na.rm = TRUE, silent errors are the among the worst type of errors.
layer2 <- layer1 + geom_path(aes(size = survivors, color = direction, group = division), lineend = "round", linejoin = "round")
layer2

Now, to the third layer, the troops dataset specified in the call to ggplot() has now been overried with the cities dataset.
layer3 <- layer2 + geom_text(aes(label = city), size = 3, data = cities)
layer3

Did you notice that I didn’t name the next variable layer4 but named it finalGraph instead? I did that wasn’t because finalGraph is a better name, but because scale_size(), scale_color_manual(), xlab(), ylab(), ggtitle() add no new layer to the graph, they are SCALES functions, they control the mapping between data and aesthetics, in other words, they modify existing layers. But which layer do they modify? In this situation, they modified layer2 (i.e., the geom_path() layer).
The function scale_size() is very useful in this case to appropeiately display numeric values in the Survivors legend. The labels aesthetic can take function that takes the breaks value as input and returns labels as output, for example, here the comma() function takes the breaks value as input and changes a label from 1e+05 to 100,000.
You might be tempted to think that comma(breaks) also works but it doesn’t (I tried). Don’t let the = fools you, unlike v, breaks is the name of an aesthetic, not a variable in this case. Thus, you have to pass in the same vector assigned to breaks into comma(), here the vector is c(1, 2, 3) * 10^5. Now I hope you realize why, in R, = and <- should not be used interchangeably.
v <- c(1, 2, 3) * 10^5
finalGraph <- layer3 + scale_size("Survivors", range = c(1, 10), breaks = v, labels = comma(v)) +
scale_color_manual("Direction", values = c("grey50", "red")) +
xlab("Longitude") + ylab("Latitude") + ggtitle("Napoleon's march to Russia")
finalGraph

That’s it, thanks for reading!
LS0tDQp0aXRsZTogIlJlLWJ1aWxkIE1pbmFyZCdzIE5hcG9sZW9uJ3MgbWFyY2ggb24gUnVzc2lhIHdpdGggZHBseXIgJiBnZ3Bsb3QyIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KSW4gaGlzIHBhcGVyIFsqQSBMYXllcmVkIEdyYW1tYXIgb2YgR3JhcGhpY3MqXShodHRwOi8vdml0YS5oYWQuY28ubnovcGFwZXJzL2xheWVyZWQtZ3JhbW1hci5wZGYpLCBIYWRsZXkgV2lja2hhbSByZS1idWlsZGVkIE1pbmFyZCdzIGluZm9yZ3JhcGhpYyBvZiBbTmFwb2xlb24ncyBtYXJjaCBvbiBSdXNzaWFdKGh0dHBzOi8vY29tbW9ucy53aWtpbWVkaWEub3JnL3dpa2kvRmlsZTpNaW5hcmQucG5nIy9tZWRpYS9GaWxlOk1pbmFyZC5wbmcpIHRvIGlsbHVzdHJhdGUgdGhlICoqbGF5ZXJzKiogbWVjaGFuaXNtIGluIGdncGxvdDIuIFNpbmNlIEknbSB0aGUgbGVhcm5pbmctYnktZG9pbmcgdHlwZSBvZiBwZXJzb24sIGluIHRoaXMgcG9zdCwgSSB3aWxsIHJlcHJvZHVjZSBIYWRsZXkncyBleGFtcGxlIGZyb20gc3RhcnQgdG8gZmluaXNoLiAgICAgICANCg0KI1ByZXBhcmUgZGF0YSB3aXRoIGBkcGx5cmAgcGFja2FnZSANClRoZSBkYXRhc2V0IGlzIG1hZGUgYXZhaWxhYmxlIGF0IHRoaXMgW2xpbmtdKGh0dHBzOi8vd3d3LmNzLnVpYy5lZHUvfndpbGtpbnNvbi9UaGVHcmFtbWFyT2ZHcmFwaGljcy9taW5hcmQudHh0KS4gIA0KDQpgYGB7cn0NCm1pbmFyZCA8LSByZWFkLmNzdigibWluYXJkLmNzdiIsIGZpbGVFbmNvZGluZz0iVVRGLTgtQk9NIikNCmBgYA0KDQpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgYWxsIHRoZSBvcmlnaW5hbCBkYXRhc2V0LiBGaXJzdCwgc29tZSBjb2x1bW4gbmFtZXMgYXJlIG5vdCBnb29kIHZhcmlhYmxlIG5hbWVzLCBmb3IgZXhhbXBsZSBgc3Vydml2YCA9IG51bWJlciBvZiBzdXJ2aXZvcnMsIGBkaXJlY2AgPSBkaXJlY3Rpb24sIGBsb25jYCwgYGxvbnRgIGFuZCBgbG9ucGAgYXJlIGRpZmZlcmVudCBuYW1lcyBmb3IgbG9uZ2l0dWRlLiBTZWNvbmQsIHRoZSBkYXRhc2V0IGlzIGFjdHVhbGx5IDMgdGFibGVzIG1lcmdlZCB0b2dldGhlciwgdGhlIGZpcnN0IDMgY29sdW1ucyBtYWtlIHVwIG9uZSB0YWJsZSwgdGhlIGxhc3QgNSBjb2x1bXMgZm9ybSBhbm90aGVyIHRhYmxlLiBJJ2xsIHVzZSB0aG9zZSAyIHRhYmxlcyB0byByZXByb2R1Y2UgTWluYXJkJ3MgZ3JhcGguICAgDQpgYGB7cn0NCm1pbmFyZA0KYGBgDQoNClRoZXJlIGlzIG5vIG5lZWQgdG8gcmVzaGFwZSBvciB0aWR5IHRoZSBkYXRhc2V0IGluIHRoaXMgc2l0dWF0aW9uIHNvIHRoZSBgZHBseXJgIHBhY2thZ2UgaXMgYWxsIHdlIG5lZWQgdG8gc2VnbWVudCB0aGUgb3JpZ2luYWwgZGF0YXNldC4gICAgDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQojc2VsZWN0IHJlbGV2YW50IGNvbHVtbnMsIHJlbmFtZSBjb2x1bW5zJ3MgbmFtZXMgYW5kIHJlbW92ZSBOQSB2YWx1ZXMNCnRyb29wcyA8LSBzZWxlY3QobWluYXJkLCBsb25nID0gbG9ucCwgbGF0ID0gbGF0cCwgc3Vydml2b3JzID0gc3Vydml2LCBkaXJlY3Rpb24gPSBkaXJlYywgZGl2aXNpb24pDQpjaXRpZXMgPC0gbmEub21pdChzZWxlY3QobWluYXJkLCBsb25nID0gbG9uYywgbGF0ID0gbGF0YywgY2l0eSkpDQojZGlzcGxheSB0YWJsZXMNCnRyb29wcw0KY2l0aWVzDQpgYGANCg0KI1Bsb3R0aW5nIHVzaW5nIGBnZ3Bsb3QyYCBwYWNrYWdlDQoNCmBnZ3Bsb3QyYCdzICoqbGF5ZXJzIG1lY2hhbmlzaW0qKiBlbmFibGVzIGl0cyB1c2VycyB0byAiZGl2aWRlIGFuZCBjb25xdWVyIiBhIHdpZGUgcmFuZ2Ugb2YgZ3JhcGhpY3MuIEFzIGEgdXNlciwgeW91IHNpbXBseSAqbGF5ZXIqIHlvdXIgd2F5IHRvIHRoZSBmaW5hbCBncmFwaGljLiBTdWJzZXF1ZW50IGxheWVycyBpbmhlcmVudCBwcmV2aW91cyBsYXllcnMnIHNldHRpbmdzIGFuZCBjYW4gb3ZlcnJpZGUgdGhvc2Ugc2V0dGluZ3MgaWYgbmVlZGVkLiAgDQoNCkZpcnN0LCB3ZSB1c2UgYGdncGxvdCgpYCB0byBjcmVhdGUgdGhlICoqImJhc2UgbGF5ZXIiKiosIHRoZSBgdHJvb3BzYCBkYXRhc2V0IHdpbGwgYmUgcGFzc2VkIHRvIHN1YnNlcXVlbnQgbGF5ZXJzLCBzaW1pbGFybHksIGBsb25nYCB2YXJpYWJsZSB3aWxsIGJlIG1hcHBlZCB0byB4LWF4aXMgYW5kIGBsYXRgIHZhcmlibGUgd2lsbCBiZSBtYXBwZWQgdG8geS1heGlzIGluIGxhdHRlciBsYXllcnMgdW5sZXNzIG92ZXJyaWRlZC4gIA0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmxheWVyMSA8LSBnZ3Bsb3QodHJvb3BzLCBhZXMobG9uZywgbGF0KSkNCmxheWVyMQ0KYGBgDQoNClRoaXMgaXMgdGhlIFtkZWZhdWx0XShodHRwOi8vZG9jcy5nZ3Bsb3QyLm9yZy9jdXJyZW50L2dlb21fcGF0aC5odG1sKSBjb2RlIG9mIGBnZW9tX3BhdGgoKWAgd2hpY2ggbWFrZXMgdXAgdGhlIHNlY29uZCBsYXllcjogIA0KYGBge3J9DQpnZW9tX3BhdGgobWFwcGluZyA9IE5VTEwsIGRhdGEgPSBOVUxMLCBzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiaWRlbnRpdHkiLCAuLi4sIGxpbmVlbmQgPSAiYnV0dCIsIGxpbmVqb2luID0gInJvdW5kIiwgbGluZW1pdHJlID0gMSwgYXJyb3cgPSBOVUxMLCBuYS5ybSA9IEZBTFNFLCBzaG93LmxlZ2VuZCA9IE5BLCBpbmhlcml0LmFlcyA9IFRSVUUpDQpgYGANCg0KKiBMZXQncyBzZWUgaG93IHNvbWUgaW1wb3J0YW50IHNldHRpbmdzIGFwcGx5IHRvIG91ciBjYXNlOiANCiAgICArICoqZGF0YSA9IE5VTEwqKjogYGxheWVyMmAgaW5oZXJpdHMgdGhlIGRhdGFzZXQgKGB0cm9vcHNgKSBzcGVjaWZpZWQgaW4gdGhlIGNhbGwgdG8gZ2dwbG90KCkgaW4gYGxheWVyMWANCiAgICArICoqaW5oZXJpdC5hZXMgPSBUUlVFKio6IGluaGVyaXRzIHRoZSBhZXN0aGV0aWNzIGluIGdncGxvdCgpLCB5b3UgZG9uJ3QgaGF2ZSB0byBjaGFuZ2UgdGhpcyB0byBgRkFMU0VgIHRvIG92ZXJyaWRlIGFueSBhZXN0aGV0aWMsIGBGQUxTRWAgbWVhbnMgaWdub3JlIGFsbCBhZXN0aGV0aWNzIGluIGdncGxvdCgpLCB1c2UgaXQgaWYgeW91IHdhbnQgdG8gc3RhcnQgYSBuZXcgbGF5ZXIgZnJvbSBmcm9tIHNjcmF0Y2ggb3IgdGhlcmUgYXJlIHRvbyBtYW55IGFlc3RoZXRpY3MgdG8gb3ZlcnJpZGUsIGBUUlVFYCBtZWFucyBrZWVwIGFlc3RoZXRpY3MgaW4gZ2dwbG90KCksIGNvbWJpbmVkIHRoZW0gd2l0aCBuZXcgYWVzdGhldGljcyBvciBvdmVycmlkZSB0aGVtLiBJbiBvdXIgY2FzZSwgd2hhdCBhcmUgaW5oZXJpdHRlZCBmcm9tIGdncGxvdCgpIGFyZTogbWFwIGBsb25nYCB2YXJpYWJsZSB0byB4LWF4aXMgYW5kIGBsYXRgIHZhcmlhYmxlIHRvIHktYXhpcy4NCiAgICArICoqc2hvdy5sZWdlbmQgPSBOQSoqOiBpbmNsdWRlcyBsZWdlbmQgaWYgYW55IGFlc3RoZXRpY3MgYXJlIG1hcHBlZCwgaGVyZSwgcGF0aCBzaXplcyBhcmUgbWFwcGVkIHRvIG51bWJlciBvZiBzdXJ2aXZvcnMsIGNvbG9yIHRvIGRpcmVjdGlvbiwgc28gdGhlcmUgd2lsbCBiZSB0d28gbGVnZW5kIGNvcnJlc3Nwb25kaW5nIHRvIHRob3NlIHR3byB2YXJpYmxlcy4NCiAgICArICoqbmEucm0gPSBGQUxTRSoqOiByZW1vdmVzIG1pc3NpbmcgdmFsdWVzIHdpdGggYSB3YXJuaW5nLCBzaW5jZSBJIGRlbGV0ZWQgbWlzaW5nIHZhbHVlcyBhdCBkYXRhIHByZXBhcmF0aW9uIHN0ZXAsIHRoZXJlIHdvbid0IGJlIGFueSB3YXJuaW5nIHNob3dpbmcgdXAsIGlmIHNldCB0byBgVFJVRWAsIG1pc3NpbmcgdmFsdWVzIHdpbGwgYmUgcmVtb3ZlZCB3aXRob3V0IGFueSB3YXJuaW5nLiBVbmxlc3MgSSBrbm93IHRoZSBkYXRhc2V0IGV4dHJlbWVseSB3ZWxsLCBJIHdpbGwgbmV2ZXIgc2V0IG5hLnJtID0gVFJVRSwgc2lsZW50IGVycm9ycyBhcmUgdGhlIGFtb25nIHRoZSB3b3JzdCB0eXBlIG9mIGVycm9ycy4NCg0KYGBge3J9DQpsYXllcjIgPC0gbGF5ZXIxICsgZ2VvbV9wYXRoKGFlcyhzaXplID0gc3Vydml2b3JzLCBjb2xvciA9IGRpcmVjdGlvbiwgZ3JvdXAgPSBkaXZpc2lvbiksIGxpbmVlbmQgPSAicm91bmQiLCBsaW5lam9pbiA9ICJyb3VuZCIpDQpsYXllcjINCmBgYA0KDQpOb3csIHRvIHRoZSB0aGlyZCBsYXllciwgdGhlIGB0cm9vcHNgIGRhdGFzZXQgc3BlY2lmaWVkIGluIHRoZSBjYWxsIHRvIGdncGxvdCgpIGhhcyBub3cgYmVlbiBvdmVycmllZCB3aXRoIHRoZSBgY2l0aWVzYCBkYXRhc2V0LiAgIA0KYGBge3J9DQpsYXllcjMgPC0gbGF5ZXIyICsgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGNpdHkpLCBzaXplID0gMywgZGF0YSA9IGNpdGllcykNCmxheWVyMw0KYGBgDQpEaWQgeW91IG5vdGljZSB0aGF0IEkgZGlkbid0IG5hbWUgdGhlIG5leHQgdmFyaWFibGUgYGxheWVyNGAgYnV0IG5hbWVkIGl0IGBmaW5hbEdyYXBoYCBpbnN0ZWFkPyBJIGRpZCB0aGF0IHdhc24ndCBiZWNhdXNlIGBmaW5hbEdyYXBoYCBpcyBhIGJldHRlciBuYW1lLCBidXQgYmVjYXVzZSBbc2NhbGVfc2l6ZSgpXShodHRwOi8vZG9jcy5nZ3Bsb3QyLm9yZy9jdXJyZW50L3NjYWxlX3NpemUuaHRtbCksIFtzY2FsZV9jb2xvcl9tYW51YWwoKV0oaHR0cDovL2RvY3MuZ2dwbG90Mi5vcmcvY3VycmVudC9zY2FsZV9tYW51YWwuaHRtbCksIFt4bGFiKCksIHlsYWIoKSwgZ2d0aXRsZSgpXShodHRwOi8vZG9jcy5nZ3Bsb3QyLm9yZy9jdXJyZW50L2xhYnMuaHRtbCkgYWRkIG5vIG5ldyBsYXllciB0byB0aGUgZ3JhcGgsIHRoZXkgYXJlIFNDQUxFUyBmdW5jdGlvbnMsIHRoZXkgY29udHJvbCB0aGUgbWFwcGluZyBiZXR3ZWVuIGRhdGEgYW5kIGFlc3RoZXRpY3MsIGluIG90aGVyIHdvcmRzLCB0aGV5IG1vZGlmeSBleGlzdGluZyBsYXllcnMuIEJ1dCB3aGljaCBsYXllciBkbyB0aGV5IG1vZGlmeT8gSW4gdGhpcyBzaXR1YXRpb24sIHRoZXkgbW9kaWZpZWQgYGxheWVyMmAgKGkuZS4sIHRoZSBgZ2VvbV9wYXRoKClgIGxheWVyKS4gICANCg0KVGhlIGZ1bmN0aW9uIGBzY2FsZV9zaXplKClgIGlzIHZlcnkgdXNlZnVsIGluIHRoaXMgY2FzZSB0byBhcHByb3BlaWF0ZWx5IGRpc3BsYXkgbnVtZXJpYyB2YWx1ZXMgaW4gdGhlIGBTdXJ2aXZvcnNgIGxlZ2VuZC4gVGhlIGBsYWJlbHNgIGFlc3RoZXRpYyBjYW4gdGFrZSBmdW5jdGlvbiB0aGF0IHRha2VzIHRoZSBgYnJlYWtzYCAqdmFsdWUqIGFzIGlucHV0IGFuZCByZXR1cm5zIGxhYmVscyBhcyBvdXRwdXQsIGZvciBleGFtcGxlLCBoZXJlIHRoZSBgY29tbWEoKWAgZnVuY3Rpb24gdGFrZXMgdGhlIGBicmVha3NgICp2YWx1ZSogYXMgaW5wdXQgYW5kIGNoYW5nZXMgYSBsYWJlbCBmcm9tIGAxZSswNWAgdG8gYDEwMCwwMDBgLiANCg0KWW91IG1pZ2h0IGJlIHRlbXB0ZWQgdG8gdGhpbmsgdGhhdCBgY29tbWEoYnJlYWtzKWAgYWxzbyB3b3JrcyBidXQgaXQgZG9lc24ndCAoSSB0cmllZCkuIERvbid0IGxldCB0aGUgYD1gIGZvb2xzIHlvdSwgdW5saWtlIGB2YCwgYGJyZWFrc2AgaXMgdGhlIG5hbWUgb2YgYW4gYWVzdGhldGljLCBub3QgYSB2YXJpYWJsZSBpbiB0aGlzIGNhc2UuIFRodXMsIHlvdSBoYXZlIHRvIHBhc3MgaW4gdGhlIHNhbWUgdmVjdG9yIGFzc2lnbmVkIHRvIGBicmVha3NgIGludG8gYGNvbW1hKClgLCBoZXJlIHRoZSB2ZWN0b3IgaXMgYGMoMSwgMiwgMykgKiAxMF41YC4gTm93IEkgaG9wZSB5b3UgcmVhbGl6ZSB3aHksIGluIFIsIGA9YCBhbmQgYDwtYCBzaG91bGQgKipub3QqKiBiZSB1c2VkIGludGVyY2hhbmdlYWJseS4gICANCg0KYGBge3J9DQp2IDwtIGMoMSwgMiwgMykgKiAxMF41DQpmaW5hbEdyYXBoIDwtIGxheWVyMyArIHNjYWxlX3NpemUoIlN1cnZpdm9ycyIsIHJhbmdlID0gYygxLCAxMCksIGJyZWFrcyA9IHYsIGxhYmVscyA9IGNvbW1hKHYpKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCgiRGlyZWN0aW9uIiwgdmFsdWVzID0gYygiZ3JleTUwIiwgInJlZCIpKSArDQogIHhsYWIoIkxvbmdpdHVkZSIpICsgeWxhYigiTGF0aXR1ZGUiKSArIGdndGl0bGUoIk5hcG9sZW9uJ3MgbWFyY2ggdG8gUnVzc2lhIikNCmZpbmFsR3JhcGgNCmBgYA0KDQpUaGF0J3MgaXQsIHRoYW5rcyBmb3IgcmVhZGluZyENCg==