Research-package workflow boundaries with thisutils
Source:vignettes/research-package-workflows.Rmd
research-package-workflows.Rmdthisutils provides a small common layer for recurring
research-package tasks. This vignette follows four boundaries: data
representation, neighborhood and metric evaluation, repeated execution,
and optional runtime dependencies.
Preserve matrix representation and meaning
library(Matrix)
library(thisutils)
x <- Matrix(
c(-3, 0, 2, -1, 4, 0),
nrow = 3,
sparse = TRUE,
dimnames = list(paste0("r", 1:3), paste0("c", 1:2))
)
compact <- matrix_to_table(x, keep_zero = FALSE)
roundtrip <- table_to_matrix(compact, return_sparse = TRUE)
collapsed <- collapse_sparse_rows(x, c("g1", "g1", "g2"))
c(
sparsity = check_sparsity(x),
stored_coordinates = nrow(compact),
collapsed_rows = nrow(collapsed),
roundtrip_equal = isTRUE(all.equal(as.matrix(roundtrip), as.matrix(x)))
)
#> sparsity stored_coordinates collapsed_rows roundtrip_equal
#> 0.3333333 4.0000000 2.0000000 1.0000000Numeric matrices and sparse graphs give unstored entries different meanings. The two top-k interfaces make that choice visible:
run_sparse_topk(x, k = 2, by = "col")$value
#> [,1] [,2]
#> [1,] 2 0
#> [2,] 4 0
run_sparse_topk_stored(x, k = 2, by = "col")$value
#> [,1] [,2]
#> [1,] 2 -3
#> [2,] 4 -1Combine neighborhood and classification evaluation
The next example creates three synthetic labels in two batches. It is small enough to run while building the vignette.
set.seed(20260810)
cell_type <- rep(c("T", "B", "Mono"), each = 40)
batch <- rep(rep(c("A", "B"), each = 20), 3)
metadata <- data.frame(batch = batch, cell_type = cell_type)
centers <- rbind(
T = c(-3, 0, 0),
B = c(3, 0, 0),
Mono = c(0, 3, 0)
)
embedding <- centers[cell_type, , drop = FALSE] +
matrix(stats::rnorm(120 * 3, sd = 0.7), ncol = 3)compute_lisi() evaluates local label diversity, while
run_biocneighbors_knn() provides a standardized neighbor
result that can be fed into
classification_metrics_compute().
mixing <- compute_lisi(
embedding,
metadata,
c("batch", "cell_type"),
perplexity = 10,
n_threads = 1,
max_dense_bytes = 16 * 1024^2
)
neighbors <- run_biocneighbors_knn(
embedding,
k = 7,
exclude_self = TRUE,
n_threads = 1
)
predicted <- apply(neighbors$idx, 1L, function(index) {
votes <- sort(table(metadata$cell_type[index]), decreasing = TRUE)
names(votes)[[1L]]
})
metrics <- classification_metrics_compute(predicted, metadata$cell_type)
c(
median_batch_lisi = median(mixing$batch),
median_cell_type_lisi = median(mixing$cell_type),
accuracy = metrics$accuracy,
macro_f1 = metrics$macro_f1
)
#> median_batch_lisi median_cell_type_lisi accuracy
#> 1.746606 1.000000 1.000000
#> macro_f1
#> 1.000000Repeat work under an explicit execution contract
The same function can run serially or with PSOCK workers. A seed
defines one random-number stream per input, while
progress = FALSE keeps stable lifecycle messages without an
elapsed-time display.
draw <- function(cores, verbose = FALSE) {
parallelize_fun(
setNames(1:4, letters[1:4]),
function(i) stats::rnorm(2),
cores = cores,
backend = "psock",
seed = 42,
verbose = verbose,
progress = FALSE,
timestamp_format = ""
)
}
serial <- draw(1)
psock <- draw(2, verbose = TRUE)
#> ℹ Using 2 cores
#> ℹ Building results
identical(serial, psock)
#> [1] TRUEPSOCK tasks that reference an object from the function environment
should include that object in each task or name it in
export_fun.
Diagnose optional dependencies without modifying a library
status <- check_r(
c("Matrix", "BiocNeighbors"),
install = FALSE,
verbose = FALSE
)
backend <- if (isTRUE(status[["BiocNeighbors"]])) {
get_namespace_fun("BiocNeighbors", "findKNN")
} else {
NULL
}
c(unlist(status), backend_is_function = is.function(backend))
#> Matrix BiocNeighbors backend_is_function
#> TRUE TRUE TRUEinstall = FALSE is useful inside package workflows
because availability checking remains read-only. Installation, when
intentionally enabled, can be bounded with the timeout
argument.