Skip to content

Test pooled family tiles with limma (or DSS), not per-region methylKit MN - #67

Merged
sr320 merged 1 commit into
mainfrom
methylation/family-replicate-unit
Oct 16, 2026
Merged

sr320 merged 1 commit into
mainfrom
methylation/family-replicate-unit

Conversation

@sr320

@sr320 sr320 commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

What and why

Follow-up to #15, merged on 2026-10-12. The first complete real methylation run, CIBNOR2021_HEAT_RRSS (12 WGBS libraries in 2 heat-resistant and 2 heat-susceptible families; draft #14), showed two problems with the family-level methylKit test from #15:

  1. The q-values were anti-conservative. 397 tiles had q < 0.05, but 296 of them differed by 5 points or less, and q reached 1.7e-127. With 2 families per arm, methylKit's per-region McCullagh-Nelder dispersion falls below 1 wherever the families happen to agree. methylKit resets it to 1, and the test becomes a binomial test on summed reads.
  2. Most of the data was discarded. Coverage was required per library and per strand (10× in all 12 libraries) before pooling, so only 23,350 1-kb tiles, about 4% of the genome, were testable. Each library has a mean of about 5× per strand.

DMR_POOLED_TILES (methylation.dmr_engine = 'limma' | 'dss'):

  • merges the two strands of each CpG;
  • pools by replicate_unit, and keeps CpGs with at least unit_min_coverage reads in every unit;
  • sums them into tile_size tiles with at least tile_min_cpgs CpGs;
  • tests with either:
    • limma: a moderated t on unit M-values, with empirical-Bayes variance and small-sample df; or
    • dss: DSS::DMLtest, a Wald test.

methylkit stays the default engine. The DMR output columns are unchanged, and the QC table records the engine, test and df_prior.

Testing

Synthetic Bismark CpG reports, both strands at 5–19× each: 12 libraries in 4 families × 3, real family-to-family variation (SD 12 points), 20 true tiles (+30 points) among 400.

Test (4 families) True found False positives (of 380)
methylKit, MN + F (#15) 0 / 20 0
DSS DMLtest 17 / 20 57
limma moderated t 4 / 20 1
  • limma is calibrated: 5.0% of null tiles have p < 0.05. DSS's Wald test has no small-sample correction, so it's overconfident at 2 vs 2. That's why limma is the recommended engine, documented in workflows/methylation/README.md.
  • The module reproduces the prototype exactly when extracted and run with DSS 2.58.0, bsseq 1.46.0, limma (R 4.5.1) and data.table.
  • Versions reach the manifest: DMR_POOLED_TILES writes versions.yml (limma, DSS, bsseq, data.table), and it's wired to EMIT_MANIFEST. A ternary reads only the DMR process that ran, which satisfies tests/test_workflow_versions.py.
  • Checks: pytest -q: 594 passed. The methylation demo (CI's command) passes.
  • Real data: CIBNOR is re-running its DMR step with dmr_engine = 'limma' now. The 12 alignments, deduplications and extractions are reused from cache, because those modules are identical on main.

Later commits, all found by running CIBNOR end to end

  • Report table capped at 500 rows (9c74a9f). Embedding all 319,088 tiles exhausted Quarto's 8 GB JavaScript heap (exit 133). The report now shows the 500 rows with the smallest q-values, and the full table is the standardized TSV.
  • Bismark version captured in deduplication and extraction (faecd31). Those tools print "Deduplicator Version" and "Bismark Extractor Version", so versions.yml got an empty field.
  • A standard error and p-value carried through to evidence (0b5621a). Methylation evidence had standard_error and p_value hard-coded to None, so no methylation study could pool. DMR_POOLED_TILES now writes pvalue, se_meth_diff_percent and analysis_method, standardizing passes them through when present, and the harmonizer reads them. There are 2 new tests in tests/test_methylation_harmonize.py. methylKit tables are unchanged.
  • Gene-body units, dmr_features = 'genes' (f2a383f). Genome-wide tiles give many records per gene, so they collided in meta-analysis, and build-evidence-cards then failed for every study. Gene mode pools each gene's body and promoter CpGs into one unit, with intergenic tiles otherwise, and ANNOTATE_REGIONS keeps the gene and context those units carry.
  • The limma SE now reproduces limma's p under a Wald test (7c8664e). Using |diff / t| dropped limma's small-sample df. 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).

End-to-end check: a full Pages-sequence rebuild with CIBNOR's evidence (register → demo → harmonize_committed_results.py → gene meta-analysis → cards → top candidates → dashboard → all-feature meta-analysis):

  • it completes with no errors;
  • CIBNOR contributes 30,732 emerging, resilience_associated candidates, none significant, as in limma;
  • 0 multi-omics candidates;
  • 889 high-priority candidates, unchanged from before CIBNOR's evidence;
  • pytest -q on that tree: 601 passed.

Checklist

Study branches waiting on this

  • study/PRJNA690951/CIBNOR2021_HEAT_RRSS (#14): set dmr_engine = 'limma', min_coverage = 1, unit_min_coverage = 10, tile_min_cpgs = 10 in its config.

🤖 Generated with Claude Code

The first family-level methylKit run on CIBNOR2021_HEAT_RRSS gave 397
tiles at q < 0.05, 296 of them differing by <= 5 points: with 2 families
per arm, methylKit's per-region MN dispersion falls below 1, is reset to 1,
and the test reverts to a binomial test on summed reads. The run also
tested only 23,350 tiles, because it required 10x per strand in all 12
libraries before pooling.

DMR_POOLED_TILES (methylation.dmr_engine = 'limma' | 'dss') destrands
CpGs, pools by replicate_unit, filters at unit level (unit_min_coverage in
every unit), tiles (tile_min_cpgs), and tests with either limma's moderated
t on unit M-values or DSS::DMLtest. On synthetic 2-vs-2-family data with
family-level variation (20 true / 380 null tiles): limma 4/20 found, 1/380
false positives, null p calibrated (5.0% < 0.05); DSS 17/20 found but
57/380 false positives (Wald test, no small-sample correction); methylKit
0/20, 0/380. methylkit stays the default engine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sr320
sr320 merged commit 62ca4f3 into main Oct 16, 2026
6 checks passed
sr320 added a commit that referenced this pull request Oct 17, 2026
… low-power null

AREE's first complete real methylation run. All 12 WGBS libraries were
aligned (Bismark 0.24.2, --score_min L,0,-0.6, 50.0-53.9% unique mapping)
and extracted with the read-2 end-repair bias trimmed (CHH 0.6-1.1%).
Strands were merged and libraries pooled by family. Each gene's
body+promoter CpGs (31,166 genes), plus 101,421 intergenic 1 kb tiles,
were tested with limma on 2 RR vs 2 SS families. Nothing is significant
(smallest q 0.57); the YAML records it as a null at very low power. Within
each family, libraries vary 6.7-8.1x more than binomial, consistent with
distinct animals despite the shared BioSample.

Archives the standardized table (gzipped; SE and p for every unit), workflow
manifest, report, DMR and annotation QC, per-library QC, the Bismark
scoring diagnostic and the replicate-dispersion table. Sets results_file,
qc_status passed_with_warnings and analysis_status harmonized, and the
config settings that produced them.

Needs PR #67 (dmr_engine limma, dmr_features genes, SE/p through
harmonization) on main first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sr320

sr320 commented Oct 17, 2026

Copy link
Copy Markdown
Owner Author

Note: this PR merged at its first commit (3926b34). The five later commits listed in the description (report cap, Bismark version capture, SE/p carry-through, gene-body units, Wald-matched SE) were pushed after the merge and are not on main. They're rebased, unchanged, in #74, which CIBNOR2021_HEAT_RRSS (#14) needs before it can merge.

🤖 Generated with Claude Code

sr320 added a commit that referenced this pull request Oct 17, 2026
Methylation gene-body units, poolable SEs, and three first-run fixes (the part of #67 that missed its merge)
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