An introduction to chantrics

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.retina = 2
)
library(chantrics)

chantrics applies the Chandler-Bate loglikelihood adjustment [@chanbate07] implemented in the chandwich package [@chandwich] to different models frequently used in basic Econometrics applications. adj_loglik() is the central function of chantrics, it is a generic function adjusting the parameter covariance matrix of the models to incorporate clustered data, and can mitigate for model misspecification. The returned object can then be plugged into a range of model analysis functions, which will be described below.

Functionality for singular model objects

Note that not all functionality demonstrated below is available for all types of models.

In order to be able to demonstrate the range of functionality available, this example will be using the misspecified count data regression from Chapter 5.1 in the Object-Oriented Computation of Sandwich Estimators vignette from the sandwich package [@zeileis06].

First, data from a negative binomial model is generated, and then a Poisson model is fit, which is clearly misspecified.

set.seed(123)
x <- rnorm(250)
y <- rnbinom(250, mu = exp(1 + x), size = 1)

## Fit the Poisson glm model, which is not correctly specified
fm_pois <- glm(y ~ x + I(x^2), family = poisson)
lmtest::coeftest(fm_pois)
## The I(x^2) term is spuriously significant.

We can now use the model object fm_pois, and adjust it using adj_loglik(). Use coef() to get a vector of the coefficients, summary() to get an overview over the adjustment, or use lmtest::coeftest() to see the results of (z) tests on each of the coefficients.

fm_pois_adj <- adj_loglik(fm_pois)
coef(fm_pois_adj) # class "numeric"
summary(fm_pois_adj)
lmtest::coeftest(fm_pois_adj)

Confidence intervals of the estimates

The function chandwich::conf_intervals() returns confidence intervals at the level specified in conf (default: 95). To use one of the other specifications of the adjustment from @chanbate07, use the type argument. Many other adjustments are available. The classic S3 method confint() is also available.

chandwich::conf_intervals(fm_pois_adj)
chandwich::conf_intervals(fm_pois_adj, type = "spectral", conf = 99)
confint(fm_pois_adj)

We can also plot confidence regions of the estimates for two coefficients using chandwich::conf_region(), where we can specify the parameters using which_pars, the type of specification of the adjustment from @chanbate07 using type, and the confidence levels using conf. Other adjustments are available.

fm_pois_adj_vert <-
  chandwich::conf_region(fm_pois_adj, which_pars = c("x", "I(x^2)"))
fm_pois_adj_none <-
  chandwich::conf_region(fm_pois_adj,
    which_pars = c("x", "I(x^2)"),
    type = "none"
  )
par(mar = c(5.1, 5.1, 2.1, 2.1))
plot(
  fm_pois_adj_vert,
  fm_pois_adj_none,
  conf = c(60, 80, 90, 95),
  col = c("brown", "darkgreen"),
  lty = c(1, 2),
  lwd = 2.5
)
par(mar = c(5.1, 4.1, 4.1, 2.1))

Other diagnostic functions

The methods

The performance of the types of adjustments that are shown in @chanbate07 can be seen using plot() if there is a single free parameter. Use type to specify the types of adjustment that should show in the plot.

fm_pois_smallest_adj <- update(fm_pois_adj, . ~ 1)
plot(fm_pois_smallest_adj, type = 1:4, col = 1:4, legend_pos = "bottom", lwd = 2.5)

Note that for one free parameter, the Cholesky and the spectral adjustments are identical, and the vertical adjustment only deviates slightly at the edges of the plot.

Functionality for multiple model objects

In order to have a wider range of coefficients to do model comparisons on, we will follow a Probit regression example in @AER-book [p. 124] using the SwissLabor dataset from the AER package [@AER].

data("SwissLabor", package = "AER")
swiss_probit <-
  glm(participation ~ . + I(age^2),
    data = SwissLabor,
    family = binomial(link = "probit")
  )
swiss_probit_adj <- adj_loglik(swiss_probit)
lmtest::coeftest(swiss_probit_adj)

Creating nested models

The update() function is also available for chantrics objects, it automatically re-estimates the updated model, and adjusts the loglikelihood.

swiss_probit_small_adj <-
  update(swiss_probit_adj, . ~ . - I(age^2) - education)
swiss_probit_smaller_adj <-
  update(swiss_probit_adj, . ~ . - I(age^2) - education - youngkids - oldkids)

Compare nested models

Nested models can be compared with anova() using an adjusted likelihood ratio test as outlined in Section 3.5 in @chanbate07. The type of adjustment can again be set using type. anova() sorts the models by the number of free variables as returned by attr(model, "p_current"), where model is the chantrics model object.

anova(swiss_probit_adj, swiss_probit_small_adj, swiss_probit_smaller_adj)

By passing in a singular model, we can also use anova() to generate a sequential analysis of deviance table, where each of the covariates is sequentially removed from the model.

anova(swiss_probit_adj)

Another way of performing an adjusted likelihood ratio test is by using alrtest(), which is inspired and similar in usage to lmtest::waldtest() and lmtest::lrtest(), where a model, and an indicator of the variables that should be restricted/removed. These indicators can be character strings of the names of the covariates, integers corresponding to the position of a covariate, formula objects, or nested model objects, allowing a flexible and easy specification of nested models.

alrtest(swiss_probit_adj, 3, "oldkids")
alrtest(swiss_probit_adj, . ~ . - youngkids - foreign, . ~ . - education)
alrtest(swiss_probit_adj, swiss_probit_small_adj)

Supported models

glm models

In a generalised linear model (glm), the user can choose between a range of distributions of a response (y), and can allow for non-linear relations between the mean outcome for a certain combination of covariates (x), (\operatorname{E}(y_i\mid x_i)=\mu_i), and the linear predictor, (\eta_i=x_i^T\beta), which is the link function (g(\mu_i)=\eta_i). The link function is required to be monotonic. (For a quick introduction, see @AER-book [, Ch. 5.1], for a more complete coverage of the topic, see, for example, @davison03 [, Ch. 10.3])

Within the array of glm families presented below, any link function should work.

poisson family

The Poisson family of models are commonly used specifications for count models. The specification of this form of a GLM is (for the canonical log-link of the Poisson specification),

\begin{equation} \operatorname{E}(y_i\mid x_i)=\mu_i=\exp(x_i^T\beta). \end{equation}

In this example, I will reuse the example of the misspecified count data regression from Chapter 5.1 in the Object-Oriented Computation of Sandwich Estimators vignette from the sandwich package [@zeileis06]. More on the Poisson specification in glms can be found in @cameron13 [, Ch. 3.2.4] or @davison03 [, Example 10.10]. More on the implementation in R is located in @count-zeil [, Ch. 2.1], and in @AER-book [, Ch. 5.3].

summary(fm_pois) # fm_pois is the fitted poisson model from above.
fm_pois_adj <- adj_loglik(fm_pois)
summary(fm_pois_adj)

binomial family

The binomial family of models is widely used for binary choice modelling of probabilities of choosing a certain option given a set of properties. As (\operatorname{E}(y_i\mid x_i)=1\cdot P(y_i=1\mid x_i)+0\cdot P(y_i=0\mid x_i)=P(y_i=1\mid x_i)), the point estimation of the model is the probability of (y_i) being equal to 1. It is specified using the GLM framework described above, with \begin{equation} \operatorname{E}(y_i\mid x_i)=P(y_i=1)=p_i=F(x_i^T\beta), \end{equation} where (F) is a valid cdf of the error terms in the model, or, equally, the link function. Most commonly, these are chosen to be either the standard normal cdf in the Probit case, family = binomial(link = "probit"), or the logistic cdf in the logit case, family = binomial(link = "logit").

This example will use the SwissLabor example in @AER-book [, pg. 124]

data("SwissLabor", package = "AER")
## Fitting a Probit model
swiss_probit <-
  glm(participation ~ . + I(age^2),
    data = SwissLabor,
    family = binomial(link = "probit")
  )
swiss_probit_adj <- adj_loglik(swiss_probit)
summary(swiss_probit_adj)

## Fitting a Logit model
swiss_logit <-
  glm(formula(swiss_probit),
    data = SwissLabor,
    family = binomial(link = "logit")
  )
swiss_logit_adj <- adj_loglik(swiss_logit)
summary(swiss_logit_adj)

Note that other specifications of the link function are possible, the code is agnostic towards the link function.

gaussian family

The GLM framework encompasses the estimation of a standard linear regression using maximum likelihood estimators, therefore we can use the adjustment to account for clustering/misspecifications in the regression. We can use the classic assumption of normally distributed error terms to specify the distribution of (y). The link is simply the identity function. [@AER-book, pg. 123]

To demonstrate the functionality, I will employ the linear regression example in @AER-book [, pg. 66], using the cross-sectional CPS1988 data.

data("CPS1988", package = "AER")
cps_glm <-
  glm(
    log(wage) ~ experience + I(experience^2) + education + ethnicity,
    data = CPS1988,
    family = gaussian()
  )
summary(cps_glm)
cps_glm_adj <- adj_loglik(cps_glm)
summary(cps_glm_adj)

MASS::negative.binomial family and MASS::glm.nb()

Another commonly used model for count data is the negative binomial model. It overcomes two issues of the Poisson model, that the expectation and the variance are assumed to be equal, and that in most datasets, the number of zeros is underestimated by Poisson family models.

The method of estimation of the negative binomial model depends on whether the dispersion parameter (\theta) of the pmf, \begin{equation} f(y;\mu,\theta)=\frac{\Gamma(\theta+y)}{\Gamma(\theta)y!}\frac{\mu^y\theta^\theta}{(\mu+\theta)^{y+\theta}} \end{equation} is known. In most cases, it is not known, and the specialised function MASS::glm.nb() can be used, which estimates the negative binomial model and the (\theta) parameter separately. If (\theta) is known, the MASS package [@mass] provides the MASS::negative.binomial() family function for glm().

A quick introduction can be found in @AER-book [, pg. 135f.] and in @count-zeil [, pg. 5], and a more general treatment with estimation using direct ML estimation in @cameron13 [, Ch. 3.3].

Note that adj_loglik() re-estimates the theta parameter of a glm.nb model.

To demonstrate the negative binomial model functions, we will use the negative binomial data generated above, which we have falsely fitted to a Poisson model.

fm_negbin_theta <- MASS::glm.nb(y ~ x)
summary(fm_negbin_theta)
fm_negbin_theta_adj <- adj_loglik(fm_negbin_theta)
summary(fm_negbin_theta_adj)

Clustering

To specify clusters, use the cluster argument in adj_loglik() to specify a vector or factor which indicates the cluster that the corresponding observation belongs to. Without specifying a cluster, adj_loglik() assumes that each observation forms its own cluster.

References



Try the chantrics package in your browser

Any scripts or data that you put into this service are public.

chantrics documentation built on Sept. 29, 2021, 9:08 a.m.