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:
betahatnumpy.ndarray

Estimated event study coefficients. Should have length num_pre_periods + num_post_periods.

sigmanumpy.ndarray

Covariance matrix of betahat. Should be (num_pre_periods + num_post_periods) x (num_pre_periods + num_post_periods).

num_pre_periodsint

Number of pre-treatment periods.

num_post_periodsint

Number of post-treatment periods.

methodstr, 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_vecnumpy.ndarray, optional

Vector of M values for sensitivity analysis. If None, constructs default sequence from 0 to data-driven upper bound.

l_vecnumpy.ndarray, optional

Vector of weights for parameter of interest. Default is first post-period effect.

monotonicity_directionstr, optional

Direction of monotonicity restriction: “increasing” or “decreasing”.

bias_directionstr, optional

Direction of bias restriction: “positive” or “negative”.

alphafloat, default=0.05

Significance level.

grid_pointsint, default=1000

Number of grid points for conditional methods.

grid_lbfloat, optional

Lower bound for grid search. If None, uses data-driven bound.

grid_ubfloat, optional

Upper bound for grid search. If None, uses data-driven bound.

Returns:
polars.DataFrame

DataFrame 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_did function 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_sm function 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 │
└───────────┴──────────┴────────┴─────────┴──────┘