Skip to content

cran/ledger

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ledger

CRAN Status Badge R-CMD-check Coverage Status RStudio CRAN mirror downloads

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.

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
beanquery

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 bean-query (to read beancount files) run the following in your shell:

pip3 install beanquery

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

Examples

API

The main function of this package is register() which reads in the register of a plaintext accounting file. This package also registers 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")
ledger_file <- system.file("extdata", "example.ledger", package = "ledger")
register(ledger_file)

## # A tibble: 42 × 8
##    date       mark  payee       description     account amount commodity comment
##    <date>     <chr> <chr>       <chr>           <chr>    <dbl> <chr>     <chr>  
##  1 2015-12-31 *     <NA>        Opening Balanc… Assets…  5000  USD       ""     
##  2 2015-12-31 *     <NA>        Opening Balanc… Equity… -5000  USD       ""     
##  3 2016-01-01 *     Landlord    Rent            Assets… -1500  USD       ""     
##  4 2016-01-01 *     Landlord    Rent            Expens…  1500  USD       ""     
##  5 2016-01-01 *     Brokerage   Buy Stock       Assets… -1000  USD       ""     
##  6 2016-01-01 *     Brokerage   Buy Stock       Equity…  1000  USD       ""     
##  7 2016-01-01 *     Brokerage   Buy Stock       Assets…     4  SP        ""     
##  8 2016-01-01 *     Brokerage   Buy Stock       Equity… -1000  USD       ""     
##  9 2016-01-01 *     Supermarket Grocery store   Expens…   501. USD       "Link:…
## 10 2016-01-01 *     Supermarket Grocery store   Liabil…  -501. USD       "Link:…
## # ℹ 32 more rows

hledger_file <- system.file("extdata", "example.hledger", package = "ledger")
register(hledger_file)

## # A tibble: 42 × 12
##    date       mark  payee   description account amount commodity historical_cost
##    <date>     <chr> <chr>   <chr>       <chr>    <dbl> <chr>               <dbl>
##  1 2015-12-31 *     <NA>    Opening Ba… Assets…  5000  USD                 5000 
##  2 2015-12-31 *     <NA>    Opening Ba… Equity… -5000  USD                -5000 
##  3 2016-01-01 *     Landlo… Rent        Assets… -1500  USD                -1500 
##  4 2016-01-01 *     Landlo… Rent        Expens…  1500  USD                 1500 
##  5 2016-01-01 *     Broker… Buy Stock   Assets… -1000  USD                -1000 
##  6 2016-01-01 *     Broker… Buy Stock   Equity…  1000  USD                 1000 
##  7 2016-01-01 *     Broker… Buy Stock   Assets…     4  SP                  1000 
##  8 2016-01-01 *     Broker… Buy Stock   Equity… -1000  USD                -1000 
##  9 2016-01-01 *     Superm… Grocery st… Expens…   501. USD                  501.
## 10 2016-01-01 *     Superm… Grocery st… Liabil…  -501. USD                 -501.
## # ℹ 32 more rows
## # ℹ 4 more variables: hc_commodity <chr>, market_value <dbl>,
## #   mv_commodity <chr>, id <chr>

beancount_file <- system.file("extdata", "example.beancount", package = "ledger")
register(beancount_file)

## # A tibble: 42 × 13
##    date       mark  payee   description account amount commodity historical_cost
##    <date>     <chr> <chr>   <chr>       <chr>    <dbl> <chr>               <dbl>
##  1 2015-12-31 *     ""      Opening Ba… Assets…  5000  USD                 5000 
##  2 2015-12-31 *     ""      Opening Ba… Equity… -5000  USD                -5000 
##  3 2016-01-01 *     "Landl… Rent        Assets… -1500  USD                -1500 
##  4 2016-01-01 *     "Landl… Rent        Expens…  1500  USD                 1500 
##  5 2016-01-01 *     "Broke… Buy Stock   Assets… -1000  USD                -1000 
##  6 2016-01-01 *     "Broke… Buy Stock   Equity…  1000  USD                 1000 
##  7 2016-01-01 *     "Broke… Buy Stock   Assets…     4  SP                  1000 
##  8 2016-01-01 *     "Broke… Buy Stock   Equity… -1000  USD                -1000 
##  9 2016-01-01 *     "Super… Grocery st… Expens…   501. USD                  501.
## 10 2016-01-01 *     "Super… Grocery st… Liabil…  -501. USD                 -501.
## # ℹ 32 more rows
## # ℹ 5 more variables: hc_commodity <chr>, market_value <dbl>,
## #   mv_commodity <chr>, tags <chr>, id <chr>

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)
print(df)

## # A tibble: 2,674 × 13
##    date       mark  payee  description account  amount commodity historical_cost
##    <date>     <chr> <chr>  <chr>       <chr>     <dbl> <chr>               <dbl>
##  1 2024-01-01 *     ""     Opening Ba… Assets…  3.04e3 USD                3041. 
##  2 2024-01-01 *     ""     Opening Ba… Equity… -3.04e3 USD               -3041. 
##  3 2024-01-01 *     ""     Allowed co… Income… -1.85e4 IRAUSD           -18500  
##  4 2024-01-01 *     ""     Allowed co… Assets…  1.85e4 IRAUSD            18500  
##  5 2024-01-03 *     "Uncl… Eating out… Liabil… -2.58e1 USD                 -25.8
##  6 2024-01-03 *     "Uncl… Eating out… Expens…  2.58e1 USD                  25.8
##  7 2024-01-04 *     "BANK… Monthly ba… Assets… -4   e0 USD                  -4  
##  8 2024-01-04 *     "BANK… Monthly ba… Expens…  4   e0 USD                   4  
##  9 2024-01-04 *     "Hool… Payroll     Assets…  1.35e3 USD                1351. 
## 10 2024-01-04 *     "Hool… Payroll     Assets…  1.2 e3 USD                1200  
## # ℹ 2,664 more rows
## # ℹ 5 more variables: hc_commodity <chr>, market_value <dbl>,
## #   mv_commodity <chr>, tags <chr>, id <chr>

suppressPackageStartupMessages(library("dplyr"))
dplyr::filter(df, grepl("Expenses", account), grepl("trip", tags)) %>%
    group_by(trip = tags, account) %>%
    summarize(trip_total = sum(amount), .groups = "drop")

## # A tibble: 6 × 3
##   trip                  account                  trip_total
##   <chr>                 <chr>                         <dbl>
## 1 trip-boston-2024      Expenses:Food:Restaurant     245.  
## 2 trip-los-angeles-2025 Expenses:Food:Alcohol          6.76
## 3 trip-los-angeles-2025 Expenses:Food:Coffee          29.6 
## 4 trip-los-angeles-2025 Expenses:Food:Restaurant     443.  
## 5 trip-new-york-2025    Expenses:Food:Coffee          52.1 
## 6 trip-new-york-2025    Expenses:Food:Restaurant     936.

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:

df2 <- rio::import(bean_example_file)
all.equal(df, tibble::as_tibble(df2))

## [1] TRUE

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")'

net_worth()

Some examples of using the net_worth function using the example files from the register examples:

dates <- seq(as.Date("2016-01-01"), as.Date("2018-01-01"), by="years")
net_worth(ledger_file, dates)

## # A tibble: 3 × 6
##   date       commodity net_worth assets liabilities revalued
##   <date>     <chr>         <dbl>  <dbl>       <dbl>    <dbl>
## 1 2016-01-01 USD           5000    5000          0         0
## 2 2017-01-01 USD           4361.   4882       -521.        0
## 3 2018-01-01 USD           6743.   6264       -521.     1000

net_worth(hledger_file, dates)

## # A tibble: 3 × 5
##   date       commodity net_worth assets liabilities
##   <date>     <chr>         <dbl>  <dbl>       <dbl>
## 1 2016-01-01 USD           5000    5000          0 
## 2 2017-01-01 USD           4361.   4882       -521.
## 3 2018-01-01 USD           6743.   7264       -521.

net_worth(beancount_file, dates)

## # A tibble: 3 × 5
##   date       commodity net_worth assets liabilities
##   <date>     <chr>         <dbl>  <dbl>       <dbl>
## 1 2016-01-01 USD           5000    5000          0 
## 2 2017-01-01 USD           4361.   4882       -521.
## 3 2018-01-01 USD           6743.   7264       -521.

dates <- seq(min(as.Date(df$date)), max(as.Date(df$date)), by="years")
net_worth(bean_example_file, dates)

## # A tibble: 6 × 5
##   date       commodity net_worth assets liabilities
##   <date>     <chr>         <dbl>  <dbl>       <dbl>
## 1 2025-01-01 IRAUSD           0      0           0 
## 2 2025-01-01 USD          42788. 43595.       -807.
## 3 2025-01-01 VACHR           66     66           0 
## 4 2026-01-01 IRAUSD           0      0           0 
## 5 2026-01-01 USD          86967. 88872.      -1905.
## 6 2026-01-01 VACHR           60     60           0

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), .groups = "drop")

## # A tibble: 11 × 3
##    account     mv_commodity market_value
##    <chr>       <chr>               <dbl>
##  1 Assets      IRAUSD             10100 
##  2 Assets      USD               113230.
##  3 Assets      VACHR                 23 
##  4 Equity      USD                -3041.
##  5 Expenses    IRAUSD             45400 
##  6 Expenses    USD               210495.
##  7 Expenses    VACHR                272 
##  8 Income      IRAUSD            -55500 
##  9 Income      USD              -297361.
## 10 Income      VACHR               -295 
## 11 Liabilities USD                -2721.

df %>% prune_coa(2) %>%
    group_by(account, mv_commodity) %>%
    summarize(market_value = sum(market_value), .groups = "drop")

## # A tibble: 17 × 3
##    account                     mv_commodity market_value
##    <chr>                       <chr>               <dbl>
##  1 Assets:US                   IRAUSD           1.01e+ 4
##  2 Assets:US                   USD              1.13e+ 5
##  3 Assets:US                   VACHR            2.3 e+ 1
##  4 Equity:Opening-Balances     USD             -3.04e+ 3
##  5 Expenses:Financial          USD              3.59e+ 2
##  6 Expenses:Food               USD              1.50e+ 4
##  7 Expenses:Health             USD              5.72e+ 3
##  8 Expenses:Home               USD              6.77e+ 4
##  9 Expenses:Taxes              IRAUSD           4.54e+ 4
## 10 Expenses:Taxes              USD              1.18e+ 5
## 11 Expenses:Transport          USD              3.24e+ 3
## 12 Expenses:Vacation           VACHR            2.72e+ 2
## 13 Income:US                   IRAUSD          -5.55e+ 4
## 14 Income:US                   USD             -2.97e+ 5
## 15 Income:US                   VACHR           -2.95e+ 2
## 16 Liabilities:AccountsPayable USD              5.68e-14
## 17 Liabilities:US              USD             -2.72e+ 3

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:

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), .groups = "drop") %>%
        filter(abs(!!amount_var) > cutoff & !is.na(!!amount_var)) %>%
        arrange(desc(abs(!!amount_var)))
}

Basic balance sheets

Here is some basic balance sheets (using the market value of our assets):

print_balance_sheet <- function(df) {
    assets <- count_beans(df, "^Assets",
                 amount="market_value", commodity="mv_commodity")
    print_tibble_rows(assets)
    liabilities <- count_beans(df, "^Liabilities",
                       amount="market_value", commodity="mv_commodity")
    print_tibble_rows(liabilities)
}
print(nw)

## # A tibble: 3 × 5
##   date       commodity net_worth assets liabilities
##   <date>     <chr>         <dbl>  <dbl>       <dbl>
## 1 2026-03-29 IRAUSD       10100  10100           0 
## 2 2026-03-29 USD          96917. 99893.      -2975.
## 3 2026-03-29 VACHR          -41    -41           0

print_balance_sheet(prune_coa(df, 2))

## # A tibble: 1 × 3
##   account   mv_commodity market_value
##   <chr>     <chr>               <dbl>
## 1 Assets:US USD                 4023.
## # A tibble: 1 × 3
##   account        mv_commodity market_value
##   <chr>          <chr>               <dbl>
## 1 Liabilities:US USD                -2975.

print_balance_sheet(df)

## # A tibble: 3 × 3
##   account                 mv_commodity market_value
##   <chr>                   <chr>               <dbl>
## 1 Assets:US:BofA:Checking USD              3576.   
## 2 Assets:US:ETrade:Cash   USD               447.   
## 3 Assets:US:Vanguard:Cash USD                -0.140
## # A tibble: 1 × 3
##   account                    mv_commodity market_value
##   <chr>                      <chr>               <dbl>
## 1 Liabilities:US:Chase:Slate USD                -2975.

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)

## # A tibble: 3 × 5
##   commodity type     `Jan 2026` `Feb 2026` `Mar 2026`
##   <chr>     <chr>         <dbl>      <dbl>      <dbl>
## 1 USD       expenses      9568.      7352.      5667.
## 2 USD       income       15719.     10479.     10587.
## 3 USD       net           6151.      3128.      4920.

print_income(prune_coa(df, 2))

## # A tibble: 1 × 5
##   account   commodity `Jan 2026` `Feb 2026` `Mar 2026`
##   <chr>     <chr>          <dbl>      <dbl>      <dbl>
## 1 Income:US USD           15719.     10479.     10587.

print_expenses(prune_coa(df, 2))

## # A tibble: 6 × 5
##   account            commodity `Jan 2026` `Feb 2026` `Mar 2026`
##   <chr>              <chr>          <dbl>      <dbl>      <dbl>
## 1 Expenses:Financial USD               4          4        13.0
## 2 Expenses:Food      USD             577.       449.      551. 
## 3 Expenses:Health    USD             291.       194.      194. 
## 4 Expenses:Home      USD            2600.      2600.        0  
## 5 Expenses:Taxes     USD            5977.      3984.     4789. 
## 6 Expenses:Transport USD             120        120       120

print_income(df)

## # A tibble: 4 × 5
##   account                       commodity `Jan 2026` `Feb 2026` `Mar 2026`
##   <chr>                         <chr>          <dbl>      <dbl>      <dbl>
## 1 Income:US:ETrade:GLD:Dividend USD              0          0        108. 
## 2 Income:US:Hooli:GroupTermLife USD             73.0       48.6       48.6
## 3 Income:US:Hooli:Match401k     USD           1800       1200       1200  
## 4 Income:US:Hooli:Salary        USD          13846.      9231.      9231.

print_expenses(df)

## # A tibble: 21 × 5
##    account                            commodity `Jan 2026` `Feb 2026` `Mar 2026`
##    <chr>                              <chr>          <dbl>      <dbl>      <dbl>
##  1 Expenses:Financial:Commissions     USD             0          0          8.95
##  2 Expenses:Financial:Fees            USD             4          4          4   
##  3 Expenses:Food:Groceries            USD           144.        94.4      242.  
##  4 Expenses:Food:Restaurant           USD           432.       355.       309.  
##  5 Expenses:Health:Dental:Insurance   USD             8.7        5.8        5.8 
##  6 Expenses:Health:Life:GroupTermLife USD            73.0       48.6       48.6 
##  7 Expenses:Health:Medical:Insurance  USD            82.1       54.8       54.8 
##  8 Expenses:Health:Vision:Insurance   USD           127.        84.6       84.6 
##  9 Expenses:Home:Electricity          USD            65         65          0   
## 10 Expenses:Home:Internet             USD            79.9       80.0        0   
## 11 Expenses:Home:Phone                USD            54.8       55.3        0   
## 12 Expenses:Home:Rent                 USD          2400       2400          0   
## 13 Expenses:Taxes:Y2025:US:Federal    USD             0          0        539.  
## 14 Expenses:Taxes:Y2025:US:State      USD             0          0        265.  
## 15 Expenses:Taxes:Y2026:US:CityNYC    USD           525.       350.       350.  
## 16 Expenses:Taxes:Y2026:US:Federal    USD          3189.      2126.      2126.  
## 17 Expenses:Taxes:Y2026:US:Medicare   USD           320.       213.       213.  
## 18 Expenses:Taxes:Y2026:US:SDI        USD             3.36       2.24       2.24
## 19 Expenses:Taxes:Y2026:US:SocSec     USD           845.       563.       563.  
## 20 Expenses:Taxes:Y2026:US:State      USD          1095.       730.       730.  
## 21 Expenses:Transport:Tram            USD           120        120        120

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

About

❗ This is a read-only mirror of the CRAN R package repository. ledger — Utilities for Importing Data from Plain Text Accounting Files. Homepage: https://github.com/trevorld/r-ledgerhttps://trevorldavis.com/R/ledger/ Report bugs for this package: https://github.com/trevorld/r-ledger/issues

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors