Repository navigation
Algorithm result views: figures, coefficient views and chart fixes - #49
KFilippopolitis wants to merge 43 commits into
Conversation
| /** A raw snake_case code (a created column such as stroke_territory_cohort, or a level such as | ||
| * cluster_2) reads as words; labelled names pass through unchanged. */ | ||
| export function readable(code: string): string { | ||
| if (!/^[a-z0-9]+(_[a-z0-9]+)+$/.test(code)) return code; |
There was a problem hiding this comment.
Not changed: false positive. [a-z0-9] cannot match _, so each underscore splits the code in exactly one way and there is no catastrophic backtracking. Adversarial 100,000-character inputs finish in about 2 ms. Leaving the alert for a maintainer to dismiss.
🤖 Addressed by Claude Code
| template: ` | ||
| <div class="key-figures" role="group" aria-label="Key figures"> | ||
| @for (figure of figures(); track figure.label) { | ||
| <div class="key-figure"> | ||
| <span class="key-figure-label">{{ figure.label }}</span> | ||
| <span class="key-figure-value">{{ figure.value }}</span> | ||
| @if (figure.note) { | ||
| <span class="key-figure-note">{{ figure.note }}</span> | ||
| } | ||
| </div> | ||
| } | ||
| </div> | ||
| `, |
There was a problem hiding this comment.
Not changed: false positive. {{ figure.label }} and the other braces sit in the component's inline Angular template, where {{ }} is Angular interpolation, not a JavaScript placeholder. Leaving the alert for a maintainer to dismiss.
🤖 Addressed by Claude Code
| template: ` | ||
| <div class="key-figures" role="group" aria-label="Key figures"> | ||
| @for (figure of figures(); track figure.label) { | ||
| <div class="key-figure"> | ||
| <span class="key-figure-label">{{ figure.label }}</span> | ||
| <span class="key-figure-value">{{ figure.value }}</span> | ||
| @if (figure.note) { | ||
| <span class="key-figure-note">{{ figure.note }}</span> | ||
| } | ||
| </div> | ||
| } | ||
| </div> | ||
| `, |
There was a problem hiding this comment.
Not changed: false positive. {{ figure.label }} and the other braces sit in the component's inline Angular template, where {{ }} is Angular interpolation, not a JavaScript placeholder. Leaving the alert for a maintainer to dismiss.
🤖 Addressed by Claude Code
7e5167e to
556a6b5
Compare
…nent The key figures, coefficients card and model-fit details lived inline in AlgorithmResultComponent. They now sit under visualisations/regression as a pure view builder (buildRegressionView) plus a presentational component, and the number formatting is shared from visualisations/result-format.ts so the next summary views can reuse it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…R test Logistic regression reused the raw coefficient table. It now gets the same summary as linear regression: odds ratios with their 95% CI, an interval-vs-1 column drawn on the log-odds scale, McFadden pseudo R², AIC, and a model likelihood-ratio test derived from ll and ll0 (chiSquarePValue). Log-odds, standard error and z stay one click away under All columns and in the CSV. The single-predictor regression line chart could no longer be reached for either model, so it is removed with its registry entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
t-tests, Mann-Whitney U, chi-squared, Fisher's exact, one- and two-way ANOVA and standardized mean difference opened on a metric/value table where p showed as 0, 1.110e-16 or 3.873e-238. They now lead with a key-figures strip: the p-value APA style with its test statistic, and an effect size with its conventional magnitude (Cohen's d, P(A > B) from U, Cramér's V, eta squared, partial eta squared per two-way term). The detailed tables stay underneath unchanged. KeyFiguresComponent is shared with the regression view and wraps on its container's width, so it also fits 360px compare columns. The t-test mean-difference chart repeated the first figure on an axis with duplicated tick labels and no zero line, so it is removed. SMD comparisons now show the grouping variable's enumeration labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every chart section was headed "Effect estimates" with a lead about comparing factors, including k-means centres, histograms and PCA. The chart registry now carries a section title per algorithm; its inputPath field and default entry were never anything but empty and are gone. The one-way ANOVA mean plot called its whiskers a 95% confidence interval, but Exaflow's ci_info m-s/m+s are mean minus/plus one sample SD. The plot now says so, drops the title and p-value the section heading and key figures already give, and lets ECharts pick round axis bounds instead of colliding 90/92 labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lours The 3-variable k-means centres chart used scatter3D from the echarts-gl UMD bundle, which registers on a different ECharts instance than ngx-echarts, so it logged "Unknown series scatter3D" and drew nothing. Three or more variables now use the parallel-coordinates profile already used past three, and k-means variables and centre keys carry their labels. The series palette had four slots, the fourth (#dfefe4) nearly invisible, so a fifth cluster reused the first cluster's blue. One 8-slot palette now lives in chart-palette.ts: the three charter colours plus five hues validated for colour-blind and normal-vision separation. The chart renderer also stops forcing every line to primary blue with smoothing and an area fill, which merged multi-line series and bent the elbow curve between points; the elbow marks the selected k with a labelled point instead of a pin. The echarts-gl dependency has no remaining import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… explained Signed loadings were coloured on a one-hue scale, so -0.71 looked weaker than +0.38, PC1 sat at the bottom and the colour legend was clipped. The loadings heatmap now diverges red / grey / blue around zero, lists PC1 first with its share of variance in the row label, and names the variables above the columns. The scree plot repeated the eigenvalues as a red line; it now shows variance explained per component with the cumulative share. The PCA table adds both percentages and an exact-value loadings table. A saved experiment's own PCA variable names are no longer replaced by whatever the studio has selected at the time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pearson drew four heatmaps: r on a one-hue scale, p-values that all read 0.00 on solid red, and the lower and upper CI bounds as two more grids, while the table only gave the row count. It now draws one lower-triangle heatmap (each pair once, no diagonal of 1s) on a diverging scale with r, the 95% CI and p in the tooltip, and the table lists every pair with r, CI and p, strongest first. A y-by-x correlation matrix still draws whole. Columns named Variable* in result tables now left-align like Variable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Naive Bayes CV and logistic regression CV opened on a confusion matrix whose colour was dominated by the majority class, that kept an unused "unknown" class as an all-zero row and column, and that listed classes bottom-up. The naive Bayes metrics table spread 13 columns (accuracy repeated per class, zeros for the unused class) until fold names wrapped letter by letter. Both now lead with key figures: fold accuracy with its spread, the majority-class baseline it has to beat, balanced accuracy and macro F1 from the pooled confusion matrix, and mean ROC AUC when folds report one. The confusion matrix drops classes absent on both sides, reads top-down and shades each cell by share of its actual class with count and percent. The naive Bayes tables become per-class precision/recall/F1 with support, accuracy per fold, and the combined matrix. ROC curves are no longer smoothed, gain a chance diagonal, and the tooltip lists every fold. Naive Bayes priors draw as one compact horizontal bar per class. The key-figure summary module is renamed summary-figures since it now covers more than hypothesis tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ixed-unit chart
The CV chart put RMSE, R², MAE and the F statistic on one axis with each
standard deviation as its own bar, so F (~3,900) flattened everything else
and the SD bars read as extra metrics. The result now leads with RMSE and
MAE in outcome units, out-of-fold R² with the share of variance it means,
each with its fold spread, and the fold count. F is left to the table: it
describes each training fit, not held-out prediction. The chart and its
renderer are removed; the [mean, sd], {mean, std} and legacy mean_sq_error
shapes are covered by the figure builder's specs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Cox summary table labelled event_var as the follow-up time and dependent_var as the event indicator, so a run on days-to-follow-up with death as the event read "Follow-up time: Death 3m". Exaflow takes the time as y (dependent_var) and the event as event_var; the rows now follow that, and the spec fixture that encoded the swap uses a real time/event pair. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linear mixed models and binary GLMMs showed a raw fixed-effects table and a 13-row model summary. They now use the regression view: fixed effects in the coefficients card (odds ratios with an interval-vs-1 column for the GLMM), and key figures led by the ICC, the share of variance between groups of the grouping variable (latent scale with pi^2/3 for the logit GLMM), then groups, observations and AIC. A model that did not converge says so first, with its iteration count. A grouping variable sent as a list is now labelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The outlier report spread strategy, tail, fold and both bounds over five
narrow columns ("madboth 3"), showed variable codes, and interleaved
variables by dataset. It now reads Variable, Dataset, Rule ("IQR × 1.5,
both tails"), Kept range, Below, Above, Outliers and Outlier %, keeps each
variable's datasets together, and shows variable labels. Null counts still
read Unavailable, distinct from zero.
Result tables meant to left-align name columns (Variable, Role, Categories)
but the rule lost to the later right-align rule of equal specificity, so it
never applied; it now comes after it, covers the header cell, and includes
Dataset and Rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 360px compare columns the coefficient rows wrapped "< 0.001" and each CI onto two lines. The table now responds to its own container width rather than the viewport: numbers never wrap, and below 620px the interval sketch column drops out so variable, estimate, CI and p fit side by side; All columns scrolls sideways there. The ICC note is shortened to fit the strip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The detail tables printed p as 0 (t-tests, Mann-Whitney), 1.110e-16 (both ANOVAs), 3.873e-238 (chi-squared) or <0.001 (Cox), next to key figures that already said < 0.001. Every table p-value now goes through formatPValue: "< 0.001" below that, three decimals above, blank when missing. Tukey p-values come from statsmodels' psturng, which limits its output to [0.001, 0.9], so the ends now read "≤ 0.001" and "≥ 0.900" instead of presenting the clip as a measured value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… CIs The ordinal GLMM joins LMM and the binary GLMM in the coefficient view. Its cumulative logit is P(Y <= j) = sigmoid(k_j - eta), so odds ratios read as the odds of a higher outcome category; the category order and cutpoints go into the model-fit details (integer categories Exaflow echoes as "2.0" show as "2"). Ratios used a fixed 3 significant digits, so a tight interval printed as "1.04 to 1.04". Each row now takes the fewest digits (3 to 6) at which its CI bounds differ: "1.036, 1.035 to 1.036". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… syntax
Coefficient rows printed patsy names: "Sex[female]",
"kmeans_cluster[cluster_2]", "stroke_territory_cohort[PCS]". The
coefficient view now splits a dummy term into the variable and a muted
level ("Sex · female") and reads raw snake_case codes of created columns as
words ("K-means cluster · Cluster 2"). When any row is a category level the
note says it is compared with the level not listed. Only the on-screen view
changes; the CSV keeps the original term names, and the tables, PDF and Cox
forest plot keep their own parsing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A survival model's information comes from its events, not its rows, so Cox results now open with the event count and rate, events per covariate (flagged below the usual 10), a non-convergence warning when it applies, and McFadden pseudo R² for the stacked model. The forest plot title drops its one-off teal for the ink and size the other chart titles use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two-variable centres chart repeated the section title, drew every centre the same size and started both axes at zero, which pushed most centres into a corner. Centres are now bubbles whose area follows the cluster's size (midpoint of its privacy-safe interval), the axes fit the centres with padding so no bubble sits on an axis, and the tooltip gives the coordinates and approximate row count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… scale Gaussian naive Bayes charted only its class priors; how the classes differ was left to the theta and variance tables, in each feature's own units. A class-profile chart now places each class mean in pooled within-class SDs from the overall mean, one row per feature on a shared axis, so the features that separate the classes stand out. Feature names in Gaussian results carry their labels (categorical results keep codes, which their category_count lookup needs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l shares Categorical naive Bayes printed a counts table and a separate log-probability table per feature, both carrying an all-zero row for unused enumerations, and the class prior as a log. Each feature is now one table, category by class, with the count and the model's P(category | class) as a percentage, unused categories dropped; the class summary shows the prior as a percentage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The t-test and Mann-Whitney figures said "group A" / "group B" because the groups live in the run's parameters, which the result view never received. AlgorithmResultComponent now takes the run parameters (experiment detail, studio execution panel and compare columns all pass them) and labels groupA and groupB through the x variable's enumerations: "yes − no · 95% CI ...". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… chart plus table Cox results drew a hazard-ratio forest plot and then listed the same hazard ratios and CIs in a table below it. Cox now uses the coefficient view like the other regression models: hazard ratios with their CI, an interval-vs-1 column and p in one card, events and events per covariate as key figures, and the study summary (follow-up time, event indicator, ties, fit) under model-fit details. The forest-plot renderer and its spec are removed, and the coefficient note names the ratio it is drawn around. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… already show Several results showed the same numbers twice: key figures and then a metric/value table (t-tests, Mann-Whitney, chi-squared and Fisher summaries, SMD, linear regression CV error metrics), or a chart and then the table it was drawn from (k-means centres, PCA variance and loadings, naive Bayes class priors, the CV confusion matrix). Each summary declares the table titles it fully covers (TABLES_SHOWN_AS_FIGURES, the chart registry's replaces), the result view hides those tables only when that summary actually rendered, and the "Detailed results" heading moves into the table renderer so it disappears with the last table. The few values a covered table had beyond its summary moved into it first: SE of the mean difference and the one-sample SD in the t-test figures, training rows next to each naive Bayes prior, approximate size in each k-means legend entry (exact centres in the tooltip), and the eigenvalue under each PCA component. PDF and CSV exports still read every table from the registry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five results still repeated part of a table under their summary. A view can now cover part of a table, not only all of it (TableTrim: columns by header, rows by first cell), and the screen drops just that part; a table with nothing left to read disappears. Exports keep every table whole. - One- and two-way ANOVA: df, F and p leave the table (they are in the figures); sums and mean squares stay. - Pearson: r leaves the pairs table (it is in the heatmap); CI and p stay. - Naive Bayes CV: the Mean ± SD row leaves the per-fold accuracy table. - Logistic regression CV: the average and stdev rows leave the per-fold table; their precision, recall and F1 moved into a positive-class F1 figure first, so nothing is lost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…harts to screen readers Leaving out tables the summary covers made some values reachable only as canvas pixels (k-means centres, PCA loadings, naive Bayes priors). A "Show all values as tables" button under the result brings every covered table back whole, and hides them again; it only appears when something was left out. Charts also turn on ECharts aria, which writes a text description of each chart for screen readers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Table cells printed counts without grouping (111854), switched to scientific notation from a million (sums of squares as 1.066e+6, four digits lost at the source) and used an ASCII hyphen for negatives next to key figures that use a minus sign. On screen, cells now group thousands (111,854; 1,065,587), keep whole numbers whole, and use a typographic minus; scientific notation is left for values below 1e-4. The table CSV keeps plain, ungrouped numbers for spreadsheets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Logistic regression and the binary GLMM read "effect on the odds of 3m mRS good outcome" without saying which level, and logistic regression CV labelled its confusion matrix and baseline "Positive" / "Negative". The run's positive_class parameter, labelled through the outcome's enumerations, now completes them: "the odds that 3m mRS good outcome is yes", a yes / not yes matrix, "always predicting yes". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Canvas text cannot read CSS, so charts drew in ECharts' default sans-serif next to Inter, and each renderer styled (or forgot to style) its own title and tooltip. The chart renderer now applies one house style under every chart's own settings: the app font, 13px ink titles with muted subtitles, and a white tooltip card with a hairline border. Renderers drop the title styles they duplicated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chi-squared and Fisher category tables named a created column by its
code ("stroke_territory_cohort", and "STROKE_TERRITORY_COHORT \ EVT" over
the expected counts) because created columns have no data-model label.
The coefficient view's readable() fallback now lives in result-format and
also serves the table labels: "Stroke territory cohort".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A classifier at 70.4% next to a 70.2% baseline read as two separate
numbers. The accuracy figure now ends with its gain over the
majority-class baseline ("+0.2 pts over the baseline"), computed from the
pooled confusion matrix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e columns In 360px compare columns the PCA loadings and Pearson variable names ran into each other and the confusion-matrix caption was cut at both ends. PCA loadings now put variables in rows (names wrap on the left) and components in columns with their variance share under each; Pearson's column names are slanted; the confusion caption is two short lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
overflow-wrap: anywhere let a squeezed first column shrink to single characters, so the k-means profiles table read "Cluste / r 0" and the notes table "Priv / acy". break-word only breaks a word that cannot fit on its own line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- K-means with three or more variables draws a centres heatmap shaded
around the size-weighted mean instead of parallel coordinates, whose
lines no palette keeps apart past four clusters. Profile lines name
variables by label; the Interpretation column, which only restated the
profile and compactness, is gone; the elbow measure moves to a subtitle.
- PCA "Loadings" are unit eigenvectors, so they are now "Component
weights"; variance bars keep one decimal.
- Cross-validation tables call per-fold n_obs "Training rows". Logistic
regression CV rows read Fold n / Mean / SD, and its positive-class F1
stays in that table instead of wrapping the figure strip.
- Outlier report shows the share as a percentage ("8.9%"); the Pearson
pairs table keeps r, the value it is sorted by.
- ROC legend reads "Fold 1 · 0.864"; Naive Bayes class profiles name the
classes on the first row; confusion matrix labels get room to breathe.
- Result tables put a gap before every column and left-align prose.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns clusters White text never reaches 4.5:1 on the red half of the diverging scale (2.1:1 at the old 0.5 switch) and only beats ink on the blue end past 0.7. Correlation, PCA weights, confusion matrix and k-means centres now share DIVERGING_COLORS and cellLabelColor from chart-palette.ts; the worst cell goes from 2.1:1 to about 4.2:1. A k-means cluster under the privacy threshold arrives as "<10". It did not parse, so the centres heatmap fell back to an unweighted mean for every cluster and the cluster lost its size. "<N" now weighs N/2 and prints as sent; an empty cluster's "0" weighs nothing and prints "0". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ECharts renders a tooltip formatter's return value as HTML. Variable, class, group, dataset and cluster labels come from data-model metadata, created columns and stored results, and shared experiments render in other users' browsers, so every custom tooltip now escapes them. escapeHtml takes any value, so callers need no String() wrapper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ale"
The Playwright run from master looked for the row "Sex[Male]", the model
syntax this branch replaces with variable and level ("Sex · Male"). The
coefficient view's rows also sat outside any table role, which is
invalid ARIA: the container is now a labelled table with column headers
and cells.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer feedback from the result-views quiz: - Large numbers group thousands with a narrow no-break space (27 308) instead of a comma; PDF cells use a plain space, the font has no glyph for it. - Logistic regression key columns are Estimate, Std. error, z and p-value; the odds ratio, its CI and the interval column move to All columns. Coefficient tables call the p column "p-value". - Linear mixed model key columns show Std. error instead of the 95% CI. - Mann-Whitney goes back to the master-style table, with P(A > B) added. - Gaussian naive Bayes explains how to read the class-profile chart; linear regression CV explains RMSE and MAE. - Independent t-test brings back the interval chart, vertical: mean difference on x, value and 95% CI on y. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… after rebase Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inite JSON drops Infinity and NaN, so a perfect fit or an undefined test only survives as Exaflow's display fields. The chart spec now renders through change detection. Co-authored-by: Cursor <cursoragent@cursor.com>
556a6b5 to
b262246
Compare
Summary
This branch reworks how algorithm results are shown in Experiment Studio, the experiment detail page and compare mode. It is display-only: no
/servicescalls, experiment payloads or stored result shapes change, and older experiments render through the same mappers.New result views
Tables and charts
ariadescriptions describe charts to screen readers.n_obsis labelled as training rows.DESIGN.mdanddocs/context/module-index.mddescribe the new views and the chart house style.Testing
npm run typecheck,npm run check:dead-codeandnpm run buildpass. The build shows two warnings that predate this branch: the algorithm-panel CSS budget and the non-ESMjszipmodule.ng test --watch=false --browsers=ChromeHeadless, visualisations and core): 185/185 pass.docs/context/testing.md(k-means, PCA, Pearson, naive Bayes CV, logistic regression CV, Cox), including compare mode.Risk
Follow-ups (exaflow, not in this PR)
averagerow (scorer_multiclass.py), which understates it by √((n−1)/n), about 0.89× at 5 folds.overall_mean(and a pooled SD), so the centres heatmap matches the Profile column exactly and is shaded in SD units.🤖 Generated with Claude Code