Code Journal Club: UCSCXenaTools — A 5-step Pipeline for Mining UCSC Xena's Cancer Omics Repositories
Architecture & Design
A 5-step pipeline encoded directly in the file structure. The R/ directory follows the workflow named in the README: XenaGenerate.R, XenaFilter.R, XenaQuery.R, XenaDownload.R, and XenaPrepare.R. One verb per file means a new user can guess where each stage of the data lifecycle lives without reading docs. The README states the contract outright: "a workflow with generate, filter, query, download and prepare 5 steps". It also chains naturally in dplyr style, which is why R/utils-pipe.R re-exports %>%.
Two-tier API with a small S4 class. api_higher.R holds the user-facing verbs, while api_inner.R holds the lower-level HTTP plumbing. XenaHub-class.R defines an S4 class, a bit unusual for a package that otherwise feels functional. Its slots are light: character vectors of host, cohort, and dataset IDs, which get passed from one step to the next. Keeping the ergonomic surface apart from the request mechanics is a clean call.
Pre-baked metadata in data/. Rather than enumerating Xena hubs over the network on every load, the package ships a cached XenaData table: 17 columns, 2,314 datasets across 255 cohorts and 11 hubs (from gdcHub with 882 datasets down to atacseqHub with 9). XenaDataUpdate() refreshes it when you need current listings, a sensible choice given how often public portals rotate datasets. (inst/api.RData, despite the name, documents the package's own internal function table, not the remote Xena API.)
Targeted fetches instead of whole-file downloads. Beyond the 5-step file workflow, R/fetch.R provides fetch_dense_values() and friends, which pull only the genes and samples you ask for straight from a hub. That is where probe → gene symbol translation happens; XenaQueryProbeMap.R only resolves the probeMap file URL. The low-level query functions are generated at load time in api_xq.R (R/dynamics.R is just a documentation stub). The upshot: the package treats UCSC Xena as a queryable database, not only a file-download portal.
Test Run
Tested on 2026-09-16 on an ARM64 Ubuntu server (2 cores, 12 GB RAM) with R 4.6.1 and UCSCXenaTools 1.7.0 from CRAN.
Setup:install.packages("UCSCXenaTools") finished in 6 seconds. It is pure R with nothing to compile, so ARM64 was a non-issue, and all nine dependencies (dplyr, httr, readr, jsonlite, and so on) were already present. No need for build_vignettes or r-universe.
First run (README example):
The TCGA lung clinical-data example ran end to end with no warnings. XenaGenerate + two XenaFilter calls matched exactly 3 datasets (LUNG, LUAD, LUSC clinicalMatrix) in 0.01 s. XenaQuery took 0.9 s (it checks the URLs), XenaDownload 2.8 s for 2.4 MB, and XenaPrepare 0.3 s, returning three data frames of 1299×134, 706×148 and 626×118. The TCGA hub responded quickly with no retries.
Real-world performance:
Fetching TP53, EGFR and KRAS across TCGA pan-cancer TPM (toilHub, tcga_RSEM_gene_tpm) with fetch_dense_values() returned a 3 × 10,535 matrix in 2.7 s, peak memory 157 MB, log2-scale values with no NAs. One papercut: that dataset is indexed by Ensembl IDs, so passing gene symbols with the default use_probeMap = FALSE fails with
The following identifiers have been removed from host https://toil.xenahubs.net dataset tcga_RSEM_gene_tpm
[1] "TP53" "EGFR" "KRAS"
Error in fetch_dense_values(...) : Bad identifiers, no one left, check input?
Setting use_probeMap = TRUE fixes it, but the error message never mentions that option.
Verdict:
If you work in R and need TCGA, GDC, ICGC or Treehouse data without clicking through the Xena browser, use it: install is trivial, the README workflow works as written, and targeted gene fetches are fast and light on memory. Remember use_probeMap = TRUE whenever you query by gene symbol.
Links:
- GitHub: https://github.com/ropensci/UCSCXenaTools
- JOSS paper: https://doi.org/10.21105/joss.01627
- CRAN: https://cran.r-project.org/package=UCSCXenaTools
- rOpenSci review: https://github.com/ropensci/software-review/issues/315