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 set and modify the
unit of time difference.
This idea can be generalized to other physical units. The
units
package, presented here, does this, and builds upon the
udunits2
R package, which in turn is build upon the
udunits2 C library.
The udunits2
library provides the following operations:
m/s
is a valid physical unitm/s
and km/h
are convertableThis R package, called units
, uses R package udunits2 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.
Numeric data with explicit physical units can be specified by as.units
:
library(units)
(a = as.units(1:10, "m/s"))
## Units: m/s
## [1] 1 2 3 4 5 6 7 8 9 10
and converted by setting a new physical unit:
b = a
units(b) = "km/h"
b
## Units: km/h
## [1] 3.6 7.2 10.8 14.4 18.0 21.6 25.2 28.8 32.4 36.0
Impossible conversions lead to an error:
x = try(units(a) <- "secs")
x
## [1] "Error in `units<-.units`(`*tmp*`, value = \"secs\") : \n cannot convert m/s into secs\n"
## attr(,"class")
## [1] "try-error"
## attr(,"condition")
## <simpleError in `units<-.units`(`*tmp*`, value = "secs"): cannot convert m/s into secs>
Arithmetic operations verify units, and create new ones
a + a
## Units: m/s
## [1] 2 4 6 8 10 12 14 16 18 20
a * a
## Units: (m/s)*(m/s)
## [1] 1 4 9 16 25 36 49 64 81 100
a ^ 2.5
## Units: (m/s)^2.5
## [1] 1.000000 5.656854 15.588457 32.000000 55.901699 88.181631
## [7] 129.641814 181.019336 243.000000 316.227766
and convert if necessary:
a + b # m/s + km/h -> m/s
## Units: m/s
## [1] 2 4 6 8 10 12 14 16 18 20
but units are not simplified:
t = as.units(1, "s")
a * t
## Units: (m/s)*(s)
## [1] 1 2 3 4 5 6 7 8 9 10
Allowed operations that require convertable units are +
, -
, ==
,
!=
, <
, >
, <=
, >=
. Operations that lead to new units are
*
, /
, and the power operations **
and ^
.
Mathematical operations allowed are: abs
, sign
, floor
,
ceiling
, trunc
, round
, signif
, cumsum
, cummax
, cummin
.
signif(a^2.5, 3)
## Units: (m/s)^2.5
## [1] 1.00 5.66 15.60 32.00 55.90 88.20 130.00 181.00 243.00 316.00
cumsum(a)
## [1] 1 3 6 10 15 21 28 36 45 55
(for reasons beyond my comprehension, here cumsum
strips units,
but signif
does not)
Summary functions sum
, min
, max
, and range
are allowed:
sum(a)
## 55 m/s
min(a)
## 1 m/s
max(a)
## 10 m/s
range(a)
## Units: m/s
## [1] 1 10
min(as.units(1, "m/s"), as.units(1, "km/h")) # converts to first unit:
## 0.2777778 m/s
Following difftime
, printing behaves differently for length-one vectors:
a
## Units: m/s
## [1] 1 2 3 4 5 6 7 8 9 10
a[1]
## 1 m/s
The usual subsetting rules work:
a[2:5]
## Units: m/s
## [1] 2 3 4 5
a[-(1:9)]
## 10 m/s
c(a,a)
## Units: m/s
## [1] 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
concatenation converts to the units of the first argument, if necessary:
c(a,b) # m/s, km/h -> m/s
## Units: m/s
## [1] 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
c(b,a) # km/h, m/s -> km/h
## Units: km/h
## [1] 3.6 7.2 10.8 14.4 18.0 21.6 25.2 28.8 32.4 36.0 3.6 7.2 10.8 14.4
## [15] 18.0 21.6 25.2 28.8 32.4 36.0
difftime
From difftime
to units
:
t1 = Sys.time()
t2 = t1 + 3600
d = t2 - t1
as.units(d)
## 1 h
(du = as.units(d, "d"))
## 0.04166667 d
vice versa:
dt = as.dt(du)
class(dt)
## [1] "difftime"
dt
## Time difference of 0.04166667 days
matrix
objectsas.units(matrix(1:4,2,2), "m/s")
## Units: m/s
## [,1] [,2]
## [1,] 1 3
## [2,] 2 4
as.units(matrix(1:4,2,2), "m/s") * as.units(4, "m/s")
## Units: (m/s)*(m/s)
## [,1] [,2]
## [1,] 4 12
## [2,] 8 16
but
as.units(matrix(1:4,2,2), "m/s") %*% as.units(4:3, "m/s")
## [,1]
## [1,] 13
## [2,] 20
strips units.
data.frame
sset.seed(131)
d = data.frame(x = runif(4), y = as.units(runif(4), "s"), z = as.units(1:4, "m/s"))
d
## x y z
## 1 0.2064370 0.8463468 1
## 2 0.1249422 0.5292048 2
## 3 0.2932732 0.5186254 3
## 4 0.3757797 0.2378545 4
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 1 0.8463468
## 2 0.1249422 0.5292048 2 1.0584095
## 3 0.2932732 0.5186254 3 1.5558761
## 4 0.3757797 0.2378545 4 0.9514180
d[1, "yz"]
## 0.8463468 (s)*(m/s)