--- title: "Advanced features" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Advanced features} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(echo = TRUE, fig.align = "center", out.width = "100%") knitr::opts_chunk$set(comment = "#>") old_options <- options(width = 100) ``` This vignette goes deeper into two things `vignette("spifa")` and `vignette("vg02-model-types")` only touch briefly: - how to control `constraints`/`priors` for every parameter group, - how to continue sampling with `update()` when a fit hasn't converged yet, and - how to compare models using `dic()`. ```{r} library(spifa) data(ipixuna) nfactors <- 3 nitems <- ncol(ipixuna$items) ``` ## Restrictions and priors `spifa()`'s parameter glossary (see `?spifa`) maps six configurable parameter names -- `easiness` (c), `discrimination` (A), `effect` (B), `corr` (Corr), `loading` (T), `range` (phi). Two `spifa()` arguments use these names: - `constraints`: fixes cells of a parameter to a value instead of estimating them. Only `discrimination`, `loading`, and `sd` support this (the model needs some fixed structure for identifiability). - `priors`: sets the `initial` value and prior hyperparameters (`mean`/`sd`, or `eta` for `corr`) for a parameter that is estimated. Every one of the six names accepts this. ### `constraints` - `discrimination` (nitems x nfactors matrix): which items inform which factors, - `loading` (nfactors x ngp matrix): which Gaussian processes feed into which factors, and - `sd`: fixed values for the latent factors' residual standard deviation (length `nfactors`). Unlike `discrimination`/`loading`, this is not estimated, it is always fixed. Define `discrimination` structure: ```{r} A <- matrix(1, nitems, nfactors) A[c(4, 8), 1] <- 0 A[c(4, 5, 6, 7, 8, 10), 2] <- 0 A[c(5, 6), 3] <- 0 A ``` Define `loading` structure, factors 1 and 2 share one Gaussian process, factor 3 gets its own: ```{r} ngp <- 2 T_loading <- matrix(c(1, 1, 0, 0, 0, 1), nfactors, ngp) T_loading ``` Fix the residual standard deviation of each factor to 0.2: ```{r} sd_fixed <- rep(0.2, nfactors) sd_fixed ``` ### `priors` Every element accepts `initial` (starting value for the sampler) and the prior hyperparameters `mean`/`sd`. The parameter `corr` is the exception: it takes `initial` and `eta` (the LKJ shape parameter) instead. By default, `spifa()` starts every parameter at a generic default and lets the data speak. If you have domain knowledge, setting `priors` closer to a plausible value can speed up convergence substantially. Here we set a plausible prior for every parameter group: - item easiness roughly increasing across items, - a couple of items with a strong sign-fixing discrimination prior (as in `vignette("spifa")`), - a wealth effect that differs in sign across factors, - moderate loadings, - factor correlations nudged away from independence, and - a range parameter around `150` as in `vignette("spifa")`: ```{r} # easiness hyperparameters c_mean <- seq(-0.7, 0.9, length.out = nitems) c_sd <- rep(0.3, nitems) # discrimination hyperparameters A_mean <- matrix(0, nitems, nfactors) A_mean[c(5, 6), 1] <- 1 A_mean[c(4, 8), 3] <- -1 A_sd <- matrix(1, nitems, nfactors) A_sd[A_mean != 0] <- 0.45 # predictor effects hyperparameters B_mean <- matrix(c(-0.5, 0.5, 0.2), nrow = 1) B_sd <- matrix(0.5, nrow = 1, ncol = nfactors) # correlation initial value corr_initial <- diag(nfactors) # range parameter hyperparameters phi_mean <- 150 phi_sd <- 0.4 ``` ### Fit with constraints and priors In the following code, we explicitely defined the `constraints`, and `priors` for all possible parameters: ```{r} set.seed(1) fit_informed <- spifa( items ~ wealth, data = ipixuna, nfactors = nfactors, ngp = 3, burnin = 200, niter = 200, thin = 2, constraints = list(discrimination = A, loading = diag(nfactors), sd = sd_fixed), priors = list( easiness = list(initial = c_mean, mean = c_mean, sd = c_sd), discrimination = list(initial = A_mean, mean = A_mean, sd = A_sd), effect = list(initial = B_mean, mean = B_mean, sd = B_sd), corr = list(initial = corr_initial, eta = 2), loading = list(initial = 0.6, mean = 0.6, sd = 0.3), range = list(initial = phi_mean, mean = phi_mean, sd = phi_sd))) fit_informed ``` ## Continue sampling Fits don't always converge on the first try. If you take a look at `rhat` in the previous section, you will notice that some values are much higher that `1.05` for example. To continue sampling from the current state you can use `update()`: ```{r, warning = FALSE} fit1 <- update(fit_informed, niter = 1000) fit1 ``` `rhat` has moved closer to 1 with the extra iterations. In practice, you have to run many more iterations. ## Compare models with DIC `dic()` computes the Deviance Information Criterion for a fitted model, useful for comparing candidate models fitted to the same data. You should compare them once convergence is ensured. For example, let's compare with a `spifa` with 2 latent factors and default `constraints` and `priors`: ```{r, warning = FALSE} fit2 <- spifa(items ~ wealth, data = ipixuna, nfactors = 2, niter = 1000, burnin = 1000) fit2 ``` Computing `dic()` for each model: ```{r} # M1: 3 factors dic(fit1, burnin = 100, thin = 2) ``` ```{r} # M2: 2 factors dic(fit2, burnin = 100, thin = 2) ``` Assuming both models have converged, the 3-factor model has the lower DIC, favouring the discrimination structure used above over an 2-factor alternative. ```{r, include = FALSE} options(old_options) ```