Cognitive Models for Subjective Scales and Decision Making Tasks
Overview
Implements cognitive models for data from subjective (Likert or analog) scales and from decision making tasks with reaction times and choice data. Provides random generation, density functions, and custom response distributions for Bayesian estimation with 'brms', covering discrete beta, ordered beta and choice-confidence models for subjective ratings; reaction time distributions such as the ex-Gaussian and the shifted log-normal, Wald, gamma and Weibull; and sequential sampling models of choice and reaction time, including the drift diffusion model (DDM), the racing diffusion model (RDM), the lognormal race model (LNR) and the linear ballistic accumulator (LBA). The website provides examples and tutorials for using and interpreting the models. Methods are described in Ratcliff and McKoon (2008) doi:10.1162/neco.2008.12-06-420, Brown and Heathcote (2008) doi:10.1016/j.cogpsych.2007.12.002, Rouder et al. (2015) doi:10.1007/s11336-013-9396-3, Tillman et al. (2020) doi:10.3758/s13423-020-01719-6, Kubinec (2023) doi:10.1017/pan.2022.20, and Sciandra et al. (2024) doi:10.1007/s10651-023-00592-5.
New features
- **A distributional parameter pinned at a boundary is now checked against the response.** The three bounded-scale families are mixtures of a continuous part and one or more point masses, and every weight involved can be fixed in
bf(). Fixing one at a boundary switches its component off, so any observation belonging to that component has zero probability and the log-likelihood is-Infeverywhere - which CmdStan reports only asInitialization failed after 100 attempts, naming nothing.cogmod_priors()now errors first, saying how many rows are involved and how to proceed. Eleven combinations are covered: forcogmod_choco(),pmid = 0with a response exactly at the midpoint,pmid = 1with one anywhere else,pex = 0with an exact 0 or 1,bex = 0with a 1 andbex = 1with a 0; forcogmod_betagate()the samepex = 0andbexcases pluspex = 1with any interior response; and forcogmod_betadiscrete(),pzero = 0with a 0 andpzero = 1with any rating. The first and the last are the ones the documentation invites -?rcogmod_betadiscreteofferspzero = 0as the way to say a scale has no zero category, and?cogmod_priorssuggests fixingpmidorpzeroat 0 to switch the parameter off.
Show all changes in 0.3.3Hide the rest of the changes in 0.3.3
- **
cogmod_priors()now coverscogmod_choco(),cogmod_betagate()andcogmod_betadiscrete().** Every distributional parameter of the three bounded-scale rating families previously arrived either flat - improper - or with abrmsdefault aimed at a different parameterization. Two of those were worse than unhelpful. The point-mass probabilitiespmidandpzerowere flat on alogitlink, which is improper in the posterior as well as the prior whenever the event is absent from the data: with no exact midpoints anywhere, the likelihood inpmidincreases monotonically all the way to zero and nothing stops the logit running to minus infinity. That ispoutlier's failure, and they now getpoutlier's treatment, including an omitted form whose mode is at zero. Measured on 400 slider responses with no midpoints and no extremes: the flat defaults putpexat-1.1e14andpmidat-3.4e13,Rhat2.9, ESS 5, with 12% divergent transitions; these priors give -4.70 and -5.15,Rhat1.00, ESS 3000-4000, no divergences.phiarrived withstudent_t(3, 0, 2.5), whichbrmssupplies because it recognises the name from its own beta family - the same trapcogmod_exgaussian()'ssigmafalls into; oncogmod_betadiscrete()'sloglink that prior's 95% interval runs tophi = 2853, where the Beta has collapsed onto a single rating category, so it is overridden rather than filled. The Beta precisions are fenced away from both ends: below about 1 the underlying Beta is U-shaped and unbounded, and a large one narrows it sharply.muis left tobrmsin all three, since on alogitlink itsstudent_t(3, 0, 2.5)is the standard weakly informative choice. See?cogmod_priorsfor the full table and the reasoning behind each number. - **
cogmod_inits()now coverscogmod_choco()andcogmod_betadiscrete().** Both are bounded-scale families for subjective ratings, and neither has a flat region of the kind that makes an init mandatory for the RT families - every start is a proper density and a chain begun at the default does move. What it has to move away from is a description of a rating scale that no data set matches. The logit origin putspmidandpzeroat 0.5, i.e. half of every response exactly on the midpoint or outside the scale altogether; they now start at 0.05.cogmod_choco()'s two Beta precisions are behind asoftpluslink, sosoftplus(0) = 0.69puts both Beta shapes below 1 - a U-shaped rating distribution, unbounded at both ends of each half of the scale; they now start at 2, in the middle of a plateau where the fit is insensitive to the exact value.pexstarts at 0.1 rather than 0.5. On a 2000-trial slider data set the old start sat about 1900 log-likelihood units from the new one, 983 of thempmidalone. Forcogmod_betadiscrete(),phi = 1withmu = 0.5is the discrete Uniform, which is where the origin already is - it is named so thatinit = "random", which drawsphianywhere from 0.14 to 7.4, no longer decides it. So this buys warmup rather than a fit that would otherwise fail. - **
cogmod_inits()listscogmod_geg()among the families it supports.** It had targets for it but was not naming it, so the error message for an unsupported family said cogmod_geg() was one. The supported list is now derived from the targets rather than kept beside them. - **
cogmod_inits()jitters the hierarchical blocks a fifth as much as the population-level ones, and starts smooths flat.** Onejitter(default 0.25) now applies to intercepts and slopes; the standardized group-level effectsz_*, the smooth coefficientszs_*and their scalessd_*andsds_*get a fifth of it (0.05), andsds_*starts at 0.05 rather than the generic 0.25. Two numbers set the two tiers directly. The reason is that a unit of noise on a group effect or a spline coefficient is not a unit on the linear predictor: it is multiplied by a scale and a design column - tensor basis values reach tens - once per participant or basis function. On a production model with a tensor smooth on five distributional parameters and a participant intercept on six, the old jitter moved linear predictors by one to two and a half link units at some rows, which started one participant's non-decision time above 98 of their 128 trials and a sigma at 0.07 s where 0.5 was intended, all of it then explained by the outlier component; at 0.05 on those blocks the same model started where the targets say. Chains still start apart where it matters for Rhat, on the intercepts and slopes. Warm starts (cogmod_warmstart(),warmstart =) use the same rule at their smaller default. - **The 7-parameter
cogmod_ddm()is 1.7x cheaper per gradient when bothsigmabiasandsigmandtare estimated.** Stan's 7-parameterwiener_lpdf()integrates the start-point and non-decision-time ranges out by adaptive cubature - eight integrals per observation, one for the density and one per partial derivative - and takes a tolerance for them that the Stan code never passed, so it ran at Stan's default of 1e-4. It now passes1e-3(.DDM_WIENER_PRECISION). Measured per observation and gradient on CmdStan 2.38.0: with both ranges open, 274 us falls to 163; with one open the cost does not move (102 to 100 us), because a one-dimensional integral is already settled by cubature's first pass, and nothing much happens past 1e-3 either (153 us at 1e-2), which is why it stops there. On the intercept-only programbenchmarks/gradient_cost.Rtimes, 5000 trials with all seven parameters estimated, a gradient went from 3.25 s to 1.50 s (ratio 0.46, median of 21 alternating blocks). The price: the Stan density still agrees with the R one to 1e-5 over the test grid, but its gradient sits about 2e-5 relative from central differences at a typical point where it sat 2e-6 before, and 1.2e-4 withndtpushed 2.5 log units above its start - a point no chain visits after warmup, and small next to the leapfrog error a sampler already carries, but a change of an order of magnitude, so it is recorded with the constant. Any value between 3e-4 and 1e-3 measures the same gradient error and 1e-3 is the cheaper. A paired fit settles it (benchmarks/ddm_precision/): the 7-parameter model on 100 trials, both tolerances from one start, metric and seed, end to end and again with adaptation held fixed - the same 24 leapfrog steps per iteration, the same step size, acceptance 0.90 against 0.92, fewer divergences (7 against 10 and 11), min bulk ESS 248 against 206 and 261 against 224, at half the cost per gradient (445 against 883 ms): 2.2 to 2.4 times the effective samples per CPU second. That fit also puts the standalone figures above in perspective: at its posterior a gradient cost 8.3 ms per observation at Stan's default tolerance, thirty times the 274 us of the benign benchmark point. The same cost measurements are now in?cogmod_ddm, under the between-trial variability section: estimatingsigmadriftcosts about 2.8x the classic model per gradient, estimating one ofsigmabias/sigmandtabout 18x and both about 30x, and the fast path is a test for *exactly* zero, so a tight prior does not buy it back - onlysigmandt = 0inbf()does. Nothing user-facing said so before, and it is the difference between a day and weeks on a large data set.
Bug fixes
- **
cogmod_priors()now sets the wiggliness prior (sds) of a smooth on a distributional parameter.** The table in?cogmod_priorshas always listedsdsalongsidesdatexponential(1), but brms fills a smooth's blanketsdsrow itself and leaves the per-term rows empty, so a function that fills only what arrives empty never reached it:ndt ~ s(x)keptstudent_t(3, 0, 2.5)on its smooth's scale. On a log or logit link that is a half-t with median 1.9 in link units - loose enough for the smooth alone to move asigmandtby a factor of seven or walk asigmabiasacross its range, behind an intercept the family had deliberately fenced. The blanket row is now replaced, for every family and every dparcogmod_priors()handles; the response's own smooth is left to brms, as its slopes are. Group-levelsdrows were never affected. - **
cogmod_rdm()'s gradient is now exact.** Every normal tail in the RDM's Stan code went through Stan'sstd_normal_lcdf(), whose value is right but whose partial derivatives are an approximation, and in a race those partials *are* the gradient of the drifts and the boundary. Measured against central differences of the log probability, the gradient sat 2e-4 relative from the truth at a typical start and as far as 7e-2 where the drift is small, while the log probability itself was smooth to 1e-7. HMC stays exact under an inexact gradient - the accept step corrects for it - but pays in step size and acceptance. All eleven calls now go throughcogmod_log_Phi(), the function introduced below for the LNR, which brings the worst error over the same grid to 2e-7 at about 14% more per gradient evaluation. The function moved into a prelude of its own (.LOG_PHI_STAN_PRELUDE) so that any family can take it; the LogNormal's and the RDM's preludes both start with it. - **
cogmod_invgaussian()'s gradient is now exact.** The same defect as the RDM's, in the family's own Stan code: every normal tail of the Wald - the two terms of its CDF and survival, the two integrated ones a non-decision range needs, and the truncation factors that makesigmadrifta *truncated* normal drift - went throughstd_normal_lcdf().sigmadriftis differentiated almost entirely through those truncation factors, and its partial sat 1.2e-3 relative from central differences of the log probability,mu's 2.7e-4, against 1e-8 for the families already oncogmod_log_Phi(). Six of the eighteen points the gradient check walks failed on it. All of them now go throughcogmod_log_Phi(), which brings the worst error over the same grid to 5e-8; the truncation factors lose a round trip throughlog1m_exp()in the bargain, sincelog1m_exp(std_normal_lcdf(-z))iscogmod_log_Phi(z)written the long way.cogmod_exwald()builds on the same prelude and gets the same fix. Values are unchanged. - **
cogmod_geg()'s CDF term is no longer Stan'sexp_mod_normal_lcdf().** The alpha-power construction puts(shape - 1) * log F_EGinside the *density*, so the ex-Gaussian CDF is differentiated on every evaluation, not only when a response is censored. The built-in agrees with the R side to 1e-8 over the range the tests walk, but outside it - atsigma / tauaround 9, where the two terms ofF_EGcancel hardest - both its value and its partials go wrong: Stan's own central differences putd/d sigmaat 28.5 where its autodiff said 77.5, andd/d tauat 113.6 against 53.0. The Stan side now subtracts the two terms in log space itself, throughcogmod_log_Phi(), exactly as.lcdf_exgaussian()always has in R; over the same grid the worst gradient error is 2e-9, and the log CDF matches quadrature of the density to 5e-15. It costs about 10% per gradient evaluation (3.1 ms to 3.5 ms over 5000 trials, median of 21 alternating blocks). Two things kept it to that. The ex-Gaussian density is the CDF's second term overtau, so the GEG writes both out from one pair of normal tails rather than calling for each; and the pair is written inline rather than returned from a helper - avector[2]return measured half again the cost of the inline form, through the autodiff-stack allocation it makes on every call. - **
cogmod_exgaussian()'s Stan density, CDF and survival go throughcogmod_log_Phi().** The density is the same expression as before -exp_mod_normal_lpdf()anddcogmod_exgaussian()in R evaluate it too, and log-probabilities are unchanged to every digit that matters - but it is now written out overcogmod_log_Phi(), so it stays finite about 38 standardized units into the left tail where the built-in's bareerfcunderflows, and it shares its normal tail with the CDF thatcogmod_geg()is built on. Thecens()path gets the accurate CDF with it:cogmod_exgaussian_lcdf()wasexp_mod_normal_lcdf()and had the same wrong partials as the GEG's, andcogmod_exgaussian_lccdf()was onstd_normal_lcdf(). About 10% per gradient evaluation, the cost ofcogmod_log_Phi()over the built-in. - **
cogmod_lnr()andcogmod_lognormal()no longer hand Stan a non-finite gradient in the tails.**cogmod_lognormal_ldiff_Phi()formedlog(Phi(y + c) - Phi(y))fromerfcaslog(u1) + log1m(u2 / u1), and theA == 0branch ofcogmod_lognormal_acc_ltails()calledlognormal_lcdf()andlognormal_lccdf(), which areerfcalone.erfcunderflows nearx = -38- about 38 standardized log units from an accumulator's median finishing time - and past that the value islog(0)and the partials areinfor0 / 0. The outlier mixture then hides it:log_mix()stays finite with one component at-inf, but reverse mode multiplies the zero adjoint into the stored partial and0 * infisNaN, so one response in a data set turned the gradient of the whole model toNaN. That reads asGradient evaluated at the initial value is not finiteat the start of a fit and as divergent transitions afterwards. Both now go through one function,cogmod_log_Phi():erfcin the body of the distribution and, belowx = -25, the asymptotic expansion of the tail, whose leading term is the exponent itself, so nothing underflows and the result stays finite and differentiable as far asx = -1e150; its six terms agree with R'spnorm(log.p = TRUE)to 4e-16 relative, so the two branches meet with no step in the density.cogmod_lognormal_ldiff_Phi()now takes the difference in logs withlog1m_exp()rather than as a quotient of two minute numbers. Measured over a grid of decision times from 1 ms to 300 s and sigmas from 0.02 to 1.2, with and without a start-point range: 6 of 72 gradients were non-finite before and none are now, and the densities agree with the R kernels everywhere, with no value newly truncated to-inf.std_normal_lcdf()is not used for this, although it has the range - its value is exact againstpnorm(log.p = TRUE)as far asx = -1e7. Its analytic partials are not: on 20000 responses they sat 1.7e-3 from central differences of the log probability where theerfcroute sat 4e-6, and in a race those partials *are* the gradient ofnuandsigma. Where the old code's gradient was finite it was not always right. On 20000 responses with a start-point range it sat 8.3e-4 from central differences and the new one sits 9.6e-7, which is the finite differences' own noise; without a start-point range both sit at 4
The NEWS section is longer than shown here.
From NEWS.md in the release
Install
Health
- OK13 OKNow
- OK6 OK2026-09-13
Documentation
The repository also keeps web-only articles.
- Help pages
- 28
- Examples that run
- 89%25 run in a plain check · 3 only when donttest examples are enabled (R CMD check --as-cran)
- Exported functions with examples
- 100%
- Documented parameters
- 66%
- Return-value docs
- 100%
- Help pages citing references
- 61%
- Overview of cogmod HTML
Downloads
Repository
- PRs merged (all time)
- 100%
- Median days to close, 50 most recently updated closed issues
- 252
- Median days to close, 50 most recently updated closed PRs
- 8
Stars over time
Releases over time
Issues over time
PRs over time
Commits over time
Commit identities over time
11 development-tooling and community-health practices detected across 7 families in the upstream repository
Checks run against github.com/dominiquemakowski/cogmod on 2026-10-18.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
The repository owner accepts GitHub Sponsors
GitHub Pages site last deployed 2026-09-26 · The repository has a site deploy workflow
pkgdown site last built 2026-09-26
Show all practicesHide 3 practices
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2026-08-17Named in .Rbuildignore: .claude, CLAUDE.mdNamed in .gitignore: CLAUDE.mdCommits: at least 5 crediting it in the message
- GitHub Copilotfirst seen 2026-09-20Commits: 2 by its own account
- Claude Fable 5.14 commits · Sep 2026
- Claude Opus 5.51 commit · Sep 2026
Dependencies
Code & Tests
Datasets
People & History
Pick two releases to compare their code metrics.
- 0.3.3Latest2026-09-25 · current release · diff ↗ ·
added 12 exports
cogmod_inv_metric, cogmod_step_size, cogmod_warmstart, pcogmod_bisa, pcogmod_exgaussian, pcogmod_gamma, pcogmod_invgamma, pcogmod_invweibull, pcogmod_lognormal, pcogmod_logstudent, pcogmod_logweibull, pcogmod_weibull - 0.3.02026-09-12
- RR 4.6.0 released · 2026-04-24
Package metadata
- First published
- 2026-09-12
- Total releases
- 2 / 1 yrs
- License
- MIT + file LICENSE OSI
- Additional repositories
- mc-stan.org
- Minimum R
- ≥ 3.5.0
- Bundled data
- 7.2 KB / 1 file
- Download size
- 655 KB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("cogmod")The package provides a citation file that builds its entry when R runs it. Run citation() to see it.
The repository also keeps a CITATION.cff.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-20, which the citation names so these numbers can be found later. More on citing and the projects behind them.