analyze_convergence_clubs

source

analyze_convergence_clubs(
    df,
    var,
    *,
    entity=None,
    time=None,
    filter='hp',
    hp_lambda=400.0,
    r=0.3,
    method='adjust',
    merge='iterative',
    cr=0.0,
    incr=0.05,
    max_cr=50.0,
    fr=0.0,
    tcrit=_TCRIT,
    title=None,
)

Phillips-Sul log(t) convergence test and data-driven club clustering for a panel.

Runs the full club-convergence workflow on one variable: optionally smooth each unit’s series with the Hodrick-Prescott filter (lambda = 400 for annual data); form the relative transition path h_it = x_it / mean_i(x_it); run the log(t) regression test for the whole panel; and, when global convergence is rejected, apply the clustering algorithm to split the units into convergence clubs, then merge adjacent clubs that jointly converge. This is the descriptive question “do these units form one converging group, several catch-up clubs, or none?”.

The variable is used as supplied — no log is taken — so for the canonical income case pass log GDP per capita (or log labor productivity). The panel must be balanced (every unit present in every period) because the HP filter needs a gap-free series.

Parameters

Name Type Description Default
df pd.DataFrame Balanced panel data frame. required
var str Numeric variable to analyse (e.g. "log_gdppc"). Used as supplied. required
entity str | None Panel identifiers. Default to those declared via :func:expdpy.set_panel. None
time str | None Panel identifiers. Default to those declared via :func:expdpy.set_panel. None
filter Literal['hp'] | None "hp" (default) applies the Hodrick-Prescott filter per unit and analyses the trend; None analyses the variable as given (already detrended). 'hp'
hp_lambda float HP smoothing parameter (400 for annual data, the convergence-literature default). 400.0
r float Initiating sample fraction for the log(t) regression: the first round(r*T) periods are discarded. Phillips-Sul recommend 0.3 for small/moderate T and 0.2 for large T. 0.3
method Literal['adjust', 'ps'] Within-club sieve: "adjust" (default) is the Schnurbus et al. (2016) refinement (add the best candidate one at a time); "ps" is the original Phillips-Sul (2007) rule that raises the inclusion threshold cr by incr until the club converges. 'adjust'
merge Literal['iterative', 'single', 'none'] Adjacent-club merging after clustering: "iterative" (default) repeats until no clubs merge, "single" does one pass, "none" reports the raw clusters. 'iterative'
cr float Sieve threshold and (for method="ps") its increment and ceiling. 0.0
incr float Sieve threshold and (for method="ps") its increment and ceiling. 0.0
max_cr float Sieve threshold and (for method="ps") its increment and ceiling. 0.0
fr float Cross-section sort key: 0 (default) sorts by the last period; fr > 0 sorts by the mean of the last (1 - fr) fraction of periods (for noisy endpoints). 0.0
tcrit float One-sided convergence critical value for the t-statistic (-1.65, the 5% level). _TCRIT
title str | None Title for the headline figure. None

Returns

Name Type Description
ConvergenceClubsResult The tidy long df (entity, time, value = trend, relative = h_it, club); the within-club average figure fig; the all-paths figure fig_paths and the per-club small-multiples fig_clubs; the classification table gt / summary and the membership frame; the panel dimensions; the whole-panel global_beta / global_tstat and converged flag; and n_clubs / n_divergent. .interpret() describes how the panel splits into clubs.

Notes

The log(t) test regresses, for t = [rT] .. T,

.. math:: (H_1 / H_t) - 2 (t) = a + b t + _t,

where H_t = N^{-1} \sum_i (h_{it} - 1)^2 is the cross-sectional variance of the relative transition paths. Under the null of convergence b = 2\alpha \ge 0; a one-sided t_b > -1.65 fails to reject it. The standard error is the Phillips-Sul scalar long-run variance form \hat{\mathrm{var}}(b) = (X'X)^{-1}\hat\omega with \hat\omega an Andrews (1991) quadratic-spectral HAC of the residuals. The clustering sorts units by their final value, forms a core group by maximising t_b, sieves in the remaining units, and recurses on the residual; adjacent clubs are then merged when they jointly converge. This is a faithful port of the Stata psecta package (Du 2017); see Phillips & Sul (2007, 2009) and Schnurbus et al. (2016).

Examples

Basic — convergence clubs in log GDP per capita across countries. The panel must be balanced; set_labels(..., set_panel=True) declares (entity, time) so the call omits entity= / time= and the figures get readable labels:

import expdpy as ex
from expdpy.data import load_productivity, load_productivity_data_def

df = ex.set_labels(
    load_productivity(), load_productivity_data_def(), set_panel=True
)
res = ex.analyze_convergence_clubs(df, "log_gdppc")
res.fig            # within-club average transition paths
res.gt             # club classification table
res.n_clubs, res.converged
print(res.interpret())
Across 108 units over 25 periods, the Phillips-Sul log(t) test for **log_gdppc** rejects global convergence (t = -17.7 <= -1.65). The clustering algorithm splits the panel into **4 convergence clubs** — groups that each converge internally but not with one another: Club 1 (80), Club 2 (16), Club 3 (6), Club 4 (5).
1 unit do not join any club (the divergent group).
Club 1 collects the highest-ranked units; within each club the log(t) slope b = 2*alpha is positive enough that its t-statistic clears -1.65.

_These are associations, not causal effects. A causal reading needs a research design — see `explain('correlation_vs_causation')`._