lucid()
Wrapper and Tuning KThis tutorial is designed as a hands-on, end-to-end guide for fitting LUCID models on HELIX-style multi-omics data.
What you will learn:
G, Z,
Y, CoG, CoY) from one
dataset.Rho_* > 0) for
selection.Rho_* = 0) for stable inference +
bootstrap CI.Important runtime note:
K and do the screen-then-refit by
hand, so the steps are visible. Section 13 then shows
lucid() doing the same thing natively, with tuning over a
small K and penalty grid.K fixed and use small bootstrap R
so the vignette remains reproducible and fast.The HELIX example data (simulated_HELIX_data.rda)
provides:
phenotype: exposure/covariate/outcome-style
variables.methylome,
transcriptome, miRNA.Model inputs used throughout:
G: exposure matrix (main predictors of latent cluster
assignment). Built with a known signal structure – three exposures
correlated with the outcome and six pure-noise exposures – so that
feature selection can be checked against the truth rather than taken on
trust. The outcome itself is deliberately kept out of
G.Z: omics input (matrix for early, list for parallel,
nested list for serial mixed).Y: continuous outcome vector; Y_binary is
its median split, used to demonstrate the binary outcome model on the
same subjects.CoG: covariates for G -> X model.CoY: covariates for X -> Y model.| Hyperparameter | Meaning | Tutorial choice and rationale |
|---|---|---|
K |
number of latent clusters | fixed to small values for speed and interpretability |
Rho_G |
penalty on G -> X coefficients |
positive in screening fit, zero in inference refit |
Rho_Z_Mu |
penalty on cluster-specific omics means | positive in screening fit, zero in inference refit |
Rho_Z_Cov |
penalty on omics covariance matrices | positive in screening fit, zero in inference refit |
max_itr, max_tot.itr,
tol |
EM controls | modest values to balance speed/stability |
family |
outcome model family | normal for this tutorial |
R (in boot_lucid()) |
number of bootstrap resamples | kept tiny (2-5) here for speed; use hundreds for a real analysis |
seed |
reproducibility | fixed before each fit/bootstrap |
# Keep knitting on error, so that one failing step reports itself and the rest of
# the tutorial still runs. The status table in section 17 records what happened.
knitr::opts_chunk$set(error = TRUE)
# Lightweight registry so the document can verify itself rather than relying on
# the reader to notice a missing output.
.reg <- new.env(parent = emptyenv()); .reg$rows <- list()
check_obj <- function(name, expected_class = NULL, section = "") {
ok <- exists(name, envir = globalenv())
cls <- if (ok) class(get(name, envir = globalenv()))[1] else NA_character_
status <- if (!ok) "MISSING"
else if (!is.null(expected_class) && !identical(cls, expected_class)) "unexpected class"
else "ok"
.reg$rows[[length(.reg$rows) + 1L]] <-
data.frame(section = section, object = name, class = cls,
status = status, stringsAsFactors = FALSE)
invisible(NULL)
}
library(LUCIDus)
# The HELIX simulation object bundled with the package.
data(simulated_HELIX_data)This chunk creates a compact tutorial dataset and deliberately injects both:
so we can observe missing-data handling in summaries.
# Use a smaller subset for vignette speed while preserving model behavior.
idx <- 1:90
ph <- simulated_HELIX_data$phenotype[idx, ]
n <- nrow(ph)
set.seed(2026)
# ---------------------------------------------------------------------------
# A tutorial dataset with a KNOWN answer.
#
# The HELIX omics matrices are real simulated data with their own structure, and
# the exposures shipped with them have no relationship to it. That is fine for
# demonstrating that code runs, but it makes feature selection impossible to
# judge: there is no right answer to compare against. So we plant one.
#
# The generating story, which is the DAG LUCID assumes:
#
# causal exposures -> latent subgroup -> omics profile
# -> outcome
#
# Three exposures carry the subgroup signal with graded strength; six are pure
# noise. Half the features of each omics layer are shifted by subgroup
# membership; the rest are left as they came. Selection therefore has an
# unambiguous target, and the tutorial can check its answer instead of asserting
# it.
# ---------------------------------------------------------------------------
# The true latent subgroup. Retained so every selection claim below can be
# checked against it.
x_true <- rbinom(n, 1, 0.5)
# Exposures. g_causal_* predict subgroup membership; g_noise_* do not.
# Effect sizes are deliberately moderate. Stronger exposures make selection
# look better but drive the G -> X model to saturation, where every subject sits
# at posterior probability 1 and no counterfactual shift can move anything --
# which would make the g-computation demonstration in section 12.3 vacuous.
G <- cbind(
g_causal_1 = 1.0 * (x_true - 0.5) + rnorm(n, sd = 0.8), # strongest
g_causal_2 = -0.8 * (x_true - 0.5) + rnorm(n, sd = 0.8), # moderate, negative
g_causal_3 = 0.6 * (x_true - 0.5) + rnorm(n, sd = 0.8), # weakest
g_noise_1 = rnorm(n), g_noise_2 = rnorm(n), g_noise_3 = rnorm(n),
g_noise_4 = rnorm(n), g_noise_5 = rnorm(n), g_noise_6 = rnorm(n)
)
G <- as.matrix(scale(G))
causal_exposures <- c("g_causal_1", "g_causal_2", "g_causal_3")
# Exposure penalty used throughout. Section 7.2 shows what this value recovers;
# section 11 sweeps it, and sweeps the omics penalty separately.
RHO_G <- 0.05
# Covariates for G->X (CoG) and X->Y (CoY).
# Here we use age-related and sex covariates from phenotype.
CoG <- cbind(
hs_child_age_yrs_None = as.numeric(ph$hs_child_age_yrs_None),
sex_male = as.numeric(ph$e3_sex_None == "male")
)
CoY <- CoG
# Two outcomes on the SAME subjects, so the normal and binary results below are
# directly comparable: the only thing that changes between them is the outcome
# model, not the sample, the omics, or the injected missingness.
#
# Continuous outcome: the real CK-18 measurement, plus a subgroup effect so the
# cluster -> outcome arm of the model has something to estimate.
Y <- as.numeric(ph$ck18_scaled) + 1.2 * x_true
# Binary outcome: median split. The median is used rather than a higher
# threshold because it splits these 90 subjects 45/45, and a balanced outcome
# gives the K = 2 outcome model the most to work with at this sample size.
Y_binary <- as.integer(Y > median(Y))
cat("binary outcome balance:\n"); print(table(Y_binary))## binary outcome balance:
## Y_binary
## 0 1
## 45 45
# Construct three omics layers and standardize each, then plant the subgroup
# signal in the first three features of every layer. The remaining seven per
# layer are left as they came and act as omics noise.
meth <- scale(simulated_HELIX_data$methylome[idx, 1:10, drop = FALSE])
tran <- scale(simulated_HELIX_data$transcriptome[idx, 1:10, drop = FALSE])
mir <- scale(simulated_HELIX_data$miRNA[idx, 1:10, drop = FALSE])
signal_features <- 1:3
omics_shift <- 3.0
meth[, signal_features] <- meth[, signal_features] + omics_shift * x_true
tran[, signal_features] <- tran[, signal_features] - omics_shift * x_true
mir[, signal_features] <- mir[, signal_features] + omics_shift * x_true
# Column positions of the signal features once the layers are stacked for the
# early model, so selection can be scored against them later.
signal_cols_early <- c(signal_features,
ncol(meth) + signal_features,
ncol(meth) + ncol(tran) + signal_features)
# Early model uses one combined Z matrix.
Z_early <- cbind(meth, tran, mir)
# Parallel model uses list-of-layers.
Z_parallel <- list(methylome = meth, transcriptome = tran, miRNA = mir)
# Inject listwise + sporadic missingness for demonstration.
Z_early_miss <- Z_early
Z_early_miss[1, ] <- NA # listwise row
Z_early_miss[2:4, 1] <- NA # sporadic block
Z_early_miss[5, 3] <- NA # sporadic cell
Z_parallel_miss <- Z_parallel
Z_parallel_miss[[1]][1, ] <- NA # listwise in layer 1
Z_parallel_miss[[2]][2, 2] <- NA # sporadic in layer 2
Z_parallel_miss[[3]][3, 1] <- NA # sporadic in layer 3
# Quick structural sanity check.
str(list(
G = G,
CoG = CoG,
CoY = CoY,
Y = Y,
Z_early = Z_early_miss,
Z_parallel = Z_parallel_miss
), max.level = 1)## List of 6
## $ G : num [1:90, 1:9] 1.587 1.051 -0.744 0.168 0.834 ...
## ..- attr(*, "dimnames")=List of 2
## ..- attr(*, "scaled:center")= Named num [1:9] -0.0377 0.0866 0.0539 -0.0695 0.1933 ...
## .. ..- attr(*, "names")= chr [1:9] "g_causal_1" "g_causal_2" "g_causal_3" "g_noise_1" ...
## ..- attr(*, "scaled:scale")= Named num [1:9] 1.053 0.864 0.732 1.038 1.211 ...
## .. ..- attr(*, "names")= chr [1:9] "g_causal_1" "g_causal_2" "g_causal_3" "g_noise_1" ...
## $ CoG : num [1:90, 1:2] 7.48 7.21 8.59 8.48 6.06 ...
## ..- attr(*, "dimnames")=List of 2
## $ CoY : num [1:90, 1:2] 7.48 7.21 8.59 8.48 6.06 ...
## ..- attr(*, "dimnames")=List of 2
## $ Y : num [1:90] 1.817 1.67 0.479 -0.281 0.394 ...
## $ Z_early : num [1:90, 1:30] NA NA NA NA 3.17 ...
## ..- attr(*, "dimnames")=List of 2
## $ Z_parallel:List of 3
These helpers implement a robust refit pipeline:
G/Z inputs.# get_selected_G()/get_selected_Z() (from the package itself) already return a
# well-shaped, aligned logical mask straight from the fitted object -- no
# length mismatch is possible, since they derive it from the model's own
# recorded fields. The one thing left for a tutorial to decide is what to do
# if a penalty happened to deselect EVERY feature: refitting on zero columns
# would fail, so this keeps everything instead in that one edge case.
keep_or_all <- function(mask) if (any(mask, na.rm = TRUE)) mask else rep(TRUE, length(mask))
# Build selected-only inputs for early model.
prepare_early_selected_inputs <- function(fit_pen, G, Z) {
list(
G = as.matrix(G[, keep_or_all(get_selected_G(fit_pen)), drop = FALSE]),
Z = as.matrix(Z[, keep_or_all(get_selected_Z(fit_pen)), drop = FALSE])
)
}
# Build selected-only inputs for parallel model.
prepare_parallel_selected_inputs <- function(fit_pen, G, Z) {
keep_g <- keep_or_all(get_selected_G(fit_pen))
Z_sel <- lapply(seq_along(Z), function(i) {
zi <- as.matrix(Z[[i]])
zi[, keep_or_all(get_selected_Z(fit_pen, layer = i)), drop = FALSE]
})
names(Z_sel) <- names(Z)
list(
G = as.matrix(G[, keep_g, drop = FALSE]),
Z = Z_sel
)
}
# Serial stage>1 uses latent-cluster-derived "G" internally.
# We therefore subset stage-1 original G and each stage's Z where applicable.
prepare_serial_selected_inputs <- function(fit_pen, G, Z) {
G_refit <- as.matrix(G)
keep_g1 <- get_selected_G(fit_pen)
if (length(keep_g1) == ncol(G_refit)) {
G_refit <- G_refit[, keep_or_all(keep_g1), drop = FALSE]
}
selected_z <- get_selected_Z(fit_pen)
Z_refit <- Z
for (i in seq_along(fit_pen$submodel)) {
sm <- fit_pen$submodel[[i]]
if (inherits(sm, "early_lucid")) {
zi <- as.matrix(Z_refit[[i]])
Z_refit[[i]] <- zi[, keep_or_all(selected_z[[i]]), drop = FALSE]
} else if (inherits(sm, "lucid_parallel")) {
zi_list <- Z_refit[[i]]
for (j in seq_along(zi_list)) {
zij <- as.matrix(zi_list[[j]])
zi_list[[j]] <- zij[, keep_or_all(selected_z[[i]][[j]]), drop = FALSE]
}
Z_refit[[i]] <- zi_list
}
}
list(G = G_refit, Z = Z_refit)
}
# Zero-penalty refit, for any model type.
#
# The three model types previously had three byte-identical wrappers differing
# only in `lucid_model` and whether `useY` was forwarded; they are one function
# here. Everything about the model -- family, K, initialization, EM controls --
# is carried over from the screening fit, so the ONLY difference between the
# screening fit and this one is that the penalties are zero. That is what makes
# the refit estimates unshrunk and therefore suitable for bootstrap inference.
refit_selected <- function(model_type, fit_pen, inputs, Y,
CoG = NULL, CoY = NULL, seed = 1, verbose = FALSE) {
args <- list(
lucid_model = model_type,
G = inputs$G,
Z = inputs$Z,
Y = Y,
CoG = CoG,
CoY = CoY,
family = fit_pen$family,
K = fit_pen$K,
init_omic.data.model = fit_pen$init_omic.data.model,
init_impute = fit_pen$init_impute,
init_par = fit_pen$init_par,
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = fit_pen$em_control$max_itr,
max_tot.itr = fit_pen$em_control$max_tot.itr,
tol = fit_pen$em_control$tol,
seed = seed,
verbose = verbose
)
# Every fitted class records useY, so it is carried over for all three model
# types. The original three wrappers omitted it on the early path, which meant
# an unsupervised screening fit would have been silently refitted supervised.
args$useY <- fit_pen$useY
do.call(estimate_lucid, args)
}
# Dispatcher for the three input-preparation helpers above.
prepare_selected_inputs <- function(model_type, fit_pen, G, Z) {
switch(model_type,
early = prepare_early_selected_inputs(fit_pen, G, Z),
parallel = prepare_parallel_selected_inputs(fit_pen, G, Z),
serial = prepare_serial_selected_inputs(fit_pen, G, Z),
stop("unknown model_type: ", model_type)
)
}
# Compact stage-wise feature-selection report for serial fits, built entirely
# from get_selected_G()/get_selected_Z() -- no per-stage dispatch of its own.
serial_selection_report <- function(fit_serial_pen) {
selected_z <- get_selected_Z(fit_serial_pen)
out <- vector("list", length(fit_serial_pen$submodel))
for (i in seq_along(fit_serial_pen$submodel)) {
sm <- fit_serial_pen$submodel[[i]]
if (inherits(sm, "early_lucid")) {
out[[i]] <- list(
stage = i,
model = "early",
selected_G = if (i == 1) sum(get_selected_G(fit_serial_pen)) else NA,
total_G = if (i == 1) length(get_selected_G(fit_serial_pen)) else NA,
selected_Z = sum(selected_z[[i]]),
total_Z = length(selected_z[[i]])
)
} else {
out[[i]] <- list(
stage = i,
model = "parallel",
selected_G = if (i == 1) sum(get_selected_G(fit_serial_pen)) else NA,
total_G = if (i == 1) length(get_selected_G(fit_serial_pen)) else NA,
selected_Z_by_layer = sapply(selected_z[[i]], sum),
total_Z_by_layer = sapply(selected_z[[i]], length)
)
}
}
out
}Rho_* > 0)Interpretation target:
set.seed(1101)
# Screening model: penalties help identify a parsimonious subset.
early_fit_pen <- estimate_lucid(
lucid_model = "early",
G = G,
Z = Z_early_miss,
Y = Y,
CoG = CoG,
CoY = CoY,
family = "normal",
K = 2,
Rho_G = RHO_G,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 15,
max_tot.itr = 40,
tol = 1e-2,
seed = 1101,
verbose = FALSE
)## Fitting LUCID early model (K = 2)...
## Finished LUCID early model. Selected G: 4/9; Selected Z: 30/30.
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 1 / 90 (1.1%)
## Sporadic missing rows : 4 / 90 (4.4%)
## Missing cells total : 34 / 2700 (1.3%)
##
## Feature selection overview
## G features selected : 4 / 9 (44.4%)
## Z features selected : 30 / 30 (100.0%)
##
## Model fit statistics
## Log-likelihood : -3017.71
## BIC : 10548.73
## Number of parameters : 1003
##
## Regularization
## Rho_G : 0.050
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma
## (Intercept) 0.02464967
## cluster2 1.49980545
## hs_child_age_yrs_None 0.02165410
## sex_male 0.07030121
##
## (2) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## cg_GRHL3 1.1543296157 2.058870236
## cg_BTF3L4 1.1744273766 2.012566626
## cg_AL358472.7 1.0236239022 2.454782946
## cg_HDGF 0.0163608615 -0.032519086
## cg_TDRD5 0.0431530037 -0.148481256
## cg_CSRNP3 -0.0261459444 0.088311279
## cg_HSPD1 0.1803236589 -0.578765527
## cg_EPM2AIP1 -0.0042038336 0.011434519
## cg_AC025171.1 -0.0267854692 0.017327370
## cg_VTRNA1_3 0.0739126958 -0.269673022
## tc_TC01006069_nc -0.9713931499 -2.644045031
## tc_SLC9A4 -1.1102799358 -2.211705681
## tc_RAB6C_AS1 -1.1111594356 -2.254127299
## tc_LOC100129029 0.0619205718 -0.129531552
## tc_BRE 0.0219307188 -0.058832943
## tc_TC03001220_nc -0.0284856626 -0.006481685
## tc_TC04002114_nc 0.0883068847 -0.236455005
## tc_TC04002369_nc 0.0106849041 -0.023325912
## tc_BEND4 -0.0786234411 0.257013206
## tc_SLC9A3 0.1545597759 -0.400505271
## miR.101.3p 1.0047746226 2.525228725
## miR.125a.5p 1.0704611990 2.362679176
## miR.125b.1.3p 1.0373718165 2.451506323
## miR.127.3p -0.1390736642 0.456784794
## miR.140.5p -0.0009644617 0.005667020
## miR.142.3p -0.0937787966 0.259762116
## miR.144.5p -0.1228981798 0.348240526
## miR.19a.3p -0.0871818782 0.253085903
## miR.19b.3p -0.0554155122 0.163435553
## miR.21.5p -0.0352845287 0.121911272
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each exposure
## beta OR
## (Intercept).cluster2 3.21838105 24.9876337
## g_causal_1.cluster2 0.20289409 1.2249427
## g_causal_2.cluster2 -0.50164801 0.6055319
## g_causal_3.cluster2 0.09877953 1.1038229
## g_noise_5.cluster2 -0.24152982 0.7854254
## hs_child_age_yrs_None.cluster2 -0.64123438 0.5266419
## sex_male.cluster2 0.15550225 1.1682446
summary() above is a printed report;
early_fit_pen itself is a plain R list, and every field on
it is documented on ?estimate_lucid’s @return.
This is what an early-integration fit actually holds:
## [1] "res_Beta" "res_Mu" "res_Sigma"
## [4] "res_Gamma" "K" "var.names"
## [7] "init_omic.data.model" "likelihood" "inclusion.p"
## [10] "family" "select" "useY"
## [13] "Z" "init_impute" "init_par"
## [16] "Rho" "missing_summary" "em_control"
The fields fall into two groups:
res_Beta/res_Mu/res_Sigma/res_Gamma
(the G->X, X->Z, X->Y estimates), likelihood,
select (feature-selection indicators),
inclusion.p (posterior cluster membership), K,
var.names, family, useY,
Z, Rho (the penalties applied),
missing_summary, em_control (convergence
diagnostics), init_impute, init_par.N (the sample size) is present for
parallel/serial but not early (use nrow(fit$inclusion.p)
instead); z, res_Delta, and
submodel belong to parallel and serial only, shown in
sections 8 and 9.## log-likelihood: -3017.709
cat("exposures selected:", sum(get_selected_G(early_fit_pen)),
"of", length(get_selected_G(early_fit_pen)), "\n")## exposures selected: 4 of 9
## List of 9
## $ tol : num 0.01
## $ max_itr : num 15
## $ max_tot.itr : num 40
## $ converged : logi TRUE
## $ n_iter : num 12
## $ n_restart : int 0
## $ loglik_trace: num [1:12] -3150 -3122 -3089 -3077 -3068 ...
## $ n_starts : int 1
## $ n_starts_ok : int 1
early_selected_G <- names(which(get_selected_G(early_fit_pen)))
cat("exposures retained:", paste(early_selected_G, collapse = ", "), "\n\n")## exposures retained: g_causal_1, g_causal_2, g_causal_3, g_noise_5
# Score the selection against the planted truth from section 5: the three
# g_causal_* exposures drive the latent subgroup, the six g_noise_* do not.
data.frame(
exposure = colnames(G),
truth = ifelse(colnames(G) %in% causal_exposures, "causal", "noise"),
selected = colnames(G) %in% early_selected_G
)## exposure truth selected
## 1 g_causal_1 causal TRUE
## 2 g_causal_2 causal TRUE
## 3 g_causal_3 causal TRUE
## 4 g_noise_1 noise FALSE
## 5 g_noise_2 noise FALSE
## 6 g_noise_3 noise FALSE
## 7 g_noise_4 noise FALSE
## 8 g_noise_5 noise TRUE
## 9 g_noise_6 noise FALSE
##
## causal exposures kept : 3 of 3
## noise exposures kept : 1 of 6
# The omics side is unpenalized in this screening fit, so every feature is
# retained. Section 11.3 penalizes the omics separately and shows why.
cat("omics features retained:", sum(get_selected_Z(early_fit_pen)),
"of", length(get_selected_Z(early_fit_pen)), "(Rho_Z_Mu = 0 here)\n")## omics features retained: 30 of 30 (Rho_Z_Mu = 0 here)
Why this step:
set.seed(1102)
# Build selected-only inputs.
early_inputs_refit <- prepare_early_selected_inputs(early_fit_pen, G, Z_early_miss)
# Refit with all penalties set to zero.
early_fit_refit <- refit_selected(
"early",
fit_pen = early_fit_pen,
inputs = early_inputs_refit,
Y = Y,
CoG = CoG,
CoY = CoY,
seed = 1102,
verbose = TRUE
)## Intializing imputation of missing values in 'Z' via LOD / sqrt(2)
##
## Initialize LUCID with mclust based on inclusion probabilities given by mclust
## Fitting Early Integration LUCID model (K = 2, Rho_G = 0, Rho_Z_Mu = 0, Rho_Z_Cov = 0)
## iteration 1 : E-step finished.
## iteration 1: log-likelihood = -3325.675
## iteration 2 : E-step finished.
## iteration 2: log-likelihood = -3316.160
## iteration 3 : E-step finished.
## iteration 3: log-likelihood = -3314.785
## iteration 4 : E-step finished.
## iteration 4: log-likelihood = -3314.499
## iteration 5 : E-step finished.
## iteration 5: log-likelihood = -3314.437
## iteration 6 : E-step finished.
## iteration 6: log-likelihood = -3314.423
## iteration 7 : E-step finished.
## iteration 7: log-likelihood = -3314.420
## Success: LUCID early model converged!
##
## Finished LUCID early model: log-likelihood = -3314.420.
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 1 / 90 (1.1%)
## Sporadic missing rows : 4 / 90 (4.4%)
## Missing cells total : 34 / 2700 (1.3%)
##
## Feature selection overview
## G features selected : 4 / 4 (100.0%)
## Z features selected : 30 / 30 (100.0%)
##
## Model fit statistics
## Log-likelihood : -3314.42
## BIC : 11142.15
## Number of parameters : 1003
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma
## (Intercept) 1.7218477
## cluster2 0.0000000
## hs_child_age_yrs_None -0.1654292
## sex_male 0.1034161
##
## (2) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## cg_GRHL3 1.3734684264 1.3734684264
## cg_BTF3L4 1.3816913508 1.3816913508
## cg_AL358472.7 1.3987916378 1.3987916378
## cg_HDGF 0.0042733091 0.0042733091
## cg_TDRD5 -0.0042363512 -0.0042363512
## cg_CSRNP3 0.0021582533 0.0021582533
## cg_HSPD1 -0.0073919764 -0.0073919764
## cg_EPM2AIP1 -0.0003366158 -0.0003366158
## cg_AC025171.1 -0.0158767778 -0.0158767778
## cg_VTRNA1_3 -0.0110528294 -0.0110528294
## tc_TC01006069_nc -1.3850242700 -1.3850242700
## tc_SLC9A4 -1.3826521909 -1.3826521909
## tc_RAB6C_AS1 -1.3938046667 -1.3938046667
## tc_LOC100129029 0.0145762574 0.0145762574
## tc_BRE 0.0019586229 0.0019586229
## tc_TC03001220_nc -0.0230442854 -0.0230442854
## tc_TC04002114_nc 0.0079963157 0.0079963157
## tc_TC04002369_nc 0.0022743484 0.0022743484
## tc_BEND4 0.0043763535 0.0043763535
## tc_SLC9A3 0.0172973964 0.0172973964
## miR.101.3p 1.3807686597 1.3807686597
## miR.125a.5p 1.3900145832 1.3900145832
## miR.125b.1.3p 1.3870740149 1.3870740149
## miR.127.3p 0.0082765431 0.0082765431
## miR.140.5p 0.0006754416 0.0006754416
## miR.142.3p -0.0063514452 -0.0063514452
## miR.144.5p -0.0063899974 -0.0063899974
## miR.19a.3p -0.0030368478 -0.0030368478
## miR.19b.3p -0.0012956967 -0.0012956967
## miR.21.5p 0.0035885183 0.0035885183
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each exposure
## beta OR
## (Intercept).cluster2 9.000000e+00 8.103084e+03
## g_causal_1.cluster2 -1.804112e-16 1.000000e+00
## g_causal_2.cluster2 -6.036838e-16 1.000000e+00
## g_causal_3.cluster2 0.000000e+00 1.000000e+00
## g_noise_5.cluster2 -2.997602e-16 1.000000e+00
## hs_child_age_yrs_None.cluster2 6.431098e+01 8.509462e+27
## sex_male.cluster2 4.600000e+00 9.948432e+01
Compare this summary against the penalized fit’s in 7.1: the coefficients on the selected exposures/features should be of similar sign and rough magnitude (the model didn’t change what it found, only stopped shrinking it), while any exposure/feature that was zeroed out in the screening step is now simply absent from the model rather than penalized toward zero. This refit – not the penalized screening fit – is the model bootstrapped next.
set.seed(1103)
# Bootstrap on zero-penalty refit for CI inference.
early_boot <- boot_lucid(
G = early_inputs_refit$G,
Z = early_inputs_refit$Z,
Y = Y,
CoG = CoG,
CoY = CoY,
model = early_fit_refit,
R = 2,
conf = 0.90
)
# Print summary with CI columns integrated into parameter tables.
summary(early_fit_refit, boot.se = early_boot)##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 1 / 90 (1.1%)
## Sporadic missing rows : 4 / 90 (4.4%)
## Missing cells total : 34 / 2700 (1.3%)
##
## Feature selection overview
## G features selected : 4 / 4 (100.0%)
## Z features selected : 30 / 30 (100.0%)
##
## Model fit statistics
## Log-likelihood : -3314.42
## BIC : 11142.15
## Number of parameters : 1003
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma norm_lower norm_upper sig
## (Intercept) 1.7218477 1.2385705 3.01142394 *
## cluster2 0.0000000 -1.5553103 0.36962057
## hs_child_age_yrs_None -0.1654292 -0.3358611 -0.04702722 *
## sex_male 0.1034161 -0.3357313 0.49041869
##
## (2) Z: mean of omics data for each latent cluster
## estimate norm_lower norm_upper sig
## cg_GRHL3.cluster1 1.3734684264 1.90846980 2.099752509 *
## cg_BTF3L4.cluster1 1.3816913508 1.83254870 1.989602820 *
## cg_AL358472.7.cluster1 1.3987916378 1.94137047 2.383747404 *
## cg_HDGF.cluster1 0.0042733091 -0.11058955 -0.036376678 *
## cg_TDRD5.cluster1 -0.0042363512 -0.09103763 0.065854846
## cg_CSRNP3.cluster1 0.0021582533 -0.09549347 0.328479723
## cg_HSPD1.cluster1 -0.0073919764 -0.06766347 0.234889561
## cg_EPM2AIP1.cluster1 -0.0003366158 -0.02304176 0.095071686
## cg_AC025171.1.cluster1 -0.0158767778 -0.19708100 0.231882365
## cg_VTRNA1_3.cluster1 -0.0110528294 -0.27535309 0.518373678
## tc_TC01006069_nc.cluster1 -1.3850242700 -1.91767623 -1.843572366 *
## tc_SLC9A4.cluster1 -1.3826521909 -2.62942724 -1.706706039 *
## tc_RAB6C_AS1.cluster1 -1.3938046667 -2.12519331 -1.866292914 *
## tc_LOC100129029.cluster1 0.0145762574 0.09153418 0.200623157 *
## tc_BRE.cluster1 0.0019586229 -0.04628868 0.206457674
## tc_TC03001220_nc.cluster1 -0.0230442854 -0.18159018 0.037166591
## tc_TC04002114_nc.cluster1 0.0079963157 -0.65693964 0.444745777
## tc_TC04002369_nc.cluster1 0.0022743484 -0.10928348 0.526175843
## tc_BEND4.cluster1 0.0043763535 -0.18605279 0.108222928
## tc_SLC9A3.cluster1 0.0172973964 -0.02750211 0.355561877
## miR.101.3p.cluster1 1.3807686597 1.33868063 2.689737042 *
## miR.125a.5p.cluster1 1.3900145832 1.46813780 2.367798998 *
## miR.125b.1.3p.cluster1 1.3870740149 1.75393173 2.092505734 *
## miR.127.3p.cluster1 0.0082765431 -0.11139779 0.312129120
## miR.140.5p.cluster1 0.0006754416 -0.22314247 0.093229150
## miR.142.3p.cluster1 -0.0063514452 -0.16926488 0.306768637
## miR.144.5p.cluster1 -0.0063899974 -0.06030234 -0.002823870 *
## miR.19a.3p.cluster1 -0.0030368478 -0.55901496 0.448739731
## miR.19b.3p.cluster1 -0.0012956967 -0.28906636 0.210747748
## miR.21.5p.cluster1 0.0035885183 0.01047006 0.016498018 *
## cg_GRHL3.cluster2 1.3734684264 -1.55720482 1.883631792
## cg_BTF3L4.cluster2 1.3816913508 -0.28523845 1.029418606
## cg_AL358472.7.cluster2 1.3987916378 -1.55863437 1.605542467
## cg_HDGF.cluster2 0.0042733091 -0.16774990 0.105639717
## cg_TDRD5.cluster2 -0.0042363512 -0.81023079 1.081631057
## cg_CSRNP3.cluster2 0.0021582533 -0.33869197 -0.253422427 *
## cg_HSPD1.cluster2 -0.0073919764 -0.19332569 0.503579948
## cg_EPM2AIP1.cluster2 -0.0003366158 -0.78257057 0.944107967
## cg_AC025171.1.cluster2 -0.0158767778 -0.36138130 0.021457404
## cg_VTRNA1_3.cluster2 -0.0110528294 -1.36369709 1.174824402
## tc_TC01006069_nc.cluster2 -1.3850242700 -1.85290408 1.112239037
## tc_SLC9A4.cluster2 -1.3826521909 -0.34755772 -0.230606697 *
## tc_RAB6C_AS1.cluster2 -1.3938046667 -2.31253644 1.528463219
## tc_LOC100129029.cluster2 0.0145762574 -1.17838339 1.050708786
## tc_BRE.cluster2 0.0019586229 -0.67894553 0.992384542
## tc_TC03001220_nc.cluster2 -0.0230442854 -0.10425668 0.047516633
## tc_TC04002114_nc.cluster2 0.0079963157 -0.78174865 0.478257313
## tc_TC04002369_nc.cluster2 0.0022743484 -0.58289999 0.471529202
## tc_BEND4.cluster2 0.0043763535 0.04912507 0.166455194 *
## tc_SLC9A3.cluster2 0.0172973964 0.08586256 0.201022279 *
## miR.101.3p.cluster2 1.3807686597 -0.46277417 0.580242183
## miR.125a.5p.cluster2 1.3900145832 -0.64905520 1.667808828
## miR.125b.1.3p.cluster2 1.3870740149 -1.69930091 1.964266389
## miR.127.3p.cluster2 0.0082765431 -0.35641390 0.428374949
## miR.140.5p.cluster2 0.0006754416 -0.46042607 0.007964872
## miR.142.3p.cluster2 -0.0063514452 -0.49059440 0.096466692
## miR.144.5p.cluster2 -0.0063899974 -0.45356660 0.550907473
## miR.19a.3p.cluster2 -0.0030368478 -0.72332598 0.583055530
## miR.19b.3p.cluster2 -0.0012956967 -0.31362693 0.134213604
## miR.21.5p.cluster2 0.0035885183 -0.29477439 0.178501284
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each exposure
## estimate norm_lower norm_upper sig
## g_causal_1.cluster2 -1.804112e-16 -0.6976451 0.3321579
## g_causal_2.cluster2 -6.036838e-16 -0.2419068 2.4689701
## g_causal_3.cluster2 0.000000e+00 -0.8445345 -0.1721251 *
## g_noise_5.cluster2 -2.997602e-16 -0.7957648 1.2030635
Each coefficient table above now carries a normal-theory confidence
interval plus the sig column: "*" marks a row
whose interval excludes 0, i.e. an effect the bootstrap distinguishes
from no effect at all. With only R = 2 replicates here the
intervals themselves are not trustworthy – this is a code-pattern
demonstration, not a real inference; a real analysis needs
R in the hundreds, as noted in section 3’s hyperparameter
guide.
Interpretation target:
set.seed(1201)
parallel_fit_pen <- estimate_lucid(
lucid_model = "parallel",
G = G,
Z = Z_parallel_miss,
Y = Y,
CoG = CoG,
CoY = CoY,
family = "normal",
K = c(2, 2, 2),
Rho_G = RHO_G,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 15,
max_tot.itr = 40,
tol = 1e-2,
seed = 1201,
verbose = FALSE
)## Fitting LUCID parallel model (3 layers)...
## Finished LUCID parallel model. Selected G: 3/9; Selected Z by layer: 10/10, 10/10, 10/10.
##
## ====================================================
## LUCID Parallel: Model Summary
## ====================================================
##
## Model specification
## Family : gaussian
## Number of observations : 90
## Clusters per layer : 2, 2, 2
##
## Missing-data profile by layer
## Layer 1 listwise rows : 1 / 90 (1.1%)
## Layer 1 sporadic rows : 0 / 90 (0.0%)
## Layer 1 missing cells : 10 / 900 (1.1%)
## Layer 2 listwise rows : 0 / 90 (0.0%)
## Layer 2 sporadic rows : 1 / 90 (1.1%)
## Layer 2 missing cells : 1 / 900 (0.1%)
## Layer 3 listwise rows : 0 / 90 (0.0%)
## Layer 3 sporadic rows : 1 / 90 (1.1%)
## Layer 3 missing cells : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 3 / 9 (33.3%)
## G features by layer
## Layer 1 : 3 / 9 (33.3%)
## Layer 2 : 3 / 9 (33.3%)
## Layer 3 : 3 / 9 (33.3%)
## Z features
## Layer 1 selected : 10 / 10 (100.0%)
## Layer 1 multi-cluster: 10
## Layer 2 selected : 10 / 10 (100.0%)
## Layer 2 multi-cluster: 10
## Layer 3 selected : 10 / 10 (100.0%)
## Layer 3 multi-cluster: 10
##
## Model fit statistics
## Log-likelihood : -3417.55
## BIC : 8702.52
## Number of parameters : 415
##
## Regularization
## Rho_G : 0.050
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): intercept, effects of each non-reference latent cluster for each layer of Y (and effect of covariates if included)
## Gamma
## (Intercept) 0.90388527
## Layer1_LC2 0.52583758
## Layer2_LC2 0.10997526
## Layer3_LC2 0.73829435
## hs_child_age_yrs_None -0.12953921
## sex_male -0.08827964
##
## (2) Z: mean of omics data for each latent cluster of each layer
## Layer 1
##
## mu_cluster1 mu_cluster2
## cg_GRHL3 0.01944230 2.78671098
## cg_BTF3L4 0.02535503 2.77114802
## cg_AL358472.7 -0.05886647 2.85100872
## cg_HDGF 0.19363958 -0.18971710
## cg_TDRD5 -0.13897998 0.13379758
## cg_CSRNP3 -0.07415664 0.08033668
## cg_HSPD1 0.18552243 -0.20501715
## cg_EPM2AIP1 -0.03920856 0.03948454
## cg_AC025171.1 -0.07330782 0.04295667
## cg_VTRNA1_3 0.15846120 -0.18470622
##
## Layer 2
##
## mu_cluster1 mu_cluster2
## tc_TC01006069_nc -0.151915803 -2.761778697
## tc_SLC9A4 0.061894410 -3.024122495
## tc_RAB6C_AS1 -0.175486361 -2.736060974
## tc_LOC100129029 0.043355812 -0.047305319
## tc_BRE 0.068234642 -0.074450491
## tc_TC03001220_nc -0.027376915 0.029870821
## tc_TC04002114_nc 0.028645224 -0.031254667
## tc_TC04002369_nc -0.121803704 0.132899439
## tc_BEND4 0.079874133 -0.087150285
## tc_SLC9A3 0.007825153 -0.008537987
##
## Layer 3
##
## mu_cluster1 mu_cluster2
## miR.101.3p -0.006887544 3.007913039
## miR.125a.5p 0.187538247 2.785670575
## miR.125b.1.3p 0.027408679 2.968675795
## miR.127.3p -0.107628701 0.123004230
## miR.140.5p 0.090862876 -0.103843286
## miR.142.3p -0.004784527 0.005468031
## miR.144.5p 0.080101772 -0.091544883
## miR.19a.3p 0.060465036 -0.069102898
## miR.19b.3p 0.108156640 -0.123607589
## miR.21.5p 0.102286783 -0.116899180
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each exposure for each layer
## Layer 1
##
## beta OR
## (Intercept).cluster2 -0.24677085 0.7813197
## g_causal_1.cluster2 0.81378250 2.2564268
## g_causal_2.cluster2 -0.77415373 0.4610938
## g_causal_3.cluster2 0.05586156 1.0574513
##
## Layer 2
##
## beta OR
## (Intercept).cluster2 -0.8436717 0.4301283
## g_causal_1.cluster2 0.9246242 2.5209208
## g_causal_2.cluster2 -0.8914857 0.4100461
## g_causal_3.cluster2 0.2035521 1.2257490
##
## Layer 3
##
## beta OR
## (Intercept).cluster2 -1.3654847 0.2552569
## g_causal_1.cluster2 0.9934557 2.7005507
## g_causal_2.cluster2 -0.8493218 0.4277049
## g_causal_3.cluster2 0.1937766 1.2138251
## [1] "res_Beta" "res_Mu" "res_Sigma"
## [4] "res_Gamma" "K" "N"
## [7] "var.names" "init_omic.data.model" "likelihood"
## [10] "inclusion.p" "family" "select"
## [13] "useY" "Z" "z"
## [16] "init_impute" "init_par" "Rho"
## [19] "missing_summary" "em_control"
Compared to the early fit in section 7.1b, two fields are new and two common fields change shape:
N (sample size) is present here but
not for early.z is the joint E-step responsibility
array across all layers, before it is marginalized into
inclusion.p – present for parallel only; nothing else in
the package reads it, so it is shown here purely for completeness.select gains layer structure:
selectG_layer (per-layer exposure selection, alongside the
selectG union already seen for early), and
selectZ is now a list, one entry per omics layer.res_Beta/res_Mu/res_Sigma/inclusion.p
are lists indexed by layer rather than single matrices.## log-likelihood: -3417.549
## sample size (N): 90
## exposures selected per layer:
print(sapply(seq_along(parallel_fit_pen$K), function(i) sum(get_selected_G(parallel_fit_pen, layer = i))))## [1] 3 3 3
# Exposure selection union across layers.
parallel_selected_G_union <- names(which(get_selected_G(parallel_fit_pen)))
# Exposure selection per layer.
parallel_selected_G_layer <- lapply(seq_along(parallel_fit_pen$K), function(i) {
names(which(get_selected_G(parallel_fit_pen, layer = i)))
})
# Omics selection per layer (get_selected_Z already collapses any
# vector/matrix selection encoding to one logical value per feature).
parallel_selected_Z_layer <- lapply(get_selected_Z(parallel_fit_pen), function(s) names(which(s)))
parallel_selected_G_union## [1] "g_causal_1" "g_causal_2" "g_causal_3"
## [[1]]
## [1] "g_causal_1" "g_causal_2" "g_causal_3"
##
## [[2]]
## [1] "g_causal_1" "g_causal_2" "g_causal_3"
##
## [[3]]
## [1] "g_causal_1" "g_causal_2" "g_causal_3"
## $methylome
## [1] "cg_GRHL3" "cg_BTF3L4" "cg_AL358472.7" "cg_HDGF"
## [5] "cg_TDRD5" "cg_CSRNP3" "cg_HSPD1" "cg_EPM2AIP1"
## [9] "cg_AC025171.1" "cg_VTRNA1_3"
##
## $transcriptome
## [1] "tc_TC01006069_nc" "tc_SLC9A4" "tc_RAB6C_AS1" "tc_LOC100129029"
## [5] "tc_BRE" "tc_TC03001220_nc" "tc_TC04002114_nc" "tc_TC04002369_nc"
## [9] "tc_BEND4" "tc_SLC9A3"
##
## $miRNA
## [1] "miR.101.3p" "miR.125a.5p" "miR.125b.1.3p" "miR.127.3p"
## [5] "miR.140.5p" "miR.142.3p" "miR.144.5p" "miR.19a.3p"
## [9] "miR.19b.3p" "miR.21.5p"
set.seed(1202)
parallel_inputs_refit <- prepare_parallel_selected_inputs(parallel_fit_pen, G, Z_parallel_miss)
parallel_fit_refit <- refit_selected(
"parallel",
fit_pen = parallel_fit_pen,
inputs = parallel_inputs_refit,
Y = Y,
CoG = CoG,
CoY = CoY,
seed = 1202,
verbose = TRUE
)## Intializing imputation of missing values in 'Z' via LOD / sqrt(2)
##
## Intializing imputation of missing values in 'Z' via LOD / sqrt(2)
##
## Fitting LUCID in Parallel model (K = 2, Rho_G = 0, Rho_Z_Mu = 0, Rho_Z_Cov = 0) (K = 2, Rho_G = 0, Rho_Z_Mu = 0, Rho_Z_Cov = 0) (K = 2, Rho_G = 0, Rho_Z_Mu = 0, Rho_Z_Cov = 0)
## iteration 1 : E-step finished.
## iteration 1: log-likelihood = -3404.888
## iteration 2 : E-step finished.
## iteration 2: log-likelihood = -3403.244
## iteration 3 : E-step finished.
## iteration 3: log-likelihood = -3403.223
## iteration 4 : E-step finished.
## iteration 4: log-likelihood = -3403.223
## Success: LUCID parallel model converged!
##
## Finished LUCID parallel model: log-likelihood = -3403.223.
##
## ====================================================
## LUCID Parallel: Model Summary
## ====================================================
##
## Model specification
## Family : gaussian
## Number of observations : 90
## Clusters per layer : 2, 2, 2
##
## Missing-data profile by layer
## Layer 1 listwise rows : 1 / 90 (1.1%)
## Layer 1 sporadic rows : 0 / 90 (0.0%)
## Layer 1 missing cells : 10 / 900 (1.1%)
## Layer 2 listwise rows : 0 / 90 (0.0%)
## Layer 2 sporadic rows : 1 / 90 (1.1%)
## Layer 2 missing cells : 1 / 900 (0.1%)
## Layer 3 listwise rows : 0 / 90 (0.0%)
## Layer 3 sporadic rows : 1 / 90 (1.1%)
## Layer 3 missing cells : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 3 / 3 (100.0%)
## G features by layer
## Layer 1 : 3 / 3 (100.0%)
## Layer 2 : 3 / 3 (100.0%)
## Layer 3 : 3 / 3 (100.0%)
## Z features
## Layer 1 selected : 10 / 10 (100.0%)
## Layer 1 multi-cluster: 10
## Layer 2 selected : 10 / 10 (100.0%)
## Layer 2 multi-cluster: 10
## Layer 3 selected : 10 / 10 (100.0%)
## Layer 3 multi-cluster: 10
##
## Model fit statistics
## Log-likelihood : -3403.22
## BIC : 8673.87
## Number of parameters : 415
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): intercept, effects of each non-reference latent cluster for each layer of Y (and effect of covariates if included)
## Gamma
## (Intercept) 0.90197504
## Layer1_LC2 0.55468259
## Layer2_LC2 0.08656150
## Layer3_LC2 0.73329966
## hs_child_age_yrs_None -0.12940582
## sex_male -0.08911495
##
## (2) Z: mean of omics data for each latent cluster of each layer
## Layer 1
##
## mu_cluster1 mu_cluster2
## cg_GRHL3 0.01911047 2.78541234
## cg_BTF3L4 0.02478402 2.77010682
## cg_AL358472.7 -0.05983045 2.85027248
## cg_HDGF 0.19299008 -0.18882557
## cg_TDRD5 -0.13874086 0.13339143
## cg_CSRNP3 -0.07401232 0.08009755
## cg_HSPD1 0.18605910 -0.20533509
## cg_EPM2AIP1 -0.03848329 0.03869584
## cg_AC025171.1 -0.07288049 0.04245059
## cg_VTRNA1_3 0.15835252 -0.18439186
##
## Layer 2
##
## mu_cluster1 mu_cluster2
## tc_TC01006069_nc -0.150718477 -2.761644116
## tc_SLC9A4 0.062093261 -3.022512916
## tc_RAB6C_AS1 -0.174660692 -2.735548496
## tc_LOC100129029 0.043334559 -0.047232147
## tc_BRE 0.067975103 -0.074088904
## tc_TC03001220_nc -0.027151255 0.029593287
## tc_TC04002114_nc 0.029476766 -0.032127959
## tc_TC04002369_nc -0.122166463 0.133154331
## tc_BEND4 0.079580265 -0.086737855
## tc_SLC9A3 0.007320192 -0.007978583
##
## Layer 3
##
## mu_cluster1 mu_cluster2
## miR.101.3p -0.006887544 3.007913039
## miR.125a.5p 0.187538247 2.785670575
## miR.125b.1.3p 0.027408679 2.968675795
## miR.127.3p -0.107628701 0.123004230
## miR.140.5p 0.090862876 -0.103843286
## miR.142.3p -0.004784527 0.005468031
## miR.144.5p 0.080101772 -0.091544883
## miR.19a.3p 0.060465036 -0.069102898
## miR.19b.3p 0.108156640 -0.123607589
## miR.21.5p 0.102286783 -0.116899180
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each exposure for each layer
## Layer 1
##
## beta OR
## (Intercept).cluster2 -1.1631418 0.3125028
## g_causal_1.cluster2 1.4640897 4.3236056
## g_causal_2.cluster2 -1.4811806 0.2273691
## g_causal_3.cluster2 0.4026573 1.4957941
##
## Layer 2
##
## beta OR
## (Intercept).cluster2 -2.2697448 0.1033385
## g_causal_1.cluster2 1.8277068 6.2196073
## g_causal_2.cluster2 -1.9585513 0.1410626
## g_causal_3.cluster2 0.6732859 1.9606694
##
## Layer 3
##
## beta OR
## (Intercept).cluster2 -3.2332090 0.03943076
## g_causal_1.cluster2 1.9557658 7.06933069
## g_causal_2.cluster2 -1.9426942 0.14331731
## g_causal_3.cluster2 0.6703573 1.95493576
Unlike the early refit in 7.3, this summary prints one
G -> X and one X -> Z block per
layer – each layer’s cluster variable is its own model, refit
unpenalized on that layer’s own selected features.
set.seed(1203)
parallel_boot <- boot_lucid(
G = parallel_inputs_refit$G,
Z = parallel_inputs_refit$Z,
Y = Y,
CoG = CoG,
CoY = CoY,
model = parallel_fit_refit,
R = 2,
conf = 0.90
)
summary(parallel_fit_refit, boot.se = parallel_boot)##
## ====================================================
## LUCID Parallel: Model Summary
## ====================================================
##
## Model specification
## Family : gaussian
## Number of observations : 90
## Clusters per layer : 2, 2, 2
##
## Missing-data profile by layer
## Layer 1 listwise rows : 1 / 90 (1.1%)
## Layer 1 sporadic rows : 0 / 90 (0.0%)
## Layer 1 missing cells : 10 / 900 (1.1%)
## Layer 2 listwise rows : 0 / 90 (0.0%)
## Layer 2 sporadic rows : 1 / 90 (1.1%)
## Layer 2 missing cells : 1 / 900 (0.1%)
## Layer 3 listwise rows : 0 / 90 (0.0%)
## Layer 3 sporadic rows : 1 / 90 (1.1%)
## Layer 3 missing cells : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 3 / 3 (100.0%)
## G features by layer
## Layer 1 : 3 / 3 (100.0%)
## Layer 2 : 3 / 3 (100.0%)
## Layer 3 : 3 / 3 (100.0%)
## Z features
## Layer 1 selected : 10 / 10 (100.0%)
## Layer 1 multi-cluster: 10
## Layer 2 selected : 10 / 10 (100.0%)
## Layer 2 multi-cluster: 10
## Layer 3 selected : 10 / 10 (100.0%)
## Layer 3 multi-cluster: 10
##
## Model fit statistics
## Log-likelihood : -3403.22
## BIC : 8673.87
## Number of parameters : 415
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): intercept, effects of each non-reference latent cluster for each layer of Y (and effect of covariates if included)
## Gamma norm_lower norm_upper sig
## (Intercept) 0.90197504 -1.82927556 3.19573424
## Layer1_LC2 0.55468259 0.14853235 0.57423132 *
## Layer2_LC2 0.08656150 -2.29068484 0.82333900
## Layer3_LC2 0.73329966 -2.91408296 2.11115240
## hs_child_age_yrs_None -0.12940582 0.00806368 0.06297141 *
## sex_male -0.08911495 -0.32395406 0.66050824
##
## (2) Z: mean of omics data for each latent cluster of each layer
## Layer 1
##
## estimate norm_lower norm_upper sig
## Layer1.cg_GRHL3.cluster1 0.01911047 -4.61289075 0.42172539
## Layer1.cg_BTF3L4.cluster1 0.02478402 -4.87904227 0.71550321
## Layer1.cg_AL358472.7.cluster1 -0.05983045 -5.46219016 0.95522043
## Layer1.cg_HDGF.cluster1 0.19299008 0.14880101 0.35476226 *
## Layer1.cg_TDRD5.cluster1 -0.13874086 -0.50361142 -0.15483002 *
## Layer1.cg_CSRNP3.cluster1 -0.07401232 -1.10895254 0.49673336
## Layer1.cg_HSPD1.cluster1 0.18605910 0.32061011 0.45740944 *
## Layer1.cg_EPM2AIP1.cluster1 -0.03848329 -0.42730073 0.21512999
## Layer1.cg_AC025171.1.cluster1 -0.07288049 -0.51415546 -0.03944904 *
## Layer1.cg_VTRNA1_3.cluster1 0.15835252 -0.28020161 1.27019123
## Layer1.cg_GRHL3.cluster2 2.78541234 1.41491171 6.11778337 *
## Layer1.cg_BTF3L4.cluster2 2.77010682 1.78935186 5.70847126 *
## Layer1.cg_AL358472.7.cluster2 2.85027248 0.23304003 7.08251062 *
## Layer1.cg_HDGF.cluster2 -0.18882557 -0.64494552 -0.14308088 *
## Layer1.cg_TDRD5.cluster2 0.13339143 -0.15858406 0.67798636
## Layer1.cg_CSRNP3.cluster2 0.08009755 -0.35050717 1.19247352
## Layer1.cg_HSPD1.cluster2 -0.20533509 -0.71952057 0.10876138
## Layer1.cg_EPM2AIP1.cluster2 0.03869584 -0.01253431 0.03733736
## Layer1.cg_AC025171.1.cluster2 0.04245059 0.10650105 0.23746119 *
## Layer1.cg_VTRNA1_3.cluster2 -0.18439186 -1.27107973 0.78344846
##
## Layer 2
##
## estimate norm_lower norm_upper sig
## Layer2.tc_TC01006069_nc.cluster1 -0.150718477 2.05128170 2.81316525 *
## Layer2.tc_SLC9A4.cluster1 0.062093261 2.56829266 3.96163161 *
## Layer2.tc_RAB6C_AS1.cluster1 -0.174660692 2.12434391 3.43675381 *
## Layer2.tc_LOC100129029.cluster1 0.043334559 -0.37341577 0.31185468
## Layer2.tc_BRE.cluster1 0.067975103 0.14597243 0.25298525 *
## Layer2.tc_TC03001220_nc.cluster1 -0.027151255 -0.52708619 0.41881040
## Layer2.tc_TC04002114_nc.cluster1 0.029476766 -0.11392084 0.52713969
## Layer2.tc_TC04002369_nc.cluster1 -0.122166463 -0.94294136 0.38815009
## Layer2.tc_BEND4.cluster1 0.079580265 0.21247640 0.45229697 *
## Layer2.tc_SLC9A3.cluster1 0.007320192 -0.42049017 0.29267780
## Layer2.tc_TC01006069_nc.cluster2 -2.761644116 -5.84711840 -4.12000693 *
## Layer2.tc_SLC9A4.cluster2 -3.022512916 -6.42428076 -5.10371608 *
## Layer2.tc_RAB6C_AS1.cluster2 -2.735548496 -5.71382338 -4.14727572 *
## Layer2.tc_LOC100129029.cluster2 -0.047232147 -0.51475117 0.49454267
## Layer2.tc_BRE.cluster2 -0.074088904 -0.21328387 -0.07772781 *
## Layer2.tc_TC03001220_nc.cluster2 0.029593287 0.02042346 0.11443281 *
## Layer2.tc_TC04002114_nc.cluster2 -0.032127959 -0.11897252 -0.08119528 *
## Layer2.tc_TC04002369_nc.cluster2 0.133154331 -0.37614047 0.80057170
## Layer2.tc_BEND4.cluster2 -0.086737855 -0.29839622 -0.07253692 *
## Layer2.tc_SLC9A3.cluster2 -0.007978583 -0.22786222 0.36205382
##
## Layer 3
##
## estimate norm_lower norm_upper sig
## Layer3.miR.101.3p.cluster1 -0.006887544 -0.85835674 0.63349326
## Layer3.miR.125a.5p.cluster1 0.187538247 -0.12054812 0.54531006
## Layer3.miR.125b.1.3p.cluster1 0.027408679 -0.11224394 0.26777031
## Layer3.miR.127.3p.cluster1 -0.107628701 -0.33956237 0.16003987
## Layer3.miR.140.5p.cluster1 0.090862876 -0.43461362 0.39456112
## Layer3.miR.142.3p.cluster1 -0.004784527 -0.90333386 0.55071060
## Layer3.miR.144.5p.cluster1 0.080101772 -0.48115276 0.23358977
## Layer3.miR.19a.3p.cluster1 0.060465036 -0.80183783 0.57872541
## Layer3.miR.19b.3p.cluster1 0.108156640 -0.71319277 0.63320509
## Layer3.miR.21.5p.cluster1 0.102286783 -0.52084664 0.54504734
## Layer3.miR.101.3p.cluster2 3.007913039 2.76591643 4.03964999 *
## Layer3.miR.125a.5p.cluster2 2.785670575 2.40835450 3.43823362 *
## Layer3.miR.125b.1.3p.cluster2 2.968675795 2.83195752 3.44316102 *
## Layer3.miR.127.3p.cluster2 0.123004230 -0.39451590 0.65348059
## Layer3.miR.140.5p.cluster2 -0.103843286 0.05693976 0.12198369 *
## Layer3.miR.142.3p.cluster2 0.005468031 0.07367142 0.40513048 *
## Layer3.miR.144.5p.cluster2 -0.091544883 -0.04645363 0.13132820
## Layer3.miR.19a.3p.cluster2 -0.069102898 0.02940357 0.24526051 *
## Layer3.miR.19b.3p.cluster2 -0.123607589 -0.06904772 0.09523343
## Layer3.miR.21.5p.cluster2 -0.116899180 -0.19704415 0.18960608
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each exposure for each layer
## Layer 1
##
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 -1.1631418 -8.2214342 -3.3771064 *
## g_causal_1.cluster2 1.4640897 0.4389506 4.7566259 *
## g_causal_2.cluster2 -1.4811806 -6.3824366 -0.7601632 *
## g_causal_3.cluster2 0.4026573 0.4750914 1.6171105 *
##
## Layer 2
##
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 -2.2697448 -8.216159 0.3394388
## g_causal_1.cluster2 1.8277068 3.384299 5.8233970 *
## g_causal_2.cluster2 -1.9585513 -6.068292 -4.3160274 *
## g_causal_3.cluster2 0.6732859 1.687756 1.8885930 *
##
## Layer 3
##
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 -3.2332090 -12.2463212 -5.925098 *
## g_causal_1.cluster2 1.9557658 1.9862321 3.295563 *
## g_causal_2.cluster2 -1.9426942 -2.3076720 -2.098619 *
## g_causal_3.cluster2 0.6703573 -0.1442928 1.294448
As in 7.4, the sig column flags rows whose normal-theory
interval excludes 0 in each layer’s coefficient table; with
layer-specific cluster variables, a feature can be significant in one
layer’s table and not another’s, since each layer’s bootstrap resamples
and refits that layer’s own model.
Serial model logic here:
Each stage uses previous latent information as designed by the serial pipeline.
set.seed(1301)
# Serial structure: list of early-stage matrices.
Z_serial_all_early <- list(
methylome = Z_parallel_miss[[1]],
transcriptome = Z_parallel_miss[[2]],
miRNA = Z_parallel_miss[[3]]
)
serial_all_early_pen <- estimate_lucid(
lucid_model = "serial",
G = G,
Z = Z_serial_all_early,
Y = Y,
CoG = CoG,
CoY = CoY,
family = "normal",
K = list(2, 2, 2),
Rho_G = RHO_G,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 15,
max_tot.itr = 40,
tol = 1e-2,
seed = 1301,
verbose = FALSE
)## Fitting LUCID serial model (3 stages)...
## Stage 1/3 (early) finished: log-likelihood = -1202.568. Selected G: 3/9; Selected Z: 10/10.
## Stage 2/3 (early) finished: log-likelihood = -1217.744.
## Stage 3/3 (early) finished: log-likelihood = -1107.108.
## Finished LUCID serial model.
##
## ====================================================
## LUCID Serial: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of stages : 3
## Stage 1 : early (K = 2)
## Stage 2 : early (K = 2)
## Stage 3 : early (K = 2)
##
## Missing-data profile by stage
## Stage 1
## Listwise rows : 1
## Sporadic rows : 0
## Stage 2
## Listwise rows : 0
## Sporadic rows : 1
## Stage 3
## Listwise rows : 0
## Sporadic rows : 1
##
## Model fit statistics
## Log-likelihood : -3527.42
## BIC : 8881.76
##
## Regularization
## Rho_G : 0.050
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Stage-wise detailed parameter estimates
##
## --- Stage 1 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 1 / 90 (1.1%)
## Sporadic missing rows : 0 / 90 (0.0%)
## Missing cells total : 10 / 900 (1.1%)
##
## Feature selection overview
## G features selected : 3 / 9 (33.3%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1202.57
## BIC : 3017.11
## Number of parameters : 136
##
## Regularization
## Rho_G : 0.050
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## cg_GRHL3 0.01961632 2.78733237
## cg_BTF3L4 0.02562603 2.77166380
## cg_AL358472.7 -0.05843567 2.85140812
## cg_HDGF 0.19392191 -0.19011728
## cg_TDRD5 -0.13906418 0.13396272
## cg_CSRNP3 -0.07422915 0.08045565
## cg_HSPD1 0.18520515 -0.20480481
## cg_EPM2AIP1 -0.03953897 0.03984596
## cg_AC025171.1 -0.07348596 0.04317287
## cg_VTRNA1_3 0.15848714 -0.18483199
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each exposure
## beta OR
## (Intercept).cluster2 -0.233824182 0.7915010
## g_causal_1.cluster2 0.807455666 2.2421958
## g_causal_2.cluster2 -0.771406772 0.4623622
## g_causal_3.cluster2 0.053236931 1.0546795
## hs_child_age_yrs_None.cluster2 -0.004677383 0.9953335
## sex_male.cluster2 0.628371411 1.8745552
##
## --- Stage 2 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 1 / 1 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1217.74
## BIC : 3029.46
## Number of parameters : 132
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## tc_TC01006069_nc -2.770982315 -0.200404643
## tc_SLC9A4 -3.075730222 0.037563680
## tc_RAB6C_AS1 -2.807651590 -0.168319407
## tc_LOC100129029 -0.052357555 0.045812320
## tc_BRE -0.112174120 0.098151196
## tc_TC03001220_nc 0.060238426 -0.052708001
## tc_TC04002114_nc -0.036339642 0.031796811
## tc_TC04002369_nc 0.078990829 -0.069116159
## tc_BEND4 -0.063253002 0.055345723
## tc_SLC9A3 -0.001011717 0.000885242
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## beta OR
## (Intercept).cluster2 7.781705 2.396358e+03
## Stage1.cluster2.cluster2 -10.462325 2.859368e-05
##
## --- Stage 3 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 1 / 1 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1107.11
## BIC : 2835.19
## Number of parameters : 138
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma
## (Intercept) 1.721848e+00
## cluster2 2.220446e-16
## hs_child_age_yrs_None -1.654292e-01
## sex_male 1.034161e-01
##
## (2) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## miR.101.3p 1.399631e+00 1.399631e+00
## miR.125a.5p 1.400000e+00 1.400000e+00
## miR.125b.1.3p 1.400000e+00 1.400000e+00
## miR.127.3p 4.294969e-17 4.294969e-17
## miR.140.5p 7.000762e-17 7.000762e-17
## miR.142.3p 3.087227e-17 3.087227e-17
## miR.144.5p 1.571613e-17 1.571613e-17
## miR.19a.3p -2.304965e-17 -2.304965e-17
## miR.19b.3p 2.827525e-17 2.827525e-17
## miR.21.5p -1.181994e-17 -1.181994e-17
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## beta OR
## (Intercept).cluster2 -45.00000 2.862519e-20
## Stage2.cluster2.cluster2 -24.00013 3.774635e-11
## [[1]]
## [[1]]$stage
## [1] 1
##
## [[1]]$model
## [1] "early"
##
## [[1]]$selected_G
## [1] 3
##
## [[1]]$total_G
## [1] 9
##
## [[1]]$selected_Z
## [1] 10
##
## [[1]]$total_Z
## [1] 10
##
##
## [[2]]
## [[2]]$stage
## [1] 2
##
## [[2]]$model
## [1] "early"
##
## [[2]]$selected_G
## [1] NA
##
## [[2]]$total_G
## [1] NA
##
## [[2]]$selected_Z
## [1] 10
##
## [[2]]$total_Z
## [1] 10
##
##
## [[3]]
## [[3]]$stage
## [1] 3
##
## [[3]]$model
## [1] "early"
##
## [[3]]$selected_G
## [1] NA
##
## [[3]]$total_G
## [1] NA
##
## [[3]]$selected_Z
## [1] 10
##
## [[3]]$total_Z
## [1] 10
## [1] "res_Beta" "res_Mu" "res_Sigma"
## [4] "res_Delta" "res_Gamma" "K"
## [7] "N" "var.names" "init_omic.data.model"
## [10] "inclusion.p" "family" "useY"
## [13] "Z" "init_impute" "init_par"
## [16] "submodel" "missing_summary" "Rho"
## [19] "em_control" "likelihood" "select"
Two fields are serial-only, beyond what early/parallel already showed:
submodel: the fitted stage models
themselves, one per stage, each a complete early_lucid or
lucid_parallel object in its own right (with its own
res_Beta, select, likelihood,
etc.) – explored below.res_Delta: the between-stage
transition coefficients, one element per transition
(n_stages - 1 of them), structured like
res_Beta for the stage that estimated it.likelihood and select at the top level are
present for serial too, the same as early/parallel – but unlike those
two, a serial fit has no single joint EM loop, so both are aggregates
over stages rather than values from one fit:
## top-level likelihood (sum over stages): -3527.42
cat("per-stage likelihoods:",
paste(round(sapply(serial_all_early_pen$submodel, `[[`, "likelihood"), 2),
collapse = ", "), "\n")## per-stage likelihoods: -1202.57, -1217.74, -1107.11
## n_stages: 3
The fit’s top-level select is stage 1’s own
selection only, not one combined across all three stages. That
is deliberate: stage 1 is the only stage whose “G” is the cohort’s
actual exposures. From stage 2 onward, “G” is the previous
stage’s latent cluster-membership probabilities – there are no real
exposures left to select among there, so Rho_G is forced to
0 for every stage after the first regardless of what penalty was
requested, and that stage’s select$selectG is not a
meaningful exposure-selection result. The complete per-stage record
(including every stage’s real omics selection, selectZ) is
always available via serial_selection_report() above, or
directly at fit$submodel[[i]]$select.
# Top-level select == stage 1's select
identical(serial_all_early_pen$select, serial_all_early_pen$submodel[[1]]$select)## [1] TRUE
cat("Stage 1 exposures kept:",
paste(names(which(get_selected_G(serial_all_early_pen))), collapse = ", "), "\n")## Stage 1 exposures kept: g_causal_1, g_causal_2, g_causal_3
# Rho_G is 0 from stage 2 on -- there is nothing there to select
sapply(serial_all_early_pen$submodel, function(sm) sm$Rho$Rho_G)## [1] 0.05 0.00 0.00
set.seed(1302)
serial_all_early_inputs_refit <- prepare_serial_selected_inputs(
serial_all_early_pen, G, Z_serial_all_early
)
serial_all_early_refit <- refit_selected(
"serial",
fit_pen = serial_all_early_pen,
inputs = serial_all_early_inputs_refit,
Y = Y,
CoG = CoG,
CoY = CoY,
seed = 1302,
verbose = TRUE
)## Fitting LUCID serial model (Stage 1/3)...
## Initialize LUCID with mclust based on inclusion probabilities given by mclust
## Fitting Early Integration LUCID model (K = 2, Rho_G = 0, Rho_Z_Mu = 0, Rho_Z_Cov = 0)
## iteration 1 : E-step finished.
## iteration 1: log-likelihood = -1200.841
## iteration 2 : E-step finished.
## iteration 2: log-likelihood = -1198.968
## iteration 3 : E-step finished.
## iteration 3: log-likelihood = -1198.666
## iteration 4 : E-step finished.
## iteration 4: log-likelihood = -1198.662
## Success: LUCID early model converged!
##
## Finished LUCID early model: log-likelihood = -1198.662.
##
## Fitting LUCID serial model (Stage 2/3)...
## Intializing imputation of missing values in 'Z' via LOD / sqrt(2)
##
## Initialize LUCID with mclust based on inclusion probabilities given by mclust
## Fitting Early Integration LUCID model (K = 2, Rho_G = 0, Rho_Z_Mu = 0, Rho_Z_Cov = 0)
## iteration 1 : E-step finished.
## iteration 1: log-likelihood = -1222.486
## iteration 2 : E-step finished.
## iteration 2: log-likelihood = -1221.061
## iteration 3 : E-step finished.
## iteration 3: log-likelihood = -1217.788
## iteration 4 : E-step finished.
## iteration 4: log-likelihood = -1217.538
## iteration 5 : E-step finished.
## iteration 5: log-likelihood = -1217.524
## iteration 6 : E-step finished.
## iteration 6: log-likelihood = -1217.524
## Success: LUCID early model converged!
##
## Finished LUCID early model: log-likelihood = -1217.524.
##
## Fitting LUCID serial model (Stage 3/3)...
## Intializing imputation of missing values in 'Z' via LOD / sqrt(2)
##
## Initialize LUCID with mclust based on inclusion probabilities given by mclust
## Fitting Early Integration LUCID model (K = 2, Rho_G = 0, Rho_Z_Mu = 0, Rho_Z_Cov = 0)
## iteration 1 : E-step finished.
## iteration 1: log-likelihood = -1023.079
## iteration 2 : E-step finished.
## iteration 2: log-likelihood = -998.902
## iteration 3 : E-step finished.
## iteration 3: log-likelihood = -984.796
## iteration 4 : E-step finished.
## iteration 4: log-likelihood = -975.734
## iteration 5 : E-step finished.
## iteration 5: log-likelihood = -965.927
## iteration 6 : E-step finished.
## iteration 6: log-likelihood = -954.788
## iteration 7 : E-step finished.
## iteration 7: log-likelihood = -949.270
## iteration 8 : E-step finished.
## iteration 8: log-likelihood = -940.423
## iteration 9 : E-step finished.
## iteration 9: log-likelihood = -938.932
## iteration 10 : E-step finished.
## iteration 10: log-likelihood = -938.932
## Success: LUCID early model converged!
##
## Finished LUCID early model: log-likelihood = -938.932.
##
## Success: LUCID serial model constructed!
##
## ====================================================
## LUCID Serial: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of stages : 3
## Stage 1 : early (K = 2)
## Stage 2 : early (K = 2)
## Stage 3 : early (K = 2)
##
## Missing-data profile by stage
## Stage 1
## Listwise rows : 1
## Sporadic rows : 0
## Stage 2
## Listwise rows : 0
## Sporadic rows : 1
## Stage 3
## Listwise rows : 0
## Sporadic rows : 1
##
## Model fit statistics
## Log-likelihood : -3355.12
## BIC : 8537.16
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Stage-wise detailed parameter estimates
##
## --- Stage 1 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 1 / 90 (1.1%)
## Sporadic missing rows : 0 / 90 (0.0%)
## Missing cells total : 10 / 900 (1.1%)
##
## Feature selection overview
## G features selected : 3 / 3 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1198.66
## BIC : 3009.30
## Number of parameters : 136
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## cg_GRHL3 0.01914989 2.78568555
## cg_BTF3L4 0.02487383 2.77032608
## cg_AL358472.7 -0.05976249 2.85053277
## cg_HDGF 0.19307368 -0.18895441
## cg_TDRD5 -0.13876544 0.13344743
## cg_CSRNP3 -0.07408488 0.08018928
## cg_HSPD1 0.18585941 -0.20517509
## cg_EPM2AIP1 -0.03862822 0.03885292
## cg_AC025171.1 -0.07293658 0.04252108
## cg_VTRNA1_3 0.15834572 -0.18442375
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each exposure
## beta OR
## (Intercept).cluster2 -1.1615235 0.3130090
## g_causal_1.cluster2 1.4615405 4.3125979
## g_causal_2.cluster2 -1.4789587 0.2278748
## g_causal_3.cluster2 0.4021624 1.4950541
## hs_child_age_yrs_None.cluster2 0.1161985 1.1232188
## sex_male.cluster2 1.0246714 2.7861797
##
## --- Stage 2 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 1 / 1 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1217.52
## BIC : 3029.02
## Number of parameters : 132
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## tc_TC01006069_nc -2.770982428 -0.2004050509
## tc_SLC9A4 -3.075852912 0.0375632835
## tc_RAB6C_AS1 -2.807651889 -0.1683196643
## tc_LOC100129029 -0.052357769 0.0458124873
## tc_BRE -0.112174378 0.0981513802
## tc_TC03001220_nc 0.060238498 -0.0527080414
## tc_TC04002114_nc -0.036339702 0.0317968501
## tc_TC04002369_nc 0.078990703 -0.0691160198
## tc_BEND4 -0.063252955 0.0553456586
## tc_SLC9A3 -0.001011683 0.0008852122
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## beta OR
## (Intercept).cluster2 35.89575 3.884436e+15
## Stage1.cluster2.cluster2 -38.55656 1.799286e-17
##
## --- Stage 3 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 1 / 1 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -938.93
## BIC : 2498.84
## Number of parameters : 138
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma
## (Intercept) 1.0162700
## cluster2 1.3386950
## hs_child_age_yrs_None -0.1394871
## sex_male -0.1010743
##
## (2) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## miR.101.3p -0.006807900 3.007913039
## miR.125a.5p 0.187538247 2.785670575
## miR.125b.1.3p 0.027408679 2.968675795
## miR.127.3p -0.107628701 0.123004230
## miR.140.5p 0.090862876 -0.103843286
## miR.142.3p -0.004784527 0.005468031
## miR.144.5p 0.080101772 -0.091544883
## miR.19a.3p 0.060465036 -0.069102898
## miR.19b.3p 0.108156640 -0.123607589
## miR.21.5p 0.102286783 -0.116899180
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## beta OR
## (Intercept).cluster2 20.28449 6.448276e+08
## Stage2.cluster2.cluster2 -33.53361 2.732356e-15
As with the other architectures, this refit’s coefficients are
unshrunk versions of the screening fit’s; a serial model additionally
reports one X -> Z/G -> X block per
stage, plus res_Delta, the transition coefficients linking
each stage’s cluster to the next stage’s.
set.seed(1303)
serial_all_early_boot <- boot_lucid(
G = serial_all_early_inputs_refit$G,
Z = serial_all_early_inputs_refit$Z,
Y = Y,
CoG = CoG,
CoY = CoY,
model = serial_all_early_refit,
R = 2,
conf = 0.90
)
summary(serial_all_early_refit, boot.se = serial_all_early_boot)##
## ====================================================
## LUCID Serial: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of stages : 3
## Stage 1 : early (K = 2)
## Stage 2 : early (K = 2)
## Stage 3 : early (K = 2)
##
## Missing-data profile by stage
## Stage 1
## Listwise rows : 1
## Sporadic rows : 0
## Stage 2
## Listwise rows : 0
## Sporadic rows : 1
## Stage 3
## Listwise rows : 0
## Sporadic rows : 1
##
## Model fit statistics
## Log-likelihood : -3355.12
## BIC : 8537.16
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Stage-wise detailed parameter estimates
##
## --- Stage 1 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 1 / 90 (1.1%)
## Sporadic missing rows : 0 / 90 (0.0%)
## Missing cells total : 10 / 900 (1.1%)
##
## Feature selection overview
## G features selected : 3 / 3 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1198.66
## BIC : 3009.30
## Number of parameters : 136
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster
## estimate norm_lower norm_upper sig
## cg_GRHL3.cluster1 0.01915096 -0.560219385 0.13496290
## cg_BTF3L4.cluster1 0.02487535 -1.331234082 0.40616099
## cg_AL358472.7.cluster1 -0.05976023 -1.616162333 0.23940750
## cg_HDGF.cluster1 0.19307498 0.187939832 0.47980106 *
## cg_TDRD5.cluster1 -0.13876549 -0.183559301 -0.15556152 *
## cg_CSRNP3.cluster1 -0.07408533 -0.055335868 0.16184040
## cg_HSPD1.cluster1 0.18585798 -0.161082010 0.20993766
## cg_EPM2AIP1.cluster1 -0.03862977 -0.508224892 -0.21767360 *
## cg_AC025171.1.cluster1 -0.07293744 -0.646622364 0.34345353
## cg_VTRNA1_3.cluster1 0.15834580 -0.163113495 -0.03726939 *
## cg_GRHL3.cluster2 2.78568846 3.009673503 3.30757464 *
## cg_BTF3L4.cluster2 2.77032851 2.848517574 3.66324114 *
## cg_AL358472.7.cluster2 2.85053469 2.900072512 3.81622141 *
## cg_HDGF.cluster2 -0.18895629 -0.198831953 -0.15492008 *
## cg_TDRD5.cluster2 0.13344788 0.039140341 0.55098278 *
## cg_CSRNP3.cluster2 0.08018997 -0.506828004 0.03702560
## cg_HSPD1.cluster2 -0.20517418 -0.694250160 0.09944692
## cg_EPM2AIP1.cluster2 0.03885462 0.001790297 0.40901997 *
## cg_AC025171.1.cluster2 0.04252212 -0.200343129 0.12291526
## cg_VTRNA1_3.cluster2 -0.18442433 -0.474583848 0.41718925
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each exposure
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 -1.1615304 -21.172239882 9.5377330
## g_causal_1.cluster2 1.4615242 -2.836796335 5.1504014
## g_causal_2.cluster2 -1.4789441 -3.907556125 0.8367722
## g_causal_3.cluster2 0.4021613 0.692934691 2.2174983 *
## hs_child_age_yrs_None.cluster2 0.1161995 -1.919663072 3.2306015
## sex_male.cluster2 1.0246523 -0.000351442 2.1629915
##
## --- Stage 2 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 1 / 1 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1217.52
## BIC : 3029.02
## Number of parameters : 132
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster
## estimate norm_lower norm_upper sig
## tc_TC01006069_nc.cluster1 -2.7709824276 -4.190443506 -2.18817793 *
## tc_SLC9A4.cluster1 -3.0758531121 -5.058980233 -1.92956811 *
## tc_RAB6C_AS1.cluster1 -2.8076518895 -3.997619035 -2.42525369 *
## tc_LOC100129029.cluster1 -0.0523577689 -0.246014096 -0.04538028 *
## tc_BRE.cluster1 -0.1121743783 -0.245962867 -0.16861658 *
## tc_TC03001220_nc.cluster1 0.0602384985 -0.390340063 0.58835541
## tc_TC04002114_nc.cluster1 -0.0363397019 0.115294247 0.16294518 *
## tc_TC04002369_nc.cluster1 0.0789907033 -0.383389214 0.15683807
## tc_BEND4.cluster1 -0.0632529551 -0.330267409 -0.29236447 *
## tc_SLC9A3.cluster1 -0.0010116834 0.023803222 0.33148801 *
## tc_TC01006069_nc.cluster2 -0.2004050509 -0.616093851 1.31924221
## tc_SLC9A4.cluster2 0.0375632835 -0.127558724 1.08815992
## tc_RAB6C_AS1.cluster2 -0.1683196643 -1.009193180 1.41397788
## tc_LOC100129029.cluster2 0.0458124873 -0.407162907 0.55574912
## tc_BRE.cluster2 0.0981513803 -0.144741630 0.40124340
## tc_TC03001220_nc.cluster2 -0.0527080414 -1.019652531 0.58263383
## tc_TC04002114_nc.cluster2 0.0317968501 -1.218955229 0.80815419
## tc_TC04002369_nc.cluster2 -0.0691160198 0.024541724 0.24264268 *
## tc_BEND4.cluster2 0.0553456586 0.003256134 0.57778269 *
## tc_SLC9A3.cluster2 0.0008852121 -0.730096272 0.11598471
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 35.88846 69.82898 72.19481 *
## Stage1.cluster2.cluster2 -38.54926 -76.64323 -74.27716 *
##
## --- Stage 3 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 1 / 1 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -938.93
## BIC : 2498.84
## Number of parameters : 138
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma norm_lower norm_upper sig
## Y.(Intercept) 1.7218477 -0.8888016 6.9577278
## Y.LC2 0.0000000 -5.2062644 2.0757824
## Y.hs_child_age_yrs_None -0.1654292 -0.2352059 -0.0500037 *
## Y.sex_male 0.1034161 -0.3582500 0.7067403
##
## (2) Z: mean of omics data for each latent cluster
## estimate norm_lower norm_upper sig
## miR.101.3p.cluster1 1.399631e+00 0.310403894 3.87707042 *
## miR.125a.5p.cluster1 1.400000e+00 0.987206375 2.39048372 *
## miR.125b.1.3p.cluster1 1.400000e+00 0.591519533 2.93104695 *
## miR.127.3p.cluster1 4.294969e-17 -0.538066124 0.92990821
## miR.140.5p.cluster1 7.000762e-17 -0.997425443 0.55933364
## miR.142.3p.cluster1 3.087227e-17 -0.853795594 0.41383900
## miR.144.5p.cluster1 1.571613e-17 -2.590246247 1.09638121
## miR.19a.3p.cluster1 -2.304965e-17 -0.525985944 0.23234210
## miR.19b.3p.cluster1 2.827525e-17 -0.843952480 0.42582390
## miR.21.5p.cluster1 -1.181994e-17 -1.035149405 0.62064189
## miR.101.3p.cluster2 1.399631e+00 1.092541036 1.91539430 *
## miR.125a.5p.cluster2 1.400000e+00 1.312499636 1.57461655 *
## miR.125b.1.3p.cluster2 1.400000e+00 1.240922694 1.30228058 *
## miR.127.3p.cluster2 4.294969e-17 -0.167841650 0.00134918
## miR.140.5p.cluster2 7.000762e-17 0.085588025 0.19077469 *
## miR.142.3p.cluster2 3.087227e-17 0.007181168 0.16614184 *
## miR.144.5p.cluster2 1.571613e-17 -0.022043374 0.21487153
## miR.19a.3p.cluster2 -2.304965e-17 -0.102841413 0.31468694
## miR.19b.3p.cluster2 2.827525e-17 -0.032782855 0.30627791
## miR.21.5p.cluster2 -1.181994e-17 0.132396412 0.18941780 *
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 45.00000 26.61747 91.10516 *
## Stage2.cluster2.cluster2 24.00014 -10.49577 90.94478
Each stage’s coefficient tables get their own sig
markers, independently of the other stages – a stage-2 effect being
significant says nothing about stage 3, since each stage’s bootstrap
resamples and refits that stage’s own conditional model.
Serial mixed architecture used here:
set.seed(1401)
# Nested list signals a parallel submodel at stage 1, followed by early stage 2.
Z_serial_mixed <- list(
list(
methylome = Z_parallel_miss[[1]],
transcriptome = Z_parallel_miss[[2]]
),
miRNA = Z_parallel_miss[[3]]
)
serial_mixed_pen <- estimate_lucid(
lucid_model = "serial",
G = G,
Z = Z_serial_mixed,
Y = Y,
CoG = CoG,
CoY = CoY,
family = "normal",
K = list(list(2, 2), 2),
Rho_G = RHO_G,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 15,
max_tot.itr = 40,
tol = 1e-2,
seed = 1401,
verbose = FALSE
)## Fitting LUCID serial model (2 stages)...
## Stage 1/2 (parallel) finished: log-likelihood = -2444.642. Selected G: 3/9; Selected Z by layer: 10/10, 10/10.
## Stage 2/2 (early) finished: log-likelihood = -1107.108.
## Finished LUCID serial model.
##
## ====================================================
## LUCID Serial: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of stages : 2
## Stage 1 : parallel (K = 2,2)
## Stage 2 : early (K = 2)
##
## Missing-data profile by stage
## Stage 1
## Layer 1 listwise/sporadic rows : 1 / 0
## Layer 2 listwise/sporadic rows : 0 / 1
## Stage 2
## Listwise rows : 0
## Sporadic rows : 1
##
## Model fit statistics
## Log-likelihood : -3551.75
## BIC : 8952.92
##
## Regularization
## Rho_G : 0.050
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Stage-wise detailed parameter estimates
##
## --- Stage 1 (parallel) ---
##
## ====================================================
## LUCID Parallel: Model Summary
## ====================================================
##
## Model specification
## Family : gaussian
## Number of observations : 90
## Clusters per layer : 2, 2
##
## Missing-data profile by layer
## Layer 1 listwise rows : 1 / 90 (1.1%)
## Layer 1 sporadic rows : 0 / 90 (0.0%)
## Layer 1 missing cells : 10 / 900 (1.1%)
## Layer 2 listwise rows : 0 / 90 (0.0%)
## Layer 2 sporadic rows : 1 / 90 (1.1%)
## Layer 2 missing cells : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 3 / 9 (33.3%)
## G features by layer
## Layer 1 : 3 / 9 (33.3%)
## Layer 2 : 3 / 9 (33.3%)
## Z features
## Layer 1 selected : 10 / 10 (100.0%)
## Layer 1 multi-cluster: 10
## Layer 2 selected : 10 / 10 (100.0%)
## Layer 2 multi-cluster: 10
##
## Model fit statistics
## Log-likelihood : -2444.64
## BIC : 6113.23
## Number of parameters : 272
##
## Regularization
## Rho_G : 0.050
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster of each layer
## Layer 1
##
## mu_cluster1 mu_cluster2
## cg_GRHL3 0.01960667 2.78730293
## cg_BTF3L4 0.02561217 2.77163898
## cg_AL358472.7 -0.05845803 2.85138969
## cg_HDGF 0.19390770 -0.19009725
## cg_TDRD5 -0.13906115 0.13395573
## cg_CSRNP3 -0.07422600 0.08045023
## cg_HSPD1 0.18521867 -0.20481312
## cg_EPM2AIP1 -0.03952313 0.03982859
## cg_AC025171.1 -0.07347676 0.04316178
## cg_VTRNA1_3 0.15848563 -0.18482556
##
## Layer 2
##
## mu_cluster1 mu_cluster2
## tc_TC01006069_nc -2.76171772 -0.151431669
## tc_SLC9A4 -3.02348512 0.061983212
## tc_RAB6C_AS1 -2.73581445 -0.175182553
## tc_LOC100129029 -0.04727047 0.043342620
## tc_BRE -0.07428108 0.068108837
## tc_TC03001220_nc 0.02974284 -0.027271414
## tc_TC04002114_nc -0.03156595 0.028943037
## tc_TC04002369_nc 0.13303025 -0.121976355
## tc_BEND4 -0.08701134 0.079781296
## tc_SLC9A3 -0.00834082 0.007647755
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each exposure for each layer
## Layer 1
##
## beta OR
## (Intercept).cluster2 -0.23378217 0.7915342
## g_causal_1.cluster2 0.80753183 2.2423666
## g_causal_2.cluster2 -0.77145952 0.4623378
## g_causal_3.cluster2 0.05322757 1.0546696
##
## Layer 2
##
## beta OR
## (Intercept).cluster2 0.8377907 2.3112551
## g_causal_1.cluster2 -0.9250451 0.3965136
## g_causal_2.cluster2 0.8918749 2.4396996
## g_causal_3.cluster2 -0.2028611 0.8163916
##
##
## --- Stage 2 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 2 / 2 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1107.11
## BIC : 2839.69
## Number of parameters : 139
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma
## (Intercept) 1.7218477
## cluster2 0.0000000
## hs_child_age_yrs_None -0.1654292
## sex_male 0.1034161
##
## (2) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## miR.101.3p 1.399631e+00 1.399631e+00
## miR.125a.5p 1.400000e+00 1.400000e+00
## miR.125b.1.3p 1.400000e+00 1.400000e+00
## miR.127.3p 4.294969e-17 4.294969e-17
## miR.140.5p 7.000762e-17 7.000762e-17
## miR.142.3p 3.087227e-17 3.087227e-17
## miR.144.5p 1.571613e-17 1.571613e-17
## miR.19a.3p -2.304965e-17 -2.304965e-17
## miR.19b.3p 2.827525e-17 2.827525e-17
## miR.21.5p -1.181994e-17 -1.181994e-17
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## beta OR
## (Intercept).cluster2 45.00000 3.493427e+19
## Stage1.Layer1.cluster2.cluster2 22.42754 5.497402e+09
## Stage1.Layer2.cluster2.cluster2 23.47467 1.566466e+10
## [[1]]
## [[1]]$stage
## [1] 1
##
## [[1]]$model
## [1] "parallel"
##
## [[1]]$selected_G
## [1] 3
##
## [[1]]$total_G
## [1] 9
##
## [[1]]$selected_Z_by_layer
## methylome transcriptome
## 10 10
##
## [[1]]$total_Z_by_layer
## methylome transcriptome
## 10 10
##
##
## [[2]]
## [[2]]$stage
## [1] 2
##
## [[2]]$model
## [1] "early"
##
## [[2]]$selected_G
## [1] NA
##
## [[2]]$total_G
## [1] NA
##
## [[2]]$selected_Z
## [1] 10
##
## [[2]]$total_Z
## [1] 10
set.seed(1402)
serial_mixed_inputs_refit <- prepare_serial_selected_inputs(serial_mixed_pen, G, Z_serial_mixed)
serial_mixed_refit <- refit_selected(
"serial",
fit_pen = serial_mixed_pen,
inputs = serial_mixed_inputs_refit,
Y = Y,
CoG = CoG,
CoY = CoY,
seed = 1402,
verbose = FALSE
)## Fitting LUCID serial model (2 stages)...
## Stage 1/2 (parallel) finished: log-likelihood = -2435.579.
## Stage 2/2 (early) finished: log-likelihood = -1107.108.
## Finished LUCID serial model.
##
## ====================================================
## LUCID Serial: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of stages : 2
## Stage 1 : parallel (K = 2,2)
## Stage 2 : early (K = 2)
##
## Missing-data profile by stage
## Stage 1
## Layer 1 listwise/sporadic rows : 1 / 0
## Layer 2 listwise/sporadic rows : 0 / 1
## Stage 2
## Listwise rows : 0
## Sporadic rows : 1
##
## Model fit statistics
## Log-likelihood : -3542.69
## BIC : 8934.80
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Stage-wise detailed parameter estimates
##
## --- Stage 1 (parallel) ---
##
## ====================================================
## LUCID Parallel: Model Summary
## ====================================================
##
## Model specification
## Family : gaussian
## Number of observations : 90
## Clusters per layer : 2, 2
##
## Missing-data profile by layer
## Layer 1 listwise rows : 1 / 90 (1.1%)
## Layer 1 sporadic rows : 0 / 90 (0.0%)
## Layer 1 missing cells : 10 / 900 (1.1%)
## Layer 2 listwise rows : 0 / 90 (0.0%)
## Layer 2 sporadic rows : 1 / 90 (1.1%)
## Layer 2 missing cells : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 3 / 3 (100.0%)
## G features by layer
## Layer 1 : 3 / 3 (100.0%)
## Layer 2 : 3 / 3 (100.0%)
## Z features
## Layer 1 selected : 10 / 10 (100.0%)
## Layer 1 multi-cluster: 10
## Layer 2 selected : 10 / 10 (100.0%)
## Layer 2 multi-cluster: 10
##
## Model fit statistics
## Log-likelihood : -2435.58
## BIC : 6095.11
## Number of parameters : 272
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster of each layer
## Layer 1
##
## mu_cluster1 mu_cluster2
## cg_GRHL3 0.01912123 2.78560954
## cg_BTF3L4 0.02483414 2.77026216
## cg_AL358472.7 -0.05982001 2.85048084
## cg_HDGF 0.19304072 -0.18890613
## cg_TDRD5 -0.13876550 0.13343713
## cg_CSRNP3 -0.07407272 0.08017097
## cg_HSPD1 0.18589857 -0.20520028
## cg_EPM2AIP1 -0.03858839 0.03880921
## cg_AC025171.1 -0.07291502 0.04249461
## cg_VTRNA1_3 0.15834446 -0.18440941
##
## Layer 2
##
## mu_cluster1 mu_cluster2
## tc_TC01006069_nc -2.761572789 -0.150145472
## tc_SLC9A4 -3.021703480 0.062198525
## tc_RAB6C_AS1 -2.735259250 -0.174299961
## tc_LOC100129029 -0.047193030 0.043320800
## tc_BRE -0.073892278 0.067829351
## tc_TC03001220_nc 0.029444093 -0.027028179
## tc_TC04002114_nc -0.032501823 0.029835020
## tc_TC04002369_nc 0.133308600 -0.122370510
## tc_BEND4 -0.086573422 0.079469996
## tc_SLC9A3 -0.007742854 0.007107546
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each exposure for each layer
## Layer 1
##
## beta OR
## (Intercept).cluster2 -1.1612442 0.3130964
## g_causal_1.cluster2 1.4619640 4.3144246
## g_causal_2.cluster2 -1.4793132 0.2277941
## g_causal_3.cluster2 0.4022122 1.4951286
##
## Layer 2
##
## beta OR
## (Intercept).cluster2 2.2583148 9.5669530
## g_causal_1.cluster2 -1.8289379 0.1605840
## g_causal_2.cluster2 1.9595122 7.0958651
## g_causal_3.cluster2 -0.6722936 0.5105363
##
##
## --- Stage 2 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 2 / 2 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1107.11
## BIC : 2839.69
## Number of parameters : 139
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma
## (Intercept) 1.7218477
## cluster2 0.0000000
## hs_child_age_yrs_None -0.1654292
## sex_male 0.1034161
##
## (2) Z: mean of omics data for each latent cluster
## mu_cluster1 mu_cluster2
## miR.101.3p 1.399631e+00 1.399631e+00
## miR.125a.5p 1.400000e+00 1.400000e+00
## miR.125b.1.3p 1.400000e+00 1.400000e+00
## miR.127.3p 4.294969e-17 4.294969e-17
## miR.140.5p 7.000762e-17 7.000762e-17
## miR.142.3p 3.087227e-17 3.087227e-17
## miR.144.5p 1.571613e-17 1.571613e-17
## miR.19a.3p -2.304965e-17 -2.304965e-17
## miR.19b.3p 2.827525e-17 2.827525e-17
## miR.21.5p -1.181994e-17 -1.181994e-17
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## beta OR
## (Intercept).cluster2 45.00000 3.493427e+19
## Stage1.Layer1.cluster2.cluster2 22.48854 5.843180e+09
## Stage1.Layer2.cluster2.cluster2 23.46155 1.546048e+10
Stage 1’s block here is itself a parallel-model summary (one
G -> X/X -> Z per layer, as in section
8), nested inside the serial report; stage 2’s block looks like the
single-layer early summary from section 7. This is the same
mix-and-match structure estimate_lucid() fit – the summary
just mirrors it.
set.seed(1403)
serial_mixed_boot <- boot_lucid(
G = serial_mixed_inputs_refit$G,
Z = serial_mixed_inputs_refit$Z,
Y = Y,
CoG = CoG,
CoY = CoY,
model = serial_mixed_refit,
R = 2,
conf = 0.90
)
summary(serial_mixed_refit, boot.se = serial_mixed_boot)##
## ====================================================
## LUCID Serial: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of stages : 2
## Stage 1 : parallel (K = 2,2)
## Stage 2 : early (K = 2)
##
## Missing-data profile by stage
## Stage 1
## Layer 1 listwise/sporadic rows : 1 / 0
## Layer 2 listwise/sporadic rows : 0 / 1
## Stage 2
## Listwise rows : 0
## Sporadic rows : 1
##
## Model fit statistics
## Log-likelihood : -3542.69
## BIC : 8934.80
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Stage-wise detailed parameter estimates
##
## --- Stage 1 (parallel) ---
##
## ====================================================
## LUCID Parallel: Model Summary
## ====================================================
##
## Model specification
## Family : gaussian
## Number of observations : 90
## Clusters per layer : 2, 2
##
## Missing-data profile by layer
## Layer 1 listwise rows : 1 / 90 (1.1%)
## Layer 1 sporadic rows : 0 / 90 (0.0%)
## Layer 1 missing cells : 10 / 900 (1.1%)
## Layer 2 listwise rows : 0 / 90 (0.0%)
## Layer 2 sporadic rows : 1 / 90 (1.1%)
## Layer 2 missing cells : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 3 / 3 (100.0%)
## G features by layer
## Layer 1 : 3 / 3 (100.0%)
## Layer 2 : 3 / 3 (100.0%)
## Z features
## Layer 1 selected : 10 / 10 (100.0%)
## Layer 1 multi-cluster: 10
## Layer 2 selected : 10 / 10 (100.0%)
## Layer 2 multi-cluster: 10
##
## Model fit statistics
## Log-likelihood : -2435.58
## BIC : 6095.11
## Number of parameters : 272
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Z: mean of omics data for each latent cluster of each layer
## Layer 1
##
## estimate norm_lower norm_upper sig
## Layer1.cg_GRHL3.cluster1 0.01912123 -1.76591782 1.08957449
## Layer1.cg_BTF3L4.cluster1 0.02483414 -1.56392335 0.60882501
## Layer1.cg_AL358472.7.cluster1 -0.05982001 -2.49983992 1.51412113
## Layer1.cg_HDGF.cluster1 0.19304072 -0.13676063 0.53007265
## Layer1.cg_TDRD5.cluster1 -0.13876550 0.04279999 0.08967088 *
## Layer1.cg_CSRNP3.cluster1 -0.07407272 -0.38744792 -0.02713626 *
## Layer1.cg_HSPD1.cluster1 0.18589857 0.09732106 0.56600143 *
## Layer1.cg_EPM2AIP1.cluster1 -0.03858839 -0.20314251 0.40283484
## Layer1.cg_AC025171.1.cluster1 -0.07291502 -0.79468524 0.69947818
## Layer1.cg_VTRNA1_3.cluster1 0.15834446 0.16124472 0.52044475 *
## Layer1.cg_GRHL3.cluster2 2.78560954 2.11508584 3.77154458 *
## Layer1.cg_BTF3L4.cluster2 2.77026216 0.94043816 4.31284257 *
## Layer1.cg_AL358472.7.cluster2 2.85048084 2.89302945 3.04909715 *
## Layer1.cg_HDGF.cluster2 -0.18890613 -0.35700217 0.13189109
## Layer1.cg_TDRD5.cluster2 0.13343713 -0.78098123 0.54432161
## Layer1.cg_CSRNP3.cluster2 0.08017097 0.35478659 0.67236357 *
## Layer1.cg_HSPD1.cluster2 -0.20520028 -1.17934214 -0.25929254 *
## Layer1.cg_EPM2AIP1.cluster2 0.03880921 -0.63207854 0.27824886
## Layer1.cg_AC025171.1.cluster2 0.04249461 -0.86892123 0.99941966
## Layer1.cg_VTRNA1_3.cluster2 -0.18440941 -0.40993676 -0.15188080 *
##
## Layer 2
##
## estimate norm_lower norm_upper sig
## Layer2.tc_TC01006069_nc.cluster1 -2.761572789 -2.63646793 -2.55846630 *
## Layer2.tc_SLC9A4.cluster1 -3.021703480 -2.80613353 -2.71283205 *
## Layer2.tc_RAB6C_AS1.cluster1 -2.735259250 -2.35323755 -2.35210713 *
## Layer2.tc_LOC100129029.cluster1 -0.047193030 -0.50490186 -0.00620643 *
## Layer2.tc_BRE.cluster1 -0.073892278 -0.06750852 0.72599118
## Layer2.tc_TC03001220_nc.cluster1 0.029444093 -0.12820967 0.24614538
## Layer2.tc_TC04002114_nc.cluster1 -0.032501823 -0.48694290 -0.20798121 *
## Layer2.tc_TC04002369_nc.cluster1 0.133308600 -0.08003833 0.15248825
## Layer2.tc_BEND4.cluster1 -0.086573422 -0.50500831 0.04879925
## Layer2.tc_SLC9A3.cluster1 -0.007742854 -0.52199443 0.32625656
## Layer2.tc_TC01006069_nc.cluster2 -0.150145472 -0.06294660 0.30806436
## Layer2.tc_SLC9A4.cluster2 0.062198525 0.52038606 0.86236798 *
## Layer2.tc_RAB6C_AS1.cluster2 -0.174299961 0.34637690 0.35208448 *
## Layer2.tc_LOC100129029.cluster2 0.043320800 -0.34898041 0.43435215
## Layer2.tc_BRE.cluster2 0.067829351 -0.20572483 -0.11546113 *
## Layer2.tc_TC03001220_nc.cluster2 -0.027028179 -0.38169361 0.26800311
## Layer2.tc_TC04002114_nc.cluster2 0.029835020 -0.02338799 0.35627002
## Layer2.tc_TC04002369_nc.cluster2 -0.122370510 -0.42281979 0.08865890
## Layer2.tc_BEND4.cluster2 0.079469996 -0.11664746 0.84758795
## Layer2.tc_SLC9A3.cluster2 0.007107546 -0.34541849 0.26602623
##
## (2) E: intercept and odds ratio of being assigned to each latent cluster for each exposure for each layer
## Layer 1
##
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 -1.1612442 -17.796723 10.9478773
## g_causal_1.cluster2 1.4619640 1.302864 1.3483204 *
## g_causal_2.cluster2 -1.4793132 -2.723976 -1.5981707 *
## g_causal_3.cluster2 0.4022122 -0.686069 0.2639282
##
## Layer 2
##
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 2.2583148 -7.018743 -2.0453165 *
## g_causal_1.cluster2 -1.8289379 -2.843784 -1.7261267 *
## g_causal_2.cluster2 1.9595122 1.922211 2.8339436 *
## g_causal_3.cluster2 -0.6722936 -1.732719 0.4350202
##
##
## --- Stage 2 (early) ---
##
## ====================================================
## LUCID Early Integration: Model Summary
## ====================================================
##
## Model specification
## Family : normal
## Number of observations : 90
## Number of clusters (K) : 2
##
## Missing-data profile
## Listwise missing rows : 0 / 90 (0.0%)
## Sporadic missing rows : 1 / 90 (1.1%)
## Missing cells total : 1 / 900 (0.1%)
##
## Feature selection overview
## G features selected : 2 / 2 (100.0%)
## Z features selected : 10 / 10 (100.0%)
##
## Model fit statistics
## Log-likelihood : -1107.11
## BIC : 2839.69
## Number of parameters : 139
##
## Regularization
## Rho_G : 0.000
## Rho_Z_Mu : 0.000
## Rho_Z_Cov : 0.000
##
## Detailed parameter estimates
## (1) Y (continuous outcome): cluster 1 mean (intercept), mean differences for other clusters, and covariate effects
## Gamma norm_lower norm_upper sig
## Y.(Intercept) 0.27612790 -1.86384979 0.5409223
## Y.LC2 1.67962912 1.30028679 4.1801879 *
## Y.hs_child_age_yrs_None 0.00697466 0.06486525 0.2240818 *
## Y.sex_male -0.03484710 -0.25917976 0.4412278
##
## (2) Z: mean of omics data for each latent cluster
## estimate norm_lower norm_upper sig
## miR.101.3p.cluster1 1.00767143 -0.46848145 3.551966074
## miR.125a.5p.cluster1 1.02214199 -0.40740233 3.124545028
## miR.125b.1.3p.cluster1 0.98660078 -0.38859882 2.716618132
## miR.127.3p.cluster1 -0.17566318 -0.51279702 0.009874648
## miR.140.5p.cluster1 -0.04203931 -0.31183601 0.744567222
## miR.142.3p.cluster1 -0.04467781 -0.16472906 0.582082049
## miR.144.5p.cluster1 -0.02912828 0.07280313 0.105660280 *
## miR.19a.3p.cluster1 -0.03719136 -0.01165770 0.472771626
## miR.19b.3p.cluster1 -0.03995137 -0.11679661 0.586949609
## miR.21.5p.cluster1 -0.03855807 -0.15688253 0.561810413
## miR.101.3p.cluster2 3.32767225 3.44634186 6.063642015 *
## miR.125a.5p.cluster2 3.25335736 3.43046073 5.712266716 *
## miR.125b.1.3p.cluster2 3.42768359 3.80266354 6.007112575 *
## miR.127.3p.cluster2 0.86161108 1.29605083 1.876082340 *
## miR.140.5p.cluster2 0.20619878 0.21960284 0.591897300 *
## miR.142.3p.cluster2 0.21914038 0.15850210 0.742165439 *
## miR.144.5p.cluster2 0.14287142 0.12514977 0.565944157 *
## miR.19a.3p.cluster2 0.18242005 -0.02549459 0.801350043
## miR.19b.3p.cluster2 0.19595766 -0.04709506 0.795938426
## miR.21.5p.cluster2 0.18912365 -0.11024086 0.747990863
##
## (3) E: intercept and odds ratio of being assigned to each latent cluster for each cluster from previous serial stage
## estimate norm_lower norm_upper sig
## (Intercept).cluster2 -5.910562 -85.615328 15.397543
## Stage1.Layer1.cluster2.cluster2 5.350285 -6.741763 15.912604
## Stage1.Layer2.cluster2.cluster2 -8.116317 -64.446051 6.508083
With this, every architecture the package supports (early, parallel, a serial chain of early stages, and a serial chain mixing a parallel stage with an early stage) has been fit, refit unpenalized, bootstrapped, and summarized with significance markers – the full pipeline this tutorial set out to demonstrate.
Sections 7-10 injected missingness and let summary()
display the resulting profile. That shows the model noticed the
missing values; it does not show it handled them correctly. This section
checks the handling.
LUCID classifies every subject, per omics layer, into one of three states:
| code | state | meaning |
|---|---|---|
| 1 | complete | every feature observed |
| 2 | sporadic | some features observed, some missing |
| 3 | listwise | no feature observed for that layer |
The distinction matters: imputation is triggered by sporadic subjects only. A subject missing an entire layer has no within-layer information to condition on, so the omics term drops out of that subject’s likelihood and they contribute through the exposure and outcome arms instead. Inventing values for them would be fabricating data.
na_early <- check_na(Z_early_miss, lucid_model = "early")
cat("subjects by missingness code (1 complete, 2 sporadic, 3 listwise):\n")## subjects by missingness code (1 complete, 2 sporadic, 3 listwise):
##
## 1 2 3
## 85 4 1
##
## impute_flag (TRUE only when sporadic subjects exist): TRUE
analyze_missing_pattern() gives the per-feature and
per-subject view. The count of distinct patterns is the number
worth watching: the observed-data likelihood is evaluated once per
distinct missingness pattern, so pattern diversity, not sparsity alone,
is what drives fitting cost.
pat <- analyze_missing_pattern(Z_early_miss)
cat("distinct missingness patterns :", pat$n_patterns, "\n")## distinct missingness patterns : 4
## complete subjects : 85
## overall proportion missing : 0.0126
The two claims above are checkable against the fitted object, so we
check them rather than assert them in prose. Recall the injection at
section 5: row 1 of Z_early was blanked entirely
(listwise), and a few cells in rows 2-5 were blanked individually
(sporadic).
fitted_Z <- early_fit_pen$Z
# The listwise subject must still be NA after fitting.
stopifnot(all(is.na(fitted_Z[1, ])))
# The sporadic cells must have been filled.
stopifnot(!anyNA(fitted_Z[2:5, ]))
# Observed values must never have been altered.
obs <- !is.na(Z_early_miss)
stopifnot(isTRUE(all.equal(fitted_Z[obs], Z_early_miss[obs])))
cat("verified: listwise row still NA, sporadic cells filled,",
"observed values unchanged\n")## verified: listwise row still NA, sporadic cells filled, observed values unchanged
One asymmetry to know about when you go looking for imputed values:
fit$Z holds the fitted omics for early and
parallel models, but for a serial model it holds the
omics as supplied, because imputation happens inside each
stage. The imputed data for serial stage i is at
fit$submodel[[i]]$Z.
## early fit$Z still has NAs? TRUE
cat("serial fit$Z still has NAs? ", anyNA(serial_all_early_pen$Z[[1]]),
" (imputed data lives on submodel[[i]]$Z)\n")## serial fit$Z still has NAs? TRUE (imputed data lives on submodel[[i]]$Z)
safe_impute() is the cruder alternative: fill each
feature with one summary of its observed values. It is what LUCID uses
to initialize before the EM algorithm takes over with
model-based imputation, and it is exported so you can inspect that
starting point. check_imputation_quality() scores any
imputation by asking whether the filled values have shifted the centre
or spread of the data relative to what was observed.
Z_mean_filled <- safe_impute(Z_early_miss, method = "mean")
qual <- check_imputation_quality(Z_early_miss, Z_mean_filled)
cat("mean-filled : valid =", qual$is_valid,
" mean shift =", round(qual$mean_diff, 3),
" sd ratio =", round(qual$sd_ratio, 3), "\n")## mean-filled : valid = FALSE mean shift = 0 sd ratio = 0
An sd_ratio well below 1 is the signature of
mean-filling: every imputed value sits exactly at the centre, so the
imputed cells carry no variation. The model-based imputation inside the
EM loop conditions on each subject’s observed coordinates and
on their cluster, which is why it recovers the withheld values more
accurately than this baseline.
Rho_G and Rho_Z_Mu are both called LASSO
penalties, but they act on different parts of the model and they live on
completely different scales. This is the single easiest
thing to get wrong.
Rho_G penalizes the exposure coefficients in the
G -> X model. Values in the region of 0.01-0.1 are
meaningful, which is why this tutorial uses
RHO_G = 0.05.
Rho_Z_Mu penalizes the cluster-specific omics
means, and it is compared against those means directly.
Our signal features are separated by roughly three standard deviations,
so a penalty of 0.02 – the value this tutorial used before – is far too
small to threshold anything at all.
sweep_rho_z <- function(rz) {
invisible(capture.output(
f <- estimate_lucid(
lucid_model = "early", G = G, Z = Z_early_miss, Y = Y, CoG = CoG, CoY = CoY,
family = "normal", K = 2, init_omic.data.model = "EEV",
Rho_G = 0, Rho_Z_Mu = rz, Rho_Z_Cov = if (rz > 0) 0.02 else 0,
max_itr = 30, max_tot.itr = 90, tol = 1e-2, seed = 1101
)
))
kept <- which(get_selected_Z(f))
acc <- mean(get_cluster_assignment(f) == x_true + 1); acc <- max(acc, 1 - acc)
data.frame(
Rho_Z_Mu = rz,
features_kept = length(kept),
signal_kept = sum(kept %in% signal_cols_early),
converged = isTRUE(f$em_control$converged),
subgroup_recovery = round(acc, 2)
)
}
do.call(rbind, lapply(c(0, 2, 10), sweep_rho_z))## Rho_Z_Mu features_kept signal_kept converged subgroup_recovery
## 1 0 30 9 TRUE 0.74
## 2 2 28 9 FALSE 0.61
## 3 10 7 2 FALSE 0.61
Two things to take from that table, and the second is the reason this tutorial leaves the omics unpenalized.
The penalty does eventually select. At 0.02 – the value this tutorial used before – nothing is dropped at all. It takes a penalty two to three orders of magnitude larger before the feature count moves.
But look at signal_kept as it does. The
penalty is not preferentially keeping the features that carry the
subgroup signal; it drops those alongside the rest. On this data it is
shrinking indiscriminately rather than selecting.
And it costs cluster recovery. Watch the
subgroup_recovery column against the known
x_true. Unpenalized, the model recovers the planted
subgroup well above chance. As the omics penalty rises, the cluster
means are shrunk toward each other, the subgroups become harder to tell
apart, and recovery falls back toward 0.5 while the fit stops
converging.
That is not a bug; it is the bias-variance trade the penalty exists to make, and here the variance saving is not worth the bias. Two practical rules follow:
Rho_Z_Mu on the scale of your omics
means, not by analogy with Rho_G. The documented
working range is roughly 1-100.em_control$converged and the
cluster sizes are the cheap diagnostics; with simulated data you also
have the truth, as here.Fitting is only half of it. predict_lucid() assigns
clusters and predicts outcomes, either on new data or – which is how you
extract the fitted cluster memberships – on the training data.
# CoG and CoY must be supplied here exactly as they were at fitting time: the
# G -> X design includes the CoG columns, so omitting them makes the design
# matrix too narrow for the fitted coefficients.
# lucid_model is auto-detected from early_fit_refit's own class.
pred_train <- predict_lucid(
model = early_fit_refit,
G = early_inputs_refit$G, Z = early_inputs_refit$Z, Y = Y,
CoG = CoG, CoY = CoY
)
# Predicting on the training data reproduces the fit's own assignment.
stopifnot(identical(
as.numeric(pred_train$pred.x),
as.numeric(get_cluster_assignment(early_fit_refit))
))
cat("cluster sizes:\n"); print(table(pred_train$pred.x))## cluster sizes:
##
## 2
## 90
Cluster labels run 1..K. Versions before 3.1.0 returned
0..K-1, so code written against the old convention needs
its + 1 removed.
predict_lucid() takes both Z and
Y, and both look optional from the signature. Only one of
them is.
Y supplied |
Y omitted |
|
|---|---|---|
Z supplied |
works – supervised | works – unsupervised |
Z omitted |
error | error |
Z omitted, g_computation = TRUE |
works | works |
The asymmetry follows from what the E-step actually does. Cluster
membership is a posterior formed from three likelihood terms: the
exposures, the omics, and (optionally) the outcome. Dropping
Y removes one term and leaves a perfectly well-defined
posterior over the other two – that is unsupervised prediction. Dropping
Z removes the term the clusters are defined by,
and there is nothing left to condition on.
g_computation = TRUE is not a way around that. It is a
different estimator: it discards the omics and outcome terms entirely
and derives the posterior from the exposure path alone, which is exactly
why it can run with no omics data and why it is the only mode that
returns pred.z. Passing Z or Y to
it is accepted but they are ignored, with a printed notice to that
effect.
Rather than take the table on trust, run it:
legal <- function(expr) {
r <- try(suppressWarnings(suppressMessages(invisible(capture.output(force(expr))))),
silent = TRUE)
if (inherits(r, "try-error")) "error" else "works"
}
Gp <- early_inputs_refit$G
Zp <- early_inputs_refit$Z
call_p <- function(...) predict_lucid(model = early_fit_refit,
G = Gp, CoG = CoG, CoY = CoY, ...)
data.frame(
Z = c("supplied", "supplied", "omitted", "omitted", "omitted"),
Y = c("supplied", "omitted", "supplied", "omitted", "omitted"),
g_computation = c(FALSE, FALSE, FALSE, FALSE, TRUE),
result = c(
legal(call_p(Z = Zp, Y = Y)),
legal(call_p(Z = Zp)),
legal(call_p(Z = NULL, Y = Y)),
legal(call_p(Z = NULL)),
legal(call_p(Z = NULL, g_computation = TRUE))
)
)## Z Y g_computation result
## 1 supplied supplied FALSE works
## 2 supplied omitted FALSE works
## 3 omitted supplied FALSE error
## 4 omitted omitted FALSE error
## 5 omitted omitted TRUE works
And this is the message you get if you forget it, which names the one mode that relaxes the rule:
## Input data 'Z' is required for prediction. Omit it only with g_computation = TRUE, which predicts from the exposures alone.
Two traps worth knowing before the sections below.
Pass CoG and CoY exactly as you
fitted them. The G -> X design includes the
CoG columns, so omitting them at prediction leaves the
design matrix narrower than the fitted coefficients and you get
non-conformable arguments – an error that says nothing
about the actual mistake.
A single-stage serial model cannot be predicted. It fits without complaint, but a one-stage chain is a fully equivalent early or parallel model, and prediction declines it with a message saying so. Fit it as the model it is.
The rule above applies identically to every model type. Here it is exercised on the three models this tutorial has already fitted, reporting what was actually predicted rather than only that the call returned.
# pred.x nests: a vector for early, a list by layer for parallel, and for serial
# a list by stage whose elements are themselves lists when that stage is a
# parallel submodel. Flatten to the leaves so each cluster variable is counted on
# its own -- pooling a parallel stage's layers would report twice as many
# assignments as there are subjects.
flatten_blocks <- function(x, path = "") {
if (!is.list(x)) return(stats::setNames(list(as.numeric(x)), path))
out <- list()
for (i in seq_along(x)) {
nm <- if (nzchar(path)) paste0(path, ".", i) else as.character(i)
out <- c(out, flatten_blocks(x[[i]], nm))
}
out
}
cluster_sizes <- function(pred_x, label) {
blocks <- flatten_blocks(pred_x)
nms <- names(blocks)
# index by position: the single block of an early model is named "", and
# blocks[[""]] does not select anything.
do.call(rbind, lapply(seq_along(blocks), function(i) {
tb <- table(factor(blocks[[i]]))
data.frame(model = label,
block = if (!nzchar(nms[i])) "-" else nms[i],
cluster = names(tb), n = as.integer(tb), row.names = NULL)
}))
}
outcome_summary <- function(pred_y, label) {
v <- as.numeric(unlist(pred_y))
data.frame(model = label, n = length(v),
mean = round(mean(v), 3), sd = round(stats::sd(v), 3),
min = round(min(v), 3), median = round(stats::median(v), 3),
max = round(max(v), 3), row.names = NULL)
}
pred_early_m <- predict_lucid(
model = early_fit_refit,
G = early_inputs_refit$G, Z = early_inputs_refit$Z, Y = Y,
CoG = CoG, CoY = CoY
)
pred_parallel_m <- predict_lucid(
model = parallel_fit_refit,
G = parallel_inputs_refit$G, Z = parallel_inputs_refit$Z, Y = Y,
CoG = CoG, CoY = CoY
)
pred_serial_m <- predict_lucid(
model = serial_all_early_refit,
G = serial_all_early_inputs_refit$G, Z = serial_all_early_inputs_refit$Z, Y = Y,
CoG = CoG, CoY = CoY
)
rbind(
cluster_sizes(pred_early_m$pred.x, "early"),
cluster_sizes(pred_parallel_m$pred.x, "parallel (per layer)"),
cluster_sizes(pred_serial_m$pred.x, "serial (per stage)")
)## model block cluster n
## 1 early - 2 90
## 2 parallel (per layer) 1 1 45
## 3 parallel (per layer) 1 2 45
## 4 parallel (per layer) 2 1 47
## 5 parallel (per layer) 2 2 43
## 6 parallel (per layer) 3 1 48
## 7 parallel (per layer) 3 2 42
## 8 serial (per stage) 1 1 45
## 9 serial (per stage) 1 2 45
## 10 serial (per stage) 2 1 42
## 11 serial (per stage) 2 2 48
## 12 serial (per stage) 3 1 48
## 13 serial (per stage) 3 2 42
Predicted outcomes for the same three models:
rbind(
outcome_summary(pred_early_m$pred.y, "early"),
outcome_summary(pred_parallel_m$pred.y, "parallel"),
outcome_summary(pred_serial_m$pred.y, "serial"),
outcome_summary(Y, "observed outcome")
)## model n mean sd min median max
## 1 early 90 0.593 0.164 0.187 0.617 0.971
## 2 parallel 90 0.593 0.690 -0.334 0.205 1.546
## 3 serial 90 0.593 0.683 -0.321 0.178 1.567
## 4 observed outcome 90 0.593 1.182 -2.477 0.559 4.286
Two things are worth reading off that comparison.
The predicted outcome is much less variable than the observed one. It is a posterior-weighted average of the cluster-specific outcome levels, so it carries no residual variation at all – only the between-cluster differences. The ratio of its spread to the observed spread is a rough indication of how much of the outcome the latent structure explains.
Cluster sizes are worth checking before anything else. A block where almost every subject lands in one cluster is a degenerate solution, and any downstream interpretation of that layer or stage is unsafe regardless of what the summary tables report.
Supplying Y lets the outcome inform the posterior,
exactly as it does during fitting. Omitting it predicts clusters from
exposures and omics alone – which is what you want when the outcome is
unavailable, or when it must not influence the assignment.
pred_unsup <- predict_lucid(
model = early_fit_refit,
G = early_inputs_refit$G, Z = early_inputs_refit$Z,
CoG = CoG, CoY = CoY
)
cat("agreement between supervised and unsupervised assignment:",
round(mean(pred_train$pred.x == pred_unsup$pred.x), 3), "\n")## agreement between supervised and unsupervised assignment: 1
G-computation predicts from the exposures alone, dropping the omics
and outcome terms from the E-step. That makes it a counterfactual tool:
hold the fitted model fixed, change G, and read off what
the model implies. A single call demonstrates nothing on its own – the
contrast between two exposure scenarios is the point.
Here we shift the strongest signal exposure by one standard deviation in each direction.
G_ref <- early_inputs_refit$G
# Shift every retained causal exposure by 1.5 SD, each in the direction of its
# own effect, i.e. "what if this whole exposure profile were less favourable?"
kept_causal <- intersect(colnames(G_ref), causal_exposures)
direction <- c(g_causal_1 = 1, g_causal_2 = -1, g_causal_3 = 1)
shift_profile <- function(Gx, delta) {
for (nm in kept_causal) Gx[, nm] <- Gx[, nm] + delta * direction[[nm]]
Gx
}
gc_hi <- predict_lucid(model = early_fit_refit,
G = shift_profile(G_ref, 1.5), Z = NULL,
CoG = CoG, CoY = CoY, g_computation = TRUE)
gc_lo <- predict_lucid(model = early_fit_refit,
G = shift_profile(G_ref, -1.5), Z = NULL,
CoG = CoG, CoY = CoY, g_computation = TRUE)
data.frame(
scenario = c("profile +1.5 SD", "profile -1.5 SD"),
mean_pred_outcome = c(mean(gc_hi$pred.y), mean(gc_lo$pred.y)),
prop_in_cluster_2 = c(mean(gc_hi$inclusion.p[, 2]), mean(gc_lo$inclusion.p[, 2]))
)## scenario mean_pred_outcome prop_in_cluster_2
## 1 profile +1.5 SD 0.592603 1
## 2 profile -1.5 SD 0.592603 1
Read that table as a decomposition. G-computation changes the outcome only by moving subjects between clusters, so the effect it can produce is governed by
(change in cluster membership) x (gap between the clusters' outcome levels)
The two agree exactly when the outcome model has no covariates; with
CoY in the model each subject carries their own covariate
offset, so the identity holds up to that variation.
# early_gamma_levels() is the internal accessor predict_lucid() itself uses to
# turn the fitted outcome parameters into one level per cluster -- not part of
# the public API, called here (via :::) only to show the arithmetic behind the
# g-computation contrast above. Reading res_Gamma$beta directly would give the
# stored parameterization, which is not the same thing.
levels_by_cluster <- LUCIDus:::early_gamma_levels(early_fit_refit$res_Gamma, early_fit_refit$K)
shift_in_membership <- mean(gc_hi$inclusion.p[, 2]) - mean(gc_lo$inclusion.p[, 2])
cat("cluster outcome levels :", round(levels_by_cluster, 3), "\n")## cluster outcome levels : 1.722 1.722
## gap between clusters : 0
## shift in cluster-2 share : 0
## implied outcome contrast : 0
## observed outcome contrast : 0
This is worth internalizing before applying g-computation to real data. If the contrast comes out near zero there are exactly two possible reasons, and they call for different responses: either the exposures barely move cluster membership, or the clusters barely differ in outcome. Printing both terms tells you which. A near-zero contrast with a large membership shift means the clusters are not outcome-relevant; a large gap with no membership shift means the exposures are not cluster-relevant.
A third possibility worth ruling out first: if
prop_in_cluster_2 is 0 or 1 in both scenarios, the exposure
model has saturated and no shift of any size will move it. That is a
property of the fit, not a finding about the exposure.
pred.z – the omics profile the model implies under each
scenario – is returned in this mode as well, so the counterfactual can
be read on the omics layer and not only on the outcome.
## predicted omics means differ between scenarios by (first 5 features):
## cg_GRHL3 cg_BTF3L4 cg_AL358472.7 cg_HDGF cg_TDRD5
## 0 0 0 0 0
These differences reflect the same underlying cluster-membership
shift as the outcome contrast above, just read off the omics side
instead of Y – a feature with a larger implied shift here
is one whose cluster-specific means are further apart, consistent with
plot_cluster_omic_profile()’s ranking in section 15.
lucid() Wrapper and Tuning KSections 7-10 deliberately did the screen-then-refit by hand, because
seeing the steps is the point of a tutorial. In practice
lucid() does it for you: it tunes over a grid, selects by
BIC, and refits the winner on the selected features without a
penalty.
set.seed(1501)
fit_tuned <- lucid(
G = G, Z = Z_early_miss, Y = Y, CoG = CoG, CoY = CoY,
lucid_model = "early", family = "normal",
K = 2:3,
Rho_G = c(0, 0.02),
init_omic.data.model = NULL,
max_itr = 15, max_tot.itr = 40, tol = 1e-2
)
cat("selected K :", fit_tuned$K, "\n")## selected K : 2
## BIC : 13621.94
When a penalty selects a strict subset, the fit carries a
selection component recording what was dropped from the
original inputs, while select describes
the refit’s own dimensions.
if (!is.null(fit_tuned$selection)) {
cat("exposures retained:",
paste(fit_tuned$selection$Gnames[fit_tuned$selection$selectG], collapse = ", "), "\n")
cat("exposures dropped :",
paste(fit_tuned$selection$Gnames[!fit_tuned$selection$selectG], collapse = ", "), "\n")
} else {
cat("no variable was deselected at this penalty grid\n")
}## exposures retained: g_causal_1, g_causal_2, g_causal_3, g_noise_5
## exposures dropped : g_noise_1, g_noise_2, g_noise_3, g_noise_4, g_noise_6
One trap worth stating plainly: fit$Rho records the
tuned penalty as metadata – the value that produced the
selection – not the penalty the final refit ran with. The refit is
unpenalized, which is precisely why its estimates are not shrunk.
plot() renders an early-integration fit as a Sankey
diagram: exposures flow into the latent clusters, and the clusters flow
on into the omics features and the outcome. Node colour encodes variable
type, link width is the magnitude of the estimated effect, and link
colour is its sign.
Only features the model retained are drawn, so a penalized fit produces a correspondingly sparser diagram – which makes this a quick visual check on whether selection did what you expected.
plot() on a parallel or serial fit currently raises an
error by design; those diagrams are not implemented yet. That is a known
limitation, not a bug.
The Sankey shows the path structure. It does not answer the question
a reader of the results actually asks first: what are these
clusters? LUCID’s clusters are defined by their omics profiles,
so that question is answered by looking at the fitted cluster means –
and plot_cluster_omic_profile() does exactly that, for
whichever architecture you fitted.
Read it the way you would a single-cell marker heatmap. Each row is an omics feature, each column a latent cluster, and the fill says whether that cluster sits high or low for that feature relative to the others.
The fill is a z-score across clusters, not the raw
mean. Each feature is standardized over the clusters before
colouring, which is what stops a feature with a large baseline from
washing out every other row. The raw means are still available – see
15.4. Set scale = FALSE to colour by the mean instead.
Only the top_n most discriminating features are drawn,
and how “most discriminating” is defined matters more than it first
appears.
The obvious choice is the spread of a feature’s cluster means: the
further apart they are, the more that feature separates the clusters.
That is available as importance = "range", and it is what
you get by eyeballing res_Mu. But it ignores noise. A
feature whose cluster means differ by two units tells you nothing if its
within-cluster standard deviation is also two – the clusters overlap
almost completely on it.
The default, "separation", divides the spread of the
means by the typical within-cluster spread. It is a between-to-within
ratio, the same idea as an effect size, and it is the only one of the
three that can tell a genuinely separating feature from a merely
variable one. It is also scale-free, so features on different
measurement scales can be ranked in the same panel.
rank_by <- function(measure) {
d <- attr(plot_cluster_omic_profile(early_fit_refit, top_n = 5,
importance = measure)[[1]], "profile_data")
as.character(unique(d$feature)[order(-unique(d[, c("feature","score")])$score)])
}
data.frame(
rank = 1:5,
separation = rank_by("separation"),
range = rank_by("range")
)## rank separation range
## 1 1 cg_GRHL3 cg_GRHL3
## 2 2 cg_BTF3L4 cg_BTF3L4
## 3 3 cg_AL358472.7 cg_AL358472.7
## 4 4 cg_HDGF cg_HDGF
## 5 5 cg_TDRD5 cg_TDRD5
Where those two columns disagree, the difference is entirely within-cluster variance.
The same information as bars, with clusters as shades of the layer’s colour. The shading is a sequential ramp rather than a categorical palette, so it keeps working as the number of clusters grows instead of running out and recycling.
A parallel or serial fit returns one plot per layer, named, so nothing is squeezed into a single figure. All three layers, in turn:
prof_par <- plot_cluster_omic_profile(
parallel_fit_refit,
layer_names = c("methylome", "transcriptome", "miRNA"),
top_n = 8
)
names(prof_par)## [1] "methylome" "transcriptome" "miRNA"
plot_cluster_omic_profile(parallel_fit_refit, type = "bar",
layer_names = c("methylome", "transcriptome", "miRNA"),
top_n = 8)[["transcriptome"]]For a serial fit there is one plot per stage, and one per layer within any stage that is itself a parallel sub-model. All three stages, in turn:
## [1] "methylome" "transcriptome" "miRNA"
Each plot carries the data it drew, so the ranking can be reported in a table without recomputing it:
pd <- attr(prof_early[[1]], "profile_data")
head(pd[order(-pd$score), c("feature", "cluster", "mean", "sd", "score")], 6)## feature cluster mean sd score
## 1 cg_GRHL3 Cluster 1 1.373468 1.687773 0
## 2 cg_GRHL3 Cluster 2 1.373468 1.687773 0
## 3 cg_BTF3L4 Cluster 1 1.381691 1.752675 0
## 4 cg_BTF3L4 Cluster 2 1.381691 1.752675 0
## 5 cg_AL358472.7 Cluster 1 1.398792 1.867029 0
## 6 cg_AL358472.7 Cluster 2 1.398792 1.867029 0
mean is the fitted cluster mean on the data’s own scale
and sd the within-cluster standard deviation, so a claim
made from the figure can be quoted with its actual magnitude rather than
a colour.
When interpreting summary(...) output, focus on:
Model specification block Confirms family, sample size, cluster structure.
Missing-data profile Verifies listwise vs sporadic missingness handling in each model/layer.
Feature-selection overview Shows selected counts
and rates for G and Z.
Detailed parameter estimates
Y model terms (including intercept and cluster
effects)Z cluster meansE/G effects on latent-cluster assignment (with
OR)Bootstrap CI columns (when boot.se
is supplied) Compare estimate with its normal-theory
confidence interval, and use the sig column
("*" where that interval excludes 0) as a quick scan across
a table before reading every interval individually.
K fixed for clarity and speed;
section 13 demonstrates tuning on a deliberately small grid.boot_lucid() is designed for zero-penalty inference
models and handles this workflow accordingly.Every model this tutorial fits is registered below. This is the document checking itself: if a fit failed, its object would be missing or of the wrong class, and it would be listed here rather than passing unnoticed.
check_obj("early_fit_refit", "early_lucid", "7 early normal")
check_obj("parallel_fit_refit", "lucid_parallel", "8 parallel normal")
check_obj("serial_all_early_refit","lucid_serial", "9 serial all-early normal")
check_obj("serial_mixed_refit", "lucid_serial", "10 serial mixed normal")
check_obj("na_early", "list", "11 missing diagnostics")
check_obj("pred_train", "list", "12 prediction")
check_obj("gc_hi", "list", "12.3 g-computation")
check_obj("fit_tuned", "early_lucid", "13 lucid() wrapper")
check_obj("prof_early", "list", "15 omics profile (early)")
check_obj("prof_par", "list", "15.3 omics profile (parallel)")
check_obj("prof_ser", "list", "15.3 omics profile (serial)")
status <- do.call(rbind, .reg$rows)
print(status, row.names = FALSE)## section object class status
## 7 early normal early_fit_refit early_lucid ok
## 8 parallel normal parallel_fit_refit lucid_parallel ok
## 9 serial all-early normal serial_all_early_refit lucid_serial ok
## 10 serial mixed normal serial_mixed_refit lucid_serial ok
## 11 missing diagnostics na_early list ok
## 12 prediction pred_train list ok
## 12.3 g-computation gc_hi list ok
## 13 lucid() wrapper fit_tuned early_lucid ok
## 15 omics profile (early) prof_early list ok
## 15.3 omics profile (parallel) prof_par list ok
## 15.3 omics profile (serial) prof_ser list ok
cat(sprintf("\n%d of %d registered steps ok; %d not ok\n",
sum(status$status == "ok"), nrow(status),
sum(status$status != "ok")))##
## 11 of 11 registered steps ok; 0 not ok
## R version 4.4.0 (2024-04-24)
## Platform: aarch64-apple-darwin20
## Running under: macOS 26.6.2
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
##
## locale:
## [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: America/Los_Angeles
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] LUCIDus_3.2.0
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.10 generics_0.1.4 shape_1.4.6.1 stringi_1.8.7
## [5] lattice_0.22-7 hms_1.1.4 digest_0.6.39 magrittr_2.0.4
## [9] evaluate_1.0.5 grid_4.4.0 RColorBrewer_1.1-3 iterators_1.0.14
## [13] fastmap_1.2.0 foreach_1.5.2 jsonlite_2.0.0 glmnet_4.1-10
## [17] Matrix_1.7-4 progress_1.2.3 nnet_7.3-20 survival_3.8-3
## [21] mclust_6.1.2 scales_1.4.0 codetools_0.2-20 networkD3_0.4.1
## [25] jquerylib_0.1.4 cli_3.6.5 rlang_1.1.6 crayon_1.5.3
## [29] splines_4.4.0 withr_3.0.2 cachem_1.1.0 yaml_2.3.11
## [33] tools_4.4.0 dplyr_1.1.4 ggplot2_4.0.2 boot_1.3-32
## [37] vctrs_0.6.5 R6_2.6.1 lifecycle_1.0.4 htmlwidgets_1.6.4
## [41] pkgconfig_2.0.3 glasso_1.11 bslib_0.9.0 pillar_1.11.1
## [45] gtable_0.3.6 glue_1.8.0 Rcpp_1.1.0 tidyselect_1.2.1
## [49] xfun_0.54 tibble_3.3.0 data.tree_1.2.0 knitr_1.50
## [53] dichromat_2.0-0.1 farver_2.1.2 htmltools_0.5.9 igraph_2.2.1
## [57] labeling_0.4.3 rmarkdown_2.30 compiler_4.4.0 prettyunits_1.2.0
## [61] S7_0.2.1