debkeepr: An R package for the analysis of non-decimal currencies

debkeepr is now on CRAN

rstats
debkeepr
debkeepr is an R package for the analysis of historical non-decimal currencies and double-entry bookkeeping. Version 0.1.1 is debkeepr’s first release on CRAN.
Published

22 March 2023

debkeepr is now—finally—on CRAN with version 0.1.1! debkeepr integrates non-decimal currencies that use tripartite or tetrapartite systems into the methodologies of Digital Humanities and the practices of reproducible research. The package makes it possible for historical non-decimal currencies, such as the tripartite system of pounds, shillings, and pence (£465 12s. 8d.), to behave like decimalized values through the implementation of the deb_lsd, deb_tetra, and deb_decimal vector types. These types are based on the infrastructure provided by the vctrs package. The name of the package derives from its ability to analyze historical account books that used the system of Double-entry bookkeeping.

You can now install the released version of debkeepr from CRAN or the development version on GitHub:

# Install CRAN release
install.packages("debkeepr")

# Install development version
# install.packages("pak")
pak::pak("jessesadler/debkeepr")

Please open an issue if you have any questions, comments, or requests.

This release has been a long time coming. I first started to develop debkeepr in 2018 to deal with the problem of performing mathematical operations on non-decimal currencies of pounds, shillings, and pence that was crucial to my research on merchant families in the 16th and 17th centuries. I introduced the package with this blog post in September 2018. After talking to Hadley Wickham at the Tidyverse developer day at RStudio::conf 2019, I completely rewrote the package, using the vctrs package to develop fully integrated S3 vector types that could represent non-decimal currencies. I discussed this process at RStudio::conf 2020. Since then, I have been developing the package on and off, while also using it for my historical research.1 But I never quite got around to putting the package on CRAN. However, progress was made. I kept up with changes to vctrs and dplyr and added a new class for tetrapartite values: pounds, shillings, pence, and farthings. The forthcoming second edition of the R Packages book by Hadley Wickham and Jenny Bryan and the recently updated chapter on Releasing to CRAN helped motivate me to finally prep and submit the package for CRAN release. On 22 March 2023 version 0.1.1 of debkeepr was accepted on CRAN after one quite minor round of changes.

Usage

The debkeepr package introduces three new vector types—deb_lsd(), deb_tetra(), and deb_decimal()—to deal with two interrelated problems inherent in historical non-decimal currencies and other such value systems.

  1. Historical currencies consist of three or four separate non-decimal units.
  2. The bases of the shillings, pence, and optionally farthing units differed by region, coinage, and era.

The deb_lsd type maintains the tripartite structure of non-decimal currencies and provides a bases attribute to record the bases for the shillings and pence units. The deb_tetra type extends the concept of the deb_lsd type to incorporate currencies and other types of values that consist of four units. The deb_decimal type provides a means to decimalize both deb_lsd and deb_tetra types, while keeping track of the two or three non-decimal bases and the unit represented as attributes.2

library(debkeepr)

# Create deb_lsd, deb_tetra, and deb_decimal vectors
# with default bases of 20s. 12d.

# deb_lsd vector: £8 13s. 4d.
deb_lsd(l = 8, s = 13, d = 4)
#> <deb_lsd[1]>
#> [1] 8:13s:4d
#> # Bases: 20s 12d

# deb_tetra vector: £8 13s. 4d. 3f.
deb_tetra(l = 8, s = 13, d = 4, f = 3)
#> <deb_tetra[1]>
#> [1] 8:13s:4d:3f
#> # Bases: 20s 12d 4f

# deb_decimal: £8 13s. 4d. in decimalized shillings
deb_decimal(173.33, unit = "s")
#> <deb_decimal[1]>
#> [1] 173.33
#> # Unit: solidus
#> # Bases: 20s 12d

# Cast £8 13s. 4d. to a decimal form
deb_as_decimal(deb_lsd(8, 13, 4))
#> <deb_decimal[1]>
#> [1] 8.666667
#> # Unit: libra
#> # Bases: 20s 12d

Resources

debkeepr provides three vignettes and example data to help get started using the package.

The rest of this post will provide a quick overview of how debkeepr can assist historians in research involving non-decimal currencies and bring the study of historical account books into the practices of reproducible research.

Arithmetic with non-decimal currencies

Arithmetic with non-decimal currencies is cumbersome and error-prone, at least if you were not educated within non-decimal systems. The primary issue is the need to do compound unit arithmetic to normalize units according to their bases.

Figure 1: A scratchpad from the archive of Daniel van der Meulen

Figure 2: Compound unit arithmetic

debkeepr types provide a way to access arithmetic operations and mathematical functions for non-decimal values, drastically simplifying these calculations. For instance, we can add together the values in Figure 2 with either + or sum().

# Add multiple deb_lsd vectors
deb_lsd(28, 15, 8) + deb_lsd(32, 8, 11) + deb_lsd(54, 18, 7) + deb_lsd(18, 12, 9)
#> <deb_lsd[1]>
#> [1] 134:15s:11d
#> # Bases: 20s 12d

# Sum of one deb_lsd vector
sum(
  deb_lsd(l = c(28, 32, 54, 18),
          s = c(15, 8, 18, 12),
          d = c(8, 11, 7, 9))
)
#> <deb_lsd[1]>
#> [1] 134:15s:11d
#> # Bases: 20s 12d

Addition and subtraction might be cumbersome with non-decimal values, but multiplication and division get tricky quite quickly. We can see this using examples from the article on Arithmetic in the third edition of the Encyclopedia Britannica, printed in 1797 and comparing these to the process with debkeepr types.

The Encyclopedia Britannica outlines the process for multiplying £15 3s. 8d. sterling by 32 and £17 3s. 8d. sterling by 75. It breaks down larger multiplication operations into smaller ones. Multiplying £15 3s. 8d. sterling by 32 becomes a matter of sequentially multiplying the monetary value by 8 and then by 4. Multiplying £17 3s. 8d. sterling by 75 is presented as a three step process of multiplying by 3, then by 5, and then 5 again as 3 * 5 * 5 = 75. With debkeepr you can use multiplication as you would any other numeric vector.

# Multiply £15 3s. 8d. sterling by 32
deb_lsd(15, 3, 8) * 32
#> <deb_lsd[1]>
#> [1] 485:17s:4d
#> # Bases: 20s 12d

# Multiply £17 3s. 8d. sterling by 75
deb_lsd(17, 3, 8) * 75
#> <deb_lsd[1]>
#> [1] 1288:15s:0d
#> # Bases: 20s 12d

Figure 3: Multiplication in Encyclopedia Britannica

The process for division presented in the Encyclopedia Britannica might bring you back to grade school and long division problems, though the operation also includes multiplication due to the need to convert between units. The Encyclopedia shows the division of £465 12s. 8d. sterling by 72 and 345 hundredweight 1 quarter 8 lbs by 22. This latter example provides an opportunity to demonstrate how you can use the bases argument in debkeepr-type vectors to adapt them to other value systems besides the default of 20 shillings to the pound and 12 pence to the shilling. In this case, there are 4 quarters in a hundredweight and 28 pounds in a quarter. This example also gives proof of the gains in accuracy, as the answer is shown as 15 cwt. 2 q. 21 lb., but it should actually be 22 lb.3 Note too that the output of debkeepr types shows the bases of the vectors: # Bases: 4s 28d.

# Divide £465 12s. 8d. sterling by 72
deb_lsd(465, 12, 8) / 72
#> <deb_lsd[1]>
#> [1] 6:9s:4.111d
#> # Bases: 20s 12d

# Divide 345 hundredweight 1 quarter 8 lbs by 22
deb_lsd(345, 1, 8, bases = c(4, 28)) / 22
#> <deb_lsd[1]>
#> [1] 15:2s:22d
#> # Bases: 4s 28d

Figure 4: Division in Encyclopedia Britannica

The example of dividing a measurement of weight also provides an opportunity to use the deb_tetra type that allows for four units, here hundredweight, quarters, stones, and pounds. We can, thus, rewrite the value to be divided as 345 cwt. 1 q. 0 st. 8 lb.

# Divide 345 cwt. 1 q. 0 st. 8 lb. by 22
deb_tetra(345, 1, 0, 8, bases = c(4, 2, 14)) / 22
#> <deb_tetra[1]>
#> [1] 15:2s:1d:8f
#> # Bases: 4s 2d 14f

For many more examples, including how to work with vectors of different types and different bases see the Getting Started with debkeepr vignette and the Transactions in Richard Dafforne’s Journal vignette.

debkeepr and double-entry bookkeeping

Figure 5: Account book of Jacques della Faille, 1585

The ability to treat non-decimal values of three (deb_lsd) or four (deb_tetra) units as numeric vectors greatly simplifies analysis of these types of values. However, it is the integration of debkeepr types in data frame columns—particularly in the form of what debkeepr refers to as transaction data frames—that makes it possible to bring the analysis of historical account books recorded in various non-decimal currencies into the practices of Digital Humanities and reproducible research. debkeepr provides a methodology to enter data from historical account books and create deb_lsd or deb_tetra columns with deb_gather_lsd() and deb_gather_tetra() respectively. These data frames can then be analyzed and visualized using methods such as those provided by the tidyverse, particularly with dplyr and ggplot2.

set.seed(240)
# Raw data of a transaction data frame
# Input each unit of non-decimal currency as separate column
raw_data <- data.frame(
  id = 1:5,
  credit = c(1, 3, 2, 2, 3),
  debit  = c(3, 2, 1, 3, 1),
  l = sample(20:100, 5),
  s = sample(1:19, 5),
  d = sample(1:11, 5)
)
raw_data
#>   id credit debit  l  s  d
#> 1  1      1     3 24  6 10
#> 2  2      3     2 34 12  5
#> 3  3      2     1 83  2  4
#> 4  4      2     3 41  5  9
#> 5  5      3     1 92 11 11

# Create deb_lsd column
deb_gather_lsd(raw_data,
               l = l, s = s, d = d,
               lsd_col = lsd, replace = TRUE)
#>   id credit debit        lsd
#> 1  1      1     3  24:6s:10d
#> 2  2      3     2  34:12s:5d
#> 3  3      2     1   83:2s:4d
#> 4  4      2     3   41:5s:9d
#> 5  5      3     1 92:11s:11d

debkeepr includes two data frames of example data. dafforne_transactions is a transactions data frame that has columns for transaction id, credit account, debit account, date of the transaction, and the value of the transaction as a deb_lsd column with bases 20s. and 12d., along with some addition information. This is paired with a data frame containing information about the 46 accounts in Dafforne’s practice ledger. See ?dafforne_transactions and ?dafforne_accounts for more details. For a fuller discussion of using these data frames, see the Analysis of Richard Dafforne’s Journal and Ledger vignette.

# Load tidyverse packages
library(dplyr)
library(ggplot2)

# Transactions data frame
dafforne_transactions
#> # A tibble: 177 × 8
#>       id credit debit date                  lsd journal ledger description      
#>    <dbl>  <dbl> <dbl> <date>     <lsd[20s:12d]>   <dbl> <chr>  <chr>            
#>  1     1      2     1 1633-01-01    1000:15s:7d       1 1/1    Various coins of…
#>  2     2      2     3 1633-01-01     477:10s:0d       1 1/1    60 Leeds dozens …
#>  3     3      2     4 1633-01-01       55:0s:6d       1 2/1    5 barrels that r…
#>  4     4      2     5 1633-01-01      240:0s:0d       1 2/1    For 800 French c…
#>  5     5      2     6 1633-01-01      229:0s:0d       1 2/1    2290 guilders re…
#>  6     6      2     8 1633-01-01       3:17s:8d       1 3/1    Expenses for the…
#>  7     7      7     2 1633-01-01      150:0s:0d       1 1/2    Ready money from…
#>  8     8      9    11 1633-01-04      360:0s:0d       1 4/3    100 Leeds dozens…
#>  9     9      1     9 1633-01-04      144:0s:0d       2 3/1    For 2/5 of the 1…
#> 10    10      5    10 1633-01-04      120:0s:0d       2 3/2    400 French crown…
#> # … with 167 more rows

# Accounts data frame
dafforne_accounts
#> # A tibble: 46 × 5
#>       id account                                    ledger investor      descr…¹
#>    <int> <chr>                                       <int> <chr>         <chr>  
#>  1     1 Cash                                            1 Ego           Accoun…
#>  2     2 Stock                                           1 Ego           Accoun…
#>  3     3 Wares                                           1 Ego           Accoun…
#>  4     4 Kettles                                         2 Ego           Accoun…
#>  5     5 Jean du Boys - account current                  2 Jean du Boys  Factor…
#>  6     6 Jacob Symonson - account by him in company      2 Jacob Symons… Aggreg…
#>  7     7 Jacob Symonson - account current                2 Jacob Symons… Curren…
#>  8     8 Jacob Symonson - Cochineal                      3 Jacob Symons… Sale o…
#>  9     9 George Pinchback                                3 George Pinch… Accoun…
#> 10    10 Jacob Symonson - ready money                    3 Jacob Symons… Accoun…
#> # … with 36 more rows, and abbreviated variable name ¹​description

debkeepr provides a set of functions that work with data frames with the same structure as dafforne_transactions. deb_account_summary() is useful for obtaining an overview of the account book at its closing, providing a summary of the total credits, debits, and current value of each account.

dafforne_transactions |> 
  deb_account_summary(credit = credit, debit = debit, lsd = lsd)
#> # A tibble: 46 × 4
#>    account_id         credit          debit        current
#>         <dbl> <lsd[20s:12d]> <lsd[20s:12d]> <lsd[20s:12d]>
#>  1          1   1956:10s:11d    2903:13s:0d   -947:-2s:-1d
#>  2          2     2006:3s:9d      150:0s:0d     1856:3s:9d
#>  3          3      570:0s:0d      570:0s:0d        0:0s:0d
#>  4          4       75:0s:8d       75:0s:8d        0:0s:0d
#>  5          5      813:3s:0d      813:3s:0d        0:0s:0d
#>  6          6     568:1s:11d      869:2s:7d    -301:0s:-8d
#>  7          7   2958:18s:10d   2958:18s:10d        0:0s:0d
#>  8          8    1580:10s:0d    1580:10s:0d        0:0s:0d
#>  9          9     1744:1s:4d     1744:1s:4d        0:0s:0d
#> 10         10      606:2s:6d      606:2s:6d        0:0s:0d
#> # … with 36 more rows

We can present this information in a line-range plot in which the upper range represents the total credit, the lower range the total debit, and a point for the current value. This also allows an opportunity to point out some limitations of the deb_lsd and deb_tetra vector types; ggplot2 currently does not know how to pick a scale for deb_lsd and deb_tetra columns. However, deb_decimal columns work as expected. It is therefore necessary to cast the deb_lsd or deb_tetra columns to deb_decimal with deb_as_decimal(), either before or within the ggplot() call.4 For a line-range plot we also need to turn the debit column into negative values.

# Plot summary of accounts
dafforne_transactions |> 
  mutate(lsd = deb_as_decimal(lsd)) |> 
  deb_account_summary() |> # Columns have default names of credit, debit, and lsd
  mutate(debit = -debit) |>
  ggplot() + 
    geom_linerange(aes(x = account_id, ymin = debit, ymax = credit)) + 
    geom_point(aes(x = account_id, y = current,
                     color = if_else(current == 0, "Closed", "Open"))) +
    scale_color_manual(values = c(Open = "black", Closed = "blue")) + 
    scale_y_continuous(labels = scales::label_dollar(prefix = "\u00a3")) + 
    labs(x = "Accounts",
         y = "Pounds sterling",
         color = "Status",
         title = "Summary of the accounts") + 
    theme_light()

debkeepr and reproducibility

Figure 6: Scratchpad: Doing math by hand

The above plot only scratches the surface of the possibilities for exploring and analyzing account books that use historical non-decimal currencies with debkeepr. The Transactions in Richard Dafforne’s Journal vignette provides additional examples. Yet, what I think is the most significant about debkeepr is not any one plot, time saving feature, or type of analysis, but rather the possibility of using the practices of reproducible research in the context of historical economic data. Individual calculations are easier to track and validate when written in code rather than scrawled out on a scratch piece of paper or entered into a calculator. Even more importantly, debkeepr provides a workflow for entering data from account books into a data base, tidying the data, and then exploring, analyzing, and visualizing the data in ways that integrate with the tidyverse and more general practices of data analysis in R.

I have seen this in my own work on the accounts of the estate of Jan della Faille de Oude. Without debkeepr the process of transcribing the over 2,100 transactions and analyzing the 480 accounts appeared to have limited use for quite a bit of work. Instead, I picked a handful of crucial accounts and worked by hand. With debkeepr the effort becomes worthwhile and whole plethora of research questions and ways to communicate were opened up. It is my hope that debkeepr can help bring to light crucial and interesting social interactions that are buried in economic manuscripts, making these stories accessible to a wider audience.

Footnotes

  1. For instance, in my talk, “Rough Estimation: Inheritance, Accounting, and Sibling Rivalry in an Early Modern Merchant Family.” See the video and/or slides.↩︎

  2. debkeepr uses the nomenclature of l, s, and d to represent pounds, shillings, and pence units in non-decimal currencies. The abbreviations derive from the Latin terms libra, solidus, and denarius. It uses the default of 20 shillings to the pound and 12 pence to the shilling that became ingrained in much of Europe during the Carolingian Empire in the 9th century. However, a variety of other bases also developed over time, and so these bases can be modified by the user using the bases argument.↩︎

  3. Checking the long division at the bottom of the calculation, 22 goes into 44 twice not once.↩︎

  4. The only context in which casting to deb_decimal results in any loss of information is in labeling the plotted values. However, this can be rectified through deb_text(), which provides a flexible way to format debkeepr types as text.↩︎