spacr.qt.widgets.regression_results¶
Inspect coefficient tables and diagnostics from a finished regression.
The results panel combines a clickable volcano plot, sortable coefficient table, p-value histogram, Q-Q calibration view, assay controls, guide-support view, model summary, and annotation for the selected gene. Selecting a coefficient in any linked plot or table highlights the same feature in the other views.
Which coefficients every tab draws – gRNA, Gene or Both – is the reader’s
choice, made on the panel beside what it changes. A run fitted at
level='both' fits twice and writes both families into one table, so the
gene half needs no re-fit to be seen. The two fits are separate
multiple-testing families and are corrected as such, and each row says which
fit it came from, so a gene and its guides are distinct rows in the table, on
the plot and in the exported copy. A level this run has no rows at says why it
has none rather than drawing an empty tab.
Gene and gRNA filters are applied to coefficient-level views, including their calibration diagnostics. Well-level residual and influence diagnostics remain unfiltered because wells do not belong to either coefficient family. Plot selections are joined by feature keys rather than drawing positions, so sorting or aggregation does not change which result is selected.
Classes¶
Volcano, table and diagnostics for one finished regression. |
Functions¶
|
The regression type a results path was written under, if it says. |
|
The results CSV for |
|
Find regression result tables beneath a path. |
|
Find the model summary associated with a regression result. |
|
|
|
Read a run's primary table, and any LEVEL it left in a sibling file. |
|
Return a model summary or an explanation of its absence. |
Module Contents¶
- class spacr.qt.widgets.regression_results.RegressionResultsPanel(parent=None, external_volcano: bool = False)[source]¶
Bases:
PySide6.QtWidgets.QWidgetVolcano, table and diagnostics for one finished regression.
Initialize the regression results panel.
- Parameters:
parent (QWidget or None, optional) – Parent widget.
external_volcano (bool, default=False) – Build and wire the interactive volcano plot without adding it to this panel’s layout. Use this when a host places the volcano in a larger external view; selection and redraw behavior are unchanged.
- apply_plot_state(state) bool[source]¶
Put a saved
plot_state()back. Returns whether it applied.ONE REDRAW, not one per setting. Every public setter here redraws, so restoring nine of them through nine setters would draw the panel nine times on every run switch – and the intermediate frames are of combinations the user never chose.
- Parameters:
state – dict as
plot_state()returns it; keys it lacks keep their current value. Nothing applies when it is not a dict or no table is loaded.
- apply_workspace_state(state) bool[source]¶
Put the remembered views back, and reopen the run that was open.
Returns whether anything was put back. THE STORE IS MERGED, NOT REPLACED: restoring a workspace into a session that already has runs open must not silently drop the views the user built since. A run in both is taken from the document, which is what the user asked to restore.
- Parameters:
state – dict as
workspace_state()returns it; its"runs"plot states are merged in and its"path"is loaded again when it still exists. A non-dict applies nothing.
- ask_refit() bool[source]¶
Offer another model for the same data, and ask for the run.
- Returns:
True if a re-fit was asked for.
The panel goes no further than emitting. It has no worker, no console and no Stop button, and a widget that started a background fit with none of those would be a run the user cannot watch or stop.
- both_levels_note() str[source]¶
One sentence naming the level shown and the one that is not.
THE RUN FITS TWICE AND THE PANEL SHOWS ONE. The design splits
level='both'into a guide fit and a gene fit – two tables, two multiple-testing families – and the panel opens on guides so a gene is not drawn once per guide. Both of those are right.Previously, nothing said so. In a representative GLM run, both fits completed –
results_grna.csvhad 15 rows andresults_gene.csvhad 5 – while half of it was invisible with nothing on screen naming the other half, so “it only runs once” is the honest reading from the user’s side.Empty when there is nothing to say: one level in the table, or no filter on. A note that fires every time is a note nobody reads.
A LEVEL WITH NO ROWS IS A DIFFERENT SENTENCE, and this one gives way to it. “genes only: 0 of 789 coefficients. The guide fit is in this run too” is true and is not an answer – the reader is looking at an empty tab and needs to know that this run HAS no gene fit and why. See
missing_level_note().
- browse_for_results() bool[source]¶
Ask for a results folder and load it. False if nothing was chosen.
Companion to
load(): the panel is otherwise only ever filled by a run finishing, which is no use to a user who has results and no run.While a load is already in flight the same button is the CANCEL, so the one control that starts a read is also the one that abandons it; a read with no way out is the freeze this loader was moved off the GUI thread to remove.
Build the coefficient-level menu with row counts.
- Returns:
PySide6.QtWidgets.QMenu – Menu entries for genes, guides, and both levels. Each label reports how many rows the corresponding view contains.
- cancel_load() bool[source]¶
Cancel an active result load while preserving the current view.
- Returns:
Whether a load was active.
- clear_diagnostics(reason: str = '') None[source]¶
Empty the three well-level tabs and SAY why they are empty.
- Parameters:
reason – the specific reason, when there is one. The default is
NO_MODEL_MESSAGE– “no run in this session has fitted anything yet”, which is the ordinary case and still an answer.
THIS DROPS THE MODEL, and that is what it is for: it is called when a NEW TABLE arrives, and the previous fit has nothing to say about it. A failure to DRAW the diagnostics is a different event and goes through
_clear_diagnostic_views(), which leaves the fit alone – a failure in the view must not destroy the thing being viewed.
- closeEvent(event)[source]¶
Stop the results loader before closing the widget.
Qt requires a running
QThreadto outlive every object that owns it. The bounded shutdown cancels the load and waits briefly; a slower worker is transferred tospacr.qt.bridge.drain_thread()so that closing the screen neither terminates the worker nor blocks on it.- Parameters:
event – Qt close event passed to the parent implementation.
- diagnostic_plots() tuple[source]¶
The three well-level tabs, in the order they are shown.
Public because it is how a caller asks the panel to restyle, export or interrogate them WITHOUT reaching past the panel into its widgets – which is how one screen ended up depending on another’s private surface.
- family_note() str[source]¶
Which multiple-testing family the p-value and Q-Q tabs are drawing.
THE ONE THING FILTERING A Q-Q CHANGES THAT FILTERING A VOLCANO DOES NOT. A volcano of the guides is the same dots with some removed; a Q-Q of the guides is a DIFFERENT DIAGNOSTIC – the expected quantiles are recomputed over 900 tests instead of 1,200, so the diagonal moves, the inflation figure at the median is a different number, and the excess in the histogram’s first bin is this family’s excess and not the run’s. A reader who does not know which one is on screen cannot use either.
- filtered_frame()[source]¶
The coefficient table at the chosen level, or
None.This is what every tab is drawn from – see
refresh_views().results_frame()is the RUN’s table and stays whole: the filter is a view, not an edit, and a caller exporting the results must get the fit rather than whatever the user last right-clicked.
- forget_plot_state(source) bool[source]¶
Drop one run’s remembered plot. The design deletes a run.
- Parameters:
source – the run’s folder, or any path inside it – the CSV a caller happens to be holding answers the same as the folder.
- Returns:
whether there was anything to drop.
A deleted run must take its state with it, or a later run written into the same folder inherits the deleted one’s level and colouring and there is nothing on screen saying where they came from.
- forget_run(source) bool[source]¶
A run was deleted: drop its view, and clear the panel if it is IT.
THE ORDER IS THE WHOLE POINT, and the failure sequence is explicit before 146 existed: “deleting the run CURRENTLY ON SCREEN must also clear the panel, or leaving that run re-saves the state that was just forgotten.”
set_framecalls_remember_plot_stateon the way out of a run, so forgetting first and clearing second files the deleted run again, under the same key, with the state it was just relieved of.So the panel lets go of the run FIRST –
_pathand_framecleared by hand rather than throughset_frame, which is a route INTO a run and re-saves on the way – and forgets afterwards.- Parameters:
source – the run’s folder, or any path inside it.
- Returns:
whether anything was dropped: a state, the table, or both.
- homogeneity_stats() dict[source]¶
The numbers behind the verdict –
regression_qc’s own dict.spearman_rho,spearman_p,levene_p,quartile_sd_ratio,verdictandn_points, exactly asspacr.regression_qc.draw_panel()returned them for the saved scale-location panel. Empty when there is no verdict.
- homogeneity_verdict() str[source]¶
Whether the residual spread is constant, in the words on screen.
Public because it is the one sentence on the Scale-location tab that changes what a reader does, and a test that reads it back has to be able to do so without reaching into a label.
- judge_homogeneity(ctx=None) str[source]¶
Say whether the residual spread is constant, and what to do if not.
- Parameters:
ctx – a
spacr.regression_qc.RegressionQCContext.Nonere-judges the context the diagnostics were last drawn from, so a caller does not have to keep a copy of it to ask again.- Returns:
the verdict now on screen.
NOT ONE NUMBER RE-DERIVED HERE. The statistics come from
spacr.regression_qc.draw_panel()– Spearman’s rho on sqrt|standardised residual| against fitted, a Brown-Forsythe test across quartiles of the fitted value, and the quartile SD ratio – drawn into a throwaway axes purely to get the dict it returns. That module is 3,444 lines and already computes them for the PDF the run writes; a second implementation here would disagree with it about the same fit inside a week, and the reader would have no way to tell which of the two was wrong.TWO STATISTICS, NOT ONE, and that is regression_qc’s decision rather than this panel’s: a rank correlation is exactly zero for a SYMMETRIC funnel – wide at both ends, narrow in the middle – which is what a mis-specified link produces, and a panel reporting “no trend in spread” over one would be a confident wrong answer.
- level()[source]¶
None,"gene"or"grna"– which family every tab shows.Public because it is the one piece of state that decides what SIX tabs are drawing, and a caller that has to read it off a private attribute is a caller that will one day set it there too.
- level_counts() dict[source]¶
{None: n, "gene": n, "grna": n}for the table on screen.Counted rather than assumed, and put in the MENU: “genes only” that silently draws 400 of 1,213 points is a filter a user applies without knowing what they gave up.
- load(path) bool[source]¶
Load a results CSV, a run folder, or a parent of one.
THE SYNCHRONOUS ENTRY POINT, kept for tests and headless callers. The GUI goes through
start_load(); both end in_apply_loaded_run(), so the two cannot drift – which is the rulestart_mergeandmergealready follow.EVERY WAY THIS FAILS SAYS SO. It used to return False five different ways and leave the user looking at a table with columns and no rows, which is indistinguishable from a run that produced nothing.
- Parameters:
path – results CSV, run folder or a folder above one (searched up to
MAX_SEARCH_DEPTHdeep);~is expanded.
- missing_level_note() str[source]¶
Explain why the selected model level contains no result rows.
Guide permutations and guide-only fits legitimately omit gene-level rows. The returned note identifies the available level; an empty string indicates that rows are present.
- plot_state() dict[source]¶
What the user has built on the plot, as data.
The design: “every regression run should have its own interactive volcano plot.” A run does not have a volcano today – the screen has one and a run borrows it – so opening run B destroyed the level, the colouring, the axis limits, the effect cut and the selection a user had chosen on run A.
EVERYTHING HERE BELONGS TO THE RUN AND NOT TO THE WIDGET. The colour column is stored by NAME rather than by index, because the combo box is rebuilt from each table’s own columns and index 3 is a different column in the next run.
- ranking()[source]¶
(kind, column)for the table on screen.kindis"p-value","selection-frequency"orNone. The panel is handed coefficient tables from every backend spaCR can fit and they do not agree on this: OLS reportsP>|t|, a GLM or a Poisson fitP>|z|, spaCR’s own writerp_value, and the penalised backends report no p-value whatsoever.
- refresh_views() None[source]¶
Draw EVERY tab from the coefficient table at the chosen level.
ONE PIECE OF STATE, READ SIX TIMES.
_levelused to reach the volcano and nothing else, so “genes only” left the coefficient table, the p-value histogram, the Q-Q, the control panel and the guide support showing the whole fit – five tabs disagreeing with the sixth, at the same time, with nothing on screen saying which was which. That is worse than no filter at all: a reader who trusts the volcano and reads the inflation figure off the Q-Q beside it has combined two different multiple-testing families and cannot tell.Public because it is also what a caller does after changing something the panel does not own – and because a private redraw that four methods have to remember to call is how the fifth one forgets.
- remembered_runs() tuple[source]¶
Return run paths whose plot state has been stored.
The currently displayed run is omitted because its state remains live in the widgets until the panel switches away from it.
- results_frame()[source]¶
Return the complete coefficient table for the displayed run.
The table is unaffected by the gene/guide view filter. Use
filtered_frame()for the rows currently shown by the result tabs.
- run_folder() str[source]¶
Return the folder for the regression run shown by this panel.
Live results may store a directory while results loaded from disk may store a table path; both resolve to the containing run folder. Return an empty string for an unassociated CSV or an in-memory frame.
- run_name() str[source]¶
The run on screen, as the name the Runs tab calls it.
results/<kind>_<n>is the folder a run writes, so the basename isols_3– which is what the Runs table shows, what the figure grid heads its section with and what the montage names. One vocabulary prevents three views from calling the same run three different things.
- say(text: str, detail: str = '') None[source]¶
Put a sentence where the user will read it.
Public because the panel is not the only thing that can fail to fill it: the caller that decides WHICH folder to hand over can come up with nothing at all, and that has to reach the same header rather than being logged at debug level and dropped.
- Parameters:
text – concise status shown in the results header and retained for
status_text().detail – the tooltip; the long form, when there is one.
- selected_keys() list[source]¶
Return identifiers selected in the results table.
If the table has no active multiselection, return the most recent single selection used by linked result views.
- set_baseline(kind, name=None) None[source]¶
Measure every effect from
kind– seespacr.baseline.The interactive volcano only. The saved figures take their own baseline argument, so a user who moved it here and then exported gets a picture and a caption that agree.
- Parameters:
kind – baseline kind from
spacr.baseline:"zero","controls","named"or"value";Noneor empty means zero.
- set_compartment(name) None[source]¶
Colour one TAGM/LOPIT compartment against grey, or none.
ONE. “Everything is grey except what the sentence is about” – and a 27-colour volcano is both what that rule forbids and, measured, the version whose legend cost 40 ms of a 49 ms redraw.
- Parameters:
name – TAGM/LOPIT compartment name to pick out,
spacr.localisation.ALLto colour every annotated coefficient by its compartment, orNone/empty for none.
- set_diagnostics(model, regression_type=None) bool[source]¶
Fill the residual tabs from the model a run just fitted.
- Parameters:
model – the fitted model, straight off
perform_regression’s return payload.Noneclears the tabs and says why.- Returns:
True when the tabs were filled.
EVERY NUMBER COMES FROM
spacr.regression_qc. The residuals, the standardisation, the leverage and Cook’s distance are the arrays that module already computes for the report it writes to disk – so the live tab and the saved PDF cannot disagree about which well is influential, which they would within a week if this panel did its own arithmetic.NEVER RAISES INTO THE GUI. A model class that cannot be diagnosed – the penalised backends keep no design matrix – puts its reason on the three tabs instead, because “this fit cannot answer that” and “this tab is broken” must not look the same.
- set_frame(frame, source: str = '') bool[source]¶
Show an already-loaded coefficient table.
- Parameters:
frame – the coefficient DataFrame;
Noneor an empty frame is reported in the status line and returnsFalse.
- set_level(level) None[source]¶
Draw only genes, only guides, or both – ON EVERY TAB.
A FILTER ON THE PANEL, not a mode of the run. The coefficient table already carries both –
featureisgene_fraction:gene[...]orfraction:grna[...]– so this needs no re-fit and no second table, which is why it is better here than in the settings where it used to live.ONE PIECE OF STATE. Reached from the volcano’s right-click menu and from the coefficients table’s, and read by every draw path, because a filter that reaches four of six tabs is worse than one that reaches none: the two then disagree on screen at the same time and nothing says which is which.
- Parameters:
level –
"gene"for genes only,"grna"for guides only, orNonefor both.
- set_p_value_kind(kind) None[source]¶
Draw the volcano against the raw or the adjusted p-value.
- Parameters:
kind –
"raw"or"adjusted".
- set_run_settings(settings) None[source]¶
Remember the settings that produced the table now on screen.
Called by the screen when a run finishes, because the run’s own settings are better than anything read back off disk: the saved copy under
settings/is overwritten by every later run of the same screen, so on a second run it describes the wrong one.- Parameters:
settings – the run’s settings mapping, copied;
Noneor empty forgets them.
- set_summary(model, regression_type=None) bool[source]¶
Fill the Summary tab from the fitted model.
- Parameters:
model – fitted model whose
summary()is shown, orNoneto use the panel’s own model, falling back to a summary saved beside the loaded results.- Returns:
True when a summary was rendered.
Rendered from
model.summary()verbatim rather than rebuilt: the point of asking for the statsmodels summary is to get the statsmodels summary, and a re-implementation would differ from every textbook and every other tool the reader compares it against.
- set_threshold_method(method) None[source]¶
Measure the effect-size cut a different way, and redraw.
- Parameters:
method – a key of
spacr.thresholds.METHODS, e.g."none"or"std".
- set_threshold_multiplier(multiplier) None[source]¶
How many spreads wide the cut is.
- Parameters:
multiplier – number of spreads, converted to
float.
- show_panel(key: str) bool[source]¶
Raise the tab that holds the live panel named
key.- Parameters:
key – a key from
FigureGridView.set_live_tiles().- Returns:
Truewhen the corresponding tab exists and was raised; otherwiseFalse. The available tabs depend on the fitted model and whether the volcano is displayed outside this panel.
- start_load(path) bool[source]¶
Start loading a regression run outside the GUI thread.
File discovery and CSV reads run in a worker. Only the resulting data is returned to the GUI thread.
- Parameters:
path – Regression-results directory to load.
- Returns:
whether a load was started.
Falsewhen one already is – a second click must not read the same folder twice.
- spacr.qt.widgets.regression_results.backend_of(path) str | None[source]¶
The regression type a results path was written under, if it says.
perform_regressionwrites toresults/<kind>[_n]/. The folder name is therefore the only evidence available when a table does not record its backend directly.- Parameters:
path – results file or folder path, or
None. A path component that, lower-cased and without a trailing_<n>, names one ofspacr.hits.NO_P_VALUE_TYPESis returned; otherwiseNone.
- spacr.qt.widgets.regression_results.find_results_table(path) str | None[source]¶
The results CSV for
path: a file, a run folder, or a parent of one.Those are the three things a user actually has to hand when they want to look at a regression again, so all three are accepted. Where a parent holds several, the most recently modified wins – see
find_results_tables().- Parameters:
path – results CSV, run folder or a folder above one.
- spacr.qt.widgets.regression_results.find_results_tables(path, *, max_depth: int = MAX_SEARCH_DEPTH, limit: int = MAX_CANDIDATES) list[source]¶
Find regression result tables beneath a path.
- Parameters:
path (path-like) – Results CSV, run directory, or parent directory to search. A CSV is returned directly; missing paths and non-CSV files return no matches.
max_depth (int, default=MAX_SEARCH_DEPTH) – Maximum directory depth to descend below
path. The root directory is still inspected when this value is zero.limit (int, default=MAX_CANDIDATES) – Stop walking after at least this many candidate tables have been collected. All recognised tables in the final run directory are kept, so the returned count can be slightly larger than this value.
- Returns:
list of str – Recognised result-table paths. Run directories are ordered by the newest modification time among their tables. Within a run,
results.csvprecedes the gene and gRNA views according toRESULT_FILENAMES.
- spacr.qt.widgets.regression_results.find_summary_file(path) str | None[source]¶
Find the model summary associated with a regression result.
- Parameters:
path (path-like) – Results CSV, run directory, or parent directory containing a discoverable results table.
- Returns:
str or None – Path to the first supported summary file, or
Nonewhen no summary is found. Both the current and legacy summary filenames are accepted.
- spacr.qt.widgets.regression_results.for_table(frame)[source]¶
framewithout the columns that are blank for every row shown.A permutation run’s table is the UNION of two schemas: a guide row has
guideandwells_with_guide, a gene row hasgene,wells_with_geneandguides_in_gene. Showing the union means that whichever level is chosen, a third of the columns are empty – and the gene the reader is looking for is named thirteen columns to the right of a blankguidecell, which is what makes a gene view read as broken even once the rows are there.Dropping a column that no visible row fills is safe because it carries no value for this selection: “how many wells hold this guide” has no answer for a gene. The identity and significance columns in
TABLE_KEEP_COLUMNSstay regardless.- Parameters:
frame – the rows about to be shown.
- Returns:
the same rows, narrowed. The input is not modified.
- spacr.qt.widgets.regression_results.read_run_tables(tables, progress=None)[source]¶
Read a run’s primary table, and any LEVEL it left in a sibling file.
results.csvis meant to hold every level a run produced – a fitted run atlevel='both'writes its guide and gene rows into one table and the panel filters them apart by thelevelcolumn.THE PERMUTATION PATH DID NOT ALWAYS DO THAT. It tested genes, corrected them as their own family, wrote them to
results_gene.csv, and leftresults.csvholding guides alone – so a reader who asked for genes was shown nothing while the rows sat in a file nothing opened. The writer is fixed, and this reads the older runs too, because a folder on disk does not update itself.A sibling is merged only when it brings a level the primary table does NOT already carry, so a run that already holds both is untouched and nothing is ever counted twice.
- Parameters:
tables – candidate paths, primary first, as
find_results_tables()returns them.progress – optional
progress(done, total, name), called before each file is read.totalcounts the primary table and every sibling beside it, so it is an upper bound: a sibling whose level the primary already carries is counted and not read.
- Returns:
(frame, found, merged)– the table, the path it came from, and the sibling paths folded into it.
- spacr.qt.widgets.regression_results.summary_text(model, regression_type=None, *, path=None, reason: str = '') str[source]¶
Return a model summary or an explanation of its absence.
- Parameters:
model (object or None) – Fitted model. Objects with a callable
summarymethod use that method;Nonetriggers lookup of a summary saved with the run.regression_type (str or None, optional) – Backend name included when the fitted object has no statsmodels-style summary.
path (path-like or None, optional) – Results CSV, run directory, or parent directory. When
modelisNone, the function looks beside the selected results table for a summary written during fitting.reason (str, optional) – Known reason that no live model is available. When omitted, the explanation is limited to what can be inferred from
path.
- Returns:
str – Saved or live summary text. If no summary is available, a message names the relevant backend, path, or read failure instead of returning an empty string.
Notes
A live statsmodels summary is returned from the fitted model rather than reconstructed. When the run summary is available on disk, it is included with the model summary.
Nested helpers¶
- RegressionResultsPanel._draw_guide_support.verdict(row)¶
Whether one row’s guide support is sufficient to trust.
spacr/qt/widgets/regression_results.py:2407
- RegressionResultsPanel._read_run.reading(done, total, name)¶
Pass one file of the read on as step 2.
spacr/qt/widgets/regression_results.py:1798