Spaces:
Running
Sync S_bal^test scores and intervals with the technical report
Browse filesThe leaderboard shows the S_bal^test scores and 95% intervals from the technical report's latest main (`dotomics/BOTANIC1-technical-report` @ d1bc232d, identical to `overleaf`). Scores and intervals come from the same evaluation; intervals are not translated onto a point estimate from a different run.
What readers see:
- Botanic1 S/M/L/XL: 0.758 / 0.764 / 0.768 / 0.770. PlantCAD2-L: 0.752.
- Margins over PlantCAD2-L: S +0.006 (not significant under either test), M +0.012 (significant over examples only), L +0.016 and XL +0.018 (significant under both).
- Evo 2 after the report's protocol corrections: 1B 0.709, 7B 0.716, 20B 0.698; Evo2-1b moves up five places.
- Family leaders: Botanic1-XL leads LLR, conservation and TTS; Botanic1-L leads genomic region and TIS; Botanic1-M leads splicing; PlantCAD2-L leads chromatin and PRO-seq; Evo2-40b leads causal variant discovery (0.756).
`scripts/sync_leaderboard.py` (replaces `add_bootstrap_ci.py`) regenerates `data/leaderboard.json` from `figures/data/botanic1_scorecard_28.csv` and `fig3_rerun/rerun_ci.json` in the report repo. All S_bal values match `botanic1_sbal_vs_baselines.csv` to 4 decimals, and the significance table matches `sections/tables/sbal_uncertainty.tex`. Prose in `leaderboard.html` and `index.html` follows `sections/results.tex`.
## Test plan
- [ ] Visually check the leaderboard page (bars, whiskers, heatmap tooltips, scatter) on the Space preview.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- README.md +4 -2
- data/leaderboard.json +0 -0
- index.html +1 -1
- leaderboard.html +19 -16
- scripts/add_bootstrap_ci.py +0 -108
- scripts/sync_leaderboard.py +140 -0
|
@@ -105,8 +105,10 @@ and the headline recalls) so a mismatch with the manuscript is visible at build
|
|
| 105 |
|
| 106 |
`data/leaderboard.json` also carries 95% confidence intervals (`s_bal_ci`, `families_ci`,
|
| 107 |
`metrics[].ci`, `vs_plantcad2l`) from the report's per-sample paired bootstrap (20,000
|
| 108 |
-
replicates, `figures/data/fig3_rerun/rerun_ci.json` in the report repository)
|
| 109 |
-
`scripts/
|
|
|
|
|
|
|
| 110 |
|
| 111 |
`data/abi5-locus.json` is a separate extract of the original Botanic1-XL 4,096 bp
|
| 112 |
scoring output for ABI5: 5,948 SNPs in a 100 kbp locus. Scores are the absolute
|
|
|
|
| 105 |
|
| 106 |
`data/leaderboard.json` also carries 95% confidence intervals (`s_bal_ci`, `families_ci`,
|
| 107 |
`metrics[].ci`, `vs_plantcad2l`) from the report's per-sample paired bootstrap (20,000
|
| 108 |
+
replicates, `figures/data/fig3_rerun/rerun_ci.json` in the report repository).
|
| 109 |
+
`scripts/sync_leaderboard.py <report checkout>` rebuilds the scores from
|
| 110 |
+
`figures/data/botanic1_scorecard_28.csv` and attaches the intervals as the report does:
|
| 111 |
+
scores and intervals come from the same evaluation, and intervals are not translated.
|
| 112 |
|
| 113 |
`data/abi5-locus.json` is a separate extract of the original Botanic1-XL 4,096 bp
|
| 114 |
scoring output for ABI5: 5,948 SNPs in a 100 kbp locus. Scores are the absolute
|
|
The diff for this file is too large to render.
See raw diff
|
|
|
|
@@ -49,7 +49,7 @@
|
|
| 49 |
<article class="fact">
|
| 50 |
<p class="fact-label">Model size</p>
|
| 51 |
<h2>318M to 3.2B parameters</h2>
|
| 52 |
-
<p>Botanic1-S scores 0.758 on the aggregate benchmark across nine task families. PlantCAD2-L scores 0.
|
| 53 |
<figure class="overview-plot" id="overview-benchmark" aria-label="Model benchmark comparison" hidden></figure>
|
| 54 |
<p class="fact-note">Frozen models · aggregate test score (S<sub>bal</sub><sup>test</sup>) · whiskers are 95% CIs</p>
|
| 55 |
<a class="text-link" href="leaderboard.html">Scores by task and species <span aria-hidden="true">↗</span></a>
|
|
|
|
| 49 |
<article class="fact">
|
| 50 |
<p class="fact-label">Model size</p>
|
| 51 |
<h2>318M to 3.2B parameters</h2>
|
| 52 |
+
<p>Botanic1-S scores 0.758 on the aggregate benchmark across nine task families. PlantCAD2-L scores 0.752, Carbon-8B 0.723, and Evo 2-7B 0.716.</p>
|
| 53 |
<figure class="overview-plot" id="overview-benchmark" aria-label="Model benchmark comparison" hidden></figure>
|
| 54 |
<p class="fact-note">Frozen models · aggregate test score (S<sub>bal</sub><sup>test</sup>) · whiskers are 95% CIs</p>
|
| 55 |
<a class="text-link" href="leaderboard.html">Scores by task and species <span aria-hidden="true">↗</span></a>
|
|
@@ -25,7 +25,7 @@
|
|
| 25 |
<p>where <span class="math">𝒮<sub>f</sub></span> is the set of species evaluated for family <span class="math">f</span> and <span class="math">m<sub>f,s</sub></span> the corresponding metric. Seven are supervised probes trained on frozen representations; the remaining two are zero-shot and score variants directly from the model's likelihood. Every leaderboard value and model comparison in the article uses <span class="math">S<sub>bal</sub><sup>test</sup></span>, which scores splicing on centre-token embeddings with the prevalence-corrected stratified AUPRC and, for auto-regressive models, performs two forward passes, one on the normal strand and one on its reverse complement.</p>
|
| 26 |
|
| 27 |
<h2 id="ranking">Model ranking</h2>
|
| 28 |
-
<p>All four Botanic1 models rank above the strongest competitor, PlantCAD2-L (aggregate score <span class="math">S<sub>bal</sub><sup>test</sup></span> = 0.
|
| 29 |
|
| 30 |
<div class="controls" id="controls">
|
| 31 |
<div class="group"><span class="label">Show</span>
|
|
@@ -43,10 +43,10 @@
|
|
| 43 |
</div>
|
| 44 |
|
| 45 |
<div class="chart" id="bars"></div>
|
| 46 |
-
<p class="chartnote">Hover a bar for the nine family means. Whiskers are 95% confidence intervals from the per-sample paired bootstrap of the technical report (20,000 replicates),
|
| 47 |
|
| 48 |
<h2 id="families">The nine task families</h2>
|
| 49 |
-
<p>Zooming in per task shows that Botanic1 leads overall without dominating every capability: the flagship Botanic1-XL tops the benchmark and
|
| 50 |
|
| 51 |
<div class="controls">
|
| 52 |
<div class="group"><span class="label">Columns</span>
|
|
@@ -60,7 +60,7 @@
|
|
| 60 |
<div class="tablewrap"><table class="heat" id="heat"></table></div>
|
| 61 |
|
| 62 |
<h2 id="efficiency">Size and training tokens</h2>
|
| 63 |
-
<p>Plotting the aggregated score against model size highlights the efficiency of this frontier: the Botanic family leads at every scale, and even Botanic1-S (318M parameters) outperforms baselines with more than an order of magnitude larger, including Carbon-8B (8.3B, 0.
|
| 64 |
<div class="two">
|
| 65 |
<div class="chart scatter" id="scatter-params"></div>
|
| 66 |
<div class="chart scatter" id="scatter-tokens"></div>
|
|
@@ -68,23 +68,26 @@
|
|
| 68 |
<p class="chartnote">Vertical bars are 95% confidence intervals from the per-sample bootstrap. Tokens are converted to base pairs using approximations for 6-mer models. Models without a documented token count are omitted from the right panel.</p>
|
| 69 |
|
| 70 |
<h2 id="uncertainty">Are leaderboard differences significant?</h2>
|
| 71 |
-
<p>We assess leaderboard differences with two paired bootstrap tests, each using 20,000 replicates: one resampling the nine task families, and one resampling test examples within each benchmark cell. Together, they test whether observed margins are robust to variation across task families and to sampling noise in the benchmark. The technical report (v2) reports both tests: the table below reproduces
|
| 72 |
-
<p>The key result is that Botanic1-S performs on par with the much larger PlantCAD2-L: its score is slightly higher (+0.
|
| 73 |
<p>Overall, the results show that Botanic1 is already highly competitive at small scale, with larger variants delivering statistically supported gains over strong baselines.</p>
|
| 74 |
<div class="tablewrap">
|
| 75 |
<table>
|
| 76 |
-
<thead><tr><th>Model</th><th>Reference</th><th class="n">Δ <span class="math">S<sub>bal</sub></span></th><th class="n">95% CI, families</th><th class="n">95% CI, examples</th></tr></thead>
|
| 77 |
<tbody>
|
| 78 |
-
<tr><td>Botanic1-S</td><td>PlantCAD2-L</td><td class="n">+0.
|
| 79 |
-
<tr><td>Botanic1-M</td><td>PlantCAD2-L</td><td class="n">+0.
|
| 80 |
-
<tr><td>Botanic1-L</td><td>PlantCAD2-L</td><td class="n">+0.
|
| 81 |
-
<tr><td>Botanic1-XL</td><td>PlantCAD2-L</td><td class="n">+0.
|
| 82 |
-
<tr><td>Botanic1-XL</td><td>PlantCaduceus-l32</td><td class="n">+0.
|
| 83 |
-
<tr><td>Botanic1-
|
| 84 |
-
<tr><td>Botanic1-
|
| 85 |
-
<tr><td>Botanic1-
|
|
|
|
|
|
|
|
|
|
| 86 |
</tbody>
|
| 87 |
-
<caption>Paired differences in reported <span class="math">S<sub>bal</sub></span><sup>test</sup> between the model and the reference. Both intervals are 95% percentile intervals over 20,000 paired replicates: "families" resamples the nine task families, "examples" resamples the test examples within each of the 22 tasks and species, and is the test that speaks to sampling noise in the benchmark. An asterisk marks an interval that excludes zero.</caption>
|
| 88 |
</table>
|
| 89 |
</div>
|
| 90 |
|
|
|
|
| 25 |
<p>where <span class="math">𝒮<sub>f</sub></span> is the set of species evaluated for family <span class="math">f</span> and <span class="math">m<sub>f,s</sub></span> the corresponding metric. Seven are supervised probes trained on frozen representations; the remaining two are zero-shot and score variants directly from the model's likelihood. Every leaderboard value and model comparison in the article uses <span class="math">S<sub>bal</sub><sup>test</sup></span>, which scores splicing on centre-token embeddings with the prevalence-corrected stratified AUPRC and, for auto-regressive models, performs two forward passes, one on the normal strand and one on its reverse complement.</p>
|
| 26 |
|
| 27 |
<h2 id="ranking">Model ranking</h2>
|
| 28 |
+
<p>All four Botanic1 models rank above the strongest competitor, PlantCAD2-L (aggregate score <span class="math">S<sub>bal</sub><sup>test</sup></span> = 0.752), and their performance scales monotonically with model size, from Botanic1-S (0.758) through Botanic1-M (0.764), Botanic1-L (0.768) to Botanic1-XL (0.770). The aggregate-score axis starts at 0.5, so bar length is not proportional to the score. Task views use the full score range.</p>
|
| 29 |
|
| 30 |
<div class="controls" id="controls">
|
| 31 |
<div class="group"><span class="label">Show</span>
|
|
|
|
| 43 |
</div>
|
| 44 |
|
| 45 |
<div class="chart" id="bars"></div>
|
| 46 |
+
<p class="chartnote">Hover a bar for the nine family means. Whiskers are 95% confidence intervals from the per-sample paired bootstrap of the technical report (20,000 replicates), computed on the same evaluation as the scores. Botanic0-L is the previous generation, shown in olive. Parameter counts are trainable parameters as declared by each model's configuration and code; PlantBiMoE has 116M trainable parameters and 64M active per token.</p>
|
| 47 |
|
| 48 |
<h2 id="families">The nine task families</h2>
|
| 49 |
+
<p>Zooming in per task shows that Botanic1 leads overall without dominating every capability: the flagship Botanic1-XL tops the benchmark and three task families, LLR mutation effect (AUROC 0.721), conservation and translation termination; Botanic1-L leads two more, genomic-region classification (0.527 versus 0.521 for PlantCAD2-L) and translation initiation; and Botanic1-M leads splicing (donor and acceptor mean 0.984 versus 0.978 for NTv3-650M-post, which uses test annotations during post-training). The autoregressive Evo 2 is close behind on variant-effect prediction, where its 7B model reaches an LLR AUROC of 0.710 and its 40B model 0.719 against 0.721 for Botanic1-XL, and its 40B model leads causal-variant discovery (recall AUC 0.756 against 0.753); PlantCAD2-L leads on chromatin accessibility (0.481 versus 0.480 for Botanic1-L) and PRO-seq (0.870 versus 0.869). Botanic1-XL is the only model that stays competitive across all nine families.</p>
|
| 50 |
|
| 51 |
<div class="controls">
|
| 52 |
<div class="group"><span class="label">Columns</span>
|
|
|
|
| 60 |
<div class="tablewrap"><table class="heat" id="heat"></table></div>
|
| 61 |
|
| 62 |
<h2 id="efficiency">Size and training tokens</h2>
|
| 63 |
+
<p>Plotting the aggregated score against model size highlights the efficiency of this frontier: the Botanic family leads at every scale, and even Botanic1-S (318M parameters) outperforms baselines with more than an order of magnitude larger, including Carbon-8B (8.3B, 0.723) and Evo 2 (7B, 0.716), both margins significant under both bootstrap tests; the most parameter-efficient baseline, GPN (66M, 0.728), still trails on performance. The right panel replots the same models against the total number of genomic base pairs seen during training: the tokens seen, reconstructed for every model from its publication or model card. The Botanic scores are attained after only 314.6B base pairs, where the strongest competitor, PlantCAD2-L, had to process 4.03T base-pairs and NTv3 12.1T. Interestingly, the Evo 2 family does not scale well with capacity: it peaks at 7B (0.716, from 0.709 at 1B) and declines through 20B (0.698) to 40B (0.623).</p>
|
| 64 |
<div class="two">
|
| 65 |
<div class="chart scatter" id="scatter-params"></div>
|
| 66 |
<div class="chart scatter" id="scatter-tokens"></div>
|
|
|
|
| 68 |
<p class="chartnote">Vertical bars are 95% confidence intervals from the per-sample bootstrap. Tokens are converted to base pairs using approximations for 6-mer models. Models without a documented token count are omitted from the right panel.</p>
|
| 69 |
|
| 70 |
<h2 id="uncertainty">Are leaderboard differences significant?</h2>
|
| 71 |
+
<p>We assess leaderboard differences with two paired bootstrap tests, each using 20,000 replicates: one resampling the nine task families, and one resampling test examples within each benchmark cell. Together, they test whether observed margins are robust to variation across task families and to sampling noise in the benchmark. The technical report (v2) reports both tests: the table below reproduces the report's S<sub>bal</sub> uncertainty table, and the per-sample intervals are drawn as error bars and as paired differences to PlantCAD2-L in the report's leaderboard figure.</p>
|
| 72 |
+
<p>The key result is that Botanic1-S performs on par with the much larger PlantCAD2-L: its score is slightly higher (+0.006), but the confidence intervals include zero, so we cannot resolve a significant difference between the two. In contrast, Botanic1-L and Botanic1-XL significantly outperform PlantCAD2-L under both tests. Botanic1-M (+0.012) is significant under the per-sample test but not under the family-level test, and shows a significant gain over Botanic1-S (+0.006) under both. Botanic1-S also significantly outperforms Carbon-8B and Evo 2-7B under both tests.</p>
|
| 73 |
<p>Overall, the results show that Botanic1 is already highly competitive at small scale, with larger variants delivering statistically supported gains over strong baselines.</p>
|
| 74 |
<div class="tablewrap">
|
| 75 |
<table>
|
| 76 |
+
<thead><tr><th>Model</th><th>Reference</th><th class="n">Δ <span class="math">S<sub>bal</sub></span></th><th class="n">95% CI, families</th><th class="n">95% CI, examples</th><th class="n">Families ahead</th></tr></thead>
|
| 77 |
<tbody>
|
| 78 |
+
<tr><td>Botanic1-S</td><td>PlantCAD2-L</td><td class="n">+0.0058</td><td class="n">[−0.0063, +0.0237]</td><td class="n">[−0.0029, +0.0159]</td><td class="n">4/9</td></tr>
|
| 79 |
+
<tr><td>Botanic1-M</td><td>PlantCAD2-L</td><td class="n">+0.0118</td><td class="n">[−0.0018, +0.0295]</td><td class="n">[+0.0039, +0.0217]<sup>*</sup></td><td class="n">5/9</td></tr>
|
| 80 |
+
<tr><td>Botanic1-L</td><td>PlantCAD2-L</td><td class="n">+0.0155</td><td class="n">[+0.0016, +0.0336]<sup>*</sup></td><td class="n">[+0.0078, +0.0252]<sup>*</sup></td><td class="n">6/9</td></tr>
|
| 81 |
+
<tr><td>Botanic1-XL</td><td>PlantCAD2-L</td><td class="n">+0.0175</td><td class="n">[+0.0026, +0.0364]<sup>*</sup></td><td class="n">[+0.0094, +0.0273]<sup>*</sup></td><td class="n">7/9</td></tr>
|
| 82 |
+
<tr><td>Botanic1-XL</td><td>PlantCaduceus-l32</td><td class="n">+0.0187</td><td class="n">[+0.0110, +0.0270]<sup>*</sup></td><td class="n">[+0.0123, +0.0253]<sup>*</sup></td><td class="n">9/9</td></tr>
|
| 83 |
+
<tr><td>Botanic1-XL</td><td>Botanic1-S</td><td class="n">+0.0117</td><td class="n">[+0.0042, +0.0226]<sup>*</sup></td><td class="n">[+0.0067, +0.0171]<sup>*</sup></td><td class="n">8/9</td></tr>
|
| 84 |
+
<tr><td>Botanic1-M</td><td>Botanic1-S</td><td class="n">+0.0060</td><td class="n">[+0.0002, +0.0141]<sup>*</sup></td><td class="n">[+0.0005, +0.0117]<sup>*</sup></td><td class="n">7/9</td></tr>
|
| 85 |
+
<tr><td>Botanic1-L</td><td>Botanic1-M</td><td class="n">+0.0037</td><td class="n">[−0.0007, +0.0076]</td><td class="n">[−0.0008, +0.0081]</td><td class="n">7/9</td></tr>
|
| 86 |
+
<tr><td>Botanic1-XL</td><td>Botanic1-L</td><td class="n">+0.0020</td><td class="n">[−0.0027, +0.0076]</td><td class="n">[−0.0040, +0.0081]</td><td class="n">4/9</td></tr>
|
| 87 |
+
<tr><td>Botanic1-S</td><td>Carbon-8B</td><td class="n">+0.0353</td><td class="n">[+0.0149, +0.0578]<sup>*</sup></td><td class="n">[+0.0249, +0.0460]<sup>*</sup></td><td class="n">8/9</td></tr>
|
| 88 |
+
<tr><td>Botanic1-S</td><td>Evo 2-7B</td><td class="n">+0.0424</td><td class="n">[+0.0187, +0.0677]<sup>*</sup></td><td class="n">[+0.0292, +0.0564]<sup>*</sup></td><td class="n">8/9</td></tr>
|
| 89 |
</tbody>
|
| 90 |
+
<caption>Paired differences in reported <span class="math">S<sub>bal</sub></span><sup>test</sup> between the model and the reference. Both intervals are 95% percentile intervals over 20,000 paired replicates: "families" resamples the nine task families, "examples" resamples the test examples within each of the 22 tasks and species, and is the test that speaks to sampling noise in the benchmark. An asterisk marks an interval that excludes zero. "Families ahead" counts the task families in which the model scores above the reference.</caption>
|
| 91 |
</table>
|
| 92 |
</div>
|
| 93 |
|
|
@@ -1,108 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Add per-sample bootstrap confidence intervals to data/leaderboard.json.
|
| 3 |
-
|
| 4 |
-
Source: figures/data/fig3_rerun/rerun_ci.json in the technical report repository
|
| 5 |
-
(dotomics/BOTANIC1-technical-report, branch overleaf, commit 6e420d15), the
|
| 6 |
-
per-example paired bootstrap of the leaderboard cohort (20,000 replicates, draws
|
| 7 |
-
shared across models within each cell, 95% percentile intervals). The same file
|
| 8 |
-
feeds every interval in the v2 report (sec. S_bal uncertainty).
|
| 9 |
-
|
| 10 |
-
Convention (figures/scripts/fig3_rerun_ci.py in that repository): the point
|
| 11 |
-
estimates stay the reported scores; each interval is the rerun's percentile
|
| 12 |
-
interval translated so that it sits about the reported value, i.e. the offsets
|
| 13 |
-
(lo - mean, hi - mean) are added to the reported point.
|
| 14 |
-
|
| 15 |
-
Fields added per model: s_bal_ci [lo, hi]; families_ci {family: [lo, hi]};
|
| 16 |
-
metrics[i].ci [lo, hi]; vs_plantcad2l {delta, lo, hi, significant} (paired
|
| 17 |
-
example-level delta to PlantCAD2-L, centred on the reported difference).
|
| 18 |
-
|
| 19 |
-
Run: python3 scripts/add_bootstrap_ci.py path/to/rerun_ci.json
|
| 20 |
-
"""
|
| 21 |
-
import json
|
| 22 |
-
import sys
|
| 23 |
-
from pathlib import Path
|
| 24 |
-
|
| 25 |
-
ROOT = Path(__file__).resolve().parents[1]
|
| 26 |
-
LB = ROOT / "data" / "leaderboard.json"
|
| 27 |
-
|
| 28 |
-
FAMILY = {"chromatin": "chromatin_access", "grc": "genomic_region_classification_v2",
|
| 29 |
-
"conservation": "plantcad_conservation", "splicing": "splicing", "tis": "plantcad_tis",
|
| 30 |
-
"tts": "plantcad_tts", "proseq": "pro_seq", "llr": "llr", "causal": "gwas"}
|
| 31 |
-
D = "downstream_tasks."
|
| 32 |
-
CELL = {
|
| 33 |
-
"GWAS · recall_auc": "gwas_eval_benchmark/recall_auc",
|
| 34 |
-
"Chromatin · arabidopsis": D + "chromatin_access/chromatin_access.arabidopis_thaliana/eval_aucpr_macro",
|
| 35 |
-
"Chromatin · brachypodium": D + "chromatin_access/chromatin_access.brachypodium_distachyon/eval_aucpr_macro",
|
| 36 |
-
"Chromatin · maize": D + "chromatin_access/chromatin_access.zea_mays/eval_aucpr_macro",
|
| 37 |
-
"Chromatin · rice MH63": D + "chromatin_access/chromatin_access.oryza_sativa_MH63_RS2/eval_aucpr_macro",
|
| 38 |
-
"Chromatin · rice ZS97": D + "chromatin_access/chromatin_access.oryza_sativa_ZS97_RS2/eval_aucpr_macro",
|
| 39 |
-
"Chromatin · setaria": D + "chromatin_access/chromatin_access.setaria_italica/eval_aucpr_macro",
|
| 40 |
-
"Chromatin · sorghum": D + "chromatin_access/chromatin_access.sorghum_bicolor/eval_aucpr_macro",
|
| 41 |
-
"Conservation · maize": D + "plantcad_conservation/plantcad_conservation.maize/eval_aucpr",
|
| 42 |
-
"Conservation · sorghum": D + "plantcad_conservation/plantcad_conservation.sorghum/eval_aucpr",
|
| 43 |
-
"GRC2 · arabidopsis": D + "genomic_region_classification_v2/genomic_region_classification_v2.arabidopsis_thaliana/eval_balanced_accuracy",
|
| 44 |
-
"GRC2 · maize": D + "genomic_region_classification_v2/genomic_region_classification_v2.zea_mays/eval_balanced_accuracy",
|
| 45 |
-
"GRC2 · rice": D + "genomic_region_classification_v2/genomic_region_classification_v2.oryza_sativa/eval_balanced_accuracy",
|
| 46 |
-
"GRC2 · tomato": D + "genomic_region_classification_v2/genomic_region_classification_v2.solanum_lycopersicum/eval_balanced_accuracy",
|
| 47 |
-
"LLR · arabidopsis": "llr_eval/arabidopsis_thaliana/auroc",
|
| 48 |
-
"LLR · maize": "llr_eval/zea_mays/auroc",
|
| 49 |
-
"LLR · tomato": "llr_eval/solanum_lycopersicum/auroc",
|
| 50 |
-
"PRO-seq · cassava": D + "pro_seq/pro_seq.m_esculenta/eval_aucpr",
|
| 51 |
-
"Splice acceptor · arabidopsis": D + "splicing/splicing.arabidopsis_thaliana_acceptor/eval_stratified_aucpr_corrected",
|
| 52 |
-
"Splice donor · arabidopsis": D + "splicing/splicing.arabidopsis_thaliana_donor/eval_stratified_aucpr_corrected",
|
| 53 |
-
"TIS · arabidopsis": D + "plantcad_tis/plantcad_tis.arabidopsis/eval_aucpr",
|
| 54 |
-
"TTS · arabidopsis": D + "plantcad_tts/plantcad_tts.arabidopsis/eval_aucpr",
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
def norm(model_id: str) -> str:
|
| 59 |
-
if model_id.startswith("botanic1-"):
|
| 60 |
-
return "Botanic1-" + model_id.split("-", 1)[1]
|
| 61 |
-
if model_id.startswith("CARBON-"):
|
| 62 |
-
return "Carbon-" + model_id.split("-", 1)[1]
|
| 63 |
-
return model_id
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
def about(entry, published, key="mean"):
|
| 67 |
-
shift = published - entry[key]
|
| 68 |
-
return [round(entry["lo"] + shift, 4), round(entry["hi"] + shift, 4)]
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
def main(rerun_path: str) -> None:
|
| 72 |
-
rerun = json.loads(Path(rerun_path).read_text())
|
| 73 |
-
lb = json.loads(LB.read_text())
|
| 74 |
-
ref = "PlantCAD2-L"
|
| 75 |
-
ref_sbal = next(m["s_bal"] for m in lb["models"] if m["id"] == ref)
|
| 76 |
-
n_ci = 0
|
| 77 |
-
for m in lb["models"]:
|
| 78 |
-
r = rerun["models"].get(norm(m["id"]))
|
| 79 |
-
if r is None:
|
| 80 |
-
print(f" no rerun entry for {m['id']}")
|
| 81 |
-
continue
|
| 82 |
-
m["s_bal_ci"] = about(r["sbal"], m["s_bal"])
|
| 83 |
-
m["families_ci"] = {k: about(r["families"][FAMILY[k]], v) for k, v in m["families"].items() if FAMILY[k] in r["families"]}
|
| 84 |
-
for mt in m["metrics"]:
|
| 85 |
-
c = r["cells"].get(CELL[mt["label"]])
|
| 86 |
-
if c is not None:
|
| 87 |
-
mt["ci"] = about(c, mt["value"])
|
| 88 |
-
if m["id"] != ref:
|
| 89 |
-
p = rerun["pairs"].get(f"{norm(m['id'])} vs {ref}")
|
| 90 |
-
if p is not None:
|
| 91 |
-
d = round(m["s_bal"] - ref_sbal, 4)
|
| 92 |
-
lo, hi = about(p, d, key="delta")
|
| 93 |
-
m["vs_plantcad2l"] = {"delta": d, "lo": lo, "hi": hi, "significant": bool(lo > 0 or hi < 0)}
|
| 94 |
-
n_ci += 1
|
| 95 |
-
# sanity: rerun point within 0.02 of the reported score
|
| 96 |
-
gap = abs(r["sbal"]["mean"] - m["s_bal"])
|
| 97 |
-
flag = " <-- check" if gap > 0.02 else ""
|
| 98 |
-
print(f"{m['id']:14s} s_bal={m['s_bal']:.4f} rerun={r['sbal']['mean']:.4f} ci={m['s_bal_ci']}{flag}")
|
| 99 |
-
lb["ci_note"] = ("95% confidence intervals from the per-sample paired bootstrap of the technical report "
|
| 100 |
-
"(20,000 replicates, draws shared across models within each cell), centred on the reported score.")
|
| 101 |
-
lb["ci_meta"] = {"replicates": rerun["meta"]["replicates"], "level": 95,
|
| 102 |
-
"source": "BOTANIC1-technical-report figures/data/fig3_rerun/rerun_ci.json @ 6e420d15"}
|
| 103 |
-
LB.write_text(json.dumps(lb, indent=1, ensure_ascii=False) + "\n")
|
| 104 |
-
print(f"wrote {LB} with intervals for {n_ci}/{len(lb['models'])} models")
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
if __name__ == "__main__":
|
| 108 |
-
main(sys.argv[1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Sync data/leaderboard.json with the technical report's S_bal^test data.
|
| 3 |
+
|
| 4 |
+
Sources, both in the technical report repository (dotomics/BOTANIC1-technical-report):
|
| 5 |
+
|
| 6 |
+
figures/data/botanic1_scorecard_28.csv the 22 skill cells per model (skill == 1)
|
| 7 |
+
figures/data/fig3_rerun/rerun_ci.json per-example paired bootstrap of the same
|
| 8 |
+
evaluation (20,000 replicates, draws shared
|
| 9 |
+
across models within each cell)
|
| 10 |
+
|
| 11 |
+
Convention (figures/scripts/fig3_rerun_ci.py, CENTRE = "rerun"; and
|
| 12 |
+
figures/scripts/rebuild_sbal_aggregates.py in that repository):
|
| 13 |
+
|
| 14 |
+
- point estimates are the scorecard cells; a family score is the mean of its cells
|
| 15 |
+
grouped by the scorecard's `task` column, and S_bal is the unweighted mean of the
|
| 16 |
+
nine family scores;
|
| 17 |
+
- intervals are the rerun's own 95% percentile intervals, not translated onto the
|
| 18 |
+
point estimate;
|
| 19 |
+
- vs_plantcad2l.delta is the scorecard S_bal difference, and its interval is the
|
| 20 |
+
rerun's paired example-level interval (as in sections/tables/sbal_uncertainty.tex).
|
| 21 |
+
|
| 22 |
+
Run: python3 scripts/sync_leaderboard.py path/to/BOTANIC1-technical-report
|
| 23 |
+
"""
|
| 24 |
+
import collections
|
| 25 |
+
import csv
|
| 26 |
+
import json
|
| 27 |
+
import subprocess
|
| 28 |
+
import sys
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
from statistics import fmean
|
| 31 |
+
|
| 32 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 33 |
+
LB = ROOT / "data" / "leaderboard.json"
|
| 34 |
+
|
| 35 |
+
# leaderboard family key -> scorecard `task` column / rerun family key
|
| 36 |
+
TASK = {"chromatin": "chromatin_access", "grc": "genomic_region_classification_v2",
|
| 37 |
+
"conservation": "plantcad_conservation", "splicing": "splicing", "tis": "plantcad_tis",
|
| 38 |
+
"tts": "plantcad_tts", "proseq": "pro_seq", "llr": "llr", "causal": "gwas"}
|
| 39 |
+
TASK_ALIASES = {"gwas": {"gwas", "gwas_eval_benchmark"}, "llr": {"llr", "llr_eval"}}
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def norm(model_id: str) -> str:
|
| 43 |
+
"""Scorecard / leaderboard spelling -> rerun_ci.json name."""
|
| 44 |
+
if model_id.startswith("botanic1-"):
|
| 45 |
+
return "Botanic1-" + model_id.split("-", 1)[1]
|
| 46 |
+
if model_id.startswith("CARBON-"):
|
| 47 |
+
return "Carbon-" + model_id.split("-", 1)[1]
|
| 48 |
+
return model_id
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def r4(x: float) -> float:
|
| 52 |
+
return round(x, 4)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def interval(entry: dict) -> list[float]:
|
| 56 |
+
return [r4(entry["lo"]), r4(entry["hi"])]
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def fam_key(task: str) -> str:
|
| 60 |
+
for k, t in TASK.items():
|
| 61 |
+
if task == t or task in TASK_ALIASES.get(t, ()):
|
| 62 |
+
return k
|
| 63 |
+
raise SystemExit(f"unmapped scorecard task {task!r}")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def main(report: str) -> None:
|
| 67 |
+
rep = Path(report)
|
| 68 |
+
data = rep / "figures" / "data"
|
| 69 |
+
commit = subprocess.run(["git", "-C", str(rep), "rev-parse", "--short=8", "HEAD"],
|
| 70 |
+
capture_output=True, text=True).stdout.strip() or "unknown"
|
| 71 |
+
|
| 72 |
+
cells: dict[str, dict[str, tuple[str, str, float]]] = collections.defaultdict(dict)
|
| 73 |
+
with open(data / "botanic1_scorecard_28.csv") as f:
|
| 74 |
+
for r in csv.DictReader(f):
|
| 75 |
+
if int(r["skill"]):
|
| 76 |
+
cells[r["model"]][r["label"]] = (fam_key(r["task"]), r["metric_key"], float(r["value"]))
|
| 77 |
+
rerun = json.loads((data / "fig3_rerun" / "rerun_ci.json").read_text())
|
| 78 |
+
lb = json.loads(LB.read_text())
|
| 79 |
+
|
| 80 |
+
ref = "PlantCAD2-L"
|
| 81 |
+
sbal: dict[str, float] = {}
|
| 82 |
+
for m in lb["models"]:
|
| 83 |
+
c = cells.get(m["id"])
|
| 84 |
+
if c is None or len(c) != 22:
|
| 85 |
+
raise SystemExit(f"{m['id']}: expected 22 skill cells, found {0 if c is None else len(c)}")
|
| 86 |
+
fam: dict[str, list[float]] = collections.defaultdict(list)
|
| 87 |
+
for k, _, v in c.values():
|
| 88 |
+
fam[k].append(v)
|
| 89 |
+
if set(fam) != set(TASK):
|
| 90 |
+
raise SystemExit(f"{m['id']}: families {sorted(fam)}")
|
| 91 |
+
m["families"] = {k: r4(fmean(fam[k])) for k in m["families"]}
|
| 92 |
+
sbal[m["id"]] = fmean(fmean(v) for v in fam.values())
|
| 93 |
+
m["s_bal"] = r4(sbal[m["id"]])
|
| 94 |
+
for mt in m["metrics"]:
|
| 95 |
+
if mt["label"] not in c:
|
| 96 |
+
raise SystemExit(f"{m['id']}: no scorecard cell {mt['label']!r}")
|
| 97 |
+
mt["value"] = r4(c[mt["label"]][2])
|
| 98 |
+
|
| 99 |
+
for m in lb["models"]:
|
| 100 |
+
r = rerun["models"].get(norm(m["id"]))
|
| 101 |
+
for k in ("s_bal_ci", "families_ci", "vs_plantcad2l"):
|
| 102 |
+
m.pop(k, None)
|
| 103 |
+
for mt in m["metrics"]:
|
| 104 |
+
mt.pop("ci", None)
|
| 105 |
+
if r is None:
|
| 106 |
+
print(f" no rerun entry for {m['id']}")
|
| 107 |
+
continue
|
| 108 |
+
m["s_bal_ci"] = interval(r["sbal"])
|
| 109 |
+
m["families_ci"] = {k: interval(r["families"][TASK[k]])
|
| 110 |
+
for k in m["families"] if TASK[k] in r["families"]}
|
| 111 |
+
c = cells[m["id"]]
|
| 112 |
+
for mt in m["metrics"]:
|
| 113 |
+
e = r["cells"].get(c[mt["label"]][1])
|
| 114 |
+
if e is not None:
|
| 115 |
+
mt["ci"] = interval(e)
|
| 116 |
+
if m["id"] != ref:
|
| 117 |
+
p = rerun["pairs"].get(f"{norm(m['id'])} vs {ref}")
|
| 118 |
+
if p is not None:
|
| 119 |
+
lo, hi = interval(p)
|
| 120 |
+
m["vs_plantcad2l"] = {"delta": r4(sbal[m["id"]] - sbal[ref]), "lo": lo, "hi": hi,
|
| 121 |
+
"significant": bool(lo > 0 or hi < 0)}
|
| 122 |
+
gap = abs(r["sbal"]["mean"] - sbal[m["id"]])
|
| 123 |
+
flag = " <-- check" if gap > 0.005 else ""
|
| 124 |
+
print(f"{m['id']:14s} s_bal={m['s_bal']:.4f} rerun={r['sbal']['mean']:.4f} "
|
| 125 |
+
f"ci={m['s_bal_ci']}{flag}")
|
| 126 |
+
|
| 127 |
+
lb["models"].sort(key=lambda m: -m["s_bal"])
|
| 128 |
+
lb["ci_note"] = ("95% confidence intervals from the per-sample paired bootstrap of the technical "
|
| 129 |
+
"report (20,000 replicates, draws shared across models within each cell). "
|
| 130 |
+
"Scores and intervals come from the same evaluation; intervals are not "
|
| 131 |
+
"translated onto the point estimate.")
|
| 132 |
+
lb["ci_meta"] = {"replicates": rerun["meta"]["replicates"], "level": 95,
|
| 133 |
+
"source": "BOTANIC1-technical-report figures/data/botanic1_scorecard_28.csv + "
|
| 134 |
+
f"figures/data/fig3_rerun/rerun_ci.json @ {commit}"}
|
| 135 |
+
LB.write_text(json.dumps(lb, indent=1, ensure_ascii=False) + "\n")
|
| 136 |
+
print(f"wrote {LB}")
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
if __name__ == "__main__":
|
| 140 |
+
main(sys.argv[1])
|