Skip to content

Bug: estimate_contrasts() fails for multivariate brmsfit models due to incorrect response-level extraction #682

Closed

Description

@AleAnsani

Bug: estimate_contrasts() fails for multivariate brmsfit models due to incorrect response-level extraction

Description

Hey guys! I encountered an error when using estimate_contrasts() with a multivariate brmsfit model. The error appears to originate from .split_contrast_response_levels() in modelbased, where the response returned by insight::get_response() seems to be incorrectly processed for multivariate models. This wasn't a problem in previous versions of modelbased; when I ran the same script around June, everything ran smoothly. I seem to have encountered this in a new version of modelbased.

Unfortunately, I cannot share the fitted model because it contains project-specific data. However, I can provide the exact function call, error message, traceback, and a workaround that resolved the issue in my case.

Environment

  • R: 4.6.1
  • modelbased: 0.17.0
  • marginaleffects: 0.32.0
  • brms: 2.23.0

Function call

The error occurred when requesting contrasts for the Music predictor, grouped by Block, for the HandRMS response of a multivariate model. I also tried with keep.iterations = F, but nothing changed.

posterior_draws_interaction_hand <- estimate_contrasts(
  FaS_ind_Bayes_RI_dyadRS,
  contrast = "Music",
  by = "Block",
  keep_iterations = T,
  re_formula = NA,
  resp = "HandRMS",
  newdata = newdata
)

The model has four response variables: Complexity, Fluidity, Head.RMS, and Hand.RMS. The supplied newdata is a 16-row tibble containing four categorical predictors: Partner, Music, Half, and Block.

Error message

The call returned the following error (shortened for readability):

Error in grepl(pattern, x_chr) :
  invalid regular expression '^c\(0\.635987531259711, 1\.22294160936826, ...'

Warning message:
In grepl(pattern, x_chr) :
  TRE pattern compilation error 'Out of memory'

The traceback pointed to the following internal call sequence:

.split_response_prefix(params$Level1, search_levels)
.split_contrast_response_levels(params, model, x)
format.marginaleffects_contrasts(...)
estimate_contrasts.default(...)

Suspected cause

The issue appears to originate from the following code in .split_contrast_response_levels():

response <- .safe(
  insight::get_response(model, verbose = FALSE)
)

if (is.factor(response)) {
  response_levels <- levels(response)
} else {
  response_levels <- unique(as.character(response))
}

For my multivariate model, insight::get_response() returns a numeric matrix containing the observed values for all four response variables. Because the returned object is neither a factor nor a vector, as.character(response) converts the entire matrix into a character vector of observed numerical values rather than extracting the response names.

These values are then used to construct regular-expression patterns in .split_response_prefix(), eventually causing grepl() to attempt to compile an extremely long pattern.

I verified that insight::find_response() correctly returns the response names:

insight::find_response(FaS_ind_Bayes_RI_dyadRS)

# Complexity   Fluidity   HeadRMS   HandRMS
# "Complexity" "Fluidity" "Head.RMS" "Hand.RMS"

Thus, the problem seems to be the initial construction of response_levels, rather than the response names themselves.

Workaround that resolved the issue

I (or better, the AI) temporarily patched .split_contrast_response_levels() in the current R session so that matrix and data-frame responses are handled using their column names:

if (is.matrix(response) || is.data.frame(response)) {
  response_levels <- colnames(response)
} else if (is.factor(response)) {
  response_levels <- levels(response)
} else {
  response_levels <- unique(as.character(response))
}

I applied this change to a local copy of the internal function and replaced the function in the modelbased namespace using assignInNamespace(). After applying the patch, the original estimate_contrasts() call completed successfully.

This suggests that extracting the response names from the matrix instead of coercing the observed values to character strings resolves the issue in my case.

Suggested fix

Would it be possible to handle matrix and data-frame responses explicitly when constructing response_levels, as illustrated above?

This would prevent observed numerical values from being interpreted as response-level prefixes and passed to grepl() as regular-expression patterns.

I could not provide a fully reproducible example because the fitted model cannot be shared (or not yet), so the diagnosis is based on the traceback, inspection of the internal functions, and the successful workaround.

I hope this helps. Thank you so much for maintaining modelbased 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions