Package 'dmsa'

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

Help Index


Project Alpha build contracts

Description

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.

Usage

alpha_design(build, focal, drop = character())

Arguments

build

1 (parents T1), 2 (T1 to T4), 3 (October 7), or 4 (children).

focal

Character focal term(s), passed to dmsa_design().

drop

Optional character vector of contract covariates to drop, for a declared deviation (e.g. dropping Epi_T1 when the exposure is the immune-cell fraction, its compositional complement). Recorded in the returned object so it shows up in print and cannot be silent.

Details

Build 2 and 3 are longitudinal: their contract asks for the time x predictor interaction, so pass e.g. focal = "time:BSI_Total_c".

Value

A dmsa_design.

Examples

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")$dropped

Alpha panel gene -> system map (bundled)

Description

Read the Project Alpha gene codebook shipped with the package: 549 genes assigned to 30 biological systems.

Usage

alpha_gene_systems()

Value

data.frame with system_id, system, gene, n_kept, usability

Examples

gs <- alpha_gene_systems()
dim(gs)
length(unique(gs$system_id))
head(gs[, c("system_id", "system", "gene", "n_kept", "usability")], 4)

Alpha panel gene -> system-activation polarity (bundled)

Description

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).

Usage

alpha_polarity()

Details

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.

Value

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".

Examples

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")]

The bundled Project Alpha biological reference

Description

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.

Usage

alpha_reference(modules = NULL)

Arguments

modules

Optional data.frame with system_id, gene, module_id, module to add a module layer (e.g. the HPA H / P / A / negative-feedback split).

Details

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.

Value

A dmsa_reference.

Examples

ref <- alpha_reference()
ref
# the polarity dmsa_align() consumes for one system (2 = HPA axis)
head(dmsa_polarity_for(ref, "2"), 4)

Integrity of the bundled Alpha biological reference

Description

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.

Usage

alpha_reference_check(verbose = TRUE)

Arguments

verbose

Print the report? Default TRUE.

Value

Invisibly, a list with the derived counts and the check results.

Examples

alpha_reference_check(verbose = FALSE)$counts

Build the aligned sign s_j for a set of probes

Description

Build the aligned sign s_j for a set of probes

Usage

dmsa_align(
  direction,
  genes = NULL,
  level = c("gene", "system"),
  polarity = "alpha",
  system_id = NULL,
  missing_polarity = c("error", "zero", "drop"),
  tissue = "blood"
)

Arguments

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 dmsa_directions), or a table of calls you supply yourself. A supplied table may be the output of cpgdirection::cpg_expression_direction() (needs cpg_id or input, best_direction, and if available best_confidence), or any data.frame with cpg, d (+1/-1) and optionally p_plus = P(d = +1).

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 direction is a table. Optional when direction is a character vector of probe IDs: leave it NULL to take every gene the bundled map maps each probe to, or supply one gene per probe to pin specific pairs.

level

"gene" (alignment = d_j only) or "system" (alignment = d_j * w_g).

polarity

For level = "system": where w_g comes from. "alpha" (default) uses the bundled Alpha polarity table. Or supply your own data.frame with columns gene and w_g (+1 / -1 / 0). User-supplied values OVERRIDE the bundled table where both cover a gene.

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: "error" (default - stop and list the genes so the user can specify each, per protocol), "zero" (treat as off-axis, probe drops from the system pool but is reported), or "drop" (silently exclude).

tissue

Which bundled direction layer to read when direction is a character vector of probe IDs: "blood" (default) or "epithelium". Choose by what the sample is made of - adult saliva is about 82 swabs are .89 to 1.00 epithelial and take epithelial calls. Getting this wrong fails quietly: coverage looks full while every direction comes from the wrong tissue. Ignored when direction is a table you supply. Further tissues live in cpgdirection.

Value

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.

Examples

# 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)

Per-system coverage and polarity balance

Description

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.

Usage

dmsa_balance(alignment)

Arguments

alignment

result of dmsa_align(level = "system")

Value

one-row data.frame per call

Examples

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)

Select units by Bayesian FDR

Description

Largest set whose MEAN local fdr does not exceed q.

Usage

dmsa_bfdr_select(lfdr, q = 0.05)

Arguments

lfdr

vector of local fdr values

q

target Bayesian FDR

Value

integer indices of selected units

Examples

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

Description

Gated hierarchical DMSA with level-specific inference

Usage

dmsa_cascade(
  b,
  se,
  alignment,
  tree,
  nulls,
  engine = NULL,
  gate = c("both", "dense", "sparse"),
  q = 0.05,
  method = c("expected", "fixed"),
  borrow = 4
)

Arguments

b, se

Probe-level coefficient and standard error, one per probe.

alignment

A dmsa_align() result covering the same probes in the same order (supplies the aligned multipliers).

tree

data.frame with one row per probe and one column per level, ordered outermost first, e.g. data.frame(system=, module=, gene=). Probes are the implicit innermost level.

nulls

Named list of null statistic vectors for calibration, one entry per level named as in tree, plus "sparse" entries named paste0(level, "_sparse") where a sparse statistic is used. Generate these once per design with dmsa_cascade_null().

engine

Named character vector giving "freq" or "eb" per level (plus "probe"). Default is "freq" everywhere: in a gated tree the surviving unit counts are too small for an empirical-Bayes mixture to be estimable, and validation showed EB neutral-to-worse there. EB belongs on an ungated level with many units.

gate

"both" (default), "dense" or "sparse" - the statistic used at frequentist levels. "both" takes the smaller of the two calibrated p-values with a factor-2 correction.

q

Target error rate at each level (BH for "freq", Bayesian FDR for "eb").

method

Pooling method passed to dmsa_test().

borrow

Upper bound on the prior-odds multiplier passed from a parent to its children at empirical-Bayes levels. 1 disables borrowing.

Value

An object of class dmsa_cascade.

Examples

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$system

Null statistics for cascade calibration

Description

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.

Usage

dmsa_cascade_null(
  b,
  se,
  alignment,
  tree,
  exposure,
  M,
  block = NULL,
  B = 999L,
  method = c("expected", "fixed")
)

Arguments

b, se, alignment, tree

as in dmsa_cascade.

exposure

numeric exposure vector used to fit b.

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.

Value

named list suitable for the nulls argument.

Examples

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))

Two-wave change-score DMSA: time x methylation x factor

Description

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.

Usage

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
)

Arguments

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 dmsa_triangulate.

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: "combined" (default) fuses the flat and reliability statistics on one shared permutation stream, "flat" weights every usable aligned probe equally, "reliability" weights each probe by its item-rest correlation with the rest of its unit. Weights are computed from methylation alone, so the permutation null is unaffected.

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 weighting = "flat". of the family (maxT default).

Value

data.frame of class "dmsa_change": unit, n, n_probes, b, t, p, p_adj; attributes term, role, formula.

Examples

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)

Validate a design against the data before fitting

Description

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.

Usage

dmsa_check_design(design, data, strict = TRUE)

Arguments

design

A dmsa_design.

data

A data.frame.

strict

If TRUE (default) problems are errors; if FALSE they are warnings and the checks are still returned.

Value

Invisibly, a list with n, problems, notes and the per-term diagnostics.

Examples

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)$problems

Correct p-values across the contrasts of one categorical exposure

Description

Correct p-values across the contrasts of one categorical exposure

Usage

dmsa_contrast_adjust(x, p, direction = NULL)

Arguments

x

A dmsa_contrasts object.

p

Named or ordered numeric vector of p-values, one per contrast, in the order of x$contrasts.

direction

Optional character or numeric vector of the pooled directions, carried through so the table reports sign next to significance.

Value

A data frame with raw and adjusted p-values.

Examples

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("+", "-"))

Expand a categorical focal exposure into one directional design per contrast

Description

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.

Usage

dmsa_contrasts(
  data,
  design,
  reference = NULL,
  correct = c("holm", "BH", "bonferroni", "none")
)

Arguments

data

The analysis data frame.

design

A dmsa_design whose focal_test names a factor (or character, or a numeric column with few unique values) with k > 2 levels.

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: "holm" (default), "BH", "bonferroni", or "none". Recorded so the choice is visible rather than assumed.

Value

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().

Examples

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)

Coverage report: the reference vs the testable data

Description

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.

Usage

dmsa_coverage(frame, level = c("system", "gene", "pairs"))

Arguments

frame

A dmsa_frame.

level

"system" (default): per-system counts - reference genes, testable genes, untestable genes, measurements, pairs, and the gene-level correction family size. "gene": per-gene pair counts with the dominant reason unused pairs were abstained. "pairs": the full pair ledger, one row per discovered CpG x gene pair.

Value

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).

Examples

## needs cpgdirection; see the vignette for a full run

Select CpG measurement columns by name pattern

Description

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.

Usage

dmsa_cpg_columns(x, include = NULL, exclude = NULL)

Arguments

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. NULL keeps all.

exclude

Optional character vector of fixed substrings; drop CpG columns containing at least one. Applied after include.

Value

Character vector of surviving CpG column names.

Examples

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

Description

Declare a DMSA design: covariates, dependence, and permutation blocks

Usage

dmsa_design(
  focal,
  fixed,
  random = NULL,
  exchangeable = NULL,
  forbid = character(),
  label = NULL,
  id = NULL,
  format = c(NA, "wide", "long")
)

Arguments

focal

Character. The focal term(s) whose pooled aligned effect is tested. May be a main effect ("IC_c"), an interaction written with a colon ("time:BSI_c"), or several terms, in which case the first is the tested one and the rest are mutually-adjusted co-focal terms.

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 (c("chip","cID"), expanded to (1|chip) + (1|cID)) or a one-sided formula (~ (1|chip) + (1|cID)). NULL declares independent observations - which dmsa_check_design() will challenge if it finds repeated block IDs.

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 dmsa_check_design() verify the data shape against the rows - whether any participant contributes more than one row, and whether a repeating id is grouped by random or exchangeable so a permutation cannot separate a participant's correlated rows.

format

Declared data shape: "wide" (one row per participant) or "long" (repeated rows). "long" requires id, since rows cannot be grouped without knowing whose they are; "wide" does not, and is verified when an id is present. NA (default) leaves the shape undeclared.

Value

An object of class dmsa_design.

Examples

dmsa_design(focal = "IC_c",
            fixed = c("sex_c", "age", "Fib", "ctrlSV3", "ctrlSV5"),
            random = c("chip", "cID"), exchangeable = "cID",
            forbid = c("ctrlSV4", "plate"))

The direction calls DMSA ships with

Description

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.

Usage

dmsa_directions(probes = NULL, genes = NULL, tissue = "blood")

Arguments

probes

Character vector of CpG identifiers. NULL (default) returns the whole map.

genes

Optional character vector restricting the result. Either one gene per entry of probes, selecting those specific pairs, or a shorter vector naming genes to keep.

tissue

Which bundled layer: "blood" (default) or "epithelium". See Details for how to choose.

Details

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.

Value

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.

Examples

# 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")]

Module-level evidence and citations for a cascade, selection or frame

Description

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.

Usage

dmsa_evidence(x, which = c("all", "moderate", "flagged"))

Arguments

x

A dmsa_sets, dmsa_selection, or dmsa_frame.

which

"all", "moderate" (Moderate evidence only), or "flagged" (Moderate, heterogeneous, or measurement-defined).

Value

data.frame of class dmsa_evidence, one row per module.

Examples

dmsa_evidence(dmsa_select(systems = "immune"), which = "flagged")

Fit, pool and permute a DMSA set test under a declared design

Description

Fit, pool and permute a DMSA set test under a declared design

Usage

dmsa_fit(
  data,
  probes,
  alignment,
  design,
  method = c("expected", "fixed"),
  B = 999L,
  engine = c("auto", "lm", "lmer"),
  beta_input = TRUE,
  check = TRUE,
  seed = NULL
)

Arguments

data

data.frame with one row per observation.

probes

Character vector of methylation columns, in the SAME order as the rows of alignment.

alignment

A dmsa_align() result.

design

A dmsa_design() (see alpha_design()).

method

"expected" (certainty-weighted) or "fixed".

B

Permutations. 0 skips the permutation (descriptive only).

engine

"auto" reports from lmer when the design declares random effects and lme4 is installed, otherwise lm. The permutation null is ALWAYS built with the matched lm, and the observed lm statistic is the one compared to it - mixing estimators between observed and null is the classic way to manufacture significance. The agreement between the two is returned.

beta_input

If TRUE (default) probes hold beta values and are converted to M; set FALSE if they are already M.

check

Run dmsa_check_design() first (default TRUE).

seed

Optional integer for reproducible permutation.

Value

An object of class dmsa_fit.

Examples

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)

Declare a DMSA analysis frame

Description

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.

Usage

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
)

Arguments

data

data.frame holding outcomes, covariates, ids and (by default) the Alpha methylation columns.

methylation

NULL (auto-detect the bundled map's columns in data), a character vector of column names, or a numeric matrix whose colnames are probe/column ids.

map

"alpha" for the bundled Project Alpha map, or a data.frame. Used by the BUNDLED direction source only; passing a table forces direction_source = "bundled".

reference

The biological reference (system > module > gene) that defines membership on the pair path: "alpha" (default, the 2026c codebook - 30 systems, 187 modules, 1,282 genes) or a dmsa_reference object. The reference never selects CpGs: which CpGs exist is read from the data, which genes they support from cpgdirection, and the reference then says which of those genes belong to the biology being asked about.

direction_source

Where CpG-to-gene direction comes from. "auto" (default) uses cpgdirection::cpg_gene_pairs() - one row per CpG x gene pair, direction resolved within the pair, pair-specific abstention - when cpgdirection is installed, and otherwise falls back to the bundled per-column Alpha snapshot with a recorded note. "cpgdirection" requires the package; "bundled" forces the snapshot. On the pair path the frame keeps the full pair ledger ($pair_ledger; see dmsa_coverage()). For testing, options(dmsa.pair_table = <pair data.frame>) injects a pair table in place of the live cpg_gene_pairs() call - the regression suite uses this seam so the pair path is exercised on machines where cpgdirection is not installed.

tissue

Tissue context for pair discovery: "blood" (default), "nasal_epithelium", or "solid_tissue". Pair path only.

replicate_policy

"collapse_site" (default): several probe designs of one CpG are ONE measurement, averaged on the beta scale before analysis (a missing value in any replicate keeps the site visible to missing_methylation rather than quietly narrowing its basis); "keep_correlated" keeps every design as its own measurement. Pair path only.

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, "_maternal" and "_paternal" mark the parents' probes; the child's carry no tag). Fixed substrings, not regular expressions: a CpG column survives cpgs_include if its name contains ANY of the patterns (NULL = all), and is then removed by cpgs_exclude if it contains any of those. Analysing the child is one line: cpgs_exclude = c("_maternal", "_paternal"). Only CpG-site columns are filtered - outcomes and covariates are never touched - and the drop is recorded in the corrections table. See also dmsa_cpg_columns() for the same filter as a standalone helper.

sample_id

Optional column of data holding sample identifiers. When the methylation matrix carries rownames, rows are matched by identifier and reordered to match data; any mismatch or duplication is an error rather than a silent realignment.

sample_align

"id" (default) matches a methylation matrix to data by sample identifier where identifiers exist; "positional" declares that the rows are already in the same order. With no identifiers on either side a shuffle is undetectable, so alignment falls back to positional and is recorded in the corrections table.

missing_methylation

What to do when the methylation block contains missing or non-finite values. "error" (default) stops and reports how many probes and samples are affected; "drop_probes" removes the affected probes and records it; "common_complete_rows" keeps every probe and analyses the samples complete across all of them. There is no silent option: downstream a probe with any non-finite value is dropped from its set, and that must be a declared choice.

methylation_scale

"auto" (default) treats values inside [0, 1] as beta and converts them to M-values; "beta" declares them beta, and values outside [0, 1] are then an error rather than being clamped; "M" declares M-values and converts nothing.

system, module, gene, probe

logical: which levels to analyse.

systems

Optional subset of systems to analyse. Accepts the selection cascade's short names - systems = c("hpa", "oxytocin") - and also system ids or full names. Matching is case-insensitive and a unique prefix of a short name is enough. NULL (default) analyses every covered system. Everything below the system - modules, genes, probes - is taken in full; use dmsa_select() and pass the result as sets to narrow a lower level. Run dmsa_systems() to print the short names.

sets

The selection cascade declaring system > module > gene > probe. "alpha" (default) uses the bundled, module-audited Project Alpha 2026c cascade; a path, data.frame, dmsa_sets or dmsa_selection may be supplied instead, and NULL disables it (module membership then falls back to the legacy bundled module map). See dmsa_sets_template() for the schema.

...

Not for new arguments. The former outcome argument is accepted here so existing scripts keep working (it means exactly outcomes); anything else is an error. A side effect of this slot: every argument after data and methylation must be given by its full name.

outcomes

Which column(s) of data the methylation is tested against - the phenotype side of the model. With several, each is tested in turn with the others entering as covariates (mutual adjustment). If your question runs the other way - these columns PREDICT the methylation - name them with predictors = instead and skip frame_role entirely.

predictors

The natural spelling when METHYLATION IS YOUR OUTCOME: which column(s) of data predict the methylation frame. Passing predictors = sets frame_role = "outcome" for you (an explicit frame_role = "predictor" alongside it is a contradiction and errors). Same columns as outcome, opposite reading; give exactly ONE of outcome / outcomes / predictors.

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 data whose rows must travel TOGETHER under permutation because they are not independent - the participant id when a person contributes more than one row, the couple id for partners, the family id for relatives. There is NO default (PI ruling, 2026-09-02): if you declare none, every row is permuted independently and dmsa_frame() says so and asks you to confirm that is intended; a block column you DID name but that is absent from data is a hard error (spec 43), because dropping it would silently change the null model. covariates = "contract" carries the Alpha build's block structure and is adopted when you give none. TWO factors and dmsa_frame() works out the structure itself and says so in lme4 notation: NESTED (e.g. couples on the same chip, (1 | chip/cID)) blocks on the nested pair, with the coarse factor's variance component left to chip =; CROSSED (partners of one couple on DIFFERENT chips, (1 | cID) + (1 | chip)) routes the finest dependence factor to the blocks and the other into the (1 | .) random intercept. Aliased factors are detected; three or more are refused (the engine fits one block structure and one intercept).

random_effects

Deprecated spelling of blocks (it always named the exchangeability blocks, not lme4 random effects). Still accepted, with a message; giving both is an error.

chip

how to handle the array/chip batch factor. TRUE (default) enters it as a fixed effect, using chip_f if present and otherwise building it from a chip/chip_T* column. FALSE leaves it out entirely - on the Alpha parent build the factor has 61 levels on about 400 arrays, so it costs 60 degrees of freedom that the control-probe surrogate variables already partly absorb, and 7 of those 61 chips hold a single array - fitted exactly by their own dummy (leverage 1, residual 0), so they are counted in n while contributing nothing to any estimate. "pool" keeps the batch adjustment but merges every level holding fewer than 3 arrays into one chip_small level, which recovers the degrees of freedom and removes the leverage-1 rows. Any other character string names the column to use. The before/after contracts forbid chip and override this argument.

chip_effect

how the chip factor enters the model, once chip has named it. "random" (default, and what the Alpha covariate contract specifies) takes chip out of the fixed design and fits it as a one-way random intercept, (1 | chip), by REML quasi-demeaning: a median of about 16 effective degrees of freedom on the parent build, and zero on the probes where the chip variance is estimated as nil. "fixed" restores the pre-1.18.0 behaviour and enters chip as a fixed factor, at one degree of freedom per chip beyond the first. "none" drops the batch adjustment altogether, whatever chip says. Validity does not rest on the variance component being right: the permutation null is computed under the same transform, so the random-intercept fit moves power, not type-I error.

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 outcome = 0 / outcome = 1. Either a length-2 character vector - honoured when the frame has ONE outcome - or a named list with one such pair per outcome, e.g. list(pills_past_T1 = c("not taking pills", "taking pills")), which works for any number of outcomes. Each pair is safest given as a NAMED vector matched by coded value, e.g. c("0" = "not taking pills", "1" = "taking pills") - immune to flipping whatever the coding (1/2, 0/1, "a"/"b"); an unnamed pair is taken in sorted-value order (low, high), and a name that is not one of the variable's two actual levels is an immediate error naming them. NULL (default) uses the coded-value fallback. Purely cosmetic: no level name reaches the model, the permutation or the family-wise correction. Spell it the way you declared the variable: with outcomes = use this argument; with predictors = use predictor_levels - same meaning, your vocabulary.

predictor_levels

The same level labels, for a frame declared with predictors = (frame_role = "outcome"): someone who wrote predictors = "pills_past_T1" thinks of pills as a predictor, so the labels are asked for in those words. Exactly one of predictor_levels/outcome_levels may be given, and each requires its matching declaration.

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: predictor_labels names the predictors = column(s), outcome_label the outcomes = column(s) (the two are the same column - give one, matching your declaration), covariate_labels the covariates, and mod_label/ mod2_label are ONE name each for the moderator column(s) (a moderator's LEVEL labels go in mod_levels). Either one per column, in order, or a named vector keyed by column name. Purely cosmetic: no label reaches the model, the permutation or the family-wise correction.

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 - mod_levels = c("1" = "Husband", "2" = "Wife") - which cannot flip whatever the coding; an unnamed pair is taken in sorted-value order (low, high).

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 - outcome_type = c(epds = "gaussian", depressed = "logistic") - and every outcome must be named. An unnamed vector of several values is refused: a family paired positionally with the wrong outcome is not visible anywhere in the output. The stored value is the bare string for a single-outcome frame and a vector named by outcome otherwise.

frame_role

Which way the SECONDARY models face. Most callers never need this argument: passing the phenotype columns as predictors = sets it for you. The main DMSA path is always methylation ~ outcome + covariates: a directional set statistic on methylation is what DMSA estimates, and no argument turns that around. "predictor" (default) faces the moderated model the same way as the shape scan, outcome ~ tone score x moderator; "outcome" declares that the named columns predict the frame, giving tone score ~ outcome x moderator. Because the shape scan cannot be turned around, frame_role = "outcome" requires type = "linear". The resolved orientation of every path is stored on the frame as model_orientation and printed.

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: "combined" (default) fuses the flat and reliability statistics on one shared permutation stream, "flat" weights every usable aligned probe equally, "reliability" weights each probe by its item-rest correlation with the rest of its unit. Weights are computed from methylation alone, so the permutation null is unaffected.

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 weighting = "flat". records it; FALSE: strict errors instead.

palette

Colour palette for every figure the report draws. One of the viridisLite palettes - "viridis" (default; the colour-blind-safe standard), "magma", "plasma", "inferno", "cividis" (designed for deuteranopia), "mako", "rocket", or "turbo" (vivid, not colour-blind-safe - avoid for publication). All are perceptually uniform and print safely in greyscale except "turbo". If viridisLite is not installed, any value falls back to base R's hcl.colors(..., "viridis"), so the figures always draw. The per-system accent colours of the overview panels come from a separate fixed scheme and are not affected.

plots, tables, summary, plot_type, table_type, outdir

output styling for dmsa_report.

gene_models

Draw real exon/intron structure (with the TSS) under the probes in each locus panel. The default "auto" fetches the model from Ensembl for NAMED genes only - a handful of small requests, announced in the report log - and degrades to the bare coordinate axis with a message when offline. TRUE forces the fetch, FALSE never goes to the network, and a table built once with dmsa_gene_model can be passed to stay offline with the exons still drawn.

progress

Show a 0-100 Defaults to interactive(): a bar writes carriage returns, which make a piped log unreadable, so it is off under Rscript and in tests.

beep

Sound a completion signal when dmsa_report() finishes. TRUE uses beepr sound 8; a number picks another sound; FALSE is silent. Defaults to interactive(), so a run never makes noise during R CMD check or a testthat run. beepr is a Suggests: without it this is silently a no-op.

autofix

TRUE: the test drive corrects what is safely correctable and

Value

object of class dmsa_frame.

Examples

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

Description

Gated hierarchical DMSA: one score, one model, level-local correction

Usage

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
)

Arguments

data

data.frame with outcome, moderators and covariates.

M

Numeric matrix, samples x probes, rows aligned to data.

map

data.frame, one row per column of M, with the level columns ordered outermost first, e.g. system, module, gene, probe. The innermost column is the probe level.

alignment

dmsa_align() result for M's columns in order, or a numeric multiplier vector.

formula

Upper-level model formula, using the literal name S where the unit's score belongs.

term

Coefficient to test, as model.matrix names it - e.g. "S" or "S:ACE".

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 data (or several, combined by interaction), or per-row labels (family/cluster id).

alpha

Level for every family, propagated to children that pass.

B

Permutations.

winsor

Passed to dmsa_scores().

gate

Which arm(s) to run: "both" (default), "sparse" (survivors descend one unit at a time), or "dense" (a surviving family descends whole).

sparse_reach

How far a sparse survivor reaches down the hierarchy: "children" (default) descends one level; a level name (e.g. "probe") descends straight to that level.

seed

Optional integer.

Value

Object of class dmsa_gate.

Examples

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)

A gene's exon, intron and UTR structure on real coordinates

Description

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.

Usage

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
)

Arguments

gene

Gene symbol, e.g. "NR3C1".

source

One of "auto", "ensembl", "gff", "txdb", "ensdb", "table". "auto" takes a cached copy if there is one, then Ensembl.

file

GTF/GFF3 path when source = "gff".

db

TxDb or EnsDb object when source is "txdb" / "ensdb".

orgdb

OrgDb for symbol lookup with "txdb" (e.g. org.Hs.eg.db).

table

A data frame already in this shape, when source = "table".

species

Ensembl species name. Default "homo_sapiens".

genome

Genome label recorded on every row. Default "hg38" - the assembly the bundled Alpha coordinates use.

cache

Directory for fetched models, or NULL to disable. Default is a dmsa folder under the session temp directory.

host

Ensembl REST host. NULL disables the network entirely and returns an empty model, which is how you guarantee an offline build.

quiet

Suppress progress messages.

Value

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.

See Also

dmsa_gene_model_check, dmsa_plot_locus

Examples

## 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

Description

Check a gene model before drawing from it

Usage

dmsa_gene_model_check(gm, verbose = TRUE)

Arguments

gm

A dmsa_gene_model, or any data frame in that shape.

verbose

Print the report.

Value

Invisibly, a list with ok and one entry per check.

Examples

## 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

Import a preprocessing pipeline's result for DMSA

Description

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.

Usage

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,
  ...
)

Arguments

x

A pipeline result: a beta/M matrix in either orientation, a data.frame export (probes in the rownames or first column), a SummarizedExperiment (covers minfi's GenomicRatioSet, MethylSet and relatives without needing minfi), a list of sesame SigDF objects, or a path to a .csv/.rds file.

...

Passed between methods.

pheno

Optional phenotype/sample sheet: a data.frame, or a path to a CSV. When x is a SummarizedExperiment its colData is used automatically unless pheno overrides it. When no phenotype is available at all, a one-column sheet holding the sample names is built so the object still works, and the print method says so.

align_by

Name of the pheno column holding the array/sample identifiers that match the methylation sample names (e.g. "Basename" or "Sample_Name"). NULL (default) auto-detects: each character/factor column is tried literally, then with directory paths stripped (minfi sample sheets), then the Sentrix_ID/Sentrix_Position pair pasted with "_". Ambiguity or failure is an error naming what was tried - never a silent guess.

values

"auto" (default) detects the value scale: everything inside [0, 1] is treated as betas; anything outside as M-values, which are converted to betas via 2^m / (2^m + 1) with a note. "beta" and "m" override the detection.

orientation

"auto" (default) reads the dimension names: the axis whose names look like probe ids (cg/ch./rs prefixes) is the probe axis. "probes_rows" and "probes_cols" override - required when a matrix has no dimnames on the probe axis.

id, format

Optional study-shape declaration carried to the analysis step: the participant-id column in pheno and the declared "wide"/"long" format, exactly as in dmsa_design. Stored and printed; pass them on to dmsa_design()/alpha_design() when modelling.

source

Optional label naming the pipeline the object came from ("minfi", "sesame", ...). Cosmetic: shown by the print method and recorded in the object; auto-filled when detectable.

Value

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.

Examples

## 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

Description

Gated hierarchical DMSA on the pooled aligned statistic

Usage

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
)

Arguments

M

Numeric matrix, samples x probes, methylation on the analysis scale. The probes are the RESPONSES, as in dmsa_fit().

data

data.frame of predictors, same row order as M.

rhs

Character vector of right-hand-side terms, e.g. c("anx","avo","sex_c","chip_f") or with an interaction c("anx*aceZ", ...).

term

The design column to pool, as model.matrix names it - "anx" or "anx:aceZ".

map

data.frame, one row per column of M, level columns outermost first (e.g. system, module, gene, probe).

alignment

dmsa_align() result for M's columns in order.

roots

Pre-registered units at the outermost level.

block

Permutation block labels, one per row.

alpha, B, winsor, seed

As in dmsa_gate.

method

Pooling method for dmsa_test().

gate

"both", "sparse" or "dense".

family_correction

Character. Family-wise correction applied inside each level-local family: "maxT" (default) Westfall-Young step-down on the strength scale, or "minP" step-down on marginal p-values. The choice is consequential and should be fixed in advance; see the package vignette and REPRODUCE.md.

sparse_reach

"children" or the name of a deeper level a unit's sparse arm may look straight at.

Value

Object of class dmsa_gate.

Examples

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)

Local false discovery rate by central matching

Description

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.

Usage

dmsa_lfdr(z, prior_odds_multiplier = 1, min_n = 50L)

Arguments

z

numeric vector of unit-level z statistics (null approximately standard normal after calibration).

prior_odds_multiplier

Multiplies the estimated pi1, bounded to [0.01, 0.9]. Values above 1 encode evidence inherited from a parent.

min_n

Below this many units the estimate is not attempted and NULL is returned, signalling the caller to fall back to a frequentist rule.

Details

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.

Value

numeric vector of local fdr values, or NULL if not estimable.

Examples

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)))

Latent moderated mediation over DMSA system tones

Description

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).

Usage

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
)

Arguments

data

data.frame of phenotype rows, or a dmsa_import object.

methylation

Optional samples x probes matrix aligned to data rows (beta scale by default). When NULL, probe columns are taken from data itself.

map

A dmsa_sets() cascade or anything it accepts (default "alpha").

x

Character vector of predictor system short names (1+).

m

Mediator system short name.

w

Moderator system short name, or NULL for plain mediation.

outcome

Outcome column in data.

covariates

Character vector of covariate columns.

measurement

Measurement model for each system's module tones: "tau" (essentially tau-equivalent - equal loadings, free uniquenesses; the small-sample default per Strauss 2026), "congeneric" (free loadings; steps down to tau on non-convergence or a Heywood bound - bounds are never patched to zero), or "single" (reliability-corrected single indicator, Savalei 2019). Systems with fewer than 3 usable modules fall back to "single" automatically.

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). NULL estimates by Spearman-Brown instead.

module_weights

How module tones combine in the SINGLE-INDICATOR composite: "equal" (default - standardized tones, equal a priori weight), "sqrt_probes" or "probes" (precision weights by each module's usable probe count). Factor rungs always weight modules by lambda/theta (Bartlett); probe-level weighting inside each module tone is upstream and always on.

measure_covariates

Which covariates are residualized out of the module tones BEFORE the measurement stage (the composition pre-adjustment). NULL (default) uses covariates; character(0) disables pre-adjustment (composition then acts only through the structural covariates); a subset (e.g. the cell fractions alone) brackets the under-/over-adjustment trade-off.

chip

Optional column in data naming the array chip. When given, module tones are chip-quasi-demeaned (REML, R/ranef.R) BEFORE the measurement stage, and outcome/covariates before the structural stage.

block

Optional column in data naming the exchangeability block (the family/couple identifier, cID). When given, Freedman-Lane relabels WHOLE blocks of equal size (row order preserved inside each block - the same relabelling dmsa_model() uses), preserving within-family dependence under the null, and the bootstrap resamples blocks rather than individuals. Incompatible with perm_blocks = "chip".

perm_blocks

"none" permutes residuals unrestricted after the chip transform; "chip" permutes within chip blocks (Winkler et al. 2015). The registered simulation gate selects the scheme; both are available so the gate can be run.

model

"7" (moderation on the a-paths only) or "8" (also on the direct paths).

map_level

"full" or "confidence" - which analysed direction-map level supplies the probe universe (as in dmsa_frame()); module membership joins from the cascade by gene.

B

Permutations per path.

boot

Bootstrap resamples for percentile CIs; the ENTIRE pipeline (measurement models included) is refit in every resample. 0 disables.

alpha_ssc

Bogaert-Loh-Rosseel alpha for the small-sample offset correction; 0 (default) subtracts the full offset, with the lambda (positive-definiteness) shrink always active and reported.

conditional_at

Moderator values (in latent SD units) at which conditional indirect effects are reported.

beta_input

TRUE when methylation holds betas (converted to M-values internally).

seed

Optional integer; the caller's RNG state is restored.

Value

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.

Examples

## 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

One model, block-permutation calibrated, for any term

Description

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.

Usage

dmsa_model(
  formula,
  data,
  term,
  block = NULL,
  B = 1999,
  seed = NULL,
  nulls = FALSE
)

Arguments

formula

Model formula.

data

data.frame.

term

Name of the coefficient to test, exactly as model.matrix names it (e.g. "S:ACE").

block

Block labels, one per row of data - the family or cluster identifier. Rows are permuted only within blocks of EQUAL SIZE, because concatenating a random order of unequal-size families is not a valid relabelling.

B

Permutations.

seed

Optional integer.

nulls

Logical (default FALSE). TRUE returns the vector of permutation null statistics as $null_t alongside the test, so a caller can compose joint corrections on one stream. (Joint calibration across lenses, engines and levels comes from sharing the seed, which dmsa_model_omnibus() does for you.)

Value

An object of class dmsa_model.

Examples

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)

Omnibus across the score projections

Description

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.

Usage

dmsa_model_omnibus(
  scores,
  formula,
  data,
  term,
  block = NULL,
  score_col = "S",
  B = 1999,
  seed = NULL
)

Arguments

scores

data.frame from dmsa_scores().

formula

Formula referring to the score by score_col.

data, block, B, seed

As in dmsa_model().

term

Term to test.

score_col

Column name the formula uses for the score.

Value

list: p_by_flavour, the omnibus p, and the fits.

Examples

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)

Description

Moderated DMSA (mDMSA)

Usage

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
)

Arguments

data

data.frame.

probes, alignment

as in dmsa_fit().

outcome

Character. The outcome column (a psychological measure).

moderator

Character. The moderator column.

design

A dmsa_design(). Its focal is ignored and replaced by score * moderator; its fixed, random, exchangeable and forbid are used as declared.

method

"expected" or "fixed" scoring.

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 dmsa_fit().

beta_input

TRUE if probes hold beta values.

check

Run the design check (default TRUE).

seed

Optional integer.

Value

An object of class dmsa_moderate.

Examples

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

Description

M-value transform with clamping

Usage

dmsa_mvalues(beta, eps = 1e-04)

Arguments

beta

numeric matrix or vector of beta values in (0,1)

eps

clamp bound

Value

A numeric matrix or vector of M-values, the same shape as beta, on the logit scale.

Examples

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))

Directionless omnibus test of a categorical focal exposure over a set

Description

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.

Usage

dmsa_omnibus(data, Y, design, beta_input = TRUE, B = 999, seed = 1L)

Arguments

data

Analysis data frame.

Y

Probe matrix (subjects x probes), betas or M-values.

design

A dmsa_design whose focal_test names the factor.

beta_input

Passed through: are the columns of Y betas?

B

Permutations.

seed

RNG seed.

Value

A list with the pooled statistic, the permutation p-value, and an explicit directional = FALSE.

Examples

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 sign

Test an aligned tone score against a non-Gaussian outcome

Description

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.

Usage

dmsa_outcome(
  data,
  outcome,
  score,
  family = c("gaussian", "binomial", "ordinal", "multinomial", "poisson"),
  moderator = NULL,
  covariates = character(),
  block = NULL,
  center = TRUE,
  B = 999L,
  seed = NULL
)

Arguments

data

data.frame with one row per subject.

outcome

Name of the outcome column. For "binomial" it may be 0/1, logical, or a two-level factor; for "ordinal" an ordered factor or an integer-coded severity; for "multinomial" a factor.

score

Name of the tone-score column (from dmsa_score()).

family

One of "gaussian", "binomial", "ordinal", "multinomial", "poisson".

moderator

Optional moderator column; when given, the tested term is score:moderator.

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.

Details

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.

Value

An object of class dmsa_outcome.

Examples

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)

The p-dance test: does a finding's p-value survive a change of analysis set?

Description

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.

Usage

dmsa_pdance(
  engines,
  set,
  focal,
  pool = NULL,
  form = c("dropout", "addition"),
  grid = NULL,
  R = 100L,
  alpha = 0.05,
  seed = NULL
)

Arguments

engines

Named list of functions. Each takes one argument - the members of a perturbed analysis set, in whatever form set is given - and returns a single p-value. Anything expressible this way can be tested, which is the point: DMSA and its competitors are put through an identical procedure.

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 set. Never perturbed. These carry the evidence the finding is about.

pool

For form = "addition", the reservoir of probes to add, disjoint from set. Ignored for dropout.

form

"dropout" (default) or "addition".

grid

Perturbation sizes. For dropout, fractions of the non-focal probes to remove; defaults to c(.01, .02, .03, .05, .10, .20, .30, .50), which spans the observed range of cross-cohort probe-set divergence and then some. For addition, counts of probes to add; defaults to c(1, 2, 5, 10, 25, 50).

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.

Details

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.

Value

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.

Examples

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

Description

Permutation p-value for a DMSA statistic

Usage

dmsa_perm_pvalue(observed, null_stats)

Arguments

observed

The observed statistic (e.g. z from dmsa_test).

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 dmsa_test).

Value

two-sided permutation p with the +1 correction (resolution floor 1/(B+1)).

Examples

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

Description

Panel 2: gene level inside one system

Usage

dmsa_plot_genes(
  genes,
  null_max = NULL,
  title = "",
  alpha = 0.05,
  file = NULL,
  width = 1500,
  height = 1500,
  res = 190
)

Arguments

genes

data.frame for ONE system and outcome: gene, z, n_probes, optionally p_adj (max-T adjusted) and selected.

null_max

Optional numeric vector of the permutation null of max|z| over the same genes; its .95 and .99 quantiles are drawn as the multiplicity-adjusted thresholds.

title

System label.

alpha

Gate level. The gate is min(1, 2*min(p_dense, p_sparse)), so the boundary drawn is at alpha/2 on each arm.

file

Optional png path. If NULL draws to the current device.

width, height, res

png dimensions.

Value

Called for its side effect of drawing a plot on the active graphics device. Returns NULL invisibly.

Examples

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"))

Locus figure: chromosome, gene with CpG lollipops, and per-CpG effects

Description

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.

Usage

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
)

Arguments

probes

data.frame for ONE gene. Required: probe, b, se, d (cpgdirection, +1/-1). Optional: pos (genomic coordinate), chr, gene_region, p (per-probe adjusted p), signal (logical, overrides p).

gene

Gene symbol, for labelling.

chrom

Chromosome, e.g. "20". Taken from a chr column when present and not given here.

gene_start, gene_end

Gene span in the same coordinate system as pos. Defaults to the probe range padded by 10%. Ignored when there are no coordinates.

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 p < signal_p are drawn as carrying signal. If probes$p is absent, all probes count as signal.

invert

Which d is reflected onto the common effect axis in strip C, exactly as in dmsa_plot_probes().

order_by

"auto" uses pos if present, else gene_region, else the supplied row order. Force one with "pos", "region" or "given".

gene_model

Real exon, UTR and strand structure to draw under the probes. A dmsa_gene_model table, a path to a GTF/GFF3, TRUE to fetch the model from Ensembl, or NULL for none. Requires genomic positions for the probes; a model on a different chromosome, or one that could not be obtained, is dropped with a message rather than drawn.

transcripts

"canonical" draws the annotation's own canonical transcript and names it under the axis; "all" stacks every transcript in the model, one lane each.

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. height = NULL scales to the number of strips and probes.

Value

Invisibly, the plotted data frame, with the columns the figure actually used (x, sig, shown, flipped).

See Also

dmsa_gene_model to build the model, dmsa_plot_locus_gviz for the Gviz engine, dmsa_probe_coords for probe positions.

Examples

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 locus panel drawn by Gviz

Description

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.

Usage

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
)

Arguments

probes

Data frame with probe, pos, b, and optionally d, p, chr - the same frame dmsa_plot_locus() takes.

gene

Gene symbol, used for the title and the model lookup.

gene_model

A dmsa_gene_model, or TRUE to fetch one.

chrom

Chromosome, e.g. "chr5". Taken from the model or the probes when absent.

genome

Genome label passed to Gviz, default "hg38".

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.

Details

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.

Value

Invisibly, the list of tracks plotted.

See Also

dmsa_plot_locus for the dependency-free panel.

Examples

## Not run: 
gm <- dmsa_gene_model("NR3C1")
dmsa_plot_locus_gviz(pr, gene = "NR3C1", gene_model = gm)

## End(Not run)

Panel 3: probe level, with the CpG-to-expression direction applied

Description

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.

Usage

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
)

Arguments

probes

data.frame for ONE system and outcome, restricted to the genes worth showing: gene, probe, b, se, d (the cpgdirection call, +1 or -1); optionally p and selected.

invert

Which d is reflected onto the common axis: "+1" (default) reflects the probes whose methylation predicts HIGHER expression, "-1" reflects the others, "none" shows raw slopes.

With "+1" the axis reads: positive = the exposure moves methylation in the direction that LOWERS this gene's expression. Every d = -1 probe is shown as fitted (more methylation, less expression) and every d = +1 probe is reflected so that it means the same thing.

gene_summary

Optional data.frame with gene and z, and optionally p_adj: the pooled gene-level result, drawn as a diamond on its own row. This is usually the point of the figure - individual probes are rarely significant on their own, and the evidence lives in their agreement.

title

System label.

axis_label

Overrides the x-axis label.

alpha

Gate level. The gate is min(1, 2*min(p_dense, p_sparse)), so the boundary drawn is at alpha/2 on each arm.

file

Optional png path. If NULL draws to the current device.

width, height, res

png dimensions.

Value

Called for its side effect of drawing a plot on the active graphics device. Returns NULL invisibly.

Examples

## 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"))

Panel 1: dense arm against sparse arm, every system

Description

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.

Usage

dmsa_plot_systems(
  systems,
  alpha = 0.05,
  file = NULL,
  width = 1500,
  height = 1400,
  res = 190
)

Arguments

systems

data.frame with columns system, outcome, p_dense, p_sparse; optionally primary (logical) and top_gene.

alpha

Gate level. The gate is min(1, 2*min(p_dense, p_sparse)), so the boundary drawn is at alpha/2 on each arm.

file

Optional png path. If NULL draws to the current device.

width, height, res

png dimensions.

Value

Invisibly, the plotted data frame.

Examples

## 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

Description

Load a gene-to-system polarity table

Usage

dmsa_polarity(x = "alpha", sets = NULL)

Arguments

x

"alpha" for the bundled Project Alpha 2026c polarity table, a path to a CSV in the same shape, a data.frame, or a dmsa_sets / dmsa_selection / dmsa_frame object whose attached polarity should be used - so a narrowed selection reviews only its own genes. NULL returns NULL, which is how a caller opts out of polarity entirely (system-level scores then weight every gene +1 and say so).

sets

Optional dmsa_sets or dmsa_selection. When given, coverage is reported against it: which of its genes have a sign, which do not, and at what evidence grade.

Details

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.

Value

An object of class dmsa_polarity, or NULL.

See Also

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.

Examples

pol <- dmsa_polarity()
pol
dmsa_polarity_review(pol)

Validate a candidate polarity table

Description

Validate a candidate polarity table

Usage

dmsa_polarity_check(x = "alpha", sets = NULL, verbose = TRUE)

Arguments

x

Path, data.frame, or dmsa_polarity.

sets

Optional cascade to check coverage against.

verbose

Print the report. Default TRUE.

Value

Invisibly, a list with ok and one entry per check.

Examples

## 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

Draft polarity signs for a panel from public databases

Description

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.

Usage

dmsa_polarity_fetch(
  sets,
  anchors = NULL,
  sources = c("go", "omnipath"),
  max_genes = 200L,
  quiet = FALSE
)

Arguments

sets

A dmsa_sets or dmsa_selection whose genes to draft signs for, or a character vector of gene symbols.

anchors

Named list mapping system_id to the anchor genes that define that system's activation tone. Required for anything except sources = "go": without an anchor there is no tone for a sign to point at.

sources

Which resources to query, in order. See details.

max_genes

Stop after this many genes. Drafting is one HTTP call per gene for go and omnipath, so raise it deliberately.

quiet

Suppress progress messages.

Details

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.

Value

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.

Examples

## 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

Description

Polarity table for one system, in the form dmsa_align() wants

Usage

dmsa_polarity_for(reference, system_id)

Arguments

reference

A dmsa_reference.

system_id

The system to extract.

Value

data.frame with gene and w_g, or NULL if the bundle carries no polarity.

Examples

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")

Rows of a polarity table that need a human decision

Description

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.

Usage

dmsa_polarity_review(
  x = "alpha",
  which = c("all", "disagreement", "unresolved", "low_confidence")
)

Arguments

x

A dmsa_polarity, or anything dmsa_polarity() accepts - including a dmsa_selection, which scopes the review to that selection's genes.

which

"all", "disagreement" (a database contradiction or a framing choice only the analyst can settle), "unresolved" or "low_confidence".

Value

data.frame of class dmsa_polarity_review.

Examples

## 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

Description

Which public resources DMSA can draft polarity from, and what each is good for

Usage

dmsa_polarity_sources()

Value

data.frame of resource name, endpoint template and caveat.

Examples

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"]

Write a template script that fetches probe coordinates from Bioconductor

Description

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.

Usage

dmsa_probe_annotation_template(
  path = "get_probe_coords.R",
  array = c("EPIC", "450K")
)

Arguments

path

Where to write the script.

array

"EPIC" or "450K".

Details

Run the written script once; it produces a csv with probe, chr, pos, gene_region, island that dmsa_plot_locus() consumes.

Value

path, invisibly.

See Also

dmsa_probe_coords

Examples

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)

Probe genomic coordinates, without Bioconductor

Description

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.

Usage

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")
)

Arguments

probes

Character vector of probe ids to look up.

file

Path to a local manifest (plain text or .gz). Takes precedence over url.

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 tools::R_user_dir("dmsa", "cache").

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.

Value

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.

See Also

dmsa_plot_locus, which needs none of this.

Examples

## 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

Description

Construct a DMSA reference bundle

Usage

dmsa_reference(
  systems,
  polarity = NULL,
  anchors = NULL,
  anchor_method = c("curated", "graph_sink", "user", "none"),
  name = "unnamed",
  version = NA_character_,
  notes = NULL
)

Arguments

systems

data.frame with columns gene, system_id, system, and optionally module_id and module. One row per gene-within-system (a gene may appear in several systems).

polarity

data.frame with columns gene, system_id, w_g. w_g may be -1/0/+1 or continuous in [-1, 1]. Optional columns evidence, role, confidence, source are carried through.

anchors

Optional data.frame with system_id, gene - the genes that define the direction of "more activation" for each system.

anchor_method

Character, how the anchors were obtained. One of "curated", "graph_sink", "user", or "none" (systems then carry no sign and only gene-level analysis is valid).

name, version, notes

Provenance, printed and stored.

Value

An object of class dmsa_reference.

Examples

## 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")

Read a user-supplied system / module / gene csv as a reference bundle

Description

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.

Usage

dmsa_reference_csv(
  path,
  name = NULL,
  version = NA_character_,
  notes = NULL,
  min_genes = 3L,
  quiet = FALSE
)

Arguments

path

Path to the csv (anything utils::read.csv accepts).

name, version, notes

Provenance for the bundle; name defaults to the file name.

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.

Details

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.

Value

A dmsa_reference.

Examples

f <- tempfile(fileext = ".csv")
dmsa_reference_template(f)
ref <- dmsa_reference_csv(f)

Read a reference bundle from CSV files

Description

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.

Usage

dmsa_reference_read(dir, anchor_method = "user")

Arguments

dir

Directory holding the files.

anchor_method

Passed to dmsa_reference() when no manifest is present.

Value

A dmsa_reference.

Examples

## 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)

Write a template csv for a user-supplied reference

Description

Produces a small, filled-in example with every accepted column, so the shape is unambiguous. Overwrite the rows with your own.

Usage

dmsa_reference_template(path, with_modules = TRUE)

Arguments

path

Where to write it.

with_modules

Include the module column.

Value

path, invisibly.

Examples

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)

Write a reference bundle to a directory

Description

The inverse of dmsa_reference_read(): useful for saving a bundle built from a one-file csv, or a generated bundle you have edited.

Usage

dmsa_reference_write(reference, dir)

Arguments

reference

A dmsa_reference.

dir

Directory to create and write into.

Value

dir, invisibly.

Examples

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)

Outcome-free reliability weights for aligned probes

Description

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".

Usage

dmsa_relweights(
  Z,
  units,
  mlt,
  w_floor = 1.5,
  weighting = c("reliability", "flat")
)

Arguments

Z

Numeric matrix, samples x probes, standardised methylation (the same matrix the lenses see; outcome-free).

units

Character/label vector, one per column of Z.

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

"reliability" (default) or "flat" (returns all ones).

Value

Numeric vector of per-probe weights, length ncol(Z), mean 1 within each weighted unit and 1 everywhere a guard fired.

Examples

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)

Run a declared frame and deliver figures, tables and a summary

Description

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.

Usage

dmsa_report(frame, progress = NULL, beep = NULL, overwrite = FALSE)

Arguments

frame

a dmsa_frame.

progress

Show a 0-100 value declared on the frame, which itself defaults to interactive().

beep

Completion sound. NULL (default) takes the frame's value. TRUE is beepr sound 8; a number picks another; FALSE is silent. Without beepr installed this is a no-op.

overwrite

Replace an existing report in 'outdir'? Default FALSE: a directory that already holds DMSA output is refused rather than silently overwritten.

Value

object of class dmsa_report: file paths and result tables, invisibly printable.

Examples

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

Description

Build the whole report: panel 1 once, panels 2 and 3 per firing system

Usage

dmsa_report_panels(
  systems,
  genes,
  probes,
  nulls = NULL,
  outdir = ".",
  alpha = 0.05,
  invert = "+1",
  genes_shown = c("selected", "top", "all")
)

Arguments

systems, genes, probes

Data frames as described in the panel functions. genes and probes must carry system_id and outcome so they can be split.

nulls

Optional named list of permutation max|z| null vectors, named paste(system_id, outcome, sep = "|").

outdir

Directory for the png files.

alpha

Gate level. The gate is min(1, 2*min(p_dense, p_sparse)), so the boundary drawn is at alpha/2 on each arm.

invert

Which d is reflected onto the common axis: "+1" (default) reflects the probes whose methylation predicts HIGHER expression, "-1" reflects the others, "none" shows raw slopes.

With "+1" the axis reads: positive = the exposure moves methylation in the direction that LOWERS this gene's expression. Every d = -1 probe is shown as fitted (more methylation, less expression) and every d = +1 probe is reflected so that it means the same thing.

genes_shown

Which genes go into panel 3: "selected" (default) or "top" (the single strongest) or "all".

Value

Invisibly, a data frame of the files written.

Examples

## 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")]

Save the probes a frame actually analyses, annotated, as a CSV

Description

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).

Usage

dmsa_save_analysis_set(frame, file = "analysis_set.csv")

Arguments

frame

A dmsa_frame.

file

Path of the CSV to write, or NULL to only return the table.

Details

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.

Value

The analysis-set data frame, invisibly when file is given.

Examples

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)

Subject-level aligned tone score

Description

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.

Usage

dmsa_score(
  data,
  probes,
  alignment,
  method = c("expected", "fixed"),
  beta_input = TRUE
)

Arguments

data

data.frame.

probes

methylation columns, in the row order of alignment.

alignment

a dmsa_align() result.

method

"expected" or "fixed".

beta_input

TRUE if probes hold beta values.

Value

numeric vector, one value per row of data (NA where the probes are missing).

Examples

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)

Aligned set scores, and the alternatives worth comparing against

Description

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.

Usage

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
)

Arguments

M

Numeric matrix, samples x probes, on the analysis scale (M-values). Columns are standardised internally.

alignment

A dmsa_align() result for the same probes in the same order, or a numeric vector of multipliers.

method

"expected" uses 2*p_s_plus - 1; "fixed" uses s.

winsor

Winsorise each probe at this many MADs first; NULL disables. 3 is the default because it won the positive-control estimator grid - outliers otherwise dominate the composite.

flavours

Which projections to return. aligned is DMSA's;

weighting

Character. Probe weighting engine within a unit: "combined" (default) fuses the flat and reliability statistics on one shared permutation stream, "flat" weights every usable aligned probe equally, "reliability" weights each probe by its item-rest correlation with the rest of its unit. Weights are computed from methylation alone, so the permutation null is unaffected.

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 weighting = "flat". mean is the unweighted mean z that "averaging the gene set" means; pc1 is the regional principal component (DMRPC-style), oriented by the alignment so its sign stays interpretable; blind is mean |z|, the sign-blind control.

Value

data.frame of standardised scores, one column per flavour.

See Also

dmsa_score for the single aligned vector on a data.frame, dmsa_model for the upper-level model.

Examples

## Not run: 
al <- dmsa_align(dir_table, genes = gene, level = "gene")
S  <- dmsa_scores(M[, probes], al)
dat$oxytocin <- S$aligned

## End(Not run)

Split a SummarizedExperiment into the pieces DMSA tests

Description

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.

Usage

dmsa_se(se, assay = 1L, probes = NULL)

Arguments

se

A SummarizedExperiment, features (probes) in rows and samples in columns, as returned by resources such as recountmethylation.

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.

Details

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.

Value

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().

Examples

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)
}

Select a set of units from a cascade

Description

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.

Usage

dmsa_select(
  x = "alpha",
  systems = NULL,
  modules = "full",
  genes = "full",
  probes = "full",
  columns = NULL,
  data = NULL
)

Arguments

x

A dmsa_sets, or anything dmsa_sets() accepts.

systems

Short names (c("hpa", "oxytocin")), system ids, or full system names. NULL selects every system in the cascade. Matching is case-insensitive and accepts a unique prefix of a short name.

modules, genes, probes

"full" (default) or explicit ids/symbols to restrict to. modules takes module ids such as "2.6", genes takes symbols, probes takes CpG ids or probe ids.

columns

Which cascade column holds the data-matrix column names for this analysis - e.g. "col_parent_T1". "auto" picks the col_* column with the most overlap against data, when data is supplied. NULL leaves it unresolved.

data

Optional data.frame or matrix used by columns = "auto".

Value

An object of class dmsa_selection.

Examples

sel <- dmsa_select(systems = c("hpa", "oxytocin"))
sel
dmsa_select(systems = "hpa", modules = c("2.6", "2.8"))

Load a selection cascade (system > module > gene > probe)

Description

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.

Usage

dmsa_sets(x = "alpha", audit = NULL, polarity = NULL, name = NULL)

Arguments

x

Either "alpha" for the bundled, literature-audited Project Alpha 2026c cascade, a path to a CSV (optionally .gz) in the same shape, or a data.frame already in that shape. See dmsa_sets_template() for the schema and dmsa_sets_check() to validate a file before use.

audit

Optional module-level evidence table: a path or data.frame with one row per module_id and any of evidence_strength, audit_status, citation_keys, evidence_note, deep_search_url. Defaults to the bundled audit when x = "alpha", and to NULL otherwise. When a user cascade already carries these columns they are used and no separate table is needed.

polarity

Optional gene-to-system polarity table (path, data.frame or dmsa_polarity) supplying w_g. Defaults to the bundled Alpha polarity when x = "alpha", to the cascade's own w_g column when it has one, and otherwise to none - in which case a system-level score weights every gene +1 and the print method says so. Use dmsa_polarity_fetch() to draft one for your own panel from public databases.

name

Optional label, printed and carried into reports.

Value

An object of class dmsa_sets.

See Also

dmsa_systems() for the short names, dmsa_select() to choose systems, dmsa_evidence() for the module evidence table.

Examples

cas <- dmsa_sets()          # bundled Alpha 2026c
cas
dmsa_systems()                 # the short names to use in `systems =`

Validate a candidate cascade before using it

Description

Validate a candidate cascade before using it

Usage

dmsa_sets_check(x, verbose = TRUE)

Arguments

x

Path, data.frame, or dmsa_sets.

verbose

Print the report. Default TRUE.

Value

Invisibly, a list with ok and one entry per check.

Examples

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$ok

Write a cascade template and the instructions for building one

Description

Write a cascade template and the instructions for building one

Usage

dmsa_sets_template(path = "dmsa_sets_template.csv", example = TRUE)

Arguments

path

Where to write the template CSV. The instructions are written alongside it as a .md file with the same stem.

example

Include two filled example rows. Default TRUE.

Value

The paths written, invisibly.

Examples

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

Description

The systems available in a cascade, with the short names to select them by

Usage

dmsa_systems(x = "alpha", pattern = NULL)

Arguments

x

A dmsa_sets or dmsa_selection, or anything dmsa_sets() accepts. A bare string that is neither "alpha" nor an existing file path is treated as pattern, so dmsa_systems("hpa") searches the bundled cascade.

pattern

Optional case-insensitive regular expression; matched against the short name, the full system name and the system id.

Value

A data.frame with system_id, system_short, system and the module/gene/CpG counts. Printed as a table.

Examples

dmsa_systems()             # all 30
dmsa_systems("stress|hpa") # search

Fixed-sign or expected-sign DMSA pooled test

Description

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.

Usage

dmsa_test(b, se, alignment, method = c("expected", "fixed"), w = NULL)

Arguments

b, se

Probe-level coefficient and SE from YOUR model (fitted on M-values standardised within probe; same estimator observed and null).

alignment

A dmsa_align() result covering the same probes, or vectors via s / p_s_plus.

method

"fixed" or "expected".

w

Optional per-probe reliability weight (see dmsa_relweights); scales each aligned multiplier. NULL (default) or all ones is the flat engine.

Details

The z test here is descriptive; the reportable error control is the family-wise permutation (dmsa_perm_pvalue) on the same statistic.

Value

list: estimate (pooled aligned slope), se, z, p_normal, n_used, n_excluded, table (per-probe contributions).

Examples

## 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

Description

Re-run the frame's test drive

Usage

dmsa_test_drive(frame)

Arguments

frame

a dmsa_frame.

Value

the frame's corrections table, invisibly (printed first).

Examples

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)

Build a cascade tree for a set of probes from a reference bundle

Description

Returns the tree argument dmsa_cascade() expects: one row per probe, columns ordered outermost first.

Usage

dmsa_tree(reference, genes, system_id = NULL, levels = NULL)

Arguments

reference

A dmsa_reference.

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 c("system", "module") when a module layer exists, else "system". "gene" is always appended.

Value

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.

Examples

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

Description

Three complementary lenses on the same family

Usage

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
)

Arguments

M

Numeric matrix, samples x probes, methylation on the analysis scale (probes are the responses).

data

data.frame of predictors, rows aligned to M.

rhs

Right-hand-side terms, e.g. c("anx","avo","sex_c","chip_f") or with an interaction c("anx*ace", ...).

term

Design column to test, as model.matrix names it.

units

Character vector, one entry per column of M, naming the unit that column belongs to (typically the gene).

alignment

dmsa_align() result covering M's columns.

block

Permutation blocks: a column name of data (or several, combined by interaction), or a vector of labels, one per row. Rows in the same block travel together under permutation.

id

Optional participant identifier - a column name or per-row vector. Purely a safety declaration: if any participant contributes more than one row, block must keep those rows together, and this stops the analysis when it would not.

B

Permutations.

winsor

MADs for winsorisation; NULL to disable.

method

Pooling method for dmsa_test().

correction

"maxT" (default; keeps the information advantage of a unit with many concordant probes) or "minP".

weighting

Character. Probe weighting engine within a unit: "combined" (default) fuses the flat and reliability statistics on one shared permutation stream, "flat" weights every usable aligned probe equally, "reliability" weights each probe by its item-rest correlation with the rest of its unit. Weights are computed from methylation alone, so the permutation null is unaffected.

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 weighting = "flat".

seed

Optional integer.

ri_group

Optional grouping factor for a one-way random intercept, one entry per row of M (typically the chip). When supplied with more than one level, the aligned response and every design column are quasi-demeaned by a REML-estimated shrinkage factor before any permutation - the GLS solution for (1 | group), at a fraction of the cost of a mixed fit per probe. NULL (default) leaves the design untouched. The transform is applied identically under permutation, so it moves power, not type-I error.

Value

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.

Examples

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

Description

Save a coordinate table so the lookup never has to be repeated

Usage

dmsa_write_coords(coords, path = "probe_coords.csv")

Arguments

coords

Result of dmsa_probe_coords().

path

csv path.

Value

path, invisibly.

Examples

## 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)