Skip to content

Methylation gene-body units, poolable SEs, and three first-run fixes (the part of #67 that missed its merge) - #74

Merged
sr320 merged 5 commits into
mainfrom
methylation/genes-and-pooling-se
Oct 17, 2026
Merged

sr320 merged 5 commits into
mainfrom
methylation/genes-and-pooling-se

Conversation

@sr320

@sr320 sr320 commented Oct 17, 2026

Copy link
Copy Markdown
Owner

What and why

#67 merged on 2026-10-16 with only its first commit (the limma/DSS pooled-tile engine). Five later commits were pushed to its branch after the merge, so they're not on main, although #67's description lists them. They're rebased onto main here, unchanged. All five came from running CIBNOR2021_HEAT_RRSS (#14) end to end, and #14 needs them.

  1. Cap the methylation report's region table at 500 rows (8188da3). Embedding all 319,088 tiles in the self-contained HTML exhausted Quarto's 8 GB JavaScript heap (exit 133). The report shows the 500 smallest q-values, and the full table is the standardized TSV.
  2. Capture Bismark's version in deduplication and extraction (ea00d0f). Those tools print "Deduplicator Version" and "Bismark Extractor Version", so versions.yml had an empty bismark field.
  3. Carry a standard error and p-value to evidence (6dd5425). The methylation harmonizer hard-coded standard_error and p_value to None, so no methylation study could enter inverse-variance pooling.
    • DMR_POOLED_TILES now writes pvalue, se_meth_diff_percent and analysis_method.
    • Standardizing passes them through when present, and the harmonizer reads them.
    • methylKit tables are unchanged and stay labelled methylKit.
    • New tests: tests/test_methylation_harmonize.py.
  4. Gene-body units, methylation.dmr_features = 'genes' (f4a7554). Genome-wide tiles give many records per gene, and once harmonized to the gene they collide in meta-analysis. build-evidence-cards then fails for every study. Gene mode pools each gene's body and strand-aware promoter CpGs into one unit, with intergenic CpGs in tiles. ANNOTATE_REGIONS keeps the gene and context those units arrive with. tiles stays the default.
  5. Make limma's SE reproduce limma's p-value under a Wald test (9b14413). Using |diff / t| dropped limma's small-sample df. At 2 vs 2 families, one CIBNOR gene went from limma p = 2.7e-4 to pooled p = 4e-27, and 383 features became "significant", 53 of them promoted to multi_omics_convergence. The SE is now |diff| / qnorm(1 − p/2).

Testing

  • Code matches the run: the DMR, annotation, standardize, harmonizer and main.nf code here is byte-identical to what produced [study] CIBNOR2021_HEAT_RRSS (PRJNA690951): first reanalysis #14's results (git diff 7c8664e HEAD on those files is empty).
  • End to end: a full Pages-sequence rebuild with CIBNOR's evidence runs register → demo → harmonize_committed_results.py → gene meta-analysis → cards → top candidates → dashboard → all-feature meta-analysis:
    • no errors;
    • CIBNOR gives 30,732 emerging candidates, none significant, as in limma;
    • 0 multi-omics candidates;
    • 889 high-priority candidates, unchanged from before CIBNOR's evidence.
  • Synthetic data:
    • gene-body units: a two-gene GTF places + and − strand promoter windows correctly, recovers the true-signal gene (+26.6 points), and gives unique feature IDs after annotation;
    • Wald p from the SE matches limma's p within output rounding.
  • Checks: pytest -q: 644 passed. ruff: clean. The methylation demo (CI's command) passes.

Checklist

Study branches waiting on this

🤖 Generated with Claude Code

sr320 and others added 5 commits October 17, 2026 07:40
With DMR_POOLED_TILES, CIBNOR2021_HEAT_RRSS tests 319,088 tiles. Embedding
every row in the self-contained HTML report exhausted Quarto's 8 GB
JavaScript heap twice (exit 133). Show the 500 regions with the smallest
q-values and point to the standardized TSV, which has them all. The real
CIBNOR table now renders to a 2.2 MB report with ~0.34 GB peak memory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deduplicate_bismark and bismark_methylation_extractor print
"Deduplicator Version" / "Bismark Extractor Version", not "Bismark
Version", so their versions.yml got an empty bismark field and the
CIBNOR2021_HEAT_RRSS manifest warned software_version_unresolved for both.
Match 'Version: vX.Y.Z' instead; both report 0.24.2 here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Methylation evidence always had standard_error and p_value set to None,
so no methylation study could enter inverse-variance pooling, including
CIBNOR2021_HEAT_RRSS (319,088 harmonized tiles). DMR_POOLED_TILES now
writes pvalue, se_meth_diff_percent (for limma, |diff / moderated t|, so
diff / se reproduces limma's statistic) and analysis_method.
STANDARDIZE_OUTPUT passes them through when present, and the harmonizer
reads them. methylKit tables are unchanged and stay labelled methylKit.
Tests: tests/test_methylation_harmonize.py (2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Genome-wide 1 kb tiles give many methylation records per gene; harmonized
to the gene, they collide, and evidence cards then fail for every study
(found rebuilding with CIBNOR2021_HEAT_RRSS: 13 tiles in LOC105317035).
With dmr_features = 'genes', DMR_POOLED_TILES pools every CpG in a gene
body and its strand-aware promoter into one unit per gene, and intergenic
CpGs into tiles. ANNOTATE_REGIONS keeps the gene and context those units
arrive with, so a tile bordering a gene window stays gene-less. 'tiles'
remains the default.

Checked on synthetic data with a two-gene GTF: + and - strand promoter
windows placed correctly, the true-signal gene recovered (+26.6 points),
and all feature ids unique after annotation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
se_meth_diff_percent was |diff / moderated t|. Meta-analysis tests diff/se
against the normal distribution, which drops limma's small-sample df: at 2
vs 2 families (df ~4), CIBNOR2021_HEAT_RRSS gene 109618586 had limma
p = 2.7e-4 (q = 0.57) but pooled p = 4e-27, and 383 of its 30,732
methylation features became 'significant' in meta-analysis, with 53
promoted to multi_omics_convergence. Use |diff| / qnorm(1 - p/2), so a
Wald test reproduces limma's p-value. On synthetic data the two agree to
within output rounding (max |log10 difference| 0.0015). DSS's diff.se
already comes from a Wald test and is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sr320
sr320 merged commit 5fa602f into main Oct 17, 2026
6 checks passed
@sr320
sr320 deleted the methylation/genes-and-pooling-se branch October 17, 2026 14:48
sr320 added a commit that referenced this pull request Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant