Start here: the question tree that turns what a user HAS into the strategies worth running.
The Strategies tab is a catalogue. A catalogue answers "what is there", and someone who arrives with
a gene list from a screen and no idea which of thirty-nine named methods applies to it is not helped
by being shown all thirty-nine. This module is the other way round: it asks what the person has, what
they want to know, and ends at a short ranked list of strategies with their settings already filled
in from the answers.
It holds no Qt, so the tree and the recommendations are testable headless and can be driven from a
notebook::
from starplast import guided, strategies
ctx = strategies.shipped("Tg") # any Context
answers = {"have": guided.HAVE_LABEL, "space": ctx.organism, "label": "compartment",
"goal": guided.PREDICT}
for r in guided.recommend(answers, ctx):
print(r.number, r.title, "--", r.why)
The tree. One question per screen, and a screen's options may depend on every answer before it:
have what the person has: one gene, a gene list, their own measurement, a label, nothing
space which organism's table to work in (the registry's available spaces)
gene / the subject itself: the gene, the gene set, the label column or the numeric column,
genes / the last two listed with their coverage so an empty column is never chosen blind
label /
measure
label asked a second time only when a single gene is to be given a label
goal what they want out of it, from the goals that make sense for what they have
baseline a second measurement, asked only when the goal is to compare two conditions
:func:route is the whole branching, :func:next_step the one question to ask now, and an answer
dictionary with no next_step is finished. Every first answer reaches an end state, and every option
leads somewhere: tests/test_guided.py walks the tree exhaustively to check exactly that.
The recommendations. :func:recommend ranks strategies by, in order of weight:
whether the strategy is one of the handful this goal and this kind of subject are FOR (KEYS),
best first;
whether its scorecard task is one the goal asks for (GOALS[...].tasks);
what the calibration sweep measured for it ON THIS SPACE (GRADE_WEIGHT) -- a strategy that is
reliable for Toxoplasma and weak for Plasmodium is recommended differently in the two;
how long it takes, as a tie-breaker only.
A strategy whose settings cannot be filled from this table at all (no numeric column, no edge layer,
no second species) is dropped before ranking rather than recommended and then failing, and a strategy
that cannot use the subject the person chose is dropped too -- a label-calling method is not offered
for a numeric screen. :func:caveat says plainly when the best thing available is only weak, and
:func:views offers the map gallery or the star map where those answer the question better than any
strategy does.
Nothing here computes anything about the data beyond reading the columns' coverage: it decides what
to offer, and strategies does the work.
HAVE_GENE =
'one gene'
HAVE_SET =
'a gene list'
HAVE_NUMBER =
'my own measurement'
HAVE_LABEL =
'a label'
HAVE_NOTHING =
'nothing yet'
SUBJECT =
{'one gene': 'gene', 'a gene list': 'genes', 'my own measurement': 'measure', 'a label': 'label', 'nothing yet': ''}
MORE_LIKE =
'more like mine'
PREDICT =
'predict it'
EXPLAIN =
'explain it'
PARTNERS =
'partners'
COMPARE =
'compare'
LEARNABLE =
'learnable'
TOUR =
'show me'
@dataclass(frozen=True)
classOption:
One answer to one question: what is stored, what is shown, and the tooltip that explains it.
One screen: its question in a few plain words, and what kind of answer it takes.
:ivar kind: choice (pick one of :func:options), gene (one identifier), genes (a
list, however it is supplied) or column (a choice whose options carry coverage).
GOALS =
{'more like mine': Goal(key='more like mine', label='Find more genes like mine', hint='Rank every other gene by how much it resembles the ones you gave, and say how often that ranking has been right on gene sets whose answer was known.', tasks=('ranking', 'set retrieval'), reason='ranks every other gene by how much it looks like yours', noun='finding more genes like yours'), 'predict it': Goal(key='predict it', label='Predict it for the genes nobody has measured', hint='Learn the chosen label or measurement from the genes that have one, and give a value to the genes that have none.', tasks=('label calls', 'values'), reason='gives the unmeasured genes a value and says how often that value is right', noun='predicting it for the unmeasured genes'), 'explain it': Goal(key='explain it', label='Explain what defines it', hint='Say which measurements carry the thing you chose, which are redundant, and which genes sit where they should not.', tasks=('label calls', 'ranking', 'values', 'replication'), reason='says which measurements carry it and which add nothing', noun='explaining what defines it'), 'partners': Goal(key='partners', label='Find partners or complex members', hint='Rank the genes most likely to be physically or functionally tied to yours, with the evidence each link rests on.', tasks=('ranking',), reason='ranks the genes most likely to be tied to yours, with the evidence for each', noun='finding partners'), 'compare': Goal(key='compare', label='Compare two conditions', hint='Ask which genes matter more in one condition than the other measurements say they should -- and whether the rest of the table explains which.', tasks=('values', 'replication'), reason='finds the genes that shift between your two measurements more than the rest of the table explains', noun='comparing two conditions'), 'learnable': Goal(key='learnable', label='Check whether it is learnable at all', hint='Before trusting any prediction: is this thing in the measurements at all, and for which of its categories?', tasks=('cluster recovery', 'label calls', 'replication', 'ranking'), reason='measures whether the data holds it at all, before anything is predicted from it', noun='checking whether it is learnable at all'), 'show me': Goal(key='show me', label='Show me what this data can do', hint='The strategies that need nothing from you: what the measurements organise, and what they link that nobody has written down.', tasks=('replication', 'ranking', 'cluster recovery'), reason='needs nothing from you and says what the measurements already organise', noun='a first look at what this data holds')}
GOALS_FOR =
{'one gene': ('partners', 'more like mine', 'predict it'), 'a gene list': ('more like mine', 'partners', 'explain it', 'learnable'), 'a label': ('predict it', 'explain it', 'learnable'), 'my own measurement': ('predict it', 'compare', 'explain it', 'learnable'), 'nothing yet': ()}
COST_WEIGHT =
{'seconds': 0.15, 'a minute': 0.05, 'minutes': 0.0}
TRUSTED =
('reliable', 'works when tuned')
MAX_RECOMMENDED =
6
MIN_RECOMMENDED =
3
STEPS =
{'have': Step(key='have', question='What do you have?', kind='choice', tip='Everything else follows from this answer, and you can come back and change it from the trail at the top.', note=''), 'space': Step(key='space', question='Which organism?', kind='choice', tip='The table the questions are asked of. Each species is measured, and its strategies calibrated, on its own.', note=''), 'gene': Step(key='gene', question='Which gene?', kind='gene', tip='Type an accession, a symbol or a word from the product description, and pick the gene from the list.', note='search by id, symbol or product'), 'genes': Step(key='genes', question='Which genes?', kind='genes', tip='The set the strategies start from: paste it, load a file, take the genes gated on the map, or try an example set of a known category.', note='paste, load a file, use the gated genes, or try an example'), 'label': Step(key='label', question='Which label?', kind='column', tip='The annotation the strategies work on. Each is shown with how many genes carry it and how many classes it has, so an almost-empty column is never picked by accident.', note='how many genes carry each one is shown'), 'measure': Step(key='measure', question='Which measurement?', kind='column', tip='The number per gene the strategies work on -- a screen, a fitness score, an abundance. Each is shown with how many genes have a value.', note='how many genes have a value is shown'), 'goal': Step(key='goal', question='What do you want to know?', kind='choice', tip='The goal decides which strategies are recommended and how they are ranked.', note=''), 'baseline': Step(key='baseline', question='Compared with what?', kind='column', tip='The second measurement: the baseline the first one is compared against, such as growth in the dish behind growth in the animal.', note='the measurement the first is compared against')}
defanswered(answers, key:str) -> bool:
Whether key holds a real answer -- an empty list or an empty string is not one.
defsubject(answers) -> str:
Which subject step this branch uses: gene, genes, label, measure or none.
defgoal_of(answers) -> str:
The goal in force: the chosen one, or the tour that "nothing yet" implies.
defroute(answers) -> list:
The steps this branch asks, in order, as far as the answers so far determine them.
The list grows as questions are answered: an unanswered step is the last entry, because what
comes after it is not yet known. When every step in the route is answered the route is complete
and :func:next_step returns nothing.
defnext_step(answers) -> str:
The one question to ask now, or "" when the answers are complete and the list is due.
defdone(answers) -> bool:
Whether this branch has reached its end state: the recommendations.
deftrail(answers) -> list:
(step, value, shown) for every answer so far, in the order they were given.
The progress trail at the top of the panel, and the way back to any earlier question.
defshown(step:str, value) -> str:
One answer in a few words, as the trail shows it.
defoptions(step:str, answers, ctx=None) -> list:
The options of one step, given the answers before it and the table they are asked of.
A gene or genes step has no fixed options -- the answer is an identifier or a list -- and
returns an empty tuple, which is how the view knows to draw a search box instead of buttons.
defcategorical_options(ctx) -> list:
Every label that can be held out, each with its coverage: the space's own held-out labels
first, then the rest by how many genes carry them.
Coverage is on the option, not behind it, because the commonest way to waste an afternoon here
is to pick a column that eleven genes carry; and the space's declared targets lead because a
column with a value for every gene is usually a derived one, not a measured one.
defnumeric_options(ctx) -> list:
Every numeric column worth predicting, the space's own screens first, then by coverage.
defexample_genes(ctx, answers=None) -> tuple:
(category, gene ids): a real gene set to try the gene-list branch on, from the default label.
The same set strategies.example_set gives the Strategies tab's "example" button, so the two
offer the same thing.
defread_gene_file(path:str) -> list:
Every identifier in a gene-list file: the first column of a CSV or TSV, or a plain list.
Qt-free, so a list can be loaded in a script exactly as the panel's "Load a file" button loads
it, and so the reading is testable without a file dialog.
defgene_ids(answers, ctx) -> list:
The genes the answers name, resolved against this table; empty when none were given.
@dataclass(frozen=True)
classRecommendation:
One recommended strategy: why it is here, and the settings the answers already decided.
Whether this table can fill everything the strategy needs before a user chooses anything.
Asked of the strategy's own defaults rather than of a list kept here: a required column, layer
or second species that resolves to nothing on this table means the strategy has nothing to run
on, whatever the user answers. Gene lists are exempt -- the user supplies those.
deffits_subject(strategy, answers, ctx) -> bool:
Whether the strategy can actually take the subject the person chose.
A label-calling method offered for someone's numeric screen would silently run on a default
label instead of on their measurement, and report a number about something they never asked
about. That is the failure this rule exists to prevent.
defgrade_of(key:str, organism:str) -> str:
What the calibration sweep graded this strategy on this space; "" if it never measured it.
defprefill(strategy, answers, ctx) -> dict:
The settings the answers decide, as Strategy.settings takes them.
Only parameters the strategy actually has are filled, and only from an answer that means the
same thing: the gene list into its gene-list parameter, the chosen label into its held-out
label, the chosen measurement into the number it predicts, the baseline into the baseline.
Everything else stays at the strategy's own default.
defrecommend(answers, ctx, limit:int=6) -> list:
The strategies worth running for these answers, best first, each with its reason.
Ranked by :func:_score; anything this table cannot fill, or that cannot take the subject the
person chose, is dropped first. When the handful a goal is FOR leaves fewer than
MIN_RECOMMENDED, strategies whose task matches the goal fill the list rather than leaving it
short -- so no answer is ever a dead end.
defcaveat(recommendations) -> str:
One plain sentence when the best thing available is not one to rely on, else "".
Said rather than hidden: a person who ran the top recommendation and read a number off it
should know beforehand that nothing better than "weak" exists here for what they asked.
VIEW_MAPS =
'maps'
VIEW_STAR =
'star map'
VIEW_DISCOVERIES =
'discoveries'
defviews(answers, ctx=None) -> list:
Panels, not strategies, that answer this goal better -- each with the reason it is offered.