Skip to content

MPT article, ppp(), mpt_data_reshape() and mpt() API changes (MPT 6/6, #255) - #489

Merged
GidonFrischkorn merged 84 commits into
developfrom
255-mpt-article
Oct 14, 2026
Merged

GidonFrischkorn merged 84 commits into
developfrom
255-mpt-article

Conversation

@GidonFrischkorn

@GidonFrischkorn GidonFrischkorn commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #255, and #497. PR 6 of 6 in the MPT stack; base 255-mpt-plot. Head 2e5c4a7.

This description replaces the earlier one, which still said this level adds no code. It does: since 2026-10-09 the level took in ppp() (#497), a reshaping helper for wide count tables, and the API changes of the MPT surface review. Measured against the base: 37 files, +3673/−426.

What merges

New exported functions

  • ppp() (R/ppp.R, +471): the goodness-of-fit statistics T1 (category frequencies) and T2 (their covariances across participants) of Klauer (2010) with posterior predictive p-values, the checks TreeBUGS' PPP() reports. It is not MPT-specific: it covers mpt(), m3(), sdt_yn(), sdt_rating(), sdt_mafc(), sdt_ranking() and sdt_cdp(). level = "participant" gives one T1 per participant. Cells, participants and categories that are impossible in every draw are handled as described in ?ppp.
  • mpt_data_reshape(data, model, id, columns) (R/model_mpt_data.R, +333): reshapes the wide frequency tables of MPTinR and TreeBUGS (one row per participant, one column per category) into one row per participant and tree, with 0 for the categories a tree cannot produce. columns = "mptinr" reads MPTinR's unnamed, sorted columns.
  • print_model_details() is exported.

API changes to functions added lower in the stack

  • mpt_tree() accepts formulas, mpt_tree("old", old ~ D + (1 - D) * g, new ~ (1 - D) * (1 - g)), as well as the named list of strings. The signature becomes mpt_tree(name, branches, ..., impossible = NULL).
  • mpt(links =) takes a named list for links per parameter, links = list(D = "probit"); links = NULL gives the default logit. A link for a parameter that restrictions removed is refused.
  • mpt_from_string() takes restrictions as its second argument, as mpt_from_eqn() already did.
  • pp_check(group = <tree column>) facets an mpt fit by tree.

Shared code touched

R/bmmformula.R gains the internal generic generated_parameters() (the derived last member of a simplex group); R/pp_check.R, R/summary.R, R/distributions.R and R/helpers-model.R have small changes for the points above.

Documentation

  • The article bmm_mpt (+716): specification, data format, fitting and default priors, interpretation, constraints, design-fixed covariates, a real-data example, non-linear parameters, goodness of fit with ppp(), importing, and migrating from MPTinR or TreeBUGS.
  • "Processing-tree models" sections in README.Rmd and bmm.Rmd, the re-knit README.md and the task-map PNG; _pkgdown.yml entries.
  • NEWS: the MPT bullets are consolidated, with new bullets for mpt_data_reshape(), the article and ppp().

Tests

  • test-ppp.R (15 tests): T1 and T2 computed by hand on fixed draws (T2 as in Klauer, 2010), each replicated data set compared with its own draw, cells with a single possible category, impossible categories and zero expected counts, participants without a full set of cells, the participant level, and argument checks on the m3 fixture.
  • test-model_mpt_data.R (17 tests), plus new cases in test-model_mpt.R (formula trees, named links), test-model_mpt_import.R, test-pp_check.R and test-links.R.
  • The two ppp() checks on real mpt fits (a well-specified model is not flagged and a misspecified one is; impossible categories) sample, so they are in tests/local/test-ppp-fits.R (ADR 0004 R1). All MPT fits of the stack are in tests/local now; tests/testthat gains no real-backend site.

Article fits

The article loads three fits from the article-fits release: fits/mpt_2htm_v2 (refit 2026-10-13 after the trees were renamed by probe type), fits/mpt_ol and fits/mpt_ol_pb1 (2026-10-09). bmm(file =) loads a cached fit without checking it by default, so I checked them against the current code at this head:

  • the local files are byte-identical to the release assets (SHA-256);
  • with file_refit = "on_change" the article's code runs to the end without a refit, so Stan code, Stan data and data match for all three; the check does refit when a formula is changed;
  • the model object stored in each fit equals the one a fit with the current code stores, which is what ppp(), print() and native_parameters() read.

The render at this head (cached fits, nothing refitted) has the same text as the render after the tree rename. No fit needs re-uploading. The release asset of the 2HTM fit from before the rename can be deleted once the stack has merged.

Gates at 2e5c4a7

  • Full suite from the source tree with four workers and the Stan tests on: 1907 tests, 0 failed, 0 warnings, 1 skip (restructure), 132 s; no MPT or ppp() test is among the slowest.
  • R CMD check on a clean export: 0 errors, 0 warnings, 0 notes.
  • document(): no drift (also at the five lower heads).
  • tests/local: the five MPT and ppp() fits pass.
  • CI: R-CMD-check passes on macOS, Ubuntu and Windows (Windows check step 12 of its 30 minutes), tests-source passes within the time budget (suite 620 s, cap 800 s), and file-size passes.

Review focus

ppp() as a cross-model function (its T2 definition and the handling of impossible categories), and the API changes above, which change functions the lower PRs add.

Closes #255. Closes #497.

@GidonFrischkorn
GidonFrischkorn added this pull request to stack #491 October 3, 2026 11:32
@GidonFrischkorn GidonFrischkorn added this to the 1.4.0 milestone Oct 4, 2026
…ons (#255)

vignettes/articles/bmm_mpt.Rmd with its references and pkgdown entry; the Processing-tree models sections of README.Rmd and the bmm overview article; README.md and the task map re-knit.
The package site moved from venpopov.github.io to popov-lab.github.io; the
two links to the m3 article still pointed at the old address.
The dependency badge counts graphics, which the plot level imports: 15/25
direct dependencies on develop's base of 25.
…s() (#255)

native_parameters() returns MPT parameters on the probability scale: on
real fits it matched plogis()/pnorm() of posterior_linpred(nlpar = ) for
logit and probit parameters and a hand-built stick-breaking map for simplex
members (ratio to 1 within 6e-16). The article and the MPTinR/TreeBUGS
table use it; posterior_linpred() keeps one sentence for latent-scale work.
…n the model comparison (#255)

fit_ol_pb1 reached a minimum bulk ESS of 243 with the default 1000 draws per
chain. With iter = 4000 and warmup = 1000 the minimum over all parameters,
random effects included, is 2032 for fit_ol and 797 for fit_ol_pb1 (one run,
seeds unset). The flagged rows are now counted inline. Moment matching was
measured and left out: on fit_ol it lowered the count from 120 to 108 of 120
rows and took 2397 s on one core, so the article names the result and keeps
plain loo(); save_pars(all = TRUE) is not needed without it.
…255)

The run that measured moment matching finished fit_ol_pb1 after the previous
commit: 105 -> 56 of 120 flagged rows (2460 s), against 120 -> 108 for fit_ol
(2397 s); elpd_diff -281.1 plain, -288.2 moment-matched (SE 48). The sentence
said moment matching does not help; it helps in part.
… person-effect structures (#255)

?mpt now says the MPT article computes the T1 statistic and its posterior
predictive p-value; the article gains that section, computed live on fit_ol
from posterior_predict() and posterior_epred() (Klauer, 2010, Eq. 17). The
2HTM fit said (1 | id) in two formulas estimates their correlation, which
contradicts ?mpt: it now says the effects are independent, like the
simulation, and that (1 |p| id) correlates them. The OL2019 fits say why
they stay uncorrelated. The covariate paragraph says bmm() stops on a
row-wise branch-sum violation.
…#255)

The article said plot() sees the restricted expressions; it now draws the trees as written and labels the restricted edges, and the sentence says so.
… the MPT article (#255)

The article said a 2HTM with separate detection parameters becomes
identifiable with the order constraint Do > Dn. It does not: replacing Do by a
sub-parameter leaves three free parameters against two degrees of freedom, so
the rank deficit remains and Dn is identified mainly by the prior and the
order. The sentence now offers the two routes that do identify the model and
says what the order constraint gives instead, and the order-constraint section
says the same where the formula is shown.

print() has reported a Jacobian rank check since the identifiability round, so
describing its last line as the classical parameters-versus-categories bound no
longer matched the output. It is now described as two checks, a new paragraph
explains what each answers and when bmm() messages rather than warns, and the
migration table names the rank check.

The moment-matching Pareto-k figures came from a different run than the counts
computed one sentence earlier, so 'from 120' and 'from 105' contradicted the
rendered 119 and 107. They are now attributed to that run, with the run-to-run
drift stated.
N1 beta-MPT bullet, N2 PPP() row, N3 plot of the g = 0.5 model, N4 'exact'
dropped for folded fractions, N6 data row plus a wide-to-long example,
N7 impossible and tree_names, N8 plot(model_ol), N9 unsourced JAGS claim
dropped, N10 log odds ratio, N11 the two steps the Al Hadhrami fits took,
N15/N16 restrictions.filename and FE, N17 logit vs probit sigma, N18 the
EQN first line. N5 withdrawn, N12 is the site-wide Pages domain, N19 fixed
by #509 (probe on this head).
The documentation said ppp() reports "the statistics that TreeBUGS' PPP()
reports" and NEWS "as TreeBUGS' PPP() does". The statistics are the same,
but their values are not: T1 sums the counts, so it is T times TreeBUGS'
value with T participants per cell, and T2 now uses the expected sample
covariance instead of TreeBUGS' sigma. The wording now says so.
mpt(restrictions = "g = 0.5", links = list(g = "probit")) set the link of D
instead, because the link matcher read the removed name 'g' as the parameter
one edit away and only warned about the spelling. A links entry that names a
restricted parameter now stops with a message that says so.
The seven MPT bullets again state that branches can be a named list of
strings, that a restrictions file may hold equalities and constants only,
that printing shows the simplex prior, that the TreeBUGS priors give the
first simplex member prior mean 0.5, that the importers take impossible,
and that the model text can be readLines() output. Every bullet stays under
100 words.
The article still said the link function is chosen once per model, which
mpt(links = list(D = "probit")) now contradicts. The paragraph names both
forms; the code chunk is unchanged.
MPT surface: named links list, restrictions second in both importers, mpt_data_reshape(), consolidated NEWS (#255)
@GidonFrischkorn

Copy link
Copy Markdown
Collaborator Author

Update 2026-10-12: stack merged forward, add-ons folded in (head d2186d7)

This branch now carries the whole MPT line as it will merge into develop:

Measured at this head: every R file parses, document() leaves man/ and NAMESPACE unchanged, the Stan code of the eight reference models is byte-identical to the previous head, no NEWS bullet of any merged branch is lost, and the full test suite (NOT_CRAN=true, 49 files) runs with no failure and no warning; the only skip is the pre-existing restructure test.

One more PR lands on this branch before the develop merge: the outcome of the read of the whole MPT user-facing surface (named links list, restrictions as the second argument of both importers, mpt_long_data() renamed to mpt_data_reshape(), consolidated NEWS, ?mpt reordered). It is based on this head and has had its own gates; it waits for an independent read of its behaviour commits.

Still to come before the window: one re-merge of develop once #536 (kfold fix) and the lifted update() prior fix are in, forwarded through all six levels.

The hint that points to mpt_tree(impossible = ) was written with glue line
continuations but interpolated into the message, so the error showed a
backslash and the source indentation. The test now matches the hint as one
line.
mpt(links = NULL) stopped with "The links argument must be a single link
function" since the named-list form arrived, while every other model
constructor takes NULL for its default links. NULL now gives the logit link
again.
The running two-high-threshold example named its trees "old" and "new",
the same strings as the response categories, so a reader took
mpt_tree("old", old ~ ..., new ~ ...) for one tree per response. The trees
are now "target" and "lure" in the article and in every MPT help page
(mpt_tree, mpt, dmpt/rmpt, ppp, plot, mpt_from_string, mpt_from_eqn,
mpt_data_reshape); the responses stay old/new.

The article introduction now says the model has one tree per probe type,
because different processes are assumed for targets and lures, and shows
the tree diagram there. The source-monitoring tree "new" becomes
"newitem" so it no longer clashes with the category New.

The 2HTM article fit is refitted with the new labels under a new name,
fits/mpt_2htm_v2, which has to be added to the article-fits release.

Full test suite: 51 files, 0 failed. R CMD check (tests skipped): OK.
The macOS and Windows pull-request jobs ship no CmdStan since #546.
# Conflicts:
#	tests/testthat/test-model_mpt.R
Both fits sample, so they live in tests/local/test-ppp-fits.R
(ADR 0004 R1); test-conventions.R drops the test-ppp.R count. The MPT
stack now adds no real-backend site to tests/testthat.
@GidonFrischkorn GidonFrischkorn changed the title MPT article and processing-tree overview sections (MPT 6/6, #255) MPT article, ppp(), mpt_data_reshape() and mpt() API changes (MPT 6/6, #255) Oct 13, 2026
Base automatically changed from 255-mpt-plot to develop October 14, 2026 07:04
@GidonFrischkorn
GidonFrischkorn merged commit ad6143e into develop Oct 14, 2026
5 checks passed
@GidonFrischkorn
GidonFrischkorn deleted the 255-mpt-article branch October 14, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant