Skip to contents

Multinomial Processing Tree (MPT) models measure the probabilities of discrete latent cognitive states from categorical responses. A model is specified as a set of trees created with mpt_tree(), one per distinct branch structure, such as one tree for targets and one for lures in a recognition test. Branch expressions that terminate in the same response category are summed within each tree, and parameters shared across trees are equated by giving them the same name. All latent probability parameters are estimated on an unconstrained latent scale (logit or probit); predictor formulas supplied via bmmformula() apply on that latent scale.

Usage

mpt(
  trees,
  tree_id = NULL,
  covariates = NULL,
  simplex = NULL,
  restrictions = NULL,
  links = "logit",
  ...
)

Arguments

trees

A single mpt_tree object or a list of mpt_tree objects. All trees must share the same set of response categories, counting those declared impossible.

tree_id

Character. Name of the data column whose values name the tree each observation belongs to; the values must match the tree names. Can be omitted for single-tree models. Effects of experimental conditions belong in the parameter formulas, not here; see Details.

covariates

Character vector. Names of data columns that appear in branch expressions but are not latent parameters, for example design-fixed guessing rates. Covariates pass into the model formulas unchanged and have no naming restrictions. bmm() stops when, with a row's covariate values, the branches of a tree do not sum to 1, leave (0, 1], are undefined on the rows of another tree, or when a covariate that a branch uses is missing. Every declared covariate needs a numeric column in the data, also one that no branch uses. The range is checked at the vertices of each branch's parameter box, which is exact when no term of the branch contains the same parameter twice (a * (1 - b) * G + (1 - a) * c). A parameter that does is also checked at 0.5, which is exact for a factor u * (1 - u) and approximate otherwise, e.g. for u * (1 - u)^2.

simplex

A character vector, or a list of character vectors, naming groups of parameters that are jointly constrained to sum to 1. Each group is reparameterized via stick-breaking: the last parameter of each group is derived as 1 minus the sum of the other members, and each other member p gets an unconstrained component praw that predictor formulas for p are applied to. The listing order sets the default prior and the meaning of the coefficients; see Details.

restrictions

Parameter restrictions in the string syntax of MPTinR and TreeBUGS, e.g. c("Dn = Do", "g = 0.5"), or as a named list, list(Dn = "Do", g = 0.5). A restriction either equates a parameter with another one (chains such as "G1 = G2 = G3" map all earlier names onto the last) or fixes it to a numeric constant ("g = 0.5"; bmm also reads fractions such as "g = 1/4", which MPTinR rejects). Constants must lie strictly between 0 and 1. The unnamed list of MPTinR and TreeBUGS, list("Dn = Do", "g = 0.5"), works as well. Restriction files are not read: in MPTinR and TreeBUGS a character vector names a restrictions file, in bmm it holds the restrictions. To use such a file, pass readLines(path); blank lines and lines starting with # are skipped, and a # after a restriction starts a comment, as in MPTinR. A restricted parameter is removed from the model; the Details say how this differs from fixing a parameter in the formula. Order constraints ("Do > Dn") are not supported here; see Details.

The link function of the latent probability parameters: "logit" (default) or "probit". A single string sets every parameter; a named list, links = list(D = "probit"), sets the named parameters and leaves the others at "logit". Members of a simplex group and their praw components take no link of their own. A parameter switched to the other link, in the list or afterwards with model$links$D <- "probit", takes that link's default priors (see Details).

...

used internally for testing, ignore it

Value

An object of class bmmodel

Details

  • Domain: Categorical decision making, memory, and reasoning

  • Task: Any task with categorical responses generated by a discrete processing-tree structure

  • Name: Multinomial Processing Tree (MPT) models

  • Citation:

    • Batchelder, W. H., & Riefer, D. M. (1999). Theoretical and empirical review of multinomial process tree modeling. Psychonomic Bulletin & Review, 6(1), 57-86. https://doi.org/10.3758/BF03210812

    The data hold aggregated counts: one row per participant and tree (and per condition, if conditions are crossed with the trees), with one count column per response category. The count columns must be named exactly like the branch names given to mpt_tree() (old and new in the example below); bmm() finds them by these names, so no response variable is passed. The tree_id column names the tree of each row, and the counts of a row must come from trials of that tree. Wide tables with one column per category of an EQN file, as MPTinR and TreeBUGS take them, are reshaped with mpt_data_reshape().

    A separate tree is needed only when the branch expressions differ, that is, when the trial determines which latent processes apply or which response categories are reachable (targets versus lures, a response set with or without distractors, inclusion versus exclusion instructions). Experimental factors whose effect on the parameters you want to estimate belong in the parameter formulas, exactly as in every other bmm model: bmf(D ~ 0 + condition). The two roles are independent, and one data column can serve both when a factor happens to change the structure as well. When several levels of a factor share a branch structure, add a column naming the tree and keep the factor for the formulas.

    Equating and fixing parameters. Parameters are equated across trees by giving them the same name. Beyond that, there are two mechanisms, and they differ in when the constraint is decided. A restriction (restrictions = c("Dn = Do", "g = 0.5")) is applied when the model is built: it is substituted into the branch expressions before the parameters are identified, so the restricted parameter is not part of the model, gets no formula and no prior, cannot be a simplex member, and is listed by print(model) under Restrictions: but not by parameter_info(). A constant in the formula (bmf(D ~ 1, g = 0.5)) is applied at fit time: the parameter stays part of the model, is listed by print() under Fixed: and by parameter_info() with its value, and is freed again by giving it a formula. The value is mapped to the latent scale when the constant prior is built, so default_prior() shows constant(0) for a guessing rate of 0.5 under the logit link, and summary() lists it as g_Intercept 0.00 under "Constant Parameters"; prior_info() and parameter_info() show the probability, 0.5. Members of a simplex group and their praw components cannot be fixed this way; fix parameters outside the group instead. As a rule of thumb, a constant that defines the model goes into restrictions, a constant you compare against a free version goes into the formula; the section "Constraining parameters" of the MPT article walks through both.

    Default priors. Under the logit link, intercepts and regression coefficients get a logistic(0, 1) prior and random-effect SDs get exponential(1). Under the probit link, intercepts and coefficients get normal(0, 1) and SDs get exponential(2). Group-level correlation matrices get lkj(2). A parameter switched to the other link after construction (model$links$D <- "probit") takes that link's priors unless its default prior was replaced. default_prior() lists the rows a given formula produces.

    The default prior of a simplex group of K members is the uniform (symmetric Dirichlet(1)) distribution on the simplex: each member has prior mean 1/K, whatever the listing order. Under stick breaking this is a Beta(1, K - k) prior on stick k, which default_prior() shows as logitbeta(1, K - k) (or probitbeta(1, K - k)) on the intercept of the k-th praw; the last stick keeps logistic(0, 1) (or normal(0, 1)), which is the same distribution. The result is exact under both links, and under 0 + cond it holds in every cell. Under 1 + cond it holds in the reference cell only, because the effects keep their own prior. TreeBUGS instead puts a uniform prior on each branch parameter, under which the first member has prior mean 0.5 (0.50/0.25/0.25 for three members). To get it for a group gA, gB, gC, gD, pass prior = brms::set_prior("logistic(0, 1)", nlpar = c("gAraw", "gBraw")), with "normal(0, 1)" under the probit link; without coef, it covers every cell of a 0 + cond formula. A predictor on member p acts on the logit (or probit) of p's share of the probability left over by the members listed before it. A predictor on the first member therefore scales all later members by the same factor, leaving their ratios unchanged. Read such effects on the probability scale with native_parameters().

    Identifiability. Printing the model ends with an identifiability check for intercept-only formulas. It compares the number of free parameters with the degrees of freedom (response categories minus 1, summed over trees) and computes the rank of the Jacobian of the category probabilities with respect to the free parameters, from exact derivatives at five interior test values (and five test values of each covariate); parameters fixed in the formula enter at their values. A rank below the number of free parameters means that a combination of the named parameters is not identified by the branch expressions, even when the count passes. The check is local: it holds at the test values, not at the boundaries of the parameter space. A test value with a non-finite derivative is left out, and the printout says how many; if stats::D() cannot differentiate a branch expression, the rank is not computed and the count decides. The rank sees the tree parameters only: after a non-linear formula, the rank line names that formula's parameter as not analysed.

    bmm() warns when the rank is deficient and every formula is intercept-only. When a formula has a predictor or is non-linear, it only says that these may identify the parameters and that it did not check.

    Example: separate detection parameters Do and Dn in a single old/new test give three free parameters and rank 2 of 3, so one combination of Do, Dn and g cannot be estimated; a second condition that varies the response bias identifies them.

    Reading the estimates. summary() reports intercepts and regression coefficients on the latent (logit or probit) scale. native_parameters() returns the posterior draws of every parameter on the probability scale for each observed combination of the predictors, e.g. native_parameters(fit, re_formula = NA, summary = TRUE); a difference between conditions is the difference of these draws. With re_formula = NA these are the probabilities of the median participant (the quantity TreeBUGS reports as "mean"), not the population mean of the participants' probabilities; see the section on group-level effects in native_parameters().

    Person effects written as (1 | id) in each parameter's formula are independent across parameters. A shared label, (1 |p| id) in every formula, estimates their correlations (prior lkj(2)), which corresponds to the latent-trait MPT model.

    For posterior predictive checks, brms::posterior_predict() returns simulated counts (draws x rows x categories) and brms::posterior_epred() the expected counts; ppp() computes the T1 and T2 statistics of Klauer (2010) and their posterior predictive p-values from them. With newdata, brms::posterior_predict() needs the columns nTrials, Idx_<tree> and Poss_<category> that check_data() builds; fit$data contains them.

    Non-linear formulas. Parameters that receive a non-linear predictor formula (a formula whose right-hand side references other formula parameters, e.g. D ~ Dmax * (1 - exp(-rate * ptime))) are not transformed by the link function: the user-supplied expression must keep the parameter within (0, 1). Sub-parameters of such formulas (e.g., Dmax and rate) are estimated on the identity scale with normal(0, 1) default priors on their intercepts and normal(0, 0.5) on their regression coefficients. Their random-effect SDs keep brms's default prior, student_t(3, 0, 2.5), because no single rate fits every scale such a formula can give them.

    Order constraints between parameters (Do > Dn) are expressed by reparameterizing the larger parameter in the model formula, e.g. bmf(Do ~ Dn + (1 - Dn) * inv_logit(phi), Dn ~ 1, phi ~ 1); the section "Order constraints" of the MPT article walks through the recipe.

    Names. Parameter and response category names must start with a letter and may contain only letters and digits, because brms does not allow underscores or dots in non-linear parameter names.

    All latent parameters are non-linear parameters of the underlying brms model, and brms has no separate Intercept prior class for those: their intercept prior is stored as class = "b", coef = "Intercept". A prior given as class = "b", nlpar = "D" replaces the defaults of all coefficients of D, the intercept included; add coef = "Intercept" to set the intercept alone. Call default_prior() to see which rows a given formula produces.

Examples

if (FALSE) { # \dontrun{
# two-high-threshold (2HTM) model of recognition memory
# one tree per probe type: targets (studied items) and lures (new items),
# each ending in the responses "old" and "new"
tree_target <- mpt_tree("target",
  old ~ D + (1 - D) * g,
  new ~ (1 - D) * (1 - g)
)
tree_lure <- mpt_tree("lure",
  old ~ (1 - D) * g,
  new ~ D + (1 - D) * (1 - g)
)

model <- mpt(
  trees = list(tree_target, tree_lure),
  tree_id = "item_type"
)

# simulate data for 20 participants with D = 0.7, g = 0.5:
# one row per participant x tree; count columns named after the branches
data <- data.frame(
  id = rep(1:20, each = 2),
  item_type = rep(c("target", "lure"), 20)
)
counts <- t(sapply(data$item_type, function(cond) {
  rmpt(
    n = 1, size = 50, pars = c(D = 0.7, g = 0.5),
    mpt_model = model, tree = cond, unpack = TRUE
  )
}))
data <- cbind(data, counts)

# predict both parameters by a fixed intercept and correlated random
# intercepts (the shared label |p| estimates the correlation)
formula <- bmf(
  D ~ 1 + (1 |p| id),
  g ~ 1 + (1 |p| id)
)

fit <- bmm(
  formula = formula,
  data = data,
  model = model,
  cores = 4
)

summary(fit)
# population-level D and g on the probability scale
native_parameters(fit, re_formula = NA, summary = TRUE)
} # }