oncodsl / web /app /paramHelpContent.tsx
govindbalki's picture
Upload folder using huggingface_hub
0fff343 verified
Raw
History Blame Contribute Delete
25.3 kB
"use client";
import React from "react";
import GenerationsLoopDiagram from "./diagrams/GenerationsLoopDiagram";
import LambdaTradeoffDiagram from "./diagrams/LambdaTradeoffDiagram";
import MaxSetsDiagram from "./diagrams/MaxSetsDiagram";
import PermutationsHistogramDiagram from "./diagrams/PermutationsHistogramDiagram";
import PrefilterFunnelDiagram from "./diagrams/PrefilterFunnelDiagram";
import SeedDiceDiagram from "./diagrams/SeedDiceDiagram";
export type ParamKey =
| "generations"
| "population"
| "genes_per_set"
| "max_sets"
| "lambda"
| "seed"
| "permutations"
| "prefilter_n"
// Objective help keys reuse the same modal infrastructure.
| "obj_msi"
| "obj_tmb"
| "obj_hpv"
| "obj_unsupervised"
// Misc rich helpers that don't fit the hover tooltip envelope
// (multi-paragraph, table-cell-anchored).
| "module_survival";
export interface ParamHelpEntry {
title: string;
short: string;
detailed: React.ReactNode;
}
// ---------- shared bits --------------------------------------------------
function Item({
label,
children,
}: {
label: string;
children: React.ReactNode;
}) {
return (
<p className="mb-3 last:mb-0 text-sm leading-relaxed text-ink">
<strong className="text-ink">{label}:</strong>{" "}
<span>{children}</span>
</p>
);
}
function Caption({ children }: { children: React.ReactNode }) {
return (
<p className="mb-3 text-xs italic text-muted">{children}</p>
);
}
function Mono({ children }: { children: React.ReactNode }) {
return (
<code className="font-mono text-[12.5px] text-ink">{children}</code>
);
}
function ExampleList({
rows,
}: {
rows: React.ReactNode[];
}) {
return (
<ul className="my-2 ml-5 list-disc text-sm leading-relaxed text-ink marker:text-muted">
{rows.map((r, i) => (
<li key={i}>{r}</li>
))}
</ul>
);
}
// ---------- the registry -------------------------------------------------
export const PARAM_HELP: Record<ParamKey, ParamHelpEntry> = {
generations: {
title: "Generations",
short:
"How many rounds of 'keep the best, breed, mutate' the engine runs. More rounds refine further, but take longer.",
detailed: (
<>
<Item label="What it is">
one &ldquo;generation&rdquo; is a single round of the engine&rsquo;s
loop β€” score every program, keep the best, then breed and mutate them
into the next batch. Generations is how many rounds it runs.
</Item>
<Item label="How it works">
the engine starts with random programs (mostly poor) and each round
nudges the population toward better ones β€” like selective breeding.
</Item>
<Item label="More vs fewer">
more rounds refine the winners further, but with diminishing returns
once it has converged. The harder part is usually <em>discovering</em>{" "}
the right genes in the first place (driven by Population and
mutation), not polishing β€” so extra generations help less once the
curve flattens.
</Item>
<Item label="Typical">30–60.</Item>
<GenerationsLoopDiagram />
<Caption>
A 4-step loop: random programs β†’ score each β†’ keep the best β†’ breed
&amp; mutate β†’ (back to score). The loop arrow is labelled
&ldquo;Γ— Generations&rdquo;.
</Caption>
</>
),
},
population: {
title: "Population",
short:
"How many candidate programs compete in each round. A 'program' is a small pipeline built by composing the DSL operators (Select β†’ Reduce β†’ Fit) over the gene data. More programs explores more options, but is slower.",
detailed: (
<>
<Item label="What it is">
how many candidate programs compete each round. A &ldquo;program&rdquo;
is a small pipeline built from the DSL operators β€”{" "}
<strong>Select</strong> (pick genes) β†’ <strong>Reduce</strong>{" "}
(average them into one score) β†’ <strong>Fit</strong> (use the score
to separate the groups).
</Item>
<Item label="Why it matters">
a bigger population samples more genes and structures each round, so
it&rsquo;s the main lever for <em>discovery</em> β€” finding the right
genes at all. Generations then refine what was found.
</Item>
<p className="mb-1 text-sm font-semibold text-ink">
Example β€” three random programs in a population:
</p>
<ExampleList
rows={[
<Mono key="a">average(g04823, g11201, g00917) β†’ score</Mono>,
<Mono key="b">
average(g15522, g02013) βˆ’ average(g08840, g00231) β†’ score
</Mono>,
<Mono key="c">average(g07788, g13002, g05340) β†’ score</Mono>,
]}
/>
<Item label="More vs fewer">
more explores more options, but each round is slower.
</Item>
<Item label="Typical">150–300.</Item>
</>
),
},
genes_per_set: {
title: "Genes / set",
short:
"The most genes a single score may use. Smaller keeps programs simple and readable.",
detailed: (
<>
<Item label="What it is">
a &ldquo;set&rdquo; is the group of genes averaged together into a
single score. Genes/set is the maximum size of that group.
</Item>
<Item label="Example">
<Mono>
Select(g05347, g00048, g06271) β†’ average β†’ one score
</Mono>{" "}
(uses 3; the cap might be 8).
</Item>
<Item label="Smaller vs larger">
smaller keeps each score simple and readable; larger lets one score
blend more genes.
</Item>
<Item label="Pairs with Max sets">
Genes/set is the <em>width</em> of each group; Max sets is{" "}
<em>how many</em> groups.
</Item>
</>
),
},
max_sets: {
title: "Max sets",
short:
"How many separate gene-scores a program may combine (1 or 2). 2 lets it build a small 'score made of scores'.",
detailed: (
<>
<Item label="What it is">
how many <em>separate</em> gene groups a program may build and then
combine (1 or 2). They use different genes β€” not the same ones
reused.
</Item>
<p className="mb-1 text-sm font-semibold text-ink">Example (2 sets):</p>
<ExampleList
rows={[
<>
Set 1:{" "}
<Mono>average(g05347, g00048, g06271) β†’ scoreA</Mono>
</>,
<>
Set 2: <Mono>average(g15522, g02013) β†’ scoreB</Mono>
</>,
<>
Combine: <Mono>scoreA βˆ’ scoreB β†’ final score</Mono>
</>,
]}
/>
<Item label="Why two">
to capture a <em>contrast</em> β€” e.g. one group that&rsquo;s low in
MSI-H and one that&rsquo;s high; the gap between them can separate
better than either alone (like &ldquo;repair activity minus immune
activity&rdquo;). One set can&rsquo;t express that; two can. If one
group is enough, the engine can still use 1.
</Item>
<MaxSetsDiagram />
<Caption>
Two short chains feed a Combine(sub) node and produce a final score.
</Caption>
</>
),
},
lambda: {
title: "Ξ» (size penalty)",
short:
"How hard the engine is penalised for using more genes. Higher pushes it toward fewer genes (simpler answers).",
detailed: (
<>
<Item label="What it is">
a &ldquo;price per gene&rdquo; that discourages bloated programs.
Every program is graded on a single number:
</Item>
<p className="mb-3 ml-5 text-sm">
<Mono>fitness = separation βˆ’ Ξ» Γ— (number of genes)</Mono>
</p>
<p className="mb-3 text-sm leading-relaxed text-ink">
so the engine ranks by accuracy <em>minus</em> a size tax.
</p>
<Item label="The key idea">
Ξ» is the admission price each gene must beat: with Ξ» = 0.005, a gene
is only worth keeping if it adds more than 0.005 of separation.
</Item>
<Item label="Worked example">
a 3-gene program scores 0.90 β†’ net 0.90 βˆ’ 0.015 = 0.885. Add a 4th
gene that lifts it to 0.903 (only +0.003) β†’ net 0.883, <em>lower</em>,
so it&rsquo;s rejected. A 4th gene that lifts it to 0.91 (+0.01) β†’
net 0.890, <em>higher</em>, so it&rsquo;s kept.
</Item>
<Item label="Turning the knob">
Ξ» = 0 β†’ genes free β†’ bloated, overfit programs. Ξ» small (0.005) β†’
trims useless genes, keeps useful ones. Ξ» large β†’ very lean programs,
but may drop useful genes.
</Item>
<LambdaTradeoffDiagram />
<Caption>
Raw separation rises and plateaus; net fitness peaks at an
intermediate size, and a larger Ξ» shifts the peak to fewer genes.
</Caption>
</>
),
},
seed: {
title: "Seed",
short:
"The starting point for the engine's randomness. The same seed reproduces the exact same run; change it to see a different run.",
detailed: (
<>
<Item label="Why randomness">
the space of possible programs is astronomically large (picking even
8 genes out of 20,000 is ~10²⁹ combinations), so the engine
can&rsquo;t try them all β€” it explores with randomness: random
starting programs, random mutations, random breeding.
</Item>
<Item label="What the seed is">
computers don&rsquo;t make true randomness; a formula generates a
sequence of numbers, each from the previous one. The seed is the{" "}
<em>starting number</em> fed into that formula β€” everything random
flows from it. (One common formula:{" "}
<Mono>next = (1664525 Γ— current + 1013904223) mod 2Β³Β²</Mono>; with
seed 42 the first value is 1,083,814,273, then mapped onto a gene
position.)
</Item>
<Item label="What it does">
think of the seed as one fixed list of dice rolls used in order
across the whole run β€” early rolls pick the starting genes, later
rolls drive mutations. Same seed β†’ same list β†’ identical run.
Different seed β†’ a different list β†’ a different run.
</Item>
<Item label="Why vary it">
because the search has luck in it, run a few seeds (42, 7, 123) and
see whether the same genes keep appearing β€” that tells you a finding
is real, not a fluke.
</Item>
<SeedDiceDiagram />
<Caption>
The seed is one fixed list of dice rolls; early rolls pick initial
genes, later rolls drive mutations.
</Caption>
</>
),
},
permutations: {
title: "Permutations",
short:
"How many times we re-run on deliberately scrambled labels to check the result isn't luck. The real result must beat these chance runs (that's the 'permutation p').",
detailed: (
<>
<Item label="What it checks">
whether the winning score is real signal or could have come up by
luck. The engine searches so hard that some programs separate the
groups well by pure coincidence, so a high score alone isn&rsquo;t
proof.
</Item>
<Item label="How it works">
shuffle the MSI-H / MSS labels across patients at random β€” this
breaks any real gene↔label link, creating a &ldquo;no-signal
world&rdquo;. Re-score there: any score above 0.5 is pure chance.
Repeat many times (this knob = how many) to build a picture of what
luck looks like.
</Item>
<Item label="Reading it">
permutation p = the fraction of shuffled runs that scored β‰₯ the real
winner. p = 0.005 means only 0.5% of chance runs matched it β†’ very
unlikely a fluke. Conventionally p &lt; 0.05 is the &ldquo;unlikely
to be luck&rdquo; line.
</Item>
<Item label="The knob">
with 200 shuffles the smallest p you can report is about 1 / 200 β‰ˆ
0.005; more permutations give a finer, more trustworthy p.
</Item>
<PermutationsHistogramDiagram />
<Caption>
200 shuffled-label runs cluster near 0.58; the real result at 0.89
sits past every one of them, giving p β‰ˆ 0.005.
</Caption>
</>
),
},
prefilter_n: {
title: "Prefilter top-N",
short:
"By default the engine searches all ~20,000 genes, so nothing is pre-excluded. Turning this on narrows to the N most promising genes first β€” faster, but it can drop a real gene that only shows signal in combination.",
detailed: (
<>
<Item label="Default (off)">
the engine searches all ~20,000 genes, so nothing is pre-excluded β€”
the most honest setting for a discovery demo.
</Item>
<Item label="Turning it on">
first narrows to the N genes most individually associated with the
target (computed name-blind, on the training split only), then
searches within that shortlist. Faster, because the space is ~10Γ—
smaller.
</Item>
<Item label="The trade-off">
speed and focus vs completeness. A univariate shortlist can drop a
gene that only matters <em>in combination</em> (no signal on its
own), and it sets a ceiling β€” if a gene isn&rsquo;t in the
shortlist, the engine can never find it.
</Item>
<PrefilterFunnelDiagram />
<Caption>
~20,000 genes β†’ keep the N most individually associated β†’ the GP
searches within N. Off by default = no funnel.
</Caption>
</>
),
},
// ---------- Objective explainers ----------
obj_msi: {
title: "MSI separation",
short:
"Reward programs whose score separates MSI-H from MSS (measured by AUROC). It rewards ANY separator, so it often finds shortcut genes, not the cause.",
detailed: (
<>
<ObjectiveIntro />
<Item label="What it optimises">
a score that ranks MSI-H patients above MSS β€” i.e. tells the two
subtypes apart.
</Item>
<Item label="How it&rsquo;s scored (AUROC)">
pick one random MSI-H and one random MSS patient; AUROC is the
chance the score puts the MSI-H one higher. 0.5 = coin-flip (no
separation), 1.0 = perfect. We use AUROC, not plain accuracy,
because MSI-H is only ~15% of patients β€” &ldquo;always guess
MSS&rdquo; would look 85% accurate while separating nothing.
</Item>
<Item label="Represented as">
<code className="font-mono text-ink">{`{ target: msi, metric: AUROC }`}</code>
{" "}β€” the engine sees only the score + the MSI label, never
gene names.
</Item>
<Item label="The catch">
it rewards ANY separator. MSI-H and MSS differ in thousands of
genes, so it usually grabs easy &ldquo;shortcut&rdquo; genes
(consequences or coincidences), not the causal MMR genes.
</Item>
<ObjectiveFooter />
</>
),
},
obj_tmb: {
title: "Mutation burden",
short:
"Reward programs whose score is negatively associated with mutation burden (low score ↔ high TMB) β€” the broken spell-checker's fingerprint. A sharper proxy for the cause, but still association, not proof.",
detailed: (
<>
<ObjectiveIntro />
<Item label="What it optimises">
a score that goes DOWN as mutation count goes UP β€” the
fingerprint of a broken repair gene (switch it off β†’ mutations
pile up). The repair genes are the MMR set: MLH1, MSH2, MSH6,
PMS2.
</Item>
<Item label="Represented as">
<code className="font-mono text-ink">{`{ target: tmb, metric: correlation, direction: negative }`}</code>
{" "}β€” the engine sees only the score + the TMB numbers.
</Item>
<Item label="Why &lsquo;negative&rsquo; (not just &lsquo;related&rsquo;)">
we reward the score being NEGATIVELY correlated with TMB.
Rewarding any correlation would also pick genes that RISE with
mutations β€” the opposite of the repair signature.
</Item>
<Item label="Correlation isn&rsquo;t causation (honest)">
this is a mechanism-shaped association β€” a much better proxy
for the cause than predicting the label, but it doesn&rsquo;t
prove causation. Genes silenced alongside the repair genes can
mimic the same low-expression↔high-TMB pattern.
</Item>
<Item label="Toward causal">
the program can choose to ADJUST for confounders (age, stage)
via the Effect operator β€” the most causal move the
observational data honestly allows. The engine decides whether
it helps; we don&rsquo;t hardcode it.
</Item>
<ObjectiveFooter />
</>
),
},
obj_hpv: {
title: "HPV detection",
short:
"Find a gene-expression pattern that tells HPV+ tumours apart from HPVβˆ’. Scored by AUROC (0.5 = coin-flip, 1.0 = perfect).",
detailed: (
<>
<p className="mb-4 rounded-md border border-border bg-bg p-3 text-[12.5px] leading-relaxed text-ink">
<strong className="text-ink">HPV detection.</strong> Head &amp;
neck cancers split into two kinds: those caused by the HPV
virus (HPV+) and those that aren&rsquo;t (HPVβˆ’). The engine
looks for a gene-expression pattern that tells the two apart.
</p>
<Item label="How it&rsquo;s scored (AUROC)">
pick one random HPV+ and one random HPVβˆ’ patient; AUROC is
the chance the engine&rsquo;s score puts the HPV+ one higher.
0.5 is a coin-flip, 1.0 is perfect. Because only ~15% of
tumours are HPV+, we use AUROC rather than plain accuracy β€”
&ldquo;always guess HPVβˆ’&rdquo; would look 85% accurate while
separating nothing.
</Item>
<Item label="What the engine sees">
only the score its program produces and the HPV+/HPVβˆ’ label β€”
never gene names. That&rsquo;s what makes recovering the
known biology afterwards a real rediscovery, not a lookup.
</Item>
<Item label="Important">
this is <em>detecting a known viral fingerprint</em> (the
virus switches specific genes on), not discovering a new
cause. The cause is the virus itself. A high AUROC means the
engine recognised the fingerprint blind.
</Item>
<ObjectiveFooter />
</>
),
},
obj_unsupervised: {
title: "Unsupervised",
short:
"No target column at all. The engine searches for a program whose score splits patients into two clean groups; afterwards we check what that split lines up with.",
detailed: (
<>
<ObjectiveIntro />
<Item label="No target at all">
Unlike the other objectives, this one is given NO target β€” only
the gene numbers. The search never sees MSI, TMB, or any label.
</Item>
<Item label="What it optimises">
a score that divides patients into two as-cleanly-separated-as-
possible groups (measured by cluster separation). It is NOT told
what the groups should be.
</Item>
<Item label="Shape vs aim">
it always produces a two-group split, but it doesn&rsquo;t aim
at MSS/dMMR β€” or anything. It finds whatever the strongest
natural division in the data is.
</Item>
<Item label="How we read it">
after the run we check what the discovered split lines up with
β€” MSI? TMB? β€” the &ldquo;post-hoc alignment&rdquo; in the
Result panel.
</Item>
<Item label="The win">
if the strongest natural split turns out to BE the MSS-vs-dMMR
divide, it aligns with MSI at high AUROC β†’ the engine
rediscovered the subtype without ever being told it exists.
</Item>
<Item label="Honest expectation">
it may instead land on a different dominant axis (e.g. immune
hot vs cold) that only partly overlaps MSI. MSI-H is only ~15%
of patients, so a clean recovery isn&rsquo;t guaranteed β€” the
result is HOW MUCH the blind split overlaps MSS/dMMR.
</Item>
<UnsupObjectiveFooter />
</>
),
},
module_survival: {
title: "What β€œSurvives” checks",
short:
"Whether a group still separates the label when you take away a possible confounder β€” something that travels with the label but isn't its biology.",
detailed: (
<>
<Item label="The question">
whether a group still separates HPV when you take away a possible
<em> confounder</em> β€” something that travels with HPV but
isn&rsquo;t HPV biology. If the group&rsquo;s AUROC mostly came
from the confounder, it collapses when the confounder is held
constant; if the signal is real, it holds.
</Item>
<Item label="Site">
HPV+ tumours are mostly in the oropharynx (back of the throat),
so a gene could look like an &ldquo;HPV gene&rdquo; just by
marking that location. The site check re-scores the group using
ONLY oropharynx patients β€” everyone the same location. The two
numbers in the chip read <em>full-cohort β†’ oropharynx-only</em>.
</Item>
<Item label="Purity">
a tumour sample is a mix of cancer cells and immune cells; HPV+
tumours carry more immune cells, so a gene could look like an
&ldquo;HPV gene&rdquo; just by marking immune content. The
purity check re-scores using only the &ldquo;purest&rdquo;
(least-immune) tumours. It&rsquo;s usually <Mono>β€”</Mono> here
because those tumours include almost no HPV+ patients, so
there&rsquo;s nothing to test.
</Item>
<Item label="Reading the chips">
<span className="font-semibold" style={{ color: "#2F6E4C" }}>
βœ“
</span>{" "}
= held up when the confounder was held constant (likely real
signal).{" "}
<span className="font-semibold" style={{ color: "#A64242" }}>
βœ—
</span>{" "}
= dropped past the tolerance (part of it was the confounder).{" "}
<Mono>β€”</Mono> = couldn&rsquo;t test (too few patients per
class in the subgroup).
</Item>
<Caption>
Tolerance is 0.05 AUROC: a subgroup AUROC within 0.05 of the
full-cohort AUROC counts as &ldquo;survives.&rdquo; HNSC/HPV
coherence-on runs only β€” other (dataset, target) pairs omit
this column.
</Caption>
</>
),
},
};
function ObjectiveIntro() {
return (
<p className="mb-4 rounded-md border border-border bg-bg p-3 text-[12.5px] leading-relaxed text-ink">
An objective is the rule that scores every program β€” the fitness the
engine maximises. It&rsquo;s computed only from the program&rsquo;s
per-patient output (and, for the supervised objectives, a target
column β€” MSI, TMB, or HPV β€” never gene names). It sets what
&ldquo;good&rdquo; means; the engine then composes DSL programs to
satisfy it.
</p>
);
}
function ObjectiveFooter() {
return (
<p className="mt-4 border-t border-border pt-3 text-[11.5px] italic text-muted">
What the program chooses is how to build the score and how to
compare it β€” a raw association (Associate) or a confounder-adjusted
one (Effect), plus the correlation kind. What stays outside the DSL
is the compass: the target it&rsquo;s scored against, in which
direction, judged honestly on held-out data. The program can&rsquo;t
pick the target β€” that would let the answer into the language.
</p>
);
}
// Unsupervised has no target / no Associate / no Effect β€” its footer
// is the deeper invariant: the program can't see a target to optimise
// against, which is exactly what makes a downstream label-alignment
// meaningful.
function UnsupObjectiveFooter() {
return (
<p className="mt-4 border-t border-border pt-3 text-[11.5px] italic text-muted">
The only thing outside the DSL here is &ldquo;find the cleanest
split, judged honestly on held-out data&rdquo; β€” there is no target,
and the program can&rsquo;t see one. That&rsquo;s exactly what makes
it meaningful when the split turns out to line up with a known
label (MSI on the colorectal cohort, HPV on head &amp; neck): the
engine wasn&rsquo;t told to look for it.
</p>
);
}