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==