moderndid.create_sensitivity_results_sm#
- moderndid.create_sensitivity_results_sm(betahat, sigma, num_pre_periods, num_post_periods, method=None, m_vec=None, l_vec=None, monotonicity_direction=None, bias_direction=None, alpha=0.05, grid_points=1000, grid_lb=None, grid_ub=None)[source]#
Perform sensitivity analysis using smoothness restrictions.
Implements methods for robust inference in difference-in-differences and event study designs using smoothness restrictions \(\Delta^{SD}(M)\) on the underlying trend, following [1]. This function computes confidence intervals across a range of smoothness bounds \(M\), facilitating sensitivity analysis that shows what causal conclusions can be drawn under various assumptions about possible trend nonlinearities.
The FLCI method has finite-sample near-optimal expected length for \(\Delta^{SD}\) and is recommended when no additional shape restrictions are imposed. The conditional and hybrid methods (C-F, C-LF) provide uniform size control and are recommended when monotonicity or sign restrictions are added.
- Parameters:
- betahat
numpy.ndarray Estimated event study coefficients. Should have length num_pre_periods + num_post_periods.
- sigma
numpy.ndarray Covariance matrix of betahat. Should be (num_pre_periods + num_post_periods) x (num_pre_periods + num_post_periods).
- num_pre_periods
int Number of pre-treatment periods.
- num_post_periods
int Number of post-treatment periods.
- method
str, optional Confidence interval method. Options are:
“FLCI”: Fixed-length confidence intervals
“Conditional”: Conditional confidence intervals
“C-F”: Conditional FLCI hybrid
“C-LF”: Conditional least-favorable hybrid
Default is “FLCI” if no restrictions, “C-F” otherwise.
- m_vec
numpy.ndarray, optional Vector of M values for sensitivity analysis. If None, constructs default sequence from 0 to data-driven upper bound.
- l_vec
numpy.ndarray, optional Vector of weights for parameter of interest. Default is first post-period effect.
- monotonicity_direction
str, optional Direction of monotonicity restriction: “increasing” or “decreasing”.
- bias_direction
str, optional Direction of bias restriction: “positive” or “negative”.
- alpha
float, default=0.05 Significance level.
- grid_points
int, default=1000 Number of grid points for conditional methods.
- grid_lb
float, optional Lower bound for grid search. If None, uses data-driven bound.
- grid_ub
float, optional Upper bound for grid search. If None, uses data-driven bound.
- betahat
- Returns:
polars.DataFrameDataFrame with columns: lb, ub, method, Delta, M.
Notes
Cannot specify both monotonicity_direction and bias_direction.
References
[1]Rambachan, A., & Roth, J. (2023). A More Credible Approach to Parallel Trends. Review of Economic Studies.
Examples
To use this function directly, we need to compute an event study and extract the estimates and covariance matrix. If you’re using moderndid’s built-in estimators, you can use the
honest_didfunction to process the event study and extract the estimates and covariance matrix for you.If you’re using an external estimator, you will need to extract the influence functions and construct the covariance matrix. Then, you can use the
create_sensitivity_results_smfunction to run the sensitivity analysis.In [1]: import numpy as np ...: from moderndid import att_gt, aggte, load_mpdta ...: from moderndid.didhonest import create_sensitivity_results_sm ...: df = load_mpdta() ...: gt_result = att_gt( ...: data=df, ...: yname="lemp", ...: tname="year", ...: gname="first.treat", ...: idname="countyreal", ...: est_method="dr", ...: boot=False ...: ) ...: es_result = aggte(gt_result, type="dynamic") ...:
Suppose this is an external estimator. We can extract the influence functions and construct the covariance matrix, removing the reference period.
In [2]: influence_func = es_result.influence_func ...: event_times = es_result.event_times ...: ref_idx = np.where(event_times == -1)[0][0] ...: att_no_ref = np.delete(es_result.att_by_event, ref_idx) ...: influence_no_ref = np.delete(influence_func, ref_idx, axis=1) ...: n = influence_no_ref.shape[0] ...: vcov = influence_no_ref.T @ influence_no_ref / (n * n) ...: num_pre = int(np.sum(np.delete(event_times, ref_idx) < -1)) ...: num_post = len(att_no_ref) - num_pre ...:
Finally, we run the smoothness-based sensitivity analysis with different values of \(M\) bounding how much the trend can change between periods.
In [3]: results = create_sensitivity_results_sm( ...: betahat=att_no_ref, ...: sigma=vcov, ...: num_pre_periods=num_pre, ...: num_post_periods=num_post, ...: m_vec=[0.0, 0.01, 0.02] ...: ) ...: results ...: Out[3]: shape: (3, 5) ┌───────────┬──────────┬────────┬─────────┬──────┐ │ lb ┆ ub ┆ method ┆ delta ┆ m │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ str ┆ str ┆ f64 │ ╞═══════════╪══════════╪════════╪═════════╪══════╡ │ -0.036708 ┆ 0.017203 ┆ FLCI ┆ DeltaSD ┆ 0.0 │ │ -0.047947 ┆ 0.023344 ┆ FLCI ┆ DeltaSD ┆ 0.01 │ │ -0.063082 ┆ 0.032342 ┆ FLCI ┆ DeltaSD ┆ 0.02 │ └───────────┴──────────┴────────┴─────────┴──────┘