Trend Diagnostics Module (trend_diagnostics)

The trend diagnostics module provides tools for assessing the parallel trends assumption and detecting heterogeneous trends across treatment cohorts in difference-in-differences settings.

Overview

This module supports three main diagnostic workflows:

  1. Parallel trends testing: Assess whether treated and control groups exhibit similar outcome trends prior to treatment.

  2. Heterogeneous trends diagnosis: Detect cohort-specific linear trends that may violate the standard parallel trends assumption.

  3. Transformation recommendation: Provide data-driven guidance on whether to use demeaning or detrending based on diagnostic results.

The conditional heterogeneous trends (CHT) framework from Lee and Wooldridge (2025) allows each treatment cohort to have its own linear trend, relaxing the standard parallel trends assumption. When CHT holds but parallel trends fails, detrending removes cohort-specific linear trends and restores consistency.

Testing Functions

recommend_transformation

lwdid.trend_diagnostics.recommend_transformation(data, y, ivar, tvar, gvar=None, controls=None, never_treated_values=None, run_all_diagnostics=True, verbose=True)[source]

Recommend optimal transformation method based on data characteristics.

Combines multiple diagnostic procedures to provide an informed recommendation on whether to use demean, detrend, or seasonal variants.

Parameters:
  • data (pd.DataFrame) – Panel data in long format.

  • y (str) – Outcome variable column name.

  • ivar (str) – Unit identifier column name.

  • tvar (str) – Time variable column name.

  • gvar (str, optional) – Cohort variable for staggered designs.

  • controls (list of str, optional) – Control variable column names.

  • never_treated_values (list, optional) – Values indicating never-treated units.

  • run_all_diagnostics (bool, default True) – Whether to run all diagnostic tests. If False, uses heuristics only.

  • verbose (bool, default True) – Whether to print summary.

Returns:

Recommendation with confidence level and supporting diagnostics.

Return type:

TransformationRecommendation

Notes

The recommendation algorithm considers:

  1. Data requirements: detrend requires at least 2 pre-treatment periods

  2. Parallel trends test: If violated, recommend detrend

  3. Trend heterogeneity: If detected, recommend detrend

  4. Panel balance: Unbalanced panels favor detrend

  5. Seasonal patterns: If detected, recommend seasonal variants

Decision tree:

n_pre_periods < 2?
|-- Yes -> demean (detrend not feasible)
+-- No -> Run diagnostics
        |-- PT violated OR heterogeneous trends?
        |   |-- Yes -> detrend
        |   +-- No -> demean (more efficient)
        +-- Seasonal patterns?
            |-- Yes -> demeanq/detrendq
            +-- No -> demean/detrend

See also

test_parallel_trends

Test parallel trends assumption.

diagnose_heterogeneous_trends

Diagnose trend heterogeneity.

Visualization

Enumerations

TrendTestMethod

class lwdid.trend_diagnostics.TrendTestMethod(value)[source]

Method for testing the parallel trends assumption.

Variables:
  • VISUAL (str) – Visual inspection of pre-treatment trajectories.

  • REGRESSION (str) – Formal regression-based test for trend differences.

  • PLACEBO (str) – Estimate pre-treatment ATTs using rolling transformation.

  • JOINT (str) – Combine placebo and regression tests.

VISUAL = 'visual'
REGRESSION = 'regression'
PLACEBO = 'placebo'
JOINT = 'joint'

TransformationMethod

class lwdid.trend_diagnostics.TransformationMethod(value)[source]

Unit-specific transformation methods for panel data.

Variables:
  • DEMEAN (str) – Subtract pre-treatment mean from post-treatment outcomes.

  • DETREND (str) – Remove unit-specific linear trend using pre-treatment periods.

  • DEMEANQ (str) – Quarterly demeaning with seasonal fixed effects.

  • DETRENDQ (str) – Quarterly detrending with seasonal effects and trends.

DEMEAN = 'demean'
DETREND = 'detrend'
DEMEANQ = 'demeanq'
DETRENDQ = 'detrendq'

RecommendationConfidence

class lwdid.trend_diagnostics.RecommendationConfidence(value)[source]

Confidence level for transformation method recommendation.

Variables:
  • HIGH (str) – Confidence score above 0.8.

  • MEDIUM (str) – Confidence score between 0.5 and 0.8.

  • LOW (str) – Confidence score below 0.5.

HIGH = 'high'
MEDIUM = 'medium'
LOW = 'low'

Result Classes

ParallelTrendsTestResult

class lwdid.trend_diagnostics.ParallelTrendsTestResult(method, reject_null, pvalue, test_statistic, pre_trend_estimates, joint_f_stat=None, joint_pvalue=None, joint_df=(0, 0), recommendation='demean', recommendation_reason='', figure=None, warnings=<factory>)[source]

Results from testing the parallel trends assumption.

Aggregates pre-treatment ATT estimates and joint test statistics to assess whether the parallel trends assumption is likely to hold. Includes a method recommendation based on the test outcome.

Variables:
  • method (TrendTestMethod) – Testing method used (placebo, regression, visual, or joint).

  • reject_null (bool) – Whether to reject H0 that parallel trends holds.

  • pvalue (float) – P-value for the overall test.

  • test_statistic (float) – Test statistic value (F-statistic for joint test).

  • pre_trend_estimates (list of PreTrendEstimate) – Pre-treatment ATT estimates by event time.

  • joint_f_stat (float or None) – F-statistic for the joint test H0: all pre-ATT = 0.

  • joint_pvalue (float or None) – P-value for the joint F-test.

  • joint_df (tuple of int) – Degrees of freedom (numerator, denominator) for the F-test.

  • recommendation (str) – Recommended transformation method based on test results.

  • recommendation_reason (str) – Explanation for the recommendation.

  • figure (Any or None) – Pre-trends visualization figure object.

  • warnings (list of str) – Warning messages from the testing procedure.

method: TrendTestMethod
reject_null: bool
pvalue: float
test_statistic: float
pre_trend_estimates: list[PreTrendEstimate]
joint_f_stat: float | None = None
joint_pvalue: float | None = None
joint_df: tuple[int, int] = (0, 0)
recommendation: str = 'demean'
recommendation_reason: str = ''
figure: Any | None = None
warnings: list[str]

Number of significant pre-treatment estimates at 5%.

property max_abs_pre_att: float

Maximum absolute pre-treatment ATT.

summary()[source]

Generate human-readable summary.

Return type:

str

HeterogeneousTrendsDiagnostics

class lwdid.trend_diagnostics.HeterogeneousTrendsDiagnostics(trend_by_cohort, trend_heterogeneity_test, trend_differences, control_group_trend, has_heterogeneous_trends, recommendation, recommendation_confidence, recommendation_reason, figure=None)[source]

Diagnostic results for heterogeneous trends across cohorts.

Tests whether different treatment cohorts have different pre-treatment linear trends. Significant heterogeneity violates the standard parallel trends assumption but may be accommodated by detrending to remove cohort-specific trends under the CHT framework.

Variables:
  • trend_by_cohort (list of CohortTrendEstimate) – Estimated linear trends for each treatment cohort.

  • trend_heterogeneity_test (dict) – Results of the F-test for overall trend heterogeneity. Keys: ‘f_stat’, ‘pvalue’, ‘df_num’, ‘df_den’, ‘reject_null’.

  • trend_differences (list of TrendDifference) – Pairwise trend differences between all cohort pairs.

  • control_group_trend (CohortTrendEstimate or None) – Trend estimate for the never-treated control group.

  • has_heterogeneous_trends (bool) – Whether significant trend heterogeneity is detected.

  • recommendation (str) – Recommended transformation method based on diagnostics.

  • recommendation_confidence (float) – Confidence score for the recommendation (0 to 1).

  • recommendation_reason (str) – Explanation for the recommendation.

  • figure (Any or None) – Trend comparison visualization figure object.

trend_by_cohort: list[CohortTrendEstimate]
trend_heterogeneity_test: dict[str, float]
trend_differences: list[TrendDifference]
control_group_trend: CohortTrendEstimate | None
recommendation: str
recommendation_confidence: float
recommendation_reason: str
figure: Any | None = None
property n_cohorts: int

Number of treatment cohorts analyzed.

property n_significant_differences: int

Number of significant pairwise trend differences.

property max_trend_difference: float

Maximum absolute trend difference.

summary()[source]

Generate human-readable summary.

Return type:

str

TransformationRecommendation

class lwdid.trend_diagnostics.TransformationRecommendation(recommended_method, confidence, confidence_level, reasons, parallel_trends_test=None, heterogeneous_trends_diag=None, n_pre_periods_min=0, n_pre_periods_max=0, has_seasonal_pattern=False, is_balanced_panel=True, alternative_method=None, alternative_reason=None, warnings=<factory>)[source]

Comprehensive recommendation for transformation method selection.

Combines parallel trends test results, heterogeneous trends diagnostics, and data characteristics to provide an informed recommendation on whether to use demean, detrend, or their seasonal variants.

Variables:
  • recommended_method (str) – Primary recommendation: ‘demean’, ‘detrend’, ‘demeanq’, or ‘detrendq’.

  • confidence (float) – Confidence score for the recommendation (0 to 1).

  • confidence_level (RecommendationConfidence) – Categorical confidence level (HIGH, MEDIUM, or LOW).

  • reasons (list of str) – List of reasons supporting the recommendation.

  • parallel_trends_test (ParallelTrendsTestResult or None) – Results from the parallel trends test, if performed.

  • heterogeneous_trends_diag (HeterogeneousTrendsDiagnostics or None) – Results from heterogeneous trends diagnostics, if performed.

  • n_pre_periods_min (int) – Minimum number of pre-treatment periods across cohorts.

  • n_pre_periods_max (int) – Maximum number of pre-treatment periods across cohorts.

  • has_seasonal_pattern (bool) – Whether seasonal patterns are detected in the outcome.

  • is_balanced_panel (bool) – Whether the panel is balanced.

  • alternative_method (str or None) – Alternative recommendation if primary is not suitable.

  • alternative_reason (str or None) – Explanation for the alternative recommendation.

  • warnings (list of str) – Warning messages about data limitations or method constraints.

recommended_method: str
confidence: float
confidence_level: RecommendationConfidence
reasons: list[str]
n_pre_periods_min: int = 0
n_pre_periods_max: int = 0
has_seasonal_pattern: bool = False
is_balanced_panel: bool = True
alternative_method: str | None = None
alternative_reason: str | None = None
warnings: list[str]
summary()[source]

Generate human-readable summary.

Return type:

str

Supporting Data Classes

PreTrendEstimate

class lwdid.trend_diagnostics.PreTrendEstimate(event_time, cohort, att, se, t_stat, pvalue, ci_lower, ci_upper, n_treated, n_control, df=0)[source]

Pre-treatment ATT estimate for a single event time.

Stores the estimated treatment effect for a pre-treatment period, used for placebo tests and parallel trends assessment. Under the null hypothesis of parallel trends, these estimates should be statistically indistinguishable from zero.

Variables:
  • event_time (int) – Event time relative to treatment onset (negative for pre-treatment).

  • cohort (int or None) – Treatment cohort identifier for staggered adoption designs.

  • att (float) – Estimated average treatment effect on the treated.

  • se (float) – Standard error of the ATT estimate.

  • t_stat (float) – t-statistic computed as att / se.

  • pvalue (float) – Two-sided p-value for testing H0: ATT = 0.

  • ci_lower (float) – Lower bound of the confidence interval.

  • ci_upper (float) – Upper bound of the confidence interval.

  • n_treated (int) – Number of treated units in the estimation sample.

  • n_control (int) – Number of control units in the estimation sample.

  • df (int) – Degrees of freedom for t-distribution inference.

event_time: int
cohort: int | None
att: float
se: float
t_stat: float
pvalue: float
ci_lower: float
ci_upper: float
n_treated: int
n_control: int
df: int = 0
property is_significant_05: bool

Whether estimate is significant at 5% level.

property is_significant_10: bool

Whether estimate is significant at 10% level.

CohortTrendEstimate

class lwdid.trend_diagnostics.CohortTrendEstimate(cohort, intercept, intercept_se, slope, slope_se, slope_pvalue, n_units, n_pre_periods, r_squared, residual_std=0.0)[source]

Estimated linear trend for a single treatment cohort.

Stores the pre-treatment linear trend estimate for a cohort, obtained by regressing outcomes on time for pre-treatment periods. Significant slopes indicate the presence of cohort-specific trends.

Variables:
  • cohort (int) – Treatment cohort identifier (first treatment period).

  • intercept (float) – Estimated intercept of the trend regression.

  • intercept_se (float) – Standard error of the intercept estimate.

  • slope (float) – Estimated slope representing the linear time trend.

  • slope_se (float) – Standard error of the slope estimate.

  • slope_pvalue (float) – Two-sided p-value for testing H0: slope = 0.

  • n_units (int) – Number of units in this cohort.

  • n_pre_periods (int) – Number of pre-treatment periods used in estimation.

  • r_squared (float) – Coefficient of determination for the trend regression.

  • residual_std (float) – Standard deviation of regression residuals.

cohort: int
intercept: float
intercept_se: float
slope: float
slope_se: float
slope_pvalue: float
n_units: int
n_pre_periods: int
r_squared: float
residual_std: float = 0.0
property has_significant_trend: bool

Whether cohort has significant linear trend.

TrendDifference

class lwdid.trend_diagnostics.TrendDifference(cohort_1, cohort_2, slope_1, slope_2, slope_diff, slope_diff_se, t_stat, pvalue, df)[source]

Pairwise difference in linear trends between two cohorts.

Stores the result of testing whether two cohorts have equal pre-treatment trends. Under the parallel trends assumption, all pairwise differences should be statistically indistinguishable from zero.

Variables:
  • cohort_1 (int) – First cohort identifier.

  • cohort_2 (int) – Second cohort identifier.

  • slope_1 (float) – Estimated slope for the first cohort.

  • slope_2 (float) – Estimated slope for the second cohort.

  • slope_diff (float) – Difference in slopes (slope_1 - slope_2).

  • slope_diff_se (float) – Standard error of the slope difference.

  • t_stat (float) – t-statistic for testing equal slopes.

  • pvalue (float) – Two-sided p-value for H0: slope_1 = slope_2.

  • df (int) – Degrees of freedom for the test.

cohort_1: int
cohort_2: int
slope_1: float
slope_2: float
slope_diff: float
slope_diff_se: float
t_stat: float
pvalue: float
df: int
property significant_at_05: bool

Whether difference is significant at 5% level.

Usage Examples

Getting Transformation Recommendation

# Get automated transformation recommendation
rec = lwdid.recommend_transformation(
    data=df,
    y='outcome',
    ivar='unit_id',
    tvar='year',
    gvar='first_treat',
    verbose=True
)

print(f"Recommended method: {rec.recommended_method}")
print(f"Confidence: {rec.confidence:.1%}")
for reason in rec.reasons:
    print(f"  - {reason}")

Methodological Background

The parallel trends assumption requires that, in the absence of treatment, treated and control units would have followed similar outcome trajectories. Formally, for all post-treatment periods \(t \geq S\):

\[E[Y_t(0) - Y_1(0) | D = 1] = E[Y_t(0) - Y_1(0) | D = 0]\]

When this assumption fails but the conditional heterogeneous trends (CHT) assumption holds, detrending can restore identification. Under CHT, each cohort \(g\) may have its own linear trend \(\eta_g\), but the detrended outcomes satisfy:

\[E[\ddot{Y}_{ir}(\infty) | \mathbf{D}, \mathbf{X}] = E[\ddot{Y}_{ir}(\infty) | \mathbf{X}]\]

where \(\ddot{Y}_{ir}\) denotes the detrended outcome.

Decision Framework

The recommended workflow for transformation selection:

  1. Run test_parallel_trends() with method='placebo'

  2. If parallel trends not rejected: use rolling='demean'

  3. If parallel trends rejected: run diagnose_heterogeneous_trends()

  4. If heterogeneous trends detected: use rolling='detrend'

  5. Use recommend_transformation() for automated guidance