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