Nothing
## ----setup, message=FALSE, warning=FALSE--------------------------------------
library(LUCIDus)
## ----build-demo-data----------------------------------------------------------
make_demo_data <- function(n = 80, pG = 6, pZ = 4, seed = 20260309) {
set.seed(seed)
# Exposures
G <- matrix(rnorm(n * pG), nrow = n, ncol = pG)
colnames(G) <- paste0("G", seq_len(pG))
# Covariates associated with exposures
CoG <- cbind(
cov_g1 = G[, 1] + 0.2 * G[, 2] + rnorm(n, sd = 0.2),
cov_g2 = -0.3 * G[, 3] + 0.4 * G[, 4] + rnorm(n, sd = 0.25)
)
CoY <- CoG
# Latent cluster driver
lin <- 1.0 * G[, 1] - 0.8 * G[, 2] + 0.4 * CoG[, 1]
prob_x <- plogis(lin)
x <- rbinom(n, size = 1, prob = prob_x)
# Layer 1 and 2 for parallel stage
Z1 <- cbind(
1.2 * x + 0.5 * G[, 1] + rnorm(n, sd = 0.5),
1.0 * x - 0.4 * G[, 2] + rnorm(n, sd = 0.5),
0.8 * x + 0.3 * G[, 3] + rnorm(n, sd = 0.5),
0.6 * x + 0.2 * G[, 4] + rnorm(n, sd = 0.5)
)
Z2 <- cbind(
-1.1 * x + 0.45 * G[, 2] + rnorm(n, sd = 0.5),
-0.9 * x - 0.35 * G[, 1] + rnorm(n, sd = 0.5),
-0.7 * x + 0.25 * G[, 5] + rnorm(n, sd = 0.5),
-0.5 * x + 0.20 * G[, 6] + rnorm(n, sd = 0.5)
)
# Layer 3 for serial second stage (early stage)
Z3 <- cbind(
0.9 * x + 0.30 * G[, 1] + rnorm(n, sd = 0.55),
0.7 * x - 0.25 * G[, 3] + rnorm(n, sd = 0.55),
-0.8 * x + 0.20 * G[, 4] + rnorm(n, sd = 0.55),
-0.6 * x + 0.15 * G[, 6] + rnorm(n, sd = 0.55)
)
colnames(Z1) <- paste0("Z1_f", seq_len(pZ))
colnames(Z2) <- paste0("Z2_f", seq_len(pZ))
colnames(Z3) <- paste0("Z3_f", seq_len(pZ))
# Outcomes
Y_normal <- 1.1 * x + 0.5 * G[, 1] - 0.25 * G[, 3] + 0.35 * CoY[, 2] + rnorm(n, sd = 0.7)
Y_binary <- rbinom(n, size = 1, prob = plogis(-0.2 + 1.0 * x + 0.35 * G[, 1] - 0.2 * CoY[, 1]))
# Structures for each model
Z_parallel <- list(layer1 = Z1, layer2 = Z2)
Z_early <- cbind(Z1, Z2)
Z_serial_mixed <- list(list(layer1 = Z1, layer2 = Z2), Z3)
list(
G = G,
CoG = CoG,
CoY = CoY,
Y_normal = as.numeric(Y_normal),
Y_binary = as.numeric(Y_binary),
Z1 = Z1,
Z2 = Z2,
Z3 = Z3,
Z_parallel = Z_parallel,
Z_early = Z_early,
Z_serial_mixed = Z_serial_mixed
)
}
d <- make_demo_data()
## ----add-missingness----------------------------------------------------------
# Early matrix missingness
Z_early_miss <- d$Z_early
Z_early_miss[1, ] <- NA # listwise
Z_early_miss[2:4, 1] <- NA # sporadic block
Z_early_miss[5, 3] <- NA # sporadic cell
# Parallel list missingness
Z_parallel_miss <- d$Z_parallel
Z_parallel_miss[[1]][1, ] <- NA # listwise on layer1
Z_parallel_miss[[2]][2, 2] <- NA # sporadic on layer2
# Serial mixed missingness (stage1 parallel + stage2 early)
Z_serial_miss <- list(
list(
layer1 = Z_parallel_miss[[1]],
layer2 = Z_parallel_miss[[2]]
),
{tmp <- d$Z3; tmp[3, ] <- NA; tmp}
)
## ----missing-diagnostics------------------------------------------------------
miss_info_early <- analyze_missing_pattern(Z_early_miss)
na_early <- check_na(Z_early_miss, lucid_model = "early")
na_parallel <- check_na(Z_parallel_miss, lucid_model = "parallel")
miss_info_early$total_missing
table(na_early$indicator_na)
na_parallel$cross_layer_summary
## ----impute-helpers-----------------------------------------------------------
# Use a sub-matrix for concise display
orig_sub <- Z_early_miss[, 1:4, drop = FALSE]
imputed_sub <- safe_impute(orig_sub, method = "mean")
quality_sub <- check_imputation_quality(orig_sub, imputed_sub)
quality_sub$overall_quality
## ----fit-early, warning=FALSE-------------------------------------------------
set.seed(101)
fit_early <- estimate_lucid(
lucid_model = "early",
G = d$G,
Z = Z_early_miss,
Y = d$Y_normal,
CoG = d$CoG,
CoY = d$CoY,
family = "normal",
K = 2,
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 8,
max_tot.itr = 30,
tol = 1e-2,
seed = 101,
verbose = FALSE
)
class(fit_early)
## ----fit-early-structure------------------------------------------------------
names(fit_early)
cat("likelihood:", fit_early$likelihood, "\n")
cat("selected G:", sum(get_selected_G(fit_early)), "of",
length(get_selected_G(fit_early)), "\n")
cat("selected Z:", sum(get_selected_Z(fit_early)), "of",
length(get_selected_Z(fit_early)), "\n")
## ----fit-parallel, warning=FALSE----------------------------------------------
set.seed(102)
fit_parallel <- estimate_lucid(
lucid_model = "parallel",
G = d$G,
Z = Z_parallel_miss,
Y = d$Y_normal,
CoG = d$CoG,
CoY = d$CoY,
family = "normal",
K = c(2, 2),
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 8,
max_tot.itr = 30,
tol = 1e-2,
seed = 102,
verbose = FALSE
)
class(fit_parallel)
## ----fit-parallel-structure---------------------------------------------------
names(fit_parallel)
cat("N:", fit_parallel$N, "\n")
cat("selected G per layer:", paste(sapply(seq_along(fit_parallel$K), function(i)
sum(get_selected_G(fit_parallel, layer = i))),
collapse = ", "), "\n")
sel_z_parallel <- get_selected_Z(fit_parallel)
cat("selected Z per layer:", paste(sapply(sel_z_parallel, sum), collapse = ", "), "\n")
## ----fit-serial, warning=FALSE------------------------------------------------
set.seed(103)
fit_serial <- estimate_lucid(
lucid_model = "serial",
G = d$G,
Z = Z_serial_miss,
Y = d$Y_normal,
CoG = d$CoG,
CoY = d$CoY,
family = "normal",
K = list(list(2, 2), 2),
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 8,
max_tot.itr = 36,
tol = 1e-2,
seed = 103,
verbose = FALSE
)
class(fit_serial)
length(fit_serial$submodel)
## ----fit-serial-structure-----------------------------------------------------
names(fit_serial)
cat("top-level likelihood (sum over stages):", fit_serial$likelihood, "\n")
cat("top-level select is stage 1's select:",
identical(fit_serial$select, fit_serial$submodel[[1]]$select), "\n")
sel_z_serial <- get_selected_Z(fit_serial)
cat("stage 1 (parallel) selected Z per layer:",
paste(sapply(sel_z_serial[[1]], sum), collapse = ", "), "\n")
cat("stage 2 (early) selected Z:", sum(sel_z_serial[[2]]), "\n")
## ----fit-verbose-demos, warning=FALSE-----------------------------------------
n_demo <- 30
G_demo <- d$G[1:n_demo, , drop = FALSE]
Y_demo <- d$Y_normal[1:n_demo]
CoG_demo <- d$CoG[1:n_demo, , drop = FALSE]
CoY_demo <- d$CoY[1:n_demo, , drop = FALSE]
Z_early_demo <- d$Z_early[1:n_demo, , drop = FALSE]
Z_parallel_demo <- lapply(d$Z_parallel, function(z) z[1:n_demo, , drop = FALSE])
Z_serial_demo <- list(
lapply(d$Z_parallel, function(z) z[1:n_demo, , drop = FALSE]),
d$Z3[1:n_demo, , drop = FALSE]
)
set.seed(111)
fit_early_verbose <- estimate_lucid(
lucid_model = "early",
G = G_demo,
Z = Z_early_demo,
Y = Y_demo,
CoG = CoG_demo,
CoY = CoY_demo,
family = "normal",
K = 2,
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 2,
max_tot.itr = 8,
tol = 1e-2,
seed = 111,
verbose = TRUE
)
set.seed(112)
fit_parallel_verbose <- estimate_lucid(
lucid_model = "parallel",
G = G_demo,
Z = Z_parallel_demo,
Y = Y_demo,
CoG = CoG_demo,
CoY = CoY_demo,
family = "normal",
K = c(2, 2),
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 2,
max_tot.itr = 8,
tol = 1e-2,
seed = 112,
verbose = TRUE
)
set.seed(113)
fit_serial_verbose <- estimate_lucid(
lucid_model = "serial",
G = G_demo,
Z = Z_serial_demo,
Y = Y_demo,
CoG = CoG_demo,
CoY = CoY_demo,
family = "normal",
K = list(list(2, 2), 2),
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 2,
max_tot.itr = 10,
tol = 1e-2,
seed = 113,
verbose = TRUE
)
## ----tune-lucid, warning=FALSE------------------------------------------------
set.seed(104)
tune_early <- tune_lucid(
G = d$G,
Z = d$Z_early,
Y = d$Y_normal,
CoG = d$CoG,
CoY = d$CoY,
family = "normal",
lucid_model = "early",
K = 2:3,
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 6,
max_tot.itr = 24,
seed = 104
)
head(tune_early$tune_list)
## ----lucid-wrapper, warning=FALSE---------------------------------------------
set.seed(105)
fit_lucid_wrapper <- lucid(
G = d$G,
Z = d$Z_early,
Y = d$Y_normal,
CoG = d$CoG,
CoY = d$CoY,
family = "normal",
lucid_model = "early",
K = 2:3,
Rho_G = 0,
Rho_Z_Mu = 0,
Rho_Z_Cov = 0,
max_itr = 6,
max_tot.itr = 24,
seed = 105
)
class(fit_lucid_wrapper)
## ----summaries, warning=FALSE-------------------------------------------------
summary(fit_early)
summary(fit_parallel)
summary(fit_serial)
## ----predict-standard, warning=FALSE------------------------------------------
# Use lightweight no-covariate fits for robust prediction demo.
set.seed(205)
fit_early_pred <- estimate_lucid(
lucid_model = "early",
G = d$G,
Z = d$Z_early,
Y = d$Y_normal,
family = "normal",
K = 2,
max_itr = 6,
max_tot.itr = 20,
tol = 1e-2,
seed = 205
)
set.seed(206)
fit_parallel_pred <- estimate_lucid(
lucid_model = "parallel",
G = d$G,
Z = d$Z_parallel,
Y = d$Y_normal,
family = "normal",
K = c(2, 2),
max_itr = 6,
max_tot.itr = 20,
tol = 1e-2,
seed = 206
)
set.seed(207)
fit_serial_pred <- estimate_lucid(
lucid_model = "serial",
G = d$G,
Z = d$Z_serial_mixed,
Y = d$Y_normal,
family = "normal",
K = list(list(2, 2), 2),
max_itr = 6,
max_tot.itr = 24,
tol = 1e-2,
seed = 207
)
pred_early <- predict_lucid(
model = fit_early_pred,
G = d$G,
Z = d$Z_early,
Y = d$Y_normal
)
pred_parallel <- predict_lucid(
model = fit_parallel_pred,
G = d$G,
Z = d$Z_parallel,
Y = d$Y_normal
)
pred_serial <- predict_lucid(
model = fit_serial_pred,
G = d$G,
Z = d$Z_serial_mixed,
Y = d$Y_normal
)
# Cluster assignment. pred.x is a vector for early and a list -- by layer for
# parallel, by stage for serial -- so the blocks are summarised in one place.
# 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)
}))
}
rbind(
cluster_sizes(pred_early$pred.x, "early"),
cluster_sizes(pred_parallel$pred.x, "parallel"),
cluster_sizes(pred_serial$pred.x, "serial")
)
## ----cluster-assignment-extractor---------------------------------------------
identical(as.numeric(get_cluster_assignment(fit_early_pred)),
as.numeric(pred_early$pred.x))
## ----predict-standard-y-------------------------------------------------------
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
)
}
rbind(
outcome_summary(pred_early$pred.y, "early"),
outcome_summary(pred_parallel$pred.y, "parallel"),
outcome_summary(pred_serial$pred.y, "serial")
)
## ----predict-standard-vs-observed---------------------------------------------
rbind(
outcome_summary(pred_early$pred.y, "predicted (early)"),
outcome_summary(d$Y_normal, "observed")
)
## ----predict-gcomp, warning=FALSE---------------------------------------------
pred_early_g <- predict_lucid(
model = fit_early_pred,
G = d$G,
Z = NULL,
Y = NULL,
g_computation = TRUE
)
pred_parallel_g <- try(
predict_lucid(
model = fit_parallel_pred,
G = d$G,
Z = NULL,
Y = NULL,
g_computation = TRUE
),
silent = TRUE
)
names(pred_early_g)
if (inherits(pred_parallel_g, "try-error")) {
"parallel g_computation returned a try-error on this demo object; code pattern is shown above."
} else {
names(pred_parallel_g)
}
pred_serial_g <- predict_lucid(
model = fit_serial_pred,
G = d$G,
Z = NULL,
Y = NULL,
g_computation = TRUE
)
names(pred_serial_g)
length(pred_serial_g$pred.z)
## ----predict-modes------------------------------------------------------------
pred_unsup <- predict_lucid(
model = fit_early_pred,
G = d$G,
Z = d$Z_early
)
missing_z <- try(
predict_lucid(
model = fit_early_pred,
G = d$G,
Z = NULL,
Y = d$Y_normal
),
silent = TRUE
)
data.frame(
mode = c("Y omitted (unsupervised)", "Z omitted, no g-computation"),
result = c(
paste(names(pred_unsup), collapse = ", "),
if (inherits(missing_z, "try-error")) "error, as documented" else "unexpectedly succeeded"
)
)
cat(if (inherits(missing_z, "try-error")) attr(missing_z, "condition")$message else "")
## ----bootstrap-all, warning=FALSE---------------------------------------------
set.seed(106)
boot_early <- boot_lucid(
G = d$G,
Z = Z_early_miss,
Y = d$Y_normal,
CoG = d$CoG,
CoY = d$CoY,
model = fit_early,
R = 2,
conf = 0.9
)
boot_parallel <- boot_lucid(
G = d$G,
Z = Z_parallel_miss,
Y = d$Y_normal,
CoG = d$CoG,
CoY = d$CoY,
model = fit_parallel,
R = 2,
conf = 0.9
)
boot_serial <- boot_lucid(
G = d$G,
Z = Z_serial_miss,
Y = d$Y_normal,
CoG = d$CoG,
CoY = d$CoY,
model = fit_serial,
R = 2,
conf = 0.9
)
summary(fit_early, boot.se = boot_early)
summary(fit_parallel, boot.se = boot_parallel)
summary(fit_serial, boot.se = boot_serial)
## ----plotting, warning=FALSE--------------------------------------------------
plot_early <- plot(fit_early)
plot_parallel <- try(plot(fit_parallel), silent = TRUE)
plot_serial <- try(plot(fit_serial), silent = TRUE)
class(plot_early)
if (inherits(plot_parallel, "try-error")) {
"plot(fit_parallel) returned try-error in this build (parallel plot is under development)."
} else {
class(plot_parallel)
}
if (inherits(plot_serial, "try-error")) {
"plot(fit_serial) returned try-error in this build (serial plot is under development)."
} else {
class(plot_serial)
}
## ----profile-heatmap-early, fig.width = 6, fig.height = 4.5-------------------
prof_early <- plot_cluster_omic_profile(fit_early, top_n = 8)
names(prof_early)
prof_early[[1]]
## ----profile-bar-early, fig.width = 6.5, fig.height = 4.5---------------------
plot_cluster_omic_profile(fit_early, type = "bar", top_n = 8)[[1]]
## ----profile-parallel, fig.width = 6.5, fig.height = 4.5----------------------
prof_parallel <- plot_cluster_omic_profile(fit_parallel, top_n = 8)
names(prof_parallel)
prof_parallel[[1]]
## ----profile-parallel-bar, fig.width = 6.5, fig.height = 4.5------------------
plot_cluster_omic_profile(fit_parallel, type = "bar", top_n = 8)[[2]]
## ----profile-serial, fig.width = 6.5, fig.height = 4.5------------------------
prof_serial <- plot_cluster_omic_profile(fit_serial, top_n = 8)
names(prof_serial)
prof_serial[[1]]
## ----profile-serial-bar, fig.width = 6.5, fig.height = 4.5--------------------
plot_cluster_omic_profile(fit_serial, type = "bar", top_n = 8)[[2]]
## ----top-omics-features-------------------------------------------------------
top_early <- get_top_omics_features(fit_early, top_n = 5)
top_early
## ----binary-example, warning=FALSE--------------------------------------------
set.seed(107)
fit_early_binary <- estimate_lucid(
lucid_model = "early",
G = d$G,
Z = d$Z_early,
Y = d$Y_binary,
CoG = d$CoG,
CoY = d$CoY,
family = "binary",
K = 2,
max_itr = 8,
max_tot.itr = 30,
tol = 1e-2,
seed = 107
)
pred_binary_prob <- predict_lucid(
model = fit_early_binary,
G = d$G,
Z = d$Z_early,
Y = d$Y_binary,
CoG = d$CoG,
CoY = d$CoY,
response = FALSE
)
range(pred_binary_prob$pred.y)
## -----------------------------------------------------------------------------
sessionInfo()
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.