Metadata-Version: 2.4
Name: localproj
Version: 0.1.0
Summary: Composable local projections for Python
Keywords: econometrics,local projections,impulse responses,time series,panel data
Author: LocalProj contributors
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Dist: formulaic>=1.0
Requires-Dist: numpy>=1.26
Requires-Dist: pandas>=2.0
Requires-Dist: plotnine==0.15.3
Requires-Dist: pyfixest>=0.50.1
Requires-Dist: scipy>=1.11
Requires-Dist: arviz>=0.17 ; extra == 'bayes'
Requires-Dist: pytest>=8.0 ; extra == 'dev'
Requires-Dist: ruff>=0.8 ; extra == 'dev'
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/whoisnnamdi/localproj
Project-URL: Repository, https://github.com/whoisnnamdi/localproj
Project-URL: Issues, https://github.com/whoisnnamdi/localproj/issues
Project-URL: Changelog, https://github.com/whoisnnamdi/localproj/blob/main/CHANGELOG.md
Provides-Extra: bayes
Provides-Extra: dev
Description-Content-Type: text/markdown

# LocalProj

[![CI](https://github.com/whoisnnamdi/localproj/actions/workflows/ci.yml/badge.svg)](https://github.com/whoisnnamdi/localproj/actions/workflows/ci.yml)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](pyproject.toml)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

`LocalProj` is a Python library for estimating local projections (LPs). The current `0.1.0` implementation includes regular time-series LPs, LP-IV, high-dimensional desparsified-lasso LPs, panel fixed effects and split-panel jackknife estimates, Herbst–Johannsen finite-sample bias correction, smooth LPs, wild-bootstrap and simultaneous inference, and observed-shock Bayesian SU-LPs.

> **Project status:** `0.1.0` is an alpha release. The public API may evolve as the package gains broader production use.

## Installation

```bash
uv add localproj
```

Plotting is included in the standard installation. Bayesian diagnostics are available as an extra:

```bash
uv add "localproj[bayes]"
```

## Quick start

```python
import localproj as lp

fit = lp.lp(
    "y ~ shock + l(y, 1:4) + l(shock, 1:4)",
    data=df,
    shock="shock",
    horizons=range(0, 13),
    outcome="level",   # also: "cumulative" or "diff"
    sample="horizon", # or "common"
    vcov="hc1",
)

print(fit.tidy())
print(fit.coef())
print(fit.confint())
print(fit.summary())
```

The formula preprocessor supports `l(var, periods)` and `f(var, periods)` helpers. `horizons=12` means horizons `0..12`; an explicit iterable is also accepted. Regular LPs use HC1 covariance when `vcov` is omitted; HAC/Newey–West remains available explicitly with `vcov="hac"` or a fixed lag specification.

## Supported estimators and inference

| Area | Current support |
|---|---|
| Regular LP | Horizon-by-horizon PyFixest OLS; multiple outcomes and shocks |
| Outcome transforms | Level, cumulative/long difference, period difference, custom callable |
| Samples | Horizon-specific or common complete-case sample |
| Covariance | Regular LPs: IID, HC1–HC3, HAC/Newey-West, and PyFixest cluster specifications; smooth LPs also support HC0 |
| LP-IV | PyFixest/`feols`-style IV formulas and first-stage diagnostics |
| Panel LP | Group-aware leads/lags, fixed effects, clustered covariance |
| Bias correction | Split-panel jackknife for panels; Herbst–Johannsen analytical BC/BCC for observed-shock time-series LPs |
| Smooth LP | B-spline ridge smoothing, contiguous-fold CV, HC0/NW inference, multiple responses, and single-shock smooth IV |
| High-dimensional LP | Adamek–Smeekes–Wilms desparsified lasso with unpenalized shock coefficients, plug-in tuning, reused nodewise regressions, and Newey–West inference |
| Joint inference | Gaussian sup-t and Scheffé bands, plus cross-horizon Wald tests, for common-sample regular OLS LPs |
| Bootstrap | Wild residual bootstrap with pointwise and sup-t intervals for regular OLS LPs |
| Bayesian | Observed-shock SU-LP with hierarchical GP/Normal-Gamma shrinkage |
| Plotting | Plotnine IRF plots, selection, and outcome/shock faceting |

## Multiple outcomes and shocks

```python
fit = lp.lp(
    "gdp + cpi ~ mp_shock + fp_shock + l(gdp, 1:4) + l(cpi, 1:4)",
    data=df,
    shock=["mp_shock", "fp_shock"],
    horizons=range(0, 13),
)

fit.tidy()  # one row per (outcome, shock, horizon)
fit.coef(outcome="gdp", shock="mp_shock")
fit.plot(facet_by=["outcome", "shock"])
```

## Simultaneous and bootstrap inference

Regular LP simultaneous bands require an aligned common sample:

```python
joint_fit = lp.lp(
    "y ~ shock + l(y, 1:4) + l(shock, 1:4)",
    data=df,
    shock="shock",
    horizons=range(0, 13),
    sample="common",
    vcov="hc1",
)

joint_fit.confint(band="sup-t")
joint_fit.confint(band="scheffe")
joint_fit.wald(horizons=range(0, 13), null=0)
```

Joint inference supports IID and HC1–HC3 covariance directly. HAC joint inference requires an explicit common lag count, such as `vcov={"type": "hac", "lags": 4}`; an omitted HAC lag remains valid for pointwise fits but does not produce joint covariance because its defaults vary by horizon.

Wild-bootstrap inference is available for regular PyFixest OLS LPs:

```python
boot = joint_fit.bootstrap(reps=999, weights="rademacher", seed=123)
boot.confint(interval="percentile")
boot.confint(band="sup-t")
```

Bootstrap support currently excludes IV, fixed effects, panels, clustered/HAC covariance, SPJ, and smooth LPs. See [`docs/bootstrap_inference.md`](docs/bootstrap_inference.md).

## LP-IV and panel LPs

PyFixest/`feols`-style formula parts are passed through horizon by horizon:

```python
iv_fit = lp.lp(
    "y ~ controls | 0 | shock ~ instrument",
    data=df,
    shock="shock",
    horizons=range(0, 13),
)

iv_fit.iv_diagnostics()
```

Panel leads and lags are built within groups. Fixed effects and clustered covariance are delegated to PyFixest:

```python
panel_fit = lp.lp(
    "y ~ shock + l(y, 1:2) | unit + time",
    data=panel,
    groupby="unit",
    time="time",
    shock="shock",
    horizons=range(0, 9),
    vcov={"CRV1": "unit"},
)

spj_fit = lp.lp(
    "y ~ shock | unit",
    data=panel,
    groupby="unit",
    time="time",
    shock="shock",
    horizons=range(0, 9),
    estimator="spj",
    vcov={"CRV1": "unit"},
)
```

SPJ currently supports one-way fixed effects matching `groupby`; two-way FE and multiway-cluster SPJ are not yet implemented. See [`docs/iv_lp.md`](docs/iv_lp.md) and [`docs/panel_lp.md`](docs/panel_lp.md) for assumptions, diagnostics, sampling, and inference details.

## Herbst–Johannsen finite-sample correction

For a time-series LP with one directly observed iid shock and predetermined controls, use the recursive Herbst–Johannsen BCC estimator:

```python
hj_fit = lp.lp(
    "y ~ shock + l(y, 1:4)",
    data=df,
    shock="shock",
    horizons=range(0, 13),
    time="date",
    estimator="herbst_johannsen",  # `"hj"` also works
    vcov="hc1",
)
```

The default recursively plugs corrected lower-horizon estimates into the analytical bias expression (equation 11 in Herbst & Johannsen). Use `hj={"plugin": "ols"}` for their one-step BC estimator (equation 10). Horizons must be ordered, contiguous, and start at zero. The current implementation supports one observed shock, no IV/fixed effects/panels, and a contiguous complete time-series sample. It retains the original OLS covariance for first-order asymptotic inference; the correction can still increase finite-sample variance. Diagnostics and horizon-specific correction terms are in `hj_fit.metadata["bias_correction"]`. See [`docs/herbst_johannsen.md`](docs/herbst_johannsen.md).

## Smooth LPs

```python
smooth_fit = lp.lp(
    "y ~ shock + l(y, 1:4) + l(shock, 1:4)",
    data=df,
    shock="shock",
    horizons=range(0, 21),
    estimator="smooth",
    smooth={"lambda": "cv", "r": 2, "vcov": "nw"},
)
```

Smooth LPs support multiple outcomes and observed shocks. Smooth IV supports one outcome and one endogenous shock:

```python
smooth_iv = lp.lp(
    "y ~ l(y, 1:4) + l(shock, 1:4) | 0 | shock ~ instrument",
    data=df,
    shock="shock",
    horizons=range(0, 21),
    estimator="smooth",
    smooth={"lambda": "cv", "vcov": "nw"},
)
```

Fixed effects and grouped/panel smooth LPs remain unsupported. See [`docs/smooth_lp.md`](docs/smooth_lp.md) for the estimand, tuning options, uncertainty, and smooth-IV scope.

## High-dimensional desparsified-lasso LPs

Use `estimator="debiased_lasso"` (aliases: `"desparsified_lasso"` and `"hdlp"`) when the formula contains many controls, potentially more than observations:

```python
hdlp_fit = lp.lp(
    "y ~ shock + l(y, 1:12) + l(shock, 1:12) + "
    + " + ".join(macro_controls),
    data=df,
    shock="shock",
    horizons=range(0, 25),
    time="date",
    estimator="debiased_lasso",
    vcov="hac",
    hdlp={"plugin_constant": 0.8, "random_state": 123},
)
```

The named shock coefficients are left unpenalized. Other formula columns are selected with the iterative plug-in lasso. Nodewise regressions use the longest outcome-independent source sample with a valid RHS design; its scaling and desparsifying state are reused only when exact Origins, columns, and evaluated design values align. Inference uses Origin Order to construct the authors' Newey–West covariance with an Andrews data-driven bandwidth by default. Explicit non-HAC requests are rejected. A fixed maximum lag can be requested with `vcov={"type": "hac", "lags": 4}`; excessive lags are rejected rather than truncated, and automatic Andrews selection requires at least four observations and defined score innovations. Exact own-response impact is returned as the normalized coefficient `1` with zero covariance; normalized-only requests skip nuisance fitting entirely.

The current scope is observed-shock time-series LPs without IV, fixed effects, or panel grouping. Data are demeaned and standardized internally. `hdlp` diagnostics record selected controls, initial and nodewise penalties, nodewise reuse, and bandwidths. See [`docs/high_dimensional_lasso.md`](docs/high_dimensional_lasso.md).

## Bayesian SU-LP

```python
bayes_fit = lp.lp(
    "y ~ shock + l(y, 1:2)",
    data=df,
    shock="shock",
    horizons=range(0, 9),
    estimator="bayesian_sulp",
    sample="common",
    sulp={"draws": 1000, "burnin": 1000, "chains": 2, "seed": 123},
)

bayes_fit.tidy(level=0.9)
bayes_fit.plot(level=0.9, include_gp=True)
bayes_fit.posterior_draws
```

The Bayesian estimator currently supports one outcome, one or more observed shocks, simple additive RHS terms, and contiguous horizons starting at zero. IV/proxy measurement equations, panels, multiple outcomes, missing-lead imputation, and stochastic volatility are deferred. See [`docs/bayesian_sulp.md`](docs/bayesian_sulp.md).

## Documentation

- [`docs/api_design.md`](docs/api_design.md) — current API and proposed extensions
- [`docs/architecture.md`](docs/architecture.md) — current implementation and target architecture
- [`CONTEXT.md`](CONTEXT.md) — local-projection domain language
- [`docs/mvp_plan.md`](docs/mvp_plan.md) — implementation status and roadmap
- [`docs/technique_inventory.md`](docs/technique_inventory.md) — literature-driven method inventory
- [`docs/outcome_transform_examples.md`](docs/outcome_transform_examples.md) — outcome transformations
- [`docs/multiple_response_examples.md`](docs/multiple_response_examples.md) — multi-response plotting
- [`docs/bootstrap_inference.md`](docs/bootstrap_inference.md) — bootstrap workflow and scope
- [`docs/iv_lp.md`](docs/iv_lp.md) — instrumental-variable LP assumptions, diagnostics, and examples
- [`docs/panel_lp.md`](docs/panel_lp.md) — fixed-effect panel LP and split-panel jackknife workflows
- [`docs/herbst_johannsen.md`](docs/herbst_johannsen.md) — Herbst–Johannsen BC/BCC workflow and scope
- [`docs/smooth_lp.md`](docs/smooth_lp.md) — smooth observed-shock and smooth-IV workflows
- [`docs/bayesian_sulp.md`](docs/bayesian_sulp.md) — Bayesian SU-LP workflow and scope
- [`docs/high_dimensional_lasso.md`](docs/high_dimensional_lasso.md) — high-dimensional desparsified-lasso workflow and scope
- [`docs/replications/`](docs/replications/) — literature replications
- [`literature/`](literature/README.md) — literature references and selected figures
- [`CHANGELOG.md`](CHANGELOG.md) — notable changes by release

## License

The LocalProj source code and original documentation are available under the [MIT License](LICENSE).
Third-party datasets and published figures retain their original rights and are not covered by
this license; see [`literature/README.md`](literature/README.md).

## Development

```bash
uv run --extra dev --extra bayes pytest -q
uv run --extra dev ruff check .
uv build
```

The test suite covers the core estimator, formulas, Horizon Samples, high-dimensional lasso inference, panel/SPJ and Herbst–Johannsen estimation, smooth LPs, IV diagnostics, simultaneous bands, bootstrap inference, Bayesian SU-LP, plotting, and sample handling.
