Changelog
All notable changes to this project are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.3.3] — 2026-08-12
Changed
jarque_bera_testnow computes skewness/kurtosis viaStatsBase.skewness/StatsBase.kurtosisinstead of hand-rolled moment formulas (identical result;StatsBase.kurtosisis excess kurtosis, so the- 3.0term moved into the library call).- The three residual-standardization plot recipes (
:residuals,:histogram,:qq) now useStatsBase.zscoreinstead of a repeated(x .- mean(x)) ./ std(x)expression. StatsAPI.nobs(model; count_groups=true)removed — it duplicated the existingngroups(model)accessor. Usengroups(model)for the number of groups;nobs(model)now only returns total observations.
[0.3.2] — 2026-07-15
Added
- Regression tests for three previously-uncovered defensive branches, found via line-coverage inspection rather than code reading alone:
build_instruments's abstract-model fallback (must raise an informative error, not a genericMethodError),ar_test's degeneratetotal_variance <= 0guard (all-zero residuals), andwald_test's near-singularR*V*R'regularization branch.
Fixed
estimatenow rejects collinear regressors (rank(X) < n_regressors) with a clear error naming the affected columns, instead of failing deep inside the GMM solver with an opaqueLinearAlgebra.SingularException. Found via adversarial testing (e.g. a regressor and an exact rescaling of it), not previously covered by any test.examples/example.jl's diagnostics comment mischaracterized the AR(1)/AR(2) results: AR(1) is expected to be significant after differencing (not a misspecification signal, per the Getting Started guide); only AR(2) significance would indicate misspecification. Reworded for precision after running the example against live data and checking the actual output.
Performance
calculate_clustered_weight_matrixand_ab_h_weight_matrix's per-cluster accumulation loops used non-mutatingA += ..., reallocating the fulln_instruments × n_instrumentsmatrix on every individual/cluster instead of accumulating in place; changed toA .+= ...(identical result, no behavior change). Benchmarked on simulated panels: roughly 20-28% less memory allocated byfitat N=20,000-50,000 (e.g. two-stepDifferenceGMMat N=50,000, T=10: ~4.08 GB → ~2.97 GB), confirming the README's large-N performance claim under an actual adversarial/stress-test pass rather than just reading the sparse-matrix code.
[0.3.1] — 2026-07-14
Added
lagdocumented indocs/src/api.md(it was exported but missing from the API reference).- Behavioral regression tests:
collapse/max_lagsare now verified to actually changefit's returned coefficients/vcov (not just the intermediate instrument-matrix column count), andSystemGMM(windmeijer=false)is now verified to skip the correction the same way the existingDifferenceGMMcase does.
Changed
DifferenceGMM/SystemGMMare now generated from a shared@dynpanel_specmacro to remove duplicated boilerplate between the two model spec structs; fields and behavior are unchanged.
Fixed
DifferenceGMM's one-step weighting matrix used plain(Z'Z)^-1instead of the Arellano-Bond (1991)(Z'HZ)^-1(Hreflecting the MA(1) structure of differenced homoskedastic errors). Coefficients were only mildly affected, but the one-step Sargan/Hansen J-statistic was biased downward by roughly a factor of the number of groups. Validated against the EmplUK dataset from the original paper (Table 4): one-step Sargan chi2(25) now ≈70.9 vs. the paper's 65.8, versus ≈0.5 before the fix.- The new
DifferenceGMMweighting-matrix code re-densified the sparse instrument matrix once per individual instead of once upfront, and calledMatrix()twice on the same slice; fixed to match the existing densify-once pattern already used elsewhere in the solver. The cluster-boundary-walking loop this needed was also duplicated three times across the file; extracted into a shared, memoized_cluster_rangeshelper (results threaded via arangeskeyword so callers don't recompute them). calculate_clustered_weight_matrix/calculate_windmeijer_correctionnow guard that an explicitly passedrangeskeyword argument is actually consumed, instead of silently ignoring it.- Added a genuine (non-tautological) Windmeijer correction regression test against a hand-computed expected value, replacing a check that only verified internal wiring.
_lag_vector's(id, time) => rowindex map was rebuilt from scratch for every regressor column inar_test(once per column, all withAny-typed Dict keys causing boxing on every insert/lookup); it is now built once perar_testcall, concretely typed oneltype(id)/eltype(time), and reused across all columns._solve_gmmdensified the instrument matrixZunconditionally, even on the one-step, non-robust path where the densified copy is never used (calculate_clustered_weight_matrixis only called whensteps == 2orrobust); it is now only densified when actually needed.
[0.3.0] — 2026-07-08
Added
- Difference GMM (Arellano-Bond, 1991) and System GMM (Blundell-Bond, 1998) estimators, plus Anderson-Hsiao (1981) as a baseline IV estimator; one-step and two-step estimation with cluster-robust and Windmeijer (2005) finite-sample corrected standard errors.
- Forward orthogonal deviations (
transform=:fod, Arellano-Bover) for Difference GMM, as an alternative to first differencing (better for unbalanced panels). exogkeyword: strictly exogenous regressors are used as their own ("IV-style") GMM instruments (Roodman, 2009). A lag of a strictly exogenous covariate may itself be exogenous; the dependent variable and its lags may not.time_effectskeyword: addsT-2exogenous period dummies to absorb common time shocks.xtabond2-style instrument lag limits (min_lag,max_lag) and automatic dropping of linearly dependent instrument columns (drop_collinear, on by default).- Richer formula syntax: whitelisted transforms (
log,log10,log2,exp,sqrt,abs) on either side and nested in lags (log(y) ~ lag(log(y)) + log(x)); explicit and ranged lags (lag(x, 2),lag(x, 0:1)). - StatsModels
@formulasupport (formula = @formula(y ~ lag(y) + x)) in addition to the string form, plus thelagformula marker. - Tables.jl input:
fit/get_diff_dataaccept any Tables-compatible source, not onlyDataFrame. - Diagnostics: Sargan/Hansen J-test, Arellano-Bond AR(1)/AR(2) tests (with cached results, so
ar_test(model, order)works afterdiagnose), Wald test, Jarque-Bera test, pseudo-R², instrument-proliferation check, anddiff_hansen_test(a Durbin-Wu-Hausman-style difference-in-Hansen test for a nested instrument subset). - StatsAPI
RegressionModelinterface (coef,vcov,stderror,confint, …), Stata-like result printing, and diagnostic plot recipes. - Aqua.jl quality tests; ~99% line coverage.
- Package infrastructure: GitHub Actions CI (+ CompatHelper, TagBot), Documenter.jl docs,
.JuliaFormatter.toml,CHANGELOG.md,CONTRIBUTING.md, and integration tests against a simulated panel with known parameters.
[Unreleased]: https://github.com/MichalS16/DynamicPanelModels.jl/compare/v0.3.3...HEAD [0.3.3]: https://github.com/MichalS16/DynamicPanelModels.jl/compare/v0.3.2...v0.3.3 [0.3.2]: https://github.com/MichalS16/DynamicPanelModels.jl/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/MichalS16/DynamicPanelModels.jl/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/MichalS16/DynamicPanelModels.jl/releases/tag/v0.3.0