# Tobit Regression

The following is a simple demonstration of tobit regression via maximum likelihood. The issue is one where data is censored such that while we observe the value, it is not the true value, which would extend beyond the range of the observed data. This is very commonly seen in cases where the dependent variable has been given some arbitrary cutoff at the lower or upper end of the range, often resulting in floor or ceiling effects respectively. The conceptual idea is that we are interested in modeling the underlying latent variable that would not have such restriction if it was actually observed.

## Censoring with an Upper Limit

### Data Setup

Data regards academic aptitude (GRE scores) with will be modeled using reading and math test scores, as well as the type of program the student is enrolled in (academic, general, or vocational). See this for an applied example and more detail.

library(tidyverse)

mutate(prog = factor(prog, labels = c('acad', 'general', 'vocational')))

Setup data and initial values.

initmod = lm(apt ~ read + math + prog, data = acad_apt)
X = model.matrix(initmod)
init = c(coef(initmod), log_sigma = log(summary(initmod)$sigma)) ### Function tobit_ll <- function(par, X, y, ul = -Inf, ll = Inf) { # this function only takes a lower OR upper limit # parameters sigma = exp(par[length(par)]) beta = par[-length(par)] # create indicator depending on chosen limit if (!is.infinite(ll)) { limit = ll indicator = y > ll } else { limit = ul indicator = y < ul } # linear predictor lp = X %*% beta # log likelihood ll = sum(indicator * log((1/sigma)*dnorm((y-lp)/sigma)) ) + sum((1-indicator) * log(pnorm((lp-limit)/sigma, lower = is.infinite(ll)))) -ll } ### Estimation Estimate via optim. fit_tobit = optim( par = init, tobit_ll, y = acad_apt$apt,
X  = X,
ul = 800,
method  = 'BFGS',
control = list(maxit = 2000, reltol = 1e-15)
)

# this would be more akin to the default Stata default approach
# optim(
#   par = init,
#   tobit_ll,
#   y = acad_apt$apt, # X = X, # ul = 800, # control = list(maxit = 16000, reltol = 1e-15) # ) ### Comparison Compare to AER package tobit function. library(survival) fit_aer = AER::tobit( apt ~ read + math + prog, data = acad_apt, left = -Inf, right = 800 ) (Intercept) read math proggeneral progvocational sigma.log_sigma logLike fit_tobit 209.566 2.698 5.914 -12.716 -46.143 65.677 -1041.063 AER 209.566 2.698 5.914 -12.715 -46.144 65.677 -1041.063 library(survival) fit_aer = AER::tobit( apt ~ read + math + prog, data = acad_apt, left = -Inf, right = 800 ) AER is actually just using survreg from the survival package. Survival models are usually for modeling time to some event, e.g. death in medical studies, and the censoring comes from the fact that the observed event does not occur for some people. Like our tobit function, an indicator is needed to denote who is or isn’t censored. In survival models, the indicator is for the event itself, and means they are NOT censored. So we’ll reverse the indicator used in the tobit function for survreg. fit_surv = survreg(Surv(apt, apt < 800, type = 'right') ~ read + math + prog, data = acad_apt, dist = 'gaussian') Compare all results. (Intercept) read math proggeneral progvocational sigma.log_sigma logLike fit_tobit 209.566 2.698 5.914 -12.716 -46.143 65.677 -1041.063 AER 209.566 2.698 5.914 -12.715 -46.144 65.677 -1041.063 survival 209.566 2.698 5.914 -12.715 -46.144 65.677 -1041.063 ## Censoring with a Lower Limit Create a censored data situation for the low end. The scale itself would be censored for anyone scoring a 200, but that basically doesn’t happen. In this data, 15 are less than a score of 500, so we’ll do that. acad_apt = acad_apt %>% mutate(apt2 = apt, apt2 = if_else(apt2 < 500, 500, apt2)) Estimate and use AER for comparison. fit_tobit = optim( par = init, tobit_ll, y = acad_apt$apt2,
X  = X,
ll = 400,
method  = 'BFGS',
control = list(maxit = 2000, reltol = 1e-15)
)

fit_aer = AER::tobit(apt2 ~ read + math + prog,
left = 400)