# Units of Measurement for R Vectors: an Introduction

R has little support for physical measurement units. The exception is formed by time differences: time differences objects of class `difftime` have a `units` attribute that can be modified:

    t1 = Sys.time()
    t2 = t1 + 3600
    d = t2 - t1
    class(d)
    ## [1] "difftime"
    units(d)
    ## [1] "hours"
    d
    ## Time difference of 1 hours
    units(d) = "secs"
    d
    ## Time difference of 3600 secs

We see here that the `units` method is used to retrieve and modify the unit of time differences.

The `units` package generalizes this idea to other physical units, building upon the udunits2 C library. The `udunits2` library provides the following operations:

- validating whether an expression, such as `m/s` is a valid physical unit
- verifying whether two units such as `m/s` and `km/h` are convertible
- converting values between two convertible units
- providing names and symbols for specific units
- handle different character encodings (utf8, ascii, iso-8859-1 and latin1)

The `units` R package uses the udunits2 C library to extend R with functionality for manipulating numeric vectors that have physical measurement units associated with them, in a similar way as `difftime` objects behave.

## Setting units, unit conversion

We can set units to numerical values by `set_units`:

    library(units)
    (a <- set_units(runif(10),  m/s))
    ## Units: [m/s]
    ##  [1] 0.8952217 0.5174261 0.1532732 0.6343891 0.1214279 0.9735305 0.9129069
    ##  [8] 0.5624314 0.1946815 0.1837322

the result, e.g.

    set_units(10, m/s)
    ## 10 [m/s]

literally means “10 times 1 m divided by 1 s”. In writing, the “1” values are omitted, and the multiplication is implicit.

### Unit conversion

When conversion is meaningful, such as hours to seconds or meters to kilometers, conversion can be done explicitly by setting the units of a vector

    b = a
    units(b) <- make_units(km/h)
    b
    ## Units: [km/h]
    ##  [1] 3.2227982 1.8627339 0.5517834 2.2838008 0.4371403 3.5047100 3.2864648
    ##  [8] 2.0247532 0.7008533 0.6614361

## Basic manipulations

### Arithmetic operations

Arithmetic operations verify units, and create new ones

    a + a
    ## Units: [m/s]
    ##  [1] 1.7904434 1.0348522 0.3065463 1.2687782 0.2428557 1.9470611 1.8258138
    ##  [8] 1.1248629 0.3893629 0.3674645
    a * a
    ## Units: [m^2/s^2]
    ##  [1] 0.80142191 0.26772975 0.02349266 0.40244955 0.01474473 0.94776172
    ##  [7] 0.83339899 0.31632913 0.03790088 0.03375754
    a ^ 2
    ## Units: [m^2/s^2]
    ##  [1] 0.80142191 0.26772975 0.02349266 0.40244955 0.01474473 0.94776172
    ##  [7] 0.83339899 0.31632913 0.03790088 0.03375754
    a ** -2
    ## Units: [s^2/m^2]
    ##  [1]  1.247782  3.735110 42.566484  2.484783 67.820853  1.055118  1.199905
    ##  [8]  3.161264 26.384615 29.623014

and convert to the units of the first argument if necessary:

    a + b # m/s + km/h -> m/s
    ## Units: [m/s]
    ##  [1] 1.7904434 1.0348522 0.3065463 1.2687782 0.2428557 1.9470611 1.8258138
    ##  [8] 1.1248629 0.3893629 0.3674645

Currently, powers are only supported for integer powers, so using `a ** 2.5` would result in an error.

### Unit simplification

There are some basic simplification of units:

    t <- make_units(s)
    a * t
    ## Units: [m]
    ##  [1] 0.8952217 0.5174261 0.1532732 0.6343891 0.1214279 0.9735305 0.9129069
    ##  [8] 0.5624314 0.1946815 0.1837322

which also work when units need to be converted before they can be simplified:

    t <- make_units(min)
    a * t
    ## Units: [m]
    ##  [1] 53.713303 31.045565  9.196390 38.063347  7.285672 58.411833 54.774413
    ##  [8] 33.745887 11.680888 11.023935

Simplification to unit-less values gives the “1” as unit:

    m <- make_units(m)
    a * t / m
    ## Units: [1]
    ##  [1] 53.713303 31.045565  9.196390 38.063347  7.285672 58.411833 54.774413
    ##  [8] 33.745887 11.680888 11.023935

Allowed operations that require convertible units are `+`, `-`, `==`, `!=`, `<`, `>`, `<=`, `>=`. Operations that lead to new units are `*`, `/`, and the power operations `**` and `^`.

### Mathematical functions

Mathematical operations allowed are: `abs`, `sign`, `floor`, `ceiling`, `trunc`, `round`, `signif`, `log`, `cumsum`, `cummax`, `cummin`.

    signif(a ** 2 / 3, 3)
    ## Units: [m^2/s^2]
    ##  [1] 0.26700 0.08920 0.00783 0.13400 0.00491 0.31600 0.27800 0.10500 0.01260
    ## [10] 0.01130
    cumsum(a)
    ## Units: [m/s]
    ##  [1] 0.8952217 1.4126478 1.5659210 2.2003101 2.3217379 3.2952685 4.2081754
    ##  [8] 4.7706068 4.9652883 5.1490205
    log(a) # base defaults to exp(1)
    ## Units: [(ln(re 1 m.s-1))]
    ##  [1] -0.11068387 -0.65888860 -1.87553359 -0.45509277 -2.10843486 -0.02682608
    ##  [7] -0.09112139 -0.57548602 -1.63639053 -1.69427578
    log(a, base = 10)
    ## Units: [(lg(re 1 m.s-1))]
    ##  [1] -0.04806939 -0.28615168 -0.81453389 -0.19764428 -0.91568162 -0.01165042
    ##  [7] -0.03957352 -0.24993040 -0.71067538 -0.73581462
    log(a, base = 2)
    ## Units: [(lb(re 1 m.s-1))]
    ##  [1] -0.15968307 -0.95057531 -2.70582301 -0.65656008 -3.04182851 -0.03870185
    ##  [7] -0.13146037 -0.83025083 -2.36081250 -2.44432327

### Summary functions

Summary functions `sum`, `min`, `max`, and `range` are allowed:

    sum(a)
    ## 5.149021 [m/s]
    min(a)
    ## 0.1214279 [m/s]
    max(a)
    ## 0.9735305 [m/s]
    range(a)
    ## Units: [m/s]
    ## [1] 0.1214279 0.9735305
    make_units(min(m/s, km/h)) # converts to first unit:
    ## 0.2777778 [m/s]

### Printing

Following `difftime`, printing behaves differently for length-one vectors:

    a
    ## Units: [m/s]
    ##  [1] 0.8952217 0.5174261 0.1532732 0.6343891 0.1214279 0.9735305 0.9129069
    ##  [8] 0.5624314 0.1946815 0.1837322
    a[1]
    ## 0.8952217 [m/s]

### Subsetting

The usual subsetting rules work:

    a[2:5]
    ## Units: [m/s]
    ## [1] 0.5174261 0.1532732 0.6343891 0.1214279
    a[-(1:9)]
    ## 0.1837322 [m/s]

### Concatenation

    c(a,a)
    ## Units: [m/s]
    ##  [1] 0.8952217 0.5174261 0.1532732 0.6343891 0.1214279 0.9735305 0.9129069
    ##  [8] 0.5624314 0.1946815 0.1837322 0.8952217 0.5174261 0.1532732 0.6343891
    ## [15] 0.1214279 0.9735305 0.9129069 0.5624314 0.1946815 0.1837322

concatenation converts to the units of the first argument, if necessary:

    c(a,b) # m/s, km/h -> m/s
    ## Units: [m/s]
    ##  [1] 0.8952217 0.5174261 0.1532732 0.6343891 0.1214279 0.9735305 0.9129069
    ##  [8] 0.5624314 0.1946815 0.1837322 0.8952217 0.5174261 0.1532732 0.6343891
    ## [15] 0.1214279 0.9735305 0.9129069 0.5624314 0.1946815 0.1837322
    c(b,a) # km/h, m/s -> km/h
    ## Units: [km/h]
    ##  [1] 3.2227982 1.8627339 0.5517834 2.2838008 0.4371403 3.5047100 3.2864648
    ##  [8] 2.0247532 0.7008533 0.6614361 3.2227982 1.8627339 0.5517834 2.2838008
    ## [15] 0.4371403 3.5047100 3.2864648 2.0247532 0.7008533 0.6614361

## Conversion to/from `difftime`

From `difftime` to `units`:

    t1 = Sys.time()
    t2 = t1 + 3600
    d = t2 - t1
    (du = as_units(d))
    ## 1 [h]

vice versa:

    (dt = as_difftime(du))
    ## Time difference of 1 hours
    class(dt)
    ## [1] "difftime"

## units in `matrix` objects

    set_units(matrix(1:4,2,2), m/s)
    ## Units: [m/s]
    ##      [,1] [,2]
    ## [1,]    1    3
    ## [2,]    2    4
    set_units(matrix(1:4,2,2), m/s * m/s)
    ## Units: [m^2/s^2]
    ##      [,1] [,2]
    ## [1,]    1    3
    ## [2,]    2    4

but

    set_units(matrix(1:4,2,2), m/s) %*% set_units(4:3, m/s)
    ##      [,1]
    ## [1,]   13
    ## [2,]   20

strips units.

## units objects in `data.frame`s

units in `data.frame` objects are printed, but do not appear in `summary`:.

    set.seed(131)
    d <- data.frame(x = runif(4),
                        y = set_units(runif(4), s),
                        z = set_units(1:4, m/s))
    d
    ##           x             y       z
    ## 1 0.2064370 0.8463468 [s] 1 [m/s]
    ## 2 0.1249422 0.5292048 [s] 2 [m/s]
    ## 3 0.2932732 0.5186254 [s] 3 [m/s]
    ## 4 0.3757797 0.2378545 [s] 4 [m/s]
    summary(d)
    ##        x                y                z
    ##  Min.   :0.1249   Min.   :0.2379   Min.   :1.00
    ##  1st Qu.:0.1861   1st Qu.:0.4484   1st Qu.:1.75
    ##  Median :0.2499   Median :0.5239   Median :2.50
    ##  Mean   :0.2501   Mean   :0.5330   Mean   :2.50
    ##  3rd Qu.:0.3139   3rd Qu.:0.6085   3rd Qu.:3.25
    ##  Max.   :0.3758   Max.   :0.8463   Max.   :4.00
    d$yz = with(d, y * z)
    d
    ##           x             y       z            yz
    ## 1 0.2064370 0.8463468 [s] 1 [m/s] 0.8463468 [m]
    ## 2 0.1249422 0.5292048 [s] 2 [m/s] 1.0584095 [m]
    ## 3 0.2932732 0.5186254 [s] 3 [m/s] 1.5558761 [m]
    ## 4 0.3757797 0.2378545 [s] 4 [m/s] 0.9514180 [m]
    d[1, "yz"]
    ## 0.8463468 [m]

## Formatting

Units are often written in the form `m2 s-1`, for square meter per second. This can be defined as unit, and also parsed by `as_units`:

    (x = 1:10 * as_units("m2 s-1"))
    ## Units: [m^2/s]
    ##  [1]  1  2  3  4  5  6  7  8  9 10

udunits understands such string, and can convert them

    y = 1:10 * make_units(m^2/s)
    x + y
    ## Units: [m^2/s]
    ##  [1]  2  4  6  8 10 12 14 16 18 20

Printing units in this form is done by

    deparse_unit(x)
    ## [1] "m2 s-1"

## Plotting

Base scatter plots and histograms support automatic unit placement in axis labels. In the following example we first convert to SI units. (Unit `in` needs a bit special treatment, because `in` is a reserved word in R.)

    mar = par("mar") + c(0, .3, 0, 0)
    displacement = mtcars$disp * as_units("in")^3
    units(displacement) = make_units(cm^3)
    weight = mtcars$wt * 1000 * make_units(lb)
    units(weight) = make_units(kg)
    par(mar = mar)
    plot(weight, displacement)

We can change grouping symbols from `[ ]` into `( )`:

    units_options(group = c("(", ")") )  # parenthesis instead of square brackets
    par(mar = mar)
    plot(weight, displacement)

We can also remove grouping symbols, increase space between variable name and unit by:

    units_options(sep = c("~~~", "~"), group = c("", ""))  # no brackets; extra space
    par(mar = mar)
    plot(weight, displacement)

More complex units can be plotted either with negative powers, or as divisions, by modifying one of `units`’s global options using `units_options`:

    gallon = as_units("gallon")
    consumption = mtcars$mpg * make_units(mi/gallon)
    units(consumption) = make_units(km/l)
    par(mar = mar)
    plot(displacement, consumption) # division in consumption

    units_options(negative_power = TRUE) # division becomes ^-1
    plot(displacement, consumption) # division in consumption

As usual, units modify automatically in expressions:

    units_options(negative_power = TRUE) # division becomes ^-1
    par(mar = mar)
    plot(displacement, consumption)

    plot(1/displacement, 1/consumption)