Repository navigation
feat: automatic K selection (CellCharter's ClusterAutoK) - #1276
selmanozleyen wants to merge 9 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1276 +/- ##
==========================================
+ Coverage 78.86% 79.27% +0.41%
==========================================
Files 65 67 +2
Lines 9611 9860 +249
Branches 1591 1629 +38
==========================================
+ Hits 7580 7817 +237
- Misses 1500 1508 +8
- Partials 531 535 +4
🚀 New features to boost your workflow:
|
|
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
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
| previous_curve: np.ndarray | None = None | ||
| converged = False | ||
|
|
||
| for run in range(max_runs): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| # 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) |
There was a problem hiding this comment.
The runs only differ at the level of random seeds. Would it make sense to combine this with e.g. bootstrapping?
There was a problem hiding this comment.
I am not sure, it sounds interesting but also sounds out of scope for this PR. wdyt @sarajimenez @timtreis ?
There was a problem hiding this comment.
I do think it's out of the scope of this PR, we're interested in the changes between Ks not across runs. Bootstrapping would be interesting in case we want to add significance. Am I reading this correct, @grst ?
ca085f2 to
afc5110
Compare
6dcb8f6 to
c522628
Compare
9b765d2 to
1f3f110
Compare
b9df9e0 to
ae5d9d7
Compare
1f3f110 to
743c3bc
Compare
ae5d9d7 to
694c55e
Compare
1ef005e to
e57e3bd
Compare
fee85a0 to
de01832
Compare
49e32ae to
e9b8b9a
Compare
de01832 to
145bfee
Compare
e9b8b9a to
07c64ef
Compare
145bfee to
08c7369
Compare
07c64ef to
6a7f8e8
Compare
06b7fa8 to
0c3243f
Compare
6a7f8e8 to
2308673
Compare
0c3243f to
16d7cc2
Compare
4d49338 to
8fc996f
Compare
8c571ab to
572bc93
Compare
Rebuilt on #1284's niche refactor, reusing its pieces instead of keeping parallel ones: main's Clusterer and LeidenClusterer, calculate_niche_custom, the hop shells and nhood_aggregate. New on top of them: - sweep_auto_k and AutoKClusterer (gr/_autok.py), with ClusterAutoKResult next to them; SweepableClusterer extends main's Clusterer with the number-of-clusters parameter the sweep sets per fit - gr.cluster_auto_k and gr.cluster_stability - calculate_niche_cellcharter takes a (min, max) tuple or a sequence for n_clusters and then sweeps, with max_runs, convergence_tol and store_labels; _fit_clusterers writes an estimator's extra labelings and diagnostics when it leaves them - gr.nhood_entropy, the entropy of nhood_aggregate's neighborhood composition
…e hint at the right argument
…, NaN entropy without neighbors
0089ea7 to
4275371
Compare
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?