Simplify + align marker step: gene filter in canonicalize, default-param Wilcoxon, drop COSG (#36)#39
Merged
Merged
Conversation
…ram Wilcoxon, drop COSG (#36) Aligns the marker step with the scanpy clustering tutorial + sc-best-practices: one DE/specificity source, one fewer dependency, no new user-facing options. - canonicalize.py: apply an upfront ABSOLUTE gene filter (sc.pp.filter_genes, GENE_FILTER_MIN_CELLS=5) when building the canonical slim, so every downstream step sees one clean, consistent gene set. Absolute (not relative-to-n_obs) on purpose: preserves rare-cluster markers (the 27-cell-microglia / P2RY12 case). Recorded in canonicalization.json. - compute_markers.py: default-param Wilcoxon (drop tie_correct); remove COSG and the --no-cosg flag; emit specificity via sc.tl.filter_rank_genes_groups as markers_filtered_<level>.csv (cluster, rank, gene, logfoldchange, score, pct_in/out). - cluster_confidence.py: re-express the confidence proxy on log2FC (lfc_top1/lfc_margin/ n_strong from the filtered table); recalibrate CONFIDENCE_BANDS to the logFC scale; --strong-thresh default 0.02 -> 1.0. Stable header when no markers survive. - Repoint all COSG readers to markers_filtered_<level>.csv (build_report, report_plots, scaffold_cards). score_signatures fallback DE aligned to default-param (no tie_correct). - pixi.toml/lock: drop cosg. Docs (SKILL.md + 5 reference docs) updated, incl. the "not measured vs not expressed" panel-gating note and canonicalize as a standard Stage-1 step. tests/test_smoke.py: drop --no-cosg, assert markers_filtered schema. Verified: smoke tests green; full canonicalize->markers->confidence->cards chain on the benchmark harness's blinded log-norm pbmc3k (2638 cells, 8 clusters) — textbook PBMC markers, both rare clusters (15 & 37 cells) keep 50 specific markers each; gene filter + P2RY12 survival confirmed on real HNOCA. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #36.
Aligns the marker step with the scanpy clustering tutorial + sc-best-practices annotation chapter: one DE/specificity source, one fewer dependency, no new user-facing options.
Changes
Change 1 — upfront gene filter in canonicalization (
canonicalize.py,constants.py)sc.pp.filter_genes(slim, min_cells=GENE_FILTER_MIN_CELLS=5)right before the slim is written, so every downstream step inherits one clean, consistent gene set.n_obson purpose: a relative threshold (e.g. 0.1%×500k=500) would delete genuine rare-cluster markers (the 27-cell-microglia / P2RY12 case). The floor is bounded by the smallest cluster you want to annotate, so it stays low and absolute. This is a noise-cleanliness step, not the atlas-speed lever.canonicalization.json(gene_filter.min_cells/.dropped).Change 2 — marker method (
compute_markers.py)tie_correct=True→ scanpy defaultFalse(keeppts=True,use_raw=False).--no-cosg/--cosgflag; specificity now viasc.tl.filter_rank_genes_groups→markers_filtered_<level>.csv(cluster, rank, gene, logfoldchange, score, pct_in, pct_out).Change 3 — COSG blast radius
cluster_confidence.py: proxy re-expressed on log2FC (lfc_top1,lfc_margin,n_strong);CONFIDENCE_BANDSrecalibrated to the logFC scale;--strong-threshdefault0.02 → 1.0. Writes a stable header even when no markers survive.build_report.py,report_plots.py,scaffold_cards.py: repointed fromcosg_<level>.csvtomarkers_filtered_<level>.csv.score_signatures.py: the rarely-hit fallback DE aligned to default-param (notie_correct) so it can't drift from the canonical markers table. (DE still computed once in the normal flow —filter_rank_genes_groupsreuses therank_genes_groupsresult, andscore_signaturesreuses the writtenmarkers_<level>.csv.)pixi.toml/pixi.lock:cosgremoved (lock regenerated cleanly).SKILL.md+reference/{differential_expression,marker_tools,methodology,artifacts,tool_registry}.md, incl. the "not measured vs not expressed" panel-gating note and canonicalize documented as a standard Stage-1 step.tests/test_smoke.py: drop--no-cosg, assert themarkers_filteredschema.Naming note
The issue refers to
prep_slim.py/cta data prep-slim; that step is nowcanonicalize.py/cta data canonicalize(its docstring says it "replaces the old prep-slim"), so Change 1 landed there.For review (judgment calls)
((3.0,5,0.9),(2.0,3,0.8),(1.0,1,0.7),(0.5,0,0.6)), base 0.45;--strong-thresh1.0 (log2FC≥1 = 2-fold). Best-defensible mapping from COSG's 0–1 cosine scale to logFC — easy to retune.Verification
canonicalize → markers → confidence → scaffold-cardschain on the benchmark harness's blinded, log-normalized pbmc3k (2638 cells, 8 clusters): textbook PBMC markers per cluster (T/B/NK/mono/DC/platelet), the logFC confidence proxy populates, and both rare clusters (15 & 37 cells) keep 50 specific markers each — the 15-cell platelet cluster (PPBP/PF4) has the highest specificity (lfc_top1=11.7), validating the rare-marker rationale.Follow-up
Companion #38 (GPU-route atlas-scale DE via rapids-singlecell) lands separately on top of this.
🤖 Generated with Claude Code