Skip to content

Add multinomial distribution functions - #145

Open
nehill197 wants to merge 9 commits into
mainfrom
add-multinom
Open

Add multinomial distribution functions#145
nehill197 wants to merge 9 commits into
mainfrom
add-multinom

Conversation

@nehill197

@nehill197 nehill197 commented Aug 10, 2026

Copy link
Copy Markdown
Member

Summary

  • Add dev_multinom(), log_lik_multinom(), ran_multinom(), and res_multinom() for long-format multinomial data (one row per category per trial), using a group argument to identify trial membership.
  • Validate group consistently across all four functions: no NA, at least 2 rows per trial, matching size and prob summing to 1 within a trial, and a consistent row count across groups (catches the common mistake of a new_expr being evaluated row-by-row instead of vectorized, and rows lost from a trial).

Test plan

  • devtools::test() — 3252/3252 passing
  • devtools::check() — 0 errors, 0 warnings, 0 notes

Adds ran_multinom(), log_lik_multinom(), dev_multinom(), and
res_multinom(). Data are expected in long format (one row per category
per trial, identified by a `group` argument) so that residuals work
one-per-observation with embr's posterior_predictive_check(), and so
that a signed residual is always well defined per cell without needing
an "extended sign" for a multi-category row.

dev_multinom()/log_lik_multinom() rely on the identity that a
multinomial is equivalent to independent Poissons conditional on the
trial total, letting each category's contribution be computed from
dev_pois()/log_lik_pois() alone. dev_multinom() is verified against
glmnet's multinomial deviance() and log_lik_multinom() against
dmultinom().
ave()'s non-standard FUN lookup fails when log_lik_multinom() is
evaluated inside a Bayesian model-fitting framework's derived-quantity
expressions; replaced with an equivalent table()-based computation.

Also reject `group` values mapping to a single row in
chk_multinom_group() (shared by ran_multinom()/log_lik_multinom(), and
so reached by res_multinom(simulate = TRUE)): a singleton group can't
represent a real multinomial trial, and silently produces a degenerate
result (rmultinom() just returns `size`) rather than erroring. This is
the shape you get if the function ends up evaluated once per row
instead of once for the whole data vector.
Note in the shared group param docs that each group must contain at
least 2 rows, matching the check already enforced in
chk_multinom_group(). Also drop an unneeded implementation aside from
res_multinom()'s docs, and add a simulate-many-and-check-mean/sd test
for res_multinom(), matching the pattern used for every other
distribution's res_*() function.
Reject groups whose row count differs from the mode across the call
(a trial silently missing a row for ordinary multinomial logistic
regression), and move the NA-group check into each calling function
so it runs before group is used to split the data.
…d res_multinom(simulate = FALSE)

Trim verbose internal comments and roxygen prose added for the multinomial
functions, and move NA-propagation notes to the size/prob docs where they
actually apply. dev_multinom() now takes group and validates size/prob
against it, and res_multinom() validates group even when simulate = FALSE,
so a prob vector that doesn't sum to 1 can no longer silently produce a
wrong result on either path.
The most common cause is size/prob/group being passed one row at a time
rather than as full vectors, so point at that instead of "group lost rows".
…ctions

dev_multinom()/log_lik_multinom()/res_multinom() now validate size/prob/
group lengths via chk_compatible_lengths() before recycling with rep_len(),
matching ran_multinom() and skewnorm()/skewlnorm() -- previously a
non-recyclable length mismatch (e.g. prob shorter than x) was silently
mis-recycled by rep_len() instead of erroring, producing a wrong or
misleading result.

chk_multinom_group()'s prob-sum check no longer skips validation entirely
whenever any prob in a group is NA; it now checks the known values don't
already exceed 1, mirroring the size check's existing NA-tolerant logic.

Also share one group-index split (multinom_split()) across
chk_multinom_group()/multinom_row_na()/ran_multinom() instead of
recomputing it up to three times per call, and add direct tests for the
two internal helpers.
Use expect_no_error() instead of expect_null() where the intent is "this
call succeeds," not "this call returns NULL." Add a regression test for
chk_multinom_group()'s documented tie-break rule (favours the smaller row
count on an exact tie).
@nehill197
nehill197 marked this pull request as ready for review August 10, 2026 18:04
@nehill197
nehill197 requested a review from joethorley August 10, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant