Pancreas downstream analysis after DFFP scoring

This notebook is a focused guide to biological analyses performed after the supervised topology, ordering, velocity graph, anchor diagnostics, and Discounted Future-Fate Propagation (DFFP) model have been accepted.

It distinguishes three questions:

  1. Which genes are expressed along the fitted trajectory?

  2. Which genes are associated with variation in a DFFP outcome?

  3. Which genes mark annotated terminal identities versus the root?

These are descriptive or associative analyses. They do not by themselves establish causal lineage drivers.

Execution provenance. The displayed outputs were generated with scCS 0.8.0.dev33. Version 0.8.0.dev34 changes documentation and tutorial explanation only; the scientific calculations are unchanged.

1. Installation, versions, and reproducibility

[1]:
from __future__ import annotations
import warnings

from pathlib import Path
import importlib.metadata as importlib_metadata
import platform
import sys

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scanpy as sc
import scvelo as scv
from scipy.spatial.distance import jensenshannon

import scCS

SEED = 20260714
N_JOBS = 1
sc.settings.verbosity = 2
scv.settings.verbosity = 2
sc.set_figure_params(dpi=100, facecolor="white")

print("Python", sys.version.split()[0])
print("Platform", platform.platform())
print("scCS", scCS.__version__)
print("Scanpy", importlib_metadata.version("scanpy"))
print("scVelo", scv.__version__)

OUTPUT_DIR = Path("tutorial_outputs/pancreas_downstream")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

# Hide known warnings emitted by optional upstream dependencies. These do not
# change the scCS calculation and would otherwise distract from the tutorial.
warnings.filterwarnings(
    "ignore",
    message=r"This process .* is multi-threaded, use of fork\(\) may lead to deadlocks.*",
    category=DeprecationWarning,
)
warnings.filterwarnings(
    "ignore",
    category=DeprecationWarning,
    module=r"cellrank\..*",
)

Python 3.12.13
Platform Linux-6.6.114.1-microsoft-standard-WSL2-x86_64-with-glibc2.39
scCS 0.8.0.dev33
Scanpy 1.11.5
scVelo 0.3.4

2. Load pancreas and fit dynamical RNA velocity

Neighbors, moments, kinetic fitting, velocity, and the velocity graph are computed in the original PCA/expression manifold. The supervised star is not used to create neighborhoods or refit velocity.

This tutorial deliberately starts from the public dataset and recomputes the velocity model. No pre-existing scCS cache is assumed. Dynamical fitting can take several minutes, but showing the complete preparation makes every analysis step reproducible for a first-time user.

[2]:
def clear_velocity_outputs(adata, *, clear_dynamics=False):
    """Remove stale velocity fields before fitting a requested model."""
    for key in ("velocity", "velocity_u", "velocity_variance"):
        adata.layers.pop(key, None)
    for key in ("velocity_graph", "velocity_graph_neg", "velocity_params"):
        adata.uns.pop(key, None)
    for key in (
        "velocity_self_transition",
        "root_cells",
        "end_points",
        "velocity_pseudotime",
        "latent_time",
    ):
        if key in adata.obs:
            del adata.obs[key]
    if clear_dynamics:
        for key in list(adata.var.columns):
            if str(key).startswith("fit_"):
                del adata.var[key]
        for key in list(adata.layers):
            if str(key).startswith("fit_"):
                del adata.layers[key]
        adata.uns.pop("recover_dynamics", None)


def ensure_pca_neighbors_moments(
    adata,
    *,
    n_pcs=30,
    n_neighbors=30,
    preserve_existing_neighbors=False,
):
    """Create the unbiased PCA neighbor graph used by RNA velocity."""
    if "X_pca" not in adata.obsm:
        n_comps = min(n_pcs, adata.n_obs - 1, adata.n_vars - 1)
        sc.pp.pca(adata, n_comps=n_comps)
    if "neighbors" not in adata.uns or not preserve_existing_neighbors:
        sc.pp.neighbors(
            adata,
            n_neighbors=min(n_neighbors, adata.n_obs - 1),
            n_pcs=min(n_pcs, adata.obsm["X_pca"].shape[1]),
            use_rep="X_pca",
            random_state=SEED,
        )
    if not {"Ms", "Mu"}.issubset(adata.layers):
        scv.pp.moments(adata, n_neighbors=None, n_pcs=None)
    if "X_umap" not in adata.obsm:
        sc.tl.umap(adata, random_state=SEED)


def resolve_present_genes(adata, candidates):
    """Resolve gene symbols case-insensitively and report missing markers."""
    lookup = {str(gene).lower(): str(gene) for gene in adata.var_names}
    resolved, missing = [], []
    for candidate in candidates:
        match = lookup.get(str(candidate).lower())
        if match is None:
            missing.append(str(candidate))
        elif match not in resolved:
            resolved.append(match)
    return resolved, missing


def row_js(left, right):
    """Row-wise Jensen-Shannon divergence with base-2 logarithms."""
    return np.asarray(
        [jensenshannon(a, b, base=2.0) ** 2 for a, b in zip(left, right)],
        dtype=float,
    )


def ordering_thirds(values):
    """Return stable early/middle/late labels for a continuous coordinate."""
    series = pd.Series(np.asarray(values, dtype=float))
    ranked = series.rank(method="first")
    return (
        pd.qcut(
            ranked,
            q=3,
            labels=["early", "middle", "late"],
            duplicates="drop",
        )
        .astype(str)
        .to_numpy()
    )
[3]:
RECOVER_DYNAMICS_MAX_ITER = 20

# Load the public scVelo pancreas dataset.
adata = scv.datasets.pancreas()
adata.var_names_make_unique()

# Fit RNA velocity in the original expression/PCA manifold.
scv.pp.filter_and_normalize(adata, min_shared_counts=20)
ensure_pca_neighbors_moments(
    adata,
    n_pcs=30,
    n_neighbors=30,
    preserve_existing_neighbors=False,
)
clear_velocity_outputs(adata, clear_dynamics=True)

# Dynamical velocity is used because the pancreas dataset contains several
# kinetic regimes and non-monotonic terminal trajectories.
scv.tl.recover_dynamics(
    adata,
    max_iter=RECOVER_DYNAMICS_MAX_ITER,
    n_jobs=N_JOBS,
)
scv.tl.velocity(adata, mode="dynamical")
scv.tl.velocity_graph(adata, n_jobs=N_JOBS)
scv.tl.terminal_states(adata)
scv.tl.latent_time(
    adata,
    vkey="velocity",
    root_key="root_cells",
    end_key="end_points",
)
scv.tl.velocity_pseudotime(adata, vkey="velocity")

print(adata)
display(adata.obs["clusters"].astype(str).value_counts().to_frame("n_cells"))
Filtered out 20801 genes that are detected 20 counts (shared).
Normalized count data: X, spliced, unspliced.
computing neighbors
    finished (0:00:13)
computing moments based on connectivities
    finished (0:00:03)
recovering dynamics (using 1/24 cores)
/home/emil/miniforge3/envs/lab-py312/lib/python3.12/multiprocessing/popen_fork.py:66: DeprecationWarning: This process (pid=93037) is multi-threaded, use of fork() may lead to deadlocks in the child.
  self.pid = os.fork()
    finished (0:26:34)
computing velocities
    finished (0:00:09)
computing velocity graph (using 1/24 cores)
/home/emil/miniforge3/envs/lab-py312/lib/python3.12/multiprocessing/popen_fork.py:66: DeprecationWarning: This process (pid=93037) is multi-threaded, use of fork() may lead to deadlocks in the child.
  self.pid = os.fork()
    finished (0:00:14)
computing terminal states
    identified 2 regions of root cells and 1 region of end points .
    finished (0:00:00)
computing latent time using root_cells, end_points as prior
    finished (0:00:03)
AnnData object with n_obs × n_vars = 3696 × 7197
    obs: 'clusters_coarse', 'clusters', 'S_score', 'G2M_score', 'initial_size_unspliced', 'initial_size_spliced', 'initial_size', 'n_counts', 'velocity_self_transition', 'root_cells', 'end_points', 'velocity_pseudotime', 'latent_time'
    var: 'highly_variable_genes', 'gene_count_corr', 'fit_r2', 'fit_alpha', 'fit_beta', 'fit_gamma', 'fit_t_', 'fit_scaling', 'fit_std_u', 'fit_std_s', 'fit_likelihood', 'fit_u0', 'fit_s0', 'fit_pval_steady', 'fit_steady_u', 'fit_steady_s', 'fit_variance', 'fit_alignment_scaling', 'velocity_genes'
    uns: 'clusters_coarse_colors', 'clusters_colors', 'day_colors', 'neighbors', 'pca', 'recover_dynamics', 'velocity_params', 'velocity_graph', 'velocity_graph_neg'
    obsm: 'X_pca', 'X_umap'
    varm: 'loss'
    layers: 'spliced', 'unspliced', 'Ms', 'Mu', 'fit_t', 'fit_tau', 'fit_tau_', 'velocity', 'velocity_u'
    obsp: 'distances', 'connectivities'
n_cells
clusters
Ductal 916
Ngn3 high EP 642
Pre-endocrine 592
Beta 591
Alpha 481
Ngn3 low EP 262
Epsilon 142
Delta 70

3. Inspect native annotations, ordering candidates, and the velocity field

[4]:
selected_labels = [
    "Ngn3 high EP",
    "Pre-endocrine",
    "Alpha",
    "Beta",
    "Delta",
    "Epsilon",
]
selected_mask_full = adata.obs["clusters"].astype(str).isin(selected_labels)
ordering_columns = [key for key in ("latent_time", "velocity_pseudotime") if key in adata.obs]
display(
    adata.obs.loc[selected_mask_full, ["clusters", *ordering_columns]]
    .groupby("clusters", observed=True)
    .agg(["count", "median", "min", "max"])
)

scv.pl.velocity_embedding_stream(
    adata,
    basis="umap",
    color="clusters",
    legend_loc="right margin",
    title="Native pancreas RNA-velocity field",
)
latent_time velocity_pseudotime
count median min max count median min max
clusters
Ngn3 high EP 642 0.538150 0.197135 0.668318 642 0.439079 0.116273 0.632564
Pre-endocrine 592 0.719086 0.544826 0.767974 592 0.814839 0.497939 0.914136
Beta 591 0.728306 0.690814 1.000000 591 0.883505 0.835416 1.000000
Alpha 481 0.745250 0.704222 0.797031 481 0.909996 0.830502 0.920302
Delta 70 0.785738 0.693870 0.841107 70 0.918165 0.904303 0.926678
Epsilon 142 0.740098 0.697535 0.758869 142 0.908112 0.863491 0.913667
computing velocity embedding
    finished (0:00:00)
../_images/tutorials_10_pancreas_downstream_analysis_8_2.png

4. Define the supervised furcation and run preflight diagnostics

The incoming path contains both Ngn3 high EP and Pre-endocrine because the earlier progenitor state is required to represent the complete approach to the endocrine furcation. Dynamical latent time is used for the scientific ordering.

[5]:
ROOT = ("Ngn3 high EP", "Pre-endocrine")
FATES = ["Alpha", "Beta", "Delta", "Epsilon"]
ORDERING_KEY = "latent_time"

scorer = scCS.SingleScorer(
    adata,
    root=ROOT,
    branches=FATES,
    obs_key="clusters",
)
preflight = scorer.preflight(ordering_metric=ORDERING_KEY, check_velocity=True)
preflight.display()
preflight.raise_for_errors()

scorer.build_embedding(
    ordering_metric=ORDERING_KEY,
    write_to_adata=True,
)
level code message value
0 info furcation_valid Root and terminal annotations are valid. 2518.0
1 info root_cells Root population contains 1234 cells. 1234.0
2 info terminal_Alpha Terminal 'Alpha' contains 481 cells. 481.0
3 info terminal_Beta Terminal 'Beta' contains 591 cells. 591.0
4 info terminal_Delta Terminal 'Delta' contains 70 cells. 70.0
5 info terminal_Epsilon Terminal 'Epsilon' contains 142 cells. 142.0
6 info ordering_valid Ordering metric is finite and non-constant amo... 1.0
7 info ordering_resolution Root ordering has 1234 unique values across 12... 1.0
8 info velocity_available Velocity information is available. NaN
[scCS] Scientific star built for 2518 cells in 4 dimensions.
       Root radial clipping: 0.050 low / 0.050 high
       Terminal scientific coordinates: fixed equal-radius simplex vertices (radius=1.000).
[5]:
<scCS.single.SingleScorer at 0x7e0efe20a720>

5. Fit the primary discounted future-fate model

[6]:
FUTURE_OPTIONS = {
    "effective_horizon": 64,
    "anchor_quantile": 0.90,
    "min_anchor_cells": 10,
    "progression_scale": "rank",
    "committed_reach_threshold": 0.25,
    "committed_specificity_threshold": 0.25,
}

scorer.fit(
    scoring_mode="future_fate",
    future_fate_options=FUTURE_OPTIONS,
)
result = scorer.score(write_to_adata=True)

display(pd.Series(result.summary(), name="value").to_frame())
anchor_diagnostics = result.anchor_diagnostics_frame()
display(anchor_diagnostics)
anchor_diagnostics.to_csv(OUTPUT_DIR / "anchor_diagnostics.csv", index=False)
scCS FutureFateScoreResult
  Furcation: root -> ['Alpha', 'Beta', 'Delta', 'Epsilon']
  Effective horizon: 64 (gamma=0.984615)
  Cells: 2518 selected; 1234 root
  Root affinity coverage: 1.000
  Root mean future-fate reach: 0.624
  Root mean future-fate entropy: 0.597
  Root mean future-fate specificity: 0.403
  Root mean reach-supported specificity: 0.254
  Root mean unresolved probability: 0.376
  Root mean signed progression: 0.041
  Root future-fate composition: Alpha=0.291, Beta=0.630, Delta=0.000, Epsilon=0.079
  Solver: direct; iterations=1; residual=1.927e-15
scCS FutureFateScoreResult
  Furcation: root -> ['Alpha', 'Beta', 'Delta', 'Epsilon']
  Effective horizon: 64 (gamma=0.984615)
  Cells: 2518 selected; 1234 root
  Root affinity coverage: 1.000
  Root mean future-fate reach: 0.624
  Root mean future-fate entropy: 0.597
  Root mean future-fate specificity: 0.403
  Root mean reach-supported specificity: 0.254
  Root mean unresolved probability: 0.376
  Root mean signed progression: 0.041
  Root future-fate composition: Alpha=0.291, Beta=0.630, Delta=0.000, Epsilon=0.079
  Solver: direct; iterations=1; residual=1.927e-15
value
0 scCS FutureFateScoreResult\n Furcation: root ...
fate n_anchors mean_transition_to_same_fate_population mean_transition_to_root mean_transition_to_other_selected_fates mean_transition_outside_selected_path mean_signed_progression forward_fraction
0 Alpha 49 0.998604 0.000101 0.001295 0.0 -0.039809 0.000000
1 Beta 60 1.000000 0.000000 0.000000 0.0 -0.009623 0.033333
2 Delta 10 0.857701 0.000722 0.141577 0.0 -0.042991 0.000000
3 Epsilon 15 0.686047 0.198733 0.115220 0.0 -0.098604 0.000000

6. Downstream-analysis checklist

Before screening genes, verify that the primary result is numerically stable and biologically interpretable. The table below records the key DFFP quantities and anchor diagnostics that should accompany every downstream analysis.

[7]:
summary_table = pd.DataFrame(
    {
        "cell_id": result.cell_ids,
        "population": np.where(
            result.root_mask,
            "root",
            result.embedding.terminal_names.astype(str),
        ),
        "DFR": result.discounted_fate_reach,
        "FFS": result.future_fate_specificity,
        "RC": result.resolved_commitment,
        "UFP": result.unresolved_future_probability,
        "SOF": result.signed_ordering_flux,
    }
)
for fate_index, fate in enumerate(result.fate_names):
    summary_table[f"CFA:{fate}"] = result.conditional_fate_affinity[:, fate_index]
display(summary_table.describe(include="all").T)
display(result.anchor_diagnostics_frame())
count unique top freq mean std min 25% 50% 75% max
cell_id 2518 2518 AAACCTGAGAGGGATA 1 NaN NaN NaN NaN NaN NaN NaN
population 2518 5 root 1234 NaN NaN NaN NaN NaN NaN NaN
DFR 2518.0 NaN NaN NaN 0.681311 0.120331 0.207807 0.610795 0.646416 0.757218 1.0
FFS 2518.0 NaN NaN NaN 0.573098 0.258765 0.202401 0.388979 0.402197 0.795657 1.0
RC 2518.0 NaN NaN NaN 0.415288 0.260153 0.121476 0.235194 0.258604 0.575375 1.0
UFP 2518.0 NaN NaN NaN 0.318689 0.120331 0.0 0.242782 0.353584 0.389205 0.792193
SOF 2518.0 NaN NaN NaN 0.040804 0.109793 -0.333231 -0.00598 0.023918 0.083306 0.516719
CFA:Alpha 2518.0 NaN NaN NaN 0.302031 0.267297 0.0 0.088344 0.305749 0.311221 1.0
CFA:Beta 2518.0 NaN NaN NaN 0.591167 0.309288 0.0 0.41709 0.622889 0.82202 1.0
CFA:Delta 2518.0 NaN NaN NaN 0.005288 0.063794 0.0 0.000044 0.000214 0.000223 1.0
CFA:Epsilon 2518.0 NaN NaN NaN 0.101514 0.175265 0.0 0.015142 0.070764 0.072405 1.0
fate n_anchors mean_transition_to_same_fate_population mean_transition_to_root mean_transition_to_other_selected_fates mean_transition_outside_selected_path mean_signed_progression forward_fraction
0 Alpha 49 0.998604 0.000101 0.001295 0.0 -0.039809 0.000000
1 Beta 60 1.000000 0.000000 0.000000 0.0 -0.009623 0.033333
2 Delta 10 0.857701 0.000722 0.141577 0.0 -0.042991 0.000000
3 Epsilon 15 0.686047 0.198733 0.115220 0.0 -0.098604 0.000000

7. Resolve and visualize known biological markers

Gene symbols are resolved case-insensitively because public datasets can use mouse-style capitalization. Missing genes are reported rather than silently ignored.

[8]:
marker_candidates = {
    "progenitor": ["Neurog3", "Neurod1", "Insm1", "Pax4"],
    "Alpha": ["Gcg", "Mafb", "Arx"],
    "Beta": ["Ins1", "Ins2", "Iapp", "Pdx1", "Nkx6-1", "Mafa"],
    "Delta": ["Sst", "Hhex"],
    "Epsilon": ["Ghrl"],
    "secretory": ["Chga", "Chgb", "Pcsk1", "Pcsk2", "Scg5"],
}
resolved_groups = {}
missing_groups = {}
for group, candidates in marker_candidates.items():
    resolved, missing = resolve_present_genes(adata, candidates)
    resolved_groups[group] = resolved
    missing_groups[group] = missing

display(
    pd.DataFrame(
        {
            "resolved": {key: ", ".join(value) for key, value in resolved_groups.items()},
            "missing": {key: ", ".join(value) for key, value in missing_groups.items()},
        }
    )
)
markers = list(dict.fromkeys(gene for genes in resolved_groups.values() for gene in genes))
resolved missing
progenitor Neurog3, Neurod1, Pax4 Insm1
Alpha Gcg, Arx Mafb
Beta Ins1, Ins2, Iapp, Pdx1, Nkx6-1 Mafa
Delta Sst, Hhex
Epsilon Ghrl
secretory Chga, Chgb, Pcsk1, Pcsk2, Scg5
[9]:
marker_figure = scorer.plot_gene_expression_star(
    markers,
    result,
    ncols=4,
    shared_scale=False,
    terminal_layout="ordering",
)
marker_figure.savefig(
    OUTPUT_DIR / "downstream_marker_expression_stars.png",
    dpi=200,
    bbox_inches="tight",
)
plt.show()
../_images/tutorials_10_pancreas_downstream_analysis_17_0.png

9. Candidate commitment-associated genes

The public pancreas dataset does not provide independent biological replicates for this tutorial. Therefore, cell_exploratory is used only for candidate generation and visualization. The resulting p-values must not be presented as replicate-level evidence.

[11]:
association = scorer.get_commitment_associated_genes(
    outcome="future_fate_affinity",
    fate_names=FATES,
    population="root",
    inference_unit="cell_exploratory",
    pseudotime_key=ORDERING_KEY,
    min_cells=100,
    min_feature_cells=10,
    min_feature_fraction=0.01,
    n_top_genes=50,
    verbose=True,
)
for fate in FATES:
    print("\n", fate)
    display(association.top(fate, n=20))
association.export(OUTPUT_DIR, prefix="pancreas_CFA_association")

[scCS] Candidate commitment-associated genes: Alpha
        inference_unit=cell_exploratory; matrix=X; n_units=1234; tested_genes=6946
 rank     gene   effect   pvalue_adj  significant
    1   Tmsb4x 0.275744 6.866232e-20         True
    2  Neurog3 0.249886 5.188467e-16         True
    3     Ppib 0.242993 3.842847e-15         True
    4      Mdk 0.242233 4.414893e-15         True
    5     Rps9 0.239684 9.050625e-15         True
    6      Cck 0.234886 2.988312e-14         True
    7     Btg2 0.233772 3.955025e-14         True
    8   Btbd17 0.233522 4.020746e-14         True
    9     Cd63 0.229856 1.163901e-13         True
   10  Igfbpl1 0.225002 4.719493e-13         True
   11    Rps11 0.220966 1.330870e-12         True
   12  Gadd45a 0.218992 2.158346e-12         True
   13     Pax4 0.214899 6.643259e-12         True
   14     Cdk4 0.208052 3.532527e-11         True
   15     Dbn1 0.203733 1.012706e-10         True
   16  Smarcd2 0.202143 1.460725e-10         True
   17     Selm 0.200973 1.935800e-10         True
   18     Tecr 0.199264 2.884216e-10         True
   19    Cldn6 0.197887 3.750920e-10         True
   20     Rps5 0.195899 6.087173e-10         True
   21     Rps8 0.193160 1.193368e-09         True
   22    Rps19 0.192583 1.350683e-09         True
   23    Rpl32 0.192380 1.387809e-09         True
   24   Rpl18a 0.192309 1.387809e-09         True
   25   Ifitm2 0.185488 6.752053e-09         True
   26   Rpl37a 0.183151 1.121243e-08         True
   27     Npc2 0.182841 1.186314e-08         True
   28    Rps23 0.181245 1.674072e-08         True
   29   Hpcal1 0.178260 3.310284e-08         True
   30    Cotl1 0.176209 5.144636e-08         True
   31 Ppp1r14a 0.173744 8.875108e-08         True
   32  Sult2b1 0.173058 1.020068e-07         True
   33 BC023829 0.171343 1.453241e-07         True
   34    Epb42 0.170686 1.657214e-07         True
   35      Hn1 0.169934 1.928961e-07         True
   36    Rpl13 0.169175 2.247872e-07         True
   37     Qdpr 0.166513 3.907411e-07         True
   38     Krt7 0.166071 4.240513e-07         True
   39    Rpl28 0.164758 5.547501e-07         True
   40   Rpl10a 0.163476 7.008623e-07         True
   41     Mfng 0.162516 8.374074e-07         True
   42     Gpx2 0.161848 9.518700e-07         True
   43    Tubb3 0.158004 2.040619e-06         True
   44   Eef1a1 0.157407 2.252172e-06         True
   45     Miat 0.157038 2.400241e-06         True
   46     Krt8 0.156340 2.735154e-06         True
   47  Gdap1l1 0.155206 3.327046e-06         True
   48   Cdc14b 0.153799 4.365228e-06         True
   49    Gspt1 0.153310 4.759460e-06         True
   50     Tpm4 0.153258 4.759460e-06         True

[scCS] Candidate commitment-associated genes: Beta
        inference_unit=cell_exploratory; matrix=X; n_units=1234; tested_genes=6946
 rank          gene   effect   pvalue_adj  significant
    1         Pcsk2 0.240656 3.654280e-14         True
    2          Rbp4 0.224756 3.213982e-12         True
    3        Npepl1 0.218365 1.550325e-11         True
    4          Pdx1 0.216645 2.026609e-11         True
    5          Mnx1 0.215440 2.376480e-11         True
    6          Pim2 0.195233 4.638021e-09         True
    7 1700086L19Rik 0.187750 2.629641e-08         True
    8           Pyy 0.183906 5.927710e-08         True
    9         Fxyd6 0.180545 1.184303e-07         True
   10          Chgb 0.178527 1.725473e-07         True
   11          Gnas 0.176425 2.570263e-07         True
   12       S100a11 0.175677 2.816025e-07         True
   13        Pcsk1n 0.170399 8.578265e-07         True
   14         Gng12 0.168234 1.289587e-06         True
   15        Elmod1 0.163479 3.172835e-06         True
   16         Prps1 0.157899 9.108764e-06         True
   17         Anxa4 0.156565 1.089599e-05         True
   18        Sec61b 0.155863 1.191686e-05         True
   19         Syt13 0.152620 2.184586e-05         True
   20       Fam159b 0.152054 2.342385e-05         True
   21        Cryba2 0.149606 3.635226e-05         True
   22          Xist 0.144618 8.142971e-05         True
   23        Klhl32 0.139189 2.147447e-04         True
   24        Papss2 0.138580 2.320391e-04         True
   25      Slc16a12 0.138162 2.350279e-04         True
   26         Flrt1 0.137802 2.434007e-04         True
   27           Fev 0.131738 6.324788e-04         True
   28         Ndc80 0.129850 8.094011e-04         True
   29        Nkx6-1 0.128587 9.539775e-04         True
   30       Hdgfrp3 0.127068 1.172122e-03         True
   31           Cpe 0.125957 1.354105e-03         True
   32        Dynll1 0.124705 1.594843e-03         True
   33         Samd5 0.124436 1.601033e-03         True
   34           Vdr 0.123962 1.633212e-03         True
   35         Plac8 0.123503 1.666802e-03         True
   36          Dlk1 0.122917 1.770042e-03         True
   37          Chga 0.121720 2.108770e-03         True
   38          Frzb 0.121537 2.135643e-03         True
   39         Aurka 0.121349 2.153212e-03         True
   40         Rap1b 0.121283 2.153212e-03         True
   41       Nkiras2 0.120491 2.403087e-03         True
   42        Rab22a 0.119050 2.836916e-03         True
   43         Hmgcr 0.118190 3.196423e-03         True
   44      Arhgef16 0.117264 3.585029e-03         True
   45         Uhrf2 0.117085 3.626002e-03         True
   46          Blnk 0.117009 3.626002e-03         True
   47       Cox7a2l 0.115653 4.401869e-03         True
   48         Kifc1 0.115057 4.753634e-03         True
   49          Ece1 0.114410 5.147539e-03         True
   50        Kcnmb2 0.113165 5.996795e-03         True

[scCS] Candidate commitment-associated genes: Delta
        inference_unit=cell_exploratory; matrix=X; n_units=1234; tested_genes=6946
 rank      gene   effect   pvalue_adj  significant
    1    Cdkn1a 0.341412 3.418676e-31         True
    2    Mboat4 0.338549 6.680157e-31         True
    3      Isl1 0.297690 2.019228e-23         True
    4       Arx 0.297191 2.019228e-23         True
    5     Cd200 0.287990 6.507125e-22         True
    6      Peg3 0.269294 6.235222e-19         True
    7    Tm4sf4 0.249135 5.823501e-16         True
    8     Peg10 0.233154 6.379363e-14         True
    9 Trp53cor1 0.228891 2.157446e-13         True
   10     Glud1 0.214502 1.092584e-11         True
   11      Muc1 0.208587 4.785118e-11         True
   12  Trp53i13 0.206268 8.553140e-11         True
   13      Arg1 0.202316 2.151444e-10         True
   14  Slc16a10 0.202032 2.225874e-10         True
   15     Syne1 0.199276 4.409085e-10         True
   16     Anpep 0.182326 2.592032e-08         True
   17      Hhex 0.178956 5.368210e-08         True
   18     Alcam 0.171484 2.706560e-07         True
   19      Gch1 0.169114 4.310142e-07         True
   20     Mctp2 0.164925 1.042353e-06         True
   21    Nap1l5 0.162142 1.789696e-06         True
   22      Zim1 0.158269 3.818520e-06         True
   23     Sfxn2 0.156223 5.667354e-06         True
   24     Rgs17 0.154249 8.250985e-06         True
   25   Eif2s3y 0.153502 9.367439e-06         True
   26      Hadh 0.144885 4.491779e-05         True
   27  Hepacam2 0.144025 5.176745e-05         True
   28   Fam183b 0.137796 1.506197e-04         True
   29      Cd47 0.137416 1.583615e-04         True
   30       Ghr 0.136067 1.974891e-04         True
   31      Irx2 0.135496 2.115194e-04         True
   32     Gm609 0.135161 2.170621e-04         True
   33  Tmem184c 0.134985 2.202861e-04         True
   34 Serpina1c 0.134610 2.282147e-04         True
   35       Bsg 0.134604 2.282147e-04         True
   36        Gk 0.134439 2.312969e-04         True
   37     Cxxc4 0.134053 2.424613e-04         True
   38      Gde1 0.128558 5.732851e-04         True
   39   Akr1c19 0.128417 5.789863e-04         True
   40   Gm43194 0.126393 7.895596e-04         True
   41     Itm2b 0.125160 9.421049e-04         True
   42    Dusp10 0.124043 1.101894e-03         True
   43       Ttr 0.121885 1.467255e-03         True
   44     Gria2 0.121626 1.505048e-03         True
   45      F11r 0.121578 1.505048e-03         True
   46      Ttc3 0.121258 1.565468e-03         True
   47     Enpp1 0.120790 1.666958e-03         True
   48    Tmem27 0.120671 1.669641e-03         True
   49     Saraf 0.120533 1.669641e-03         True
   50      Gng4 0.120499 1.669641e-03         True

[scCS] Candidate commitment-associated genes: Epsilon
        inference_unit=cell_exploratory; matrix=X; n_units=1234; tested_genes=6946
 rank          gene   effect   pvalue_adj  significant
    1        Cdkn1a 0.309303 6.677054e-25         True
    2           Arx 0.303512 3.788425e-24         True
    3        Mboat4 0.290981 4.025278e-22         True
    4        Tm4sf4 0.237835 2.868121e-14         True
    5          Peg3 0.237758 2.868121e-14         True
    6         Glud1 0.237460 2.868121e-14         True
    7         Cd200 0.229925 1.865291e-13         True
    8     Trp53cor1 0.211789 2.642934e-11         True
    9          Muc1 0.208200 6.648502e-11         True
   10         Anpep 0.187485 1.181492e-08         True
   11          Isl1 0.183961 2.461173e-08         True
   12          Arg1 0.183844 2.461173e-08         True
   13        Nap1l5 0.172484 2.901449e-07         True
   14      Trp53i13 0.159244 4.842869e-06         True
   15         Mctp2 0.158913 4.842869e-06         True
   16         Alcam 0.156351 7.914514e-06         True
   17      Slc16a10 0.145347 6.052592e-05         True
   18         Gsta3 0.144897 6.224458e-05         True
   19         Peg10 0.143662 7.637226e-05         True
   20          Zim1 0.142210 9.493699e-05         True
   21           Bsg 0.141145 1.099295e-04         True
   22        Grin3a 0.138595 1.586106e-04         True
   23     Serpina1c 0.138225 1.631704e-04         True
   24         Sfxn2 0.135531 2.432142e-04         True
   25         Syne1 0.135229 2.515859e-04         True
   26          Irx2 0.134787 2.668251e-04         True
   27         Gria2 0.131847 4.160088e-04         True
   28         Anxa2 0.131817 4.160088e-04         True
   29         Acot1 0.128322 6.409868e-04         True
   30          Dll1 0.127325 7.461387e-04         True
   31       Eif2s3y 0.126966 7.805553e-04         True
   32 1700011H14Rik 0.126720 8.013617e-04         True
   33          Asb4 0.126551 8.125764e-04         True
   34      Pdzk1ip1 0.125224 9.833686e-04         True
   35        Dusp10 0.124273 1.118002e-03         True
   36          F11r 0.122532 1.406444e-03         True
   37           Ttr 0.121221 1.644347e-03         True
   38         Gm609 0.121203 1.644347e-03         True
   39         Wfdc2 0.121193 1.644347e-03         True
   40         Prox1 0.121170 1.644347e-03         True
   41         Habp2 0.120574 1.765007e-03         True
   42       Gm43194 0.120461 1.776364e-03         True
   43         Itm2b 0.120093 1.861033e-03         True
   44          Spp1 0.119633 1.970153e-03         True
   45       Fam107b 0.117576 2.608242e-03         True
   46         P2rx4 0.117305 2.690570e-03         True
   47         Saraf 0.116507 2.978266e-03         True
   48          Tgm2 0.116096 3.138867e-03         True
   49           Clu 0.114376 4.031917e-03         True
   50         Rps19 0.114070 4.179651e-03         True

 Alpha
gene effect pvalue pvalue_adj feature_mean feature_sd n_detected_cells n_units significant absolute_effect rank
0 Tmsb4x 0.275744 5.931096e-23 6.866232e-20 26.480909 25.720442 1169 1234 True 0.275744 1
1 Neurog3 0.249886 5.228804e-19 5.188467e-16 13.379286 13.727636 988 1234 True 0.249886 2
2 Ppib 0.242993 4.979215e-18 3.842847e-15 8.263345 5.597094 1201 1234 True 0.242993 3
3 Mdk 0.242233 6.356023e-18 4.414893e-15 15.501658 12.948563 1124 1234 True 0.242233 4
4 Rps9 0.239684 1.433298e-17 9.050625e-15 32.504977 9.654044 1234 1234 True 0.239684 5
5 Cck 0.234886 6.453309e-17 2.988312e-14 20.157887 22.982749 994 1234 True 0.234886 6
6 Btg2 0.233772 9.110337e-17 3.955025e-14 8.378012 5.691946 1196 1234 True 0.233772 7
7 Btbd17 0.233522 9.840581e-17 4.020746e-14 5.285445 5.445983 882 1234 True 0.233522 8
8 Cd63 0.229856 3.016156e-16 1.163901e-13 10.626693 5.767550 1221 1234 True 0.229856 9
9 Igfbpl1 0.225002 1.290964e-15 4.719493e-13 2.379176 3.276287 703 1234 True 0.225002 10
10 Rps11 0.220966 4.215253e-15 1.330870e-12 22.155707 7.528621 1234 1234 True 0.220966 11
11 Gadd45a 0.218992 7.457573e-15 2.158346e-12 6.003202 7.458282 832 1234 True 0.218992 12
12 Pax4 0.214899 2.391038e-14 6.643259e-12 2.811147 2.896685 863 1234 True 0.214899 13
13 Cdk4 0.208052 1.594313e-13 3.532527e-11 7.591262 4.271979 1200 1234 True 0.208052 14
14 Dbn1 0.203733 5.102894e-13 1.012706e-10 2.202929 2.235892 888 1234 True 0.203733 15
15 Smarcd2 0.202143 7.781001e-13 1.460725e-10 3.190157 2.798472 999 1234 True 0.202143 16
16 Selm 0.200973 1.059032e-12 1.935800e-10 5.732411 5.718559 1001 1234 True 0.200973 17
17 Tecr 0.199264 1.655557e-12 2.884216e-10 6.637705 4.874872 1189 1234 True 0.199264 18
18 Cldn6 0.197887 2.366074e-12 3.750920e-10 3.883023 3.513969 1029 1234 True 0.197887 19
19 Rps5 0.195899 3.943605e-12 6.087173e-10 37.870234 10.694716 1234 1234 True 0.195899 20

 Beta
gene effect pvalue pvalue_adj feature_mean feature_sd n_detected_cells n_units significant absolute_effect rank
0 Pcsk2 0.240656 1.052197e-17 3.654280e-14 0.824410 2.119913 285 1234 True 0.240656 1
1 Rbp4 0.224756 1.388129e-15 3.213982e-12 6.374880 17.573840 573 1234 True 0.224756 2
2 Npepl1 0.218365 8.927872e-15 1.550325e-11 1.879040 2.161760 844 1234 True 0.218365 3
3 Pdx1 0.216645 1.458831e-14 2.026609e-11 1.220922 1.919877 586 1234 True 0.216645 4
4 Mnx1 0.215440 2.052819e-14 2.376480e-11 0.251422 0.889925 125 1234 True 0.215440 5
5 Pim2 0.195233 4.674078e-12 4.638021e-09 0.999573 1.959778 460 1234 True 0.195233 6
6 1700086L19Rik 0.187750 3.028669e-11 2.629641e-08 1.625739 3.346094 461 1234 True 0.187750 7
7 Pyy 0.183906 7.680592e-11 5.927710e-08 15.748496 61.305059 631 1234 True 0.183906 8
8 Fxyd6 0.180545 1.705015e-10 1.184303e-07 0.975067 1.746535 455 1234 True 0.180545 9
9 Chgb 0.178527 2.732536e-10 1.725473e-07 26.170255 48.147127 615 1234 True 0.178527 10
10 Gnas 0.176425 4.440420e-10 2.570263e-07 24.092879 17.366661 1234 1234 True 0.176425 11
11 S100a11 0.175677 5.270417e-10 2.816025e-07 2.517067 2.615982 914 1234 True 0.175677 12
12 Pcsk1n 0.170399 1.728991e-09 8.578265e-07 3.587842 6.484170 558 1234 True 0.170399 13
13 Gng12 0.168234 2.784883e-09 1.289587e-06 0.531641 1.061274 355 1234 True 0.168234 14
14 Elmod1 0.163479 7.765361e-09 3.172835e-06 0.060351 0.287795 57 1234 True 0.163479 15
15 Prps1 0.157899 2.491600e-08 9.108764e-06 0.478884 0.964309 328 1234 True 0.157899 16
16 Anxa4 0.156565 3.273181e-08 1.089599e-05 1.785787 2.317612 721 1234 True 0.156565 17
17 Sec61b 0.155863 3.774415e-08 1.191686e-05 4.848659 3.190025 1200 1234 True 0.155863 18
18 Syt13 0.152620 7.233727e-08 2.184586e-05 2.618174 2.080583 1035 1234 True 0.152620 19
19 Fam159b 0.152054 8.093469e-08 2.342385e-05 0.019040 0.209811 14 1234 True 0.152054 20

 Delta
gene effect pvalue pvalue_adj feature_mean feature_sd n_detected_cells n_units significant absolute_effect rank
0 Cdkn1a 0.341412 4.921791e-35 3.418676e-31 7.192985 11.485302 930 1234 True 0.341412 1
1 Mboat4 0.338549 1.923454e-34 6.680157e-31 0.244339 1.053657 119 1234 True 0.338549 2
2 Isl1 0.297690 1.187606e-26 2.019228e-23 3.723734 8.049448 438 1234 True 0.297690 3
3 Arx 0.297191 1.453519e-26 2.019228e-23 0.548678 1.612283 209 1234 True 0.297191 4
4 Cd200 0.287990 5.620897e-25 6.507125e-22 0.846095 1.491867 425 1234 True 0.287990 5
5 Peg3 0.269294 6.283696e-22 6.235222e-19 5.624904 5.022496 1143 1234 True 0.269294 6
6 Tm4sf4 0.249135 6.707171e-19 5.823501e-16 3.366878 4.171071 862 1234 True 0.249135 7
7 Peg10 0.233154 1.102107e-16 6.379363e-14 0.182685 0.908079 72 1234 True 0.233154 8
8 Trp53cor1 0.228891 4.037835e-16 2.157446e-13 0.106156 0.414571 95 1234 True 0.228891 9
9 Glud1 0.214502 2.674047e-14 1.092584e-11 3.630171 4.123165 972 1234 True 0.214502 10
10 Muc1 0.208587 1.377805e-13 4.785118e-11 0.439841 0.891349 326 1234 True 0.208587 11
11 Trp53i13 0.206268 2.585890e-13 8.553140e-11 0.562178 1.028628 403 1234 True 0.206268 12
12 Arg1 0.202316 7.433724e-13 2.151444e-10 0.135893 0.751025 83 1234 True 0.202316 13
13 Slc16a10 0.202032 8.011352e-13 2.225874e-10 0.061193 0.363854 45 1234 True 0.202032 14
14 Syne1 0.199276 1.650392e-12 4.409085e-10 0.107190 0.437560 87 1234 True 0.199276 15
15 Anpep 0.182326 1.119507e-10 2.592032e-08 0.294801 0.849643 224 1234 True 0.182326 16
16 Hhex 0.178956 2.473117e-10 5.368210e-08 0.840135 3.748434 196 1234 True 0.178956 17
17 Alcam 0.171484 1.358231e-09 2.706560e-07 0.421799 0.895275 297 1234 True 0.171484 18
18 Gch1 0.169114 2.295930e-09 4.310142e-07 2.214220 3.968295 497 1234 True 0.169114 19
19 Mctp2 0.164925 5.702481e-09 1.042353e-06 0.017739 0.159846 17 1234 True 0.164925 20

 Epsilon
gene effect pvalue pvalue_adj feature_mean feature_sd n_detected_cells n_units significant absolute_effect rank
0 Cdkn1a 0.309303 9.612805e-29 6.677054e-25 7.192985 11.485302 930 1234 True 0.309303 1
1 Arx 0.303512 1.090822e-27 3.788425e-24 0.548678 1.612283 209 1234 True 0.303512 2
2 Mboat4 0.290981 1.738531e-25 4.025278e-22 0.244339 1.053657 119 1234 True 0.290981 3
3 Tm4sf4 0.237835 2.569547e-17 2.868121e-14 3.366878 4.171071 862 1234 True 0.237835 4
4 Peg3 0.237758 2.632457e-17 2.868121e-14 5.624904 5.022496 1143 1234 True 0.237758 5
5 Glud1 0.237460 2.890418e-17 2.868121e-14 3.630171 4.123165 972 1234 True 0.237460 6
6 Cd200 0.229925 2.953960e-16 1.865291e-13 0.846095 1.491867 425 1234 True 0.229925 7
7 Trp53cor1 0.211789 5.707460e-14 2.642934e-11 0.106156 0.414571 95 1234 True 0.211789 8
8 Muc1 0.208200 1.531472e-13 6.648502e-11 0.439841 0.891349 326 1234 True 0.208200 9
9 Anpep 0.187485 3.231839e-11 1.181492e-08 0.294801 0.849643 224 1234 True 0.187485 10
10 Isl1 0.183961 7.580492e-11 2.461173e-08 3.723734 8.049448 438 1234 True 0.183961 11
11 Arg1 0.183844 7.795250e-11 2.461173e-08 0.135893 0.751025 83 1234 True 0.183844 12
12 Nap1l5 0.172484 1.086059e-09 2.901449e-07 0.037859 0.316134 27 1234 True 0.172484 13
13 Trp53i13 0.159244 1.888294e-08 4.842869e-06 0.562178 1.028628 403 1234 True 0.159244 14
14 Mctp2 0.158913 2.021929e-08 4.842869e-06 0.017739 0.159846 17 1234 True 0.158913 15
15 Alcam 0.156351 3.418304e-08 7.914514e-06 0.421799 0.895275 297 1234 True 0.156351 16
16 Slc16a10 0.145347 2.962686e-07 6.052592e-05 0.061193 0.363854 45 1234 True 0.145347 17
17 Gsta3 0.144897 3.226037e-07 6.224458e-05 0.195227 0.672787 144 1234 True 0.144897 18
18 Peg10 0.143662 4.068203e-07 7.637226e-05 0.182685 0.908079 72 1234 True 0.143662 19
19 Zim1 0.142210 5.330467e-07 9.493699e-05 0.116127 0.372238 118 1234 True 0.142210 20
[11]:
['tutorial_outputs/pancreas_downstream/pancreas_CFA_association_Alpha.csv',
 'tutorial_outputs/pancreas_downstream/pancreas_CFA_association_Beta.csv',
 'tutorial_outputs/pancreas_downstream/pancreas_CFA_association_Delta.csv',
 'tutorial_outputs/pancreas_downstream/pancreas_CFA_association_Epsilon.csv',
 'tutorial_outputs/pancreas_downstream/pancreas_CFA_association_metadata.json']
[12]:
fig, axes = plt.subplots(2, 2, figsize=(14, 11))
for axis, fate in zip(axes.ravel(), FATES):
    table = association.tables[fate].copy()
    shown = pd.concat(
        [
            table.nlargest(12, "effect"),
            table.nsmallest(12, "effect"),
        ]
    ).drop_duplicates("gene")
    axis.scatter(
        table["effect"],
        -np.log10(table["pvalue_adj"].clip(lower=1e-300)),
        s=10,
        alpha=0.35,
    )
    for row in shown.itertuples():
        axis.text(
            row.effect,
            -np.log10(max(row.pvalue_adj, 1e-300)),
            row.gene,
            fontsize=8,
        )
    axis.axvline(0, color="0.5", linewidth=1)
    axis.set_title(f"{fate} CFA-associated genes")
    axis.set_xlabel("Partial Spearman effect")
    axis.set_ylabel("−log10 adjusted p-value")
fig.suptitle("Exploratory cell-level candidate associations", y=1.01)
fig.tight_layout()
fig.savefig(OUTPUT_DIR / "CFA_association_volcanoes.png", dpi=200, bbox_inches="tight")
plt.show()
../_images/tutorials_10_pancreas_downstream_analysis_22_0.png

10. Fate markers versus commitment-associated genes

Fate markers compare annotated terminal cells with the root. Commitment associations are calculated within the selected analysis population and can identify genes varying before terminal identity is fully established.

[13]:
fate_markers = scorer.get_fate_markers(
    method="wilcoxon",
    fdr_threshold=0.05,
    logfc_threshold=0.25,
    min_cells=10,
    verbose=True,
)
for fate, table in fate_markers.items():
    table.to_csv(OUTPUT_DIR / f"fate_markers_{fate}.csv", index=False)
    print("\n", fate)
    display(table.head(20))
ranking genes
    finished (0:00:00)

[scCS] Fate markers: Alpha vs root; significant=2275
 rank    gene  logfoldchange    pvalue_adj
    1     Pyy     233.343811 1.051944e-160
    2     Gcg     209.721115  1.613987e-83
    3    Iapp     100.156334 3.267432e-138
    4     Ttr      69.962547 3.499607e-146
    5    Gnas      51.120472 2.153794e-130
    6    Rbp4      43.370220 7.934233e-127
    7   Hpgds      26.723766  1.768276e-02
    8     Tnr      26.722797  1.221011e-02
   21  Tmem27      18.768389 3.627194e-200
   22 Slc38a5      18.712347 1.562776e-124
   23  Pcsk1n      17.453619 6.113637e-142
   24     Cpe      13.869763 1.434444e-125
   25 Slc25a5      12.463739  3.970180e-52
   26    Gast      11.470784  3.008741e-52
   27   Pcsk2       9.351954 1.479988e-117
   28    Gpx3       8.338355 3.580264e-131
   29   Meis2       8.270338 1.147131e-145
   30   Peg10       7.708434  2.012753e-97
   31     Ppy       7.698100  3.638924e-25
   32    Isl1       7.264405  2.414985e-71
ranking genes
    finished (0:00:00)

[scCS] Fate markers: Beta vs root; significant=2516
 rank    gene  logfoldchange    pvalue_adj
    1    Iapp     186.131287 1.182187e-212
    2     Pyy     162.698456 1.170819e-166
    3    Ins1      99.442055  1.314981e-65
    4    Rbp4      50.622169 2.567789e-195
    5    Ins2      48.017612  9.881409e-92
    6    Gnas      42.680595 7.839004e-158
    7    Nnat      42.575066 3.539079e-142
   11     Ttr      25.199047  7.322854e-82
   22  Sec61b      13.772089 1.602273e-188
   23    Dlk1      13.032376 4.344538e-143
   24  Pcsk1n      11.741943 4.973997e-133
   25 Ppp1r1a      11.731724 5.904462e-129
   26     Npy      11.361814  6.546743e-10
   27   Pcsk2      10.887548 2.477486e-210
   28 Slc38a5       8.947008 5.318520e-109
   29     Cpe       8.691454  3.550664e-97
   30  Malat1       8.601723  3.752770e-08
   31     Gip       8.531458  3.147848e-21
   32   Hspa5       8.391294 3.347481e-145
   33   G6pc2       8.110472  2.820160e-49
ranking genes
    finished (0:00:00)

[scCS] Fate markers: Delta vs root; significant=798
 rank    gene  logfoldchange   pvalue_adj
    1     Pyy     507.735718 1.707881e-38
    2     Sst     333.075989 3.239883e-31
    3    Rbp4     204.135147 3.087391e-40
    4    Iapp     103.372108 3.450474e-38
    5  Ptprz1      28.902637 4.037080e-03
   11  Malat1      20.850237 2.335053e-07
   12     Ppy      20.007435 2.511014e-08
   13    Hhex      18.528818 2.771210e-36
   14    Dlk1      17.492176 3.239883e-31
   15   Cd24a      17.178232 9.241425e-29
   16    Gnas      16.492489 1.108358e-09
   17    Isl1      14.357853 2.157751e-24
   18    Gpx3      10.721635 2.015825e-30
   19    Ssr2      10.476543 9.734139e-28
   20   Pcsk2      10.011806 2.975781e-28
   21     Ttr       9.984013 9.294936e-04
   22    Hadh       9.676562 2.701236e-34
   23    Arg1       9.438912 6.021122e-32
   24 Fam183b       8.570268 5.275840e-25
   25   Meis2       7.521049 5.229345e-28
ranking genes
    finished (0:00:00)

[scCS] Fate markers: Epsilon vs root; significant=1228
 rank    gene  logfoldchange   pvalue_adj
    1    Ghrl     496.845154 1.711900e-80
    2     Pyy     105.076279 1.520765e-33
    3    Rbp4      84.230026 1.166756e-63
    4  Lrpprc      26.874617 2.212602e-35
   10    Isl1      23.863163 5.599882e-57
   16  Cdkn1a      20.302576 1.014479e-09
   17     Gcg      20.285036 8.288713e-05
   18    Iapp      19.742641 2.165407e-31
   19     Ttr      18.806128 4.338757e-05
   20  Maged2      16.161346 1.336546e-53
   21     Bsg      13.106299 2.802268e-52
   22 Slc38a5      10.352853 2.793423e-28
   23  Tmem27      10.297958 4.252569e-35
   24   Hspa5       9.316794 1.551241e-41
   25   Itm2b       8.679244 9.753602e-37
   26 Fam183b       8.656516 3.120110e-44
   27    Gnas       8.329838 7.783550e-03
   28    Ssr2       8.275435 4.458118e-48
   29    Arg1       7.801110 3.470800e-39
   31    Gast       6.628323 1.065891e-07

 Alpha
gene score logfoldchange pvalue pvalue_adj pct_terminal significant analysis_type rank
0 Pyy 27.298784 233.343811 4.384929e-164 1.051944e-160 0.968815 True terminal_annotation_marker_vs_root 1
1 Gcg 19.606232 209.721115 1.367976e-85 1.613987e-83 0.656965 True terminal_annotation_marker_vs_root 2
2 Iapp 25.275759 100.156334 5.901990e-141 3.267432e-138 0.854470 True terminal_annotation_marker_vs_root 3
3 Ttr 26.014395 69.962547 3.403814e-149 3.499607e-146 1.000000 True terminal_annotation_marker_vs_root 4
4 Gnas 24.545862 51.120472 4.788205e-133 2.153794e-130 1.000000 True terminal_annotation_marker_vs_root 5
5 Rbp4 24.200369 43.370220 2.204872e-129 7.934233e-127 0.962578 True terminal_annotation_marker_vs_root 6
6 Hpgds 2.812783 26.723766 4.911470e-03 1.768276e-02 0.087318 True terminal_annotation_marker_vs_root 7
7 Tnr 2.946726 26.722797 3.211580e-03 1.221011e-02 0.091476 True terminal_annotation_marker_vs_root 8
8 Dpp10 1.406392 25.546453 1.596078e-01 3.284975e-01 0.043659 False terminal_annotation_marker_vs_root 9
9 Kcnip4 0.803652 24.992832 4.215977e-01 6.472897e-01 0.024948 False terminal_annotation_marker_vs_root 10
10 Cntn1 0.803652 24.814041 4.215977e-01 6.472897e-01 0.024948 False terminal_annotation_marker_vs_root 11
11 Sorcs1 0.803652 24.681318 4.215977e-01 6.472897e-01 0.024948 False terminal_annotation_marker_vs_root 12
12 Cntnap5b 0.535768 24.193756 5.921187e-01 7.905839e-01 0.016632 False terminal_annotation_marker_vs_root 13
13 Nlgn1 0.401826 23.765213 6.878119e-01 8.604524e-01 0.012474 False terminal_annotation_marker_vs_root 14
14 Cdh23 0.200913 22.676788 8.407665e-01 9.450252e-01 0.006237 False terminal_annotation_marker_vs_root 15
15 Dlg2 0.200913 22.563169 8.407665e-01 9.450252e-01 0.006237 False terminal_annotation_marker_vs_root 16
16 Ptprz1 0.200913 22.523197 8.407665e-01 9.450252e-01 0.006237 False terminal_annotation_marker_vs_root 17
17 Sntg1 0.066971 20.907154 9.466048e-01 9.868608e-01 0.002079 False terminal_annotation_marker_vs_root 18
18 Cdh6 0.066971 20.481556 9.466048e-01 9.868608e-01 0.002079 False terminal_annotation_marker_vs_root 19
19 Lsamp 0.066971 20.481556 9.466048e-01 9.868608e-01 0.002079 False terminal_annotation_marker_vs_root 20

 Beta
gene score logfoldchange pvalue pvalue_adj pct_terminal significant analysis_type rank
0 Iapp 31.406631 186.131287 1.642611e-216 1.182187e-212 0.944162 True terminal_annotation_marker_vs_root 1
1 Pyy 27.752472 162.698456 1.626816e-169 1.170819e-166 0.962775 True terminal_annotation_marker_vs_root 2
2 Ins1 17.335060 99.442055 2.557974e-67 1.314981e-65 0.582064 True terminal_annotation_marker_vs_root 3
3 Rbp4 30.064129 50.622169 1.427144e-198 2.567789e-195 0.996616 True terminal_annotation_marker_vs_root 4
4 Ins2 20.527544 48.017612 1.221961e-93 9.881409e-92 0.610829 True terminal_annotation_marker_vs_root 5
5 Gnas 27.004543 42.680595 1.307045e-160 7.839004e-158 1.000000 True terminal_annotation_marker_vs_root 6
6 Nnat 25.606888 42.575066 1.278533e-144 3.539079e-142 0.825719 True terminal_annotation_marker_vs_root 7
7 Cdh23 1.639956 25.650280 1.010144e-01 2.167563e-01 0.047377 False terminal_annotation_marker_vs_root 8
8 Ptprz1 1.815665 25.624140 6.942174e-02 1.598811e-01 0.052453 False terminal_annotation_marker_vs_root 9
9 Tnr 1.698526 25.451895 8.940862e-02 1.963606e-01 0.049069 False terminal_annotation_marker_vs_root 10
10 Ttr 19.382299 25.199047 1.088711e-83 7.322854e-82 0.994924 True terminal_annotation_marker_vs_root 11
11 Sntg1 0.995687 24.867790 3.194021e-01 5.239883e-01 0.028765 False terminal_annotation_marker_vs_root 12
12 Dpp10 0.937118 24.725800 3.486981e-01 5.561050e-01 0.027073 False terminal_annotation_marker_vs_root 13
13 Hpgds 1.054257 24.674431 2.917652e-01 4.899286e-01 0.030457 False terminal_annotation_marker_vs_root 14
14 Cntn1 0.702838 24.308039 4.821566e-01 6.856934e-01 0.020305 False terminal_annotation_marker_vs_root 15
15 Nlgn1 0.292849 23.271965 7.696374e-01 8.987686e-01 0.008460 False terminal_annotation_marker_vs_root 16
16 Cntnap5b 0.175710 22.454672 8.605221e-01 9.479838e-01 0.005076 False terminal_annotation_marker_vs_root 17
17 Kcnip4 0.175710 22.304205 8.605221e-01 9.479838e-01 0.005076 False terminal_annotation_marker_vs_root 18
18 Dlg2 0.058570 20.705379 9.532947e-01 9.837292e-01 0.001692 False terminal_annotation_marker_vs_root 19
19 Lsamp 0.058570 20.256542 9.532947e-01 9.837292e-01 0.001692 False terminal_annotation_marker_vs_root 20

 Delta
gene score logfoldchange pvalue pvalue_adj pct_terminal significant analysis_type rank
0 Pyy 13.587565 507.735718 4.746092e-42 1.707881e-38 1.000000 True terminal_annotation_marker_vs_root 1
1 Sst 12.178400 333.075989 4.051542e-34 3.239883e-31 0.871429 True terminal_annotation_marker_vs_root 2
2 Rbp4 13.927866 204.135147 4.289831e-44 3.087391e-40 1.000000 True terminal_annotation_marker_vs_root 3
3 Iapp 13.506161 103.372108 1.438297e-41 3.450474e-38 0.985714 True terminal_annotation_marker_vs_root 4
4 Ptprz1 3.623568 28.902637 2.905666e-04 4.037080e-03 0.257143 True terminal_annotation_marker_vs_root 5
5 Cntn1 1.409165 27.251200 1.587863e-01 5.970662e-01 0.100000 False terminal_annotation_marker_vs_root 6
6 Hpgds 1.207856 26.802217 2.271026e-01 7.303207e-01 0.085714 False terminal_annotation_marker_vs_root 7
7 Tnr 1.409165 26.669956 1.587863e-01 5.970662e-01 0.100000 False terminal_annotation_marker_vs_root 8
8 Dpp10 0.805237 26.087811 4.206827e-01 9.597528e-01 0.057143 False terminal_annotation_marker_vs_root 9
9 Nlgn1 0.201309 24.924381 8.404567e-01 1.000000e+00 0.014286 False terminal_annotation_marker_vs_root 10
10 Malat1 5.792293 20.850237 6.943189e-09 2.335053e-07 1.000000 True terminal_annotation_marker_vs_root 11
11 Ppy 6.192138 20.007435 5.935342e-10 2.511014e-08 0.485714 True terminal_annotation_marker_vs_root 12
12 Hhex 13.157539 18.528818 1.540203e-39 2.771210e-36 0.985714 True terminal_annotation_marker_vs_root 13
13 Dlk1 12.181336 17.492176 3.908267e-34 3.239883e-31 1.000000 True terminal_annotation_marker_vs_root 14
14 Cd24a 11.691277 17.178232 1.412473e-31 9.241425e-29 1.000000 True terminal_annotation_marker_vs_root 15
15 Gnas 6.687908 16.492489 2.263842e-11 1.108358e-09 1.000000 True terminal_annotation_marker_vs_root 16
16 Isl1 10.758631 14.357853 5.396626e-27 2.157751e-24 1.000000 True terminal_annotation_marker_vs_root 17
17 Gpx3 12.019669 10.721635 2.800925e-33 2.015825e-30 0.985714 True terminal_annotation_marker_vs_root 18
18 Ssr2 11.468760 10.476543 1.893538e-30 9.734139e-28 1.000000 True terminal_annotation_marker_vs_root 19
19 Pcsk2 11.584097 10.011806 4.961703e-31 2.975781e-28 0.928571 True terminal_annotation_marker_vs_root 20

 Epsilon
gene score logfoldchange pvalue pvalue_adj pct_terminal significant analysis_type rank
0 Ghrl 19.460413 496.845154 2.378630e-84 1.711900e-80 1.000000 True terminal_annotation_marker_vs_root 1
1 Pyy 12.460391 105.076279 1.227675e-35 1.520765e-33 0.873239 True terminal_annotation_marker_vs_root 2
2 Rbp4 17.321424 84.230026 3.242339e-67 1.166756e-63 0.992958 True terminal_annotation_marker_vs_root 3
3 Lrpprc 12.808173 26.874617 1.475683e-37 2.212602e-35 0.873239 True terminal_annotation_marker_vs_root 4
4 Tnr 1.788753 26.569187 7.365458e-02 2.781175e-01 0.091549 False terminal_annotation_marker_vs_root 5
5 Hpgds 1.375964 26.106819 1.688328e-01 4.925373e-01 0.070423 False terminal_annotation_marker_vs_root 6
6 Sorcs1 0.550386 25.018215 5.820549e-01 9.302797e-01 0.028169 False terminal_annotation_marker_vs_root 7
7 Cntn1 0.412789 24.271919 6.797611e-01 9.705308e-01 0.021127 False terminal_annotation_marker_vs_root 8
8 Nlgn1 0.412789 24.233652 6.797611e-01 9.705308e-01 0.021127 False terminal_annotation_marker_vs_root 9
9 Isl1 16.356800 23.863163 3.890428e-60 5.599882e-57 0.992958 True terminal_annotation_marker_vs_root 10
10 Kcnip4 0.275193 23.464123 7.831681e-01 9.975780e-01 0.014085 False terminal_annotation_marker_vs_root 11
11 Cntnap5b 0.275193 23.415113 7.831681e-01 9.975780e-01 0.014085 False terminal_annotation_marker_vs_root 12
12 Dpp10 0.137596 23.186213 8.905594e-01 1.000000e+00 0.007042 False terminal_annotation_marker_vs_root 13
13 Dlg2 0.137596 22.930840 8.905594e-01 1.000000e+00 0.007042 False terminal_annotation_marker_vs_root 14
14 Ptprz1 0.137596 22.502655 8.905594e-01 1.000000e+00 0.007042 False terminal_annotation_marker_vs_root 15
15 Cdkn1a 6.598829 20.302576 4.144183e-11 1.014479e-09 0.887324 True terminal_annotation_marker_vs_root 16
16 Gcg 4.514143 20.285036 6.357328e-06 8.288713e-05 0.338028 True terminal_annotation_marker_vs_root 17
17 Iapp 12.045594 19.742641 2.045959e-33 2.165407e-31 0.760563 True terminal_annotation_marker_vs_root 18
18 Ttr 4.656311 18.806128 3.219253e-06 4.338757e-05 0.929577 True terminal_annotation_marker_vs_root 19
19 Maged2 15.854918 16.161346 1.299961e-56 1.336546e-53 0.943662 True terminal_annotation_marker_vs_root 20
[14]:
overlap_rows = []
for fate in FATES:
    association_genes = set(association.top(fate, n=100, positive_only=True)["gene"])
    marker_genes = set(fate_markers[fate].head(100)["gene"])
    overlap_rows.append(
        {
            "fate": fate,
            "top_association_genes": len(association_genes),
            "top_marker_genes": len(marker_genes),
            "overlap": len(association_genes & marker_genes),
            "overlap_genes": ", ".join(sorted(association_genes & marker_genes)),
        }
    )
overlap_table = pd.DataFrame(overlap_rows)
display(overlap_table)
overlap_table.to_csv(OUTPUT_DIR / "association_marker_overlap.csv", index=False)
fate top_association_genes top_marker_genes overlap overlap_genes
0 Alpha 100 100 0
1 Beta 100 100 20 1700086L19Rik, Chga, Cpe, Dlk1, Dynll1, Fam159...
2 Delta 100 100 15 Arg1, Bsg, Cd200, Cd24a, Fam183b, Ghr, Gria2, ...
3 Epsilon 100 100 22 Alcam, Anpep, Arg1, Arx, Bsg, Cdkn1a, Cpn1, En...

11. Reproducible local enrichment

The tutorial uses a small local mouse endocrine gene-set mapping so the analysis is deterministic and does not depend on a changing remote database. For a publication, replace it with a versioned GMT file and an assay-specific background.

[15]:
local_gene_sets = {
    "endocrine_progenitor_program": {"Neurog3", "Neurod1", "Insm1", "Pax4", "Pax6"},
    "insulin_secretory_program": {"Ins1", "Ins2", "Iapp", "Pcsk1", "Pcsk2", "Chga", "Chgb"},
    "alpha_identity_program": {"Gcg", "Mafb", "Arx", "Ttr", "Slc7a2"},
    "beta_identity_program": {"Ins1", "Ins2", "Pdx1", "Nkx6-1", "Mafa", "Iapp"},
    "delta_identity_program": {"Sst", "Hhex", "Ghsr", "Rbp4", "Col25a1"},
    "epsilon_identity_program": {"Ghrl", "Irx2", "Tspan8", "Slc18a1", "Fev"},
    "neuroendocrine_granules": {"Chga", "Chgb", "Scg2", "Scg3", "Scg5", "Cpe"},
}
present = set(map(str, adata.var_names))
local_gene_sets = {
    term: sorted(set(genes) & present)
    for term, genes in local_gene_sets.items()
}
display(pd.Series({term: len(genes) for term, genes in local_gene_sets.items()}, name="n_genes"))
endocrine_progenitor_program    4
insulin_secretory_program       7
alpha_identity_program          4
beta_identity_program           5
delta_identity_program          3
epsilon_identity_program        4
neuroendocrine_granules         5
Name: n_genes, dtype: int64
[16]:
enrichment = scCS.run_commitment_enrichment(
    association,
    gene_sets=local_gene_sets,
    background=adata.var_names,
    direction="positive",
    effect_threshold=0.05,
    significant_only=False,
    max_genes=150,
    min_query_genes=3,
    min_set_size=3,
    fdr_threshold=0.10,
    verbose=True,
)
enrichment.export(OUTPUT_DIR, prefix="pancreas_local_enrichment")

fig, axes = plt.subplots(2, 2, figsize=(15, 10))
for axis, fate in zip(axes.ravel(), FATES):
    scCS.plot_enrichment_dotplot(
        enrichment,
        fate,
        n_terms=10,
        significant_only=False,
        ax=axis,
    )
fig.tight_layout()
fig.savefig(OUTPUT_DIR / "local_enrichment_dotplots.png", dpi=200, bbox_inches="tight")
plt.show()
[scCS] Enrichment Alpha: query_genes=150, significant_terms=1
[scCS] Enrichment Beta: query_genes=150, significant_terms=6
[scCS] Enrichment Delta: query_genes=150, significant_terms=1
[scCS] Enrichment Epsilon: query_genes=150, significant_terms=1
../_images/tutorials_10_pancreas_downstream_analysis_28_1.png

12. Subset summaries: early, middle, and late root

Subset comparisons can show how fate composition changes along the incoming path. They remain descriptive unless subsets correspond to independent biological replicate units.

[17]:
root_ids = result.cell_ids[result.root_mask]
root_ordering = pd.Series(
    adata.obs.loc[root_ids, ORDERING_KEY].to_numpy(float),
    index=root_ids,
)
third = pd.qcut(
    root_ordering.rank(method="first"),
    q=3,
    labels=["early", "middle", "late"],
)
subset_summaries = {}
for label in ["early", "middle", "late"]:
    mask = result.root_mask.copy()
    mask[result.root_mask] = third.to_numpy() == label
    subset_summaries[label] = result.summarize(mask)

subset_figure = scorer.plot_subset_comparison(
    subset_summaries,
    metric="commitment_composition",
)
subset_figure.savefig(
    OUTPUT_DIR / "root_ordering_thirds_composition.png",
    dpi=200,
    bbox_inches="tight",
)
plt.show()
../_images/tutorials_10_pancreas_downstream_analysis_30_0.png

13. Replicate-aware template for a real study

For a real multi-sample dataset, rerun the association with genuine biological replicate metadata:

association_replicate = scorer.get_commitment_associated_genes(
    outcome="future_fate_affinity",
    fate_names=FATES,
    population="root",
    inference_unit="replicate",
    replicate_key="sample_id",
    condition_key="condition",
    pseudotime_key=ORDERING_KEY,
    min_replicates=6,
    min_cells_per_replicate=5,
)

Do not construct pseudo-replicates for biological inference.

14. Export the complete downstream record

[18]:
summary_table.to_csv(OUTPUT_DIR / "cell_level_DFFP_metrics.csv", index=False)
adata.write_h5ad(OUTPUT_DIR / "pancreas_downstream_analysis.h5ad")

metadata = {
    "scoring_method": "Discounted Future-Fate Propagation",
    "scoring_mode": "future_fate",
    "velocity_model": "dynamical",
    "ordering_key": ORDERING_KEY,
    "effective_horizon": FUTURE_OPTIONS["effective_horizon"],
    "anchor_quantile": FUTURE_OPTIONS["anchor_quantile"],
    "association_scope": "cell_exploratory_candidate_generation",
    "enrichment_source": "local_in_notebook_mapping",
}
pd.Series(metadata).to_json(
    OUTPUT_DIR / "downstream_metadata.json",
    indent=2,
)
print("Outputs written to", OUTPUT_DIR.resolve())
Outputs written to /home/emil/notebooks/08-tutorials/tutorial_outputs/pancreas_downstream

Interpretation checklist

  • DFFP must be accepted before downstream screening.

  • Marker expression, commitment association, and terminal markers answer different questions.

  • Cell-level association is exploratory without biological replicates.

  • Local gene sets and the tested-gene background should be versioned.

  • Enrichment supports interpretation; it does not establish causal regulation.