https://github.com/trevorld/r-ledger
  
  
    Imports data from plain text accounting files 
    https://github.com/trevorld/r-ledger
  
beancount hledger ledger plaintext-accounting
        Last synced: 6 months ago 
        JSON representation
    
Imports data from plain text accounting files
- Host: GitHub
 - URL: https://github.com/trevorld/r-ledger
 - Owner: trevorld
 - License: other
 - Created: 2017-10-29T00:30:10.000Z (about 8 years ago)
 - Default Branch: master
 - Last Pushed: 2024-05-20T16:46:12.000Z (over 1 year ago)
 - Last Synced: 2025-03-30T20:33:45.974Z (7 months ago)
 - Topics: beancount, hledger, ledger, plaintext-accounting
 - Language: R
 - Homepage:
 - Size: 362 KB
 - Stars: 40
 - Watchers: 3
 - Forks: 2
 - Open Issues: 5
 - 
            Metadata Files:
            
- Readme: README.Rrst
 - Changelog: NEWS.md
 - Contributing: CONTRIBUTING
 - License: LICENSE
 
 
Awesome Lists containing this project
- jimsghstars - trevorld/r-ledger - Imports data from plain text accounting files (R)
 
README
          ledger
======
.. {r setup, echo = FALSE}
knitr::opts_chunk$set(fig.path = "man/figures/README-")
options(width=80)
.. ..
.. |CRAN-status| image:: https://www.r-pkg.org/badges/version/ledger
    :target: https://cran.r-project.org/package=ledger
    :alt: CRAN Status Badge
.. |R-CMD-check| image:: https://github.com/trevorld/r-ledger/workflows/R-CMD-check/badge.svg
    :target: https://github.com/trevorld/r-ledger/actions
    :alt: R-CMD-check
.. |codecov| image:: https://codecov.io/github/trevorld/r-ledger/branch/master/graph/badge.svg
    :target: https://app.codecov.io/github/trevorld/r-ledger?branch=master
    :alt: Coverage Status
.. |downloads| image:: https://cranlogs.r-pkg.org/badges/ledger
    :target: https://cran.r-project.org/package=ledger
    :alt: RStudio CRAN mirror downloads
|CRAN-status| |R-CMD-check| |codecov| |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.  
.. contents::
Installation
------------
To install the last version released to CRAN use the following command in R:
.. code:: 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:
.. code:: 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:
.. code:: bash
    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:
.. code:: bash
    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 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:
.. {r register}
library("ledger")
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``:
.. {r register2}
bean_example_file <- tempfile(fileext = ".beancount")
system(paste("bean-example -o", bean_example_file), ignore.stderr=TRUE)
df <- register(bean_example_file)
print(df)
suppressPackageStartupMessages(library("dplyr"))
dplyr::filter(df, grepl("Expenses", account), grepl("trip", tags)) %>% 
    group_by(trip = tags, account) %>% 
    summarize(trip_total = sum(amount), .groups = "drop")
.. ..
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:
.. {r rio}
df2 <- rio::import(bean_example_file)
all.equal(df, tibble::as_tibble(df2))
.. ..
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:
.. code:: bash
    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:
.. {r net_worth}
dates <- seq(as.Date("2016-01-01"), as.Date("2018-01-01"), by="years")
net_worth(ledger_file, dates)
net_worth(hledger_file, dates)
net_worth(beancount_file, dates)
dates <- seq(min(as.Date(df$date)), max(as.Date(df$date)), by="years")
net_worth(bean_example_file, dates)
.. ..
``prune_coa()``
~~~~~~~~~~~~~~~
Some examples using the ``prune_coa`` function to simplify the "Chart of Account" names to a given maximum depth:
.. {r prune_coa}
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")
df %>% prune_coa(2) %>% 
    group_by(account, mv_commodity) %>%
    summarize(market_value = sum(market_value), .groups = "drop")
.. ..
    
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:
.. {r setup_things, message=FALSE}
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:
.. {r setup2}
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):
.. {r balance}
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)
print_balance_sheet(prune_coa(df, 2))
print_balance_sheet(df)
.. ..
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:
.. {r net_worth_chart, fig.width=5, fig.height=5, fig.cap="Basic net worth chart"}
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 income sheets
~~~~~~~~~~~~~~~~~~~
.. {r income}
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:
.. {r income_chart, fig.width=5, fig.height=5, fig.cap="Monthly income chart"}
    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) 
.. ..