A Bayesian analysis for Q methodology, alongside the classical
one. Models the forced Q sort as an ordered partition of the statements
through an exact rank-order likelihood (the design quotas fix the
partition margins, so the likelihood of the observed sorting event is
exact), fits it by a parameter-expanded Gibbs sampler in R with no
compiled code and a
convergence gate on rotation-invariant functionals, resolves
rotational ambiguity via the MatchAlign post-processing of Poworoznek
et al. (2025)

bayesqm turns forced Q sorts into the full set of Q-methodology
reports, each carrying its posterior uncertainty. It reads the common
study formats, PQMethod .DAT, Ken-Q, KADE, HTMLQ and FlashQ exports,
CSV and Excel grids, and models the sort itself: each participant places
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. From one panel it derives
what a Q study reports, bounded loadings, flags, factor arrays,
statement scores, distinguishing and consensus statements, the number of
factors, and the checks behind them. Every analysis returns an object
that prints its own summary and draws its own plots.
Every decision states its rule. One posterior false-discovery rule
selects everything the analysis reports, flags, distinguishing listings,
consensus statements, and pairwise stars, and gives the expected number
of false claims alongside. The number of factors is decided by two
checks read together, whether K factors account for the panel and
whether every factor earns its place. Quantities classical practice
fixes by convention, above all the critical difference behind
distinguishing statements, are estimated from the posterior instead, and
the alignment of the posterior draws follows the MatchAlign procedure of
Poworoznek et al. (2025). The implementation is checked two ways,
against planted truth in simulation and against the qmethod reference
implementation wherever both compute the same object, and those checks
run live in the validation article on the package website.
You can usually see the answer before you compute it. Plot the sorts and each completed grid becomes its own pyramid, every statement on its tile, the color giving the column it was placed in. Two like-minded sorters put the same statements under the same colors, and a panel that shares one viewpoint repeats itself pyramid after pyramid. The numbers that follow put a figure to what the picture shows.
# install.packages("remotes")
remotes::install_github("rdazadda/bayesqm")
CRAN still carries 0.1.0, an earlier model fitted through Stan. This version is a different model with its own sampler. Install from GitHub until 0.2.0 reaches CRAN.
One real panel ships with the package, the childhood obesity study of Akhtar-Danesh (2023), so this runs without any data of your own. 33 participants sorted 42 statements about childhood obesity onto a nine-column grid.
library(bayesqm)
obesity_sorts
#> Q-sort data
#> statements : 42 participants : 33
#> distribution: 2 4 5 6 8 6 5 4 2 (sum = 42 )
#> value range : [-4, 4]
#> source : excel:Childhood obesity dataset.xlsx
plot(obesity_sorts, participants = 1:12)
How many viewpoints does the panel hold? Fit two, three, and four factors and let two checks read each candidate. One asks whether K factors account for the shared structure. The other asks whether every factor earns its place, meaning at least two participants define it and at least one statement sets it apart from the rest.
sel <- select_k(fit_ladder(obesity_sorts, K_min = 2, K_max = 4,
seed = 11, quiet = TRUE))
plot_choice_k(sel)
The classical analysis of this panel reports three factors. Here every K accounts for the panel and no factor at any K is set apart by a single statement, so the rule refuses to select, and the verdict names what the sorts show. One shared viewpoint. The one-factor fit is then the panel’s report, and its array is the shared sort itself.
fit1 <- fit_bayesian(obesity_sorts, K = 1, seed = 11)
#> grid labels -4..4 recoded onto categories 1..9 (a monotone relabeling; the sorts are unchanged)
plot_factor_array(fit1)
Darker tiles are placements the posterior is more certain of. Each of these fits takes a minute or two.
Simulated sorts with two planted viewpoints, so the right answer is known and the multi-factor reports have something to find.
sim <- generate_data(N = 14, J = 20, K = 2, noise_sd = 0.6,
primary_range = c(0.65, 0.9), seed = 7)
qdata <- qsort_data(sim$Y, distribution = sim$distribution)
lad <- fit_ladder(qdata, K_min = 2, K_max = 4, seed = 7, quiet = TRUE)
plot_choice_k(select_k(lad))
Two factors pass both checks, boxed on their row. Fit that model. The sampler runs until a convergence check passes, and the printout reports it.
fit <- fit_bayesian(qdata, K = 2, seed = 7)
#> grid labels -4..4 recoded onto categories 1..9 (a monotone relabeling; the sorts are unchanged)
fit
#> bayesqm fit: exact partition (rank-order) likelihood, PX-Gibbs
#> 14 participants, 20 statements, 2 factors; grid 1-2-2-3-4-3-2-2-1
#> draws: 2000 kept (12000 iterations, burn 2000, thin 5)
#> gate: passed (max Rhat 1.003; min ESS 1255 bulk / 1606 tail)
#> alignment: pivot draw 332, mean congruence 0.89
#> tables: compute_loadings(), compute_flags(), compute_factor_array(),
#> compute_qdc(), claims()
What gets reported is decided by one rule. 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 five percent.
claims(fit)
#> Selected claims at q = 0.05 (posterior expected FDR):
#> flags 9 participants selected (expected false 0.41)
#> distinguishing 11 listings selected (expected false 0.47)
#> consensus 0 statements selected (expected false 0.00)
#> stars 5 pairwise selected (expected false 0.17)
Behind the counts sit the full tables, and two of the views show the result at a glance. The reported sort for each factor, on its own grid:
plot_factor_array(fit)
And statement by statement, where the two viewpoints separate and where they agree:
plot_contrasts(fit)
Import. read_qsort() reads CSV, Excel (HTMLQ, FlashQ, and tablet
exports), PQMethod .DAT, Ken-Q JSON and Excel, KADE ZIP, and
Easy-HTMLQ Firebase JSON. Grids labeled -4..+4, from zero, or with any
distinct printed values work as they are, and the sorts are never
altered. qsort_data() builds the object from a plain matrix, and
plot() on it draws the pyramids.
The tables. compute_loadings() gives bounded loadings with
credible intervals, compute_flags() flag probabilities with an
explicit unclassified state, compute_factor_array() quota-exact
arrays, compute_zscores() statement scores, and compute_qdc()
distinguishing and consensus verdicts against a posterior critical
difference and the one-grid-column region. factor_characteristics() is
the per-factor block a results section quotes, and claims() selects
what gets reported. rename_factors() relabels every table at once, and
rotate_factors() and flip_factor() apply judgmental rotation to
every draw.
The number of factors. fit_ladder() fits the candidate models,
select_k() applies the two checks, and plot_choice_k() draws the
whole decision. loo_ladder() adds PSIS-LOO as directional
corroboration.
Checks. check_fit() runs the posterior-predictive checks, and
check_persons() separates sorts the model spans from shared viewpoints
it does not. Every fit passes a convergence check before it returns, and
extend() continues a chain draw for draw.
Plots and draws. The 11 views share one style, from the raw sorts to
the reported arrays, with ggplot2::autoplot() methods for loadings,
flags, contrasts, and the array; bayesqm_set_colors() themes them and
save_bayesqm_plot() writes the figures. as.matrix(), as.array(),
and as.data.frame() return the draws under Stan-style names, and the
posterior::as_draws_*() family is registered, so bayesplot and
tidybayes read a fit directly.
The package website is https://rdazadda.github.io/bayesqm/.
vignette("bayesqm-intro") walks one analysis end to end, and
vignette("output-codebook") defines every column of every reported
table. The articles go deeper: the validation checks, the decision rules
with a worked example of each verdict, and a start-to-finish walkthrough
of the obesity panel. The reference index groups every function by task,
and issues belong at https://github.com/rdazadda/bayesqm/issues.
citation("bayesqm") gives the reference. Report the version with
packageVersion("bayesqm").
GPL (>= 3); see https://cran.r-project.org/web/licenses/GPL-3 for the full text.
bayesqm is developed and maintained at the Center for Alaska Native Health Research, University of Alaska Fairbanks.