Source code for figures.referent_decomposition
"""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.
"""
from __future__ import annotations
import numpy as np
import pandas as pd
from matplotlib.figure import Figure
from matplotlib.gridspec import GridSpec
from figures import style
# The two referents get a fixed colour: retrospective (the population signature) blue, current-state
# (the timing signature) vermillion, from the house palette.
_RETROSPECTIVE = style.PALETTE[0]
_CURRENT = style.PALETTE[3]
_REFERENT_COLOUR = {"retrospective": _RETROSPECTIVE, "current_state": _CURRENT}
_REFERENT_LABEL = {"retrospective": "retrospective / lifetime", "current_state": "current-state"}
_INSTRUMENT_LABEL = {
"scq": "SCQ (lifetime)",
"background_history_child": "developmental history",
"cbcl_6_18": "CBCL 6-18",
"rbsr": "RBS-R",
}
[docs]
def referent_decomposition_figure(
grains: pd.DataFrame, contrast: pd.DataFrame, meta: dict
) -> Figure:
"""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
-------
matplotlib.figure.Figure
The two-panel referent figure.
"""
import matplotlib.pyplot as plt
contrast = contrast.sort_values("ref_class")
classes = contrast["ref_class"].tolist()
names = contrast.set_index("ref_class")["class_name"].to_dict()
y = np.arange(len(classes))[::-1]
instruments = grains[grains["grain_kind"] == "instrument"]
instrument_names = sorted(
instruments["grain"].unique(),
key=lambda n: str(instruments.loc[instruments["grain"] == n, "referent"].iloc[0]),
)
with style.house_style():
fig = plt.figure(figsize=(9.4, 3.8))
grid = GridSpec(1, 2, width_ratios=(1.0, 1.2), wspace=0.3, figure=fig)
# Panel A: the per-class current-minus-retrospective contrast.
ax_a = fig.add_subplot(grid[0, 0])
for pos, (_, row) in zip(y, contrast.iterrows(), strict=True):
colour = _CURRENT if row["contrast"] > 0 else _RETROSPECTIVE
ax_a.barh(pos, row["contrast"], color=colour, height=0.62, zorder=2)
ax_a.plot(
[row["ci_low"], row["ci_high"]],
[pos, pos],
color="#333333",
linewidth=1.2,
zorder=3,
)
if bool(row["reject"]):
ax_a.text(
row["contrast"],
pos + 0.34,
"*",
ha="center",
va="center",
fontsize=11,
color="#333333",
)
ax_a.axvline(0.0, color=style.REFERENCE_COLOUR, linewidth=0.8)
ax_a.set_yticks(y)
ax_a.set_yticklabels([names[c] for c in classes])
ax_a.set_xlabel("current − retrospective RMS contrast")
style.panel_title(ax_a, "A", "referent contrast (all retrospective-dominant)")
# Panel B: the per-instrument RMS intensity, grouped by class and coloured by referent.
ax_b = fig.add_subplot(grid[0, 1])
n_inst = len(instrument_names)
width = 0.8 / max(n_inst, 1)
seen: set[str] = set()
for k, name in enumerate(instrument_names):
sub = instruments[instruments["grain"] == name].set_index("ref_class")
referent = str(sub["referent"].iloc[0])
colour = _REFERENT_COLOUR.get(referent, style.REFERENCE_COLOUR)
xs = np.arange(len(classes)) + (k - (n_inst - 1) / 2) * width
heights = [float(sub["rms"].get(c, 0.0)) for c in classes]
label = _REFERENT_LABEL.get(referent, referent) if referent not in seen else None
ax_b.bar(xs, heights, width=width, color=colour, label=label, zorder=2)
for x, height in zip(xs, heights, strict=True):
ax_b.text(
x,
height + 0.005,
str(k + 1),
ha="center",
va="bottom",
fontsize=5.5,
color="#555555",
)
seen.add(referent)
ax_b.set_xticks(np.arange(len(classes)))
ax_b.set_xticklabels([names[c] for c in classes], rotation=20, ha="right")
ax_b.set_ylabel("size-fair RMS intensity")
ax_b.legend(loc="upper left", fontsize=7, title="referent", title_fontsize=7)
style.panel_title(ax_b, "B", "which instruments carry the drift")
key = ", ".join(
f"{i + 1} {_INSTRUMENT_LABEL.get(n, n)}" for i, n in enumerate(instrument_names)
)
fig.text(
0.5,
-0.14,
f"instruments per class: {key}",
ha="center",
fontsize=6.4,
color="#555555",
)
return fig