Nothing
## BMlmSE.R implements Bell-McCaffrey standard errors
## This code is taken from Michal Kolesar
## https://github.com/kolesarm/Robust-Small-Sample-Standard-Errors
## Only changed the name of one function, df
#' Compute the inverse square root of a symmetric matrix
#' @param A matrix
MatSqrtInverse <- function(A) {
ei <- eigen(A, symmetric = TRUE)
if (min(ei$values) <= 0) {
warning("Gram matrix doesn't appear to be positive definite")
}
d <- pmax(ei$values, 0)
d2 <- 1 / sqrt(d)
d2[d == 0] <- 0
## diag(d2) is d2 x d2 identity if d2 is scalar, instead we want 1x1 matrix
ei$vectors %*% (if (length(d2) == 1) d2 else diag(d2)) %*% t(ei$vectors)
}
#' Compute Bell-McCaffrey Standard Errors
#' @param model Fitted model returned by the \code{lm} function
#' @param clustervar Factor variable that defines clusters. If \code{NULL} (or
#' not supplied), the command computes heteroscedasticity-robust standard
#' errors, rather than cluster-robust standard errors.
#' @param ell A vector of the same length as the dimension of covariates,
#' specifying which linear combination \eqn{\ell'\beta} of coefficients
#' \eqn{\beta} to compute. If \code{NULL}, compute standard errors for each
#' regressor coefficient
#' @param IK Logical flag only relevant if cluster-robust standard errors are
#' being computed. Specifies whether to compute the degrees-of-freedom
#' adjustment using the Imbens-Kolesár method (if \code{TRUE}), or the
#' Bell-McCaffrey method (if \code{FALSE})
#' @return Returns a list with the following components \describe{
#'
#' \item{vcov}{Variance-covariance matrix estimator. For the case without
#' clustering, it corresponds to the HC2 estimator (see MacKinnon and White,
#' 1985 and the reference manual for the \code{sandwich} package). For the case
#' with clustering, it corresponds to a generalization of the HC2 estimator,
#' called LZ2 in Imbens and Kolesár.}
#'
#' \item{dof}{Degrees-of-freedom adjustment}
#'
#' \item{se}{Standard error}
#'
#' \item{adj.se}{Adjusted standard errors. For \beta_j, they are defined as
#' \code{adj.se[j]=sqrt(vcov[j,j]se*qt(0.975,df=dof)} so that the Bell-McCaffrey
#' confidence intervals are given as \code{coefficients(fm)[j] +- 1.96* adj.se=}
#'
#' \item{se.Stata}{Square root of the cluster-robust variance estimator used in
#' STATA}
#'
#' }
#' @examples
#' ## No clustering:
#' set.seed(42)
#' x <- sin(1:10)
#' y <- rnorm(10)
#' fm <- lm(y~x)
#' BMlmSE(fm)
#' ## Clustering, defining the first six observations to be in cluster 1, the
#' #next two in cluster 2, and the last three in cluster three.
#' clustervar <- as.factor(c(rep(1, 6), rep(2, 2), rep(3, 2)))
#' BMlmSE(fm, clustervar)
BMlmSE <- function(model, clustervar=NULL, ell=NULL, IK=TRUE) {
X <- model.matrix(model)
sum.model <- summary.lm(model)
n <- sum(sum.model$df[1:2])
K <- model$rank
XXinv <- sum.model$cov.unscaled # XX^{-1}
u <- residuals(model)
## Compute DoF given G'*Omega*G without calling eigen as suggested by
## Winston Lin
DoF <- function(GG)
sum(diag(GG)) ^ 2 / sum(GG * GG)
## Previously:
## lam <- eigen(GG, only.values=TRUE)$values
## sum(lam)^2/sum(lam^2)
## no clustering
if (is.null(clustervar)) {
Vhat <- sandwich::vcovHC(model, type = "HC2")
Vhat.Stata <- Vhat * NA
M <- diag(n) - X %*% XXinv %*% t(X) # annihilator matrix
## G'*Omega*G
GOG <- function(ell) {
Xtilde <- drop(X %*% XXinv %*% ell / sqrt(diag(M)))
crossprod(M * Xtilde)
}
} else {
if (!is.factor(clustervar)) stop("'clustervar' must be a factor")
## Stata
S <- length(levels(clustervar)) # number clusters
uj <- apply(u * X, 2, function(x) tapply(x, clustervar, sum))
Vhat.Stata <- S / (S - 1) * (n - 1) / (n - K) *
sandwich::sandwich(model, meat = crossprod(uj) / n)
## HC2
tXs <- function(s) {
Xs <- X[clustervar == s, , drop = FALSE]
MatSqrtInverse(diag(NROW(Xs)) - Xs %*% XXinv %*% t(Xs)) %*% Xs
}
tX <- lapply(levels(clustervar), tXs) # list of matrices
tu <- split(u, clustervar)
tutX <- sapply(seq_along(tu), function(i) crossprod(tu[[i]], tX[[i]]))
Vhat <- sandwich::sandwich(model, meat = tcrossprod(tutX) / n)
## DOF adjustment
tHs <- function(s) {
Xs <- X[clustervar == s, , drop = FALSE]
index <- which(clustervar == s)
ss <- outer(rep(0, n), index) # n x ns matrix of 0
ss[cbind(index, 1:length(index))] <- 1
ss - X %*% XXinv %*% t(Xs)
}
tH <- lapply(levels(clustervar), tHs) # list of matrices
Moulton <- function() {
## Moulton estimates
ns <- tapply(u, clustervar, length)
ssr <- sum(u ^ 2)
rho <- max((sum(sapply(seq_along(tu), function(i)
sum(tu[[i]] %o% tu[[i]]))) - ssr) / (sum(ns ^ 2) - n), 0)
c(sig.eps = max(ssr / n - rho, 0), rho = rho)
}
GOG <- function(ell) {
G <- sapply(
seq_along(tX),
function(i) tH[[i]] %*% tX[[i]] %*% XXinv %*% ell
)
GG <- crossprod(G)
## IK method
if (IK == TRUE) {
Gsums <- apply(
G, 2,
function(x) tapply(x, clustervar, sum)
) # Z'*G
GG <- Moulton()[1] * GG + Moulton()[2] * crossprod(Gsums)
}
GG
}
}
if (!is.null(ell)) {
se <- drop(sqrt(crossprod(ell, Vhat) %*% ell))
dof <- DoF(GOG(ell))
se.Stata <- drop(sqrt(crossprod(ell, Vhat.Stata) %*% ell))
} else {
se <- sqrt(diag(Vhat))
dof <- sapply(seq(K), function(k) DoF(GOG(diag(K)[, k])))
se.Stata <- sqrt(diag(Vhat.Stata))
}
names(dof) <- names(se)
list(
vcov = Vhat, dof = dof, adj.se = se * qt(0.975, df = dof) / qnorm(0.975),
se = se, se.Stata = se.Stata
)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.