{units} of uncleaned herring

Hex sticker design with a picture of a herring and the word 'cran'.

The hex sticker is better than the package.

tl;dr

I made the tiny R package {cran} to convert volumes to an antiquated measurement of fish. Why? To test out the {units} package and to resolve a joke about the Comprehensive R Archive Network (CRAN).

{units}

The {units} package by Edzer Pebesma, Thomas Mailund and James Hiebert (site, source, R Journal) helps you set and create units, convert between them and raise an error where that isn’t possible.

I’ve used the package to solve a trivial unit conversion question and to create my own units. This post shows how.

A 12 gallon hat?

Here’s a really simple example of the {units} package in action.

A colleague bought a 1 gallon water bottle, only to realise later that it was US gallons rather than UK gallons (viva litres!). What’s the relationship between the two gallon units?

First install and attach the {units} package, which is available on CRAN. It will print the location where the units dataset is stored. These units are derived from the comprehensive UNIDATA udunits database, which has all the relevant SI units and some that are a little more… nonstandard.

# install.packages("units")  # install if you haven't already
library(units)
## udunits system database from /Users/matt.dray/Library/R/3.6/library/units/share/udunits

I’ll also load a few other packages for the purposes of this post.

library(dplyr)
library(stringr)
library(purrr)

You can inspect the valid_units() dataframe to find out what units you can work with. Here’s five random units from the dataframe:

valid_udunits() %>% 
  filter(symbol != "" & name_singular != "") %>% 
  sample_n(5) %>% 
  select(symbol, name_singular, definition)
## # A tibble: 5 x 3
##   symbol name_singular  definition                                              
##   <chr>  <chr>          <chr>                                                   
## 1 °C     degree_Celsius unit (and scale) of temperature, with same magnitude as…
## 2 Gy     gray           unit of ionizing radiation, a measure of the absorbed d…
## 3 cal    IT_calorie     unit of heat energy used in thermochemistry, the Intern…
## 4 Å      angstrom       unit of length equal to one hundred-millionth of a cent…
## 5 h      hour           period of time equal to 60 minutes

We can filter the name_singular column to find the available gallon units.

dplyr::filter(
  valid_udunits(),
  str_detect(name_singular, "gallon")
) %>% 
  select(name_singular)
## # A tibble: 4 x 1
##   name_singular         
##   <chr>                 
## 1 Canadian_liquid_gallon
## 2 US_dry_gallon         
## 3 US_liquid_gallon      
## 4 UK_liquid_gallon

We’re interested in UK_liquid_gallon and US_liquid_gallon, but wow, there’s two more, including a ‘dry’ one.

We can supply a unit to a value with as_units(), so we can create 1 UK gallon with the following:

uk_gal <- as_units(1, "UK_liquid_gallon")

That gives us an object with class units and the print method adds the unit in square brackets:

class(uk_gal)
## [1] "units"
uk_gal
## 1 [UK_liquid_gallon]

We can also do maths with these objects:

uk_gal + uk_gal
## 2 [UK_liquid_gallon]

And to convert it, we can take set the units of our unit-class object and specify a different unit. The units need to be compatible though, so you can’t convert a gallon to a parsec, for example.

# Using purrr::safely() to capture the error
safe_set_units <- safely(set_units)
safe_set_units(uk_gal, "parsec")$error
## <simpleError: cannot convert UK_liquid_gallon into parsec>

This prevents you from combining non-compatible units, which is a real danger if your data is stored as bare numeric values with no unit information.

And now we’ll set the new units for the gallon-to-gallon conversion.

us_gal <- set_units(uk_gal, "US_liquid_gallon")
us_gal
## 1.20095 [US_liquid_gallon]

So a US liquid gallon is about 20% larger than a UK one. Because of course.

Herring aid

Who doesn’t like getting lost in the Wikipedia rabbithole? I came upon the page for ‘cran’ and found it amusing that the Comprehensive R Archive Network (CRAN) package database had a rival.

What’s a cran, then? Well, an antiquated legal unit for measuring the volume of landed, uncleaned herring in the North Sea fishing industry. Also used as the name for a basket that could carry that volume.1

It sounds like the initial 18th-century measurement was volumetric and inexact, equalling something like 1200 fish. Later this was made official in terms of ‘wine gallons’, with Wikipedia pegging it to 170.5 litres in more modern units.

Naturally, I checked valid_udunits() and cran isn’t in there. So obviously I needed to make it.

You can basically do this in three steps with {units}: define a new unit based on known units; create a unit object; convert it to the newly-defined unit.

So, you can ‘install’ a new unit with reference to another unit by multiplying or offsetting by some constant. In our case, our new unit is equal to 170.5 litres.

install_conversion_constant("cran", "L", 170.5)

Now we can work with the cran unit. Let’s first create a unit-class object to convert. For example, we can confirm that 170.5 litres is equal to one cran.

one_litre <- as_units(170.5, "L")
one_litre
## 170.5 [L]

We can supply this to the set_units() function and specify we want it converted to cran.

set_units(one_litre, "cran")
## 1 [cran]

{cran}

So I created a package called {cran} that contains this conversion. You can install it from GitHub using the {remotes} package. Except, you know, don’t, because you have no need for it unless you’re an 18th century fisherman.

remotes::install_github("matt-dray/cran")

And then when you load the package it asks if you want to create the cran unit. Answering ‘yes’ results in the cran unit being available in your session.

library(cran)
## Create the 'cran' unit of measurement for this session? yes/no: yes
## You're ready to measure uncleaned herring.

Now you can use cran for setting and converting units. So we can revisit our check that 170.5 litres equals 1 cran:

cran::cran_convert(170.5, "L")
## 1 [cran]

…And that’s it, basically. You can remove and reinstall the unit at any point with cran_remove() and cran_install().

cran::cran_remove()
## Remove the 'cran' unit of measurement for this session? yes/no: yes
## You're done measuring uncleaned herring.

Session info

## ─ Session info ───────────────────────────────────────────────────────────────
##  setting  value                       
##  version  R version 3.6.1 (2019-07-05)
##  os       macOS Sierra 10.12.6        
##  system   x86_64, darwin15.6.0        
##  ui       X11                         
##  language (EN)                        
##  collate  en_GB.UTF-8                 
##  ctype    en_GB.UTF-8                 
##  tz       Europe/London               
##  date     2020-09-12                  
## 
## ─ Packages ───────────────────────────────────────────────────────────────────
##  package     * version    date       lib source                            
##  assertthat    0.2.1      2019-03-21 [1] CRAN (R 3.6.0)                    
##  blogdown      0.18       2020-03-04 [1] CRAN (R 3.6.0)                    
##  bookdown      0.20       2020-06-23 [1] CRAN (R 3.6.2)                    
##  cli           2.0.2      2020-02-28 [1] CRAN (R 3.6.0)                    
##  cran          0.0.0.9000 2020-09-12 [1] Github (matt-dray/cran@b061efe)   
##  crayon        1.3.4      2017-09-16 [1] CRAN (R 3.6.0)                    
##  digest        0.6.25     2020-02-23 [1] CRAN (R 3.6.0)                    
##  dplyr       * 1.0.2      2020-08-18 [1] CRAN (R 3.6.2)                    
##  ellipsis      0.3.1      2020-05-15 [1] CRAN (R 3.6.2)                    
##  evaluate      0.14       2019-05-28 [1] CRAN (R 3.6.0)                    
##  fansi         0.4.1      2020-01-08 [1] CRAN (R 3.6.0)                    
##  generics      0.0.2      2018-11-29 [1] CRAN (R 3.6.0)                    
##  glue          1.4.2      2020-08-27 [1] CRAN (R 3.6.1)                    
##  htmltools     0.5.0.9000 2020-08-27 [1] Github (rstudio/htmltools@e35c3fa)
##  knitr         1.29       2020-06-23 [1] CRAN (R 3.6.2)                    
##  lifecycle     0.2.0      2020-03-06 [1] CRAN (R 3.6.0)                    
##  magrittr      1.5        2014-11-22 [1] CRAN (R 3.6.0)                    
##  pillar        1.4.6      2020-07-10 [1] CRAN (R 3.6.2)                    
##  pkgconfig     2.0.3      2019-09-22 [1] CRAN (R 3.6.0)                    
##  purrr       * 0.3.4      2020-04-17 [1] CRAN (R 3.6.2)                    
##  R6            2.4.1      2019-11-12 [1] CRAN (R 3.6.0)                    
##  Rcpp          1.0.5      2020-07-06 [1] CRAN (R 3.6.2)                    
##  rlang         0.4.7      2020-07-09 [1] CRAN (R 3.6.1)                    
##  rmarkdown     2.3        2020-06-18 [1] CRAN (R 3.6.2)                    
##  sessioninfo   1.1.1      2018-11-05 [1] CRAN (R 3.6.0)                    
##  stringi       1.4.6      2020-02-17 [1] CRAN (R 3.6.1)                    
##  stringr     * 1.4.0      2019-02-10 [1] CRAN (R 3.6.0)                    
##  tibble        3.0.3      2020-07-10 [1] CRAN (R 3.6.2)                    
##  tidyselect    1.1.0      2020-05-11 [1] CRAN (R 3.6.2)                    
##  units       * 0.6-7      2020-06-13 [1] CRAN (R 3.6.2)                    
##  utf8          1.1.4      2018-05-24 [1] CRAN (R 3.6.0)                    
##  vctrs         0.3.4      2020-08-29 [1] CRAN (R 3.6.2)                    
##  withr         2.2.0      2020-04-20 [1] CRAN (R 3.6.2)                    
##  xfun          0.16       2020-07-24 [1] CRAN (R 3.6.2)                    
##  xml2          1.3.2      2020-04-23 [1] CRAN (R 3.6.2)                    
##  yaml          2.2.1      2020-02-01 [1] CRAN (R 3.6.0)                    
## 
## [1] /Users/matt.dray/Library/R/3.6/library
## [2] /Library/Frameworks/R.framework/Versions/3.6/Resources/library

  1. You can still buy one today.↩︎