--- title: "Getting started with bayesqm" output: rmarkdown::html_vignette: toc: true toc_depth: 2 vignette: > %\VignetteIndexEntry{Getting started with bayesqm} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} bibliography: REFERENCES.bib csl: apa.csl link-citations: true --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5) library(bayesqm) ``` You have a stack of completed Q sorts and a practical question. How many viewpoints does the panel hold, who holds each one, and which statements set them apart? `bayesqm` answers from a model of the sorting event itself. Each participant placed every statement into a fixed grid, the quotas make that placement an ordered partition of the statement set, and the package computes the probability of the observed partition exactly. The tables that come back are the ones a Q study reports, with the uncertainty of every entry. This vignette walks one analysis end to end on a small demonstration fit, so it knits in seconds. On real data, replace `demo_fit()` with `fit_bayesian()`. A fit at typical panel sizes takes a minute or two. ## The sorts `read_qsort()` reads the common formats. CSV and Excel grids, PQMethod `.DAT`, Ken-Q JSON and multi-sheet Excel, KADE ZIP exports, and Easy-HTMLQ Firebase JSON. ```{r, eval = FALSE} qdata <- read_qsort("mystudy.dat") # PQMethod file, for example qdata <- qsort_data(Y, distribution = c(2, 3, 4, 5, 4, 3, 2)) ``` One scope note up front. The exact likelihood is a statement about the forced sorting event, so `fit_bayesian()` requires every sort to match the design grid and names any participant who does not. Free-distribution studies, which Ken-Q, HTMLQ, and KADE permit, are outside this model's scope. The import functions still read them for inspection. Before any model runs, look at what the participants actually did. `plot(qdata)` draws every completed sort as its own pyramid, one tile per statement, the color giving the column it was placed in. Here on `obesity_sorts`, the childhood obesity panel of @AkhtarDanesh2023 that ships with the package. 33 participants, 42 statements, a nine-column grid. ```{r, fig.height = 6} data(obesity_sorts) plot(obesity_sorts, participants = 1:6) ``` A reversed sorter, a pattern shared by nobody, a data-entry slip, all of it shows up here first, while it is still a data question rather than a modeling one. ## The fit ```{r} fit <- demo_fit(seed = 1) fit ``` The header carries what a fit is. The likelihood, the panel, the draws, the convergence check, and the alignment. Convergence is judged on summaries that do not depend on rotation, and a chain that misses the bar is extended at successive doublings up to a cap. If it still misses, the fit warns, and `extend(fit)` continues exactly where it stopped, draw for draw identical to one longer run. Rotational, sign, and label ambiguity is resolved by the MatchAlign procedure of @PoworoznekEtAl2025 with a polarity rule, so defining sorts load positively and every summary comes from one common orientation. ## Who holds each viewpoint Loadings on a bounded correlation scale, with credible intervals. ```{r} head(compute_loadings(fit)) ``` A flag probability is the posterior share of draws in which a participant defines the factor, and the unclassified state keeps cross-loading and abstention visible instead of forcing a yes or no. ```{r} compute_flags(fit) ``` ## What each viewpoint says The factor array is the viewpoint written as a completed sort, quota-exact by construction. ```{r} head(compute_factor_array(fit)) ``` ```{r} plot_factor_array(fit) ``` Darker tiles are placements the posterior is more certain of. ## Where they differ and where they agree The critical difference is computed from the posterior spread of each score contrast, and consensus is a positive finding, the event that every factor places a statement within one grid column of the others. A statement can be distinguishing, consensus, or neither. ```{r} qdc <- compute_qdc(fit) table(qdc$verdict) ``` ```{r} plot_contrasts(fit) ``` ## What gets reported One rule decides. `claims()` keeps the most probable flags, distinguishing statements, consensus statements, and pairwise stars, and stops adding claims when the expected share of false ones passes the level you set. ```{r} claims(fit, q = 0.05) ``` ## The per-factor block `factor_characteristics()` is the summary a results section quotes. How many sorts define each factor, with an interval, how spread the factor's statement scores are, and how reliable its defining sorts are. ```{r} factor_characteristics(fit) ``` ## Checking the model ```{r} check_fit(fit, draws = 30) ``` The person check separates sorts the model spans from shared viewpoints it does not. ```{r} check_persons(fit, draws = 15, mixes = 30) ``` ## How many viewpoints `fit_ladder()` fits a ladder of candidate K and `select_k()` applies two checks together. Adequacy asks whether K factors account for the shared structure in the panel. Support asks whether every factor earns its place, meaning at least two selected flags and one selected distinguishing statement. Expect one fit's runtime per rung. ```{r, eval = FALSE} ladder <- fit_ladder(qdata) select_k(ladder) plot_choice_k(select_k(ladder)) ``` `loo_ladder()` adds PSIS-LOO [@VehtariEtAl2017] as directional corroboration only. At typical Q panel sizes its standard errors cannot certify adjacent K. ## Coming from PQMethod or qmethod The outputs you know have direct counterparts. Flagging, automatic or by hand, becomes `compute_flags()`, and `claims()` selects the flags at your false-discovery level. The loadings table is `compute_loadings()`, the factor arrays and z-scores are `compute_factor_array()` and `compute_zscores()`, and the distinguishing and consensus statements are the three-way verdicts of `compute_qdc()`. In place of eigenvalue rules and parallel analysis, the number of factors comes from `fit_ladder()` and `select_k()`. Explained variance alone has no counterpart in a generative model. Report the defining-sort counts from `factor_characteristics()` and the extra-factor check from `check_fit()` instead. ## Reproducibility Fits are exactly reproducible given a seed, and `extend()` preserves that. It restores the sampler's saved random-number state, so continuing a chain after any amount of unrelated R work gives the same draws as one uninterrupted run. ## Where next The remaining views are `plot_zscores()` for the whole statement panel, `plot_statement()` for one statement in depth, `plot_loading_posterior()`, `plot_flags()`, `plot_person_check()`, `plot_convergence()`, and `plot_ppc()`, with `ggplot2::autoplot()` methods for loadings, flags, contrasts, and the array. The reference index at groups every function by task. ## References