starplast.analysis_panel
The analysis tab: choose the data, tune the map, cluster it, and ask what the clusters mean.
This is the interface to embedding, clustering, tuning and search. It is deliberately arranged in
the order the work is actually done, top to bottom, because the order matters scientifically:
1 data which blocks feed the map, and how missing values and scales are handled
2 map UMAP hyperparameters, with a seeded walk to pick them
3 clusters DBSCAN / HDBSCAN, with a walk to pick those too
4 inference what the clusters correspond to, held-out features only
5 search walk dataset combinations looking for structure that recovers a held-out label
6 validation hide labels you already have and see whether the clustering puts them back
Long jobs run on a worker thread. The signal is relayed through a bound method rather than connected
directly, because a directly-connected finished handler runs on the worker thread and touching widgets
from there is a crash waiting for a slow machine.
Runs one callable off the GUI thread and reports back.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
Why a spin box stops where it does, read off the widget itself.
A minimum and a maximum are decisions, and an unexplained one reads as arbitrary. Generated rather than written per control so it cannot fall out of step with the range actually set.
Data selection, tuning, clustering and the hypothesis battery.
Build analysis tabs for a gene table, with optional stores and job services.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL
types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.
Take newly imported columns, so the Data tab can offer them as a block.
Every feature block and whether it is ticked. The read-only replacement for block_cb.
Rebuild the configuration on one row and put it on screen with its clustering.
Through search.rebuild, which is what the recovery table uses: one implementation, so the
two tables cannot come to disagree about what a row means. A row here carries the newer
coordinates as well -- the method, the algorithm -- and rebuild reads what it recognises.
A comma-separated list of values to sweep, or a min:max:step range.
Accepts "5, 15, 25" and "5:100:5" alike, because both are natural ways to say the same thing and refusing one of them is a papercut on the control used most. An unparsable entry falls back to the default rather than raising: a walk is expensive to start, and losing one to a stray comma is worse than sweeping the default set.
The categories TICKED in the list, or an empty list meaning all of them.
Ticks, not highlights. A highlight is destroyed by the next ordinary click, which made a set of four categories something a reader had to rebuild every time they looked away.
The scoring choices, as the keyword arguments objectives.score takes.
Returned as one dict so the settings travel together into the job and into whatever records the run -- a score whose objective is not recorded beside it cannot be compared with another.
Save the candidate list as annotations, with the numbers that justify each one.
Only from here, and only from a validated run: the refusal is the feature. The candidates
and their error rate are joined in annotations.from_candidates so a precision from a
different category or a different clustering cannot travel with a row.
The node-table columns the current embedding is built from, categoricals included.
The real column names, not the block names. The block names were what the validation tab passed as "the columns the embedding used", which made the circularity guard compare a compartment against "expression_summary" -- so it never fired, and the tab would have scored a target the map was built on.
Put an error rate on an annotation by hiding labels that are already known.
The genes one category's cluster would have you annotate, with the numbers attached.
Never a bare list. Each candidate carries the composition of the cluster it comes from and whatever independent agreement exists, because the same list looks identical whether it is 90% right or 6% right -- and on this proteome it has been 6%.
Give a results table the two things every results table needs.
Clicking a row shows the map that row is about -- rebuilt from the row's own configuration where the row names one, and the current map with its clustering where the row is about a category of it. A table of scores is not a result; the map it describes is, and a score nobody can look at is the thing this application exists not to produce.
Right-clicking saves it. A table that can only be read on screen has to be re-derived anywhere else it is needed, and the run that produced it is minutes long.
Wired here rather than per tab so a new table cannot arrive without either.
The key a saved file records for this table -- what makes a load refuse a mismatch.
Every results table by its key. What "all tabs at once" means, in one place.
Write one table so it can be loaded back and clicked, not merely read.
Load a saved table back into this one, refusing a file that came from another.
Refused rather than loaded anyway: the rows of one table are recipes for rebuilding a map and the rows of another are per-category scores, so a validation file dropped into the walk tab produces rows nobody can rebuild and an error message about the wrong thing.
Write a results table to CSV, whole rather than as displayed.
The full frame, not the 200 rows the widget shows: the truncation is there to keep the window responsive, and a file that silently stopped at row 200 would be a different result from the one that was computed. How many rows were written is reported for that reason.
Put the selected rows on the clipboard, tab-separated, with their headers.
One row as {column: text}, which is how a row is turned back into a configuration.
Ask every job this panel started to stop. Returns how many were asked.
Sweep a grid of UMAP hyperparameters, reporting each configuration as it finishes.
The walk emits per configuration and the table fills a row at a time, so a sweep of 288 settings -- half an hour -- can be read while it runs instead of showing nothing until it ends. The ranking still arrives at the end, because ranking needs the whole sweep.
Each embedding is written through the store as it is computed, so a walk that is stopped half way still leaves behind every configuration it finished, with the recipe to rebuild it.
Build and display the configuration on one row of the walk table, and cluster it.
The walk scores configurations and returns numbers; this is what turns a number back into something you can look at. It rebuilds rather than caching all of them, because a walk of 288 embeddings over 8,140 genes is gigabytes and one rebuild is seconds -- and it goes through the same path as "build this map", so the result behaves identically.
The clustering comes with it when the row reports one, at the same min_cluster_size the
walk used: the row says "11 clusters", and a map shown without them leaves the reader to
take that number on trust.
Re-cluster the current map with the settings on one row of the clustering walk.
The walk scores clusterings of the map that is already on screen, so there is nothing to rebuild -- the row is a set of parameters, and this applies them and colors the map by the result. Without it the tab reported silhouettes for clusterings nobody could see.
Rebuild the exact configuration on one row of the search table, and show it clustered.
This is the table where a row is a whole recipe -- blocks, missing-value policy, scaling, both UMAP hyperparameters, the clustering size, the seed and the subsample -- and until now it was the one table whose rows could not be looked at. A recovery score with no way to see the structure it scored is exactly the "trust me" this project refuses elsewhere.
Rebuilt on the SAME subsample, from the seed and sample size the row records, and with the same columns excluded. Rebuilding at full size, or over a different draw, would put a different map on screen from the one the row's numbers describe.
Color the map by the clustering a battery row is about, and say which cluster it names.
An Inference row is not a configuration -- it is a feature of the map already on screen -- so the map does not change. What clicking it does is put the clustering the row was scored against back on the map, because reading "cluster 3 is 90% apicoplast" while looking at a map colored by compartment is a needless act of translation.
Build one embedding from the current spec and show it in the 3D view.
then_cluster clusters it in the same job at that min_cluster_size, which is how a walk
row arrives with the clustering its score counted. One job rather than two, because the two
belong together: a map that appears for a moment without the clusters the row promised
reads as the clustering having failed.
Store the current embedding with its full recipe, so it can be rebuilt exactly.
Walk dataset combinations, scoring each by how well it recovers the held-out label.
Every configuration is embedded, clustered at each min_cluster_size in the grid, and
scored per category -- the automated walk. Results stream: the configuration table and the
per-category table fill as they are computed, and each embedding's best clustering appears
in the gallery, colored by that clustering rather than by the map's color mode. A search is
hundreds of runs and a table that arrives at the end is a table nobody watches.
Rebuild the configuration behind one per-category row, and say which cluster to look at.
The same rebuild as the configuration table -- the recipe is on the row -- so a category score can be taken from a number to a map in one click. The cluster it names is the one the score is about, which is not obvious from a map colored by 40 clusters.