API reference#

The importable figures API: the command-line interface, the house style and save helper, the helpers that resolve and load a cached analysis run, the output-path helpers, the reproduction, model-selection, stability, minimum-stratum-size, replication, class-trajectory, and trajectory-roughness figures, and the helper that publishes rendered figures into the documentation tree.

Command-line interface#

figures command-line interface (Typer).

One subcommand per figure. Each resolves a cached analysis run, builds the figure, and writes it under artefacts/figures/ with a JSON provenance sidecar.

figures.cli.main()[source]#

Generate figures from the analysis artefacts.

figures.cli.reproduce(run=<typer.models.OptionInfo object>, as_of_run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the recovered class signatures against the published profile, across conditions.

figures.cli.select(run=<typer.models.OptionInfo object>, as_of_run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the model-selection criteria across the number of latent classes, across conditions.

figures.cli.replicate(run=<typer.models.OptionInfo object>, as_of_run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the SPARK-to-SSC class signatures and the per-category replication across conditions.

figures.cli.stability(run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the profile and membership stability of the reference fit under refitting.

figures.cli.nmin(run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot recovery against subsample size and the minimum viable stratum size.

figures.cli.trajectory(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot each class’s trajectory through the strata in the pooled discriminant space.

figures.cli.sweep(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot each class’s drift as a curve along the axis, from a sweep run’s decision table.

figures.cli.local_trajectory(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the combined four-class local trajectory in the discriminant plane, with the tube.

figures.cli.local_panels(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the per-class local-trajectory panels, each tube over its faint member ellipse.

figures.cli.local_specificity(name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the specificity small-multiple: timing-axis drift against the control panel.

Reads the latest invariance-trajectory run for each timing axis and pools their endpoint displacements, so the era and age effects are shown together against household income, area deprivation, and the random-ordering floor.

figures.cli.category_decomposition(name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the H0F category decomposition: what symptom categories carry each class’s drift.

Reads the latest invariance-trajectory run for each timing axis and pools their category grains and per-feature displacements, so the era and age concentrations are shown together with the leading features behind the age drift.

figures.cli.dense_features(name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot every significant feature’s signed drift, per class and axis, grouped by category.

figures.cli.referent_decomposition(run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the H0G referent split of the era drift: the contrast and its instruments.

figures.cli.atlas(run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the displacement atlas: per-class endpoint drift along every non-modelling axis.

figures.cli.demographic_conditioning(name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the demographic conditioning heatmap: whether any demographic explains the drift.

Reads the latest demographic-conditioning run for each timing axis and shows, per covariate, the shrinkage of each class’s drift beside the covariate’s linear span of the axis, the ceiling.

figures.cli.brief(fmt=<typer.models.OptionInfo object>)[source]#

Build the collaboration-brief figures: the trajectory, atlas, and category panels.

Writes each figure to reports/brief/figures beside main.tex, so the brief inputs the .pgf at natural size, its text set in the brief’s sans-serif font. The trajectory shows the age-at-diagnosis drift in one column; the displacement atlas is the per-class endpoint drift along every non-modelling ordering axis, grouped into stacked kind panels; the category heatmaps are the H0F share decomposition across both axes, at the text width. Needs a working TeX install on the path, and completed invariance-trajectory and displacement-atlas runs.

figures.cli.presentation(fmt=<typer.models.OptionInfo object>)[source]#

Build the 15 July talk’s figures as pgf beside reports/jul-15-presentation/main.tex.

Renders the deck’s figures: the reference reconstruction, the SSC replication, the displacement atlas, the demographic-conditioning panel (which now carries the timing covariates), the category-share heatmaps, the two-dimensional local plane trajectory per axis, the age-at-diagnosis proportion curves, and the stacked class composition per axis. Each is written in the document’s own font, so the talk inputs it at natural size. Needs a working TeX install and the align, replicate, displacement-atlas, demographic-conditioning, invariance-trajectory, and prevalence runs.

figures.cli.local_directional(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the H0E figure: each class’s signed trajectory along the axis, with its break.

figures.cli.local_referent(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the H0G figure: per-class current-versus-retrospective drift with the underlay.

figures.cli.prevalence(axis=<typer.models.OptionInfo object>, layout=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the H0B figure: per-class proportion curves, or the stacked class composition.

--layout panels draws one panel per class, the corrected proportion curve with its bootstrap band, the naive cross-check, and the pooled proportion line. --layout stacked draws the four corrected proportions stacked to one across the axis, the compositional view. --layout stacked-pair sets the diagnostic-era and age-at-diagnosis stacked compositions side by side in one figure (the --axis option is ignored, both axes are drawn).

figures.cli.invariance(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot the strongest-drifting block’s fluctuation process against its bridge null.

figures.cli.pairwise(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot each class’s neighbour-to-neighbour drift along the axis, from a pairwise run.

figures.cli.attribute(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot each class’s membership churn across the strata and what carries each shift (archived).

The refit-era attribution figure, kept for the refit-pilot archive page; the single-fit $H_0^F$ category attribution is drawn by category-decomposition and dense-features.

figures.cli.attribute_contrast(axis=<typer.models.OptionInfo object>, run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot, per class, the features marking the probands that changed class at its peak churn.

The refit-era mover contrast, kept for the refit-pilot archive page (a single fit relabels no proband, so it has no single-fit counterpart).

figures.cli.roughness(age_run=<typer.models.OptionInfo object>, era_run=<typer.models.OptionInfo object>, name=<typer.models.OptionInfo object>, fmt=<typer.models.OptionInfo object>)[source]#

Plot trajectory roughness and directional movement across both axes.

figures.cli.publish(figure=<typer.models.ArgumentInfo object>, run=<typer.models.OptionInfo object>)[source]#

Copy rendered figures into the committed documentation tree, with provenance.

Each figure is taken from the latest completed run of its source stage (or --run) and copied to docs/source/_figures/ beside a JSON sidecar recording its source. A figure that has not been rendered yet is skipped with a note, so publishing the whole set surfaces what still needs building.

House style and output#

House style for the figures: Matplotlib settings, a palette, and a save helper.

The settings are deliberately small and explicit, so a figure looks the same whoever builds it. house_style() returns a context manager that applies them around figure construction without leaking into the global state, and save_figure() writes one file per format alongside a JSON provenance sidecar.

figures.style.panel_title(ax, letter, text)[source]#

Set a left-aligned panel title prefixed with a bold letter label.

The letter is rendered in bold and the scientific title follows in the regular weight, so each panel reads as, for example, “A Information criteria”, left-aligned over the axes.

Parameters:
  • ax (matplotlib.axes.Axes) – The axis to title.

  • letter (str) – The panel letter, shown in bold (for example "A").

  • text (str) – The scientific title that follows the letter.

figures.style.brief_axis_style(ax, *, minor_x=True)[source]#

Match a brief figure’s axis fonts to the document and add major and minor gridlines.

The axis labels are sized to the brief’s \small (10pt) and the tick labels to \footnotesize (9pt), so an included .pgf reads at the same sizes as the surrounding LaTeX. Major and minor gridlines are drawn (the minor lines fainter). Minor ticks and their gridlines are added on the y-axis always and on the x-axis only when it carries a continuous scale.

Parameters:
  • ax (matplotlib.axes.Axes) – The axis to restyle.

  • minor_x (bool, optional) – Whether to add minor ticks and gridlines on the x-axis, and to resize its tick labels. Set false for a categorical x-axis (bar groups), whose labels are kept at their own size.

figures.style.house_style()[source]#

Apply the package Matplotlib settings for the duration of the context.

Yields:

None – Control returns to the caller with the package rcParams in effect; the previous settings are restored on exit, so the styling does not leak into other figures.

figures.style.save_figure(fig, stem, *, formats=('pdf', 'png'), provenance=None, pgf_rc=None)[source]#

Write a figure to one file per format and a JSON provenance sidecar.

Parameters:
  • fig (matplotlib.figure.Figure) – The figure to save.

  • stem (pathlib.Path) – Output path without a suffix; each format appends its own. Parent directories are created if absent.

  • formats (collections.abc.Sequence of str, optional) – File extensions to write; defaults to ("pdf", "png"). PDF is the vector format for the manuscript; PNG is the raster preview the docs embed. "pgf" writes a LaTeX picture the document inputs, its text rendered with the document’s fonts; it needs a working TeX install on the path.

  • provenance (dict, optional) – Extra fields to record in the <stem>.json sidecar, alongside the package version and the time of writing.

  • pgf_rc (dict, optional) – Matplotlib settings for the "pgf" format, so the figure text matches the target document’s fonts; defaults to the serif preamble. Pass PGF_RC_SANS for a sans-serif document. Ignored when "pgf" is not in formats.

Returns:

The written image paths, in formats order.

Return type:

list of pathlib.Path

Output paths for generated figures, under the gitignored artefacts tree.

figures.paths.figures_dir(root)[source]#

Return the figures output directory, <root>/artefacts/figures.

figures.paths.docs_figures_dir(root)[source]#

Return the committed documentation figures directory.

This is <root>/docs/source/_figures, the one place under version control that holds rendered figures. The figures publish command copies the chosen PNGs here from the gitignored artefacts tree, and the documentation pages embed them from here.

figures.paths.brief_figures_dir(root)[source]#

Return the collaboration brief’s figures directory, <root>/reports/brief/figures.

This holds the .pgf assets reports/brief/main.tex inputs, beside the brief so the \input paths are relative; the brief compiles from here without a separate build step.

figures.paths.presentation_figures_dir(root)[source]#

Return the 15 July talk’s figures directory, <root>/reports/jul-15-presentation/figures.

This holds the .pgf assets reports/jul-15-presentation/main.tex inputs, beside the deck so the \input paths are relative and the talk compiles from here with no build step.

figures.paths.figure_stem(root, source_stage, source_hash, name)[source]#

Return the output-path stem for a figure built from one source run.

Parameters:
  • root (pathlib.Path) – The monorepo root.

  • source_stage (str) – The analysis stage whose run the figure visualises, for example "select".

  • source_hash (str) – The short hash of that source run, so a figure is traceable to its inputs.

  • name (str) – The figure’s base name, without a suffix.

Returns:

<root>/artefacts/figures/<source_stage>/<source_hash>/<name> with no suffix; the save helper appends one suffix per format.

Return type:

pathlib.Path

Loading analysis runs#

Locating and loading the cached analysis run a figure visualises.

figures.data.resolve_run(root, stage, run=None, *, axis=None, require=None)[source]#

Return the run directory to visualise for an analysis stage.

Parameters:
  • root (pathlib.Path) – The monorepo root.

  • stage (str) – The analysis stage, for example "select".

  • run (str, optional) – A run’s short hash. When given, that run’s directory is returned. When omitted, the most recently finished run for the stage is chosen (the latest manifest whose status is "ok").

  • axis (str, optional) – Restrict the latest-run search to runs whose manifest records this axis (for the per-axis stages, "age_at_diagnosis" or "era"). Ignored when run is given.

  • require (dict, optional) – Further manifest-parameter equalities the latest run must satisfy, so one stage that writes several run flavours (for example the drift stage’s pooled and pairwise runs) can be told apart by {"reference_scheme": "pairwise"}. Ignored when run is given.

Returns:

The resolved run directory.

Return type:

pathlib.Path

Raises:

FileNotFoundError – When the named run has no manifest, or when no completed run exists for the stage.

figures.data.load_alignment(run_directory, root)[source]#

Load an align run’s reproduction inputs.

Parameters:
  • run_directory (pathlib.Path) – A completed align run directory.

  • root (pathlib.Path) – The monorepo root, used to resolve the upstream fit run for the class proportions.

Returns:

(our_signature, published_signature, alignment, our_proportions, published_proportions): the recovered class-by-category signature, the published figure-1b signature, the alignment record (alignment.json), the recovered class proportions by class id, and the published proportions by named class.

Return type:

tuple

figures.data.load_selection_summary(run_directory)[source]#

Load the per-component selection summary from a select run directory.

Parameters:

run_directory (pathlib.Path) – A completed select run directory.

Returns:

The summary table, one row per number of components, sorted by component count.

Return type:

pandas.DataFrame

figures.data.load_replication(run_directory)[source]#

Load a replicate run’s metrics and its two category signatures.

Returns:

(metrics, spark_signature, ssc_signature): the metrics dictionary (replication.json) and the two class-by-category signature matrices.

Return type:

tuple

figures.data.load_stability(run_directory)[source]#

Load a stability run’s per-fit comparisons, aggregate, and mean overlap.

Returns:

(comparisons, aggregate, overlap_mean): one row per compared fit, the aggregate dictionary (aggregate.json), and the mean class-overlap matrix.

Return type:

tuple

figures.data.load_trajectory(run_directory)[source]#

Load a trajectory run’s embedding table and its manifest metrics.

Returns:

(embedding, meta): the embedding_<axis> table (anchors and stratum centroids in discriminant coordinates) and the manifest metrics (axis, n_strata).

Return type:

tuple

figures.data.load_roughness(run_directory)[source]#

Load a trajectory run’s roughness and directional tables.

Returns:

(axis, roughness, directional): the axis id and the per-class roughness_<axis> and directional_<axis> tables.

Return type:

tuple

figures.data.load_attribution(run_directory)[source]#

Load an attribute run’s summary, category, and mover tables and its metrics.

Returns:

(summary, category, movers, meta): the per-class summary_<axis> headline, the per-category category_<axis> contributions, the per-feature movers_<axis> contrast, and the manifest metrics (axis).

Return type:

tuple

figures.data.load_nmin(run_directory)[source]#

Load an nmin run’s per-fit metrics, per-size summary, and floor metrics.

Returns:

(per_fit, summary, metrics): one row per (size, replicate) fit, the per-size summary, and the manifest metrics (the floor and its bootstrap interval).

Return type:

tuple

figures.data.load_sweep(run_directory)[source]#

Load a sweep run’s decision table and its manifest.

Parameters:

run_directory (pathlib.Path) – The sweep run directory.

Returns:

The decision table (decision_<axis>.parquet) and the run manifest.

Return type:

tuple

figures.data.load_invariance(run_directory)[source]#

Load an invariance run’s stored fluctuation process and its manifest.

Parameters:

run_directory (pathlib.Path) – The invariance run directory.

Returns:

The process table (process_<axis>.parquet) and the run manifest.

Return type:

tuple

figures.data.load_pairwise(run_directory)[source]#

Load a pairwise drift run’s trajectory table and its manifest metrics.

Parameters:

run_directory (pathlib.Path) – The drift run directory of a --reference-scheme pairwise run.

Returns:

The trajectory table (pairwise_<axis>.parquet: one row per neighbour comparison and reference class) and the manifest metrics (axis, mode, class_separation).

Return type:

tuple

figures.data.load_local_trajectory(run_directory)[source]#

Load an invariance-trajectory run’s plane and capture tables and its metrics.

Returns:

(plane, capture, meta): the trajectory_<axis> discriminant-plane table (anchors and per-focal centroids with the bootstrap tube), the per-class capture_<axis> table, and the manifest metrics (axis).

Return type:

tuple

figures.data.load_local_specificity(run_directory)[source]#

Load an invariance-trajectory run’s specificity table (endpoint magnitude by axis).

figures.data.load_local_directional(run_directory)[source]#

Load an invariance-trajectory run’s H0E tables and its metrics.

Returns:

(signed, directional, meta): the signed_trajectory_<axis> table (per class per focal point, the one-dimensional signed trajectory with its bootstrap band), the per-class directional_<axis> summary (net trend, interval, p, FDR decision, break), and the manifest metrics (axis).

Return type:

tuple

figures.data.load_grain_magnitude(run_directory)[source]#

Load an invariance-trajectory run’s per-grain magnitude table (H0F category grains).

Returns:

The grain_magnitude_<axis> frame: per grain ("class" and "category:<name>"), class, and focal point, the separation-scaled magnitude with its bootstrap band.

Return type:

pandas.DataFrame

figures.data.load_feature_displacement(run_directory)[source]#

Load an invariance-trajectory run’s per-feature displacement table.

Returns:

The feature_displacement_<axis> frame: per class and feature, the signed separation-standardised endpoint displacement, its category, and the FDR decision.

Return type:

pandas.DataFrame

figures.data.load_demographic_conditioning(run_directory)[source]#

Load a demographic-conditioning run’s per-covariate, per-class shrinkage table.

Returns:

The demographic_conditioning_<axis> frame: per covariate and reference class, the shrinkage, the raw and conditioned magnitude, the covariate’s axis $R^2$, the covariate label, kind, and coding, and the joined sample size.

Return type:

pandas.DataFrame

figures.data.load_referent(run_directory)[source]#

Load an era invariance-trajectory run’s H0G tables and its metrics.

Returns:

(grains, contrast, meta): the referent_<axis> table (per class per grain, the per-referent and per-instrument root-mean-square intensity, additive share, and FDR count), the per-class referent_contrast_<axis> table (the current-minus-retrospective contrast with its interval, p, FDR decision, and mechanism), and the manifest metrics (axis).

Return type:

tuple

figures.data.load_prevalence(run_directory)[source]#

Load a prevalence run’s proportion-curve and slope tables and its metrics.

Returns:

(curve, slopes, meta): the proportion_curve_<axis> table (per class per focal point, the corrected and naive predicted proportion with the corrected bootstrap band), the slopes_<axis> table (the corrected, naive, adjusted, and DSM-5 per-class contrasts), and the manifest metrics (axis).

Return type:

tuple

figures.data.load_atlas(run_directory)[source]#

Load a displacement-atlas run’s per-axis, per-class endpoint table and its metrics.

Returns:

(atlas, meta): the displacement_atlas frame (per axis and reference class, the separation-scaled endpoint displacement, the axis label and kind, and the joined sample size), and the manifest metrics (the class-summed displacement per axis, the random floor, and the axes dropped below the coverage floor).

Return type:

tuple

figures.data.class_names(root, axis)[source]#

Return the reference-class id to name map for an axis, from its trajectory run.

The sweep decision table carries only class ids, so the names come from the latest trajectory run for the axis, whose directional table pairs each id with its name. An empty map is returned when no such run exists, and the figure falls back to the numeric ids.

Figures#

The reproduction figure: recovered class signatures against the published profile.

Built from an analysis align run, the figure puts each recovered class signature beside the value read from figure 1b of Litman et al. (2025). One panel per named class shows the seven-category profile two ways: the recovered signature (solid) and the published target (dashed). The panels are ordered by published class size, and each title carries the class proportion (recovered against published) and the per-class profile correlation, or a note that the class is anchor-confirmed where its published profile is saturated and the correlation is uninformative.

The figure is the visual form of the reproduction benchmark: a clean match in the developmental-led and saturated classes, and the one real divergence (Social or behavioural showing weaker social-communication and restricted-or-repetitive enrichment than the paper) visible rather than hidden.

figures.reproduction.reproduction_figure(our_signature, published_signature, alignment, our_proportions, published_proportions, comparison=None)[source]#

Build the reproduction figure from an align run.

Parameters:
  • our_signature (pandas.DataFrame) – The recovered class-by-category signature, indexed by class id, one column per category.

  • published_signature (pandas.DataFrame) – The published figure-1b signature, indexed by named class, with the same columns.

  • alignment (dict) – The alignment record, with mapping (class id to named class), correlations (class id to a per-class profile correlation or None), overall_correlation, and anchors_hold.

  • our_proportions (dict of int to float) – The recovered class proportions, by class id.

  • published_proportions (dict of str to float) – The published class proportions, by named class.

  • comparison (dict, optional) – A second condition’s reproduction (the V9 subset), with keys signature, alignment, and proportions mirroring the primary arguments. When given, each panel adds the subset’s recovered signature and the size line reads full / V9 vs published.

Returns:

A two-by-two figure, one panel per named class, each overlaying the recovered and published seven-category profiles.

Return type:

matplotlib.figure.Figure

Raises:

ValueError – When the two signatures differ in their categories, or the alignment names a class the published signature does not carry.

The model-selection figure: information criteria across the number of classes.

Built from the summary table of an analysis select run, the figure tells the number-of-classes story in three panels. The information criteria fall and formally minimise far out along the grid (panel a), the cross-validated log-likelihood elbows at the chosen four classes (panel b), and higher-class solutions degenerate into tiny, poorly separated classes (panel c). A reference line marks the four classes chosen by Litman et al.

The naive Lo-Mendell-Rubin proxy that analysis.selection reports is left out, because that module documents it as not the analytically correct test.

figures.selection.selection_figure(summary, *, reference_k=4, criteria=('bic', 'aic', 'caic'), comparison=None)[source]#

Build the model-selection figure from a select summary table.

Parameters:
  • summary (pandas.DataFrame) – The per-component summary from analysis.selection, with an n_components column and <name>_mean / <name>_std columns for each criterion drawn.

  • reference_k (int, default 4) – The number of classes to mark with a reference line (the Litman choice).

  • criteria (collections.abc.Sequence of str, optional) – The information criteria to draw in the first panel; the first is emphasised. Defaults to ("bic", "aic", "caic"). Ignored when comparison is given, where the first panel shows the leading criterion alone for both conditions.

  • comparison (pandas.DataFrame, optional) – A second condition’s summary (the V9 subset). When given, each panel overlays the two conditions, the full release solid and the subset dashed, so the effect of cutting the cohort back to V9 on model selection is visible. The first panel then shows the leading criterion (BIC) for both conditions rather than the three criteria of one.

Returns:

A three-panel figure: the information criteria, the cross-validated log-likelihood, and the smallest-class proportion.

Return type:

matplotlib.figure.Figure

Raises:

ValueError – When summary is missing a column the figure needs.

The stability figure: how the reference solution holds under refitting.

Built from a stability run (either mode), the figure shows three things. Panel (a) contrasts the distribution of the seven-category profile correlation, which clusters high, with the adjusted Rand index, which is more moderate: the class definitions reproduce, while individual proband assignments are softer at the boundaries. Panel (b) gives the per-category correlation, uniformly high. Panel (c) is the mean class-overlap matrix, whose diagonal is each class’s retention.

figures.stability.stability_figure(comparisons, aggregate, overlap_mean)[source]#

Build the stability figure from a stability run.

Parameters:
  • comparisons (pandas.DataFrame) – One row per compared fit, with overall_correlation and adjusted_rand_index.

  • aggregate (dict) – The aggregate metrics, with category_correlation_mean (one entry per category) and a run descriptor (n_fits/top_k or n_reps/frac).

  • overlap_mean (pandas.DataFrame) – The mean class-overlap matrix (refit class on rows, reference class on columns).

Returns:

A three-panel figure: the correlation and Rand-index distributions, the per-category correlation, and the mean overlap matrix.

Return type:

matplotlib.figure.Figure

Raises:

ValueError – When a required column or metric is missing.

The minimum-viable-stratum-size figure: recovery against sample size.

Built from an nmin run, panel (a) plots the profile correlation of each refit against its sample size, with the per-size mean, the recovery benchmark, and the isotonic floor and its bootstrap interval. The floor is drawn as an interval because the recovery metric is noisy even at ten replicates. Panel (b) shows the smallest class proportion holding well clear of zero, so the four classes survive at every size.

figures.nmin.nmin_figure(per_fit, summary, metrics)[source]#

Build the minimum-viable-stratum-size figure from an nmin run.

Parameters:
  • per_fit (pandas.DataFrame) – One row per (size, replicate) fit, with size, overall_correlation, and smallest_class_proportion.

  • summary (pandas.DataFrame) – The per-size summary, with the same three columns.

  • metrics (dict) – The floor metrics: floor and floor_ci90 (a two-element list, or None), and the benchmark correlation.

Returns:

A two-panel figure: recovery against size, and the smallest class proportion.

Return type:

matplotlib.figure.Figure

Raises:

ValueError – When a required column is missing.

The cross-cohort replication figure: SPARK class signatures projected onto the SSC.

Built from a replicate run, the figure shows how closely the seven-category class signatures agree between the SPARK fit and its projection onto the SSC, against the values Litman et al. (2025) report. Panel (a) scatters every class-by-category value, SSC against SPARK, around the line of equality; panel (b) gives the per-category correlation with the published per-category coefficients overlaid and a proband-bootstrap 95 per cent interval on each. Each per-category coefficient is taken over the four classes alone, so it shifts easily when one class moves and its interval is wide; the overall correlation, over all 28 class-by-category points, is the stabler quantity. The developmental category sits below the rest, a gap the replication investigation examines rather than ascribes to a single cause.

figures.replication.replication_figure(spark_signature, ssc_signature, metrics, comparison=None)[source]#

Build the cross-cohort replication figure from a replicate run.

Parameters:
  • spark_signature (pandas.DataFrame) – The class-by-category signature matrices (one row per class, one column per category) for the SPARK fit and its SSC projection. They must share shape and columns.

  • ssc_signature (pandas.DataFrame) – The class-by-category signature matrices (one row per class, one column per category) for the SPARK fit and its SSC projection. They must share shape and columns.

  • metrics (dict) – The replication metrics for the primary condition (the full release), with overall_correlation, category_correlation (one entry per category), and optionally n_ssc.

  • comparison (dict, optional) – A second condition’s metrics (the V9-subset projection), with the same keys. When given, the per-category panel groups the two conditions side by side against the published values, so the effect of cutting the training cohort back to V9 is visible.

Returns:

A two-panel figure: the signature scatter and the per-category correlation.

Return type:

matplotlib.figure.Figure

Raises:

ValueError – When the two signatures differ in shape or columns, or a metric is missing.

The class-trajectory figure: each class’s path through the strata in discriminant space.

Built from a trajectory run, the figure shows, in one panel per class, where the class sits in the pooled four-class discriminant space and how its centroid moves across the strata of the axis (age at diagnosis or diagnostic era). The focal class’s members are drawn as nested grey Gaussian coverage contours from 50 to 95 per cent, the tighter ones more opaque, so the shading shows where its probands concentrate without plotting any individual; the other three classes are marked by their centroid alone. The stratum centroids are coloured from the first stratum to the last, with a red ring where membership reorganised (Jaccard below 0.5); an arrow marks the net displacement from the first third of the strata to the last. The projection is a linear discriminant embedding, so positions and distances are honest, but it is an illustration: the drift claim rests on the full-dimensional statistics of the drift and roughness stages, not on this picture.

figures.trajectory.trajectory_figure(embedding, meta)[source]#

Build the class-trajectory figure from a trajectory run.

Parameters:
  • embedding (pandas.DataFrame) – The embedding_<axis> table: one row per anchor and per (class, stratum), with kind, ref_class, class_name, order, ld1, ld2, jaccard, and reorganised.

  • meta (dict) – The run’s manifest metrics, carrying axis and n_strata.

Returns:

A 2 by 2 figure, one panel per class.

Return type:

matplotlib.figure.Figure

Raises:

ValueError – When the embedding table is missing a required column.

Figures for the local class-profile displacement (the score-invariance recast, plan 7e).

An invariance-trajectory run reads how each pooled class centroid moves as a smooth function of the axis, under frozen responsibilities, with a family-clustered bootstrap tube. These figures are a view of that full-dimensional effect, not its authority: each carries the in-plane capture fraction, so a class whose drift is mostly out of the discriminant plane is flagged rather than flattered by the picture.

Three figures:

  • plane_figure() draws all four classes in one discriminant plane: the pooled anchors, each class’s local trajectory with a time arrow, the centroid bootstrap tube, and the capture fraction;

  • panels_figure() gives one panel per class, the trajectory and its tube over the faint within-class member ellipse, which answer different questions (where the centroid sits versus where the members spread);

  • specificity_figure() is the small-multiple that reads the separation-scaled endpoint displacement of the timing axes against the control panel (household income, area deprivation, a random ordering), so the timing effect is shown to be larger than a control rather than merely non-zero;

  • directional_figure() (DIREC, plan 12b) draws each class’s one-dimensional signed trajectory, the projection onto its net direction, with the clustered-bootstrap band and the descriptive single-break location, so a monotone trend and a boundary discontinuity are visible;

  • referent_figure() (ATTR-REF, era only) draws, per class, the size-fair current-state and retrospective root-mean-square drift intensity with the per-instrument underlay, so the measurement-timing signature (current-dominant) and the diagnosed-population signature (retrospective-dominant) are read off the two-way split.

figures.trajectory_local.plane_figure(plane, capture, meta, *, arrow=True, width_in=None, height_in=None, brief=False)[source]#

Build the combined four-class discriminant-plane trajectory figure.

Each class is a run of focal-point dots coloured low-to-high along the ordering, wrapped in the translucent bootstrap tube, with a black-edged marker at the pooled anchor and the in-plane capture fraction in the label. A colourbar keys the dot colour to the ordering.

Parameters:
  • plane (pandas.DataFrame) – The trajectory_<axis> table: anchor rows (with the member covariance) and per-focal centroid rows (with the bootstrap tube box ld1_lo, ld1_hi, ld2_lo, ld2_hi).

  • capture (pandas.DataFrame) – The capture_<axis> table, carrying the per-class in-plane capture fraction.

  • meta (dict) – The run’s manifest metrics, carrying axis.

  • arrow (bool, optional) – When true (the default) draw a net-drift arrow from the early to the late focal points of each class; set it false for the dots and tube alone.

  • width_in (float, optional) – The figure size in inches; sensible defaults are used when omitted.

  • height_in (float, optional) – The figure size in inches; sensible defaults are used when omitted.

  • brief (bool, optional) – When true, drop the panel letter for a document that supplies its own caption.

Returns:

The single-panel figure.

Return type:

matplotlib.figure.Figure

figures.trajectory_local.plane_overlay_figure(planes, captures, *, width_in=None, height_in=None, brief=False)[source]#

Overlay the local trajectories of two timing axes in the one shared discriminant plane.

Both axes are read against the same pooled fit, so the class anchors and the discriminant basis are common: each class grows two trajectories from its anchor, one per axis, drawn in the class colour and told apart by line style (age at diagnosis solid, diagnostic era dashed). The colour-by-focal-point scale of plane_figure() is dropped, since one scale cannot serve two axes; a small start dot and the arrowhead carry the low-to-high direction instead.

Parameters:
  • planes (dict of str to pandas.DataFrame) – The trajectory_<axis> table per axis name ("age_at_diagnosis", "era"); the anchors are taken from the first and the others must share them.

  • captures (dict of str to pandas.DataFrame) – The capture_<axis> table per axis name, for the in-plane capture note.

  • width_in (float, optional) – The figure width in inches; the height follows the default aspect. Set this to the document text width so the input is placed at natural size.

  • height_in (float, optional) – The figure height in inches; when omitted it follows the default aspect from the width.

  • brief (bool, optional) – When true, drop the panel letter and the in-axes capture note for a document that supplies its own caption.

Returns:

The single-panel overlay figure.

Return type:

matplotlib.figure.Figure

figures.trajectory_local.panels_figure(plane, capture, meta)[source]#

Build the per-class panels: the local trajectory and tube over the member ellipse.

Parameters:
  • plane (pandas.DataFrame) – The trajectory_<axis> table (anchors with the member covariance and per-focal centroids with the tube box).

  • capture (pandas.DataFrame) – The capture_<axis> table.

  • meta (dict) – The run’s manifest metrics, carrying axis.

Returns:

The 2 by 2 figure, one panel per class.

Return type:

matplotlib.figure.Figure

figures.trajectory_local.specificity_figure(specificity, meta, *, width_in=None, height_in=None, brief=False)[source]#

Build the specificity small-multiple: endpoint displacement by axis, per class.

The separation-scaled endpoint magnitude of each axis, with the timing axes drawn against the control panel. A per-class dot sits on each axis’s bar (the mean over classes), so the comparison is read as a magnitude ordering rather than a reject-or-not decision.

Parameters:
  • specificity (pandas.DataFrame) – The merged specificity rows, with axis_name, ref_class, class_name and endpoint_magnitude. The timing axes and the control axes are pooled here.

  • meta (dict) – Presentation metrics; timing_axes names the axes drawn as the effect (highlighted).

  • width_in (float, optional) – The figure width in inches; the height follows the default aspect unless height_in is given. Set this to the document text width so the input is placed at natural size.

  • height_in (float, optional) – The figure height in inches, overriding the default aspect. Use it to flatten the panel (the five long axis labels need close to the full text width, so height is the free knob).

  • brief (bool, optional) – When true, drop the panel letter for a document that supplies its own caption.

Returns:

The single-panel figure.

Return type:

matplotlib.figure.Figure

figures.trajectory_local.specificity_panels_figure(specificity, meta, *, width_in=None, height_in=None)[source]#

Build the two-panel specificity figure: per-class timing drift vs a random control.

Each ordering axis is a group of four bars, one per class in a consistent colour (legend), so the reader sees which class moves. A black rule across each group marks the across-class mean, the summary the single bar of specificity_figure() used to carry. Panel A holds the timing axes and panel B a random-order control; they share the y-axis and a dotted line at the control mean, so the timing bars clearing that line, and the random control sitting on it, is the read.

Parameters:
  • specificity (pandas.DataFrame) – The merged specificity rows (axis_name, ref_class, class_name, endpoint_magnitude), timing and control axes pooled.

  • meta (dict) – Presentation metrics; timing_axes names the axes shown in panel A.

  • width_in (float, optional) – The figure size in inches; sensible defaults are used when omitted.

  • height_in (float, optional) – The figure size in inches; sensible defaults are used when omitted.

Returns:

The two-panel figure.

Return type:

matplotlib.figure.Figure

figures.trajectory_local.directional_figure(signed, directional, meta)[source]#

Build the DIREC figure: each class’s one-dimensional signed trajectory along the axis.

Each class is projected onto its own net direction, giving a signed trajectory $s_k(f)$ whose slope is the directional statistic. The clustered-bootstrap band is shaded, the horizontal zero line is the pooled centroid, and a marker sits at the descriptive single-break location. The legend carries each class’s separation-scaled net trend with its interval and whether it is directional, so the picture and the test agree.

Parameters:
  • signed (pandas.DataFrame) – The signed_trajectory_<axis> table (ref_class, position, signed, band_lo, band_hi).

  • directional (pandas.DataFrame) – The per-class directional_<axis> summary (net_trend, its interval, reject, break_position).

  • meta (dict) – The run’s manifest metrics, carrying axis.

Returns:

The single-panel figure.

Return type:

matplotlib.figure.Figure

figures.trajectory_local.referent_figure(grains, contrast, meta)[source]#

Build the ATTR-REF figure: per-class current-versus-retrospective drift intensity.

One panel per class. Two bars give the size-fair root-mean-square displacement intensity of the current-state referent (RBS-R, CBCL 6-18) and the retrospective referent (SCQ Lifetime, developmental milestones and history); the per-instrument intensities are overlaid as points, the transparent underlay showing which instrument carries each referent’s drift. The panel title reads the current-minus-retrospective contrast with its clustered-bootstrap interval and the mechanism it implies: a current-dominant class carries the measurement-timing signature, a retrospective-dominant class the diagnosed-population signature.

Parameters:
  • grains (pandas.DataFrame) – The referent_<axis> table (per class per grain: grain_kind, grain, referent, rms, share, n_features).

  • contrast (pandas.DataFrame) – The per-class referent_contrast_<axis> summary (contrast, ci_low, ci_high, reject, mechanism).

  • meta (dict) – The run’s manifest metrics, carrying axis.

Returns:

The 2 by 2 figure, one panel per class.

Return type:

matplotlib.figure.Figure

The score-based invariance figure: the empirical fluctuation process against its null band.

Built from an invariance run’s stored process, the figure plots the squared norm of the standardised fluctuation process $lVert B(t) rVert^2$ for the strongest-drifting focal block, against the axis (age at diagnosis or diagnosis year), with the pointwise envelope of the simulated Brownian-bridge null shaded beneath. Under stability the observed curve would sit inside the null band; a curve that climbs far above it is a class profile drifting along the axis, and the peak marks the estimated break. The y-axis is logarithmic because the observed excursion dwarfs the null band by orders of magnitude at this sample size.

figures.invariance.invariance_process_figure(process, meta)[source]#

Build the fluctuation-process figure from an invariance run’s process table.

Parameters:
  • process (pandas.DataFrame) – The stored process, with position, observed, null_q50 and null_q95 columns (one row per grid point).

  • meta (dict) – The run’s manifest metrics, carrying axis and top_block for the labels.

Returns:

The one-panel figure.

Return type:

matplotlib.figure.Figure

The movement-attribution figures: where the classes move, and what carries the move (archived).

Archived. These figures render the refit-era attribute stage: the per-stratum re-estimated membership churn and the mover-versus-stayer contrast. They are kept for the refit pilot page. The single-fit category attribution ($H_0^F$) is now drawn by figures.category_decomposition and figures.dense_features.

Both figures are built from an attribute run and are general renderers of its tables, not tied to any particular result: the classes, strata, categories, and features are read from the data, so the same code draws either axis and any run.

  • attribution_figure() is the two-panel summary. Panel A is a heatmap of each class’s churn (the fraction of its membership that changed, one minus the Jaccard overlap) across the strata of the axis, with a box around the cells where the membership reorganised. Panel B stacks each class’s centroid shift by literature category, pooled across strata, so the panel shows which kinds of feature carry each class’s movement.

  • mover_contrast_figure() is the companion. One panel per class shows, at the stratum where the class churns most, the features that most distinguish the probands that changed class from the stable core (a signed standardised mean difference), so a movement reads down to the features that mark who moved.

The figures are descriptive: they open up an already-measured drift. They do not test it.

figures.attribution.attribution_figure(summary, category, meta)[source]#

Build the two-panel attribution summary from an attribute run.

Parameters:
  • summary (pandas.DataFrame) – The summary_<axis> table: one row per stratum and class, with churn, jaccard, ref_class, and class_name.

  • category (pandas.DataFrame) – The category_<axis> table: one row per stratum, class, and category, with the signed contribution to the squared distance.

  • meta (dict) – The run’s manifest metrics, carrying axis.

Returns:

Panel A (churn heatmap) beside panel B (category composition).

Return type:

matplotlib.figure.Figure

figures.attribution.mover_contrast_figure(summary, movers, meta, top_k=8)[source]#

Build the per-class mover-contrast figure from an attribute run.

For each class the panel is drawn at the stratum where the class churns most, and shows the top_k features whose standardised mean difference between the probands that changed class and the stayers is largest, signed so a positive bar is a feature higher in the movers.

Parameters:
  • summary (pandas.DataFrame) – The summary_<axis> table, used to pick each class’s peak-churn stratum.

  • movers (pandas.DataFrame) – The movers_<axis> table: one row per stratum, class, and feature, with the signed effect, its magnitude, and an fdr_significant flag.

  • meta (dict) – The run’s manifest metrics, carrying axis.

  • top_k (int, default 8) – Number of features to show per panel.

Returns:

One panel per class, arranged in a near-square grid.

Return type:

matplotlib.figure.Figure

The H0F category-decomposition figure: what symptom categories carry the class drift.

Reads the one-fit block-attribution decomposition (plan section 7f): the per-class, per-category separation-scaled displacement magnitudes the invariance-trajectory stage writes to grain_magnitude_<axis>.parquet, and the per-feature displacements it writes to feature_displacement_<axis>.parquet. The figure has two parts. The heatmaps (panels A and B) show each class’s drift split into category shares along diagnostic era and age at diagnosis, so the concentration is legible at a glance. The per-class lollipops (panel C) name the leading features behind the age drift, coloured by category, so the developmental milestones carrying the developmental class and the internalizing items carrying the others are visible feature by feature.

figures.category_decomposition.category_decomposition_figure(grains, features, meta)[source]#

Build the H0F category-decomposition figure across era and age at diagnosis.

Parameters:
  • grains (dict of str to pandas.DataFrame) – The grain_magnitude_<axis>.parquet frame per axis ("era" and "age_at_diagnosis"), carrying the per-class category-grain magnitudes.

  • features (dict of str to pandas.DataFrame) – The feature_displacement_<axis>.parquet frame per axis, carrying the per-feature signed displacement, category, and false-discovery-rate decision.

  • meta (dict) – Figure metadata; unused beyond documenting provenance.

Returns:

The two category heatmaps and the four per-class leading-feature panels.

Return type:

matplotlib.figure.Figure

figures.category_decomposition.category_heatmaps_figure(grains, meta, *, width_in=6.5, height_in=2.9)[source]#

Build the two category-share heatmaps on their own, for the collaboration brief.

A compact standalone of the top row of category_decomposition_figure(): each class’s drift split into category shares along diagnostic era (panel A) and age at diagnosis (panel B), sized to span the brief’s text width. The two heatmaps share one colour ceiling and one colour bar, so the shading is comparable across the axes; the per-class leading-feature lollipops of the full figure are dropped.

Parameters:
  • grains (dict of str to pandas.DataFrame) – The grain_magnitude_<axis>.parquet frame per axis ("era" and "age_at_diagnosis"), carrying the per-class category-grain magnitudes.

  • meta (dict) – Figure metadata; unused beyond documenting provenance.

  • width_in (float) – The figure size in inches; the defaults span the brief text width.

  • height_in (float) – The figure size in inches; the defaults span the brief text width.

Returns:

The two category-share heatmaps with a shared colour bar.

Return type:

matplotlib.figure.Figure

The dense feature matrix: every significant feature’s signed drift, per class and axis.

The top-five lollipops of the {py:mod}`~figures.category_decomposition` figure name the leading features; this figure shows them all. Each row is a feature that clears false-discovery-rate control in at least one class on at least one axis (227 of the 238 do), each column is a class on an axis, and the cell colour is the signed separation-standardised displacement, red for a rise and blue for a fall, with the non-significant cells left faint so the significance pattern reads at a glance. The rows are grouped by a chosen key, the author symptom category for $H_0^F$ or the instrument referent for $H_0^G$, with a colour sidebar and a divider between groups, so the concentration the summary figures report is visible feature by feature.

figures.dense_features.dense_feature_figure(features, meta, *, group_by='category', group_order=('developmental', 'social/communication', 'restricted/repetitive', 'anxiety/mood', 'disruptive behavior', 'attention', 'self-injury', 'somatic', 'thought problems', 'other problems'), group_label='category')[source]#

Build the dense signed-displacement matrix over every significant feature.

Parameters:
  • features (dict of str to pandas.DataFrame) – The feature_displacement_<axis>.parquet frame per axis, each carrying feature, ref_class, class_name, displacement, reject, and the grouping column.

  • meta (dict) – Figure metadata; unused beyond documenting provenance.

  • group_by (str, optional) – The column the rows are grouped by ("category" for $H_0^F$, "referent" for $H_0^G$).

  • group_order (tuple of str, optional) – The order the groups are shown in, top to bottom.

  • group_label (str, optional) – The word for the grouping, used in the sidebar title.

Returns:

The dense feature-by-class heatmap with a group colour sidebar.

Return type:

matplotlib.figure.Figure

The H0G referent figure: is the era drift measurement timing or a population change.

Reads the H0G referent split of the era drift (plan section 7f; the block engine’s size-fair grain contrast). Panel A is the test: the per-class current-minus-retrospective root-mean-square contrast, negative when the drift sits in the retrospective and lifetime instruments (the diagnosed-population signature) and positive when it sits in the current-state instruments (the measurement-timing signature). Panel B opens the contrast up by instrument, so the reader sees which questionnaires carry each referent: the lifetime SCQ and the developmental history against the current-state CBCL and RBS-R.

figures.referent_decomposition.referent_decomposition_figure(grains, contrast, meta)[source]#

Build the H0G referent figure: the current-minus-retrospective contrast and its instruments.

Parameters:
  • grains (pandas.DataFrame) – The referent_era table: per class and grain (referent and instrument), the size-fair root-mean-square intensity, additive share, and FDR-surviving feature count.

  • contrast (pandas.DataFrame) – The referent_contrast_era table: per class, the current-minus-retrospective contrast with its interval, p-value, FDR decision, and mechanism.

  • meta (dict) – Figure metadata; unused beyond documenting provenance.

Returns:

The two-panel referent figure.

Return type:

matplotlib.figure.Figure

The displacement atlas: per-class endpoint drift along every non-modelling ordering axis.

Reads a displacement-atlas run (plan section 12b) and draws a heatmap of each class’s separation-scaled endpoint displacement along every continuous or ordered axis outside the 238 clustered features. The axes are grouped into stacked panels by kind (the timing axes, the covariate pool, and the random floor), one panel each labelled A onward, sharing a single colour scale and the class columns on the x-axis. Within each panel the rows run from the largest class-summed mover to the smallest, so the map reads as a ranking. The random floor is the last panel: an axis whose row sits above it carries drift beyond sampling noise. No covariate is assumed orthogonal to timing, so the atlas reports every axis against that single random reference.

figures.atlas.atlas_figure(atlas, meta, *, width_in=6.4, height_in=None, label_pt=9.0, value_pt=7.0, compact=False)[source]#

Build the displacement atlas: stacked per-kind heatmap panels, sorted by displacement.

Parameters:
  • atlas (pandas.DataFrame) – The displacement_atlas table: per axis and reference class, the separation-scaled endpoint displacement, the axis label and kind, and the joined sample size.

  • meta (dict) – The run metrics; unused beyond documenting provenance.

  • width_in (float, optional) – Figure width in inches.

  • height_in (float, optional) – Figure height in inches; derived from the row count when omitted, for dense rows.

  • label_pt (float, optional) – Point sizes for the axis and tick labels and for the in-cell values, so the same figure reads at both the documentation size and the smaller collaboration-brief size.

  • value_pt (float, optional) – Point sizes for the axis and tick labels and for the in-cell values, so the same figure reads at both the documentation size and the smaller collaboration-brief size.

  • compact (bool, optional) – Use the short axis labels, for the narrow collaboration-brief column where the full names do not fit.

Returns:

The stacked atlas: one heatmap panel per axis kind, sharing a colour scale and the class columns, with the random floor as the last panel.

Return type:

matplotlib.figure.Figure

Do demographic differences explain the drift? The conditioning heatmap (plan section 7g).

Reads a demographic-conditioning run for each timing axis and draws, per demographic covariate, how much of each class’s along-axis drift the covariate accounts for. The main panel is the shrinkage: the fraction of a class’s separation-scaled endpoint drift removed by residualising the 238 clustered features on the covariate, read for every class on both the era and the age axis. A near-zero value means the drift is untouched; a small negative value means it grew slightly, the noise around no effect. A narrow panel to its left carries each covariate’s linear span of the timing axis (the axis $R^2$), the ceiling on that shrinkage, because a covariate orthogonal to the axis cannot account for an axis-ordered drift however much feature variance it explains. A colour sidebar groups the rows by covariate family (socioeconomic, family structure, parental, individual), and the joined sample size annotates each row, since the survey-version covariates join far fewer probands than the registration-complete ones.

The figure is the demographic counterpart of the H0F category decomposition: where that asks which symptom categories carry the drift, this asks whether any demographic does. On SPARK the answer reads straight off the two panels: the axis $R^2$ column is near zero for every covariate, so the shrinkage is near zero too, and the drift is not a demographic story.

figures.demographic_conditioning.demographic_conditioning_figure(tables, meta, *, width_in=8.2)[source]#

Build the demographic conditioning heatmap: shrinkage per class, with the axis-span ceiling.

Parameters:
  • tables (dict of str to pandas.DataFrame) – The demographic_conditioning_<axis> frame per timing axis: per covariate and reference class, the shrinkage, the axis_r2 (constant across a covariate’s classes), the covariate label, kind, and coding, and the joined sample size n_joint.

  • meta (dict) – The run metrics; unused beyond documenting provenance.

  • width_in (float, optional) – Figure width in inches.

Returns:

The row-aligned figure: a covariate-family colour strip, the axis-$R^2$ ceiling panel, and the shrinkage panel (four classes for each of the two axes, divided).

Return type:

matplotlib.figure.Figure

Figure for the prevalence-drift test (H0B, plan section 3 / 12b).

A prevalence run reads how each frozen class’s mixing proportion trends along an axis, under the maximum-likelihood three-step correction, with a family-clustered bootstrap band. The estimand is the proportion as a function of the axis, so the figure draws that curve directly.

proportion_curve_figure() gives one panel per class: the corrected proportion curve with its bootstrap band, the naive hard-label curve as a thin dashed cross-check, a dotted line at the pooled (axis-free) proportion the class trends away from, and a title carrying the per-year log-odds slope, its odds ratio, and whether the class’s proportion trends under the false-discovery control. stacked_area_figure() gives the compositional view: the four corrected proportions stacked to one across the axis, so a class growing as another shrinks is read as a single shifting composition. stacked_area_pair_figure() sets the diagnostic-era and age-at-diagnosis compositions side by side in one figure, sharing the vertical scale and one legend, so the two axes read together.

figures.prevalence.proportion_curve_figure(curve, slopes, meta)[source]#

Build the H0B figure: each class’s predicted proportion as a function of the axis.

Parameters:
  • curve (pandas.DataFrame) – The proportion_curve_<axis> table (ref_class, class_name, position, corrected, naive, band_lo, band_hi).

  • slopes (pandas.DataFrame) – The slopes_<axis> table; the corrected rows supply each panel’s slope, odds ratio, and false-discovery decision.

  • meta (dict) – The run’s manifest metrics, carrying axis.

Returns:

The four-panel figure, one class per panel, sharing the axis.

Return type:

matplotlib.figure.Figure

figures.prevalence.stacked_area_figure(curve, meta)[source]#

Build the stacked-area H0B figure: the class composition across the axis.

The corrected proportions sum to one at every axis position, so they stack into a full composition. The classes are stacked largest-pooled at the bottom for a stable base, and each band is labelled with its class name, so the compositional shift (one class growing as another shrinks) is read directly.

Parameters:
  • curve (pandas.DataFrame) – The proportion_curve_<axis> table (ref_class, class_name, position, corrected, pooled).

  • meta (dict) – The run’s manifest metrics, carrying axis.

Returns:

The single-panel stacked-area figure.

Return type:

matplotlib.figure.Figure

figures.prevalence.stacked_area_pair_figure(curves, meta)[source]#

Build the side-by-side stacked-area H0B figure: composition on both timing axes at once.

The diagnostic-era and age-at-diagnosis compositions are drawn as two panels sharing the vertical scale, with a single legend beneath, so the two axes’ shifts are read together: the gentler era trade on the left and the starker age-at-diagnosis trade on the right. The stacking order and colours are fixed from the pooled proportions, which are axis-free and so shared, so a class sits in the same band and colour in both panels.

Parameters:
  • curves (dict of str to pandas.DataFrame) – The proportion_curve_<axis> table (ref_class, class_name, position, corrected, pooled) for each timing axis, keyed "era" and "age_at_diagnosis". Whichever axes are present are drawn, era first.

  • meta (dict) – The run metrics; unused beyond documenting provenance.

Returns:

The two-panel stacked-area figure, one timing axis per panel.

Return type:

matplotlib.figure.Figure

The trajectory-roughness figure: step size against noise, and directional movement.

Built from the trajectory runs of both axes, the figure reads each class’s path in two ways. Panel A compares the mean step between adjacent strata with the step that independent sampling of a class of that size would produce, so a jagged path can be read as sampling noise rather than movement. Panel B compares each class’s net young-to-old displacement with an ordering-shuffle null, so a large displacement tied to the axis reads as directional drift rather than scatter. The directional test is a pilot on the observed centroids; the confirmatory test is the continuous-trend regression against the refit permutation null.

figures.roughness.roughness_figure(roughness_by_axis, directional_by_axis)[source]#

Build the trajectory-roughness figure from both axes’ trajectory runs.

Parameters:
  • roughness_by_axis (dict of str to pandas.DataFrame) – Per axis label, the roughness_<axis> table (ref_class, class_name, step, sampling_noise).

  • directional_by_axis (dict of str to pandas.DataFrame) – Per axis label, the directional_<axis> table (ref_class, class_name, net, null95, significant).

Returns:

A two-panel figure: step magnitude versus sampling noise, and net displacement against the ordering-shuffle null.

Return type:

matplotlib.figure.Figure

Raises:

ValueError – When the two mappings do not share their axis labels, or a table is empty.

Publishing#

Promoting generated figures into the committed documentation tree.

Figures are rendered under the gitignored artefacts/ directory, so they do not reach the published documentation on their own. figures publish copies the chosen PNGs into docs/source/_figures/, a committed directory the documentation pages embed, and writes a provenance sidecar beside each so a published figure traces back to the analysis run and the commit it was built from.

Only the rendered PNGs cross into the committed tree. They are aggregate, non-disclosive summaries (class profiles, correlations, drift), so committing them is within the data governance that keeps the rest of the artefacts tree out of the history.

class figures.publish.FigureSpec(name, source_stage, file_name, axis=None)[source]#

One publishable figure.

name#

The figure’s key: its figures <name> build command and its documentation handle.

Type:

str

source_stage#

The analysis stage whose cached run the figure visualises.

Type:

str

file_name#

The figure’s base file name, without a suffix.

Type:

str

axis#

For a per-axis stage, the axis whose latest run the figure is taken from. None for a figure whose source stage is not split by axis.

Type:

str, optional

figures.publish.publish_figure(root, spec, run=None)[source]#

Copy one rendered figure into the documentation tree and record its provenance.

Parameters:
  • root (pathlib.Path) – The monorepo root.

  • spec (FigureSpec) – The figure to publish.

  • run (str, optional) – The source run’s short hash. When omitted, the latest completed run of the figure’s source stage is used, the same run figures.data.resolve_run() would pick.

Returns:

The written PNG in the documentation tree.

Return type:

pathlib.Path

Raises:

FileNotFoundError – When no PNG has been rendered for the figure yet, so the caller is told to build it first.