Core Workflow#
Run active_score (default parameters)#
adata_res, significant, all_results = scat.active_score(
adata_input=adata,
groupby="condition",
target_group="Disease",
reference_group="Control",
show_plot=True, # shows a comet plot for quick visual check
)
This computes differential expression, reference-group gamma excess for the
unspliced layer, optional Huber bias correction on gene length and intron
number, a composite active score, and stores diagnostics in
adata_res.uns["scatrans"]["diagnostics"].
Common basic switches: pseudobulk and DE test method#
These are standard options available for most analyses.
Pseudobulk mode (use when you have multiple biological replicates per condition):
adata_res, significant, all_results = scat.active_score(
adata_input=adata,
groupby="condition",
target_group="Disease",
reference_group="Control",
use_pseudobulk=True,
sample_col="sample", # column identifying biological samples/individuals
pseudobulk_de_backend="pydeseq2", # or "scanpy"
min_cells=5,
min_counts=100,
show_plot=True,
)
Requires
sample_col.pseudobulk_de_backend="pydeseq2"uses the count-based DESeq2 model (install withpip install "scatrans[pseudobulk]").pseudobulk_de_backend="scanpy"+de_method="wilcoxon"(or"t-test_overestim_var") uses scanpy’s rank_genes_groups on the aggregated data.
Switching the DE statistical test (works for both single-cell and pseudobulk):
# Use Wilcoxon rank-sum test instead of the default t-test
adata_res, significant, all_results = scat.active_score(
...,
de_method="wilcoxon", # any method supported by scanpy.tl.rank_genes_groups
)
When using use_pseudobulk=True + pseudobulk_de_backend="scanpy", the
de_method you choose (including "wilcoxon") will be used for the
pseudobulk DE step.
These choices are recorded in adata_res.uns["scatrans"] (de_method,
pseudobulk_de_backend, use_pseudobulk).
The filter_active_genes helper has a preset="pseudobulk" that applies
more lenient default thresholds suitable after aggregation.
Choosing a DE backend (decision guide)#
Your design |
Recommended backend |
Caveats |
|---|---|---|
Exploratory / default |
scanpy |
Fast; standard pseudoreplication limits |
≥2 biological replicates per group, aggregated counts |
|
Requires raw counts ( |
Few pseudobulk samples, no DESeq2 |
|
Non-parametric on aggregated profiles |
Cell-level data + true sample replicates |
|
Lightweight LMM (log1p); check |
Method-of-moments cell-level DE |
|
Raw integer counts required; compare |
Always run recommend_workflow(...) first; inspect
adata.uns["scatrans"]["diagnostics"] (bias, gamma, permutation
disabled_reason) before publication claims.
Gene filtering with filter_active_genes (core output tool)#
The internal significant list is strict. Users typically filter the full
table returned in all_results with filter_active_genes.
# Start permissive, then tighten based on your data
candidates = scat.filter_active_genes(
all_results,
active_score_cutoff=30,
unspliced_excess_residual_cutoff=0.5,
unspliced_excess_fdr_cutoff=0.05,
logfc_cutoff=0.3,
pval_cutoff=0.05,
)
# Or use presets that choose reasonable defaults for common analysis styles
candidates = scat.filter_active_genes(all_results, preset="heuristic")
# Reproduce the built-in `significant` list exactly (requires use_permutation=True upstream)
builtin_again = scat.filter_active_genes(all_results, preset="significant")
assert builtin_again.index.tolist() == significant.index.tolist()
# Advanced usage
mask = scat.filter_active_genes(all_results, return_mask=True) # boolean Series
filtered_inplace = scat.filter_active_genes(all_results, preset="heuristic", inplace=True)
# or preset="pseudobulk" after aggregation, or preset="permissive"
preset="significant" (aliases: "builtin", "active_score_significant")
replays the built-in significant mask from active_score using metadata in
all_results.attrs["scatrans_filter_context"]. It requires
use_permutation=True on the upstream run. When permutation FDR was
disabled (e.g. too few pseudobulk shuffles), preset="heuristic" is often a
better exploratory fallback than preset="significant".
For pure differential_expression() results you can also select
downregulated genes:
down_cands = scat.filter_active_genes(de_results, pval_cutoff=0.05, logfc_cutoff=0.3, logfc_direction="down")
both = scat.filter_active_genes(de_results, pval_cutoff=0.05, logfc_cutoff=0.3, logfc_direction="both")
The helper safely ignores filters for columns that do not exist (e.g.
unspliced_excess_fdr when you did not use use_permutation). Legacy column
names velocity_residual / velocity_delta_raw remain in adata.var as
aliases.
diagnose_design#
diagnose_design analyzes experimental design (cell counts, replicate
numbers, global unspliced fraction) and returns warnings and
recommendations. It is called automatically inside active_score when
sample_col or use_pseudobulk=True.
import scanpy as sc
import scatrans as scat
adata = sc.read_h5ad("your_velocity_data.h5ad")
diag = scat.diagnose_design(
adata,
groupby="condition",
target_group="Disease",
reference_group="Control",
sample_col="sample" # required for pseudobulk and mixed-model paths when replicates exist
)
print("Warnings:")
for w in diag["warnings"]:
print(" -", w)
print("\nRecommendations:")
for r in diag["recommendations"]:
print(" -", r)
print("\nSuggested preset for filter_active_genes:", diag.get("suggested_preset"))
What it returns, a dictionary containing:
n_cells_target,n_cells_referencen_samples_target,n_samples_reference(whensample_colis provided)unspliced_global_fractionwarnings: list of strings (e.g. low power warnings)recommendations: list of stringssuggested_preset:"heuristic","pseudobulk", orNone
diagnose_design is automatically called inside active_score(...)
whenever you pass sample_col or set use_pseudobulk=True. You will see its
output in the log.
Layer names#
The package auto-detects mature/nascent (kb_python) and remaps them
internally. You can also pass spliced_layer=... and unspliced_layer=...
explicitly.