Travis-CI Build Status

AppVeyor Build Status

Coverage Status

RStudio CRAN mirror downloads

Project Status: Inactive – The project has reached a stable, usable state but is no longer being actively developed: support/maintenance will be provided as time allows.

ledger is an R package to import data from plain text accounting software like Ledger, HLedger, and Beancount into an R data frame for convenient analysis, plotting, and export.

Right now it supports reading in the register from ledger, hledger, and beancount files.

::: {.contents} :::

Installation

To install the last version released to CRAN use the following command in R:

install.packages("ledger")

To install the development version of the ledger package (and its R package dependencies) use the install_github function from the remotes package in R:

install.packages("remotes")
remotes::install_github("trevorld/r-ledger")

This package also has some system dependencies that need to be installed depending on which plaintext accounting files you wish to read to be able to read in:

ledger

: ledger (>= 3.1)

hledger

: hledger (>= 1.4)

beancount

: beancount (>= 2.0)

To install hledger run the following in your shell:

stack update && stack install --resolver=lts-14.3 hledger-lib-1.15.2 hledger-1.15.2 hledger-web-1.15 hledger-ui-1.15 --verbosity=error 

To install beancount run the following in your shell:

pip3 install beancount

Several pre-compiled Ledger binaries are available (often found in several open source repos).

To run the unit tests you'll also need the suggested R package testthat.

Examples

API

The main function of this package is register which reads in the register of a plaintext accounting file. This package also exports S3 methods so one can use rio::import to read in a register, a net_worth convenience function, and a prune_coa convenience function.

register

Here are some examples of very basic files stored within the package:

library("ledger")
options(width=180)
ledger_file <- system.file("extdata", "example.ledger", package = "ledger") 
register(ledger_file)
hledger_file <- system.file("extdata", "example.hledger", package = "ledger") 
register(hledger_file)
beancount_file <- system.file("extdata", "example.beancount", package = "ledger") 
register(beancount_file)

Here is an example reading in a beancount file generated by bean-example:

bean_example_file <- tempfile(fileext = ".beancount")
system(paste("bean-example -o", bean_example_file), ignore.stderr=TRUE)
df <- register(bean_example_file)
options(width=240)
print(df)
suppressPackageStartupMessages(library("dplyr"))
dplyr::filter(df, grepl("Expenses", account), grepl("trip", tags)) %>% 
    group_by(trip = tags, account) %>% 
    summarise(trip_total = sum(amount))

Using rio::import and rio::convert

If one has loaded in the ledger package one can also use rio::import to read in the register:

df <- rio::import(beancount_file)
all.equal(register(ledger_file), rio::import(ledger_file))

The main advantage of this is that it allows one to use rio::convert to easily convert plaintext accounting files to several other file formats such as a csv file. Here is a shell example:

bean-example -o example.beancount
Rscript --default-packages=ledger,rio -e 'convert("example.beancount", "example.csv")'

prune_coa

Some examples using the prune_coa function to simplify the "Chart of Account" names to a given maximum depth:

suppressPackageStartupMessages(library("dplyr"))
df <- register(bean_example_file) %>% dplyr::filter(!is.na(commodity))
df %>% prune_coa() %>% 
    group_by(account, mv_commodity) %>% 
    summarize(market_value = sum(market_value))
df %>% prune_coa(2) %>% 
    group_by(account, mv_commodity) %>%
    summarize(market_value = sum(market_value))

Basic personal accounting reports

Here is some examples using the functions in the package to help generate various personal accounting reports of the beancount example generated by bean-example.

First we load the (mainly tidyverse) libraries we'll be using and adjusting terminal output:

options(width=240) # tibble output looks better in wide terminal output
library("ledger")
library("dplyr")
filter <- dplyr::filter
library("ggplot2")
library("scales")
library("tidyr")
library("zoo")
filename <- tempfile(fileext = ".beancount")
system(paste("bean-example -o", filename), ignore.stderr=TRUE)
df <- register(filename) %>% mutate(yearmon = zoo::as.yearmon(date)) %>%
      filter(commodity=="USD")
nw <- net_worth(filename)

Then we'll write some convenience functions we'll use over and over again:

print_tibble_rows <- function(df) {
    print(df, n=nrow(df))
}
count_beans <- function(df, filter_str = "", ..., 
                        amount = "amount",
                        commodity="commodity", 
                        cutoff=1e-3) {
    commodity <- sym(commodity)
    amount_var <- sym(amount)
    filter(df, grepl(filter_str, account)) %>% 
        group_by(account, !!commodity, ...) %>%
        summarize(!!amount := sum(!!amount_var)) %>% 
        filter(abs(!!amount_var) > cutoff & !is.na(!!amount_var)) %>%
        arrange(desc(abs(!!amount_var)))
}

Basic net worth chart

Here is a basic chart of one's net worth from the beginning of the plaintext accounting file to today by month:

next_month <- function(date) {
    zoo::as.Date(zoo::as.yearmon(date) + 1/12)
}
nw_dates <- seq(next_month(min(df$date)), next_month(Sys.Date()), by="months")
df_nw <- net_worth(filename, nw_dates) %>% filter(commodity=="USD")
ggplot(df_nw, aes(x=date, y=net_worth, colour=commodity, group=commodity)) + 
  geom_line() + scale_y_continuous(labels=scales::dollar)

Basic net worth chart

Basic income sheets

month_cutoff <- zoo::as.yearmon(Sys.Date()) - 2/12
compute_income <- function(df) {
    count_beans(df, "^Income", yearmon) %>% 
        mutate(income = -amount) %>%
        select(-amount) %>% ungroup()
}
print_income <- function(df) {
    compute_income(df) %>% 
        filter(yearmon >= month_cutoff) %>%
        spread(yearmon, income, fill=0) %>%
        print_tibble_rows()
}
compute_expenses <- function(df) {
    count_beans(df, "^Expenses", yearmon) %>% 
        mutate(expenses = amount) %>%
        select(-amount) %>% ungroup()
}
print_expenses <- function(df) {
    compute_expenses(df) %>%
        filter(yearmon >= month_cutoff) %>%
        spread(yearmon, expenses, fill=0) %>%
        print_tibble_rows()
}
compute_total <- function(df) {
full_join(compute_income(prune_coa(df)) %>% select(-account),
          compute_expenses(prune_coa(df)) %>% select(-account), 
          by=c("yearmon", "commodity")) %>%
    mutate(income = ifelse(is.na(income), 0, income),
           expenses = ifelse(is.na(expenses), 0, expenses),
           net = income - expenses) %>%
    gather(type, amount, -yearmon, -commodity)
}
print_total <- function(df) {
    compute_total(df) %>%
        filter(yearmon >= month_cutoff) %>%
        spread(yearmon, amount, fill=0) %>%
        print_tibble_rows()
}
print_total(df)
print_income(prune_coa(df, 2))
print_expenses(prune_coa(df, 2))
print_income(df)
print_expenses(df)

And here is a plot of income, expenses, and net income over time:

ggplot(compute_total(df), aes(x=yearmon, y=amount, group=commodity, colour=commodity)) +
  facet_grid(type ~ .) +
  geom_line() + geom_hline(yintercept=0, linetype="dashed") +
  scale_x_continuous() + scale_y_continuous(labels=scales::comma) 

Monthly income chart