Skip to contents

thisutils 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.0000000

Numeric 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   -1

Combine 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.000000

Repeat 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] TRUE

PSOCK 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                TRUE

install = FALSE is useful inside package workflows because availability checking remains read-only. Installation, when intentionally enabled, can be bounded with the timeout argument.