---
title: "An independent reproduction of AlphaGenome on a saturation-mutagenesis atlas: zero-shot recovery of disease regulatory element maps and blind recovery of the known TERT promoter cancer hotspots"
author: "William Guesdon"
date: now
date-format: "YYYY-MM-DD HH:mm"
abstract: |
Sequence-to-function models predict regulatory activity from DNA, but whether they capture the
effect of individual base substitutions in disease regulatory elements is best judged against
exhaustive experimental maps. This is an independent reproduction of that test. We asked a
pretrained DNA sequence model, AlphaGenome, to predict the effect of every single-base substitution
across a saturation-mutagenesis reporter atlas of about twenty disease-associated human regulatory
elements, without showing it any measured value. AlphaGenome has itself already been evaluated on
this benchmark, so the result is a reproduction rather than a new model finding. Across the atlas the
model recovers the measured maps above chance. On the TERT promoter the track-averaged predicted
effect correlates with the measured effect at Spearman 0.53. The correlation is specific and
collapses under a label-permutation null. Following a pre-registered procedure, the model's most
impacted predicted bases are enriched for clinically significant variants. The variant it ranks first
is the pathogenic TERT C250T promoter mutation, a recurrent cancer driver that is already known, and
the neighbouring C228T hotspot is recovered in the same blind ranking. The analysis uses open data
and a public non-commercial model API, costs a few dollars, and runs in an afternoon. We report the
method, the per-element recovery, and the conditions under which it succeeds.
format:
html:
theme: cosmo
css: styles/report.css
title-block-banner: false
toc: true
toc-depth: 3
toc-title: "Contents"
toc-location: left
number-sections: true
code-fold: true
code-tools: true
code-summary: "Show code"
embed-resources: true
fig-align: center
fig-width: 8
fig-height: 5
df-print: kable
tbl-cap-location: top
citations-hover: true
bibliography: references.bib
csl: https://www.zotero.org/styles/nature
engine: jupyter
execute:
echo: false
warning: false
cache: false
---
```{python}
#| tags: [setup]
import json
from pathlib import Path
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import Markdown
REPO = Path.cwd().parent if Path.cwd().name == "reports" else Path.cwd()
T = REPO / "results" / "tables"
manifest = pd.read_csv(T / "element_manifest.csv")
qc = pd.read_csv(T / "sequence_reconstruction_qc.csv")
gate = pd.read_csv(T / "oracle_score_summary.csv")
heat = pd.read_csv(T / "heatmap_data_TERT-HEK.csv")
recovery = pd.read_csv(T / "nomination_recovery.csv").iloc[0]
nomination = pd.read_csv(T / "nomination.csv").iloc[0]
annot = pd.read_csv(T / "variant_annotations_TERT-HEK.csv")
# The per-element atlas and its permutation control are produced by scripts/21_score_all.py after the
# full sweep. The report reads them when present and states so plainly when a section depends on them.
by_element = pd.read_csv(T / "spearman_by_element.csv") if (T / "spearman_by_element.csv").exists() else None
poscontrol = pd.read_csv(T / "positive_control.csv") if (T / "positive_control.csv").exists() else None
svz = pd.read_csv(T / "supervised_vs_zeroshot.csv") if (T / "supervised_vs_zeroshot.csv").exists() else None
svz_sum = json.loads((T / "supervised_vs_zeroshot_summary.json").read_text()) if (T / "supervised_vs_zeroshot_summary.json").exists() else None
locus_boot = json.loads((T / "atlas_locus_bootstrap.json").read_text()) if (T / "atlas_locus_bootstrap.json").exists() else None
N_ELEMENTS = len(manifest)
N_PROM = int((manifest["element_class"] == "promoter").sum())
N_ENH = int((manifest["element_class"] == "enhancer").sum())
N_SNV = int(manifest["n_snv"].sum())
N_QC_OK = int(qc["coordinate_ok"].sum())
tert_dnase = gate[gate["output_type"] == "DNASE"].iloc[0]
tert_cage = gate[gate["output_type"] == "CAGE"].iloc[0]
# Report palette and styling.
from matplotlib.colors import LinearSegmentedColormap
PALETTE = {"ink": "#1a1a1a", "ink2": "#4d4d4d", "muted": "#8c8c8c", "grid": "#e3e3e0",
"axis": "#b8b8b2", "blue": "#3070b3", "vermillion": "#c9542b", "grey": "#cfcfc9"}
# Diverging map: blue for silencing, near-white at zero, vermillion for activating.
DIVERGE = LinearSegmentedColormap.from_list("bwv", [PALETTE["blue"], "#f7f7f5", PALETTE["vermillion"]])
plt.rcParams.update({"figure.dpi": 130, "font.size": 10, "font.family": "sans-serif",
"axes.spines.top": False, "axes.spines.right": False})
def axis_style(ax, xgrid: bool = False, ygrid: bool = True) -> None:
"""Apply a consistent minimal style to one axis.
Args:
ax: The axis to style.
xgrid: Draw the vertical grid.
ygrid: Draw the horizontal grid.
"""
for side in ("top", "right"):
ax.spines[side].set_visible(False)
for side in ("left", "bottom"):
ax.spines[side].set_color(PALETTE["axis"])
ax.spines[side].set_linewidth(0.8)
if xgrid:
ax.grid(axis="x", color=PALETTE["grid"], linewidth=0.6, zorder=0)
if ygrid:
ax.grid(axis="y", color=PALETTE["grid"], linewidth=0.6, zorder=0)
ax.set_axisbelow(True)
ax.tick_params(colors=PALETTE["ink2"], labelsize=8.5, length=3, width=0.8)
def twin_heatmap(df, hotspots=()):
"""Draw the measured and predicted saturation-mutagenesis heatmaps for one element.
Args:
df: Frame with columns offset, alt, pos, measured, predicted_dnase.
hotspots: Genomic positions to mark with an arrow over the measured map.
"""
order = ["A", "C", "G", "T"]
piv_m = df.pivot_table(index="alt", columns="offset", values="measured").reindex(order)
piv_p = df.pivot_table(index="alt", columns="offset", values="predicted_dnase").reindex(order)
# Colour both maps on the same absolute, zero-centred scale (each normalised by its own 98th
# percentile below), so red is always activating and blue silencing. Do not subtract the element
# mean from the predicted map: that moves the colour zero off true zero and flips the sign of
# substitutions that lie between zero and the mean.
fig, axes = plt.subplots(2, 1, figsize=(9, 3.2), sharex=True)
for ax, data, title in ((axes[0], piv_m.values, "Measured (Kircher MPRA)"),
(axes[1], piv_p.values, "Predicted (AlphaGenome, DNA only)")):
vmax = np.nanpercentile(np.abs(data), 98)
ax.imshow(data, aspect="auto", cmap=DIVERGE, vmin=-vmax, vmax=vmax)
ax.set_yticks(range(4)); ax.set_yticklabels(order)
ax.set_ylabel(title, fontsize=8, color=PALETTE["ink2"])
ax.tick_params(colors=PALETTE["ink2"], labelsize=8, length=3, width=0.8)
for side in ("top", "right", "left", "bottom"):
ax.spines[side].set_color(PALETTE["axis"]); ax.spines[side].set_linewidth(0.8)
start = int(df["pos"].min())
for h in hotspots:
off = h - start
axes[0].annotate("", xy=(off, -0.6), xytext=(off, -1.6),
arrowprops=dict(arrowstyle="->", color=PALETTE["ink"], lw=1.2))
axes[1].set_xlabel("position in element (bp)", color=PALETTE["ink2"])
fig.tight_layout(); plt.show()
```
# Introduction
Non-coding variation drives a large fraction of disease risk, yet the functional consequence of a
single base change in a regulatory element is hard to read from sequence alone. Massively parallel
reporter assays measure that consequence directly. Saturation mutagenesis takes this to its limit and
measures the effect of every possible single-base substitution in an element [@kircher2019].
In parallel, DNA sequence models have learned to predict regulatory activity from sequence
[@avsec2021enformer]. The natural test of such a model is whether it can redraw a measured
saturation-mutagenesis map, base by base, for elements it was not trained to fit. If it can, the model
becomes a cheap in-silico assay for prioritising causal non-coding variants.
We independently reproduce the reported behaviour of a recent DNA sequence model, AlphaGenome
[@alphagenome2026], on an open saturation-mutagenesis atlas of about twenty disease-associated
regulatory elements [@kircher2019]. These elements formed the CAGI5 Regulation Saturation community
benchmark, in which supervised models were scored against the same measured maps, with sequence-based
methods such as deltaSVM reaching only modest per-element correlations [@shigaki2019]. AlphaGenome has
itself already been evaluated on this benchmark, so the result here is a reproduction rather than a new
model finding, and that benchmark is the reference point for the correlations reported. The model is
used zero-shot. It never sees a measured value. We ask three questions. Does the model
recover the measured maps above chance. Is the agreement specific rather than an artifact of the score
distribution. And do the bases the model predicts to be most impactful coincide with variants that are
independently known to be clinically significant. The aim is a validated, low-cost, reproducible recipe
for reading a disease regulatory element base by base, and one concrete variant nomination. AlphaGenome
is used under its non-commercial API terms. The code and the data are open. The model weights are not.
# Methods
## Data
The ground truth is the saturation-mutagenesis reporter atlas of @kircher2019, retrieved from the
authors' repository and frozen at a fixed commit. The atlas provides, for each element and genome
build, one measured log2 activity effect per single-base substitution. We use the GRCh38 coordinates.
```{python}
Markdown(
f"The frozen atlas contains **{N_ELEMENTS} element tracks** across about twenty loci, "
f"**{N_PROM} promoter** and **{N_ENH} enhancer** tracks, and **{N_SNV:,} measured single-base "
f"substitutions**. Some loci were assayed in several cell types or constructs, which is why the "
f"number of tracks exceeds the number of loci."
)
```
## Coordinate validation
Before any prediction, each element's reference sequence was reconstructed from the frozen table and
checked against the GRCh38 reference from the Ensembl-associated UCSC service. This guards against the
single most common failure mode, a coordinate or strand error between the measured table and the
model's input.
```{python}
tert_qc = qc[qc["element"] == "TERT-HEK"].iloc[0]
Markdown(
f"All **{N_QC_OK} of {len(qc)}** element tracks matched the GRCh38 reference on the forward strand "
f"at or above 99 percent. The TERT-HEK promoter, used below as the primary test, matched at "
f"{tert_qc['match_fwd']:.0%}."
)
```
## Oracle and in-silico saturation mutagenesis
For each element a window of 16,384 bp was centred on the element. AlphaGenome performed in-silico
saturation mutagenesis over the element, scoring every single-base substitution. Variant effects were
summarised with AlphaGenome's recommended centre-mask scorer, which reports the log2 change in the
summed signal of a regulatory output track over a window centred on the variant. Two output modalities
were scored, chromatin accessibility (DNASE) and cap analysis of gene expression (CAGE). The model was
run through the public AlphaGenome API and saw only DNA.
## Scoring and controls
For each element and output modality, the predicted effect of a variant was averaged across all cell
type tracks of that modality, which involves no within-modality track selection. We report chromatin
accessibility (DNASE) as the primary readout because it gives the higher rank correlation across the
atlas, and we report the expression readout (CAGE) alongside it with equal prominence; for promoters
the two are close and CAGE is marginally higher. Agreement with the measured effect was quantified by
the Spearman rank correlation across the element's substitutions. Two controls were applied. A label-permutation null shuffled the measured effects within each element to confirm the
correlation is not a distributional artifact. A pre-registered recovery test asked whether the top
decile of predicted absolute effects coincides with clinically significant variants more than a
permutation null of the same size.
## Variant annotation and nomination
Known variants overlapping each element were retrieved from the Ensembl variation REST API on GRCh38
[@cunningham2022ensembl] for their rsID and consequence. Clinical significance was taken per allele
from ClinVar, so a classification applies only to the exact substitution it belongs to and not to the
other alternate alleles at the same position. This annotation was queried after the predictions existed
and never entered the model. The nomination criterion was fixed in advance (see the pre-registration):
the nominated variant maximises the smaller of its predicted and measured absolute rank, subject to a
consistent sign between predicted and measured effect.
# Results
## The model recovers the TERT promoter map zero-shot
The TERT promoter is the primary positive control. Its 259 bp are fully saturated, so all 777
single-base substitutions are measured. Predicting from sequence alone, the model reproduces the
measured map.
```{python}
Markdown(
f"The track-averaged DNASE predicted effect correlates with the measured effect at "
f"**Spearman {tert_dnase['spearman_track_mean']:+.3f}** across all {int(tert_dnase['n_variants'])} "
f"substitutions, with the best single accessibility track reaching "
f"{tert_dnase['spearman_best_track']:+.3f}. The expression readout (CAGE) gives a comparable "
f"Spearman {tert_cage['spearman_track_mean']:+.3f}. This is a zero-shot result with no fitted "
f"parameters."
)
```
```{python}
#| label: fig-heatmap
#| fig-cap: "Measured and predicted saturation-mutagenesis maps of the TERT-HEK promoter. Each column is a position and each row an alternate base. Colour is the effect on activity: red increases, blue decreases. The measured map is on top and the model's map, computed from DNA alone, is below. The two share the same activating and silencing structure. Arrows mark the two recurrent cancer driver positions, C228T and C250T."
bases = ["A", "C", "G", "T"]
piv_m = heat.pivot_table(index="alt", columns="offset", values="measured").reindex(bases)
piv_p = heat.pivot_table(index="alt", columns="offset", values="predicted_dnase").reindex(bases)
# Colour both maps on the same absolute, zero-centred scale (each normalised by its own 98th
# percentile below). Do not subtract the element mean from the predicted map: that would move the
# colour zero off true zero and flip the sign of substitutions between zero and the mean.
fig, axes = plt.subplots(2, 1, figsize=(9, 3.4), sharex=True)
for ax, piv, title in ((axes[0], piv_m, "Measured (Kircher MPRA)"), (axes[1], piv_p, "Predicted (AlphaGenome, DNA only)")):
data = piv.values if hasattr(piv, "values") else piv
vmax = np.nanpercentile(np.abs(data), 98)
im = ax.imshow(data, aspect="auto", cmap=DIVERGE, vmin=-vmax, vmax=vmax)
ax.set_yticks(range(4)); ax.set_yticklabels(bases)
ax.set_ylabel(title, fontsize=8, color=PALETTE["ink2"])
ax.tick_params(colors=PALETTE["ink2"], labelsize=8, length=3, width=0.8)
for side in ("top", "right", "left", "bottom"):
ax.spines[side].set_color(PALETTE["axis"]); ax.spines[side].set_linewidth(0.8)
start = int(heat["pos"].min())
for hotspot in (1295113, 1295135):
off = hotspot - start
axes[0].annotate("", xy=(off, -0.6), xytext=(off, -1.6),
arrowprops=dict(arrowstyle="->", color=PALETTE["ink"], lw=1.2))
axes[1].set_xlabel("position in element (bp)", color=PALETTE["ink2"])
fig.tight_layout(); plt.show()
```
## The same holds for a distal enhancer
The SORT1 enhancer at the 1p13 cardiovascular locus is one of the best characterised human enhancers.
Predicting its map from sequence alone recovers the measured structure, though the correlation is lower
than for the TERT promoter, consistent with the harder grammar of distal elements.
```{python}
#| label: fig-heatmap-sort1
#| fig-cap: "Measured and predicted saturation-mutagenesis maps of the SORT1 enhancer, drawn as in the previous figure. The predicted map, from DNA alone, shares the activating and silencing structure of the measured map."
sort1 = pd.read_csv(T / "heatmap_data_SORT1.csv")
twin_heatmap(sort1)
```
## The agreement holds across the atlas and is specific
```{python}
if by_element is not None and poscontrol is not None:
dnase = by_element[by_element["output_type"] == "DNASE"].sort_values("spearman_track_mean")
pc = poscontrol[poscontrol["output_type"] == "DNASE"].set_index("group")
allrow = pc.loc["all"]
prom = pc.loc["promoter"] if "promoter" in pc.index else None
enh = pc.loc["enhancer"] if "enhancer" in pc.index else None
txt = (
f"Across the **{int(allrow['n_elements'])} element tracks**, the median track-averaged DNASE "
f"Spearman is **{allrow['median_spearman']:+.3f}**, which reproduces above-chance recovery across "
f"the atlas. Under the label-permutation null the median is {allrow['perm_null_median']:+.3f} "
f"(permutation p = {allrow['perm_p']:.4f}), so the agreement is specific to the pairing of "
f"predicted and measured effects."
)
if locus_boot is not None:
txt += (
f" The tracks are not independent: several are the same locus assayed in different cell "
f"types, so the {int(allrow['n_elements'])} tracks collapse to **{int(locus_boot['n_loci'])} "
f"distinct loci**, which are the proper inferential unit. Averaging contexts within each "
f"locus, the median DNASE Spearman across loci is **{locus_boot['locus_median']:+.3f}** "
f"(95 percent bootstrap {locus_boot['boot_lo']:.2f} to {locus_boot['boot_hi']:.2f}, "
f"{int(locus_boot['n_boot']):,} resamples, seed {int(locus_boot['seed'])}), and "
f"{int(locus_boot['positive_loci'])} of {int(locus_boot['n_loci'])} loci are positive. The "
f"locus-level median is lower than the track-level median, and it is the value to read as the "
f"headline. The TERT enrichment below is a pre-selected positive control and is reported as "
f"descriptive, not as a confirmatory test."
)
if prom is not None and enh is not None:
txt += (
f" Promoters are recovered better than enhancers (median "
f"{prom['median_spearman']:+.3f} versus {enh['median_spearman']:+.3f}), consistent with the "
f"model's stronger resolution of promoter-proximal regulatory grammar."
)
display(Markdown(txt))
else:
display(Markdown("_The full-atlas sweep is still completing; this section is finalised from "
"`spearman_by_element.csv` and `positive_control.csv` once the sweep lands._"))
```
```{python}
#| label: fig-atlas
#| fig-cap: "Per-element agreement between predicted and measured saturation-mutagenesis maps. Each bar is the track-averaged DNASE Spearman for one element, coloured by regulatory class. The dashed line is the label-permutation null."
if by_element is not None:
dnase = by_element[by_element["output_type"] == "DNASE"].sort_values("spearman_track_mean")
colours = [PALETTE["vermillion"] if c == "promoter" else PALETTE["blue"] for c in dnase["element_class"]]
fig, ax = plt.subplots(figsize=(8, max(3, 0.28 * len(dnase))))
ax.barh(dnase["element"], dnase["spearman_track_mean"], color=colours, zorder=3)
axis_style(ax, xgrid=True, ygrid=False)
ax.axvline(0.0, color=PALETTE["axis"], lw=0.8)
from matplotlib.patches import Patch
handles = [Patch(color=PALETTE["vermillion"], label="promoter"), Patch(color=PALETTE["blue"], label="enhancer")]
if poscontrol is not None:
null_med = poscontrol[poscontrol["output_type"] == "DNASE"]["perm_null_median"].median()
ax.axvline(null_med, color=PALETTE["ink2"], ls="--", lw=0.9)
handles.append(mpl.lines.Line2D([0], [0], color=PALETTE["ink2"], ls="--", lw=0.9, label="permutation null"))
ax.set_xlabel("Spearman (predicted vs measured), DNASE track mean", color=PALETTE["ink2"])
ax.legend(handles=handles, loc="lower right", fontsize=8, frameon=False)
fig.tight_layout(); plt.show()
else:
plt.figure(figsize=(6, 1)); plt.text(0.5, 0.5, "atlas sweep completing", ha="center"); plt.axis("off"); plt.show()
```
The recovery is not confined to the best element. Across a spread of elements from the strongest to the
weakest, the predicted and measured effects track each other, and the few elements with no signal are
visible as the diffuse panels.
```{python}
#| label: fig-gallery
#| fig-cap: "Predicted versus measured effect for six representative elements spanning the performance range: three promoters (top row) and three enhancers (bottom row). Each point is one single-base substitution. The title gives the DNASE track-mean Spearman. TERT-HEK and LDLR are strong, IRF4 and SORT1 moderate, HBB weaker, and FOXE1 near zero."
gallery = ["TERT-HEK", "LDLR", "HBB", "IRF4", "SORT1", "FOXE1"]
sp_d = pd.read_csv(T / "spearman_by_element.csv")
sp_d = sp_d[sp_d["output_type"] == "DNASE"].set_index("element")["spearman_track_mean"]
cls_of = manifest.set_index("element")["element_class"]
fig, axes = plt.subplots(2, 3, figsize=(8.5, 5.4))
for ax, el in zip(axes.ravel(), gallery):
g = pd.read_csv(T / f"heatmap_data_{el}.csv")
col = PALETTE["vermillion"] if cls_of.get(el) == "promoter" else PALETTE["blue"]
ax.scatter(g["measured"], g["predicted_dnase"], s=5, c=col, edgecolor="none", alpha=0.45, zorder=3)
axis_style(ax, xgrid=True, ygrid=True)
ax.set_title(f"{el} ρ = {sp_d.get(el, float('nan')):+.2f}", fontsize=9, color=PALETTE["ink"])
ax.tick_params(labelsize=7)
for ax in axes[:, 0]:
ax.set_ylabel("predicted", fontsize=8, color=PALETTE["ink2"])
for ax in axes[1, :]:
ax.set_xlabel("measured", fontsize=8, color=PALETTE["ink2"])
fig.tight_layout(); plt.show()
```
The gap between promoters and enhancers, and between the two readout modalities, is summarised below.
```{python}
#| label: fig-class-modality
#| fig-cap: "Per-element agreement by regulatory class and readout modality. Each point is one element; the horizontal bar is the group median. Promoters are recovered better than enhancers in both modalities. For promoters the two modalities are comparable; enhancers are recovered better by the local accessibility readout (DNASE) than by the expression readout (CAGE)."
sp = pd.read_csv(T / "spearman_by_element.csv")
groups = [("promoter", "DNASE"), ("promoter", "CAGE"), ("enhancer", "DNASE"), ("enhancer", "CAGE")]
fig, ax = plt.subplots(figsize=(7, 3.8))
labels = []
for i, (cls, mod) in enumerate(groups):
vals = sp[(sp["element_class"] == cls) & (sp["output_type"] == mod)]["spearman_track_mean"].to_numpy()
col = PALETTE["vermillion"] if cls == "promoter" else PALETTE["blue"]
x = np.full(len(vals), i, dtype=float) + np.linspace(-0.13, 0.13, len(vals))
ax.scatter(x, vals, s=20, c=col, alpha=0.65, edgecolor="none", zorder=3)
ax.plot([i - 0.22, i + 0.22], [np.median(vals)] * 2, color=PALETTE["ink"], lw=2.2, zorder=4)
labels.append(f"{cls}\n{mod}")
axis_style(ax, xgrid=False, ygrid=True)
ax.axhline(0, color=PALETTE["axis"], lw=0.8)
ax.set_xticks(range(4)); ax.set_xticklabels(labels, fontsize=8.5, color=PALETTE["ink2"])
ax.set_ylabel("Spearman (predicted vs measured)", color=PALETTE["ink2"])
fig.tight_layout(); plt.show()
```
## Blind recovery of the known TERT promoter cancer hotspots
The model's predictions are blind to any annotation. We then asked whether the positions it ranks as
most impactful are also flagged in an independent clinical database, using allele-specific ClinVar. The
three flagged alleles here are not equivalent. TERT C250T is a ClinVar Pathogenic record, while C228T
and the c.-57A>C allele carry conflicting classifications of pathogenicity in ClinVar. We treat all
three as ClinVar-flagged alleles and do not read a conflicting record as an unqualified Pathogenic one. We test at the position level because the flagged
variants sit at only a few positions; scoring each position by its strongest predicted effect avoids
treating those few as exchangeable across every substitution. TERT is a pre-selected positive control,
so this is a descriptive check rather than a confirmatory test.
```{python}
Markdown(
f"The element spans **{int(recovery['n_positions'])} positions**, of which "
f"**{int(recovery['n_clinical_positions'])} carry a ClinVar-flagged variant**. Ranking "
f"positions by their strongest predicted effect, all "
f"**{int(recovery['observed_clinical_in_top'])} of {int(recovery['n_clinical_positions'])}** fall in "
f"the top decile ({int(recovery['n_top_decile'])} positions), against a permutation null of "
f"{recovery['expected_null_mean']:.2f} (permutation p = **{recovery['perm_p']:.4f}**). The ClinVar "
f"flag and the measured effect size are not fully mechanistically independent, so this is "
f"corroboration rather than a wholly orthogonal validation."
)
```
```{python}
#| label: fig-recovery
#| fig-cap: "Predicted versus measured effect for every TERT-HEK substitution. Grey points are unannotated substitutions. Red points carry an allele-specific ClinVar flag: C250T is Pathogenic, while C228T and the c.-57A>C allele carry conflicting classifications of pathogenicity. The two recurrent cancer driver mutations, C228T and C250T, sit among the most impactful by both the blind model and the assay."
d = annot.copy()
clin = d[d["is_clinical"] == True] # noqa: E712
fig, ax = plt.subplots(figsize=(5.2, 5))
axis_style(ax, xgrid=True, ygrid=True)
ax.scatter(d["measured"], d["predicted_dnase"], s=8, c=PALETTE["grey"], edgecolor="none", label="unannotated", zorder=2)
ax.scatter(clin["measured"], clin["predicted_dnase"], s=42, c=PALETTE["vermillion"],
edgecolor=PALETTE["ink"], lw=0.4, label="ClinVar flagged", zorder=3)
for _, r in clin.iterrows():
# Name only the established driver allele (the minus-strand C>T equals a plus-strand G>A).
if r["pos"] in (1295113, 1295135) and r["ref"] == "G" and r["alt"] == "A":
name = "C228T" if r["pos"] == 1295113 else "C250T"
ax.annotate(f"{name}\n{int(r['pos'])} {r['ref']}>{r['alt']}", (r["measured"], r["predicted_dnase"]),
fontsize=7, xytext=(6, 0), textcoords="offset points", va="center", color=PALETTE["ink"])
ax.axhline(0, color=PALETTE["axis"], lw=0.6); ax.axvline(0, color=PALETTE["axis"], lw=0.6)
ax.set_xlabel("measured effect (log2)", color=PALETTE["ink2"])
ax.set_ylabel("predicted effect (DNASE, log2)", color=PALETTE["ink2"])
ax.legend(fontsize=8, loc="upper left", frameon=False); fig.tight_layout(); plt.show()
```
```{python}
known = "a known" if bool(nomination["known_variant"]) else "a novel"
Markdown(
f"**Nomination.** The pre-registered criterion nominates "
f"**chr{nomination['chrom']}:{int(nomination['pos'])} {nomination['ref']}>{nomination['alt']}**, "
f"which is {known} variant, {nomination['rs_id']}, reported as "
f"*{nomination['clinical_significance']}*. Its predicted effect is "
f"{nomination['predicted_dnase']:+.3f} and its measured effect is {nomination['measured']:+.3f}, "
f"both strongly activating. On the minus-strand TERT gene this genomic substitution is the C250T "
f"promoter mutation, one of the two canonical TERT promoter cancer drivers [@huang2013; @horn2013; "
f"@vinagre2013]. The model placed it at the top of its blind ranking."
)
```
## A supervised baseline, trained on AWS, and where the pretrained model still wins
The natural question is how a purpose-built supervised model, trained on these very effects, compares to
the pretrained model that never sees them. We trained one and compared the two head to head. The model is
a gradient-boosted decision-tree regressor on sequence-derived features only: the k-mer spectrum of the
reference window around each position, the change in that spectrum caused by the substitution (the
deltaSVM idea), and a few local context features, with no coordinate or absolute-position feature. This is
the feature-and-model family the CAGI5 Regulation Saturation community challenge was built on
[@shigaki2019; @kreimer2019]. It was trained on Amazon Web Services, as a managed SageMaker job launched
from the command line; the trainer and the launcher are in the repository.
We score it two ways, both as the field does. The first is the CAGI5 protocol: per element, hold out whole
positions, train on the rest, and correlate predicted with measured effect. The second is a deliberately
harder cross-element stress test, leave-one-locus-out, in which the model must predict a locus it never
saw, with elements that share genomic coordinates grouped so that no locus straddles train and test.
```{python}
if svz_sum is not None:
s = svz_sum
display(Markdown(
f"Under the within-element protocol the supervised model reaches a median "
f"**Pearson {s['supervised_within_pearson_median']:+.3f}** "
f"(Spearman {s['supervised_within_spearman_median']:+.3f}) across {int(s['n_elements'])} elements, "
f"with promoters at {s['supervised_within_pearson_promoter']:+.3f} and enhancers at "
f"{s['supervised_within_pearson_enhancer']:+.3f}. This sits within the band of the strongest CAGI5 "
f"supervised submissions (about 0.45 continuous Pearson) and above the classical single-track "
f"deltaSVM level, so it is a fair, competitive comparator rather than a strawman."
))
```
```{python}
if svz_sum is not None:
s = svz_sum
display(Markdown(
f"Scored on the same central substitutions the supervised model uses "
f"({int(s['n_matched_substitutions']):,} of the {int(N_SNV):,}, the rest dropped for lacking a "
f"full context window), the zero-shot AlphaGenome model, which sees no measured value, is "
f"competitive with the trained one: median **Pearson {s['zeroshot_pearson_matched_median']:+.3f}** "
f"(Spearman {s['zeroshot_spearman_matched_median']:+.3f}) against the supervised model's "
f"{s['supervised_within_pearson_median']:+.3f}. The supervised model is modestly ahead by the "
f"median, but AlphaGenome, with no training, wins the head-to-head on "
f"{int(s['alphagenome_beats_supervised_pearson_matched_n'])} of {int(s['n_elements'])} elements. "
f"It wins where it matters most. On the TERT promoter it reaches Pearson "
f"{s['tert_zeroshot_pearson_matched']:+.3f} against the supervised model's "
f"{s['tert_supervised_within_pearson']:+.3f}. Under leave-one-locus-out the supervised model "
f"collapses to a median Spearman of {s['supervised_lolo_spearman_median']:+.3f}, while the "
f"pretrained model needs no training and keeps its per-element Spearman of about "
f"{s['zeroshot_spearman_matched_median']:+.3f}. The right reading is cross-task transfer without "
f"fitting the MPRA labels, not a matched generalisation test. AlphaGenome was trained genome-wide "
f"and was never put through the supervised model's leave-one-locus-out split. On this atlas of "
f"about twenty loci the supervised model does not transfer across loci. We report this as an "
f"observation, not a general law. The cross-locus collapse of trained models has been described "
f"before [@sasse2023; @kreimer2019], and the pattern in which a pretrained sequence model matches "
f"or beats supervised CAGI5 entries reproduces an earlier result [@avsec2021enformer]."
))
```
```{python}
#| label: fig-supervised
#| fig-cap: "Per-element agreement for the two models on the same central substitutions. Because the supervised model needs a full context window, both models are scored on the same 34,867 central substitutions here. The supervised gradient-boosted model was trained on the measured effects; the zero-shot AlphaGenome model sees none. Each pair of bars is one element, ordered by the zero-shot score. The supervised model is modestly ahead by the median, but the pretrained model wins the head-to-head on more than half the elements, is far ahead on the TERT promoter, and it alone transfers to an unseen locus. The two are not a matched generalisation test: the supervised model was trained under leave-one-locus-out, the pretrained model was not."
if svz is not None:
d = svz.dropna(subset=["within_pearson", "zs_pearson_matched"]).sort_values("zs_pearson_matched").reset_index(drop=True)
y = np.arange(len(d)); bar = 0.4
fig, ax = plt.subplots(figsize=(8, max(3, 0.34 * len(d))))
ax.barh(y + bar / 2, d["within_pearson"], height=bar, color=PALETTE["grey"], label="supervised (trained)", zorder=3)
ax.barh(y - bar / 2, d["zs_pearson_matched"], height=bar, color=PALETTE["blue"], label="AlphaGenome (zero-shot)", zorder=3)
ax.set_yticks(y); ax.set_yticklabels(d["element"], fontsize=7)
axis_style(ax, xgrid=True, ygrid=False)
ax.axvline(0, color=PALETTE["axis"], lw=0.8)
ax.set_xlabel("Pearson (predicted vs measured), per element", color=PALETTE["ink2"])
ax.legend(fontsize=8, loc="lower right", frameon=False)
fig.tight_layout(); plt.show()
```
The comparison is the point. A pretrained model, given no measured values and no task-specific training,
is competitive with a supervised model built on the community-standard recipe on this atlas, and is far
ahead on the flagship cancer promoter. It also transfers to an unseen locus without fitting the measured
labels, where the trained model collapses. This is cross-task transfer rather than a matched generalisation
test, and it is the practical case for reading a disease element with a pretrained sequence model rather
than training a new one for every assay.
# Discussion
A pretrained DNA sequence model, used with no fitted parameters and no sight of any measured value,
reproduces the saturation-mutagenesis map of a disease promoter at a rank correlation of about 0.5,
recovers the measured maps across an atlas above a permutation null, and concentrates its highest
predicted effects on bases that are independently annotated as clinically significant. The nominated
variant is the pathogenic TERT C250T promoter mutation, and the neighbouring C228T mutation is
recovered in the same ranking. These two substitutions create de-novo ETS transcription factor binding
sites and are among the most common non-coding driver mutations in human cancer [@vinagre2013]. The
zero-shot correlations reported here fall within the range of the supervised methods evaluated on these
same CAGI5 elements and exceed the classical deltaSVM baseline, despite the model using no parameters
fitted to the assay [@shigaki2019].
The contribution is not that a sequence model can read these maps. The AlphaGenome authors already
evaluated their model on this exact CAGI5 saturation-mutagenesis benchmark, reaching Pearson 0.57 with
cell-type-matched DNase, 0.63 with an all-cell DNase regression, and 0.65 with a multimodal regression
[@alphagenome2026]. What is offered here is an independent reproduction with a deliberately simple,
tissue-agnostic recipe: a single all-track average with no cell-type selection, run through a public API
for a few dollars in an afternoon. On top of it we add locus-level uncertainty across the 21 distinct
loci, an allele-specific ClinVar audit of the highest-impact predictions, and a reusable interactive
explorer of the measured and predicted maps. The code and the data are open. The model runs through a
public non-commercial API and its weights are not open.
## Limitations
The zero-shot correlation of about 0.5 is meaningful but far from the experimental ceiling; a fitted
readout head over cell type tracks would likely improve it and is left for future work. The
track-averaged readout deliberately avoids cell type matching, which trades peak accuracy for an
honest, selection-free headline. The clinical-recovery test rests on the variants that happen to be
annotated in a single element and is strongest where annotation is dense, as at the heavily studied
TERT promoter; it should be read as a proof of concept rather than a calibrated screen. Enhancers are
recovered less well than promoters. This is consistent with the harder regulatory grammar of distal
elements, but it is also confounded by two choices: the window is AlphaGenome's smallest supported
context, and the centre-mask scorer is local, so an enhancer whose activity depends on a distal target
promoter may have that target clipped from the window. A larger-window sensitivity analysis was not run
and is left for future work. The measured atlas is itself a reporter assay and does not observe the
native chromatin context.
## Conclusion
Reading a disease regulatory element base by base with a public sequence model is now cheap, open, and
reproducible. Used zero-shot, the model recovers the measured maps above chance and places a canonical
cancer driver at the top of its blind ranking. That recovery is a strong positive control rather than a
novel discovery, and it is exactly the property that makes the approach trustworthy as a triage layer.
The method, the per-element recovery, and the conditions under which it succeeds are reported here for
reuse.
# References
::: {#refs}
:::
```{python}
import subprocess
commit = subprocess.run(["git", "-C", str(REPO), "rev-parse", "--short", "HEAD"],
capture_output=True, text=True).stdout.strip() or "unknown"
Markdown(f"*Rendered from committed tables in `results/tables/` at source commit {commit}. "
f"Atlas element tracks: {N_ELEMENTS}. Measured substitutions: {N_SNV:,}.*")
```