library(marginaleffects)options(marginaleffects_print_omit =c("s.value"))# estimate marginal effects w/ all vars held at means# if called w/o 'newdata' option, calculates ME for every obs in dataslopes(m5,variables =c("male", "educ_scale", "income_scale"),newdata ="mean")
# estimate marginal effect of income across values of knowledgem_income <-slopes(m5,variables =c("income_scale"),newdata =datagrid(know_mean =seq(0, 1, 0.1)))m_income
In this context, “simulation” describes the process of repeated random sampling from the sampling distribution
We can calculate quantities of interest for each sample realization and then summarize these
The resulting distributions give us information about uncertainty w/o needing analytic solutions for SEs
This is easily done using the arm package’s sim function, but it is easy by hand as well
marginaleffects has this functionality too (though it is currently labeled “EXPERIMENTAL”) - it also does NOT (based on help files) propogate uncertainty for \(\hat{\sigma}\) (see below)
Using sim from arm
# draw 1,000 realizations of model parameters from sampling distributionm4_sims <- arm::sim(m4, n.sims=1000)
# the sigmas are stored in @sigmam4_sims@sigma[1:3]
[1] 0.1872169 0.1880067 0.1916445
What does sim do?
For each sample, sim first draws a random value of \(\hat{\sigma}\) from its sampling distribution, and then uses this value to draw a random sample from the sampling distribution for \(\hat{\boldsymbol{\beta}}\)
This is the asymptotic distribution of \(\hat{\boldsymbol{\beta}}\) under standard assumptions, and the t distribution converges pretty quickly to normal with increasing sample size
For small samples, say, less than 100, you might not want to rely on asymptotic normality
A good alternative is bootstrapping (described below), which does not make distributional assumptions
Example, quadratic
# calculate conditional marginal effect of age for each simage_cme_sims <-apply(m4_sims@coef, 1, function(x) x["age_scale"] +2*seq(0,1,0.05)*x["I(age_scale^2)"])dim(age_cme_sims)
Resampling methods use the actual data you have to approximate the sampling distribution of quantities of interest
They are “resampling” in the sense that they estimate the model many times, on varying subsets of observations, to produce a distribution of estimates
Jackknife
Bootstrap (many variants; large and complicated literature)
Much of my discussion here draws from Hanson (2022), Ch. 10, which has an extensive treatment of these methods
Advantages and disadvantages
Advantages
Does not require ability to calculate variance of estimator of quantity of interest
Often performs better when standard assumptions don’t hold (e.g., normality of errors, “large enough” sample)
Disadvantages
Computationally more expensive (though varies a lot across methods)
The statistical theory is rather difficult (for me at least!)
There is a large literature with many variants and approaches
All that said, if you are looking for a “go-to” approach when finite-sample properties cannot be assumed, bootstrapping is a reasonable bet
Jackknife
A leave-one-out estimator computes estimates using all observations except one (there are thus \(N\) possibilities)
The jackknife variance estimator calculates the variance of a parameter of interest by calculating a function of the variance of the set of leave-one-out estimators
Very flexible method, overlap with other approaches
Requires no distributional assumptions
Asymptotically approximates the Delta-method estimator (see below)
Approximates the HC3 heteroskedasticity-robust estimator (see week on heteroskedasticity), but without assumptions regarding asymptotic covariance matrix
But does require \(N\) separate estimations (not a big deal for the models we are dealing with)
Jackknife example
# sample size and num parms for modelN <-nrow(m5$model)K <-length(coef(m5))# init array to store betasbetas <-array(NA, c(K, N))# leave one outfor (i in1:nrow(m5$model)){ betas[, i] <-coef(update(m5, . ~ ., data = m5$model[-i, ]))}# calculate SE estimatescbind(JK =round( apply(betas, 1, function(x) sqrt( ( (N-1)/N ) *sum( (x -mean(x))^2 ) ) ),3 ), OLS =round(sqrt(diag(vcov(m5))), 3),HC3 =round(sqrt(diag(sandwich::vcovHC(m5, type ="HC3"))), 3))
Draw boots random samples from analytical sample, each of size \(N\), run model, store coefs
# number of bootstrap samplesboots <-1000# initialize array to store coefsboots_beta <-array(NA, c(boots, # num of bootstrap samplessummary(m4)$df[1]) # number of regression coefficients (K+1))# iterate over samplesfor (i in1:boots){ new <-sample(1:nrow(m4$model), nrow(m4$model), replace = T) # sample rows of data w/ replacement boots_beta[i, ] <-coef(lm(formula(m4), data = m4$model[new, ])) # run model with bootstrap sample# repeat boots number of times }
Standard errors
The distribution of bootstrap estimates is an estimate of the sampling distribution of the parameter, which we can then use for inference
The bootstrap standard error is simply the square root of the sample variance of bootstrap estimates
It is consistent for simple linear models
There are cases where it is inconsistent, especially w/ non-linear functions of estimated coefficients
An alternative, “trimmed” estimator is preferable in these cases
Cut the most extreme p% of the bootstrap estimates (e.g., 1%)
Consistent under more general conditions
Confidence intervals
There is a large literature on methods for calculating bootstrap confidence intervals, and various different options
normal approximation
percentile intervals
bias-corrected
percentile-t
Normal approximation
Use bootstrap standard error and plug into formula for confidence intervals using normal distribution
Often the default
Not the best option: worse performance than others
Percentile intervals
Choose confidence level (e.g., \(\alpha = 5%\)), select quantiles of bootstrap estimates associated with \(\alpha/2\) and \((100 - \alpha/2)\) (e.g., 2.5% and 97.5%)
Easy and popular; does not require calculation of standard errors
Consistent under weaker assumptions than normal approximation and bootstrap standard errors, and does not require trimming
Requires that the asymptotic distribution is symmetric about 0
Finite-sample performance may be unsatisfactory if estimator is asymmetric for your sample size
This includes cases where estimators are biased (even if consistent), which is often true with non-linear models
Bias-corrected (BC) intervals
Adjusts percentile intervals for finite-sample bias using degree of asymmetry in empirical distribution of bootstrap estimates around parameter estimate
Provides “exact” coverage under assumption that bias depends only on a parameter drawn from a symmetric, invertible distribution
Is also asymptotically valid for the interval of interest
An extension, the accelerated bias-corrected estimator (BC\(_{\alpha}\)) also estimates skewness in sampling distribution (in addition to bias) and adjusts for it
Will typically have better performance than BC
Faster rate of asymptotic convergence than percentile and BC methods
Can take a long time to estimate in R
Percentile-t intervals
Calculate a t-statistic for every bootstrap sample: \(T^* = \frac{\hat{\theta}^* - \hat{\theta}}{se(\hat{\theta}^*)}\)
\(\hat{\theta}^*\) is the bootstrap parameter estimate for a particular bootstrap sample
\(se(\hat{\theta}^*)\) is the estimated standard error for a particular bootstrap sample
\(\hat{\theta}\) is the estimate from your standard procedure (e.g., OLS coefficient)
Percentile-t intervals
Calculate the percentile-t interval from the quantiles of \(T^*\), \(q^*\):
Uses standard error, so need a method for calculating, and accuracy depends on the reliability of the estimate
Can use bootstrap to estimate SE on each bootstrap (i.e., nested bootstrap)
When SE estimate reliable, may be better than other methods
Faster rate of asymptotic convergence than percentile and BC methods
Using boot package via marginaleffects
# estimate slopesm5_me <-slopes(m5,variables =c("male", "educ_scale", "income_scale"),newdata ="mean")# use inferences() to get bootstrap intervalsinferences(m5_me, method ="boot", R =1000, conf_type ="perc")inferences(m5_me, method ="boot", R =1000, conf_type ="bca")
boot package
library(boot)# boot callm5_boot <-boot(m5$model, function(data, indices) { d <- data[indices, ]coef(update(m5, . ~ ., data = d))[5] },R =10) # DON'T USE 10!!! THIS IS NOT A RECOMMENDATION# use boot.ci to get CIsboot.ci(m5_boot)
BOOTSTRAP CONFIDENCE INTERVAL CALCULATIONS
Based on 10 bootstrap replicates
CALL :
boot.ci(boot.out = m5_boot)
Intervals :
Level Normal Basic
95% ( 0.0129, 0.0399 ) ( 0.0141, 0.0366 )
Level Percentile BCa
95% ( 0.0149, 0.0375 ) ( 0.0149, 0.0375 )
Calculations and Intervals on Original Scale
Warning : Basic Intervals used Extreme Quantiles
Some basic intervals may be unstable
Warning : Percentile Intervals used Extreme Quantiles
Some percentile intervals may be unstable
Warning : BCa Intervals used Extreme Quantiles
Some BCa intervals may be unstable
boot package to get percentile-t
# boot callm5_boot <-boot(m5$model, function(data, indices) { d <- data[indices, ] fit <-update(m5, . ~ ., data = d) B <-coef(fit)[5] se <-sqrt(diag(vcov(fit)))[5]return(c(B, se)) },R =1000)# use boot.ci to get CIsboot.ci(m5_boot, type ="stud")
BOOTSTRAP CONFIDENCE INTERVAL CALCULATIONS
Based on 1000 bootstrap replicates
CALL :
boot.ci(boot.out = m5_boot, type = "stud")
Intervals :
Level Studentized
95% ( 0.0033, 0.0467 )
Calculations and Intervals on Original Scale
Bootstrap hypothesis testing
We can use the bootstrapped t-statistics to calculate p-values - recall:
The (two-tailed) p-value is then the percent of \(|T^*| > |T|\), where \(T\) is the t-statistic from your standard procedure (e.g., OLS output for a coefficient)
Wild bootstrap
Our bootstrap methods to this point all sample both \(X\) and \(Y\), but our standard linear regression model treats \(X\) as fixed and imposes \(\text{E}(u | \mathbf{X}) = 0\)
Integrating these assumptions into the bootstrap will increase precision (and also take into account heteroskedasticity)
The wild bootstrap fixes \(X\) and takes draws of the error to generate bootstrap observations:
For hypothesis testing, it is better to generate bootstrap resamples using the null model (set coefficients to null hypothesis values to generate residuals and then generate \(y^*\))
m1 <-lm(log(salary) ~ yrs.since.phd + sex + discipline, data = carData::Salaries)summary(m1)
Call:
lm(formula = log(salary) ~ yrs.since.phd + sex + discipline,
data = carData::Salaries)
Residuals:
Min 1Q Median 3Q Max
-0.84527 -0.15697 -0.00855 0.15550 0.62670
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1.125e+01 4.189e-02 268.671 < 2e-16 ***
yrs.since.phd 9.584e-03 9.075e-04 10.560 < 2e-16 ***
sexMale 6.639e-02 3.830e-02 1.734 0.0838 .
disciplineB 1.447e-01 2.319e-02 6.239 1.14e-09 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.2244 on 393 degrees of freedom
Multiple R-squared: 0.2615, Adjusted R-squared: 0.2559
F-statistic: 46.39 on 3 and 393 DF, p-value: < 2.2e-16
Non-linear example
Avg effect of change from 10 to 20 years since PhD on unlogged salaries: \(f = \frac{1}{N} \sum_N e^{\boldsymbol{x}_{i,yrs=20}\hat{\boldsymbol{\beta}}}E[u_i] - e^{\boldsymbol{x}_{i,yrs=10}\hat{\boldsymbol{\beta}}}E[u_i]\)
The delta method is the default approach to calculating standard errors in marginaleffects
I do not see a simple approach for logged DVs, but I think you can write your own functions (or use car)
But for most cases, it will work well
The exception is when you have exact standard errors (assuming normal regression errors), in which case, you would use t for tests and CIs, but delta method uses normal