analyze_beta_convergence
analyze_beta_convergence(
df,
var,
controls=None,
*,
entity=None,
time=None,
start=None,
end=None,
model='ols',
gdf=None,
w=None,
fixed_effects=None,
vcov='hetero',
n_draws=10000,
seed=20250620,
min_obs=10,
title=None,
)β-convergence of a panel variable, from plain OLS to the spatial Durbin model.
Builds the growth cross-section (:func:growth_cross_section) over a common window and regresses each unit’s annualized log growth on its initial log level (plus initial-period controls and fixed_effects dummies). A negative slope β is convergence: units that start lower grow faster. The slope maps to a speed λ = -ln(1 + β·T)/T and a half-life ln 2 / λ.
With a spatial model the cross-section is first aligned to the entity geometry gdf (and the weights w) so rows, polygons and weights always match, and the regression is estimated with :mod:spreg: "sar" (ML spatial lag), "sem" (ML spatial error), "slx" (spatially lagged regressors) or "sdm" (spatial Durbin). The initial-level term is decomposed into direct, indirect (spillover) and total impacts with Monte-Carlo standard errors from n_draws draws (the impacts table covers every regressor); speed and half-life derive from the total impact. An OLS baseline on the same sample is always reported alongside.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| df | pd.DataFrame | Long panel data frame. | required |
| var | str | Numeric, strictly positive variable in levels (the log is taken internally; growth is the annualized log-difference). | required |
| controls | Sequence[str] | str | None | Optional control name(s), entering at their initial-period values (conditional convergence). | None |
| entity | str | None | Panel identifiers. Default to those declared via :func:geometrics.set_panel. |
None |
| time | str | None | Panel identifiers. Default to those declared via :func:geometrics.set_panel. |
None |
| start | float | None | Growth window endpoints; default to the earliest and latest period. | None |
| end | float | None | Growth window endpoints; default to the earliest and latest period. | None |
| model | str | "ols" (default), "sar", "sem", "slx" or "sdm". |
'ols' |
| gdf | gpd.GeoDataFrame | None | Entity geometry (required for the spatial models; optional for OLS, where it only adds the growth map and restricts the sample to matched units). | None |
| w | W | None | libpysal weights aligned to the gdf ids. None builds the default weights (queen contiguity for polygons, 6-nearest-neighbor otherwise) with a :class:~geometrics.GeometricsWarning. |
None |
| fixed_effects | Sequence[str] | str | None | Optional categorical column name(s) (e.g. a state id) entered as dummy variables (first level dropped), valued at the initial period. | None |
| vcov | str | Standard errors of the OLS baseline: "hetero" (HC1, default) or "iid". The spatial models use their spreg (ML) covariance. |
'hetero' |
| n_draws | int | Monte-Carlo draws for the SAR/SDM impact standard errors. | 10000 |
| seed | int | Seed for the Monte-Carlo draws (reproducible). | 20250620 |
| min_obs | int | Minimum number of cross-section units required. | 10 |
| title | str | None | Title for the growth-vs-initial scatter. | None |
Returns
| Name | Type | Description |
|---|---|---|
| BetaConvergenceResult | The growth cross-section df; the scatter fig, the Frisch-Waugh-Lovell partial scatter fig_conditional (None without controls/fixed effects) and the growth choropleth fig_map (None without gdf); the estimate table gt / summary (OLS column always, an impact column for spatial models); the fitted models; the beta_direct / beta_indirect / beta_total triple with standard errors; rho / lam; speed and half_life; and the per-regressor impacts table for spatial models. |
Raises
| Name | Type | Description |
|---|---|---|
| KeyError | If var, a control or a fixed-effect column is missing from df. |
|
| TypeError | If var or a control is not numeric. |
|
| ValueError | For an unknown model / vcov, a spatial model without gdf, an empty or inverted window, non-positive values of var, too few units, or a zero-variance initial level. |
Examples
Unconditional convergence across 12 regions over one decade (β < 0 by construction — low-income regions grow faster):
import numpy as np
import pandas as pd
from geometrics.convergence import analyze_beta_convergence
ids = [f"r{i}" for i in range(12)]
y0 = np.linspace(1000.0, 12000.0, 12)
growth = 0.05 - 0.02 * np.log(y0) / np.log(y0).mean() # poorer -> faster
df = pd.concat(
[
pd.DataFrame({"region": ids, "year": 2000, "gdppc": y0}),
pd.DataFrame(
{"region": ids, "year": 2010, "gdppc": y0 * np.exp(10 * growth)}
),
]
)
res = analyze_beta_convergence(df, "gdppc", entity="region", time="year")
round(res.beta_total, 4), res.model