| Title: | Directional Methylation Set Analysis |
|---|---|
| Description: | Aligns probe-level methylation effects to their predicted effect on gene expression and, for system-level analysis, to each gene's curated polarity with respect to the system's activation tone (e.g. NR3C1 opposes HPA-axis activation). Pools the aligned coefficients with fixed-sign or expected-sign estimators and reports coverage and activation/brake balance per set. Ships the Project Alpha gene-system map, a gene-to-system polarity table carrying a stated source and review flag on every row, and a curated system > module > gene > probe selection cascade whose module labels carry literature-audited evidence tiers and citations; genes without a polarity entry must be specified by the user. |
| Authors: | Tsachi Ein-Dor [aut, cre, fnd] (ORCID: <https://orcid.org/0000-0002-3078-7591>) |
| Maintainer: | Tsachi Ein-Dor <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.99.11 |
| Built: | 2026-10-08 12:44:46 UTC |
| Source: | https://github.com/BiocStaging/dmsa |
The four 2026 Project Alpha builds, transcribed from the student pack's
covariate_sets.csv including its never-list. Supply focal for
the analysis at hand; everything else is fixed by the build.
alpha_design(build, focal, drop = character())alpha_design(build, focal, drop = character())
build |
1 (parents T1), 2 (T1 to T4), 3 (October 7), or 4 (children). |
focal |
Character focal term(s), passed to |
drop |
Optional character vector of contract covariates to drop, for
a declared deviation (e.g. dropping |
Build 2 and 3 are longitudinal: their contract asks for the
time x predictor interaction, so pass e.g.
focal = "time:BSI_Total_c".
A dmsa_design.
d <- alpha_design(1, focal = "IC_T1_c") d # builds 2 and 3 are longitudinal: the contract asks for the time interaction alpha_design(2, focal = "time:BSI_Total_c") # a deviation from the contract is recorded on the object, never silent alpha_design(1, focal = "IC_T1_c", drop = "Epi_T1")$droppedd <- alpha_design(1, focal = "IC_T1_c") d # builds 2 and 3 are longitudinal: the contract asks for the time interaction alpha_design(2, focal = "time:BSI_Total_c") # a deviation from the contract is recorded on the object, never silent alpha_design(1, focal = "IC_T1_c", drop = "Epi_T1")$dropped
Read the Project Alpha gene codebook shipped with the package: 549 genes assigned to 30 biological systems.
alpha_gene_systems()alpha_gene_systems()
data.frame with system_id, system, gene, n_kept, usability
gs <- alpha_gene_systems() dim(gs) length(unique(gs$system_id)) head(gs[, c("system_id", "system", "gene", "n_kept", "usability")], 4)gs <- alpha_gene_systems() dim(gs) length(unique(gs$system_id)) head(gs[, c("system_id", "system", "gene", "n_kept", "usability")], 4)
Curated w_g per gene: +1 gene product increases the system's activation tone, -1 decreases it (e.g. NR3C1 in the HPA axis: more GR = stronger negative feedback = LOWER axis tone), 0 = not on the activation axis (readouts, specificity controls, ambiguous).
alpha_polarity()alpha_polarity()
Returns the audited 2026c table: 1,234 genes across all 30 systems. A legacy 115-gene draft covering six systems also ships, and is used only as a fallback if the 2026c file is missing from the installation.
The 2026c table is why a system-level alignment works at all: an
alpha_polarity() that covered only six systems made
dmsa_align(level = "system") fail on most of the panel, since a
gene with no w_g entry stops the alignment by design.
data.frame with system_id, system, gene, w_g, role, confidence and,
for the 2026c table, module, w_g_source, anchor, evidence and citation.
The "status" attribute is "audited" or "draft".
pol <- alpha_polarity() nrow(pol) head(pol[, c("system", "gene", "w_g", "role")], 4) # CRH drives the HPA axis (w_g = +1); NR3C1 is its brake, so more GR means # stronger negative feedback and LOWER axis tone (w_g = -1) pol[pol$gene %in% c("CRH", "NR3C1") & pol$system_id == 2, c("gene", "w_g", "role")]pol <- alpha_polarity() nrow(pol) head(pol[, c("system", "gene", "w_g", "role")], 4) # CRH drives the HPA axis (w_g = +1); NR3C1 is its brake, so more GR means # stronger negative feedback and LOWER axis tone (w_g = -1) pol[pol$gene %in% c("CRH", "NR3C1") & pol$system_id == 2, c("gene", "w_g", "role")]
Reads the audited 2026c gene codebook that ships with the package and
returns it as a dmsa_reference: one row per gene within its
system and module, with the curated gene-to-system polarity attached.
alpha_reference(modules = NULL)alpha_reference(modules = NULL)
modules |
Optional data.frame with |
The reference is BIOLOGICAL - it defines system > module > gene and
stops there. Which CpGs exist is a property of the methylation data a user
supplies, not of the biology, so a gene with no probe in any particular
cohort still belongs to its system here. Genes with no curated polarity are
left UNRESOLVED; they are never silently treated as +1.
A dmsa_reference.
ref <- alpha_reference() ref # the polarity dmsa_align() consumes for one system (2 = HPA axis) head(dmsa_polarity_for(ref, "2"), 4)ref <- alpha_reference() ref # the polarity dmsa_align() consumes for one system (2 = HPA axis) head(dmsa_polarity_for(ref, "2"), 4)
Derives the system, module and gene counts from the shipped file and checks the reference is well formed: every gene in exactly one system and module, no probe-derived filtering, and polarity either curated or explicitly unresolved. Counts are DERIVED, never asserted against literals.
alpha_reference_check(verbose = TRUE)alpha_reference_check(verbose = TRUE)
verbose |
Print the report? Default TRUE. |
Invisibly, a list with the derived counts and the check results.
alpha_reference_check(verbose = FALSE)$countsalpha_reference_check(verbose = FALSE)$counts
Build the aligned sign s_j for a set of probes
dmsa_align( direction, genes = NULL, level = c("gene", "system"), polarity = "alpha", system_id = NULL, missing_polarity = c("error", "zero", "drop"), tissue = "blood" )dmsa_align( direction, genes = NULL, level = c("gene", "system"), polarity = "alpha", system_id = NULL, missing_polarity = c("error", "zero", "drop"), tissue = "blood" )
direction |
Either a plain character vector of CpG identifiers, in
which case the direction calls and the gene mapping are taken from the
map bundled with dmsa (see |
genes |
The set-membership gene of each probe - which gene this probe
is being read against. Required, and the same length as the probes, when
|
level |
|
polarity |
For |
system_id |
Optional integer: restrict the bundled polarity lookup to one Alpha system (a gene can carry different roles in different systems). |
missing_polarity |
What to do with a called probe whose gene has no
polarity entry: |
tissue |
Which bundled direction layer to read when |
data.frame: probe, gene, d, p_plus, w_g, s (= d * w_g at system level, d at gene level), p_s_plus = P(s = +1) (chained), usable (logical), and reason for every non-usable probe.
# The short way: probe IDs alone. Directions and gene mapping come from # the bundled map, so nothing has to be obtained first. al <- dmsa_align(c("cg00052046", "cg00176879", "cg00308631")) al[, c("probe", "gene", "d", "s", "usable")] # Which map produced this alignment travels with it. attr(al, "direction_map") # Pin each probe to one gene when the panel says which gene it belongs to. dmsa_align(c("cg00052046", "cg00176879"), genes = c("AVP", "AVP"))$s # Direction is tissue-specific, so which layer to read is part of the # analysis, not a default to leave alone. An adult saliva sample is about # 82% immune and takes blood calls; a neonatal buccal swab is near-pure # epithelium and takes epithelial ones. bu <- dmsa_align("cg00176879", tissue = "epithelium") attr(bu, "direction_map") # d is the CpG -> expression sign, w_g the gene -> system-tone sign. # A promoter CpG silences NR3C1 (d = -1) and GR is the HPA brake (w_g = -1), # so the probe votes for HIGHER axis tone: s = d * w_g = +1. g <- c("CRH", "NR3C1") dcall <- data.frame(cpg = c("cg_crh", "cg_nr3c1"), d = c(1, -1), p_plus = c(0.9, 0.1)) pol <- data.frame(gene = g, w_g = c(1, -1)) dmsa_align(dcall, genes = g, level = "gene") dmsa_align(dcall, genes = g, level = "system", polarity = pol)# The short way: probe IDs alone. Directions and gene mapping come from # the bundled map, so nothing has to be obtained first. al <- dmsa_align(c("cg00052046", "cg00176879", "cg00308631")) al[, c("probe", "gene", "d", "s", "usable")] # Which map produced this alignment travels with it. attr(al, "direction_map") # Pin each probe to one gene when the panel says which gene it belongs to. dmsa_align(c("cg00052046", "cg00176879"), genes = c("AVP", "AVP"))$s # Direction is tissue-specific, so which layer to read is part of the # analysis, not a default to leave alone. An adult saliva sample is about # 82% immune and takes blood calls; a neonatal buccal swab is near-pure # epithelium and takes epithelial ones. bu <- dmsa_align("cg00176879", tissue = "epithelium") attr(bu, "direction_map") # d is the CpG -> expression sign, w_g the gene -> system-tone sign. # A promoter CpG silences NR3C1 (d = -1) and GR is the HPA brake (w_g = -1), # so the probe votes for HIGHER axis tone: s = d * w_g = +1. g <- c("CRH", "NR3C1") dcall <- data.frame(cpg = c("cg_crh", "cg_nr3c1"), d = c(1, -1), p_plus = c(0.9, 0.1)) pol <- data.frame(gene = g, w_g = c(1, -1)) dmsa_align(dcall, genes = g, level = "gene") dmsa_align(dcall, genes = g, level = "system", polarity = pol)
The honest-reporting companion: how many probes each system can actually align, on which side of the activation axis. A system whose brake genes have no usable probes silently degrades to an activation-gene test - this table is how that is caught and reported.
dmsa_balance(alignment)dmsa_balance(alignment)
alignment |
result of dmsa_align(level = "system") |
one-row data.frame per call
dcall <- data.frame(cpg = paste0("p", 1:4), d = c(1, -1, 1, NA), p_plus = c(0.9, 0.1, 0.8, NA)) genes <- c("CRH", "POMC", "AVPR2", "NR3C1") pol <- data.frame(gene = genes, w_g = c(1, 1, 0, -1)) al <- dmsa_align(dcall, genes, level = "system", polarity = pol) # the only brake gene abstained, so this "system" test is really an # activation-gene test - which is what the balance table makes visible dmsa_balance(al)dcall <- data.frame(cpg = paste0("p", 1:4), d = c(1, -1, 1, NA), p_plus = c(0.9, 0.1, 0.8, NA)) genes <- c("CRH", "POMC", "AVPR2", "NR3C1") pol <- data.frame(gene = genes, w_g = c(1, 1, 0, -1)) al <- dmsa_align(dcall, genes, level = "system", polarity = pol) # the only brake gene abstained, so this "system" test is really an # activation-gene test - which is what the balance table makes visible dmsa_balance(al)
Largest set whose MEAN local fdr does not exceed q.
dmsa_bfdr_select(lfdr, q = 0.05)dmsa_bfdr_select(lfdr, q = 0.05)
lfdr |
vector of local fdr values |
q |
target Bayesian FDR |
integer indices of selected units
lfdr <- c(rep(0.001, 10), rep(0.5, 10), rep(0.99, 10)) sel <- dmsa_bfdr_select(lfdr, q = 0.05) sel mean(lfdr[sel]) # nothing is selected when no unit is credible enough to fit the budget dmsa_bfdr_select(rep(0.9, 20), q = 0.05)lfdr <- c(rep(0.001, 10), rep(0.5, 10), rep(0.99, 10)) sel <- dmsa_bfdr_select(lfdr, q = 0.05) sel mean(lfdr[sel]) # nothing is selected when no unit is credible enough to fit the budget dmsa_bfdr_select(rep(0.9, 20), q = 0.05)
Gated hierarchical DMSA with level-specific inference
dmsa_cascade( b, se, alignment, tree, nulls, engine = NULL, gate = c("both", "dense", "sparse"), q = 0.05, method = c("expected", "fixed"), borrow = 4 )dmsa_cascade( b, se, alignment, tree, nulls, engine = NULL, gate = c("both", "dense", "sparse"), q = 0.05, method = c("expected", "fixed"), borrow = 4 )
b, se
|
Probe-level coefficient and standard error, one per probe. |
alignment |
A |
tree |
data.frame with one row per probe and one column per level,
ordered outermost first, e.g. |
nulls |
Named list of null statistic vectors for calibration, one entry
per level named as in |
engine |
Named character vector giving |
gate |
|
q |
Target error rate at each level (BH for |
method |
Pooling method passed to |
borrow |
Upper bound on the prior-odds multiplier passed from a parent to its children at empirical-Bayes levels. 1 disables borrowing. |
An object of class dmsa_cascade.
set.seed(1) tree <- expand.grid(probe = 1:3, gene = 1:2, system = 1:4) tree <- tree[, c("system", "gene")] # outermost level first P <- nrow(tree); n <- 60; x <- rnorm(n) M <- matrix(rnorm(n * P), n, P) + outer(x, 0.9 * (tree$system == 1)) al <- dmsa_align(data.frame(cpg = paste0("p", 1:P), d = 1, p_plus = 0.95), genes = paste0("g", tree$gene), level = "gene") fit <- lm(M ~ x); b <- coef(fit)["x", ] se <- sqrt(colSums(residuals(fit)^2) / (n - 2) / sum((x - mean(x))^2)) nulls <- dmsa_cascade_null(b, se, al, tree, exposure = x, M = M, B = 49) cs <- dmsa_cascade(b, se, al, tree, nulls) cs$tables$systemset.seed(1) tree <- expand.grid(probe = 1:3, gene = 1:2, system = 1:4) tree <- tree[, c("system", "gene")] # outermost level first P <- nrow(tree); n <- 60; x <- rnorm(n) M <- matrix(rnorm(n * P), n, P) + outer(x, 0.9 * (tree$system == 1)) al <- dmsa_align(data.frame(cpg = paste0("p", 1:P), d = 1, p_plus = 0.95), genes = paste0("g", tree$gene), level = "gene") fit <- lm(M ~ x); b <- coef(fit)["x", ] se <- sqrt(colSums(residuals(fit)^2) / (n - 2) / sum((x - mean(x))^2)) nulls <- dmsa_cascade_null(b, se, al, tree, exposure = x, M = M, B = 49) cs <- dmsa_cascade(b, se, al, tree, nulls) cs$tables$system
Simulates the pooled aligned statistic under the global null for each unit
shape present in the tree, so that dmsa_cascade() can convert observed
statistics into calibrated p-values. Uses the supplied residual covariance
structure by resampling the exposure within blocks.
dmsa_cascade_null( b, se, alignment, tree, exposure, M, block = NULL, B = 999L, method = c("expected", "fixed") )dmsa_cascade_null( b, se, alignment, tree, exposure, M, block = NULL, B = 999L, method = c("expected", "fixed") )
b, se, alignment, tree
|
as in |
exposure |
numeric exposure vector used to fit |
M |
matrix of probe values (rows = observations), used to refit under permutation. |
block |
optional grouping vector defining exchangeable units. |
B |
number of permutations. |
method |
pooling method. |
named list suitable for the nulls argument.
set.seed(1) tree <- expand.grid(probe = 1:3, gene = 1:2, system = 1:4) tree <- tree[, c("system", "gene")] # outermost level first P <- nrow(tree); n <- 60; x <- rnorm(n) M <- matrix(rnorm(n * P), n, P) al <- dmsa_align(data.frame(cpg = paste0("p", 1:P), d = 1, p_plus = 0.95), genes = paste0("g", tree$gene), level = "gene") fit <- lm(M ~ x); b <- coef(fit)["x", ] se <- sqrt(colSums(residuals(fit)^2) / (n - 2) / sum((x - mean(x))^2)) # B = 49 keeps the example quick; a real calibration wants B >= 999 nulls <- dmsa_cascade_null(b, se, al, tree, exposure = x, M = M, B = 49) vapply(nulls, length, integer(1))set.seed(1) tree <- expand.grid(probe = 1:3, gene = 1:2, system = 1:4) tree <- tree[, c("system", "gene")] # outermost level first P <- nrow(tree); n <- 60; x <- rnorm(n) M <- matrix(rnorm(n * P), n, P) al <- dmsa_align(data.frame(cpg = paste0("p", 1:P), d = 1, p_plus = 0.95), genes = paste0("g", tree$gene), level = "gene") fit <- lm(M ~ x); b <- coef(fit)["x", ] se <- sqrt(colSums(residuals(fit)^2) / (n - 2) / sum((x - mean(x))^2)) # B = 49 keeps the example quick; a real calibration wants B >= 999 nulls <- dmsa_cascade_null(b, se, al, tree, exposure = x, M = M, B = 49) vapply(nulls, length, integer(1))
Tests Delta-S x exposure (x mod2) per unit through the composite machinery
(dmsa_model, Freedman-Lane, family blocks), with maxT across
the units of the level-local family on one shared permutation stream.
dmsa_change( M_before, M_after, data, outcome = NULL, exposure, mod2 = NULL, units, alignment, covariates = NULL, baseline = TRUE, role = c("predictor", "outcome"), block = NULL, B = 1999, correction = c("maxT", "minP"), weighting = c("combined", "reliability", "flat"), w_floor = 1.5, seed = 1 )dmsa_change( M_before, M_after, data, outcome = NULL, exposure, mod2 = NULL, units, alignment, covariates = NULL, baseline = TRUE, role = c("predictor", "outcome"), block = NULL, B = 1999, correction = c("maxT", "minP"), weighting = c("combined", "reliability", "flat"), w_floor = 1.5, seed = 1 )
M_before, M_after
|
numeric matrices, same probes in the same order (rows = the same people in the same order). |
data |
data.frame with outcome, exposure, moderators and covariates. |
outcome |
outcome column (role = "predictor"). |
exposure |
the moderating factor E; with two waves this is the 'mod' of the time x S x E three-way (Delta carries the time axis). |
mod2 |
optional second moderator: tests Delta-S x E x mod2 (moderators mean-centered, all lower-order terms in the model, highest-order tested). |
units |
unit label per probe column (genes, modules, or one system). |
alignment |
alignment as in |
covariates |
character vector of covariate columns. Supply the CHANGE-SCALE cell composition here; a message reminds you if empty. |
baseline |
include the unit's before-wave score as a covariate. |
role |
"predictor": outcome ~ dS * E (* mod2) + S0 + covariates. "outcome": dS ~ E (* mod2) + covariates (exposure predicts the change). |
block |
exchangeable blocks (e.g. couple id). |
B, correction, seed
|
as elsewhere; correction applies across the units |
weighting |
Character. Probe weighting engine within a unit: |
w_floor |
Numeric. Lower bound applied to reliability weights before normalisation, so a single poorly behaved probe cannot be driven to zero influence. Ignored when |
data.frame of class "dmsa_change": unit, n, n_probes, b, t, p, p_adj; attributes term, role, formula.
set.seed(5) n <- 80; g <- rep(c("g1", "g2"), each = 4); P <- length(g) d <- rep(c(1, -1), length.out = P); E <- rnorm(n); y <- rnorm(n) M0 <- matrix(rnorm(n * P), n, P) M1 <- 0.6 * M0 + matrix(rnorm(n * P), n, P) # plant a change in g1 that tracks y x E through the alignment M1[, g == "g1"] <- M1[, g == "g1"] + outer(0.5 * y * E, d[g == "g1"]) al <- dmsa_align(data.frame(cpg = paste0("cg", 1:P), d = d, p_plus = ifelse(d > 0, 0.9, 0.1)), genes = g) dat <- data.frame(y = y, E = E, dEpi = rnorm(n)) dmsa_change(M0, M1, dat, outcome = "y", exposure = "E", units = g, alignment = al, covariates = "dEpi", B = 99, seed = 1)set.seed(5) n <- 80; g <- rep(c("g1", "g2"), each = 4); P <- length(g) d <- rep(c(1, -1), length.out = P); E <- rnorm(n); y <- rnorm(n) M0 <- matrix(rnorm(n * P), n, P) M1 <- 0.6 * M0 + matrix(rnorm(n * P), n, P) # plant a change in g1 that tracks y x E through the alignment M1[, g == "g1"] <- M1[, g == "g1"] + outer(0.5 * y * E, d[g == "g1"]) al <- dmsa_align(data.frame(cpg = paste0("cg", 1:P), d = d, p_plus = ifelse(d > 0, 0.9, 0.1)), genes = g) dat <- data.frame(y = y, E = E, dEpi = rnorm(n)) dmsa_change(M0, M1, dat, outcome = "y", exposure = "E", units = g, alignment = al, covariates = "dEpi", B = 99, seed = 1)
Checks the things that silently ruin a methylation set analysis: a covariate that is constant in this subsample, a covariate that proxies the focal term, a grouping factor with too few levels to estimate, a grouping factor aliased with the focal term, and declared independence in data that plainly repeats.
dmsa_check_design(design, data, strict = TRUE)dmsa_check_design(design, data, strict = TRUE)
design |
A |
data |
A data.frame. |
strict |
If TRUE (default) problems are errors; if FALSE they are warnings and the checks are still returned. |
Invisibly, a list with n, problems, notes and
the per-term diagnostics.
set.seed(1) d <- data.frame(exposure = rnorm(80), sex_c = rep(c(-0.5, 0.5), 40), age = rnorm(80, 40, 5), Fib = 0, chip = rep(1:10, each = 8), cID = rep(1:40, each = 2)) des <- dmsa_design("exposure", c("sex_c", "age"), random = c("chip", "cID"), exchangeable = "cID") dmsa_check_design(des, d)$n # Fib is constant in this subsample: absorbed silently by lm, caught here bad <- dmsa_design("exposure", c("sex_c", "Fib"), random = "chip", exchangeable = "cID") dmsa_check_design(bad, d, strict = FALSE)$problemsset.seed(1) d <- data.frame(exposure = rnorm(80), sex_c = rep(c(-0.5, 0.5), 40), age = rnorm(80, 40, 5), Fib = 0, chip = rep(1:10, each = 8), cID = rep(1:40, each = 2)) des <- dmsa_design("exposure", c("sex_c", "age"), random = c("chip", "cID"), exchangeable = "cID") dmsa_check_design(des, d)$n # Fib is constant in this subsample: absorbed silently by lm, caught here bad <- dmsa_design("exposure", c("sex_c", "Fib"), random = "chip", exchangeable = "cID") dmsa_check_design(bad, d, strict = FALSE)$problems
Correct p-values across the contrasts of one categorical exposure
dmsa_contrast_adjust(x, p, direction = NULL)dmsa_contrast_adjust(x, p, direction = NULL)
x |
A |
p |
Named or ordered numeric vector of p-values, one per contrast, in
the order of |
direction |
Optional character or numeric vector of the pooled directions, carried through so the table reports sign next to significance. |
A data frame with raw and adjusted p-values.
set.seed(1) d <- data.frame(style = factor(sample(c("secure", "anxious", "avoidant"), 90, TRUE)), age = rnorm(90), cID = rep(1:45, each = 2)) des <- dmsa_design(focal = "style", fixed = "age", exchangeable = "cID") cc <- dmsa_contrasts(d, des, reference = "secure") # one p-value per directional contrast, corrected within the family dmsa_contrast_adjust(cc, c(anxious = 0.01, avoidant = 0.04), direction = c("+", "-"))set.seed(1) d <- data.frame(style = factor(sample(c("secure", "anxious", "avoidant"), 90, TRUE)), age = rnorm(90), cID = rep(1:45, each = 2)) des <- dmsa_design(focal = "style", fixed = "age", exchangeable = "cID") cc <- dmsa_contrasts(d, des, reference = "secure") # one p-value per directional contrast, corrected within the family dmsa_contrast_adjust(cc, c(anxious = 0.01, avoidant = 0.04), direction = c("+", "-"))
DMSA pools signed per-probe effects, so a focal factor with k > 2
levels cannot be tested as one directional term. This builds the k - 1
single-contrast designs, each of which is directional, and states the
multiplicity correction that applies across them.
dmsa_contrasts( data, design, reference = NULL, correct = c("holm", "BH", "bonferroni", "none") )dmsa_contrasts( data, design, reference = NULL, correct = c("holm", "BH", "bonferroni", "none") )
data |
The analysis data frame. |
design |
A |
reference |
Level to contrast against. Defaults to the first level, which for a factor is R's own reference. |
correct |
Multiplicity correction across the contrasts: |
An object of class dmsa_contrasts: a list of
dmsa_design objects in $designs, the data frame with the
contrast columns added in $data, and the correction in
$correct. Feed each design to dmsa_fit() and pass the
resulting p-values to dmsa_contrast_adjust().
set.seed(1) d <- data.frame(style = factor(sample(c("secure", "anxious", "avoidant"), 40, TRUE)), age = rnorm(40), cID = rep(1:20, each = 2)) des <- dmsa_design(focal = "style", fixed = "age", exchangeable = "cID") dmsa_contrasts(d, des)set.seed(1) d <- data.frame(style = factor(sample(c("secure", "anxious", "avoidant"), 40, TRUE)), age = rnorm(40), cID = rep(1:20, each = 2)) des <- dmsa_design(focal = "style", fixed = "age", exchangeable = "cID") dmsa_contrasts(d, des)
What the biological reference asked for, what the data at hand can test,
and why every discovered CpG x gene pair was or was not used. Only
available for frames built on the pair path
(direction_source = "cpgdirection"); a frame on the bundled
snapshot has no pair ledger and gets an error saying so.
dmsa_coverage(frame, level = c("system", "gene", "pairs"))dmsa_coverage(frame, level = c("system", "gene", "pairs"))
frame |
A |
level |
|
A data.frame (invisible its print). For "system" the
columns are system_id, system, n_genes_reference, n_genes_testable,
n_genes_untestable, n_measurements, n_pairs_used, n_pairs_discovered,
family_size_gene (= n_genes_testable, the size of the maxT/minP family
at the gene level).
## needs cpgdirection; see the vignette for a full run## needs cpgdirection; see the vignette for a full run
Returns the CpG-site column names of x that survive an
include/exclude filter on the NAMES. Patterns are fixed substrings
(not regular expressions), matched case-sensitively; a column survives
include if it contains ANY of the include patterns, and is then
removed by exclude if it contains ANY of the exclude patterns.
Only CpG-site columns (names starting cg...) are considered -
outcomes and covariates are never touched. The same filter is available
directly inside dmsa_frame() as cpgs_include /
cpgs_exclude.
dmsa_cpg_columns(x, include = NULL, exclude = NULL)dmsa_cpg_columns(x, include = NULL, exclude = NULL)
x |
A data.frame, a matrix, or a character vector of column names. |
include |
Optional character vector of fixed substrings; keep only
CpG columns containing at least one. |
exclude |
Optional character vector of fixed substrings; drop CpG
columns containing at least one. Applied after |
Character vector of surviving CpG column names.
cols <- c("cg00247334_BC21_T4_OXTR", "cg00247334_BC21_maternal_T1_OXTR", "cg00247334_BC21_paternal_T1_OXTR", "EPDS_Total_T4") ## the child's probes: "not the parents'" dmsa_cpg_columns(cols, exclude = c("_maternal", "_paternal")) ## the mother's probes dmsa_cpg_columns(cols, include = "_maternal")cols <- c("cg00247334_BC21_T4_OXTR", "cg00247334_BC21_maternal_T1_OXTR", "cg00247334_BC21_paternal_T1_OXTR", "EPDS_Total_T4") ## the child's probes: "not the parents'" dmsa_cpg_columns(cols, exclude = c("_maternal", "_paternal")) ## the mother's probes dmsa_cpg_columns(cols, include = "_maternal")
Declare a DMSA design: covariates, dependence, and permutation blocks
dmsa_design( focal, fixed, random = NULL, exchangeable = NULL, forbid = character(), label = NULL, id = NULL, format = c(NA, "wide", "long") )dmsa_design( focal, fixed, random = NULL, exchangeable = NULL, forbid = character(), label = NULL, id = NULL, format = c(NA, "wide", "long") )
focal |
Character. The focal term(s) whose pooled aligned effect is
tested. May be a main effect ( |
fixed |
Character vector of fixed-effect covariate columns. These are the build's contract, in full - no defaults are supplied, because a default is exactly what goes stale. |
random |
Grouping factors for random intercepts: a character vector
( |
exchangeable |
Character. The column defining the outermost independent unit for permutation: couples share a family, repeated measures share a person, and a person sits inside a family - so the exchangeable unit is the family in all three cases. Rows inside a block move together and keep their order, which is what makes the same mechanism valid cross-sectionally and longitudinally. |
forbid |
Character vector of columns that must NOT enter the model (the build's never-list). Supplying these turns a documentation rule into an error. |
label |
Optional name for printing. |
id |
Optional participant identifier column. Declaring it lets
|
format |
Declared data shape: |
An object of class dmsa_design.
dmsa_design(focal = "IC_c", fixed = c("sex_c", "age", "Fib", "ctrlSV3", "ctrlSV5"), random = c("chip", "cID"), exchangeable = "cID", forbid = c("ctrlSV4", "plate"))dmsa_design(focal = "IC_c", fixed = c("sex_c", "age", "Fib", "ctrlSV3", "ctrlSV5"), random = c("chip", "cID"), exchangeable = "cID", forbid = c("ctrlSV4", "plate"))
Returns per-probe methylation-to-expression direction calls from the maps bundled with dmsa, so an alignment can be built without obtaining a direction resource separately.
dmsa_directions(probes = NULL, genes = NULL, tissue = "blood")dmsa_directions(probes = NULL, genes = NULL, tissue = "blood")
probes |
Character vector of CpG identifiers. |
genes |
Optional character vector restricting the result. Either one
gene per entry of |
tissue |
Which bundled layer: |
Two layers of cpgdirection are bundled, each restricted to the
probe-gene pairs that carry an actual call. About seven in ten rows of the
full table abstain, and an abstention cannot be aligned to, so those rows
are not shipped - which is why the reduction costs nothing DMSA could have
used.
"blood"Whole blood: 488,204 pairs over 237,761 probes and 5,376 genes. The externally validated layer, and the right calls for an immune-dominated sample - adult saliva by passive drool runs about 82 immune.
"epithelium"Nasal epithelium: 688,843 pairs over 277,830 probes and 4,447 genes. The right calls for an epithelium-dominated sample - neonatal and infant buccal swabs run .89 to 1.00 epithelial, where blood calls would be the wrong tissue.
Choose the layer by what the sample is made of, not by what it is called. The failure mode this guards against is quiet: an epithelial sample scored against immune-derived calls reports full coverage while every direction is drawn from the wrong tissue.
The brain bridge, the SMR causal layer, the remaining tissues and the full
3.1M-pair table live in cpgdirection
(doi:10.5281/zenodo.22024185), which remains the complete resource.
A probe may map to more than one gene - a median of two, up to seven - and every mapped pair is returned, because which gene a probe is read against is a modelling decision rather than a lookup.
A data.frame with probe, gene, d
(-1 or +1), p_plus (the probability the direction is
+1) and tier (evidence grade). The map's provenance and
version are attached as the "dmsa_direction_map" attribute.
# What the bundled blood map knows about one gene's probes. avp <- dmsa_directions(genes = "AVP") nrow(avp) head(avp, 3) # Coverage of a probe set you care about. probes <- c("cg00000029", "cg00000165", "not_a_real_probe") got <- dmsa_directions(probes) got # The layers are not nested: each calls pairs the other declines to call, # so coverage of the same probe set differs by tissue. dmsa_directions(probes, tissue = "epithelium") # Provenance travels with the map. attr(dmsa_directions(), "dmsa_direction_map")[c("tissue", "version")]# What the bundled blood map knows about one gene's probes. avp <- dmsa_directions(genes = "AVP") nrow(avp) head(avp, 3) # Coverage of a probe set you care about. probes <- c("cg00000029", "cg00000165", "not_a_real_probe") got <- dmsa_directions(probes) got # The layers are not nested: each calls pairs the other declines to call, # so coverage of the same probe set differs by tissue. dmsa_directions(probes, tissue = "epithelium") # Provenance travels with the map. attr(dmsa_directions(), "dmsa_direction_map")[c("tissue", "version")]
Every module label in the bundled cascade was checked against the literature. This returns what that check concluded, so a module-level finding can be reported with the strength of its own definition attached: whether the label was retained or revised, whether the locked membership is homogeneous, the evidence tier, and the citation keys behind it.
dmsa_evidence(x, which = c("all", "moderate", "flagged"))dmsa_evidence(x, which = c("all", "moderate", "flagged"))
x |
A |
which |
|
data.frame of class dmsa_evidence, one row per module.
dmsa_evidence(dmsa_select(systems = "immune"), which = "flagged")dmsa_evidence(dmsa_select(systems = "immune"), which = "flagged")
Fit, pool and permute a DMSA set test under a declared design
dmsa_fit( data, probes, alignment, design, method = c("expected", "fixed"), B = 999L, engine = c("auto", "lm", "lmer"), beta_input = TRUE, check = TRUE, seed = NULL )dmsa_fit( data, probes, alignment, design, method = c("expected", "fixed"), B = 999L, engine = c("auto", "lm", "lmer"), beta_input = TRUE, check = TRUE, seed = NULL )
data |
data.frame with one row per observation. |
probes |
Character vector of methylation columns, in the SAME order as
the rows of |
alignment |
A |
design |
A |
method |
|
B |
Permutations. |
engine |
|
beta_input |
If TRUE (default) |
check |
Run |
seed |
Optional integer for reproducible permutation. |
An object of class dmsa_fit.
set.seed(1) n <- 80; K <- 6; x <- rnorm(n); d <- sample(c(-1, 1), K, TRUE) M <- matrix(rnorm(n * K), n, K) + outer(x, 0.5 * d) dat <- data.frame(cID = rep(1:40, each = 2), chip = rep(1:10, each = 8), x = x, sex_c = rep(c(-0.5, 0.5), 40)) probes <- paste0("cg", 1:K); dat[probes] <- 1 / (1 + 2^(-M)) al <- dmsa_align(data.frame(cpg = probes, d = d, p_plus = ifelse(d > 0, 0.95, 0.05)), genes = rep(c("g1", "g2"), each = 3)) des <- dmsa_design("x", "sex_c", random = c("chip", "cID"), exchangeable = "cID") dmsa_fit(dat, probes, al, des, B = 99, engine = "lm", seed = 1)set.seed(1) n <- 80; K <- 6; x <- rnorm(n); d <- sample(c(-1, 1), K, TRUE) M <- matrix(rnorm(n * K), n, K) + outer(x, 0.5 * d) dat <- data.frame(cID = rep(1:40, each = 2), chip = rep(1:10, each = 8), x = x, sex_c = rep(c(-0.5, 0.5), 40)) probes <- paste0("cg", 1:K); dat[probes] <- 1 / (1 + 2^(-M)) al <- dmsa_align(data.frame(cpg = probes, d = d, p_plus = ifelse(d > 0, 0.95, 0.05)), genes = rep(c("g1", "g2"), each = 3)) des <- dmsa_design("x", "sex_c", random = c("chip", "cID"), exchangeable = "cID") dmsa_fit(dat, probes, al, des, B = 99, engine = "lm", seed = 1)
Call 1 of the two-call interface. Declares the data, the levels
(system / module / gene / probe), the outcomes, the covariates and blocks,
optional moderation, and the direction map - validates all of it, runs the
test drive (with autofix = TRUE correcting what is safely
correctable, LOUDLY), and returns a dmsa_frame for
dmsa_report.
dmsa_frame( data, methylation = NULL, map = "alpha", reference = "alpha", direction_source = c("auto", "cpgdirection", "bundled"), tissue = "blood", replicate_policy = c("collapse_site", "keep_correlated"), cpgs_include = NULL, cpgs_exclude = NULL, sample_id = NULL, sample_align = c("id", "positional"), missing_methylation = c("error", "drop_probes", "common_complete_rows"), methylation_scale = c("auto", "beta", "M"), system = TRUE, module = FALSE, gene = TRUE, probe = TRUE, systems = NULL, sets = "alpha", ..., outcomes = NULL, predictors = NULL, covariates = "contract", blocks = NULL, random_effects = NULL, chip = TRUE, chip_effect = c("random", "fixed", "none"), outcome_levels = NULL, predictor_levels = NULL, moderation = FALSE, mod = "", mod2 = "", outcome_label = NULL, predictor_labels = NULL, mod_label = NULL, mod2_label = NULL, covariate_labels = NULL, mod_levels = NULL, type = c("linear", "non-linear", "exponential"), outcome_type = c("gaussian", "logistic", "multinomial"), frame_role = c("predictor", "outcome"), cpg_map = c("full", "confidence"), B = 1999, alpha = 0.05, seed = 1, correction = c("maxT", "minP"), weighting = c("combined", "reliability", "flat"), w_floor = 1.5, palette = "viridis", plots = TRUE, tables = TRUE, summary = TRUE, gene_models = "auto", progress = interactive(), beep = interactive(), plot_type = c("png", "pdf"), table_type = c("html", "docx", "rtf"), outdir = "dmsa_output", autofix = TRUE )dmsa_frame( data, methylation = NULL, map = "alpha", reference = "alpha", direction_source = c("auto", "cpgdirection", "bundled"), tissue = "blood", replicate_policy = c("collapse_site", "keep_correlated"), cpgs_include = NULL, cpgs_exclude = NULL, sample_id = NULL, sample_align = c("id", "positional"), missing_methylation = c("error", "drop_probes", "common_complete_rows"), methylation_scale = c("auto", "beta", "M"), system = TRUE, module = FALSE, gene = TRUE, probe = TRUE, systems = NULL, sets = "alpha", ..., outcomes = NULL, predictors = NULL, covariates = "contract", blocks = NULL, random_effects = NULL, chip = TRUE, chip_effect = c("random", "fixed", "none"), outcome_levels = NULL, predictor_levels = NULL, moderation = FALSE, mod = "", mod2 = "", outcome_label = NULL, predictor_labels = NULL, mod_label = NULL, mod2_label = NULL, covariate_labels = NULL, mod_levels = NULL, type = c("linear", "non-linear", "exponential"), outcome_type = c("gaussian", "logistic", "multinomial"), frame_role = c("predictor", "outcome"), cpg_map = c("full", "confidence"), B = 1999, alpha = 0.05, seed = 1, correction = c("maxT", "minP"), weighting = c("combined", "reliability", "flat"), w_floor = 1.5, palette = "viridis", plots = TRUE, tables = TRUE, summary = TRUE, gene_models = "auto", progress = interactive(), beep = interactive(), plot_type = c("png", "pdf"), table_type = c("html", "docx", "rtf"), outdir = "dmsa_output", autofix = TRUE )
data |
data.frame holding outcomes, covariates, ids and (by default) the Alpha methylation columns. |
methylation |
NULL (auto-detect the bundled map's columns in
|
map |
"alpha" for the bundled Project Alpha map, or a data.frame.
Used by the BUNDLED direction source only; passing a table forces
|
reference |
The biological reference (system > module > gene) that
defines membership on the pair path: |
direction_source |
Where CpG-to-gene direction comes from.
|
tissue |
Tissue context for pair discovery: |
replicate_policy |
|
cpgs_include, cpgs_exclude
|
Optional CpG-set selection by NAME
pattern, for methylation files that carry several biological groups in
one table (in Alpha's child file, |
sample_id |
Optional column of |
sample_align |
"id" (default) matches a methylation matrix to
|
missing_methylation |
What to do when the methylation block contains
missing or non-finite values. |
methylation_scale |
|
system, module, gene, probe
|
logical: which levels to analyse. |
systems |
Optional subset of systems to analyse. Accepts the selection
cascade's short names - |
sets |
The selection cascade declaring system > module > gene > probe.
|
... |
Not for new arguments. The former |
outcomes |
Which column(s) of |
predictors |
The natural spelling when METHYLATION IS YOUR OUTCOME:
which column(s) of |
covariates |
"contract" for the Alpha student contract, or a character vector. chip is added as a fixed factor automatically when present. |
blocks |
The exchangeability blocks: the column(s) of |
random_effects |
Deprecated spelling of |
chip |
how to handle the array/chip batch factor. |
chip_effect |
how the chip factor enters the model, once |
outcome_levels |
Optional labels for the two levels of a two-level
outcome, used only in the written report so a sentence can say
"taking pills [1] ... than not taking pills [0]" instead of falling
back to |
predictor_levels |
The same level labels, for a frame declared with
|
moderation, mod, mod2
|
moderation switch and moderator column(s); see the layout note above. |
outcome_label, predictor_labels, covariate_labels, mod_label, mod2_label
|
Optional display NAMES used in figures and the written report instead of
raw column names, spelled the way you declared each variable:
|
mod_levels |
Labels for the two LEVELS of a two-level moderator,
used in the moderation figures and prose. Safest as a named vector
matched by coded value - |
type |
"linear", "non-linear" (linear/quadratic/threshold arms, ACAT-combined), or "exponential" (adds an exp arm). |
outcome_type |
The declared family of each outcome: "gaussian",
"logistic", or "multinomial". A single value applies to every outcome. With
several outcomes of different kinds, name them -
|
frame_role |
Which way the SECONDARY models face. Most callers never
need this argument: passing the phenotype columns as |
cpg_map |
"full" (default) or "confidence"; see the note above. "full" analyses every CpG-gene pair with a usable direction call. "confidence" is an opt-in sensitivity restricted to high-confidence calls (tier A or SMR S1); it is a strict subset of "full" and never redefines system, module or gene membership. |
B, alpha, seed, correction
|
inference settings (correction is applied within level-local families only). |
weighting |
Character. Probe weighting engine within a unit: |
w_floor |
Numeric. Lower bound applied to reliability weights before normalisation, so a single poorly behaved probe cannot be driven to zero influence. Ignored when |
palette |
Colour palette for every figure the report draws. One of
the viridisLite palettes - |
plots, tables, summary, plot_type, table_type, outdir
|
output
styling for |
gene_models |
Draw real exon/intron structure (with the TSS) under
the probes in each locus panel. The default |
progress |
Show a 0-100
Defaults to |
beep |
Sound a completion signal when |
autofix |
TRUE: the test drive corrects what is safely correctable and |
object of class dmsa_frame.
set.seed(42) map <- data.frame(gene = rep(paste0("G", 1:3), each = 3), system_id = 1L, system = "Sim system") map$probe <- sprintf("cg%07d", seq_len(nrow(map))) map$column <- paste0(map$probe, "_", map$gene) map$best_direction <- rep(c(-1, 1), length.out = nrow(map)) map$p_plus <- ifelse(map$best_direction > 0, 0.9, 0.1) d <- data.frame(out1 = rnorm(60), cov1 = rnorm(60), cID = rep(1:30, each = 2)) sig <- 0.5 * outer(d$out1, map$best_direction * (map$gene == "G1")) d[map$column] <- plogis(matrix(rnorm(60 * nrow(map)), 60) + sig) dmsa_frame(d, map = map, outcomes = "out1", covariates = "cov1", blocks = "cID", B = 99, outdir = tempfile("dmsa_ex"))set.seed(42) map <- data.frame(gene = rep(paste0("G", 1:3), each = 3), system_id = 1L, system = "Sim system") map$probe <- sprintf("cg%07d", seq_len(nrow(map))) map$column <- paste0(map$probe, "_", map$gene) map$best_direction <- rep(c(-1, 1), length.out = nrow(map)) map$p_plus <- ifelse(map$best_direction > 0, 0.9, 0.1) d <- data.frame(out1 = rnorm(60), cov1 = rnorm(60), cID = rep(1:30, each = 2)) sig <- 0.5 * outer(d$out1, map$best_direction * (map$gene == "G1")) d[map$column] <- plogis(matrix(rnorm(60 * nrow(map)), 60) + sig) dmsa_frame(d, map = map, outcomes = "out1", covariates = "cov1", blocks = "cID", B = 99, outdir = tempfile("dmsa_ex"))
Gated hierarchical DMSA: one score, one model, level-local correction
dmsa_gate( data, M, map, alignment, formula, term, roots, block = NULL, alpha = 0.05, B = 1999, winsor = 3, gate = c("both", "sparse", "dense"), sparse_reach = "children", seed = NULL )dmsa_gate( data, M, map, alignment, formula, term, roots, block = NULL, alpha = 0.05, B = 1999, winsor = 3, gate = c("both", "sparse", "dense"), sparse_reach = "children", seed = NULL )
data |
data.frame with outcome, moderators and covariates. |
M |
Numeric matrix, samples x probes, rows aligned to |
map |
data.frame, one row per column of |
alignment |
|
formula |
Upper-level model formula, using the literal name |
term |
Coefficient to test, as |
roots |
Units at the outermost level that were PRE-REGISTERED. Their count is the first family's size; one root means no correction at the top. |
block |
Permutation blocks: a column name of |
alpha |
Level for every family, propagated to children that pass. |
B |
Permutations. |
winsor |
Passed to |
gate |
Which arm(s) to run: |
sparse_reach |
How far a sparse survivor reaches down the hierarchy:
|
seed |
Optional integer. |
Object of class dmsa_gate.
set.seed(1) n <- 60; probes <- paste0("cg", 1:6) map <- data.frame(gene = rep(c("OXTR", "AVP"), each = 3), probe = probes) al <- dmsa_align(data.frame(cpg = probes, d = rep(c(1, -1), 3), p_plus = rep(c(.9, .1), 3)), genes = map$gene, level = "gene") M <- matrix(rnorm(n * 6), n, 6, dimnames = list(NULL, probes)) d <- data.frame(age = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) d$y <- as.numeric(scale(M %*% (2 * al$p_s_plus - 1))) + rnorm(n) ## the two genes are the pre-registered family; a probe is corrected ## inside its own gene only, never against all six dmsa_gate(d, M, map, al, y ~ S + age, term = "S", roots = c("OXTR", "AVP"), block = d$cID, B = 99, seed = 1)set.seed(1) n <- 60; probes <- paste0("cg", 1:6) map <- data.frame(gene = rep(c("OXTR", "AVP"), each = 3), probe = probes) al <- dmsa_align(data.frame(cpg = probes, d = rep(c(1, -1), 3), p_plus = rep(c(.9, .1), 3)), genes = map$gene, level = "gene") M <- matrix(rnorm(n * 6), n, 6, dimnames = list(NULL, probes)) d <- data.frame(age = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) d$y <- as.numeric(scale(M %*% (2 * al$p_s_plus - 1))) + rnorm(n) ## the two genes are the pre-registered family; a probe is corrected ## inside its own gene only, never against all six dmsa_gate(d, M, map, al, y ~ S + age, term = "S", roots = c("OXTR", "AVP"), block = d$cID, B = 99, seed = 1)
Returns one row per drawable feature - coding exon slice, 5' UTR, 3' UTR, or
an exon whose coding status the annotation did not resolve - for every
transcript of a gene, with strand and genomic start/end. This is what
dmsa_plot_locus draws the gene from, and it is deliberately a
plain data frame so it can be built, inspected, corrected and cached like
any other reference layer in this package.
dmsa_gene_model( gene, source = c("auto", "ensembl", "gff", "txdb", "ensdb", "table"), file = NULL, db = NULL, orgdb = NULL, table = NULL, species = "homo_sapiens", genome = "hg38", cache = NULL, host = "https://rest.ensembl.org", quiet = FALSE )dmsa_gene_model( gene, source = c("auto", "ensembl", "gff", "txdb", "ensdb", "table"), file = NULL, db = NULL, orgdb = NULL, table = NULL, species = "homo_sapiens", genome = "hg38", cache = NULL, host = "https://rest.ensembl.org", quiet = FALSE )
gene |
Gene symbol, e.g. |
source |
One of |
file |
GTF/GFF3 path when |
db |
TxDb or EnsDb object when |
orgdb |
OrgDb for symbol lookup with |
table |
A data frame already in this shape, when |
species |
Ensembl species name. Default |
genome |
Genome label recorded on every row. Default |
cache |
Directory for fetched models, or |
host |
Ensembl REST host. |
quiet |
Suppress progress messages. |
A data frame with the columns in .GM_COLS, class
dmsa_gene_model. Zero rows if the model could not be obtained -
never a fabricated one.
dmsa_gene_model_check, dmsa_plot_locus
## Not run: ## Needs network: one lookup plus one region query per gene. gm <- dmsa_gene_model("NR3C1") subset(gm, canonical)[, c("feature", "start", "end", "exon_rank")] ## Or from an annotation you already have, with no network at all: gm <- dmsa_gene_model("NR3C1", source = "gff", file = "Homo_sapiens.gtf.gz") ## End(Not run)## Not run: ## Needs network: one lookup plus one region query per gene. gm <- dmsa_gene_model("NR3C1") subset(gm, canonical)[, c("feature", "start", "end", "exon_rank")] ## Or from an annotation you already have, with no network at all: gm <- dmsa_gene_model("NR3C1", source = "gff", file = "Homo_sapiens.gtf.gz") ## End(Not run)
Check a gene model before drawing from it
dmsa_gene_model_check(gm, verbose = TRUE)dmsa_gene_model_check(gm, verbose = TRUE)
gm |
A |
verbose |
Print the report. |
Invisibly, a list with ok and one entry per check.
## AVP sits on the minus strand, so its 5' UTR is the HIGH coordinate gm <- data.frame( gene = "AVP", gene_id = "ENSG00000101200", chr = "chr20", gene_start = 3082556, gene_end = 3084724, strand = "-", transcript = "ENST00000380293", transcript_biotype = "protein_coding", canonical = TRUE, feature = c("utr5", "cds", "cds", "utr3"), start = c(3084665, 3084555, 3082700, 3082556), end = c(3084724, 3084664, 3082802, 3082699), exon_rank = c(1, 1, 3, 3), source = "gff", genome = "hg38") dmsa_gene_model_check(gm)$ok ## a feature outside the gene span means a neighbour's exons survived bad <- gm; bad$start[1] <- 3000000; bad$end[1] <- 3000100 dmsa_gene_model_check(bad, verbose = FALSE)$ok## AVP sits on the minus strand, so its 5' UTR is the HIGH coordinate gm <- data.frame( gene = "AVP", gene_id = "ENSG00000101200", chr = "chr20", gene_start = 3082556, gene_end = 3084724, strand = "-", transcript = "ENST00000380293", transcript_biotype = "protein_coding", canonical = TRUE, feature = c("utr5", "cds", "cds", "utr3"), start = c(3084665, 3084555, 3082700, 3082556), end = c(3084724, 3084664, 3082802, 3082699), exon_rank = c(1, 1, 3, 3), source = "gff", genome = "hg38") dmsa_gene_model_check(gm)$ok ## a feature outside the gene span means a neighbour's exons survived bad <- gm; bad$start[1] <- 3000000; bad$end[1] <- 3000100 dmsa_gene_model_check(bad, verbose = FALSE)$ok
Takes the end object of any mainstream methylation preprocessing pipeline
(minfi, sesame, meffil, ewastools, ChAMP, RnBeads, methylprep - see
Details) and returns a dmsa_import object holding a samples-by-probes
beta matrix and an aligned phenotype data.frame. Pass the result straight to
dmsa_frame as its first argument.
dmsa_import(x, ...) ## S3 method for class 'matrix' dmsa_import( x, pheno = NULL, align_by = NULL, values = c("auto", "beta", "m"), orientation = c("auto", "probes_rows", "probes_cols"), id = NULL, format = NULL, source = "matrix", ... ) ## S3 method for class 'data.frame' dmsa_import( x, pheno = NULL, align_by = NULL, values = c("auto", "beta", "m"), orientation = c("auto", "probes_rows", "probes_cols"), id = NULL, format = NULL, source = "data.frame", ... ) ## S3 method for class 'character' dmsa_import(x, ...) ## S3 method for class 'list' dmsa_import(x, pheno = NULL, align_by = NULL, id = NULL, format = NULL, ...) ## Default S3 method: dmsa_import( x, pheno = NULL, align_by = NULL, values = c("auto", "beta", "m"), id = NULL, format = NULL, source = NULL, ... )dmsa_import(x, ...) ## S3 method for class 'matrix' dmsa_import( x, pheno = NULL, align_by = NULL, values = c("auto", "beta", "m"), orientation = c("auto", "probes_rows", "probes_cols"), id = NULL, format = NULL, source = "matrix", ... ) ## S3 method for class 'data.frame' dmsa_import( x, pheno = NULL, align_by = NULL, values = c("auto", "beta", "m"), orientation = c("auto", "probes_rows", "probes_cols"), id = NULL, format = NULL, source = "data.frame", ... ) ## S3 method for class 'character' dmsa_import(x, ...) ## S3 method for class 'list' dmsa_import(x, pheno = NULL, align_by = NULL, id = NULL, format = NULL, ...) ## Default S3 method: dmsa_import( x, pheno = NULL, align_by = NULL, values = c("auto", "beta", "m"), id = NULL, format = NULL, source = NULL, ... )
x |
A pipeline result: a beta/M matrix in either orientation, a
|
... |
Passed between methods. |
pheno |
Optional phenotype/sample sheet: a |
align_by |
Name of the |
values |
|
orientation |
|
id, format
|
Optional study-shape declaration carried to the analysis
step: the participant-id column in |
source |
Optional label naming the pipeline the object came from
( |
An object of class dmsa_import: a list with data (the
aligned phenotype data.frame), methylation (samples x probes beta
matrix), source, values (scale detected), align
(how samples were matched), id, format and notes.
dmsa_frame() accepts it directly as its first argument.
## a probes-x-samples beta matrix, the shape most pipelines produce set.seed(1) B <- matrix(runif(60), nrow = 6, dimnames = list(sprintf("cg%08d", 1:6), paste0("S", 1:10))) ph <- data.frame(Sample_Name = paste0("S", 1:10), age = rnorm(10)) imp <- dmsa_import(B, pheno = ph) imp## a probes-x-samples beta matrix, the shape most pipelines produce set.seed(1) B <- matrix(runif(60), nrow = 6, dimnames = list(sprintf("cg%08d", 1:6), paste0("S", 1:10))) ph <- data.frame(Sample_Name = paste0("S", 1:10), age = rnorm(10)) imp <- dmsa_import(B, pheno = ph) imp
Gated hierarchical DMSA on the pooled aligned statistic
dmsa_levels( M, data, rhs, term, map, alignment, roots, block = NULL, alpha = 0.05, B = 1999, winsor = 3, method = c("expected", "fixed"), gate = c("both", "sparse", "dense"), family_correction = c("maxT", "minP"), sparse_reach = "children", seed = NULL )dmsa_levels( M, data, rhs, term, map, alignment, roots, block = NULL, alpha = 0.05, B = 1999, winsor = 3, method = c("expected", "fixed"), gate = c("both", "sparse", "dense"), family_correction = c("maxT", "minP"), sparse_reach = "children", seed = NULL )
M |
Numeric matrix, samples x probes, methylation on the analysis scale.
The probes are the RESPONSES, as in |
data |
data.frame of predictors, same row order as |
rhs |
Character vector of right-hand-side terms, e.g.
|
term |
The design column to pool, as |
map |
data.frame, one row per column of |
alignment |
|
roots |
Pre-registered units at the outermost level. |
block |
Permutation block labels, one per row. |
alpha, B, winsor, seed
|
As in |
method |
Pooling method for |
gate |
|
family_correction |
Character. Family-wise correction applied inside each level-local family: |
sparse_reach |
|
Object of class dmsa_gate.
set.seed(1) n <- 60; probes <- paste0("cg", 1:6) map <- data.frame(gene = rep(c("OXTR", "AVP"), each = 3), probe = probes) al <- dmsa_align(data.frame(cpg = probes, d = rep(c(1, -1), 3), p_plus = rep(c(.9, .1), 3)), genes = map$gene, level = "gene") d <- data.frame(anx = rnorm(n), age = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) ## the probes are the RESPONSES: their aligned coefficients are pooled, ## so cross-probe concordance is what carries the evidence M <- matrix(rnorm(n * 6), n, 6) + outer(0.3 * d$anx, 2 * al$p_s_plus - 1) dmsa_levels(M, d, rhs = c("anx", "age"), term = "anx", map = map, alignment = al, roots = c("OXTR", "AVP"), block = d$cID, B = 99, seed = 1)set.seed(1) n <- 60; probes <- paste0("cg", 1:6) map <- data.frame(gene = rep(c("OXTR", "AVP"), each = 3), probe = probes) al <- dmsa_align(data.frame(cpg = probes, d = rep(c(1, -1), 3), p_plus = rep(c(.9, .1), 3)), genes = map$gene, level = "gene") d <- data.frame(anx = rnorm(n), age = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) ## the probes are the RESPONSES: their aligned coefficients are pooled, ## so cross-probe concordance is what carries the evidence M <- matrix(rnorm(n * 6), n, 6) + outer(0.3 * d$anx, 2 * al$p_s_plus - 1) dmsa_levels(M, d, rhs = c("anx", "age"), term = "anx", map = map, alignment = al, roots = c("OXTR", "AVP"), block = d$cID, B = 99, seed = 1)
Efron-style two-group model on z-values: the null centre and scale are
fitted on an iteratively trimmed core (robust to a minority signal slab),
pi0 is the observed mass inside the window that holds 50
the fitted null, and the alternative is a wider normal. Returns one local
fdr per unit.
dmsa_lfdr(z, prior_odds_multiplier = 1, min_n = 50L)dmsa_lfdr(z, prior_odds_multiplier = 1, min_n = 50L)
z |
numeric vector of unit-level z statistics (null approximately standard normal after calibration). |
prior_odds_multiplier |
Multiplies the estimated |
min_n |
Below this many units the estimate is not attempted and
|
Assumptions, stated plainly (calibrated by simulation, 2026-08-29):
signal must be a MINORITY of units - power is good to roughly a third
signal and collapses conservatively (toward selecting nothing) beyond
that; weak signal (|z| ~ 2) is largely invisible to the EB arm (also
conservative); and the null must be approximately GAUSSIAN. A null with
both tails significantly heavier than the fitted Gaussian - kurtosis,
which the two-group model would misread as signal - is detected by a
two-radius symmetric-tail-excess guard and the function returns
NULL with a warning, the callers' documented fall-back to the
frequentist rule. The guard keys on BOTH tails being in excess, so
one-sided signal does not trip it (~3
symmetric two-sided signal DOES trip it and is handled by the frequentist
arm instead - a conservative, never a silent, outcome. Residual risk:
with only a few hundred units a heavy-tailed null is detected in about
two runs of three; below that scale prefer the frequentist engine.
numeric vector of local fdr values, or NULL if not estimable.
set.seed(1) ## 300 null genes and 30 carrying a real aligned effect z <- c(rnorm(300), rnorm(30, mean = 4)) lfdr <- dmsa_lfdr(z) round(quantile(lfdr, c(0, .5, 1)), 3) mean(lfdr[301:330]) < mean(lfdr[1:300]) ## too few units to fit the two-group model: NULL asks the caller to ## fall back to a frequentist rule rather than guess is.null(dmsa_lfdr(rnorm(10)))set.seed(1) ## 300 null genes and 30 carrying a real aligned effect z <- c(rnorm(300), rnorm(30, mean = 4)) lfdr <- dmsa_lfdr(z) round(quantile(lfdr, c(0, .5, 1)), 3) mean(lfdr[301:330]) < mean(lfdr[1:300]) ## too few units to fit the two-group model: NULL asks the caller to ## fall back to a frequentist rule rather than guess is.null(dmsa_lfdr(rnorm(10)))
Tests a moderated-mediation claim in which the predictors, moderator and mediator are latent SYSTEM TONES, each measured by its module tones as reflective indicators, and the outcome is an observed variable. The structural model runs on measurement-error-corrected moments (structural-after-measurement / Croon family), so the interaction paths are not attenuated by tone unreliability; inference is permutation-native (Freedman-Lane per path, joint-significance for products, Holm over the declared index family).
dmsa_mediate( data, methylation = NULL, map = "alpha", x, m, w = NULL, outcome, covariates = character(), measurement = c("tau", "congeneric", "single"), min_module_probes = 3L, single_rel = NULL, module_weights = c("equal", "sqrt_probes", "probes"), measure_covariates = NULL, chip = NULL, block = NULL, perm_blocks = c("none", "chip"), model = c("7", "8"), map_level = c("full", "confidence"), B = 1999L, boot = 0L, alpha_ssc = 0, conditional_at = c(-1, 0, 1), beta_input = TRUE, seed = NULL )dmsa_mediate( data, methylation = NULL, map = "alpha", x, m, w = NULL, outcome, covariates = character(), measurement = c("tau", "congeneric", "single"), min_module_probes = 3L, single_rel = NULL, module_weights = c("equal", "sqrt_probes", "probes"), measure_covariates = NULL, chip = NULL, block = NULL, perm_blocks = c("none", "chip"), model = c("7", "8"), map_level = c("full", "confidence"), B = 1999L, boot = 0L, alpha_ssc = 0, conditional_at = c(-1, 0, 1), beta_input = TRUE, seed = NULL )
data |
data.frame of phenotype rows, or a |
methylation |
Optional samples x probes matrix aligned to
|
map |
A |
x |
Character vector of predictor system short names (1+). |
m |
Mediator system short name. |
w |
Moderator system short name, or |
outcome |
Outcome column in |
covariates |
Character vector of covariate columns. |
measurement |
Measurement model for each system's module tones:
|
min_module_probes |
A module enters as an indicator only with at least this many usable aligned probes (default 3). |
single_rel |
Optional FIXED reliability for any system that lands in
single-indicator mode (Savalei 2019: fixed a-priori values outperform
same-sample estimates in small samples). |
module_weights |
How module tones combine in the SINGLE-INDICATOR
composite: |
measure_covariates |
Which covariates are residualized out of the
module tones BEFORE the measurement stage (the composition
pre-adjustment). |
chip |
Optional column in |
block |
Optional column in |
perm_blocks |
|
model |
|
map_level |
|
B |
Permutations per path. |
boot |
Bootstrap resamples for percentile CIs; the ENTIRE pipeline
(measurement models included) is refit in every resample. |
alpha_ssc |
Bogaert-Loh-Rosseel alpha for the small-sample offset
correction; |
conditional_at |
Moderator values (in latent SD units) at which conditional indirect effects are reported. |
beta_input |
|
seed |
Optional integer; the caller's RNG state is restored. |
An object of class dmsa_mediate: per-system measurement
tables, path coefficient tables with permutation p per path, indices
of moderated mediation with joint-significance + Holm p, conditional
indirect effects, bootstrap CIs when requested, and the corrected
moment machinery for inspection.
## synthetic end-to-end run at analysis scale (see tests for the ## simulation-gate harness) set.seed(1) n <- 120 eta <- matrix(rnorm(n * 3), n, 3) # hpa, oxytocin, kyn mk <- function(f, p) sapply(seq_len(p), function(j) 0.7 * f + rnorm(n, sd = sqrt(1 - 0.49))) tones <- list(hpa = mk(eta[, 1], 6), oxytocin = mk(eta[, 2], 8), kyn = mk(0.4 * eta[, 1] * eta[, 2] + 0.3 * eta[, 1], 5)) dat <- data.frame(y = 0.5 * (0.4 * eta[, 1] * eta[, 2] + 0.3 * eta[, 1]) + rnorm(n), age = rnorm(n)) fit <- dmsa:::.med_core(tones, dat, x = "hpa", m = "kyn", w = "oxytocin", outcome = "y", covariates = "age", measurement = "tau", B = 199, blocks = NULL, model8 = FALSE, conditional_at = c(-1, 0, 1), alpha_ssc = 0) fit$indices## synthetic end-to-end run at analysis scale (see tests for the ## simulation-gate harness) set.seed(1) n <- 120 eta <- matrix(rnorm(n * 3), n, 3) # hpa, oxytocin, kyn mk <- function(f, p) sapply(seq_len(p), function(j) 0.7 * f + rnorm(n, sd = sqrt(1 - 0.49))) tones <- list(hpa = mk(eta[, 1], 6), oxytocin = mk(eta[, 2], 8), kyn = mk(0.4 * eta[, 1] * eta[, 2] + 0.3 * eta[, 1], 5)) dat <- data.frame(y = 0.5 * (0.4 * eta[, 1] * eta[, 2] + 0.3 * eta[, 1]) + rnorm(n), age = rnorm(n)) fit <- dmsa:::.med_core(tones, dat, x = "hpa", m = "kyn", w = "oxytocin", outcome = "y", covariates = "age", measurement = "tau", B = 199, blocks = NULL, model8 = FALSE, conditional_at = c(-1, 0, 1), alpha_ssc = 0) fit$indices
The upper level of DMSA 2.0. Fits formula once, then permutes the
reduced-model residuals within permutation blocks of equal size
(Freedman-Lane) to obtain an exact p-value for one named coefficient -
including an interaction. dmsa_moderate is the special case of
this for score * moderator with a declared dmsa_design();
use that when you want the design contract enforced and simple slopes
reported, and this when the question is an arbitrary term.
dmsa_model( formula, data, term, block = NULL, B = 1999, seed = NULL, nulls = FALSE )dmsa_model( formula, data, term, block = NULL, B = 1999, seed = NULL, nulls = FALSE )
formula |
Model formula. |
data |
data.frame. |
term |
Name of the coefficient to test, exactly as |
block |
Block labels, one per row of |
B |
Permutations. |
seed |
Optional integer. |
nulls |
Logical (default |
An object of class dmsa_model.
set.seed(1) n <- 80 d <- data.frame(S = rnorm(n), ACE = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) # couples d$y <- 0.5 * d$S * d$ACE + rnorm(n) ## rows are relabelled only within equal-size families, so the couple ## dependence is preserved under the null dmsa_model(y ~ S * ACE, d, term = "S:ACE", block = d$cID, B = 99, seed = 1)set.seed(1) n <- 80 d <- data.frame(S = rnorm(n), ACE = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) # couples d$y <- 0.5 * d$S * d$ACE + rnorm(n) ## rows are relabelled only within equal-size families, so the couple ## dependence is preserved under the null dmsa_model(y ~ S * ACE, d, term = "S:ACE", block = d$cID, B = 99, seed = 1)
Refits the same model once per projection, substituting each into the column
named score_col, and combines the p-values with the aggregated Cauchy
test. ACAT is valid under arbitrary dependence, so hedging across projections
costs no multiplicity - unlike a Bonferroni over arms, which is what the
old min(1, 2*min(p)) gate amounted to.
dmsa_model_omnibus( scores, formula, data, term, block = NULL, score_col = "S", B = 1999, seed = NULL )dmsa_model_omnibus( scores, formula, data, term, block = NULL, score_col = "S", B = 1999, seed = NULL )
scores |
data.frame from |
formula |
Formula referring to the score by |
data, block, B, seed
|
As in |
term |
Term to test. |
score_col |
Column name the formula uses for the score. |
list: p_by_flavour, the omnibus p, and the fits.
set.seed(1) n <- 80 M <- matrix(rnorm(n * 6), n, 6) al <- dmsa_align(data.frame(cpg = paste0("cg", 1:6), d = c(1, 1, -1, 1, -1, 1), p_plus = rep(.9, 6)), genes = rep("OXTR", 6), level = "gene") sc <- dmsa_scores(M, al) d <- data.frame(age = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) d$y <- 0.6 * sc$aligned + rnorm(n) ## one p per projection, combined by ACAT - hedging costs no multiplicity o <- dmsa_model_omnibus(sc, y ~ S + age, d, term = "S", block = d$cID, B = 99, seed = 1) round(c(o$p_by_flavour, omnibus = o$p), 3)set.seed(1) n <- 80 M <- matrix(rnorm(n * 6), n, 6) al <- dmsa_align(data.frame(cpg = paste0("cg", 1:6), d = c(1, 1, -1, 1, -1, 1), p_plus = rep(.9, 6)), genes = rep("OXTR", 6), level = "gene") sc <- dmsa_scores(M, al) d <- data.frame(age = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) d$y <- 0.6 * sc$aligned + rnorm(n) ## one p per projection, combined by ACAT - hedging costs no multiplicity o <- dmsa_model_omnibus(sc, y ~ S + age, d, term = "S", block = d$cID, B = 99, seed = 1) round(c(o$p_by_flavour, omnibus = o$p), 3)
Moderated DMSA (mDMSA)
dmsa_moderate( data, probes, alignment, outcome, moderator, design, method = c("expected", "fixed"), center = TRUE, scale_outcome = TRUE, B = 999L, engine = c("auto", "lm", "lmer"), beta_input = TRUE, check = TRUE, seed = NULL )dmsa_moderate( data, probes, alignment, outcome, moderator, design, method = c("expected", "fixed"), center = TRUE, scale_outcome = TRUE, B = 999L, engine = c("auto", "lm", "lmer"), beta_input = TRUE, check = TRUE, seed = NULL )
data |
data.frame. |
probes, alignment
|
as in |
outcome |
Character. The outcome column (a psychological measure). |
moderator |
Character. The moderator column. |
design |
A |
method |
|
center |
Center the moderator (default TRUE). Leaving a moderator uncentered makes the product term collinear with its parents and the simple slopes uninterpretable; the returned object records what was done. |
scale_outcome |
Standardise the outcome (default TRUE). |
B |
Permutations (Freedman-Lane: residuals of the no-interaction model are permuted within exchangeable blocks, preserving the dependence). |
engine |
as in |
beta_input |
TRUE if |
check |
Run the design check (default TRUE). |
seed |
Optional integer. |
An object of class dmsa_moderate.
set.seed(1) n <- 100; probes <- paste0("cg", 1:6) dat <- data.frame(sex_c = rep(c(-.5, .5), length.out = n), chip = rep(1:10, each = 10), cID = rep(seq_len(n / 2), each = 2), mod = rnorm(n)) dat[probes] <- plogis(matrix(rnorm(n * 6), n, 6)) al <- dmsa_align(data.frame(cpg = probes, d = rep(c(1, -1), 3), p_plus = rep(c(.9, .1), 3)), genes = rep("OXTR", 6), level = "gene") dat$out <- 0.6 * dmsa_score(dat, probes, al) * dat$mod + rnorm(n) des <- dmsa_design("placeholder", "sex_c", random = "chip", exchangeable = "cID") dmsa_moderate(dat, probes, al, outcome = "out", moderator = "mod", design = des, B = 99, engine = "lm", seed = 3)set.seed(1) n <- 100; probes <- paste0("cg", 1:6) dat <- data.frame(sex_c = rep(c(-.5, .5), length.out = n), chip = rep(1:10, each = 10), cID = rep(seq_len(n / 2), each = 2), mod = rnorm(n)) dat[probes] <- plogis(matrix(rnorm(n * 6), n, 6)) al <- dmsa_align(data.frame(cpg = probes, d = rep(c(1, -1), 3), p_plus = rep(c(.9, .1), 3)), genes = rep("OXTR", 6), level = "gene") dat$out <- 0.6 * dmsa_score(dat, probes, al) * dat$mod + rnorm(n) des <- dmsa_design("placeholder", "sex_c", random = "chip", exchangeable = "cID") dmsa_moderate(dat, probes, al, outcome = "out", moderator = "mod", design = des, B = 99, engine = "lm", seed = 3)
M-value transform with clamping
dmsa_mvalues(beta, eps = 1e-04)dmsa_mvalues(beta, eps = 1e-04)
beta |
numeric matrix or vector of beta values in (0,1) |
eps |
clamp bound |
A numeric matrix or vector of M-values, the same shape as
beta, on the logit scale.
beta <- matrix(runif(24, 0.05, 0.95), nrow = 6, dimnames = list(NULL, paste0("cg", 1:4))) round(head(dmsa_mvalues(beta), 3), 2) ## clamping is what keeps a beta of exactly 0 or 1 from becoming +/-Inf dmsa_mvalues(c(0, 0.5, 1))beta <- matrix(runif(24, 0.05, 0.95), nrow = 6, dimnames = list(NULL, paste0("cg", 1:4))) round(head(dmsa_mvalues(beta), 3), 2) ## clamping is what keeps a beta of exactly 0 or 1 from becoming +/-Inf dmsa_mvalues(c(0, 0.5, 1))
Pools the per-probe F statistic for the k - 1 contrast columns and
calibrates it by the same block permutation the directional tests use. This
answers "does this factor move the set at all" and gives up the
direction, which is the property that separates DMSA from the methods it is
compared against. Use it as a screen, or when the levels have no ordering and
no reference level is defensible - and report dmsa_contrasts()
alongside it.
dmsa_omnibus(data, Y, design, beta_input = TRUE, B = 999, seed = 1L)dmsa_omnibus(data, Y, design, beta_input = TRUE, B = 999, seed = 1L)
data |
Analysis data frame. |
Y |
Probe matrix (subjects x probes), betas or M-values. |
design |
A |
beta_input |
Passed through: are the columns of |
B |
Permutations. |
seed |
RNG seed. |
A list with the pooled statistic, the permutation p-value, and an
explicit directional = FALSE.
set.seed(1) n <- 90 d <- data.frame(style = factor(sample(c("secure", "anxious", "avoidant"), n, TRUE)), age = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) Y <- matrix(plogis(rnorm(n * 8)), n, 8) # beta values for 8 probes des <- dmsa_design(focal = "style", fixed = "age", exchangeable = "cID") ## three attachment styles with no defensible reference level: the pooled ## F asks only whether the factor moves the set, and gives up the direction o <- dmsa_omnibus(d, Y, des, B = 99, seed = 5) round(o$p_perm, 3) o$directional # always FALSE: a pooled F on 2 df has no signset.seed(1) n <- 90 d <- data.frame(style = factor(sample(c("secure", "anxious", "avoidant"), n, TRUE)), age = rnorm(n), cID = rep(seq_len(n / 2), each = 2)) Y <- matrix(plogis(rnorm(n * 8)), n, 8) # beta values for 8 probes des <- dmsa_design(focal = "style", fixed = "age", exchangeable = "cID") ## three attachment styles with no defensible reference level: the pooled ## F asks only whether the factor moves the set, and gives up the direction o <- dmsa_omnibus(d, Y, des, B = 99, seed = 5) round(o$p_perm, 3) o$directional # always FALSE: a pooled F on 2 df has no sign
The subject-level arm of DMSA for outcomes that are not continuous: binary (logistic), ordered-categorical (proportional odds), unordered-categorical (multinomial), or counts (Poisson / negative binomial). Optionally moderated.
dmsa_outcome( data, outcome, score, family = c("gaussian", "binomial", "ordinal", "multinomial", "poisson"), moderator = NULL, covariates = character(), block = NULL, center = TRUE, B = 999L, seed = NULL )dmsa_outcome( data, outcome, score, family = c("gaussian", "binomial", "ordinal", "multinomial", "poisson"), moderator = NULL, covariates = character(), block = NULL, center = TRUE, B = 999L, seed = NULL )
data |
data.frame with one row per subject. |
outcome |
Name of the outcome column. For |
score |
Name of the tone-score column (from |
family |
One of |
moderator |
Optional moderator column; when given, the tested term is
|
covariates |
Character vector of covariate columns. |
block |
Optional column naming the exchangeable unit for permutation (families, repeated measures). |
center |
Center the moderator (default TRUE). |
B |
Permutations. 0 skips them and returns model-based inference only. |
seed |
Optional integer. |
Direction. A binary, count or ordinal outcome yields ONE coefficient
for the tone score, so DMSA's directional claim survives intact - the sign of
the log-odds (or log-rate, or latent-scale slope) is the expression-aligned
direction. A multinomial outcome yields K-1 coefficients and there is
no single sign: the test becomes an omnibus "tone predicts category" and the
per-category log-odds are reported separately. If the categories are in any
sense ordered, family = "ordinal" keeps the direction and is strictly
preferable.
An object of class dmsa_outcome.
set.seed(1) n <- 120 d <- data.frame(S = rnorm(n), cv = rnorm(n), cid = rep(seq_len(n / 2), each = 2)) d$y <- rbinom(n, 1, plogis(0.9 * d$S)) ## a binary outcome yields ONE coefficient, so the sign of the log-odds ## is still the expression-aligned direction dmsa_outcome(d, outcome = "y", score = "S", family = "binomial", covariates = "cv", block = "cid", B = 99, seed = 2)set.seed(1) n <- 120 d <- data.frame(S = rnorm(n), cv = rnorm(n), cid = rep(seq_len(n / 2), each = 2)) d$y <- rbinom(n, 1, plogis(0.9 * d$S)) ## a binary outcome yields ONE coefficient, so the sign of the log-odds ## is still the expression-aligned direction dmsa_outcome(d, outcome = "y", score = "S", family = "binomial", covariates = "cv", block = "cid", B = 99, seed = 2)
Perturbs the analysis set around a fixed finding and records what happens to each engine's p-value. The focal unit's own probes are never touched, so any movement is attributable to the analysis set rather than to the evidence.
dmsa_pdance( engines, set, focal, pool = NULL, form = c("dropout", "addition"), grid = NULL, R = 100L, alpha = 0.05, seed = NULL )dmsa_pdance( engines, set, focal, pool = NULL, form = c("dropout", "addition"), grid = NULL, R = 100L, alpha = 0.05, seed = NULL )
engines |
Named list of functions. Each takes one argument - the
members of a perturbed analysis set, in whatever form |
set |
The full analysis set: a character vector of probe identifiers or an integer vector of column indices. |
focal |
The focal unit's own members, a subset of |
pool |
For |
form |
|
grid |
Perturbation sizes. For dropout, fractions of the non-focal
probes to remove; defaults to |
R |
Replicates per grid point. Default 100. The reported quantity is a spread, so this is the resolution of the answer. |
alpha |
Significance threshold used only to report whether a replicate's verdict differs from the unperturbed one. Default 0.05. |
seed |
Optional integer for reproducibility. The caller's RNG state is restored on exit. |
Two forms, both reported in the same way:
"dropout"Removes a random fraction of the non-focal probes. The omitted probes carry no focal evidence, so a finding that depends on them was never about its own unit.
"addition"Adds probes drawn from pool, which sits
outside the analysis set. This is the direction a competitive engine is
most sensitive to, because added probes enter its background.
The two forms move competitive and whole-set engines in opposite directions, which is why running only one of them understates the problem.
A data.frame with one row per engine, grid point and
replicate: engine, form, size (the fraction dropped or
the count added), rep, p, and flip - whether that
replicate's verdict at alpha differs from the unperturbed verdict.
The unperturbed run is included as size = 0, rep = 0. The
baseline p-value per engine is attached as the "baseline" attribute.
set.seed(1) n <- 120L; K <- 40L f <- rnorm(n) M <- sapply(seq_len(K), function(j) if (j <= 6) 0.7 * f + rnorm(n) else rnorm(n)) colnames(M) <- sprintf("cg%04d", seq_len(K)) y <- 0.6 * f + rnorm(n) focal <- colnames(M)[1:6] # Two engines on identical evidence. The competitive one scores the focal # unit against whatever else happens to be loaded; the unit-only one uses # the focal probes and nothing else. competitive <- function(u) { rest <- setdiff(u, focal) if (length(rest) < 2) return(NA_real_) t.test(abs(cor(M[, focal], y)), abs(cor(M[, rest], y)))$p.value } unit_only <- function(u) { summary(lm(rowMeans(M[, focal]) ~ y))$coefficients["y", 4] } pd <- dmsa_pdance(list(competitive = competitive, unit_only = unit_only), set = colnames(M), focal = focal, grid = c(0.1, 0.3, 0.5), R = 20, seed = 1) # Both start from the same evidence, and it is never touched. round(attr(pd, "baseline"), 4) # How far each engine's p travels when only the OTHER probes change. round(tapply(pd$p, pd$engine, function(x) diff(range(x, na.rm = TRUE))), 4) # And how often the verdict at .05 reverses. The unit-only engine cannot # move, because nothing it reads has changed. tapply(pd$flip, pd$engine, sum)set.seed(1) n <- 120L; K <- 40L f <- rnorm(n) M <- sapply(seq_len(K), function(j) if (j <= 6) 0.7 * f + rnorm(n) else rnorm(n)) colnames(M) <- sprintf("cg%04d", seq_len(K)) y <- 0.6 * f + rnorm(n) focal <- colnames(M)[1:6] # Two engines on identical evidence. The competitive one scores the focal # unit against whatever else happens to be loaded; the unit-only one uses # the focal probes and nothing else. competitive <- function(u) { rest <- setdiff(u, focal) if (length(rest) < 2) return(NA_real_) t.test(abs(cor(M[, focal], y)), abs(cor(M[, rest], y)))$p.value } unit_only <- function(u) { summary(lm(rowMeans(M[, focal]) ~ y))$coefficients["y", 4] } pd <- dmsa_pdance(list(competitive = competitive, unit_only = unit_only), set = colnames(M), focal = focal, grid = c(0.1, 0.3, 0.5), R = 20, seed = 1) # Both start from the same evidence, and it is never touched. round(attr(pd, "baseline"), 4) # How far each engine's p travels when only the OTHER probes change. round(tapply(pd$p, pd$engine, function(x) diff(range(x, na.rm = TRUE))), 4) # And how often the verdict at .05 reverses. The unit-only engine cannot # move, because nothing it reads has changed. tapply(pd$flip, pd$engine, sum)
Permutation p-value for a DMSA statistic
dmsa_perm_pvalue(observed, null_stats)dmsa_perm_pvalue(observed, null_stats)
observed |
The observed statistic (e.g. z from |
null_stats |
Vector of the same statistic computed on B
family-wise-permuted datasets (shuffle the exposure BY FAMILY, refit
the probe models with the SAME estimator, re-run |
two-sided permutation p with the +1 correction (resolution floor 1/(B+1)).
set.seed(1) null_z <- rnorm(999) # z from B family-wise permuted datasets dmsa_perm_pvalue(3.1, null_z) ## the +1 correction puts a floor of 1/(B+1) on any permutation p dmsa_perm_pvalue(50, null_z)set.seed(1) null_z <- rnorm(999) # z from B family-wise permuted datasets dmsa_perm_pvalue(3.1, null_z) ## the +1 correction puts a floor of 1/(B+1) on any permutation p dmsa_perm_pvalue(50, null_z)
Panel 2: gene level inside one system
dmsa_plot_genes( genes, null_max = NULL, title = "", alpha = 0.05, file = NULL, width = 1500, height = 1500, res = 190 )dmsa_plot_genes( genes, null_max = NULL, title = "", alpha = 0.05, file = NULL, width = 1500, height = 1500, res = 190 )
genes |
data.frame for ONE system and outcome: |
null_max |
Optional numeric vector of the permutation null of
|
title |
System label. |
alpha |
Gate level. The gate is |
file |
Optional png path. If |
width, height, res
|
png dimensions. |
Called for its side effect of drawing a plot on the active
graphics device. Returns NULL invisibly.
set.seed(1) genes <- data.frame(gene = paste0("G", 1:8), n_probes = sample(3:9, 8, TRUE), z = c(rnorm(7), 3.6)) genes$p_adj <- 2 * pnorm(-abs(genes$z)) ## the permutation null of max|z| over this same family of genes null_max <- apply(matrix(rnorm(200 * 8), 200, 8), 1, function(r) max(abs(r))) dmsa_plot_genes(genes, null_max = null_max, title = "HPA axis", file = tempfile(fileext = ".png"))set.seed(1) genes <- data.frame(gene = paste0("G", 1:8), n_probes = sample(3:9, 8, TRUE), z = c(rnorm(7), 3.6)) genes$p_adj <- 2 * pnorm(-abs(genes$z)) ## the permutation null of max|z| over this same family of genes null_max <- apply(matrix(rnorm(200 * 8), 200, 8), 1, function(r) max(abs(r))) dmsa_plot_genes(genes, null_max = null_max, title = "HPA axis", file = tempfile(fileext = ".png"))
Genomic coordinates are optional. Supply pos and the CpGs are placed
to scale and the chromosome strip is drawn; supply gene_region instead
and they are ordered along the gene (TSS1500 to 3'UTR) and spaced evenly;
supply neither and they keep the order you gave. The axis states which of
the three you got. Nothing else in the figure changes.
dmsa_plot_locus( probes, gene = "", chrom = NA, gene_start = NA, gene_end = NA, chrom_length = NA, signal_p = 0.05, invert = c("+1", "-1", "none"), order_by = c("auto", "pos", "region", "given"), gene_model = NULL, transcripts = c("canonical", "all"), context = "", file = NULL, width = 1800, height = NULL, res = 190 )dmsa_plot_locus( probes, gene = "", chrom = NA, gene_start = NA, gene_end = NA, chrom_length = NA, signal_p = 0.05, invert = c("+1", "-1", "none"), order_by = c("auto", "pos", "region", "given"), gene_model = NULL, transcripts = c("canonical", "all"), context = "", file = NULL, width = 1800, height = NULL, res = 190 )
probes |
data.frame for ONE gene. Required: |
gene |
Gene symbol, for labelling. |
chrom |
Chromosome, e.g. |
gene_start, gene_end
|
Gene span in the same coordinate system as
|
chrom_length |
Length of the chromosome. Defaults to a built-in hg19 table; pass your annotation's value to be sure. Strip A is omitted when neither is available. |
signal_p |
Probes with |
invert |
Which |
order_by |
|
gene_model |
Real exon, UTR and strand structure to draw under the
probes. A |
transcripts |
|
context |
One line of context drawn under the title - in a DMSA report this is the outcome the effects belong to. Without it the panel names a gene and a chromosome but never says what strip C's effects are effects OF, which is not recoverable from the figure. Empty string draws nothing. |
file |
Optional png path. |
width, height, res
|
png dimensions. |
Invisibly, the plotted data frame, with the columns the figure
actually used (x, sig, shown, flipped).
dmsa_gene_model to build the model,
dmsa_plot_locus_gviz for the Gviz engine,
dmsa_probe_coords for probe positions.
probes <- data.frame(probe = paste0("cg", 1:5), chr = "chr20", pos = c(3082600, 3082750, 3083050, 3084600, 3084700), b = c(-.04, .02, .03, -.05, -.06), se = rep(.015, 5), d = c(-1, 1, 1, -1, -1), p = c(.01, .3, .04, .002, .001)) ## invert = "+1" (the default) reflects the d = +1 probes, so a positive ## effect always means "moved methylation the way that LOWERS expression" out <- dmsa_plot_locus(probes, gene = "AVP", context = "attachment anxiety", file = tempfile(fileext = ".png")) out[, c("probe", "d", "shown", "flipped")]probes <- data.frame(probe = paste0("cg", 1:5), chr = "chr20", pos = c(3082600, 3082750, 3083050, 3084600, 3084700), b = c(-.04, .02, .03, -.05, -.06), se = rep(.015, 5), d = c(-1, 1, 1, -1, -1), p = c(.01, .3, .04, .002, .001)) ## invert = "+1" (the default) reflects the d = +1 probes, so a positive ## effect always means "moved methylation the way that LOWERS expression" out <- dmsa_plot_locus(probes, gene = "AVP", context = "attachment anxiety", file = tempfile(fileext = ".png")) out[, c("probe", "d", "shown", "flipped")]
The same probes and the same gene model as dmsa_plot_locus,
handed to Gviz instead of drawn here. Use this when you want what Gviz
gives and this package does not: a real cytoband ideogram, transcript
collapsing, and the ability to stack any other GdObject you already
build - CpG islands, ATAC peaks, ChIP tracks, conservation - in the same
coordinate frame.
dmsa_plot_locus_gviz( probes, gene = "", gene_model = NULL, chrom = NA, genome = "hg38", extra = list(), ideogram = FALSE, file = NULL, width = 1800, height = 1100, res = 190 )dmsa_plot_locus_gviz( probes, gene = "", gene_model = NULL, chrom = NA, genome = "hg38", extra = list(), ideogram = FALSE, file = NULL, width = 1800, height = 1100, res = 190 )
probes |
Data frame with |
gene |
Gene symbol, used for the title and the model lookup. |
gene_model |
A |
chrom |
Chromosome, e.g. |
genome |
Genome label passed to Gviz, default |
extra |
A list of further Gviz tracks to stack under the model. |
ideogram |
Draw the cytoband ideogram. Needs network on first use, as Gviz fetches the band table from UCSC. |
file |
Optional PNG path. |
width, height, res
|
PNG geometry. |
Gviz, GenomicRanges and IRanges are Suggests, not dependencies. This function errors with the install line if they are absent; everything else in the package keeps working without them.
Invisibly, the list of tracks plotted.
dmsa_plot_locus for the dependency-free panel.
## Not run: gm <- dmsa_gene_model("NR3C1") dmsa_plot_locus_gviz(pr, gene = "NR3C1", gene_model = gm) ## End(Not run)## Not run: gm <- dmsa_gene_model("NR3C1") dmsa_plot_locus_gviz(pr, gene = "NR3C1", gene_model = gm) ## End(Not run)
The panel that declares a direction. Each probe's slope is reflected
according to its cpgdirection call so that every probe is read on one
axis; reflected probes are drawn as open symbols and the convention is
printed under the title.
dmsa_plot_probes( probes, invert = c("+1", "-1", "none"), gene_summary = NULL, title = "", axis_label = NULL, alpha = 0.05, file = NULL, width = 1700, height = 1400, res = 190 )dmsa_plot_probes( probes, invert = c("+1", "-1", "none"), gene_summary = NULL, title = "", axis_label = NULL, alpha = 0.05, file = NULL, width = 1700, height = 1400, res = 190 )
probes |
data.frame for ONE system and outcome, restricted to the genes
worth showing: |
invert |
Which With |
gene_summary |
Optional data.frame with |
title |
System label. |
axis_label |
Overrides the x-axis label. |
alpha |
Gate level. The gate is |
file |
Optional png path. If |
width, height, res
|
png dimensions. |
Called for its side effect of drawing a plot on the active
graphics device. Returns NULL invisibly.
## Every probe is read on one axis. A CpG whose methylation predicts HIGHER ## expression (d = +1) is reflected, so positive always means the exposure ## moved methylation the way that LOWERS this gene's expression. pr <- data.frame( gene = "NR3C1", probe = paste0("cg", 10001:10006), b = c(-0.021, -0.014, 0.018, -0.009, 0.026, -0.017), se = c(0.008, 0.007, 0.009, 0.006, 0.010, 0.008), d = c(-1, -1, 1, -1, 1, -1)) dmsa_plot_probes(pr, gene_summary = data.frame(gene = "NR3C1", z = -2.9), title = "HPA axis", file = tempfile(fileext = ".png"))## Every probe is read on one axis. A CpG whose methylation predicts HIGHER ## expression (d = +1) is reflected, so positive always means the exposure ## moved methylation the way that LOWERS this gene's expression. pr <- data.frame( gene = "NR3C1", probe = paste0("cg", 10001:10006), b = c(-0.021, -0.014, 0.018, -0.009, 0.026, -0.017), se = c(0.008, 0.007, 0.009, 0.006, 0.010, 0.008), d = c(-1, -1, 1, -1, 1, -1)) dmsa_plot_probes(pr, gene_summary = data.frame(gene = "NR3C1", z = -2.9), title = "HPA axis", file = tempfile(fileext = ".png"))
Distance from the diagonal is how concentrated a system's response is: a system null on the pooled arm and strong on the max-gene arm carries its signal in one gene.
dmsa_plot_systems( systems, alpha = 0.05, file = NULL, width = 1500, height = 1400, res = 190 )dmsa_plot_systems( systems, alpha = 0.05, file = NULL, width = 1500, height = 1400, res = 190 )
systems |
data.frame with columns |
alpha |
Gate level. The gate is |
file |
Optional png path. If |
width, height, res
|
png dimensions. |
Invisibly, the plotted data frame.
## Distance from the diagonal says how concentrated a system's response is: ## strong on the sparse arm only means the signal sits in a single gene. sys <- data.frame( system = c("HPA axis", "Oxytocin", "Serotonin", "Inflammation"), outcome = "PTSD severity", p_dense = c(0.0004, 0.08, 0.41, 0.62), p_sparse = c(0.0012, 0.0003, 0.33, 0.55), primary = c(TRUE, TRUE, FALSE, FALSE), top_gene = c("NR3C1", "OXTR", "TPH2", "IL6")) dmsa_plot_systems(sys, file = tempfile(fileext = ".png"))## Distance from the diagonal says how concentrated a system's response is: ## strong on the sparse arm only means the signal sits in a single gene. sys <- data.frame( system = c("HPA axis", "Oxytocin", "Serotonin", "Inflammation"), outcome = "PTSD severity", p_dense = c(0.0004, 0.08, 0.41, 0.62), p_sparse = c(0.0012, 0.0003, 0.33, 0.55), primary = c(TRUE, TRUE, FALSE, FALSE), top_gene = c("NR3C1", "OXTR", "TPH2", "IL6")) dmsa_plot_systems(sys, file = tempfile(fileext = ".png"))
Load a gene-to-system polarity table
dmsa_polarity(x = "alpha", sets = NULL)dmsa_polarity(x = "alpha", sets = NULL)
x |
|
sets |
Optional |
Signs are read against the system's declared activation tone, not
against a gene's immediate partner. A gene that inhibits a brake therefore
carries w_g = +1: FKBP5 restrains GR, GR mediates HPA negative
feedback, so more FKBP5 means more axis drive. The role vocabulary
names this explicitly (brake-of-brake,
feedback-enabler) because it is the error that inverts a module.
An object of class dmsa_polarity, or NULL.
dmsa_polarity_review() for the rows needing adjudication,
dmsa_polarity_fetch() to draft signs for a panel of your own,
dmsa_polarity_check() to validate a candidate table.
pol <- dmsa_polarity() pol dmsa_polarity_review(pol)pol <- dmsa_polarity() pol dmsa_polarity_review(pol)
Validate a candidate polarity table
dmsa_polarity_check(x = "alpha", sets = NULL, verbose = TRUE)dmsa_polarity_check(x = "alpha", sets = NULL, verbose = TRUE)
x |
Path, |
sets |
Optional cascade to check coverage against. |
verbose |
Print the report. Default |
Invisibly, a list with ok and one entry per check.
## An anchor gene is what defines "more activation" for its system, so an ## anchor that brakes its own system is a contradiction, not a small error. cand <- data.frame(system_id = "HPA", gene = c("CRH", "POMC", "NR3C1"), w_g = c(-1, 1, -1), anchor = c(TRUE, FALSE, FALSE), w_g_source = "curated") chk <- dmsa_polarity_check(cand) chk$ok dmsa_polarity_check("alpha", verbose = FALSE)$ok## An anchor gene is what defines "more activation" for its system, so an ## anchor that brakes its own system is a contradiction, not a small error. cand <- data.frame(system_id = "HPA", gene = c("CRH", "POMC", "NR3C1"), w_g = c(-1, 1, -1), anchor = c(TRUE, FALSE, FALSE), w_g_source = "curated") chk <- dmsa_polarity_check(cand) chk$ok dmsa_polarity_check("alpha", verbose = FALSE)$ok
Queries public signed-relationship resources for the genes of a cascade and returns a DRAFT polarity table for a curator to check. It does not decide anything on its own, and it is deliberately unwilling to guess.
dmsa_polarity_fetch( sets, anchors = NULL, sources = c("go", "omnipath"), max_genes = 200L, quiet = FALSE )dmsa_polarity_fetch( sets, anchors = NULL, sources = c("go", "omnipath"), max_genes = 200L, quiet = FALSE )
sets |
A |
anchors |
Named list mapping |
sources |
Which resources to query, in order. See details. |
max_genes |
Stop after this many genes. Drafting is one HTTP call per
gene for |
quiet |
Suppress progress messages. |
What each resource can and cannot do is worth knowing before you rely on it.
go is the only one that gives a gene-to-process sign directly, through
positive/negative regulation of X terms, so it needs no path chaining -
but its coverage is uneven gene to gene. signor carries explicit
gene-to-phenotype rows, with a cell-biological vocabulary that fits metabolic
and immune systems better than behavioural ones. omnipath has the
broadest coverage and the weakest signs: CollecTRI and DoRothEA, two of its
inputs, both document assigning an activating mode BY DEFAULT when they do not
know, so a consensus_stimulation at curation_effort = 1 should be
read as sign-unknown. trrust is the most honest transcription-factor
resource because it emits Unknown rather than guessing.
Nothing returned here is a polarity. It is a proposal, and the
needs_review column is TRUE for every row.
A data.frame of drafted rows with w_g, w_g_source,
evidence, citation and needs_review, plus an
attr(, "unreachable") listing resources that did not respond.
## Not run: ## Needs network access: each gene is one HTTP call to the chosen resources. sel <- dmsa_select(systems = "hpa") draft <- dmsa_polarity_fetch(sel, anchors = list("2" = c("CRH", "POMC"))) utils::write.csv(draft, file.path(tempdir(), "polarity_draft.csv"), row.names = FALSE) ## End(Not run)## Not run: ## Needs network access: each gene is one HTTP call to the chosen resources. sel <- dmsa_select(systems = "hpa") draft <- dmsa_polarity_fetch(sel, anchors = list("2" = c("CRH", "POMC"))) utils::write.csv(draft, file.path(tempdir(), "polarity_draft.csv"), row.names = FALSE) ## End(Not run)
Polarity table for one system, in the form dmsa_align() wants
dmsa_polarity_for(reference, system_id)dmsa_polarity_for(reference, system_id)
reference |
A |
system_id |
The system to extract. |
data.frame with gene and w_g, or NULL if the bundle
carries no polarity.
f <- tempfile(fileext = ".csv") dmsa_reference_template(f) ref <- dmsa_reference_csv(f, quiet = TRUE) ## w_g is the gene's sign toward its system's activation tone: +1 raises it, ## -1 opposes it. dmsa_align() multiplies w_g by each CpG's ## methylation-to-expression direction to get the probe's aligned sign. dmsa_polarity_for(ref, "HPA axis")f <- tempfile(fileext = ".csv") dmsa_reference_template(f) ref <- dmsa_reference_csv(f, quiet = TRUE) ## w_g is the gene's sign toward its system's activation tone: +1 raises it, ## -1 opposes it. dmsa_align() multiplies w_g by each CpG's ## methylation-to-expression direction to get the probe's aligned sign. dmsa_polarity_for(ref, "HPA axis")
Three kinds of row should never be used without someone looking at them: a sign the curator and a database disagree about, a sign no evidence could settle, and a non-zero sign held at low confidence. This returns them.
dmsa_polarity_review( x = "alpha", which = c("all", "disagreement", "unresolved", "low_confidence") )dmsa_polarity_review( x = "alpha", which = c("all", "disagreement", "unresolved", "low_confidence") )
x |
A |
which |
|
data.frame of class dmsa_polarity_review.
## The three kinds of row nobody should use unseen: a database contradiction, ## a sign no evidence settles, and a non-zero sign held at low confidence. flagged <- dmsa_polarity_review(dmsa_select(systems = "hpa")) table(flagged$review_flag) ## a disagreement is a framing call the analyst has to make, not a bug dmsa_polarity_review(which = "disagreement")$gene## The three kinds of row nobody should use unseen: a database contradiction, ## a sign no evidence settles, and a non-zero sign held at low confidence. flagged <- dmsa_polarity_review(dmsa_select(systems = "hpa")) table(flagged$review_flag) ## a disagreement is a framing call the analyst has to make, not a bug dmsa_polarity_review(which = "disagreement")$gene
Which public resources DMSA can draft polarity from, and what each is good for
dmsa_polarity_sources()dmsa_polarity_sources()
data.frame of resource name, endpoint template and caveat.
src <- dmsa_polarity_sources() src[c("source", "resource")] ## the trap worth knowing about: two of OmniPath's inputs record an ## activating sign by default when the direction is actually unknown src$caveat[src$source == "omnipath"]src <- dmsa_polarity_sources() src[c("source", "resource")] ## the trap worth knowing about: two of OmniPath's inputs record an ## activating sign by default when the direction is actually unknown src$caveat[src$source == "omnipath"]
The heavyweight route, kept for people who already have the annotation
packages installed. dmsa_probe_coords does the same job from a
plain-text manifest with base R only, and
dmsa_plot_locus needs no coordinates at all.
dmsa_probe_annotation_template( path = "get_probe_coords.R", array = c("EPIC", "450K") )dmsa_probe_annotation_template( path = "get_probe_coords.R", array = c("EPIC", "450K") )
path |
Where to write the script. |
array |
|
Run the written script once; it produces a csv with probe, chr, pos,
gene_region, island that dmsa_plot_locus() consumes.
path, invisibly.
f <- tempfile(fileext = ".R") dmsa_probe_annotation_template(f, array = "EPIC") ## the script is not run here: it installs and queries the minfi annotation ## packages. dmsa_probe_coords() does the same job from a plain manifest. head(readLines(f), 5)f <- tempfile(fileext = ".R") dmsa_probe_annotation_template(f, array = "EPIC") ## the script is not run here: it installs and queries the minfi annotation ## packages. dmsa_probe_coords() does the same job from a plain manifest. head(readLines(f), 5)
Called with probe ids alone, reads them from
cpgdirection::cpgd_cpg_positions() - the same package the
cpgdirection calls d come from, so nothing new is installed.
Given file or url instead, reads any delimited manifest with
base R. No annotation packages either way.
dmsa_probe_coords( probes, file = NULL, url = NULL, cache = NULL, probe_col = c("probeID", "Probe_ID", "probe", "IlmnID", "Name", "cpg", "cpg_id"), chr_col = c("CpG_chrm", "chrm", "chr", "CHR", "seqnames", "chromosome"), pos_col = c("CpG_beg", "start", "pos", "MAPINFO", "position", "CpG_end") )dmsa_probe_coords( probes, file = NULL, url = NULL, cache = NULL, probe_col = c("probeID", "Probe_ID", "probe", "IlmnID", "Name", "cpg", "cpg_id"), chr_col = c("CpG_chrm", "chrm", "chr", "CHR", "seqnames", "chromosome"), pos_col = c("CpG_beg", "start", "pos", "MAPINFO", "position", "CpG_end") )
probes |
Character vector of probe ids to look up. |
file |
Path to a local manifest (plain text or |
url |
URL of a plain-text manifest to download once. Public Infinium manifests in tsv form are published by the Zhou lab (https://zwdzwd.github.io/InfiniumAnnotation); any file with a probe column and chromosome and position columns will do. |
cache |
Directory to keep the downloaded manifest in, so the download
happens once. Defaults to |
probe_col, chr_col, pos_col
|
Column names in the manifest. Defaults cover the common spellings; if they are not found the function lists the columns it did find rather than guessing. |
data.frame with probe, chr, pos, restricted to
probes and in that order. Probes not found come back with
NA, which dmsa_plot_locus() handles.
dmsa_plot_locus, which needs none of this.
## Not run: co <- dmsa_probe_coords(my_probes) # via cpgdirection co <- dmsa_probe_coords(my_probes, file = "EPIC.hg38.manifest.tsv.gz") dmsa_write_coords(co) # now a project asset ## End(Not run)## Not run: co <- dmsa_probe_coords(my_probes) # via cpgdirection co <- dmsa_probe_coords(my_probes, file = "EPIC.hg38.manifest.tsv.gz") dmsa_write_coords(co) # now a project asset ## End(Not run)
Construct a DMSA reference bundle
dmsa_reference( systems, polarity = NULL, anchors = NULL, anchor_method = c("curated", "graph_sink", "user", "none"), name = "unnamed", version = NA_character_, notes = NULL )dmsa_reference( systems, polarity = NULL, anchors = NULL, anchor_method = c("curated", "graph_sink", "user", "none"), name = "unnamed", version = NA_character_, notes = NULL )
systems |
data.frame with columns |
polarity |
data.frame with columns |
anchors |
Optional data.frame with |
anchor_method |
Character, how the anchors were obtained. One of
|
name, version, notes
|
Provenance, printed and stored. |
An object of class dmsa_reference.
## One row per gene-within-system, a sign per gene, and the anchor genes that ## fix which way "more activation" points for each system. sys <- data.frame(gene = c("CRH", "POMC", "NR3C1", "OXT", "OXTR"), system_id = c("1", "1", "1", "2", "2"), system = c(rep("HPA axis", 3), rep("Oxytocin", 2))) pol <- data.frame(gene = sys$gene, system_id = sys$system_id, w_g = c(1, 1, -1, 1, 0.6)) anc <- data.frame(system_id = c("1", "2"), gene = c("CRH", "OXT")) dmsa_reference(sys, pol, anc, anchor_method = "curated", name = "toy", version = "0.1")## One row per gene-within-system, a sign per gene, and the anchor genes that ## fix which way "more activation" points for each system. sys <- data.frame(gene = c("CRH", "POMC", "NR3C1", "OXT", "OXTR"), system_id = c("1", "1", "1", "2", "2"), system = c(rep("HPA axis", 3), rep("Oxytocin", 2))) pol <- data.frame(gene = sys$gene, system_id = sys$system_id, w_g = c(1, 1, -1, 1, 0.6)) anc <- data.frame(system_id = c("1", "2"), gene = c("CRH", "OXT")) dmsa_reference(sys, pol, anc, anchor_method = "curated", name = "toy", version = "0.1")
One flat file, one row per gene-within-module (or per gene-within-system if
there is no module layer). Column names are matched case- and
punctuation-insensitively, so System, system_id and
"System ID" are all accepted.
dmsa_reference_csv( path, name = NULL, version = NA_character_, notes = NULL, min_genes = 3L, quiet = FALSE )dmsa_reference_csv( path, name = NULL, version = NA_character_, notes = NULL, min_genes = 3L, quiet = FALSE )
path |
Path to the csv (anything |
name, version, notes
|
Provenance for the bundle; |
min_genes |
Systems with fewer genes than this are dropped with a message - a set of two genes is not a set test. Set to 1 to keep everything. |
quiet |
Suppress the summary message. |
Required: a system column (system, system_id,
pathway, set) and a gene column (gene, symbol,
gene_symbol).
Optional:
module (module, subsystem, submodule,
module_id) - adds the module level to the cascade.
w_g (wg, weight, polarity, sign) -
the gene's effect on system activation, -1/0/+1 or
continuous in [-1, 1]. Without it the bundle carries no polarity
and only gene-level DMSA is valid.
anchor (anchor, is_anchor, driver) - TRUE for the
genes that define what "more activation" means. Without it, anchors are
taken from role == "driver" if a role column exists, else inferred
from the most positive w_g with a warning.
role, confidence, source - carried through untouched.
A dmsa_reference.
f <- tempfile(fileext = ".csv") dmsa_reference_template(f) ref <- dmsa_reference_csv(f)f <- tempfile(fileext = ".csv") dmsa_reference_template(f) ref <- dmsa_reference_csv(f)
The format written by the bundle builder: systems.csv,
polarity.csv and optionally anchors.csv in one directory,
plus an optional manifest.dcf carrying name, version and notes.
dmsa_reference_read(dir, anchor_method = "user")dmsa_reference_read(dir, anchor_method = "user")
dir |
Directory holding the files. |
anchor_method |
Passed to |
A dmsa_reference.
## A bundle on disk is systems.csv, polarity.csv, anchors.csv and an optional ## manifest.dcf carrying the provenance, all in one directory. sys <- data.frame(gene = c("CRH", "POMC", "NR3C1"), system_id = "1", system = "HPA axis") pol <- data.frame(gene = sys$gene, system_id = "1", w_g = c(1, 1, -1)) d <- file.path(tempdir(), "hpa_bundle") dmsa_reference_write(dmsa_reference(sys, pol, anchor_method = "curated", name = "hpa-toy", version = "1"), d) list.files(d) dmsa_reference_read(d)## A bundle on disk is systems.csv, polarity.csv, anchors.csv and an optional ## manifest.dcf carrying the provenance, all in one directory. sys <- data.frame(gene = c("CRH", "POMC", "NR3C1"), system_id = "1", system = "HPA axis") pol <- data.frame(gene = sys$gene, system_id = "1", w_g = c(1, 1, -1)) d <- file.path(tempdir(), "hpa_bundle") dmsa_reference_write(dmsa_reference(sys, pol, anchor_method = "curated", name = "hpa-toy", version = "1"), d) list.files(d) dmsa_reference_read(d)
Produces a small, filled-in example with every accepted column, so the shape is unambiguous. Overwrite the rows with your own.
dmsa_reference_template(path, with_modules = TRUE)dmsa_reference_template(path, with_modules = TRUE)
path |
Where to write it. |
with_modules |
Include the module column. |
path, invisibly.
f <- tempfile(fileext = ".csv") dmsa_reference_template(f) head(utils::read.csv(f), 3) ## the filled-in rows show every accepted column; replace them with your own ## panel, then read the result back as a reference bundle dmsa_reference_csv(f, quiet = TRUE)f <- tempfile(fileext = ".csv") dmsa_reference_template(f) head(utils::read.csv(f), 3) ## the filled-in rows show every accepted column; replace them with your own ## panel, then read the result back as a reference bundle dmsa_reference_csv(f, quiet = TRUE)
The inverse of dmsa_reference_read(): useful for saving a bundle built
from a one-file csv, or a generated bundle you have edited.
dmsa_reference_write(reference, dir)dmsa_reference_write(reference, dir)
reference |
A |
dir |
Directory to create and write into. |
dir, invisibly.
f <- tempfile(fileext = ".csv") dmsa_reference_template(f) ref <- dmsa_reference_csv(f, quiet = TRUE) d <- tempfile() dmsa_reference_write(ref, d) list.files(d) nrow(dmsa_reference_read(d)$systems)f <- tempfile(fileext = ".csv") dmsa_reference_template(f) ref <- dmsa_reference_csv(f, quiet = TRUE) d <- tempfile() dmsa_reference_write(ref, d) list.files(d) nrow(dmsa_reference_read(d)$systems)
Item-rest correlation of each aligned probe with the rest of its unit,
computed from the (standardised, already winsorised) methylation matrix only.
Used internally by dmsa_triangulate, dmsa_scores
and dmsa_change when weighting = "reliability".
dmsa_relweights( Z, units, mlt, w_floor = 1.5, weighting = c("reliability", "flat") )dmsa_relweights( Z, units, mlt, w_floor = 1.5, weighting = c("reliability", "flat") )
Z |
Numeric matrix, samples x probes, standardised methylation (the same matrix the lenses see; outcome-free). |
units |
Character/label vector, one per column of |
mlt |
Numeric per-probe aligned multiplier (0 for unusable probes). |
w_floor |
Minimum leading eigenvalue of the aligned probes' correlation matrix for weighting to engage (independent probes give eigenvalues near 1; the default 1.5 asks for a shared factor stronger than sampling noise). Below it the unit is weighted flat. |
weighting |
|
Numeric vector of per-probe weights, length ncol(Z), mean 1
within each weighted unit and 1 everywhere a guard fired.
set.seed(2) n <- 120 f <- rnorm(n) ## three probes report the gene's shared axis well, three barely at all lam <- c(.8, .8, .8, .1, .1, .1) Z <- scale(sapply(lam, function(l) l * f + sqrt(1 - l^2) * rnorm(n))) ## weights come from methylation alone, so the permutation null is untouched round(dmsa_relweights(Z, units = rep("NR3C1", 6), mlt = rep(1, 6)), 2)set.seed(2) n <- 120 f <- rnorm(n) ## three probes report the gene's shared axis well, three barely at all lam <- c(.8, .8, .8, .1, .1, .1) Z <- scale(sapply(lam, function(l) l * f + sqrt(1 - l^2) * rnorm(n))) ## weights come from methylation alone, so the permutation null is untouched round(dmsa_relweights(Z, units = rep("NR3C1", 6), mlt = rep(1, 6)), 2)
Call 2 of the two-call interface. Runs each requested level x outcome with
the locked statistics, applies correction ONLY within level-local families,
runs the always-on brain-bridge post-hoc on surviving probes, and writes
everything under frame$outdir.
dmsa_report(frame, progress = NULL, beep = NULL, overwrite = FALSE)dmsa_report(frame, progress = NULL, beep = NULL, overwrite = FALSE)
frame |
a |
progress |
Show a 0-100
value declared on the frame, which itself defaults to |
beep |
Completion sound. |
overwrite |
Replace an existing report in 'outdir'? Default FALSE: a directory that already holds DMSA output is refused rather than silently overwritten. |
object of class dmsa_report: file paths and result tables,
invisibly printable.
set.seed(1) map <- data.frame(gene = rep(c("NR3C1", "FKBP5"), each = 2), system_id = 1L, system = "HPA axis", probe = paste0("cg0", 1:4), column = paste0("cg0", 1:4), best_direction = c(-1, 1, -1, 1), p_plus = c(.1, .9, .1, .9)) dat <- data.frame(anx = rnorm(40), cov1 = rnorm(40), cID = rep(1:20, each = 2)) ## methylation tracks anxiety in each probe's own expression direction for (i in 1:4) dat[[map$column[i]]] <- plogis(rnorm(40) + .6 * dat$anx * map$best_direction[i]) ## everything lands under outdir; figures and tables are switched off here ## only to keep the example quick fr <- dmsa_frame(dat, map = map, outcome = "anx", covariates = "cov1", B = 49, seed = 1, outdir = tempfile(), plots = FALSE, tables = FALSE) r <- dmsa_report(fr) r$results[, c("level", "unit", "n_probes", "p_omnibus")]set.seed(1) map <- data.frame(gene = rep(c("NR3C1", "FKBP5"), each = 2), system_id = 1L, system = "HPA axis", probe = paste0("cg0", 1:4), column = paste0("cg0", 1:4), best_direction = c(-1, 1, -1, 1), p_plus = c(.1, .9, .1, .9)) dat <- data.frame(anx = rnorm(40), cov1 = rnorm(40), cID = rep(1:20, each = 2)) ## methylation tracks anxiety in each probe's own expression direction for (i in 1:4) dat[[map$column[i]]] <- plogis(rnorm(40) + .6 * dat$anx * map$best_direction[i]) ## everything lands under outdir; figures and tables are switched off here ## only to keep the example quick fr <- dmsa_frame(dat, map = map, outcome = "anx", covariates = "cov1", B = 49, seed = 1, outdir = tempfile(), plots = FALSE, tables = FALSE) r <- dmsa_report(fr) r$results[, c("level", "unit", "n_probes", "p_omnibus")]
Build the whole report: panel 1 once, panels 2 and 3 per firing system
dmsa_report_panels( systems, genes, probes, nulls = NULL, outdir = ".", alpha = 0.05, invert = "+1", genes_shown = c("selected", "top", "all") )dmsa_report_panels( systems, genes, probes, nulls = NULL, outdir = ".", alpha = 0.05, invert = "+1", genes_shown = c("selected", "top", "all") )
systems, genes, probes
|
Data frames as described in the panel functions.
|
nulls |
Optional named list of permutation |
outdir |
Directory for the png files. |
alpha |
Gate level. The gate is |
invert |
Which With |
genes_shown |
Which genes go into panel 3: |
Invisibly, a data frame of the files written.
## one system fires, so it gets a gene panel and a probe panel of its own systems <- data.frame(system_id = 1:2, system = c("HPA axis", "Oxytocin"), outcome = "anx", p_dense = c(0.004, 0.40), p_sparse = c(0.02, 0.55), top_gene = c("NR3C1", "OXTR")) genes <- data.frame(system_id = 1L, outcome = "anx", gene = c("NR3C1", "FKBP5", "CRH"), z = c(3.4, -1.1, 0.6), n_probes = c(6L, 4L, 3L), p_adj = c(0.008, 0.51, 0.88), selected = c(TRUE, FALSE, FALSE)) probes <- data.frame(system_id = 1L, outcome = "anx", gene = "NR3C1", probe = paste0("cg", 1:6), se = rep(.08, 6), b = c(.21, .18, .09, -.14, -.20, .16), d = c(-1, -1, -1, 1, 1, -1)) out <- dmsa_report_panels(systems, genes, probes, outdir = tempfile()) out[, c("panel", "system_id", "outcome")]## one system fires, so it gets a gene panel and a probe panel of its own systems <- data.frame(system_id = 1:2, system = c("HPA axis", "Oxytocin"), outcome = "anx", p_dense = c(0.004, 0.40), p_sparse = c(0.02, 0.55), top_gene = c("NR3C1", "OXTR")) genes <- data.frame(system_id = 1L, outcome = "anx", gene = c("NR3C1", "FKBP5", "CRH"), z = c(3.4, -1.1, 0.6), n_probes = c(6L, 4L, 3L), p_adj = c(0.008, 0.51, 0.88), selected = c(TRUE, FALSE, FALSE)) probes <- data.frame(system_id = 1L, outcome = "anx", gene = "NR3C1", probe = paste0("cg", 1:6), se = rep(.08, 6), b = c(.21, .18, .09, -.14, -.20, .16), d = c(-1, -1, -1, 1, 1, -1)) out <- dmsa_report_panels(systems, genes, probes, outdir = tempfile()) out[, c("panel", "system_id", "outcome")]
One row per CpG x gene pair that entered the analysis: exactly the probes
dmsa_frame() selected after mapping, probe QC, replicate collapse
and any cpgs_include/cpgs_exclude filter. Each row carries
the annotation the frame resolved for it - gene, system, module, the
direction call with its evidence tier - plus genomic coordinates when the
bundled cascade covers the probe. Intended for the supplement of a paper
and for user validation; the same information, plus every pair that was
not used and why, is in the pair ledger
(frame$pair_ledger, written by the report as
tables/cpg_gene_pair_ledger.csv).
dmsa_save_analysis_set(frame, file = "analysis_set.csv")dmsa_save_analysis_set(frame, file = "analysis_set.csv")
frame |
A |
file |
Path of the CSV to write, or |
A report run writes this file automatically as
tables/analysis_set.csv; call this yourself to write it anywhere
else, or with file = NULL to get the table without writing.
The analysis-set data frame, invisibly when file is given.
set.seed(1) map <- data.frame(gene = "NR3C1", system_id = 1L, system = "HPA axis", probe = c("cg01", "cg02"), column = c("cg01", "cg02"), best_direction = c(-1, 1), p_plus = c(.1, .9)) dat <- data.frame(anx = rnorm(40), cov1 = rnorm(40), cg01 = plogis(rnorm(40)), cg02 = plogis(rnorm(40))) fr <- dmsa_frame(dat, map = map, outcomes = "anx", covariates = "cov1", random_effects = NULL, B = 49, seed = 1, plots = FALSE, tables = FALSE, summary = FALSE, progress = FALSE, beep = FALSE, outdir = tempfile()) dmsa_save_analysis_set(fr, file = NULL)set.seed(1) map <- data.frame(gene = "NR3C1", system_id = 1L, system = "HPA axis", probe = c("cg01", "cg02"), column = c("cg01", "cg02"), best_direction = c(-1, 1), p_plus = c(.1, .9)) dat <- data.frame(anx = rnorm(40), cov1 = rnorm(40), cg01 = plogis(rnorm(40)), cg02 = plogis(rnorm(40))) fr <- dmsa_frame(dat, map = map, outcomes = "anx", covariates = "cov1", random_effects = NULL, B = 49, seed = 1, plots = FALSE, tables = FALSE, summary = FALSE, progress = FALSE, beep = FALSE, outdir = tempfile()) dmsa_save_analysis_set(fr, file = NULL)
Weighted mean of within-probe standardised M-values, each weighted by its
aligned multiplier (s_j for fixed-sign, E[s_j] for
expected-sign), then standardised. Positive = the set reads as higher
system activation tone.
dmsa_score( data, probes, alignment, method = c("expected", "fixed"), beta_input = TRUE )dmsa_score( data, probes, alignment, method = c("expected", "fixed"), beta_input = TRUE )
data |
data.frame. |
probes |
methylation columns, in the row order of |
alignment |
a |
method |
|
beta_input |
TRUE if |
numeric vector, one value per row of data (NA where the
probes are missing).
set.seed(1) n <- 80 d <- rep(c(1, -1), each = 3) # methylation-to-expression direction f <- rnorm(n) # the tone the gene actually tracks probes <- paste0("cg", 1:6) dat <- as.data.frame(sapply(d, function(dj) plogis(dj * .8 * f + rnorm(n)))) names(dat) <- probes al <- dmsa_align(data.frame(cpg = probes, d = d, p_plus = ifelse(d > 0, .9, .1)), genes = rep("FKBP5", 6), level = "gene") ## one standardised tone per subject, despite the probes opposing each other dat$tone <- dmsa_score(dat, probes, al) round(cor(dat$tone, f), 2)set.seed(1) n <- 80 d <- rep(c(1, -1), each = 3) # methylation-to-expression direction f <- rnorm(n) # the tone the gene actually tracks probes <- paste0("cg", 1:6) dat <- as.data.frame(sapply(d, function(dj) plogis(dj * .8 * f + rnorm(n)))) names(dat) <- probes al <- dmsa_align(data.frame(cpg = probes, d = d, p_plus = ifelse(d > 0, .9, .1)), genes = rep("FKBP5", 6), level = "gene") ## one standardised tone per subject, despite the probes opposing each other dat$tone <- dmsa_score(dat, probes, al) round(cor(dat$tone, f), 2)
The aligned projection is dmsa_score()'s, restated for a matrix input
and with optional winsorisation. The other three are what competing methods
implicitly use, returned so that the comparison can be run rather than
argued.
dmsa_scores( M, alignment, method = c("expected", "fixed"), winsor = 3, flavours = c("aligned", "mean", "pc1", "blind"), weighting = c("combined", "reliability", "flat"), w_floor = 1.5 )dmsa_scores( M, alignment, method = c("expected", "fixed"), winsor = 3, flavours = c("aligned", "mean", "pc1", "blind"), weighting = c("combined", "reliability", "flat"), w_floor = 1.5 )
M |
Numeric matrix, samples x probes, on the analysis scale (M-values). Columns are standardised internally. |
alignment |
A |
method |
|
winsor |
Winsorise each probe at this many MADs first; |
flavours |
Which projections to return. |
weighting |
Character. Probe weighting engine within a unit: |
w_floor |
Numeric. Lower bound applied to reliability weights before normalisation, so a single poorly behaved probe cannot be driven to zero influence. Ignored when |
data.frame of standardised scores, one column per flavour.
dmsa_score for the single aligned vector on a
data.frame, dmsa_model for the upper-level model.
## Not run: al <- dmsa_align(dir_table, genes = gene, level = "gene") S <- dmsa_scores(M[, probes], al) dat$oxytocin <- S$aligned ## End(Not run)## Not run: al <- dmsa_align(dir_table, genes = gene, level = "gene") S <- dmsa_scores(M[, probes], al) dat$oxytocin <- S$aligned ## End(Not run)
DMSA models probes as the multivariate response, so it wants a
samples x probes matrix and a data.frame of phenotypes with
rows in the same order. A SummarizedExperiment stores the assay the
other way round - features x samples - with the phenotypes in
colData. This function performs that transposition once and returns
the two objects DMSA takes, so the orientation is not something every
analysis script has to get right on its own.
dmsa_se(se, assay = 1L, probes = NULL)dmsa_se(se, assay = 1L, probes = NULL)
se |
A |
assay |
Which assay to use: a name or a positive integer. Defaults to the first. On a methylation object this is normally the beta or M-value matrix; DMSA standardises probes before aggregating, so either scale works, but M-values are the better-behaved choice for linear models. |
probes |
Optional character vector restricting the probes returned, in the order given. Probes absent from the assay are an error, since a silently shortened panel changes which family a unit is corrected in. |
Probe names are taken from the assay's rownames and become the column names
of M, which is what dmsa_align() matches its direction calls
against. An assay with no rownames is an error rather than a warning: with
no probe identifiers there is nothing to align to, and a positional match
would be a guess.
A list with two elements: M, a samples x probes
numeric matrix, and data, a data.frame of the object's
colData with rows in the same order as M. Pass them to
dmsa_triangulate() or dmsa_frame().
if (requireNamespace("SummarizedExperiment", quietly = TRUE)) { set.seed(1) counts <- matrix(rnorm(20 * 30), nrow = 20, dimnames = list(sprintf("cg%04d", 1:20), NULL)) se <- SummarizedExperiment::SummarizedExperiment( assays = list(mval = counts), colData = data.frame(age = rnorm(30), sex = rep(c("F", "M"), 15))) parts <- dmsa_se(se) dim(parts$M) # samples x probes, the orientation DMSA wants head(parts$data, 3) }if (requireNamespace("SummarizedExperiment", quietly = TRUE)) { set.seed(1) counts <- matrix(rnorm(20 * 30), nrow = 20, dimnames = list(sprintf("cg%04d", 1:20), NULL)) se <- SummarizedExperiment::SummarizedExperiment( assays = list(mval = counts), colData = data.frame(age = rnorm(30), sex = rep(c("F", "M"), 15))) parts <- dmsa_se(se) dim(parts$M) # samples x probes, the orientation DMSA wants head(parts$data, 3) }
Choose one or more systems by short name. Everything below the system -
modules, genes, probes - defaults to "full", meaning every unit the
cascade assigns to the chosen system(s). Narrow a level only when the
question is genuinely narrower, and note that narrowing changes the declared
family and therefore the multiplicity toll.
dmsa_select( x = "alpha", systems = NULL, modules = "full", genes = "full", probes = "full", columns = NULL, data = NULL )dmsa_select( x = "alpha", systems = NULL, modules = "full", genes = "full", probes = "full", columns = NULL, data = NULL )
x |
A |
systems |
Short names ( |
modules, genes, probes
|
|
columns |
Which cascade column holds the data-matrix column names for
this analysis - e.g. |
data |
Optional data.frame or matrix used by |
An object of class dmsa_selection.
sel <- dmsa_select(systems = c("hpa", "oxytocin")) sel dmsa_select(systems = "hpa", modules = c("2.6", "2.8"))sel <- dmsa_select(systems = c("hpa", "oxytocin")) sel dmsa_select(systems = "hpa", modules = c("2.6", "2.8"))
The cascade declares the analysis structure DMSA corrects within. Loading it is separate from loading a direction map: the cascade says which probes form which unit, the direction map says which way each probe points.
dmsa_sets(x = "alpha", audit = NULL, polarity = NULL, name = NULL)dmsa_sets(x = "alpha", audit = NULL, polarity = NULL, name = NULL)
x |
Either |
audit |
Optional module-level evidence table: a path or
|
polarity |
Optional gene-to-system polarity table (path,
|
name |
Optional label, printed and carried into reports. |
An object of class dmsa_sets.
dmsa_systems() for the short names,
dmsa_select() to choose systems, dmsa_evidence() for the
module evidence table.
cas <- dmsa_sets() # bundled Alpha 2026c cas dmsa_systems() # the short names to use in `systems =`cas <- dmsa_sets() # bundled Alpha 2026c cas dmsa_systems() # the short names to use in `systems =`
Validate a candidate cascade before using it
dmsa_sets_check(x, verbose = TRUE)dmsa_sets_check(x, verbose = TRUE)
x |
Path, |
verbose |
Print the report. Default |
Invisibly, a list with ok and one entry per check.
sets <- data.frame( system_id = "1", system_short = "hpa", system = "HPA axis", module_id = "1.1", module = "Corticosteroid receptors", gene = c("NR3C1", "NR3C1", "FKBP5"), cpg = c("cg01234567", "cg07654321", "cg11223344")) ## a gene in two modules, or a duplicated row, would make the family ## ambiguous; missing module evidence is only a caveat chk <- dmsa_sets_check(sets) chk$oksets <- data.frame( system_id = "1", system_short = "hpa", system = "HPA axis", module_id = "1.1", module = "Corticosteroid receptors", gene = c("NR3C1", "NR3C1", "FKBP5"), cpg = c("cg01234567", "cg07654321", "cg11223344")) ## a gene in two modules, or a duplicated row, would make the family ## ambiguous; missing module evidence is only a caveat chk <- dmsa_sets_check(sets) chk$ok
Write a cascade template and the instructions for building one
dmsa_sets_template(path = "dmsa_sets_template.csv", example = TRUE)dmsa_sets_template(path = "dmsa_sets_template.csv", example = TRUE)
path |
Where to write the template CSV. The instructions are written
alongside it as a |
example |
Include two filled example rows. Default |
The paths written, invisibly.
p <- tempfile(fileext = ".csv") dmsa_sets_template(p) ## the .md alongside it states the rules the validator enforces basename(c(p, sub("\\.csv$", ".md", p))) utils::read.csv(p)[, c("system_short", "module_id", "gene", "cpg")]p <- tempfile(fileext = ".csv") dmsa_sets_template(p) ## the .md alongside it states the rules the validator enforces basename(c(p, sub("\\.csv$", ".md", p))) utils::read.csv(p)[, c("system_short", "module_id", "gene", "cpg")]
The systems available in a cascade, with the short names to select them by
dmsa_systems(x = "alpha", pattern = NULL)dmsa_systems(x = "alpha", pattern = NULL)
x |
A |
pattern |
Optional case-insensitive regular expression; matched against the short name, the full system name and the system id. |
A data.frame with system_id, system_short,
system and the module/gene/CpG counts. Printed as a table.
dmsa_systems() # all 30 dmsa_systems("stress|hpa") # searchdmsa_systems() # all 30 dmsa_systems("stress|hpa") # search
Pools probe-level coefficients after alignment. Two estimators:
"fixed" multiplies each b_j by the hard sign s_j; expected-sign
("expected") multiplies by E[s_j] = 2 P(s_j = +1) - 1, the
first-order latent-sign approximation - low-tier calls contribute in
proportion to their calibrated certainty, which is why no tier filtering
is needed upstream.
dmsa_test(b, se, alignment, method = c("expected", "fixed"), w = NULL)dmsa_test(b, se, alignment, method = c("expected", "fixed"), w = NULL)
b, se
|
Probe-level coefficient and SE from YOUR model (fitted on M-values standardised within probe; same estimator observed and null). |
alignment |
A |
method |
|
w |
Optional per-probe reliability weight (see
|
The z test here is descriptive; the reportable error control is the
family-wise permutation (dmsa_perm_pvalue) on the same statistic.
list: estimate (pooled aligned slope), se, z, p_normal, n_used, n_excluded, table (per-probe contributions).
## Two promoter CpGs in NR3C1: methylation silences the gene (d = -1), and ## GR is the HPA brake (w_g = -1), so both probes vote for HIGHER axis tone. calls <- data.frame(cpg = c("cg01", "cg02"), d = c(-1, -1), p_plus = c(.1, .2)) al <- dmsa_align(calls, genes = c("NR3C1", "NR3C1"), level = "system", polarity = data.frame(gene = "NR3C1", w_g = -1)) tt <- dmsa_test(b = c(0.28, 0.19), se = c(0.09, 0.08), al, method = "expected") tt[c("estimate", "se", "z", "p_normal", "n_used")] tt$table## Two promoter CpGs in NR3C1: methylation silences the gene (d = -1), and ## GR is the HPA brake (w_g = -1), so both probes vote for HIGHER axis tone. calls <- data.frame(cpg = c("cg01", "cg02"), d = c(-1, -1), p_plus = c(.1, .2)) al <- dmsa_align(calls, genes = c("NR3C1", "NR3C1"), level = "system", polarity = data.frame(gene = "NR3C1", w_g = -1)) tt <- dmsa_test(b = c(0.28, 0.19), se = c(0.09, 0.08), al, method = "expected") tt[c("estimate", "se", "z", "p_normal", "n_used")] tt$table
Re-run the frame's test drive
dmsa_test_drive(frame)dmsa_test_drive(frame)
frame |
a |
the frame's corrections table, invisibly (printed first).
set.seed(1) map <- data.frame(gene = "NR3C1", system_id = 1L, system = "HPA axis", probe = c("cg01", "cg02"), column = c("cg01", "cg02"), best_direction = c(-1, 1), p_plus = c(.1, .9)) dat <- data.frame(anx = rnorm(40), cov1 = rnorm(40), cID = rep(1:20, each = 2), cg01 = plogis(rnorm(40)), cg02 = plogis(rnorm(40))) fr <- dmsa_frame(dat, map = map, outcome = "anx", covariates = "cov1", B = 49, seed = 1, outdir = tempfile()) ## beta values were detected and converted; every such fix is on the record dmsa_test_drive(fr)set.seed(1) map <- data.frame(gene = "NR3C1", system_id = 1L, system = "HPA axis", probe = c("cg01", "cg02"), column = c("cg01", "cg02"), best_direction = c(-1, 1), p_plus = c(.1, .9)) dat <- data.frame(anx = rnorm(40), cov1 = rnorm(40), cID = rep(1:20, each = 2), cg01 = plogis(rnorm(40)), cg02 = plogis(rnorm(40))) fr <- dmsa_frame(dat, map = map, outcome = "anx", covariates = "cov1", B = 49, seed = 1, outdir = tempfile()) ## beta values were detected and converted; every such fix is on the record dmsa_test_drive(fr)
Returns the tree argument dmsa_cascade() expects: one row per
probe, columns ordered outermost first.
dmsa_tree(reference, genes, system_id = NULL, levels = NULL)dmsa_tree(reference, genes, system_id = NULL, levels = NULL)
reference |
A |
genes |
Character vector, the set-membership gene of each probe. |
system_id |
Optional: restrict to one system (a gene can sit in several, which would otherwise duplicate probes). |
levels |
Which levels to include, in order. Defaults to
|
data.frame with one row per probe. Probes whose gene is not in the
reference get NA and should be routed to the flat unannotated arm.
systems <- data.frame( gene = c("CRH", "NR3C1", "FKBP5", "OXT"), system_id = c("HPA", "HPA", "HPA", "OXT"), system = c("HPA axis", "HPA axis", "HPA axis", "Oxytocin"), module_id = c("HPA.a", "HPA.b", "HPA.b", "OXT.a"), module = c("Drive", "Receptors", "Receptors", "Ligand")) ref <- dmsa_reference(systems, anchor_method = "none") ## a probe whose gene is not in the bundle gets NA and goes to the flat arm dmsa_tree(ref, genes = c("CRH", "NR3C1", "FKBP5", "NOTINSET"), system_id = "HPA")systems <- data.frame( gene = c("CRH", "NR3C1", "FKBP5", "OXT"), system_id = c("HPA", "HPA", "HPA", "OXT"), system = c("HPA axis", "HPA axis", "HPA axis", "Oxytocin"), module_id = c("HPA.a", "HPA.b", "HPA.b", "OXT.a"), module = c("Drive", "Receptors", "Receptors", "Ligand")) ref <- dmsa_reference(systems, anchor_method = "none") ## a probe whose gene is not in the bundle gets NA and goes to the flat arm dmsa_tree(ref, genes = c("CRH", "NR3C1", "FKBP5", "NOTINSET"), system_id = "HPA")
Three complementary lenses on the same family
dmsa_triangulate( M, data, rhs, term, units, alignment, block = NULL, id = NULL, B = 1999, winsor = 3, method = c("expected", "fixed"), correction = c("maxT", "minP"), weighting = c("combined", "reliability", "flat"), w_floor = 1.5, seed = NULL, ri_group = NULL )dmsa_triangulate( M, data, rhs, term, units, alignment, block = NULL, id = NULL, B = 1999, winsor = 3, method = c("expected", "fixed"), correction = c("maxT", "minP"), weighting = c("combined", "reliability", "flat"), w_floor = 1.5, seed = NULL, ri_group = NULL )
M |
Numeric matrix, samples x probes, methylation on the analysis scale (probes are the responses). |
data |
data.frame of predictors, rows aligned to |
rhs |
Right-hand-side terms, e.g. |
term |
Design column to test, as |
units |
Character vector, one entry per column of |
alignment |
|
block |
Permutation blocks: a column name of |
id |
Optional participant identifier - a column name or per-row
vector. Purely a safety declaration: if any participant contributes more
than one row, |
B |
Permutations. |
winsor |
MADs for winsorisation; |
method |
Pooling method for |
correction |
|
weighting |
Character. Probe weighting engine within a unit: |
w_floor |
Numeric. Lower bound applied to reliability weights before normalisation, so a single poorly behaved probe cannot be driven to zero influence. Ignored when |
seed |
Optional integer. |
ri_group |
Optional grouping factor for a one-way random intercept,
one entry per row of |
data.frame, one row per unit: the raw and family-adjusted p under
each lens, the ACAT omnibus, the direction, and how many probes agreed.
p_union_exact is the second-level Westfall-Young minP calibration
of the any-lens rule: an EXACT family-wise p for the claim "some lens
carries this unit", computed by ranking the unit's smallest per-lens
family-adjusted p against the null distribution of the family-wide best
adjusted p (one value per permutation draw, min over units and lenses;
returned as attr(., "union_null_min")). It adapts to the measured
lens dependence - perfectly correlated lenses cost nothing extra,
independent ones the full threefold - and mean(union_null_min <
alpha) is the realized family-wise error of naming at the per-lens
adjusted-p threshold alpha. References: Westfall & Young (1993);
Ge, Dudoit & Speed (2003). p_omnibus_adj family-corrects the
ACAT omnibus the same way (permutation minP over each draw's per-unit
omnibus, attr(., "omnibus_null_min")): an exact family-wise p
that defends against BOTH multiplicities - the cross-lens combination
is absorbed by ACAT (Liu & Xie 2020), the cross-unit search by the
calibration - and is most powerful when all three lenses agree.
attr(., "probe_fits") is the per-probe fit the lenses were built
from (column, b, se of each standardised,
covariate-adjusted, random-intercept-transformed probe on the tested
term; its own attributes carry the residual df and the RI gamma), so a
figure can draw exactly the numbers the test used.
set.seed(1) n <- 100 ## half the probes raise expression when methylated, half lower it: on the ## methylation scale they cancel, on the expression scale they agree d <- rep(c(1, -1), each = 3) f <- rnorm(n) M <- sapply(d, function(dj) dj * .6 * f + sqrt(1 - .6^2) * rnorm(n)) colnames(M) <- paste0("cg", 1:6) dat <- data.frame(y = .5 * f + rnorm(n), cv = rnorm(n)) al <- dmsa_align(data.frame(cpg = colnames(M), d = d, p_plus = ifelse(d > 0, .9, .1)), genes = rep("OXTR", 6), level = "gene") res <- dmsa_triangulate(M, dat, rhs = c("y", "cv"), term = "y", units = rep("OXTR", 6), alignment = al, B = 99, seed = 1) res[, c("unit", "n_probes", "concordance", "direction", "p_omnibus")]set.seed(1) n <- 100 ## half the probes raise expression when methylated, half lower it: on the ## methylation scale they cancel, on the expression scale they agree d <- rep(c(1, -1), each = 3) f <- rnorm(n) M <- sapply(d, function(dj) dj * .6 * f + sqrt(1 - .6^2) * rnorm(n)) colnames(M) <- paste0("cg", 1:6) dat <- data.frame(y = .5 * f + rnorm(n), cv = rnorm(n)) al <- dmsa_align(data.frame(cpg = colnames(M), d = d, p_plus = ifelse(d > 0, .9, .1)), genes = rep("OXTR", 6), level = "gene") res <- dmsa_triangulate(M, dat, rhs = c("y", "cv"), term = "y", units = rep("OXTR", 6), alignment = al, B = 99, seed = 1) res[, c("unit", "n_probes", "concordance", "direction", "p_omnibus")]
Save a coordinate table so the lookup never has to be repeated
dmsa_write_coords(coords, path = "probe_coords.csv")dmsa_write_coords(coords, path = "probe_coords.csv")
coords |
Result of |
path |
csv path. |
path, invisibly.
## run the lookup once, then keep the csv with the project co <- data.frame(probe = c("cg00000029", "cg00000108", "cg00000109"), chr = c("16", "3", "3"), pos = c(53434200, 37417716, 171916037)) f <- dmsa_write_coords(co, tempfile(fileext = ".csv")) utils::read.csv(f)## run the lookup once, then keep the csv with the project co <- data.frame(probe = c("cg00000029", "cg00000108", "cg00000109"), chr = c("16", "3", "3"), pos = c(53434200, 37417716, 171916037)) f <- dmsa_write_coords(co, tempfile(fileext = ".csv")) utils::read.csv(f)