Stratified Analysis
Table of contents
- TOC
Stratified analysis runs the GeneCircuitry downstream pipeline (CellOracle + Hotspot + GRN analysis + Reporting) independently for each group in a categorical adata.obs column — typically a cell type or condition label. This allows you to compare GRN structure across cell populations.
Each stratification gets its own output directory, checkpoint files, and HTML report. Stratifications can run sequentially or in parallel.
When to use stratified analysis
Use stratified analysis when you want to:
- Infer separate GRNs for each cell type (e.g. endothelial vs epithelial)
- Compare Hotspot gene modules across conditions (treated vs untreated)
- Identify cell-type-specific transcription factor usage
- Run reproducible per-cluster analyses as part of an atlas project
CLI usage
# Sequential stratified analysis (one cluster at a time)
python -m genecircuitry.pipeline \
--input data.h5ad \
--output results/ \
--cluster-key-stratification cell_type
# Multi-key stratification (e.g. combination of cell_type and condition)
python -m genecircuitry.pipeline \
--input data.h5ad \
--output results/ \
--cluster-key-stratification cell_type,condition
# Filter to specific clusters / subsets (e.g. analyzing only 'clu1' across all conditions)
python -m genecircuitry.pipeline \
--input data.h5ad \
--output results/ \
--cluster-key-stratification cell_type,condition \
--clusters clu1
# Parallel stratified analysis (all clusters at once)
python -m genecircuitry.pipeline \
--input data.h5ad \
--output results/ \
--cluster-key-stratification cell_type \
--parallel \
--n-jobs 4
The --cluster-key-stratification option accepts single columns, comma-separated columns (e.g. cell_type,condition), or lists of columns in adata.obs. When multiple keys are provided, datasets are stratified by combinations (e.g. <cell_type>_<condition>). The --clusters option can filter by composite names (e.g. clu1_Ctrl), individual component clusters (e.g. clu1), or key-value pairs (e.g. cell_type:clu1).
Python API
from genecircuitry.pipeline.controller import PipelineController
from argparse import Namespace
from datetime import datetime
args = Namespace(
input="data.h5ad",
output="results/",
cluster_key_stratification="cell_type", # obs column
parallel=True,
n_jobs=4,
seed=42,
)
controller = PipelineController(args, start_time=datetime.now())
# Run preprocessing and stratification first
controller.run_step_load()
controller.run_step_preprocessing()
controller.run_step_stratification()
print(f"Stratifications: {controller.adata_stratification_list}")
# Sequential
controller.run_stratified_pipeline_sequential()
# Or parallel
controller.run_stratified_pipeline_parallel(n_jobs=4)
Inspecting stratification results
# After run_step_stratification(), the controller holds:
controller.adata_list # list of per-cluster AnnData objects
controller.adata_stratification_list # matching cluster names
# Process only specific clusters
for adata_sub, name in zip(controller.adata_list, controller.adata_stratification_list):
if name in ["T_cells", "NK_cells"]:
controller.process_single_stratification(adata_sub, name)
Output structure
Each cluster gets its own subdirectory under <output>/stratified_analysis/:
results/
└── stratified_analysis/
├── T_cells/
│ ├── report.html
│ ├── clustered_adata.h5ad
│ ├── logs/
│ │ ├── pipeline.log
│ │ ├── error.log
│ │ └── *.checkpoint
│ ├── figures/
│ │ ├── grn/
│ │ └── hotspot/
│ ├── celloracle/
│ │ ├── grn_merged_scores.csv
│ │ └── grn_filtered_links.pkl
│ └── hotspot/
│ ├── autocorrelation_results.csv
│ └── gene_modules.csv
├── NK_cells/
│ └── ...
└── B_cells/
└── ...
Checkpoint files are per-stratification — if one cluster fails, re-running the pipeline resumes from where it left off for that cluster only.
Parallel vs sequential
| Aspect | Parallel (--parallel) |
Sequential (default) |
|---|---|---|
| Speed | Near-linear speedup with --n-jobs |
One cluster finishes before the next starts |
| Memory | n_jobs × per-cluster data size |
1 × per-cluster data size |
| Failure isolation | A failed cluster does not block others | Failure in one cluster stops the sequence |
| Log output | Interleaved (prefixed by cluster name) | Sequential, easy to follow |
| Best for | Many clusters, ample RAM | Few clusters or memory-constrained machines |
Each parallel worker receives a full copy of the AnnData for its cluster. For large datasets, start with
--n-jobs 2and monitor memory usage before scaling up.
Configuration
Stratified analysis respects all global config parameters. Per-cluster overrides can be applied before calling process_single_stratification():
from genecircuitry import config
# Apply stricter QC for a small cluster
config.update_config(QC_MIN_CELLS=5)
controller.process_single_stratification(adata_sub, "Rare_population")
# Reset to default
config.update_config(QC_MIN_CELLS=3)
Troubleshooting
Out-of-memory with --parallel: reduce --n-jobs or run without --parallel.
Some clusters produce no GRN links: the cluster may be too small for CellOracle regression. Check cluster sizes; cells under ~100 are unlikely to yield reliable networks.
Resume after partial failure: delete the failed cluster’s checkpoint files and re-run. Successful clusters are skipped:
rm -rf results/stratified_analysis/Rare_population/logs/*.checkpoint
python -m genecircuitry.pipeline --input data.h5ad --output results/ \
--cluster-key-stratification cell_type