moderndid.create_sensitivity_results_rm#

moderndid.create_sensitivity_results_rm(betahat, sigma, num_pre_periods, num_post_periods, bound='deviation from parallel trends', method='C-LF', m_bar_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 relative magnitude bounds.

Implements methods for robust inference using the relative magnitudes restriction \(\Delta^{RM}(\bar{M})\), following [1]. This restriction bounds post-treatment violations of parallel trends by \(\bar{M}\) times the maximum pre-treatment violation, formalizing the intuition that confounding factors in the post-treatment period should be similar in magnitude to those observed pre-treatment. When \(\bar{M} = 1\), the worst-case post-treatment violation is bounded by the maximum pre-treatment violation. This function computes confidence intervals across a range of \(\bar{M}\) values, facilitating sensitivity analysis.

Parameters:
betahatnumpy.ndarray

Estimated event study coefficients.

sigmanumpy.ndarray

Covariance matrix of betahat.

num_pre_periodsint

Number of pre-treatment periods.

num_post_periodsint

Number of post-treatment periods.

boundstr, default=”deviation from parallel trends”

Type of bound:

  • “Deviation from parallel trends”: \(\Delta^{RM}\) and variants

  • “Deviation from linear trend”: \(\Delta^{SDRM}\) and variants

methodstr, default=”C-LF”

Confidence interval method: “Conditional” or “C-LF”.

m_bar_vecnumpy.ndarray, optional

Vector of \(\bar{M}\) values. Default is 10 values from 0 to 2.

l_vecnumpy.ndarray, optional

Vector of weights for parameter of interest.

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.

grid_ubfloat, optional

Upper bound for grid search.

Returns:
polars.DataFrame

DataFrame with columns: lb, ub, method, Delta, Mbar.

Notes

Deviation from linear trend requires at least 3 pre-treatment periods.

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_rm 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_rm
   ...: 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 sensitivity analysis with different values of \(\bar{M}\) bounding how large post-treatment violations can be relative to pre-treatment violations.

In [3]: results = create_sensitivity_results_rm(
   ...:     betahat=att_no_ref,
   ...:     sigma=vcov,
   ...:     num_pre_periods=num_pre,
   ...:     num_post_periods=num_post,
   ...:     m_bar_vec=[0.0, 0.5, 1.0]
   ...: )
   ...: results
   ...: 
Out[3]: 
shape: (3, 5)
┌───────────┬──────────┬────────┬─────────┬──────┐
│ lb        ┆ ub       ┆ method ┆ delta   ┆ Mbar │
│ ---       ┆ ---      ┆ ---    ┆ ---     ┆ ---  │
│ f64       ┆ f64      ┆ str    ┆ str     ┆ f64  │
╞═══════════╪══════════╪════════╪═════════╪══════╡
│ -0.042424 ┆ 0.002561 ┆ C-LF   ┆ DeltaRM ┆ 0.0  │
│ -0.045739 ┆ 0.006349 ┆ C-LF   ┆ DeltaRM ┆ 0.5  │
│ -0.051421 ┆ 0.012505 ┆ C-LF   ┆ DeltaRM ┆ 1.0  │
└───────────┴──────────┴────────┴─────────┴──────┘