This blog post concerns the development version of units, installed by

devtools::install_github("edzer/units")

[view raw Rmd]

Have you ever tried to properly add measurement units to R plots? It might go like this:

xlab = parse(text = "temperature ~~ group('[', degree * C, ']')")
ylab = parse(text = "speed ~~ group('[', m * ~~ s^-1, ']')")
par(mar = par("mar") + c(0, .3, 0, 0)) # avoids cutting of superscript
plot(3 + 1:10 + 2 * rnorm(10), xlab = xlab, ylab = ylab)

The main observation is, of course that it can be done. However,

  • it looks geeky, and not quite intuitive
  • you would typically postpone this work to just before submitting the paper, or during review
  • you need this so infrequently that you tend to forget how it works.

Although well-written help is found in ?plotmath, all three observations cause frustration.

The original paper desribing plotmath is by Paul Murrell and Ross Ihaka. R core member Paul Murrell also wrote package grid, part of base R. Few people use it directly, but without it ggplot2 or lattice could not exist.

Automatic unit handling

The new units CRAN package now makes working with units

  • easier
  • automatic, and
  • less error-prone

Here is an example using mtcars. First, we specify the imperial units to those known in the udunits2 database:

library(units)
gallon = make_unit("gallon")
consumption = mtcars$mpg * with(ud_units, mi/gallon)
displacement = mtcars$disp * ud_units[["in"]]^3

For displacement, we cannot use the normal lookup in the database

displacement = mtcars$disp * with(ud_units, in)

because in (inch) is also a reserved word in R.

We convert these values to SI units by

units(displacement) = with(ud_units, cm^3)
units(consumption) = with(ud_units, km/l)
consumption[1:5]

## Units: km/l
## [1] 8.928017 8.928017 9.693276 9.098075 7.950187

Automatic measurement units in axis labels

We can plot these numeric variabes of type units by

par(mar = par("mar") + c(0, .1, 0, 0)) # avoids cutting of brackets at lhs
plot(displacement, consumption)

The units automatically appear in axis labels! If we want to have negative power instead of division bars, we can set a global option

units_options(negative_power = TRUE) # division becomes ^-1

Expressions such as

1/displacement [1:10]

## Units: cm^-3
##  [1] 0.0003813984 0.0003813984 0.0005650347 0.0002365261 0.0001695104
##  [6] 0.0002712166 0.0001695104 0.0004159764 0.0004334073 0.0003641035

automatically convert units, which also happens in plots (note the converted units symbols):

par(mar = par("mar") + c(0, .3, 0, 0))
plot(1/displacement, 1/consumption)

How to do this with ggplot?

We can of course plot these data by dropping units:

library(ggplot2)
ggplot() + geom_point(aes(x = as.numeric(displacement), 
    y = as.numeric(consumption)))

but that doesn’t show us units. Giving the units as variables gives an error:

ggplot() + geom_point(aes(x = displacement, y = consumption))

## Don't know how to automatically pick scale for object of type units. Defaulting to continuous.
## Don't know how to automatically pick scale for object of type units. Defaulting to continuous.

## Error in Ops.units(x, range[1]): both operands of the expression should be "units" objects

(I could make that error go away by letting units drop the requirement that in a comparison both sides should have compatible units, which of course would be wrong.)

We can then go all the way with

ggplot() + geom_point(aes(x = as.numeric(displacement), 
    y = as.numeric(consumption))) +
    xlab(make_unit_label("displacement", displacement)) +
    ylab(make_unit_label("consumption", consumption))

which at least doesn’t cut off the left label, but feels too convoluted and error-prone.

Oh ggplot gurus, who can help us out, here? How can we obtain that last plot by

ggplot() + geom_point(aes(x = displacement, y = consumption))

?

Update of Dec 2, 2016

Thanks to ggguru Thomas Lin Pedersen, automatic units in axis labels of ggplots are now provided by CRAN package ggforce:

library(ggforce)
ggplot() + geom_point(aes(x = displacement, y = consumption))

and see this vignette for more examples. In addition to printing units in default axes labels, it allows for on-the-fly unit conversion in ggplot expressions:

dm = with(ud_units, dm)
gallon = with(ud_units, gallon)
mi = with(ud_units, mi)
ggplot() + geom_point(aes(x = displacement, y = consumption)) +
    scale_x_unit(unit = dm^3) + scale_y_unit(unit = mi/gallon)