# 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.55381844 0.02812965 0.33080525 0.62777998 0.43092858 0.21895528
    ##  [7] 0.74398914 0.26355383 0.63086172 0.67370789

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] 1.9937464 0.1012667 1.1908989 2.2600079 1.5513429 0.7882390 2.6783609
    ##  [8] 0.9487938 2.2711022 2.4253484

## Basic manipulations

### Arithmetic operations

Arithmetic operations verify units, and create new ones

    a + a
    ## Units: [m/s]
    ##  [1] 1.10763689 0.05625929 0.66161050 1.25555996 0.86185716 0.43791057
    ##  [7] 1.48797829 0.52710767 1.26172345 1.34741577
    a * a
    ## Units: [m^2/s^2]
    ##  [1] 0.306714869 0.000791277 0.109432112 0.394107705 0.185699441 0.047941417
    ##  [7] 0.553519845 0.069460623 0.397986515 0.453882317
    a ^ 2
    ## Units: [m^2/s^2]
    ##  [1] 0.306714869 0.000791277 0.109432112 0.394107705 0.185699441 0.047941417
    ##  [7] 0.553519845 0.069460623 0.397986515 0.453882317
    a ** -2
    ## Units: [s^2/m^2]
    ##  [1]    3.260357 1263.779979    9.138085    2.537377    5.385046   20.858791
    ##  [7]    1.806620   14.396646    2.512648    2.203214

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

    a + b # m/s + km/h -> m/s
    ## Units: [m/s]
    ##  [1] 1.10763689 0.05625929 0.66161050 1.25555996 0.86185716 0.43791057
    ##  [7] 1.48797829 0.52710767 1.26172345 1.34741577

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.55381844 0.02812965 0.33080525 0.62777998 0.43092858 0.21895528
    ##  [7] 0.74398914 0.26355383 0.63086172 0.67370789

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

    t <- make_units(min)
    a * t
    ## Units: [m]
    ##  [1] 33.229107  1.687779 19.848315 37.666799 25.855715 13.137317 44.639349
    ##  [8] 15.813230 37.851703 40.422473

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

    m <- make_units(m)
    a * t / m
    ## Units: [1]
    ##  [1] 33.229107  1.687779 19.848315 37.666799 25.855715 13.137317 44.639349
    ##  [8] 15.813230 37.851703 40.422473

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.102000 0.000264 0.036500 0.131000 0.061900 0.016000 0.185000 0.023200
    ##  [9] 0.133000 0.151000
    cumsum(a)
    ## Units: [m/s]
    ##  [1] 0.5538184 0.5819481 0.9127533 1.5405333 1.9714619 2.1904172 2.9344063
    ##  [8] 3.1979602 3.8288219 4.5025298
    log(a) # base defaults to exp(1)
    ## Units: [ln(re 1 m.s-1)]
    ##  [1] -0.5909184 -3.5709312 -1.1062254 -0.4655655 -0.8418129 -1.5188877
    ##  [7] -0.2957288 -1.3334976 -0.4606686 -0.3949587
    log(a, base = 10)
    ## Units: [lg(re 1 m.s-1)]
    ##  [1] -0.2566326 -1.5508357 -0.4804276 -0.2021925 -0.3655947 -0.6596446
    ##  [7] -0.1284334 -0.5791307 -0.2000658 -0.1715284
    log(a, base = 2)
    ## Units: [lb(re 1 m.s-1)]
    ##  [1] -0.8525150 -5.1517648 -1.5959460 -0.6716691 -1.2144793 -2.1912918
    ##  [7] -0.4266465 -1.9238304 -0.6646043 -0.5698049

### Summary functions

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

    sum(a)
    ## 4.50253 [m/s]
    min(a)
    ## 0.02812965 [m/s]
    max(a)
    ## 0.7439891 [m/s]
    range(a)
    ## Units: [m/s]
    ## [1] 0.02812965 0.74398914

### Printing

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

    a
    ## Units: [m/s]
    ##  [1] 0.55381844 0.02812965 0.33080525 0.62777998 0.43092858 0.21895528
    ##  [7] 0.74398914 0.26355383 0.63086172 0.67370789
    a[1]
    ## 0.5538184 [m/s]

### Subsetting

The usual subsetting rules work:

    a[2:5]
    ## Units: [m/s]
    ## [1] 0.02812965 0.33080525 0.62777998 0.43092858
    a[-(1:9)]
    ## 0.6737079 [m/s]

### Concatenation

    c(a,a)
    ## Units: [m/s]
    ##  [1] 0.55381844 0.02812965 0.33080525 0.62777998 0.43092858 0.21895528
    ##  [7] 0.74398914 0.26355383 0.63086172 0.67370789 0.55381844 0.02812965
    ## [13] 0.33080525 0.62777998 0.43092858 0.21895528 0.74398914 0.26355383
    ## [19] 0.63086172 0.67370789

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.55381844 0.02812965 0.33080525 0.62777998 0.43092858 0.21895528
    ##  [7] 0.74398914 0.26355383 0.63086172 0.67370789 0.55381844 0.02812965
    ## [13] 0.33080525 0.62777998 0.43092858 0.21895528 0.74398914 0.26355383
    ## [19] 0.63086172 0.67370789
    c(b,a) # km/h, m/s -> km/h
    ## Units: [km/h]
    ##  [1] 1.9937464 0.1012667 1.1908989 2.2600079 1.5513429 0.7882390 2.6783609
    ##  [8] 0.9487938 2.2711022 2.4253484 1.9937464 0.1012667 1.1908989 2.2600079
    ## [15] 1.5513429 0.7882390 2.6783609 0.9487938 2.2711022 2.4253484

## 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)
    ## Units: [m^2/s^2]
    ##      [,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)