## ----setup, include = FALSE--------------------------------------------------- knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(vbpm) ## ----fit---------------------------------------------------------------------- sim <- sim_fa(N = 500, K = 3, ipf = 6, lam = .7, lac = .3, rseed = 1) Y <- sim$dat ## an AZ (anchor-zero) design: two anchors per factor, each specified (1) on ## its own factor and fixed to zero on the other two; every non-anchor row is ## left entirely to the data Q <- matrix(-1L, ncol(Y), 3) for (k in 1:3) { a <- which(rep(1:3, each = 6) == k)[1:2] Q[a, ] <- 0L Q[a, k] <- 1L } fit <- vbfa(Y, Q) fit ## ----access------------------------------------------------------------------- round(fit$Lam[1:6, ], 2) # posterior mean loadings round(fit$pi[1:6, ], 2) # PIPs of the unspecified entries round(fit$Phi, 2) # factor correlations (oblique by default) ## ----recovery----------------------------------------------------------------- active <- (Q == 1) | (Q == -1 & fit$pi >= .5) table(truth = sim$MLA != 0, active = active) ## ----vbfit-------------------------------------------------------------------- round(fit_stats(fit), 3) ## ----rank-adjustment, eval=FALSE---------------------------------------------- # fit_stats(fit, rank_adjust = TRUE, rank_max_J = 100) ## ----ld----------------------------------------------------------------------- simLD <- sim_fa(N = 500, K = 3, ipf = 6, lam = .7, lac = .3, ecr = .3, rseed = 2) fLD <- vbfa(simLD$dat, Q, ld = TRUE, max_it = 300, tolVal = 1e-3) ## the largest recovered residual edges, vs the planted pairs Poff <- abs(fLD$Psi); Poff[lower.tri(Poff, diag = TRUE)] <- 0 which(Poff >= sort(Poff, decreasing = TRUE)[3], arr.ind = TRUE) simLD$ofd_ind ## ----ld-restricted------------------------------------------------------------ J <- ncol(simLD$dat) Qe <- matrix(-1L, J, J) Qe[1:3, 1:3] <- 1L # a known testlet: freely estimated among these 3 items Qe[4:6, 7:9] <- 0L # a block known to be residually independent Qe[7:9, 4:6] <- 0L isSymmetric(unname(Qe)) fRestricted <- vbfa(simLD$dat, Q, ld = TRUE, Qe = Qe, max_it = 300, tolVal = 1e-3) round(fRestricted$Psi[1:3, 1:3], 3) # freely estimated: off-diagonal is not forced round(fRestricted$Psi[4:6, 7:9], 3) # fixed absent: driven to (numerical) zero ## ----ld-compare--------------------------------------------------------------- ## fully exploratory loadings isolate the comparison to the residual side Qexp <- matrix(-1L, J, 3) fDiag <- vbfa(simLD$dat, Qexp) fLDc <- vbfa(simLD$dat, Qexp, ld = TRUE, max_it = 300, tolVal = 1e-3) round(rbind(diagonal = fit_stats(fDiag)[c("BIC", "RMSEA")], ld = fit_stats(fLDc)[c("BIC", "RMSEA")]), 3) ## ----ld-control--------------------------------------------------------------- fCtrl <- vbfa(simLD$dat, Q, ld = TRUE, max_it = 300, tolVal = 1e-3, ld_control = list(xi0 = c(0.1, 0.5, 1))) fCtrl$converged # TRUE if the final v0 stage met the tolerance ## ----mar-sim------------------------------------------------------------------ simM <- sim_fa(N = 400, K = 3, ipf = 6, lam = .7, lac = .3, rseed = 1) Ym0 <- simM$dat ## items 5 and 11 go missing depending on the OBSERVED value of anchor items ## 1 and 7 (higher values make missingness more likely); the anchors ## themselves stay fully observed. This is MAR, not MCAR: the probability of ## missingness varies systematically with observed, not missing, data. set.seed(42) Ymar <- Ym0 p5 <- ifelse(Ym0[, 1] > stats::median(Ym0[, 1]), .40, .05) p11 <- ifelse(Ym0[, 7] > stats::median(Ym0[, 7]), .40, .05) Ymar[stats::rbinom(nrow(Ym0), 1, p5) == 1, 5] <- NA Ymar[stats::rbinom(nrow(Ym0), 1, p11) == 1, 11] <- NA sum(is.na(Ymar)) fMar <- vbfa(Ymar, Q) fMar$preprocess$n_missing round(fMar$Lam[c(1, 5, 7, 11), ], 2) # loadings recovered despite the missingness ## ----mar-sim-clean------------------------------------------------------------ fClean <- vbfa(Ym0, Q) max(abs(fMar$Lam - fClean$Lam)) # largest discrepancy mean(abs(fMar$Lam - fClean$Lam)) # typical discrepancy is much smaller ## ----nlsy--------------------------------------------------------------------- data(nlsy27) Yn <- as.matrix(nlsy27$dat) dim(Yn) sum(is.na(Yn)) # incomplete cells, handled in-loop fn <- vbfa(Yn, nlsy27$Q) fn fn$preprocess$n_missing # recorded on the fit round(fn$Lam, 2) ## ----nlsy-cc------------------------------------------------------------------ nrow(Yn) - sum(stats::complete.cases(Yn)) # respondents listwise deletion drops ## ----softq-------------------------------------------------------------------- ## a toy graded membership matrix for the simulated items set.seed(3) S <- matrix(runif(18 * 3, 0, .2), 18, 3) # baseline noise S[cbind(1:18, rep(1:3, each = 6))] <- runif(18, .55, .95) # true memberships Qsoft <- matrix(0L, 18, 3) # S < .20 stays 0 Qsoft[S >= .20 & S < .80] <- -1L Qsoft[S >= .80] <- 1L table(Qsoft) fsoft <- vbfa(Y, Qsoft) fsoft$converged