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.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-trajectoryrun 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-trajectoryrun 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-conditioningrun 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/figuresbesidemain.tex, so the brief inputs the.pgfat 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 completedinvariance-trajectoryanddisplacement-atlasruns.
- 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 panelsdraws one panel per class, the corrected proportion curve with its bootstrap band, the naive cross-check, and the pooled proportion line.--layout stackeddraws the four corrected proportions stacked to one across the axis, the compositional view.--layout stacked-pairsets the diagnostic-era and age-at-diagnosis stacked compositions side by side in one figure (the--axisoption 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-decompositionanddense-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 todocs/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.pgfreads 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 packagercParamsin 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.Sequenceofstr, 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>.jsonsidecar, 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. PassPGF_RC_SANSfor a sans-serif document. Ignored when"pgf"is not informats.
- Returns:
The written image paths, in
formatsorder.- Return type:
listofpathlib.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. Thefigures publishcommand 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
.pgfassetsreports/brief/main.texinputs, beside the brief so the\inputpaths 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
.pgfassetsreports/jul-15-presentation/main.texinputs, beside the deck so the\inputpaths 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:
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 whenrunis given.require (
dict, optional) – Further manifest-parameter equalities the latest run must satisfy, so one stage that writes several run flavours (for example thedriftstage’s pooled and pairwise runs) can be told apart by{"reference_scheme": "pairwise"}. Ignored whenrunis given.
- Returns:
The resolved run directory.
- Return type:
- 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
alignrun’s reproduction inputs.- Parameters:
run_directory (
pathlib.Path) – A completedalignrun directory.root (
pathlib.Path) – The monorepo root, used to resolve the upstreamfitrun 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:
- figures.data.load_selection_summary(run_directory)[source]#
Load the per-component selection summary from a
selectrun directory.- Parameters:
run_directory (
pathlib.Path) – A completedselectrun directory.- Returns:
The summary table, one row per number of components, sorted by component count.
- Return type:
- figures.data.load_replication(run_directory)[source]#
Load a
replicaterun’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:
- figures.data.load_stability(run_directory)[source]#
Load a
stabilityrun’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:
- figures.data.load_trajectory(run_directory)[source]#
Load a
trajectoryrun’s embedding table and its manifest metrics.- Returns:
(embedding, meta): theembedding_<axis>table (anchors and stratum centroids in discriminant coordinates) and the manifest metrics (axis,n_strata).- Return type:
- figures.data.load_roughness(run_directory)[source]#
Load a
trajectoryrun’s roughness and directional tables.- Returns:
(axis, roughness, directional): the axis id and the per-classroughness_<axis>anddirectional_<axis>tables.- Return type:
- figures.data.load_attribution(run_directory)[source]#
Load an
attributerun’s summary, category, and mover tables and its metrics.- Returns:
(summary, category, movers, meta): the per-classsummary_<axis>headline, the per-categorycategory_<axis>contributions, the per-featuremovers_<axis>contrast, and the manifest metrics (axis).- Return type:
- figures.data.load_nmin(run_directory)[source]#
Load an
nminrun’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:
- 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:
- 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:
- figures.data.load_pairwise(run_directory)[source]#
Load a pairwise
driftrun’s trajectory table and its manifest metrics.- Parameters:
run_directory (
pathlib.Path) – Thedriftrun directory of a--reference-scheme pairwiserun.- 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:
- figures.data.load_local_trajectory(run_directory)[source]#
Load an
invariance-trajectoryrun’s plane and capture tables and its metrics.- Returns:
(plane, capture, meta): thetrajectory_<axis>discriminant-plane table (anchors and per-focal centroids with the bootstrap tube), the per-classcapture_<axis>table, and the manifest metrics (axis).- Return type:
- figures.data.load_local_specificity(run_directory)[source]#
Load an
invariance-trajectoryrun’s specificity table (endpoint magnitude by axis).
- figures.data.load_local_directional(run_directory)[source]#
Load an
invariance-trajectoryrun’s H0E tables and its metrics.- Returns:
(signed, directional, meta): thesigned_trajectory_<axis>table (per class per focal point, the one-dimensional signed trajectory with its bootstrap band), the per-classdirectional_<axis>summary (net trend, interval,p, FDR decision, break), and the manifest metrics (axis).- Return type:
- figures.data.load_grain_magnitude(run_directory)[source]#
Load an
invariance-trajectoryrun’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:
- figures.data.load_feature_displacement(run_directory)[source]#
Load an
invariance-trajectoryrun’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:
- figures.data.load_demographic_conditioning(run_directory)[source]#
Load a
demographic-conditioningrun’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 covariatelabel,kind, andcoding, and the joined sample size.- Return type:
- figures.data.load_referent(run_directory)[source]#
Load an era
invariance-trajectoryrun’s H0G tables and its metrics.- Returns:
(grains, contrast, meta): thereferent_<axis>table (per class per grain, the per-referent and per-instrument root-mean-square intensity, additive share, and FDR count), the per-classreferent_contrast_<axis>table (the current-minus-retrospective contrast with its interval,p, FDR decision, and mechanism), and the manifest metrics (axis).- Return type:
- figures.data.load_prevalence(run_directory)[source]#
Load a
prevalencerun’s proportion-curve and slope tables and its metrics.- Returns:
(curve, slopes, meta): theproportion_curve_<axis>table (per class per focal point, the corrected and naive predicted proportion with the corrected bootstrap band), theslopes_<axis>table (the corrected, naive, adjusted, and DSM-5 per-class contrasts), and the manifest metrics (axis).- Return type:
- figures.data.load_atlas(run_directory)[source]#
Load a
displacement-atlasrun’s per-axis, per-class endpoint table and its metrics.- Returns:
(atlas, meta): thedisplacement_atlasframe (per axis and reference class, the separation-scaled endpoint displacement, the axislabelandkind, 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:
- 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
alignrun.- 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, withmapping(class id to named class),correlations(class id to a per-class profile correlation orNone),overall_correlation, andanchors_hold.our_proportions (
dictofinttofloat) – The recovered class proportions, by class id.published_proportions (
dictofstrtofloat) – The published class proportions, by named class.comparison (
dict, optional) – A second condition’s reproduction (the V9 subset), with keyssignature,alignment, andproportionsmirroring the primary arguments. When given, each panel adds the subset’s recovered signature and the size line readsfull / 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:
- 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
selectsummary table.- Parameters:
summary (
pandas.DataFrame) – The per-component summary fromanalysis.selection, with ann_componentscolumn and<name>_mean/<name>_stdcolumns for each criterion drawn.reference_k (
int, default4) – The number of classes to mark with a reference line (the Litman choice).criteria (
collections.abc.Sequenceofstr, optional) – The information criteria to draw in the first panel; the first is emphasised. Defaults to("bic", "aic", "caic"). Ignored whencomparisonis 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:
- Raises:
ValueError – When
summaryis 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
stabilityrun.- Parameters:
comparisons (
pandas.DataFrame) – One row per compared fit, withoverall_correlationandadjusted_rand_index.aggregate (
dict) – The aggregate metrics, withcategory_correlation_mean(one entry per category) and a run descriptor (n_fits/top_korn_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:
- 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
nminrun.- Parameters:
per_fit (
pandas.DataFrame) – One row per (size, replicate) fit, withsize,overall_correlation, andsmallest_class_proportion.summary (
pandas.DataFrame) – The per-size summary, with the same three columns.metrics (
dict) – The floor metrics:floorandfloor_ci90(a two-element list, orNone), and thebenchmarkcorrelation.
- Returns:
A two-panel figure: recovery against size, and the smallest class proportion.
- Return type:
- 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
replicaterun.- 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), withoverall_correlation,category_correlation(one entry per category), and optionallyn_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:
- 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
trajectoryrun.- Parameters:
embedding (
pandas.DataFrame) – Theembedding_<axis>table: one row per anchor and per (class, stratum), withkind,ref_class,class_name,order,ld1,ld2,jaccard, andreorganised.meta (
dict) – The run’s manifest metrics, carryingaxisandn_strata.
- Returns:
A 2 by 2 figure, one panel per class.
- Return type:
- 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) – Thetrajectory_<axis>table: anchor rows (with the member covariance) and per-focal centroid rows (with the bootstrap tube boxld1_lo,ld1_hi,ld2_lo,ld2_hi).capture (
pandas.DataFrame) – Thecapture_<axis>table, carrying the per-class in-plane capture fraction.meta (
dict) – The run’s manifest metrics, carryingaxis.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:
- 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 (
dictofstrtopandas.DataFrame) – Thetrajectory_<axis>table per axis name ("age_at_diagnosis","era"); the anchors are taken from the first and the others must share them.captures (
dictofstrtopandas.DataFrame) – Thecapture_<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:
- 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) – Thetrajectory_<axis>table (anchors with the member covariance and per-focal centroids with the tube box).capture (
pandas.DataFrame) – Thecapture_<axis>table.meta (
dict) – The run’s manifest metrics, carryingaxis.
- Returns:
The 2 by 2 figure, one panel per class.
- Return type:
- 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 mergedspecificityrows, withaxis_name,ref_class,class_nameandendpoint_magnitude. The timing axes and the control axes are pooled here.meta (
dict) – Presentation metrics;timing_axesnames the axes drawn as the effect (highlighted).width_in (
float, optional) – The figure width in inches; the height follows the default aspect unlessheight_inis 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:
- 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 mergedspecificityrows (axis_name,ref_class,class_name,endpoint_magnitude), timing and control axes pooled.meta (
dict) – Presentation metrics;timing_axesnames 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:
- 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) – Thesigned_trajectory_<axis>table (ref_class,position,signed,band_lo,band_hi).directional (
pandas.DataFrame) – The per-classdirectional_<axis>summary (net_trend, its interval,reject,break_position).meta (
dict) – The run’s manifest metrics, carryingaxis.
- Returns:
The single-panel figure.
- Return type:
- 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) – Thereferent_<axis>table (per class per grain:grain_kind,grain,referent,rms,share,n_features).contrast (
pandas.DataFrame) – The per-classreferent_contrast_<axis>summary (contrast,ci_low,ci_high,reject,mechanism).meta (
dict) – The run’s manifest metrics, carryingaxis.
- Returns:
The 2 by 2 figure, one panel per class.
- Return type:
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, withposition,observed,null_q50andnull_q95columns (one row per grid point).meta (
dict) – The run’s manifest metrics, carryingaxisandtop_blockfor the labels.
- Returns:
The one-panel figure.
- Return type:
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
attributerun.- Parameters:
summary (
pandas.DataFrame) – Thesummary_<axis>table: one row per stratum and class, withchurn,jaccard,ref_class, andclass_name.category (
pandas.DataFrame) – Thecategory_<axis>table: one row per stratum, class, and category, with the signedcontributionto the squared distance.meta (
dict) – The run’s manifest metrics, carryingaxis.
- Returns:
Panel A (churn heatmap) beside panel B (category composition).
- Return type:
- figures.attribution.mover_contrast_figure(summary, movers, meta, top_k=8)[source]#
Build the per-class mover-contrast figure from an
attributerun.For each class the panel is drawn at the stratum where the class churns most, and shows the
top_kfeatures 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) – Thesummary_<axis>table, used to pick each class’s peak-churn stratum.movers (
pandas.DataFrame) – Themovers_<axis>table: one row per stratum, class, and feature, with the signedeffect, itsmagnitude, and anfdr_significantflag.meta (
dict) – The run’s manifest metrics, carryingaxis.top_k (
int, default8) – Number of features to show per panel.
- Returns:
One panel per class, arranged in a near-square grid.
- Return type:
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 (
dictofstrtopandas.DataFrame) – Thegrain_magnitude_<axis>.parquetframe per axis ("era"and"age_at_diagnosis"), carrying the per-class category-grain magnitudes.features (
dictofstrtopandas.DataFrame) – Thefeature_displacement_<axis>.parquetframe 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:
- 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 (
dictofstrtopandas.DataFrame) – Thegrain_magnitude_<axis>.parquetframe 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:
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 (
dictofstrtopandas.DataFrame) – Thefeature_displacement_<axis>.parquetframe per axis, each carryingfeature,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 (
tupleofstr, 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:
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) – Thereferent_eratable: per class and grain (referent and instrument), the size-fair root-mean-square intensity, additive share, and FDR-surviving feature count.contrast (
pandas.DataFrame) – Thereferent_contrast_eratable: 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:
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) – Thedisplacement_atlastable: per axis and reference class, the separation-scaled endpoint displacement, the axislabelandkind, 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:
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 (
dictofstrtopandas.DataFrame) – Thedemographic_conditioning_<axis>frame per timing axis: per covariate and reference class, theshrinkage, theaxis_r2(constant across a covariate’s classes), the covariatelabel,kind, andcoding, and the joined sample sizen_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:
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) – Theproportion_curve_<axis>table (ref_class,class_name,position,corrected,naive,band_lo,band_hi).slopes (
pandas.DataFrame) – Theslopes_<axis>table; thecorrectedrows supply each panel’s slope, odds ratio, and false-discovery decision.meta (
dict) – The run’s manifest metrics, carryingaxis.
- Returns:
The four-panel figure, one class per panel, sharing the axis.
- Return type:
- 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) – Theproportion_curve_<axis>table (ref_class,class_name,position,corrected,pooled).meta (
dict) – The run’s manifest metrics, carryingaxis.
- Returns:
The single-panel stacked-area figure.
- Return type:
- 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 (
dictofstrtopandas.DataFrame) – Theproportion_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:
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’
trajectoryruns.- Parameters:
roughness_by_axis (
dictofstrtopandas.DataFrame) – Per axis label, theroughness_<axis>table (ref_class,class_name,step,sampling_noise).directional_by_axis (
dictofstrtopandas.DataFrame) – Per axis label, thedirectional_<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:
- 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.
- 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 runfigures.data.resolve_run()would pick.
- Returns:
The written PNG in the documentation tree.
- Return type:
- Raises:
FileNotFoundError – When no PNG has been rendered for the figure yet, so the caller is told to build it first.