Skip to content

Commit 66c78aa

Browse files
authored
Merge branch 'main' into IK-edits
2 parents b461ac3 + 57b5344 commit 66c78aa

15 files changed

Lines changed: 2337 additions & 2193 deletions

File tree

.github/workflows/pkgdown.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
22
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
33
on:
4-
push:
5-
branches: [main, master]
6-
pull_request:
7-
branches: [main, master]
8-
release:
9-
types: [published]
4+
# push:
5+
# branches: [main, master]
6+
# pull_request:
7+
# branches: [main, master]
8+
# release:
9+
# types: [published]
1010
workflow_dispatch:
1111

1212
name: 'pkgdown Quarto manuscript'

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: brlavaan
22
Title: Bias-Reduced Latent Variable Analysis
3-
Version: 0.1.1.9005
3+
Version: 0.1.1.9007
44
Authors@R: c(
55
person("Haziq", "Jamil", , "haziq.jamil@gmail.com", role = c("aut", "cre"),
66
comment = c(ORCID = "0000-0003-3298-1010")),

R/10-sem_rbm_functions.R

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -286,14 +286,26 @@ fit_sem <- function(
286286
if (!is.null(lavargs$estimator.args$plugin_pen))
287287
plugin_pen <- lavargs$estimator.args$plugin_pen
288288
}
289-
if ("information" %in% names(lavargs)) information <- lavargs$information
290-
else information <- "observed"
291-
if ("estimator" %in% names(lavargs)) estimator <- lavargs$estimator
292-
else estimator <- "ML"
289+
# Default estimator is ML and observed information for se
290+
if ("information" %in% names(lavargs))
291+
information <- lavargs$information
292+
else
293+
information <- "observed"
294+
if ("se" %in% names(lavargs))
295+
se <- lavargs$se
296+
else
297+
se <- "standard"
298+
if ("estimator" %in% names(lavargs))
299+
estimator <- lavargs$estimator
300+
else
301+
estimator <- "ML"
293302

294303
# Validate arguments
295304
rbm <- validate_rbm(rbm)
296-
if (estimator != "ML") cli::cli_abort("Bias reduction methods are currently only available for ML estimation.")
305+
if (estimator != "ML")
306+
cli::cli_abort("Bias reduction methods are currently only available for ML estimation.")
307+
if (!(se %in% c("none", "standard", "robust.huber.white")))
308+
cli::cli_abort("Bias reduction methods are currently only for standard and robust (Huber White) standard errors.")
297309

298310
# Which method?
299311
is_ML <- rbm == "none"
@@ -469,37 +481,57 @@ fit_sem <- function(
469481
}
470482

471483
# Standard errors ------------------------------------------------------------
472-
j <- information_matrix(
473-
x = as.numeric(est),
474-
lavmodel = lavmodel,
475-
lavsamplestats = lavsamplestats,
476-
lavdata = lavdata,
477-
lavoptions = lavoptions,
478-
kind = information
479-
)
480-
if (lavmodel@ceq.simple.only) {
481-
K <- lavmodel@ceq.simple.K
482-
j <- t(K) %*% j %*% K
483-
}
484-
if (check_mat(j)) {
485-
sds <- rep(NA, length(est))
486-
jinv <- NULL
487-
488-
# EXPERIMENTAL
489-
if (TRUE) {
490-
jinv <- try(solve(Matrix::nearPD(j)$mat), silent = !TRUE)
491-
sds <- sqrt(diag(jinv))
492-
}
493-
484+
sds <- rep(NA, length(est))
485+
V <- NULL
486+
if (se == "none") {
487+
# Do nothing
494488
} else {
495-
jinv <- try(solve(j), silent = !TRUE)
496-
sds <- sqrt(diag(jinv))
489+
j <- information_matrix(
490+
x = as.numeric(est),
491+
lavmodel = lavmodel,
492+
lavsamplestats = lavsamplestats,
493+
lavdata = lavdata,
494+
lavoptions = lavoptions,
495+
kind = information
496+
)
497+
if (lavmodel@ceq.simple.only) {
498+
K <- lavmodel@ceq.simple.K
499+
j <- t(K) %*% j %*% K
500+
}
501+
if (check_mat(j)) {
502+
# Case: Information matrix is not positive definite
503+
if (FALSE) { ### EXPERIMENTAL ###
504+
V <- try(solve(Matrix::nearPD(j)$mat), silent = !TRUE)
505+
sds <- sqrt(diag(V))
506+
}
507+
} else {
508+
jinv <- try(solve(j), silent = !TRUE)
509+
if (se == "standard") {
510+
V <- jinv
511+
} else if (se == "robust.huber.white") {
512+
e <- information_matrix(
513+
x = as.numeric(est),
514+
lavmodel = lavmodel,
515+
lavsamplestats = lavsamplestats,
516+
lavdata = lavdata,
517+
lavoptions = lavoptions,
518+
kind = "first.order"
519+
)
520+
if (lavmodel@ceq.simple.only) {
521+
K <- lavmodel@ceq.simple.K
522+
e <- t(K) %*% e %*% K
523+
}
524+
V <- jinv %*% e %*% jinv
525+
}
526+
sds <- sqrt(diag(V))
527+
}
497528
}
498529

499530
# Unpack estimators and se
500531
idx <- lavpartable$free[lavpartable$free > 0]
501532
est <- est[idx]
502533
sds <- sds[idx]
534+
V <- V[idx, idx, drop = FALSE]
503535
names(est) <- names(lavaan::coef(fit0))
504536

505537
list(
@@ -509,9 +541,10 @@ fit_sem <- function(
509541
converged = res$convergence == 0L,
510542
scaled_grad = scaled_grad,
511543
optim = res,
512-
vcov = jinv,
544+
vcov = V,
513545
Sigma = Sigma,
514546
information = information,
547+
se = se,
515548
lavfun = lavfun,
516549
estimator = estimator,
517550
rbm = rbm,

R/11-brlavaan.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ create_lav_from_fitsem <- function(
210210
# else if (fit$estimator == "IBRMP") "IMP-BR ML"
211211
# else if (fit$estimator == "EBRM") "EXP-BR ML"
212212
# Change vcov slot
213-
fit0@vcov$se <- "standard"
213+
fit0@vcov$se <- fit$se
214214
fit0@vcov$vcov <- fit$vcov
215215

216216
# fit0@test <- fit_lav@test

R/12-plugins.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ NULL
1313

1414
#' @rdname plugin-penalties
1515
#' @export
16-
pen_ridge <- function(x, call = FALSE, ...) {
16+
pen_ridge <- function(x, target = rep(0, length(x)), call = FALSE, ...) {
1717
if (isTRUE(call)) return("Ridge penalty")
18-
sum(x ^ 2)
18+
sum((x - target) ^ 2)
1919
}
2020

2121
#' @rdname plugin-penalties

experiments/brsem/_prepDR.R

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Purpose of this script is to prepare the raw simulation results from Dhaene &
2+
# Rosseel (2022) for the two factor model only (the growth model was re-run by
3+
# us). Need only run this script once.
4+
15
truth <-
26
expand_grid(
37
model = c("twofac", "growth"),
@@ -11,7 +15,6 @@ truth <-
1115
unnest_wider(out) |>
1216
unnest(c(param, truth))
1317

14-
# Others
1518
# dr_file1 <- here::here("experiments/GCM_est_combined_final.RData")
1619
dr_file2 <- here::here("experiments/brsem/2FSEM_est_combined_final.RData")
1720
# if (!file.exists(dr_file1))

experiments/brsem/_seeds.R

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ here::i_am("experiments/obtain_seeds.R")
44
simu_id <-
55
expand_grid(
66
dist = c("Normal", "Kurtosis", "Non-normal"),
7-
# model = "twofac",
87
rel = c(0.8, 0.5),
98
n = c(15, 20, 50, 100, 1000)
109
) |>

experiments/brsem/_setup.R

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ library(brlavaan)
22
library(tidyverse)
33
library(furrr)
44
theme_set(theme_bw())
5-
load(here::here("experiments/simu_id.RData"))
5+
load(here::here("experiments/brsem/simu_id.RData"))
66

77
ncores <- future::availableCores() - 1
88
future::plan(multisession, workers = ncores)
@@ -19,6 +19,8 @@ sim_fun <- function(
1919
lavsim = FALSE,
2020
whichsims = c("ML", "eRBM", "iRBM", "Ozenne", "REML"),
2121
bounds = "standard",
22+
information = NULL,
23+
se = NULL,
2224
data_scale = 1,
2325
seeds = NULL
2426
) {
@@ -77,7 +79,9 @@ sim_fun <- function(
7779
maxgrad = FALSE,
7880
fn.scale = 1,
7981
bounds = bounds,
80-
start = true_vals
82+
start = true_vals,
83+
information = information,
84+
se = se
8185
)
8286

8387
fit_list <- list()
@@ -98,9 +102,10 @@ sim_fun <- function(
98102
# D&R SIMS -----------------------------------------------------------------
99103
if (model == "growth") {
100104
fit_lav <- try(growth(mod, dat, start = true_vals, bounds = bounds,
101-
ceq.simple = TRUE))
105+
information = information, se = se, ceq.simple = TRUE))
102106
} else if (model == "twofac") {
103-
fit_lav <- try(sem(mod, dat, bounds = bounds, meanstructure = TRUE))
107+
fit_lav <- try(sem(mod, dat, bounds = bounds, meanstructure = TRUE,
108+
information = information, se = se))
104109
}
105110
if (!inherits(fit_lav, "try-error")) {
106111
out <- list()

experiments/brsem/prep_res.R

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ data_files <- paste0("experiments/brsem/", c(
2828
"simu_res_twofac_mp1.RData",
2929
"simu_res_twofac_mp2.RData",
3030
"simu_res_twofac_mp3.RData",
31-
"simu_res_growth.RData"
31+
"simu_res_growth.RData",
32+
"simu_res_serobust_twofac.RData",
33+
"simu_res_serobust_growth.RData"
3234
))
3335
if (any(!file.exists(here::here(data_files)))) {
3436
cat("Downloading data files...\n")
@@ -57,6 +59,40 @@ simu_res_twofac[20:30] <- simu_res_twofac1[20:30]
5759
load(here::here("experiments/brsem/simu_res_growth.RData"))
5860
simu_res <- c(simu_res_twofac, simu_res_growth)
5961

62+
# simu_res is a list of length 60
63+
# 2 models x 2 reliability x 3 distributions x 5 sample sizes = 60 simulations
64+
#
65+
# > glimpse(simu_res[[1]])
66+
# Rows: 13,979
67+
# Columns: 13
68+
# $ seed <dbl> 1235, 1235, 1235, 1235, 1235, 1235, 1235, 1236, 1236, 1236, 1236, …
69+
# $ sim <int> 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, …
70+
# $ dist <chr> "Normal", "Normal", "Normal", "Normal", "Normal", "Normal", "Norma…
71+
# $ model <chr> "twofac", "twofac", "twofac", "twofac", "twofac", "twofac", "twofa…
72+
# $ rel <chr> "0.8", "0.8", "0.8", "0.8", "0.8", "0.8", "0.8", "0.8", "0.8", "0.…
73+
# $ n <dbl> 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15…
74+
# $ method <chr> "ML", "eRBM", "iRBM", "lav", "Ozenne", "JB", "BB", "ML", "eRBM", "…
75+
# $ est <named list> <0.74697154, 0.50594263, 1.40644613, 0.55439543, 0.23777413…
76+
# $ se <named list> <0.14227840, 0.10401636, 0.33865785, 0.16550320, 0.15639856…
77+
# $ truth <list> <0.7000, 0.6000, 0.7000, 0.6000, 0.2500, 0.2500, 0.1225, 0.0900, …
78+
# $ timing <dbl> 0.042, 1.211, 16.191, 0.068, 0.225, 0.889, 275.691, 0.057, 2.293, …
79+
# $ converged <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, …
80+
# $ Sigma_OK <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE,…
81+
82+
# Add new simulations about robust standard errors
83+
load(here::here("experiments/brsem/simu_res_serobust_twofac.RData"))
84+
load(here::here("experiments/brsem/simu_res_serobust_growth.RData"))
85+
simu_res_serobust <- c(simu_res_serobust_twofac, simu_res_serobust_growth)
86+
87+
simu_res <-
88+
map2(simu_res, simu_res_serobust, function(X, Y) {
89+
left_join(
90+
X,
91+
select(Y, seed, sim, dist, model, rel, n, method, serob = se)
92+
) |>
93+
select(seed:se, serob, everything())
94+
})
95+
6096
## ----- Convergence statistics ------------------------------------------------
6197
res_ours_nested <-
6298
simu_res |>
@@ -73,7 +109,7 @@ res_ours_nested <-
73109
} else {
74110
return(TRUE)
75111
}
76-
}, se, model)
112+
}, serob, model)
77113
)
78114

79115
res_conv <-
@@ -118,7 +154,7 @@ res <-
118154
grepl("[xy][0-9]~1", param) ~ "nu",
119155
TRUE ~ NA
120156
),
121-
across(c(est, truth, se), ~if_else(model == "growth" & type != "alpha", .x * 100, .x)), # rescale back
157+
across(c(est, truth, se, serob), ~if_else(model == "growth" & type != "alpha", .x * 100, .x)), # rescale back
122158
dist = factor(dist, levels = c("Normal", "Kurtosis", "Non-normal")),
123159
rel = factor(rel, levels = c("0.8", "0.5"), labels = c("Rel = 0.8", "Rel = 0.5")),
124160
method = factor(
@@ -128,7 +164,8 @@ res <-
128164
),
129165
bias = est - truth,
130166
relbias = bias / truth,
131-
covered = truth <= est + qnorm(0.975) * se & truth >= est - qnorm(0.975) * se
167+
covered = truth <= est + qnorm(0.975) * serob & truth >= est - qnorm(0.975) * serob,
168+
covrdse = truth <= est + qnorm(0.975) * se & truth >= est - qnorm(0.975) * se
132169
)
133170

134171
# Load D&R two factor sims
@@ -139,7 +176,9 @@ if (!file.exists(dr_data_file)) {
139176
load(dr_data_file)
140177
}
141178

142-
# For two-factor model BB & JB, get from res_dr
179+
# For two-factor model BB & JB, get from res_dr (more stable and "nicer" results
180+
# compared to ours -- but the BB & JB for growth models were rerun by us, and
181+
# results similar to D&R 2022)
143182
res <-
144183
res |>
145184
filter(!(method %in% c("Bootstrap", "Jackknife") & model == "twofac")) |>
@@ -153,7 +192,8 @@ res <-
153192
plot_df <-
154193
res |>
155194
filter(method %in% c("ML", "eRBM", "iRBM")) |>
156-
filter( converged, !is.na(se)) |>
195+
filter(converged, !is.na(se)) |>
196+
filter(param %in% c(twofacpars, growthpars)) |>
157197
# for each kind of model, filter bad standard errors
158198
filter(!(model == "twofac" & abs(se) > 5)) |>
159199
filter(!(model == "growth" & abs(se) > 500)) |>
@@ -167,7 +207,8 @@ plot_df <-
167207
plot_df50 <-
168208
res |>
169209
filter(method %in% c("ML", "eRBM", "iRBM")) |>
170-
filter( converged, !is.na(se)) |>
210+
filter(converged, !is.na(se)) |>
211+
filter(param %in% c(twofacpars, growthpars)) |>
171212
# for each kind of model, filter bad standard errors
172213
filter(!(model == "twofac" & abs(se) > 5)) |>
173214
filter(!(model == "growth" & abs(se) > 500)) |>
@@ -244,12 +285,18 @@ create_summdf <- function(model, rel) {
244285

245286
create_covrdf <- function(model) {
246287
i <- res$model == model
247-
if (model == "growth") i <- i & res$method != "REML"
288+
if (model == "twofac") i <- i & res$method != "REML"
248289

249290
res |>
250-
filter(dist != "Kurtosis", param %in% c(twofacpars, growthpars), i) |>
291+
filter(i) |>
292+
filter(dist != "Kurtosis", param %in% c(twofacpars, growthpars)) |>
251293
filter(!(model == "twofac" & abs(se) > 5)) |>
252294
filter(!(model == "growth" & abs(se) > 500)) |>
295+
filter(method != "lav") |>
296+
mutate(covered = case_when(
297+
is.na(covered) ~ covrdse, # plug in the JB and BB standard errors
298+
TRUE ~ covered
299+
)) |>
253300
summarise(
254301
covr = mean(covered, na.rm = TRUE),
255302
.by = c(dist:method, param)
@@ -412,3 +459,5 @@ save(twofacpars, growthpars, mycols, simu_id,
412459
bias_growth_80_df, bias_growth_50_df, covr_growth_df,
413460
tab_bias, tab_covr,
414461
file = here::here("experiments/brsem/results.RData"))
462+
463+
save(twofacpars, growthpars, mycols, simu_id, res, file = here::here("experiments/brsem/full_res.RData"))

0 commit comments

Comments
 (0)