Phenotype-systems overview: design note
Date: 2026-09-13 · Status: implemented (src/dismech/phenotype_systems.py)
The ask
An UpSet plot over the browser's "Phenotype Systems" facet (Nervous System 1,629, Musculoskeletal 1,361, ...), or some other way to get a sense of "multiple parentage" for a disease.
What the data said
Before drawing anything, the facet data was profiled (2,569 diseases at the time; 2,924 by implementation):
| Fact | Value |
|---|---|
| Distinct exact combinations of systems | 1,721 |
| Diseases whose combination no other disease shares | 56% |
| Diseases in a combination shared by 5+ diseases | 22% |
| Coverage of the 30 most frequent combinations | 18% |
| Median systems per disease (presence) | 5 |
| HP terms in use that sit under 2+ top-level branches | 407 of 1,415 |
(The last row was read from the category cache as it stood on 2026-09-04, before #11299 rebuilt it; at implementation the cache held 4,661 terms, 1,380 of them multi-branch. The ratio is a snapshot, not a current figure.)
Two consequences drove the design:
- Exact combinations are nearly unique per disease. A classic UpSet plot sorts exact intersections by size, and here the size-1 tail is the corpus. The plot can only ever draw the shared fifth, and a reader would take the drawn bars for the whole picture.
- "Multiple parentage" has two sources that must not be conflated. A disease can span systems because it has phenotypes in several of them, or because a single HP term is under two branches (HP is a DAG). The facet counts both identically, and it is presence-only: Marfan syndrome counts in Nervous System on the strength of one phenotype out of 26.
Framings considered
Four framings were mocked up with the real data and reviewed:
- A. UpSet over exact combinations. Rejected as the primary view for the reason above, kept as a degree-limited panel with a sharing bar in front of it that shows what it cannot draw.
- B. Pairwise co-occurrence matrix coloured by lift. Adopted: it is what an UpSet plot degrades to gracefully when exact sets fragment, and it answers "which systems travel together beyond chance".
- C. Per-disease profile with an effective number of systems (exp of the Shannon entropy of phenotypes per system). Adopted as the per-disease answer to the original question: it separates Rett (14 phenotypes, effectively 1.8 systems) from Fanconi anemia (180 phenotypes, effectively 15).
- D. Distribution of systems per disease. Adopted, drawn twice (presence and effective) on one axis so the overstatement is visible.
A first round of mockups truncated the system list (top 9 in the UpSet rows, top 14 in the matrix, top 8 per profile) and was rightly called out as giving a misleading picture of heterogeneity. Every panel now carries all 23 systems.
Design
- One module,
dismech.phenotype_systems, with the computation (profile_disorder,collect_phenotype_systems) separated from rendering (render_phenotype_systems_page) and from the report writer that follows the existingqc_dashboardpattern: HTML + JSON underdashboard/, and a sentinel-delimited block injected intodashboard/index.html. - Input is the browser's category cache,
app/hpo_category_cache.json, so the module never touches the ontology and the two views cannot disagree on what a system is. The CI build already writes that cache before the dashboard. - Charts are server-rendered inline SVG with no JavaScript library; the only script is the sortable/filterable disease table. Dashboard pages are light-theme only, so the page follows that rather than the disorder pages' theme handling.
- Wired into
just gen-dashboardvia a newgen-phenotype-systemsrecipe, so thegenerate-pagesworkflow picks it up without changes.
Deliberately out of scope
- Changing the browser facet, or adding sort-by-spread to the browser. The per-disease numbers are in the JSON if that is wanted later.
- Frequency-weighting phenotypes (a
VERY_FREQUENTfinding counting more than anOCCASIONALone). Frequency coverage is uneven across entries, so it would make the effective count depend on curation depth as much as biology. - A second axis of parentage, MONDO parents. Half the KB's diseases have two MONDO parents; that is a different question from phenotype spread and would be its own page.