The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
Block 6.5 promotes the population-level reference \(\theta_{\text{ref}}\) of the AMM canonical decomposition
\[\theta_i = \theta_{\text{ref}} + a(x_i) + b(x_i) \odot \theta_{\text{ref}} + W(\theta_{\text{ref}})\,x_i\]
from a single global scalar (Block 6 default) to a per-group vector \(\theta_{\text{ref}}[g]\), \(g = 1, \ldots, J_{\text{groups}}\), sampled hierarchically from \(\mathrm{Normal}(\mu_{\theta_{\text{ref}}}, \sigma_{\theta_{\text{ref}}})\) with both hyperparameters estimated. The model under grouping is
\[\theta_i = \theta_{\text{ref}}[g_i] + a(x_i) + b(x_i) \odot \theta_{\text{ref}}[g_i] + W(\theta_{\text{ref}}[g_i])\,x_i, \qquad \theta_{\text{ref}}[g] \sim \mathrm{Normal}(\mu_{\theta_{\text{ref}}}, \sigma_{\theta_{\text{ref}}}),\]
where \(\odot\) denotes the Hadamard
(elementwise) product, coherent with the canonical notation of
vignette("v00_framework_overview", package = "gdpar") §8.2
and vignette("v01_amm_identifiability", package = "gdpar")
§3.3.
The user activates the grouping via the one-sided formula argument
group of gdpar(). Default
group = NULL reduces bit-exactly to the Block 6 scalar
regime, so existing fits and tests do not change.
This vignette documents:
group API and what changes inside the model.a or b.coef() schema under grouping: per-group anchors
plus the population-level hyperparameters.predict() semantics for newdata that contains
levels unseen at fit time.vop01_parametrization_toggle.group argumentgroup is a one-sided formula identifying a single
variable in data:
fit <- gdpar(
formula = y ~ x1 + x2,
family = gdpar_family("gaussian"),
amm = amm_spec(a = ~ x1 + x2),
data = df,
group = ~ school # promotes theta_ref to per-school anchor
)Constraints accepted by the resolver
.resolve_group_argument():
~ school, ~ region. Expressions such
as ~ school + region are rejected; if you need an
interaction, materialize it as a column beforehand:
df$cell <- interaction(df$school, df$region); gdpar(..., group = ~ cell).data, contain no NA values,
and have at least one level. Internally it is coerced to a factor; the
factor levels are stored in fit$group_info$levels and used
later by predict() to map newdata.gdpar_grouping_warning when at least one level has fewer
than 5 observations: the hierarchical anchor is then dominated by
shrinkage and per-group summaries should be interpreted with
caution.vector[J_groups] theta_ref
(univariate) or array[J_groups] vector[p] theta_ref
(multivariate), with mu_theta_ref and
sigma_theta_ref allocated only when
use_groups = 1. The model branch is the standard
hierarchical prior
theta_ref[g] ~ Normal(mu_theta_ref, sigma_theta_ref) with
weakly informative hyperpriors (default normal(0, 2.5) on
mu_theta_ref and student_t(3, 0, 1) on
sigma_theta_ref).b_coef = c_b / theta_ref
derived quantity is not reported when
use_groups = 1, because it would be ambiguous per group
(different denominator per row). The structurally identified
c_b is sampled and reported instead; users can derive
b_coef[j, g] = c_b[j] / theta_ref[g, k] post-hoc per group
if they need to.gdpar_prior(sigma_theta_ref = "student_t(3, 0, 1)") is the
new hyperprior knob; the existing theta_ref prior now
applies to mu_theta_ref under grouping (and stays applied
to the scalar theta_ref when
group = NULL).When group is supplied, the pre-flight check
.check_group_aliasing_c7() runs in addition to the standard
Block 1 identifiability check. It enforces that neither a
nor b contains columns that are linearly dependent with the
group indicator \(G\). The check has
two layers:
group is rank-deficient with \(G\) and creates a perfect alias with \(\theta_{\text{ref}}[g]\). This catches the
most frequent mistake: including factor(group) (or any
deterministic function of group) in a or
b.On violation, gdpar() aborts with
gdpar_input_error naming the offending columns. The user
either removes them from the AMM spec or drops the group
argument.
Defense in depth: the standard Block 1 identifiability check
(gdpar_check_identifiability()) frequently catches the
direct factor(group) case before C7 even runs, because
\(Z_a\) centered is rank-deficient with
the implicit intercept that the model carries via \(\theta_{\text{ref}}\). C7 covers the
residual subtler aliases.
See
vignette("v01_amm_identifiability", package = "gdpar")
Section 6.6.2 for the formal statement and proofs.
coef(fit) returns an object of class
gdpar_coef with the following slots when
use_groups = 1:
theta_ref: data.frame with columns
(g, k, mean, q05, q50, q95). One row per
(group, coordinate). In the univariate case
(p = 1), k is constant at 1. The
factor levels are stored in fit$group_info$levels for the
mapping g <-> name.mu_theta_ref: data.frame with columns
(k, mean, q05, q50, q95). One row per coordinate. The
population-level mean of the per-group anchors.sigma_theta_ref: data.frame with columns
(k, mean, q05, q50, q95). Same shape. The population-level
scale.a, b, W: unchanged from Block
6.Under use_groups = 0 (default), the
theta_ref data.frame keeps the Block 6 columns
(k, mean, q05, q50, q95) without g, preserving
backward compatibility bit-exactly.
as.data.frame(coef(fit)) returns a long-tidy table that
always includes the g column (NA when grouping is inactive)
plus rows tagged component = "mu_theta_ref" and
component = "sigma_theta_ref" when present.
Example (untested):
cf <- coef(fit)
cf$theta_ref # one row per (g, k)
cf$mu_theta_ref # population-level mu
cf$sigma_theta_ref # population-level sigma
as.data.frame(cf) # long-tidy: component, g, k, identifier, x_name, mean, q05, q50, q95The print method shows the population-level hyperparameter blocks first, followed by the per-group anchors and the per-component summaries.
predict(fit, newdata = new_df, ...) under a grouped fit
resolves new_df[[var_name]] against
fit$group_info$levels to obtain the per-row group
index:
theta_ref[, g] (or theta_ref[, g, k] in
multivariate) is read draw by draw and used as \(\theta_{\text{ref}}\).gdpar_predict_unseen_group_warning naming the offending
levels and, for each affected row, draws \(\theta_{\text{ref}}\) from the
marginal prior predictive \(\mathrm{Normal}(\mu_{\theta_{\text{ref}}}[s],
\sigma_{\theta_{\text{ref}}}[s])\) per posterior draw \(s\) (and per coordinate \(k\) in multivariate). The prediction is
therefore well-defined but its uncertainty includes the population-level
variance, not just the within-group variance.The newdata data.frame must contain the same grouping variable. If
absent, predict() aborts with
gdpar_input_error.
The b-block of the predictor uses c_b directly under
grouping (consistent with the Stan model that does not report
b_coef per group). Mathematically the formula is the same
as in Block 6 with Z_b %*% c_b replacing
Z_b %*% b_coef * theta_ref, since
c_b = theta_ref * b_coef by construction.
Example (untested):
new_df <- df[1:10, ]
new_df$school <- factor(c(rep("school_A", 8), rep("new_school_X", 2)),
levels = c(levels(df$school), "new_school_X"))
# predict() emits a single warning naming "new_school_X" and uses the
# prior predictive marginal for those two rows:
pred <- predict(fit, newdata = new_df, summary = "draws")The parametrization toggle (parametrization,
parametrization_a, parametrization_W)
discussed in
vignette("vop01_parametrization_toggle", package = "gdpar")
is orthogonal to grouping: it controls the CP/NCP
geometry of the hierarchical scales sigma_a and
sigma_W on the basis coefficients, not the hierarchical
scale sigma_theta_ref on the per-group anchors. The
per-group anchor is always sampled from a standard random-intercept
hierarchy without an additional CP/NCP toggle.
In practice, when a fit shows divergences under grouping, the diagnostic order is:
fit$parametrization$meta for the per-component
CP/NCP decision; switch to the alternative parametrization with the
standard toggle if sigma_a or sigma_W is the
culprit.theta_ref[g], consider tightening
gdpar_prior(sigma_theta_ref = ...), since with very few
observations per group the random-intercept funnel can be severe
regardless of the per-component parametrization.group argument is part of the call recorded in
fit$call. Re-running the call with the same seed and the
same data (including the same factor levels for the
grouping variable) reproduces the fit bit-exactly.fit$group_info stores the resolved metadata:
var_name, levels, and group_id
(the integer vector of length n). The mapping
g <-> name is therefore portable across sessions
through the saved gdpar_fit object.coef(fit)$group_levels returns the same character
vector, useful for plotting per-group anchors with the original
labels.vignette("v01_amm_identifiability", package = "gdpar")
Section 6.6.2 for the formal statement of (C7).vignette("v03_special_cases", package = "gdpar")
Section 4.1 for the random-intercept canonization.vignette("vop01_parametrization_toggle", package = "gdpar")
for the CP/NCP toggle on the hierarchical scales of a and
W.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.