Zero-Inflated Model
Log likelihood function to estimate parameters for a Zero-inflated Poisson model. With examples and comparison to pscl package output. Also includes approach based on Hilbe GLM text.
Zero-inflated models are applied to situations in which target data has relatively many of one value, usually zero, to go along with the other observed values. They are two-part models, a logistic model for whether an observation is zero or not, and a count model for the other part. The key distinction from hurdle count models is that the count distribution contributes to the excess zeros. While the typical application is count data, the approach can be applied to any distribution in theory.
Poisson
Data Setup
Get the data.
library(tidyverse)
= haven::read_dta("http://www.stata-press.com/data/r11/fish.dta") fish
Function
The log likelihood function.
<- function(y, X, par) {
zip_ll # arguments are response y, predictor matrix X, and parameter named starting points of 'logit' and 'pois'
# Extract parameters
= par[grep('logit', names(par))]
logitpars = par[grep('pois', names(par))]
poispars
# Logit part; in this function Xlogit = Xpois but one could split X argument into Xlogi and Xpois for example
= X
Xlogit = Xlogit %*% logitpars
LPlogit = plogis(LPlogit) # alternative 1/(1+exp(-LPlogit))
logi0
# Poisson part
= X
Xpois = exp(Xpois %*% poispars)
mupois
# LLs
= log( logi0 + exp(log(1 - logi0) - mupois) )
logliklogit = log(1 - logi0) + dpois(y, lambda = mupois, log = TRUE)
loglikpois
# Hilbe formulation
# logliklogit = log(logi0 + (1 - logi0)*exp(- mupois) )
# loglikpois = log(1-logi0) -mupois + log(mupois)*y #not necessary: - log(gamma(y+1))
= y == 0 # 0 values
y0 = y > 0 # Count part
yc
= sum(logliklogit[y0]) + sum(loglikpois[yc])
loglik -loglik
}
Estimation
Get starting values or simply do zeros.
# for zip: need 'logit', 'pois'
= glm(
initial_model ~ persons + livebait,
count data = fish,
x = TRUE,
y = TRUE,
"poisson"
)
# starts = c(logit = coef(initial_model), pois = coef(initial_model))
= c(rep(0, 3), rep(0, 3))
starts
names(starts) = c(paste0('pois.', names(coef(initial_model))),
paste0('logit.', names(coef(initial_model))))
Estimate with optim.
= optim(
fit par = starts ,
fn = zip_ll,
X = initial_model$x,
y = initial_model$y,
method = "BFGS",
control = list(maxit = 5000, reltol = 1e-12),
hessian = TRUE
)
# fit
Comparison
Extract for clean display.
= fit$par
B = sqrt(diag(solve((fit$hessian))))
se = B/se
Z = pnorm(abs(Z), lower = FALSE)*2 p
Results from pscl.
library(pscl)
= zeroinfl(count ~ persons + livebait, data = fish, dist = "poisson") fit_pscl
Compare.
coef | B | se | Z | p | |
---|---|---|---|---|---|
pscl | X.Intercept. | -2.006 | 0.324 | -6.196 | 0.000 |
pscl | persons | 0.747 | 0.043 | 17.516 | 0.000 |
pscl | livebait | 1.809 | 0.292 | 6.195 | 0.000 |
pscl | X.Intercept..1 | 0.303 | 0.674 | 0.449 | 0.654 |
pscl | persons.1 | -0.069 | 0.129 | -0.537 | 0.591 |
pscl | livebait.1 | -0.031 | 0.558 | -0.056 | 0.956 |
zip_poisson_ll | pois.(Intercept) | -2.006 | 0.324 | -6.196 | 0.000 |
zip_poisson_ll | pois.persons | 0.747 | 0.043 | 17.516 | 0.000 |
zip_poisson_ll | pois.livebait | 1.809 | 0.292 | 6.195 | 0.000 |
zip_poisson_ll | logit.(Intercept) | 0.303 | 0.674 | 0.449 | 0.654 |
zip_poisson_ll | logit.persons | -0.069 | 0.129 | -0.537 | 0.591 |
zip_poisson_ll | logit.livebait | -0.031 | 0.558 | -0.056 | 0.956 |
Negative Binomial
Function
<- function(y, X, par) {
zinb_ll # arguments are response y, predictor matrix X, and parameter named starting points of 'logit', 'negbin', and 'theta'
# Extract parameters
= par[grep('logit', names(par))]
logitpars = par[grep('negbin', names(par))]
negbinpars = exp(par[grep('theta', names(par))])
theta
# Logit part; in this function Xlogit = Xnegbin but one could split X argument into Xlogit and Xnegbin for example
= X
Xlogit = Xlogit %*% logitpars
LPlogit = plogis(LPlogit)
logi0
# Negbin part
= X
Xnegbin = exp(Xnegbin %*% negbinpars)
munb
# LLs
=
logliklogit log(
+
logi0 exp(
log(1 - logi0) +
suppressWarnings(dnbinom(0, size = theta, mu = munb, log = TRUE))
)
)
= log(1 - logi0) +
logliknegbin suppressWarnings(dnbinom(y,
size = theta,
mu = munb,
log = TRUE))
# Hilbe formulation
# theta part
# alpha = 1/theta
# m = 1/alpha
# p = 1/(1 + alpha*munb)
# logliklogit = log( logi0 + (1 - logi0)*(p^m) )
# logliknegbin = log(1-logi0) + log(gamma(m+y)) - log(gamma(m)) + m*log(p) + y*log(1-p) # gamma(y+1) not needed
= y == 0 # 0 values
y0 = y > 0 # Count part
yc
= sum(logliklogit[y0]) + sum(logliknegbin[yc])
loglik -loglik
}
Estimation
Get starting values or simply do zeros.
# for zinb: 'logit', 'negbin', 'theta'
= model.matrix(count ~ persons + livebait, data = fish) # to get X matrix
initial_model
= glm(count == 0 ~ persons + livebait, data = fish, family = "binomial")
startlogi = glm(count ~ persons + livebait, data = fish, family = "poisson")
startcount
= c(
starts negbin = coef(startcount),
logit = coef(startlogi),
theta = 1
) # starts = c(negbin = rep(0, 3),
# logit = rep(0, 3),
# theta = log(1))
Estimate with optim.
= optim(
fit_nb par = starts ,
fn = zinb_ll,
X = initial_model,
y = fish$count,
method = "BFGS",
control = list(maxit = 5000, reltol = 1e-12),
hessian = TRUE
)# fit_nb
Comparison
Extract for clean display.
= fit_nb$par
B = sqrt(diag(solve((fit_nb$hessian))))
se = B/se
Z = pnorm(abs(Z), lower = FALSE)*2 p
Results from pscl.
# pscl results
library(pscl)
= zeroinfl(count ~ persons + livebait, data = fish, dist = "negbin") fit_pscl
Compare.
coef | B | se | Z | p | |
---|---|---|---|---|---|
pscl | X.Intercept. | -2.803 | 0.558 | -5.026 | 0.000 |
pscl | persons | 0.849 | 0.124 | 6.833 | 0.000 |
pscl | livebait | 1.791 | 0.511 | 3.504 | 0.000 |
pscl | Log.theta. | -0.969 | 0.302 | -3.206 | 0.001 |
pscl | X.Intercept..1 | -4.276 | 4.278 | -1.000 | 0.318 |
pscl | persons.1 | 0.560 | 0.517 | 1.084 | 0.279 |
pscl | livebait.1 | 1.168 | 3.661 | 0.319 | 0.750 |
zinb_ll | negbin.(Intercept) | -2.803 | 0.558 | -5.026 | 0.000 |
zinb_ll | negbin.persons | 0.849 | 0.124 | 6.834 | 0.000 |
zinb_ll | negbin.livebait | 1.791 | 0.511 | 3.504 | 0.000 |
zinb_ll | logit.(Intercept) | -4.276 | 4.278 | -1.000 | 0.318 |
zinb_ll | logit.persons | 0.560 | 0.517 | 1.084 | 0.279 |
zinb_ll | logit.livebait | 1.168 | 3.661 | 0.319 | 0.750 |
zinb_ll | theta | -0.969 | 0.302 | -3.206 | 0.001 |
Supplemental Example
This supplemental example uses the bioChemists data. It contains a sample of 915 biochemistry graduate students with the following information:
- art: count of articles produced during last 3 years of Ph.D.
- fem: factor indicating gender of student, with levels Men and Women
- mar: factor indicating marital status of student, with levels Single and Married
- kid5: number of children aged 5 or younger
- phd: prestige of Ph.D. department
- ment: count of articles produced by Ph.D. mentor during last 3 years
data("bioChemists", package = "pscl")
= model.matrix(art ~ fem + mar + kid5 + phd + ment, data = bioChemists) # to get X matrix
initial_model = glm(art==0 ~ fem + mar + kid5 + phd + ment, data = bioChemists, family = "binomial")
startlogi = glm(art ~ fem + mar + kid5 + phd + ment, data = bioChemists, family = "quasipoisson")
startcount
= c(
starts negbin = coef(startcount),
logit = coef(startlogi),
theta = summary(startcount)$dispersion
)
# starts = c(negbin = rep(0, 6),
# logit = rep(0, 6),
# theta = 1)
= optim(
fit_nb_pub par = starts ,
fn = zinb_ll,
X = initial_model,
y = bioChemists$art,
method = "BFGS",
control = list(maxit = 5000, reltol = 1e-12),
hessian = TRUE
)# fit_nb_pub
= fit_nb_pub$par
B = sqrt(diag(solve((fit_nb_pub$hessian))))
se = B/se
Z = pnorm(abs(Z), lower = FALSE)*2
p
library(pscl)
= zeroinfl(art ~ . | ., data = bioChemists, dist = "negbin")
fit_pscl
summary(fit_pscl)$coefficients
$count
Estimate Std. Error z value Pr(>|z|)
(Intercept) 0.4167465901 0.143596450 2.90220678 3.705439e-03
femWomen -0.1955076374 0.075592558 -2.58633447 9.700275e-03
marMarried 0.0975826042 0.084451953 1.15548073 2.478936e-01
kid5 -0.1517320709 0.054206071 -2.79917119 5.123397e-03
phd -0.0006997593 0.036269674 -0.01929323 9.846072e-01
ment 0.0247861500 0.003492672 7.09661548 1.278491e-12
Log(theta) 0.9763577454 0.135469554 7.20721163 5.710921e-13
$zero
Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.19160645 1.3227962 -0.1448496 0.884829645
femWomen 0.63587048 0.8488959 0.7490559 0.453823498
marMarried -1.49943716 0.9386562 -1.5974296 0.110169987
kid5 0.62840922 0.4427746 1.4192531 0.155825245
phd -0.03773288 0.3080059 -0.1225070 0.902497523
ment -0.88227364 0.3162186 -2.7900755 0.005269575
round(data.frame(B, se, Z, p), 4)
B se Z p
negbin.(Intercept) 0.4167 0.1436 2.9021 0.0037
negbin.femWomen -0.1955 0.0756 -2.5863 0.0097
negbin.marMarried 0.0976 0.0845 1.1555 0.2479
negbin.kid5 -0.1517 0.0542 -2.7992 0.0051
negbin.phd -0.0007 0.0363 -0.0195 0.9845
negbin.ment 0.0248 0.0035 7.0967 0.0000
logit.(Intercept) -0.1916 1.3229 -0.1449 0.8848
logit.femWomen 0.6359 0.8490 0.7491 0.4538
logit.marMarried -1.4995 0.9387 -1.5974 0.1102
logit.kid5 0.6284 0.4428 1.4192 0.1558
logit.phd -0.0377 0.3080 -0.1225 0.9025
logit.ment -0.8823 0.3162 -2.7901 0.0053
theta 0.9763 0.1355 7.2071 0.0000
Source
Original code for zip_ll found at https://github.com/m-clark/Miscellaneous-R-Code/blob/master/ModelFitting/poiszeroinfl.R
Original code for zinb_ll found at https://github.com/m-clark/Miscellaneous-R-Code/blob/master/ModelFitting/NBzeroinfl.R