Component-wise Z-residual Diagnosis for Bayesian Hurdle Models
2025-07-24
vignette_hurdle.Rmd
Introduction
This vignette demonstrates how to use the Zresidual
package to compute component-wise Z-residuals for diagnosing Bayesian
hurdle models (Mullahy 1986), based on the output
from the brms
package in R (Bürkner 2017) can be calculated
separately for the zero, count and hurdle components to reveal potential
model misspecifications. The examples illustrate the practical use of
these residuals for
RPP (Feng, Li, and Sadeghpour
2020) diagnostics. For a detailed explanation of the
methodology and theoretical foundations, please refer to the “To
be updated”.
Definitions of Component-wise Z-residuals for Bayesian Hurdle Models
This section demonstrates the definiotns of component-wise posterior predictive quantities including the posterior predictive PMF, survival function, and RPP for Bayesian hurdle models. Hurdle models consist of two parts:- A logistic component modeling the probability of structural zeros.
- A count component modeling positive counts using a zero-truncated distribution.
Let , where indicates a non-zero value, and indicates a zero value for the $i^\th$ observations. If , the corresponding count model then operates on , i.e., the positive counts only.
Using Bayesian estimation (e.g., via thebrms
package), we
draw
samples from the posterior distribution. Let
denote the $t^\th$ posterior draw,
including component-specific parameters like:
- : the zero porbability,
- : parameters for the count component.
For a given observation , the component-wise posterior predictive PMF and survival functions are defined below.
Count Compoenent:
where and denote the PMF and survival function of the untruncated count distribution, given the component-specific posterior parameters .
For any observed value , we define: where . Here, is the observed value, which can refer to either the binary response or the positive count , depending on the component being evaluated. Then, the Z-residual of a discrete response variable is, where is the quantile function of standard normal distribution.
A Simulation Example
Model fitting with brms
To demonstrate how Zresidual
works with hurdle models,
we first simulate data from a
HNB model. This simulated
dataset allows us to evaluate how well the model and residual
diagnostics perform when the true data-generating process is known.
# Simulation parameters
n <- 100
beta0 <- -1 # Intercept for hurdle (zero) part
beta1 <- -1 # Coefficient for hurdle part
alpha0 <- 2 # Intercept for count part
alpha1 <- 6 # Coefficient for count part
size <- 6 # Dispersion parameter for negative binomial
x <- rnorm(n) # Predictor
# Hurdle (zero) part
logit_p <- beta0 + beta1 * x
p_zero <- exp(logit_p) / (1 + exp(logit_p))
zeros <- rbinom(n, 1, p_zero)
# Count (non-zero) part
log_mu <- alpha0 + alpha1 * x
mu <- exp(log_mu)
# Generate from zero-truncated negative binomial
prob <- size / (size + mu)
y <- (1-zeros)*distributions3::rztnbinom(n, size, prob)
# A random error variable
z <- rnorm(n, mean = 0, sd = 1)
# Final dataset
sim_data <- data.frame(y = y, x = x, z = z)
This dataset includes a single continuous predictor x
, a
rando error variable z
and the outcome y
is
generated from a hurdle negative binomial process. Note that the error
variable is not included in generating the y
outcome
variable. The hurdle (zero) part is modeled with a logistic function and
the count part uses a zero-truncated negative binomial distribution.
Now, we use the brms
package to fit a hurdle negative
binomial model to the simulated data.
library(brms)
fit_hnb <- brm(bf(y ~ x + z, hu ~ x + z),
data = sim_data,
family = hurdle_negbinomial())
The hu
formula models the hurdle (zero) part, while the
main formula models the count component. The
family = hurdle_negbinomial()
tells brms
to
use a hurdle model with a negative binomial distribution for the
non-zero counts. We use default parameter setting on this example to fit
the model.
Computing Z-residuals
In this example, we compute Z-residuals for the
HNB model separately for,
logistic component (zero part) and count component using
zresidual_hurdle_negbinomial()
. Apart from component-wise
Z-residuals, the Zresidual
package support overall model
Z-residual calculation. The package take brms
fit as an
input and the type
argument
("zero", "count" or "hurdle"
) specifies which component to
use when calculating the residuals. By default, Z-residuals are computed
using the Importance Sampling Cross-Validation (iscv
)
method based on randomized predictive p-values
(RPP). Alternatively,
users can choose the standard Posterior
RPP method by setting
method = "post"
.
library(hurdlemodels)
zres_hnb_post_zero <- zresidual_hurdle_negbinomial(fit_hnb, type = "zero", method = "post")
zres_hnb_post_count <- zresidual_hurdle_negbinomial(fit_hnb, type = "count", method = "post")
zres_hnb_post_hurdle <- zresidual_hurdle_negbinomial(fit_hnb, type = "hurdle", method = "post")
What the function returns
The function zresidual_hurdle_negbinomial()
(and other
Z-residual computing functions) returns a matrix of Z-residuals, with
additional attributes. The returned object is of class
zresid
, which includes metadata useful for diagnostic and
plotting purposes.
Return Value
A numeric matrix of dimension n × nrep
, where
n
is the number of observations in the data and
nrep
is the number of randomized replicates of Z-residuals
(default is 1). Each column represents a set of Z-residuals computed
from a RPP, using
either posterior (post
) or importance sampling
cross-validation (iscv
) log predictive distributions.
Matrix Attributes
The returned matrix includes the following attached attributes:-
type
: The component of the hurdle model the residuals correspond to. One of “zero”, “count”, or “hurdle”. -
zero_id
: Indices of observations where the response value was 0. Useful for separating diagnostics by zero and non-zero parts. -
log_pmf
: A matrix of log predictive probabilities (log-PMF) per observation and posterior draw. -
log_cdf
: A matrix of log predictive CDF values used in computing the RPPs. -
covariates
: A data frame containing the covariates used in the model (excluding the response variable). This can be used for plotting or conditional diagnostics. -
fitted.value
: The posterior mean predicted value for each observation depending on the type.
Diagnostic Tools for Z-Residuals
Visualizing Z-residuals
TheZresidual
package includes built-in plotting functions
(QQ Plot, Scatter Plot, Boxplot) to help diagnose model fit using
Z-residuals. These functions are designed to work directly with objects
of class zresid
returned by functions like
zresidual_hurdle_negbinomial()
. These plots help assess:
- Whether residuals are approximately standard normal (via QQ plots),
- Whether there are patterns in residuals across fitted values (which may suggest model misspecification),
- Whether residuals differ across covariates (optional extensions).
par(mfrow = c(1,3))
qqnorm.zresid(zres_hnb_post_zero)
qqnorm.zresid(zres_hnb_post_count)
qqnorm.zresid(zres_hnb_post_hurdle)
par(mfrow = c(1,3))
plot.zresid(zres_hnb_post_zero, X="fitted.value", outlier.return = TRUE)
plot.zresid(zres_hnb_post_count, X="fitted.value", outlier.return = TRUE, log = "x")
plot.zresid(zres_hnb_post_hurdle, X="fitted.value", outlier.return = TRUE, log = "x")
par(mfrow = c(1,3))
boxplot.zresid(zres_hnb_post_zero, X="fitted.value")
boxplot.zresid(zres_hnb_post_count, X="fitted.value")
boxplot.zresid(zres_hnb_post_hurdle, X="fitted.value")
The diagnostic evaluations for the true model—comprising scatter plots, Q-Q plots, and boxplots of Z-residuals—demonstrate that the model adequately captures the data structure. Across the logistic, count, and hurdle components, Z-residuals are evenly scattered around zero and mostly fall within the range of -3 to 3, indicating no visible model misfit. Complementary statistical tests, including the SW test for normality, ANOVA for mean equality, and BL test for variance homogeneity, all return p-values above the 0.05 threshold. This suggests that the residuals follow a normal distribution and exhibit equal means and variances across fitted value intervals. The Q-Q plots further support normality through close alignment with the 45-degree reference line, while the boxplots confirm consistent residual means across partitions. Collectively, these diagnostics validate that the true model satisfies key distributional assumptions and that the proposed Z-residual methods are effective in detecting model adequacy.
The plotting functions in the Zresidual package are designed to be
flexible and lightweight, allowing users to quickly visualize residual
patterns across different components of hurdle models. These functions
support all customizable arguments in base R
functions such
as axes, labels etc. by making them adaptable to a wide range of
diagnostic workflows. The plot.zresid()
function offers
flexible diagnostic plotting for Z-residuals, supporting various x-axes
such as index, fitted values, and covariates. Both
plot.zresid()
and qqnorm.zresid()
automatically highlights outlier residuals that fall outside the typical
(or user specified)range making it easier to identify problematic
observations.
Statistical Tests
In addition to visual diagnostics, the package offers formal
statistical tests to quantify deviations from normality or homogeneity
of variance in Z-residuals by taking an zresid
class object
as an input.
Shapiro-Wilk Test for Normality (SW)
sw.test.zresid(zres_hnb_post_zero)
## [1] 0.2378934
sw.test.zresid(zres_hnb_post_count)
## [1] 0.1950657
sw.test.zresid(zres_hnb_post_hurdle)
## [1] 0.3845721
Analysis of Variance (ANOVA)
aov.test.zresid(zres_hnb_post_zero)
## [1] 0.7293346
aov.test.zresid(zres_hnb_post_count)
## [1] 0.4973731
aov.test.zresid(zres_hnb_post_hurdle)
## [1] 0.3801038
Bartlett Test (BL)
bartlett.test.zresid(zres_hnb_post_zero)
## [1] 0.5471266
bartlett.test.zresid(zres_hnb_post_count)
## 'x' must be a list with at least 2 elements. Fitted values converted to log.
## [1] 0.294551
bartlett.test.zresid(zres_hnb_post_hurdle)
## [1] 0.376077
These tests return standard htest
or aov
objects, making them easy to report, summarize, or integrate into
automated workflows. One advantage of the visualization functions
provided by the Zresidual
package is that they allow users
to diagnose the model both visually and using statistical tests
simultaneously.
We might be also interested in comparing our HNB model to HP model taking the HP model as misspecified model .
fit_hp <- brm(bf(y ~ x + z, hu ~ x + z),
data = sim_data,
family = hurdle_negbinomial(),
prior = prior("normal(1000, 1)", class = "shape"))
This prior("normal(1000, 1)", class = "shape")
is a very
strong prior, tightly centered around 1000. Its practical effect is to
force the shape parameter to be very large, which in turn makes the
model behave almost like a Poisson distribution for the positive counts
(truncated part of the hurdle model).
zres_hp_post_zero <- zresidual_hurdle_poisson(fit_hp, type = "zero", method = "post")
zres_hp_post_count <- zresidual_hurdle_poisson(fit_hp, type = "count", method = "post")
zres_hp_post_hurdle <- zresidual_hurdle_poisson(fit_hp, type = "hurdle", method = "post")
par(mfrow = c(1,3))
qqnorm.zresid(zres_hp_post_zero)
qqnorm.zresid(zres_hp_post_count)
qqnorm.zresid(zres_hp_post_hurdle)
par(mfrow = c(1,3))
plot.zresid(zres_hp_post_zero, X="fitted.value", outlier.return = TRUE)
plot.zresid(zres_hp_post_count, X="fitted.value", outlier.return = TRUE, log = "x")
plot.zresid(zres_hp_post_hurdle, X="fitted.value", outlier.return = TRUE, log = "x")
par(mfrow = c(1,3))
boxplot.zresid(zres_hp_post_zero, X="fitted.value")
boxplot.zresid(zres_hp_post_count, X="fitted.value")
boxplot.zresid(zres_hp_post_hurdle, X="fitted.value")
While the logistic component shows randomly scattered residuals and normal Q-Q alignment, supported by non-significant p-values, the count and hurdle components display clear signs of misspecification. These include banded residual patterns, heavy tails, Q-Q deviations, and significant p-values from the SW, ANOVA, and BL tests. The overall hurdle model diagnostics reflect similar issues but cannot isolate the source of misfit. This highlights a key advantage of component-wise residual analysis: it reveals that the logistic sub-model is correctly specified, while the count component is not. Such separation enables more precise identification and correction of modeling issues.
A Real-World Example
Dataset
This example illustrates the use of hurdle models to analyze healthcare utilization among older adults. The dataset is drawn from the National Medical Expenditure Survey (NMES), a nationally representative U.S. survey conducted in 1987-1988. A subsample of 4,406 individuals aged 66 and older, all enrolled in Medicare, is used. The primary response variable is the number of emergency department (ED) visits, with demographic, socioeconomic, and health-related covariates included in the analysis.
nmes.data$hlth <- NA # Initialize with NA
nmes.data$hlth[nmes.data$exclhlth == 1] <- 2 # Set hlth = 2 for Excellent
nmes.data$hlth[nmes.data$poorhlth == 1] <- 0 # Set hlth = 0 for Poor
nmes.data$hlth[nmes.data$exclhlth == 0 & nmes.data$poorhlth == 0] <- 1 # Set hlth = 2 for Average
Fit models
The data are modeled using hurdle Poisson
(HP) and hurdle negative binomial
(HNB) models via the
brms
package in R. These models allow to examine whether
the logistic and count components offer additional, more localized
insights compared to those obtained from the combined hurdle model.
nmes.data1 <- nmes.data %>% select(c(emr, numchron, hlth, adldiff, school))
nmes.data2 <- nmes.data %>% select(c(emr, numchron, hlth, adldiff, school, black))
predictors_hnb <- c("numchron", "hlth", "adldiff", "school")
predictors_hp <- c("numchron", "hlth", "adldiff", "school", "black")
count_formula_hnb <- formula(paste("emr ~ ", paste(predictors_hnb, collapse=" + ")))
zero_formula_hnb <- formula(paste("hu ~ ", paste(predictors_hnb, collapse=" + ")))
count_formula_hp <- formula(paste("emr ~ ", paste(predictors_hp, collapse=" + ")))
zero_formula_hp <- formula(paste("hu ~ ", paste(predictors_hp, collapse=" + ")))
fit_hnb <- brm(bf(count_formula_hnb, zero_formula_hnb),
data = nmes.data1,
family = hurdle_negbinomial(link = "log", link_shape = "identity", link_hu = "logit"),
prior = c(prior(normal(0, 5), class = "b"),
prior(normal(0, 5), class = "Intercept"),
prior(gamma(0.01, 0.01), class = "shape"),
prior(normal(0, 2), class = "b", dpar = "hu")),
refresh = 0)
fit_hp <- brm(bf(count_formula_hp, zero_formula_hp),
data = nmes.data2,
family = hurdle_negbinomial(link = "log",
link_shape = "identity",
link_hu = "logit"),
prior = prior("normal(1000, 1)", class = "shape"), refresh = 0)
Z-residual calculation
We computed Z-residuals for logistic, count and hurdle components separately using the Posterior RPP method for the NMES dataset.
zres_hnb_zero <- zresidual_hurdle_negbinomial(fit_hnb, type = "zero", method = "post", nrep = 50)
zres_hnb_count <- zresidual_hurdle_negbinomial(fit_hnb, type = "count", method = "post", nrep = 50)
zres_hnb_hurdle <- zresidual_hurdle_negbinomial(fit_hnb, type = "hurdle", method = "post", nrep = 50)
zres_hp_zero <- zresidual_hurdle_poisson(fit_hp, type = "zero", method = "post", nrep = 50)
zres_hp_count <- zresidual_hurdle_poisson(fit_hp, type = "count", method = "post", nrep = 50)
zres_hp_hurdle <- zresidual_hurdle_poisson(fit_hp, type = "hurdle", method = "post", nrep = 50)
Graphically assess model
Figure 1 displays diagnostic plots of Z-residuals for the logistic,
count, and overall components of the
HNB model. The residuals
generally fall within the expected range (-3 to 3) without showing
strong patterns, indicating a good model fit. Some outliers are present,
particularly in the logistic component, as highlighted in the Q-Q plots.
These outliers also appear in the overall model but are mostly driven by
the logistic part. None of the residual-based statistical tests
(SW,
ANOVA, r
BL`)
yield significant p-values for any of the model components, providing no
strong evidence of model misspecification. Collectively, these
diagnostic results suggest that the hurdle negative binomial model
offers an adequate fit to the data.
for(i in 1:50){
par(mfrow = c(2,3), mar = c(2, 2, 2, 2))
qqnorm.zresid(zres_hnb_zero, irep = i, main.title = "HNB, Zero")
qqnorm.zresid(zres_hnb_count, irep = i, main.title = "HNB, Count")
qqnorm.zresid(zres_hnb_hurdle, irep = i, main.title = "HNB, Hurdle")
plot.zresid(zres_hnb_zero, irep = i, X="fitted.value", outlier.return = T, main.title = NULL)
plot.zresid(zres_hnb_count, irep = i, X="fitted.value", outlier.return = T, main.title = NULL)
plot.zresid(zres_hnb_hurdle, irep = i, X="fitted.value", outlier.return = T, main.title = NULL)
}

Figure 1: QQ Plots and Scatter plots of HNB for logistic, count and hurdle components.
Figure 2 shows Z-residual diagnostics for the logistic, count, and hurdle components of the HP model. The hurdle component exhibits greater variability and a funnel shape in the residuals versus fitted plot, along with large outliers and deviations from normality in the Q-Q plot, suggesting potential misspecification. In contrast, the logistic component displays more stable residuals and better alignment with theoretical quantiles. These observations are supported by the SW test, which yields a significant p-value for the hurdle component and a non-significant result for the logistic component.
for(i in 1:50){
par(mfrow = c(2,3), mar = c(2, 2, 2, 2))
qqnorm.zresid(zres_hp_zero, irep = i, main.title = "HP, Zero")
qqnorm.zresid(zres_hp_count, irep = i, main.title = "HP, Count")
qqnorm.zresid(zres_hp_hurdle, irep = i, main.title = "HP, Hurdle")
plot.zresid(zres_hp_zero, irep = i, X="fitted.value", outlier.return = T, main.title = NULL)
plot.zresid(zres_hp_count, irep = i, X="fitted.value", outlier.return = T, main.title = NULL)
plot.zresid(zres_hp_hurdle, irep = i, X="fitted.value", outlier.return = T, main.title = NULL)
}

QQ Plots and Scatter plots of HP for logistic, count and hurdle components.
Other Functions
In addition to Z-residual computation and visualization, the
Zresidual
package provides several utility functions to
support deeper model diagnostics and probabilistic analysis including
functions for calculating the logarithmic predictive p-values
(post_logrpp()
, iscv_logrpp()
). The package
also includes dedicated functions to compute the logarithmic PDFs and
CDFs for supported distributions. These can be used to manually inspect
likelihood components or to derive custom model evaluation metrics. The
log-scale calculations offer improved numerical stability, especially
when dealing with small probabilities. These tools integrate seamlessly
with outputs from Bayesian models fitted using brms
,
maintaining compatibility and flexibility. Together, they extend the
package’s functionality beyond residual analysis, supporting a
comprehensive and rigorous approach to Bayesian model checking.