RcppTskit: Working with tree sequences in R

Introduction

This vignette introduces working with tree sequences in R using the RcppTskit package. RcppTskit provides R access to the tskit C application programming interface (API) (Jeffery et al. 2026) https://tskit.dev/tskit/docs/stable/c-api.html. If you are new to tree sequences and the broader concept of ancestral recombination graphs (ARGs), see Brandt et al. (2024), Lewanski et al. (2024), Nielsen et al. (2024), and Wong et al. (2024). Before showing how to use RcppTskit, we highlight the targeted users and summarise the now extensive tree sequence ecosystem, as this has shaped the aim and design of RcppTskit. We then describe specific aims of RcppTskit, describe the implemented data and class model, and show four typical use cases.

Who is RcppTskit for?

As summarised below, Python is the most widely used environment for working with tree sequences. Most R users who want to work with tree sequences are advised to start with the tskit Python API via the R package reticulate (Ushey et al. 2025) https://rstudio.github.io/reticulate/. See https://tskit.dev/tutorials/tskitr.html for a tutorial. This approach provides access to the most complete and mature API (Jeffery et al. 2026) https://tskit.dev/tskit/docs/stable/python-api.html and interoperability with other Python packages.

RcppTskit is primarily geared towards providing R access to the tskit C API (Jeffery et al. 2026), for cases where the reticulate approach is not optimal; for example, high-performance or low-level work with tree sequences. As a result, RcppTskit currently provides a limited set of functions because the Python API (and reticulate) already covers most needs. As the name suggests, RcppTskit leverages the R package Rcpp (Eddelbuettel et al. 2026) https://www.rcpp.org/, which significantly lowers the barrier to using C++ with R. However, we still need to write C++ wrappers and expose them to R, so we recommend using reticulate first. To understand which functions are implemented, users should explore the RcppTskit help pages of R functions, while developers should explore the underlying RcppTskit:::rtsk_* low-level R and C++ functions.

State of the tree sequence ecosystem

The tree sequence ecosystem is rapidly evolving. The website https://tskit.dev/software/ lists tools that closely interoperate with tskit, while Jeffery et al. (2026) lists additional tools that depend on tskit functionality. Consequently, there are now many tools for the generation and analysis of tree sequences. Below is a quick summary of some of the tools relevant to RcppTskit as of January 2026.

The above tools enable work with tree sequences and/or generate them via simulation. There is a growing list of tools that estimate ARGs from observed genomic data and can export them in the tree sequence file format. Notable examples include: tsinfer (https://tskit.dev/tsinfer/docs/ and https://github.com/tskit-dev/tsinfer/), Relate (https://myersgroup.github.io/relate/ and https://github.com/MyersGroup/relate/), SINGER (https://github.com/popgenmethods/SINGER/), ARGNeedle (https://palamaralab.github.io/software/argneedle/ and https://github.com/PalamaraLab/arg-needle-lib/), and Threads (https://palamaralab.github.io/software/threads/ and https://github.com/palamaraLab/threads/).

As described above, the tree sequence ecosystem is extensive. Python is the most widely used platform to interact with tree sequences, with comprehensive packages for simulation and analysis.

There is interest in working with tree sequences in R. Because we can call Python from within R using the reticulate R package, there is no pressing need for a dedicated R API for work with tree sequences. As noted above, see https://tskit.dev/tutorials/tskitr.html for an example of this approach. This keeps the community focused on the Python collection of packages. While there are differences between Python and R, many R users should be able to follow the extensive Python API documentation, examples, and tutorials listed above, especially those at https://tskit.dev/tutorials/.

To provide an idiomatic R interface to some population genetic simulation steps and operations with tree sequences, slendr implements bespoke functions and wrappers to interact with msprime, SLiM, and tskit. It uses reticulate to interact with the Python APIs of these packages, which further lowers barriers for R users to work with tree sequences.

One downside of using reticulate is the overhead of calling Python functions. This overhead is minimal for most analyses because a user calls a few Python functions, which do all the work (including loops) on the Python side, which often call the tskit C API. However, the overhead can be limiting for repeated calls between R and Python, such as calling Python functions from within an R loop, say to record a tree sequence in a multi-generation simulation with many individuals.

Aims for RcppTskit

Given the current tree sequence ecosystem, the aims of the RcppTskit package are to provide an easy-to-install R package that supports users in four typical cases of working with tree sequences and table collections. The authors are open to expanding this scope depending on user demand and engagement. The four typical use cases are:

  1. Load a tree sequence into R and summarise it,

  2. Pass a tree sequence between R and reticulate or standard Python,

  3. Call the tskit C API from C++ in an R session or script, and

  4. Call the tskit C API from C++ in another R package.

Examples for all of these cases are provided below after we describe the implemented data and class model, and API mirroring across the languages.

Data and class model

RcppTskit represents a tree sequence as a lightweight R6 object of class TreeSequence. The R6 interface is used so that TreeSequence method calls in R resemble the tskit Python API, particularly when compared to reticulate Python. Internally, TreeSequence stores an external pointer (externalptr) to a heap-allocated object tsk_treeseq_t from the tskit C API.

Most methods (for example, ts$num_individuals() and ts$dump()) call the tskit C API via Rcpp, so the calls are fast. The external pointer is exposed as TreeSequence$xptr for developers and advanced users who work with C++. In C++, the external pointer has type rtsk_treeseq_t, and the tree sequence memory is released by the Rcpp::XPtr finaliser when the pointer is garbage-collected in R.

RcppTskit also provides a lightweight TableCollection R6 class, which stores an external pointer to a heap-allocated object tsk_table_collection_t from the tskit C API. In C++, the external pointer has type rtsk_table_collection_t with the same memory management as rtsk_treeseq_t.

While tree sequence (tsk_treeseq_t) is an immutable object, table collection (tsk_table_collection_t) is a mutable object, which can be edited. No R functions for expanding and editing are implemented to date, so all editing should happen in C/C++ or Python.

API mirroring across the languages

RcppTskit aims to keep naming, arguments, defaults, and behaviour aligned with upstream tskit APIs whenever practical. Specifically, the RcppTskit R API mirrors the tskit Python API; the R functions and R6 classes (with their methods) aim to mirror tskit Python functions and classes (with their methods). Note that the RcppTskit R API is far more limited than the tskit Python API. The RcppTskit C++ API aims to mirror the tskit C API functions and semantics, but is deliberately R oriented. When we intentionally deviate (for example, to support R idioms or safety), we document the rationale and test the chosen behaviour.

Four typical use cases

First install RcppTskit from CRAN and load it.

# install.packages("RcppTskit")

test <- require(RcppTskit)
#> Loading required package: RcppTskit
if (!test) {
  message("RcppTskit not available; skipping vignette execution.")
  knitr::opts_chunk$set(eval = FALSE)
}

1) Load a tree sequence into R and summarise it

# Load a tree sequence
ts_file <- system.file("examples/test.trees", package = "RcppTskit")
ts <- ts_load(ts_file)
methods::is(ts)
#> [1] "TreeSequence"

# Print the summary of the tree sequence
ts$print()
# ts # the same as above

ts$num_individuals()
#> integer64
#> [1] 8

# Access the table collection
tc <- ts$dump_tables()
tc$print()

# Convert the table collection to tree sequence
ts2 <- tc$tree_sequence()

# Users should explore the help pages
help(package = "RcppTskit")

Developers should also explore the internal R and C++ functions. Their names all start with rtsk_ and are accessible via RcppTskit:::. Since these are internal functions we instruct the lint tool jarl not to warn about their use. For example, the above R function ts_load() effectively calls:

# Low-level R function, which further calls the C++ function
# jarl-ignore internal_function: exposing for demo/doc
RcppTskit:::rtsk_treeseq_load
#> function (filename, options = 0L) 
#> {
#>     .Call(`_RcppTskit_rtsk_treeseq_load`, filename, options)
#> }
#> <bytecode: 0x5603839e6e28>
#> <environment: namespace:RcppTskit>

# C++ function (see also the C++ source code)
# jarl-ignore internal_function: exposing for demo/doc
RcppTskit:::`_RcppTskit_rtsk_treeseq_load`
#> $name
#> [1] "_RcppTskit_rtsk_treeseq_load"
#> 
#> $address
#> <pointer: 0x560386fbd770>
#> attr(,"class")
#> [1] "RegisteredNativeSymbol"
#> 
#> $dll
#> DLL name: RcppTskit
#> Filename: /tmp/RtmpydtxRV/Rinstb7a244dee35/RcppTskit/libs/RcppTskit.so
#> Dynamic lookup: FALSE
#> 
#> $numParameters
#> [1] 2
#> 
#> attr(,"class")
#> [1] "CallRoutine"      "NativeSymbolInfo"

# The same as the ts_load() example, but exposing the external pointer
# jarl-ignore internal_function: exposing for demo/doc
xptr <- RcppTskit:::rtsk_treeseq_load(ts_file)
methods::is(xptr)
#> [1] "externalptr" "refObject"

# We pass the external pointer to other low-level functions
# jarl-ignore internal_function: exposing for demo/doc
RcppTskit:::rtsk_treeseq_get_num_individuals(xptr)
#> integer64
#> [1] 8

2) Pass a tree sequence between R and reticulate or standard Python

# Tree sequence in R
ts_file <- system.file("examples/test.trees", package = "RcppTskit")
ts <- ts_load(ts_file)

# If you now want to use the tskit Python API via reticulate, use
tskit <- get_tskit_py()
#> Downloading uv...Done!
#> Python module tskit is not available. Attempting to install it ...
if (check_tskit_py(tskit)) {
  ts_py <- ts$r_to_py()
  # ... continue in reticulate Python ...
  ts_py$num_individuals # 8
  ts2_py = ts_py$simplify(samples = c(0L, 1L, 2L, 3L))
  ts2_py$num_individuals # 2
  ts2_py$num_nodes # 8
  ts2_py$tables$nodes$time # 0.0 ... 5.0093910
  # ... and to bring it back to R, use ...
  ts2 <- ts_py_to_r(ts2_py)
  ts2$num_individuals() # 2
}
#> integer64
#> [1] 2

# If you prefer standard (non-reticulate) Python, use
ts_file <- tempfile()
print(ts_file)
#> [1] "/tmp/RtmplucBEK/filed7d10ef5aa9"
ts$dump(file = ts_file)
# ... continue in standard Python ...
# import tskit
# ts = tskit.load("insert_ts_file_path_here")
# ts.num_individuals # 8
# ts2 = ts.simplify(samples = [0, 1, 2, 3])
# ts2.num_individuals # 2
# ts2.dump("insert_ts_file_path_here")
# ... and to bring it back to R, use ...
ts2 <- ts_load(ts_file)
ts2$num_individuals() # 2 (if you have run the above Python code)
#> integer64
#> [1] 8
file.remove(ts_file)
#> [1] TRUE

# Analogous to the above, you can also use reticulate to work on a table collection
tc <- ts$dump_tables()
if (check_tskit_py(tskit)) {
  tc_py <- tc$r_to_py()
  # ... continue in reticulate Python ...
  tmp <- tc_py$simplify(samples = c(0L, 1L, 2L, 3L))
  tmp
  tc_py$individuals$num_rows # 2
  tc_py$nodes$num_rows # 8
  tc_py$nodes$time # 0.0 ... 5.0093910
  # ... and to bring it back to R, use ...
  tc2 <- tc_py_to_r(tc_py)
  tc2$print()
}

3) Call the tskit C API from C++ in an R session or script

# Write a C++ function as a multi-line character string
codeString <- '
  #include <RcppTskit.hpp>
  int ts_num_individuals(SEXP ts) {
      rtsk_treeseq_t ts_xptr(ts);
    return (int) tsk_treeseq_get_num_individuals(ts_xptr);
  }'

# Compile the C++ function
ts_num_individuals2 <- Rcpp::cppFunction(
  code = codeString,
  depends = "RcppTskit",
  plugins = "RcppTskit"
)
# We must specify both the `depends` and `plugins` arguments!

# Load a tree sequence
ts_file <- system.file("examples/test.trees", package = "RcppTskit")
ts <- ts_load(ts_file)

# Apply the compiled function
# (on the pointer)
ts_num_individuals2(ts$xptr)
#> [1] 8

# The above is identical to the RcppTskit implementation
# (available as a method of the TreeSequence class)
ts$num_individuals()
#> integer64
#> [1] 8

4) Call the tskit C API from C++ in another R package

To call the tskit C API in your own R package via Rcpp you can leverage RcppTskit, which simplifies installation, provides the required linking flags, bootstraps your work by providing some RcppTskit C++ functions that interface tskit C API and R, etc. To leverage RcppTskit, follow the steps below and check how they are implemented in the demo R package RcppTskitTestLinking at https://github.com/HighlanderLab/RcppTskitTestLinking/.

  1. Open the DESCRIPTION file and add RcppTskit to the Imports: and LinkingTo: fields, and add Rcpp to the LinkingTo: field.

  2. Create R/YourPackage-package.R and add at minimum: #' @import RcppTskit in one line and "_PACKAGE" in another line, assuming you use devtools to manage your package NAMESPACE imports.

  3. Add #include <RcppTskit.hpp> as needed to your C++ header files in src directory.

  4. Add // [[Rcpp::depends(RcppTskit)]] to your C++ files in src directory.

  5. Add // [[Rcpp::plugins(RcppTskit)]] to your C++ files in src directory.

  6. Call the RcppTskit C++ API and the tskit C API as needed in src directory.

  7. Configure your package build to link against the RcppTskit library with the following steps:

  • Add src/Makevars.in and src/Makevars.win.in files with the PKG_LIBS = @RCPPTSKIT_LIB@ line, in addition to any other flags.

  • Add tools/configure.R file, which will replace @RCPPTSKIT_LIB@ in src/Makevars.in and src/Makevars.win.in with the installed RcppTskit library file (including appropriate flags), and generate src/Makevars and src/Makevars.win.

  • Add configure and configure.win scripts (and make them executable) to call tools/configure.R.

  • Add cleanup and cleanup.win scripts (and make them executable) to remove src/Makevars and src/Makevars.win as well as other build artefacts.

  1. You should now be ready to build, check, and install your package using devtools::build(), devtools::check(), and devtools::install() or their R CMD equivalents.

Here is code you can run to check out RcppTskitTestLinking:

# Install and load the demo package
remotes::install_github("HighlanderLab/RcppTskitTestLinking")
library(RcppTskitTestLinking)

# Check the demo function
print(rtsk_treeseq_get_num_individuals2)

# Example tree sequence
ts_file <- system.file("examples", "test.trees", package = "RcppTskit")
ts <- ts_load(ts_file)

# Function from RcppTskit (working with TreeSequence R6 class)
ts$num_individuals()

# Function from RcppTskitTestLinking (working with externalptr)
rtsk_treeseq_get_num_individuals2(ts$xptr)

Conclusion

RcppTskit provides R access to the tskit C API with a simple installation and a lightweight interface. It provides a limited number of functions because most users can and should use reticulate to call the tskit Python API from R. The implemented RcppTskit R API closely mimics tskit Python API to streamline their use. When this option is not optimal, developers and advanced users can easily use the RcppTskit C++ API and tskit C API.

Session information

sessionInfo()
#> R version 4.6.0 (2026-04-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] RcppTskit_0.3.0
#> 
#> loaded via a namespace (and not attached):
#>  [1] cli_3.6.6         knitr_1.51        rlang_1.2.0       xfun_0.59        
#>  [5] otel_0.2.0        png_0.1-9         jsonlite_2.0.0    bit_4.6.0        
#>  [9] buildtools_1.0.0  rprojroot_2.1.1   htmltools_0.5.9   maketools_1.3.2  
#> [13] sys_3.4.3         rmarkdown_2.31    rappdirs_0.3.4    grid_4.6.0       
#> [17] evaluate_1.0.5    fastmap_1.2.0     yaml_2.3.12       compiler_4.6.0   
#> [21] Rcpp_1.1.1-1.1    here_1.0.2        lattice_0.22-9    digest_0.6.39    
#> [25] R6_2.6.1          reticulate_1.46.0 Matrix_1.7-5      withr_3.0.3      
#> [29] tools_4.6.0       bit64_4.8.2
Brandt, Débora Y C, Christian D Huber, Charleston W K Chiang, and Diego Ortega-Del Vecchyo. 2024. “The Promise of Inferring the Past Using the Ancestral Recombination Graph (ARG).” Genome Biology and Evolution, evae005. https://doi.org/10.1093/gbe/evae005.
Eddelbuettel, Dirk, Romain Francois, JJ Allaire, et al. 2026. Rcpp: Seamless r and c++ Integration. https://doi.org/10.32614/CRAN.package.Rcpp.
Jeffery, Ben, Yan Wong, Kevin Thornton, et al. 2026. “Population-Scale Ancestral Recombination Graphs with Tskit 1.0.” arXiv, ahead of print. https://doi.org/10.48550/arXiv.2602.09649.
Lewanski, Alexander L., Michael C. Grundler, and Gideon S. Bradburd. 2024. “The Era of the ARG: An Introduction to Ancestral Recombination Graphs and Their Significance in Empirical Evolutionary Genomics.” PLOS Genetics 20 (1): 1–24. https://doi.org/10.1371/journal.pgen.1011110.
Nielsen, Rasmus, Andrew H. Vaughn, and Yun Deng. 2024. “Inference and Applications of Ancestral Recombination Graphs.” Nature Reviews Genetics 26: 47–58. https://doi.org/10.1038/s41576-024-00772-4.
Ushey, Kevin, JJ Allaire, and Yuan Tang. 2025. Reticulate: Interface to Python. https://doi.org/10.32614/CRAN.package.reticulate.
Wong, Yan, Anastasia Ignatieva, Jere Koskela, Gregor Gorjanc, Anthony W Wohns, and Jerome Kelleher. 2024. “A General and Efficient Representation of Ancestral Recombination Graphs.” Genetics 228 (1): iyae100. https://doi.org/10.1093/genetics/iyae100.