Skip to content

feat: automatic K selection (CellCharter's ClusterAutoK) - #1276

Draft
selmanozleyen wants to merge 10 commits into
mainfrom
feat/cluster-auto-k-seed
Draft

feat: automatic K selection (CellCharter's ClusterAutoK) #1276
selmanozleyen wants to merge 10 commits into
mainfrom
feat/cluster-auto-k-seed

Conversation

@selmanozleyen

@selmanozleyen selmanozleyen commented Aug 22, 2026

Copy link
Copy Markdown
Member

fixes: #1244

will add more description but I want to start somewhere @sarajimenez, @FrancescaDr these are all the things I could find in https://github.com/Jieran-S/hitchhikers_guide_to_spatial_project/blob/spatial_clustering/notebooks/Spatially_aware_clustering/sac.ipynb.

Anything else needed? If everything is there, how does this state look like for you?

`random_state` is dropped outright rather than deprecated: it is simply gone
from the signatures, so passing it raises Python's own TypeError.

The released defaults are preserved -- `seed` still defaults to 42, so calls
that do not pass it stay reproducible exactly as before. Passing `seed=None`
opts out.

Internally `seed` now feeds `numpy.random.Generator`s: `spawn_generators`
derives an independent generator per library and per resolution, and
`rng_to_random_state` converts at the boundary of third-party APIs that take
an int but not a Generator (scikit-learn, spatialleiden). Adding a library or
a resolution therefore no longer shifts the others.

Derived from 777449e on feat/cluster-auto-k.
Completes the rename, so `random_state` no longer appears as a squidpy
parameter name anywhere.

`WekaParams.random_state`, `VahadaneParams.random_state` and
`_refine_with_background_classifier`'s parameter become `seed`, keeping their
existing default of 0. No conversion helper is needed: these are plain
`int | None` and go straight into scikit-learn, which accepts that. The
`random_state=` keywords that remain are scikit-learn's own, on
`RandomForestClassifier` and `NMF`.
@selmanozleyen selmanozleyen changed the title Rename random_state to seed and improve niche function clarity feat: automatic K selection (CellCharter's ClusterAutoK) Aug 22, 2026
@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.48611% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.15%. Comparing base (76b6757) to head (ca085f2).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/squidpy/gr/_autok.py 96.22% 2 Missing and 2 partials ⚠️
src/squidpy/gr/_niche.py 95.60% 1 Missing and 3 partials ⚠️
src/squidpy/gr/_cluster_auto_k.py 95.08% 1 Missing and 2 partials ⚠️
src/squidpy/gr/_nhood.py 91.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1276      +/-   ##
==========================================
+ Coverage   78.60%   79.15%   +0.55%     
==========================================
  Files          63       65       +2     
  Lines        9348     9562     +214     
  Branches     1559     1589      +30     
==========================================
+ Hits         7348     7569     +221     
+ Misses       1426     1419       -7     
  Partials      574      574              
Files with missing lines Coverage Δ
src/squidpy/_docs.py 95.29% <100.00%> (ø)
src/squidpy/_utils.py 70.46% <100.00%> (+0.25%) ⬆️
src/squidpy/experimental/im/_detect_tissue.py 67.77% <100.00%> (ø)
...c/squidpy/experimental/im/_stain/_decomposition.py 91.86% <100.00%> (ø)
src/squidpy/gr/_nhood.py 76.65% <91.66%> (+1.65%) ⬆️
src/squidpy/gr/_cluster_auto_k.py 95.08% <95.08%> (ø)
src/squidpy/gr/_autok.py 96.22% <96.22%> (ø)
src/squidpy/gr/_niche.py 81.25% <95.60%> (+5.10%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@grst

grst commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

I think a Bayesian GMM could also be very interesting for the same purpose: https://scikit-learn.org/stable/modules/generated/sklearn.mixture.BayesianGaussianMixture.html

This class allows to infer an approximate posterior distribution over the parameters of a Gaussian mixture distribution. The effective number of components can be inferred from the data.

The downside is that it's quite slow - not sure how hard it would be to make a fast GPU implementation for that @Intron7.

stable K is the one whose partition is least sensitive to adding a cluster.

Reimplements the K selection of CellCharter's ``ClusterAutoK``, using
:class:`~sklearn.mixture.GaussianMixture` for the fits.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be tied to the GaussianMixture? We deliberately split the niche calling procedure such that arbitrary embeddings and clustering algos can be combined... In principle, why not use the same approach for Leiden?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to define a clusterer protocol publicly. And to have a hard-coded list of clusterers I wanted some opinions first. Bayesian GMM is a good hint

Comment thread src/squidpy/gr/_autok.py
previous_curve: np.ndarray | None = None
converged = False

for run in range(max_runs):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is embarrassingly parallel. Probably the clustering algorithm has some sort of threading, too, but did you evaluate how well it scales to many cores? Maybe parallelizing at this level could make sense.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we rely on the clustering algorithm to be utilizing the cores as much as possible so that any other threading would oversubscribe only. Also we can only parallelize runs if tolerance is 0, ie we don't early stop.

But from my small benches I didn't see much improvement than 1.5x tbh. And BLAS was already enough.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was planning to open a separate issue the next days to discuss a public "extensibility" interface for the niche algos. We can discuss it again in that context.

Comment thread src/squidpy/gr/_autok.py
# keyed by (run, K) rather than drawn in sequence, so appending a candidate K
# does not re-seed every other K's fits
random_state = int(np.random.SeedSequence([seed, run, k]).generate_state(1)[0])
labels, nll = _fit_once(X, k, random_state, model_params)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runs only differ at the level of random seeds. Would it make sense to combine this with e.g. bootstrapping?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add automatic K selection (CellCharter's ClusterAutoK) for the cellcharter niche flavor

2 participants