Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: splikit
Title: Analysing RNA Splicing in Single-Cell RNA Sequencing Data
Version: 2.3.2
Version: 2.3.3
Authors@R:
person("Arsham", "Mikaeili Namini", , "arsham.mikaeilinamini@mail.mcgill.ca", role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-9453-6951"))
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxygen2: do not edit by hand

S3method(print,splikit_junction_plot)
S3method(print,splikit_pseudo_correlation_result)
export(SplikitObject)
export(find_variable_events)
export(find_variable_genes)
Expand Down
26 changes: 25 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
# splikit 2.3.3

## Gene-expression input selection

* `make_gene_count()` now separates count-matrix selection from barcode
filtering. `matrix_source` selects the `raw` or `filtered` directory, while
`matrix_file` supports either the standard `matrix.mtx` or an alternative
Matrix Market file. By default, the function reads from `raw/`, prefers
STARsolo's `UniqueAndMult-EM.mtx` when available, and falls back to
`raw/matrix.mtx`. Explicit arguments can still force any supported source or
filename.

## Pseudo-correlation result export

* `get_pseudo_correlation()` and `SplikitObject$getPseudoCorrelation()` now
return a structured `splikit_pseudo_correlation_result`. Its `statistics`
component retains the existing per-event summaries and empirical inference,
while `null_distribution` contains one exportable row for every retained
event and permutation. The long table preserves the event and permutation
identifiers and replaces the previous `null_draws` matrix attribute.
* The pooled null values are provided for descriptive analysis and export;
event-level empirical p-values continue to use each event's own permutation
draws.

# splikit 2.3.2

## New Features
Expand Down Expand Up @@ -199,4 +223,4 @@ library(splikit)
* Further performance optimizations

---
*For questions or issues, please visit: https://github.com/Arshammik/splikit/issues*
*For questions or issues, please visit: https://github.com/Arshammik/splikit/issues*
7 changes: 3 additions & 4 deletions R/SplikitObject.R
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,8 @@ SplikitObject <- R6::R6Class("SplikitObject",
#' permutations for reproducibility without disturbing the global RNG
#' (default: NULL).
#'
#' @return A data.table with event names, pseudo_correlation, null_distribution,
#' null_sd, n_perm_valid, emp_pvalue, and emp_padj, plus the full null draws
#' in attr(result, "null_draws").
#' @return A `splikit_pseudo_correlation_result` with per-event statistics,
#' a long event-by-permutation null distribution, and computation metadata.
getPseudoCorrelation = function(ZDB_matrix, metric = "CoxSnell",
suppress_warnings = TRUE,
permutation_count = 100L,
Expand Down Expand Up @@ -266,7 +265,7 @@ SplikitObject <- R6::R6Class("SplikitObject",

# Warn about NA removal
n_before <- nrow(self$m1)
n_after <- nrow(result)
n_after <- nrow(result$statistics)
if (n_before > n_after) {
n_removed <- n_before - n_after
message("Removed ", n_removed, " event(s) with NA values (",
Expand Down
86 changes: 73 additions & 13 deletions R/general_tools.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@ utils::globalVariables(c("unique_mapped", "i", "j", "x_1", "x_tot", "x_2", "grou
#' caller's global RNG stream is saved and restored, so passing a seed does not
#' perturb downstream randomness. Defaults to `NULL` (use the ambient RNG state).
#'
#' @return A `data.table` with the following columns:
#' @return An S3 object of class `splikit_pseudo_correlation_result` with three
#' components:
#' \describe{
#' \item{statistics}{A per-event `data.table` with the columns described below.}
#' \item{null_distribution}{A long `data.table` containing one row per retained
#' event and permutation, with columns `event`, `permutation`, and
#' `null_pseudo_correlation`. This table is suitable for direct export or pooled
#' descriptive analyses; event-level p-values remain based on each event's own
#' permutation draws.}
#' \item{metadata}{A list recording the permutation settings and null-draw counts.}
#' }
#' The `statistics` component contains:
#' \describe{
#' \item{event}{The event names from `ZDB_matrix` rownames.}
#' \item{pseudo_correlation}{The computed pseudo R² correlation values using the specified metric.}
Expand All @@ -43,8 +54,6 @@ utils::globalVariables(c("unique_mapped", "i", "j", "x_1", "x_tot", "x_2", "grou
#' \item{emp_pvalue}{Two-sided empirical p-value, `(b + 1) / (n_perm_valid + 1)`, where `b` is the number of valid null draws whose absolute value is greater than or equal to the absolute observed correlation.}
#' \item{emp_padj}{Benjamini-Hochberg adjusted `emp_pvalue` across the retained events.}
#' }
#' The full matrix of per-event null draws (events x `permutation_count`) is attached
#' as `attr(result, "null_draws")` for custom downstream analyses.
#'
#' @examples
#' \donttest{
Expand Down Expand Up @@ -83,9 +92,10 @@ utils::globalVariables(c("unique_mapped", "i", "j", "x_1", "x_tot", "x_2", "grou
#' permutation_count = 20,
#' permutation_seed = 1)
#'
#' # Per-event empirical p-values and the full null draws
#' head(pseudo_r_square_sparse[, c("event", "pseudo_correlation", "emp_pvalue")])
#' dim(attr(pseudo_r_square_sparse, "null_draws"))
#' # Per-event empirical p-values and the exportable long null distribution
#' head(pseudo_r_square_sparse$statistics[,
#' c("event", "pseudo_correlation", "emp_pvalue")])
#' dim(pseudo_r_square_sparse$null_distribution)
#'
#' # Example using Nagelkerke R-squared instead of Cox-Snell
#' pseudo_r_square_nagel <- get_pseudo_correlation(ZDB_matrix, m1_inclusion, m2_exclusion,
Expand Down Expand Up @@ -291,15 +301,65 @@ get_pseudo_correlation <- function(ZDB_matrix, m1_inclusion = NULL, m2_exclusion
# Benjamini-Hochberg adjusted empirical p-values across the retained events.
results$emp_padj <- stats::p.adjust(results$emp_pvalue, method = "BH")

# Attach the full per-event null draws for custom downstream analyses.
if (nrow(null_mat) > 0L) {
rownames(null_mat) <- results$event
colnames(null_mat) <- paste0("perm_", seq_len(permutation_count))
}
data.table::setattr(results, "null_draws", null_mat)
# Expose every retained event-by-permutation draw in a long, exportable table.
# R matrices are column-major, so this ordering groups all retained events for
# permutation 1, followed by all retained events for permutation 2, and so on.
null_distribution <- data.table::data.table(
event = rep(results$event, times = permutation_count),
permutation = rep(seq_len(permutation_count), each = nrow(results)),
null_pseudo_correlation = as.vector(null_mat)
)

metadata <- list(
metric = metric,
permutation_count = permutation_count,
permutation_seed = permutation_seed,
n_events_input = nrow(ZDB_matrix),
n_events_retained = nrow(results),
n_null_draws = nrow(null_distribution),
n_null_valid = sum(!is.na(null_distribution$null_pseudo_correlation)),
pooled_null_usage = "descriptive"
)

result <- structure(
list(
statistics = results,
null_distribution = null_distribution,
metadata = metadata
),
class = c("splikit_pseudo_correlation_result", "list")
)

if (verbose) message("Computation completed successfully.")
return(results)
return(result)
}

#' Print a pseudo-correlation result
#'
#' Prints a short preview of the per-event statistics and reports the size of
#' the long null distribution without printing every permutation draw.
#'
#' @param x A `splikit_pseudo_correlation_result` object.
#' @param n Number of statistic rows to preview. Defaults to `6L`.
#' @param ... Additional arguments passed to the `data.table` print method.
#'
#' @return `x`, invisibly.
#' @method print splikit_pseudo_correlation_result
#' @export
print.splikit_pseudo_correlation_result <- function(x, n = 6L, ...) {
if (!is.numeric(n) || length(n) != 1L || is.na(n) || n < 0) {
stop("n must be a single non-negative integer.", call. = FALSE)
}
n <- as.integer(n)

cat("Pseudo-correlation result\n")
cat("Statistics:", nrow(x$statistics), "retained event(s)\n")
print(utils::head(x$statistics, n), ...)
cat(
"Null distribution:", nrow(x$null_distribution), "row(s);",
x$metadata$n_null_valid, "valid draw(s)\n"
)
invisible(x)
}

#' Calculate Row-wise Variance for Dense or Sparse Matrices
Expand Down
80 changes: 69 additions & 11 deletions R/star_solo_processing.R
Original file line number Diff line number Diff line change
Expand Up @@ -1122,8 +1122,10 @@ make_eventdata_plus <- function(eventdata, GTF_file_direction) {
#' Constructs sparse gene expression matrices from one or more directories containing 10X Genomics-style output.
#' The function supports barcode filtering using either an external whitelist or the internally provided filtered barcode file.
#'
#' @param expression_dirs A character vector or list of strings. Each element must be a path to a directory containing the
#' gene expression matrix files: \code{matrix.mtx}, \code{barcodes.tsv}, and \code{features.tsv} (or \code{genes.tsv}).
#' @param expression_dirs A character vector or list of strings. Each element
#' must point to a gene-expression output directory containing \code{raw}
#' and/or \code{filtered} subdirectories with the matrix, barcodes, and
#' features files.
#'
#' @param sample_ids A character vector or list of unique sample identifiers, one for each element in \code{expression_dirs}.
#' These are used to name outputs in the returned list when multiple samples are provided.
Expand All @@ -1136,6 +1138,17 @@ make_eventdata_plus <- function(eventdata, GTF_file_direction) {
#' the function will attempt to use the default filtered barcode list from the input directory.
#' If \code{FALSE}, no internal filtration will be applied unless a whitelist is explicitly provided.
#' @param verbose Logical. If \code{TRUE}, prints progress and informational messages. Default is \code{FALSE}.
#' @param matrix_source Character. Directory below each \code{expression_dirs}
#' entry from which the count matrix, barcodes, and features are read. One of
#' \code{"raw"}, \code{"filtered"}, or \code{"auto"}. The default is
#' \code{"raw"}. The compatibility option \code{"auto"} preserves the
#' historical directory behavior: it uses
#' \code{"filtered"} when \code{use_internal_whitelist = TRUE} and
#' \code{"raw"} otherwise.
#' @param matrix_file Character. Name of the Matrix Market count file inside the
#' selected source directory. The default, \code{"auto"}, uses STARsolo's
#' \code{"UniqueAndMult-EM.mtx"} when available and otherwise falls back to
#' \code{"matrix.mtx"}. Supply a filename explicitly to force that file.
#'
#' @return
#' If a single sample is provided, returns a sparse matrix of class \code{"dgCMatrix"} with genes as rows and barcodes as columns.
Expand All @@ -1150,11 +1163,28 @@ make_eventdata_plus <- function(eventdata, GTF_file_direction) {
#' If neither an external whitelist nor an internal filtered barcode file is available, all barcodes from the
#' raw matrix will be retained.
#'
#' Matrix selection and barcode filtering are independent. For example,
#' \code{matrix_source = "raw"} with \code{use_internal_whitelist = TRUE}
#' reads the raw count matrix and then retains barcodes listed in
#' \code{filtered/barcodes.tsv}. In contrast,
#' \code{matrix_source = "filtered"} reads the already-filtered matrix directly.
#' With the defaults, \code{raw/UniqueAndMult-EM.mtx} is preferred and
#' \code{raw/matrix.mtx} is used as a fallback.
#'
#' @section Dependencies:
#' Requires the \pkg{Matrix} package for sparse matrix handling and potentially \pkg{data.table} for efficient I/O.
#'
#' @export
make_gene_count <- function(expression_dirs, sample_ids, whitelist_barcodes = NULL, use_internal_whitelist = TRUE, verbose = FALSE) {
make_gene_count <- function(expression_dirs, sample_ids, whitelist_barcodes = NULL,
use_internal_whitelist = TRUE, verbose = FALSE,
matrix_source = c("raw", "filtered", "auto"),
matrix_file = "auto") {

matrix_source <- match.arg(matrix_source)
if (!is.character(matrix_file) || length(matrix_file) != 1L ||
is.na(matrix_file) || !nzchar(matrix_file)) {
stop("matrix_file must be a single non-empty filename.", call. = FALSE)
}

# Handle single sample input by converting to lists
if (!is.list(expression_dirs)) expression_dirs <- list(expression_dirs)
Expand All @@ -1172,22 +1202,50 @@ make_gene_count <- function(expression_dirs, sample_ids, whitelist_barcodes = NU

# Helper function to process one sample
process_ex_sample <- function(expression_dir, sample_id, whitelist_barcode) {
# Determine directory (filtered or raw)
data_dir <- if (use_internal_whitelist) paste0(expression_dir, "/filtered") else paste0(expression_dir, "/raw")
# "auto" retains the pre-existing coupling for backward compatibility.
selected_source <- if (identical(matrix_source, "auto")) {
if (use_internal_whitelist) "filtered" else "raw"
} else {
matrix_source
}
data_dir <- file.path(expression_dir, selected_source)

selected_matrix_file <- matrix_file
if (identical(matrix_file, "auto")) {
candidate_files <- c("UniqueAndMult-EM.mtx", "matrix.mtx")
available <- candidate_files[
file.exists(file.path(data_dir, candidate_files))
]
if (length(available) == 0L) {
stop(
"No expression matrix file found for sample ", sample_id,
" in ", data_dir, ". Tried: ",
paste(candidate_files, collapse = ", "),
call. = FALSE
)
}
selected_matrix_file <- available[[1L]]
}

# Define paths
expression_matrix_dir <- paste0(data_dir, "/matrix.mtx")
expression_barcodes_dir <- paste0(data_dir, "/barcodes.tsv")
expression_features_dir <- paste0(data_dir, "/features.tsv")
filtered_barcodes_dir <- paste0(expression_dir, "/filtered/barcodes.tsv") # For barcode filtration
expression_matrix_dir <- file.path(data_dir, selected_matrix_file)
expression_barcodes_dir <- file.path(data_dir, "barcodes.tsv")
expression_features_dir <- file.path(data_dir, "features.tsv")
filtered_barcodes_dir <- file.path(expression_dir, "filtered", "barcodes.tsv")

# Check for required files
if (!file.exists(expression_matrix_dir)) stop("No expression matrix file found for sample: ", sample_id, call. = FALSE)
if (!file.exists(expression_matrix_dir)) {
stop("No expression matrix file found for sample ", sample_id,
": ", expression_matrix_dir, call. = FALSE)
}
if (!file.exists(expression_barcodes_dir)) stop("No barcodes file found for sample: ", sample_id, call. = FALSE)
if (!file.exists(expression_features_dir)) stop("No features file found for sample: ", sample_id, call. = FALSE)

# Read gene expression data
if (verbose) message("|-- Processing gene expression data for sample: ", sample_id)
if (verbose) {
message("|-- Processing gene expression data for sample: ", sample_id,
" (", selected_source, "/", selected_matrix_file, ")")
}
g_mtx <- Matrix::readMM(expression_matrix_dir)
g_brc <- data.table::fread(expression_barcodes_dir, header = FALSE, showProgress = FALSE)$V1
g_feature <- data.table::fread(expression_features_dir, header = FALSE, showProgress = FALSE)
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
[![CRAN status](https://www.r-pkg.org/badges/version/splikit)](https://CRAN.R-project.org/package=splikit)
[![R-CMD-check](https://github.com/csglab/splikit/actions/workflows/R-CMD-check.yml/badge.svg)](https://github.com/csglab/splikit/actions/workflows/R-CMD-check.yml)
[![Documentation](https://img.shields.io/badge/Docs-Learn%20More-blue.svg)](https://csglab.github.io/splikit/)
[![CRAN downloads](https://cranlogs.r-pkg.org/badges/grand-total/splikit)](https://cran.r-project.org/package=splikit)
[![CRAN status](https://www.r-pkg.org/badges/version/splikit)](https://CRAN.R-project.org/package=splikit) [![GitHub version](https://img.shields.io/github/v/release/csglab/splikit?label=GitHub)](https://github.com/csglab/splikit/releases/latest)



## **Requirements**

Expand Down Expand Up @@ -100,4 +102,3 @@ This project is licensed under the MIT License – see the `LICENSE.md` file for

The author is aware of the package’s limitations and potential breakpoints. It was developed under limited knowledge, time, and resources, and is provided with the hope that it will be useful. Feedback and contributions from the community are warmly welcomed. If you encounter any issues or have suggestions, please [open an issue](https://github.com/csglab/splikit/issues/new).


18 changes: 18 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ canonical, CRAN-facing changelog see `NEWS.md`.

---

## 2.3.3 — 2026-07-27

**Summary — Exportable pseudo-correlation null results.**

- Replaced the pseudo-correlation result's `null_draws` matrix attribute with a
structured S3 result containing `statistics`, a long `null_distribution`, and
computation `metadata`.
- The long table has one row per retained event and permutation and preserves
both identifiers for reliable export and reconstruction.
- Per-event empirical p-values and adjusted p-values are unchanged; the pooled
null column is descriptive and is not used as a global event-level null.
- Added independent `matrix_source` and `matrix_file` controls to
`make_gene_count()`, including direct support for raw or filtered matrices
and STARsolo files such as `UniqueAndMult-EM.mtx`. The defaults now read
`raw/UniqueAndMult-EM.mtx` when available and fall back to `raw/matrix.mtx`.

---

## 2.3.2 — 2026-06-09

**Author:** Arsham Mikaeili Namini
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ reference:
desc: Additional analysis utilities
contents:
- get_pseudo_correlation
- print.splikit_pseudo_correlation_result
- get_silhouette_mean

- title: Data
Expand Down
5 changes: 2 additions & 3 deletions man/SplikitObject.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading