|
| 1 | +# Migrating from R to StatsPAI |
| 2 | + |
| 3 | +A practical one-page map for researchers moving from R's causal inference |
| 4 | +ecosystem to StatsPAI. Every R function listed here has an independent |
| 5 | +Python re-implementation in StatsPAI, following the same statistical |
| 6 | +methodology but exposed through a unified `sp.*` API. |
| 7 | + |
| 8 | +```python |
| 9 | +import statspai as sp |
| 10 | +``` |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## Regression & Fixed Effects (`fixest`) |
| 15 | + |
| 16 | +| R (`fixest`) | StatsPAI | Notes | |
| 17 | +| ----------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------ | |
| 18 | +| `feols(y ~ x1 + x2, data = df)` | `sp.feols("y ~ x1 + x2", data=df)` | pyfixest-backed; same formula syntax | |
| 19 | +| `feols(y ~ x \| firm + year, data = df)` | `sp.feols("y ~ x \| firm + year", data=df)` | Two-way fixed effects | |
| 20 | +| `fepois(y ~ x \| firm, data = df)` | `sp.fepois("y ~ x \| firm", data=df)` | HDFE Poisson / PPML for gravity models | |
| 21 | +| `feglm(..., family = "binomial")` | `sp.feglm(..., family="binomial")` | HDFE GLM | |
| 22 | +| `etable(m1, m2, m3)` | `sp.etable([m1, m2, m3])` | Publication-quality regression tables (LaTeX / MD / HTML) | |
| 23 | +| `vcov(m, cluster = ~firm)` | `vcov={"CRV1": "firm"}` in `sp.feols` | Cluster-robust SE | |
| 24 | +| `fixef(m)` | `m.fixef()` (on pyfixest result) | Extract fixed-effect estimates | |
| 25 | + |
| 26 | +For plain OLS without HDFE, `sp.regress("y ~ x", data=df)` gives a statsmodels- |
| 27 | +compatible interface returning a `CausalResult`. |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## Staggered DID (`did`, `fixest::sunab`, `didimputation`, `DIDmultiplegt`) |
| 32 | + |
| 33 | +| R | StatsPAI | |
| 34 | +| ------------------------------------------------------- | -------------------------------------------------------- | |
| 35 | +| `did::att_gt(yname, tname, gname, idname, data)` | `sp.callaway_santanna(data, y=, time=, first_treat=, group=)` | |
| 36 | +| `did::aggte(obj, type = "dynamic")` | `sp.aggte(cs_result, type="dynamic")` | |
| 37 | +| `did::aggte(obj, type = "group")` | `sp.aggte(cs_result, type="group")` | |
| 38 | +| `did::ggdid(agg)` | `sp.ggdid(agg_result)` | |
| 39 | +| `fixest::sunab(cohort, period)` inside `feols` | `sp.sun_abraham(data, y=, time=, first_treat=, group=)` | |
| 40 | +| `didimputation::did_imputation(...)` | `sp.did_imputation(data, y=, time=, first_treat=, group=)` | |
| 41 | +| `DIDmultiplegt::did_multiplegt(...)` | `sp.did_multiplegt(data, y=, group=, time=, treatment=)` | |
| 42 | +| `DIDmultiplegt::did_multiplegt_dyn(...)` | `sp.did_multiplegt(..., dynamic=True)` | |
| 43 | +| `bacondecomp::bacon(...)` | `sp.bacon_decomposition(data, y=, time=, treat=, id=)` | |
| 44 | +| — | `sp.etwfe(...)` — Wooldridge (2021) explicit API | |
| 45 | +| `HonestDiD::createSensitivityResults(...)` | `sp.honest_did(cs_result, Mbar=...)` / `sp.breakdown_m(...)` | |
| 46 | +| `HonestDiD::createSensitivityResults_relativeMagnitudes` | `sp.sensitivity_rr(cs_result, Mbar=...)` | |
| 47 | +| `pretrends::pretrends_power(...)` | `sp.pretrends_power(...)` / `sp.pretrends_test(...)` | |
| 48 | +| — | `sp.stacked_did(...)`, `sp.ddd(...)`, `sp.continuous_did(...)`, `sp.cic(...)` | |
| 49 | + |
| 50 | +One-call integrated report: |
| 51 | + |
| 52 | +```python |
| 53 | +report = sp.cs_report(data, y="y", time="t", first_treat="g", group="id", |
| 54 | + save_to="output/did_report") |
| 55 | +# Produces .txt + .md + .tex + .xlsx + .png in one call |
| 56 | +``` |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +## Regression Discontinuity (`rdrobust`, `rddensity`, `rdmulti`, `rdhonest`) |
| 61 | + |
| 62 | +| R | StatsPAI | |
| 63 | +| --------------------------------------------- | ------------------------------------------------------ | |
| 64 | +| `rdrobust::rdrobust(y, x, c = 0)` | `sp.rdrobust(y, x, c=0)` | |
| 65 | +| `rdrobust::rdplot(y, x)` | `sp.rdplot(y, x)` | |
| 66 | +| `rdrobust::rdbwselect(y, x)` | bandwidth auto-selected inside `sp.rdrobust(...)` | |
| 67 | +| `rddensity::rddensity(x, c = 0)` | `sp.rdplotdensity(x, c=0)` | |
| 68 | +| `rdmulti::rdmc(...)` | `sp.rdmc(...)` | |
| 69 | +| `rdmulti::rdms(...)` | `sp.rdms(...)` | |
| 70 | +| `rdhonest::RDHonest(...)` | `sp.rd_honest(...)` (Armstrong-Kolesár honest CI) | |
| 71 | +| — | `sp.rkd(...)` — Regression kink designs | |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## Synthetic Control (`Synth`, `gsynth`, `augsynth`, `synthdid`) |
| 76 | + |
| 77 | +| R | StatsPAI | |
| 78 | +| -------------------------------------------- | -------------------------------------------- | |
| 79 | +| `Synth::synth(...)` | `sp.synth(...)` | |
| 80 | +| `gsynth::gsynth(Y ~ D \| X, data)` | `sp.gsynth(data, y=, treat=, unit=, time=)` | |
| 81 | +| `augsynth::augsynth(...)` | `sp.augsynth(...)` | |
| 82 | +| `synthdid::synthdid_estimate(...)` | `sp.sdid(...)` | |
| 83 | +| — | `sp.staggered_synth(...)` — Staggered SCM | |
| 84 | +| — | `sp.robust_synth(...)` — Robust SCM | |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +## Matching & Reweighting (`MatchIt`, `ebal`, `cobalt`) |
| 89 | + |
| 90 | +| R | StatsPAI | |
| 91 | +| ---------------------------------------- | --------------------------------------------------- | |
| 92 | +| `MatchIt::matchit(..., method = "nearest")` | `sp.match(..., method="psm")` | |
| 93 | +| `MatchIt::matchit(..., method = "cem")` | `sp.match(..., method="cem")` | |
| 94 | +| `MatchIt::matchit(..., method = "mahal")` | `sp.match(..., method="mahalanobis")` | |
| 95 | +| `ebal::ebalance(...)` | `sp.ebalance(...)` | |
| 96 | +| `cobalt::bal.tab(...)` | Built into `sp.match()` output: `result.balance` | |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## IV (`AER`, `ivmodel`, `ivreg`) |
| 101 | + |
| 102 | +| R | StatsPAI | |
| 103 | +| ---------------------------------------------- | ----------------------------------------------------- | |
| 104 | +| `AER::ivreg(y ~ x \| z, data = df)` | `sp.ivreg("y ~ x", instruments=["z"], data=df)` | |
| 105 | +| `ivmodel::LIML(...)` | `sp.liml(...)` | |
| 106 | +| `ivmodel::JIVE(...)` | `sp.jive(...)` | |
| 107 | +| `hdm::rlassoIV(...)` | `sp.lasso_iv(...)` | |
| 108 | +| `fixest::feols(y ~ 1 \| fe \| x ~ z)` | `sp.feols("y ~ 1 \| fe \| x ~ z", data=df)` | |
| 109 | +| `MendelianRandomization::mr_ivw(...)` | `sp.mr_ivw(...)` | |
| 110 | +| `MendelianRandomization::mr_egger(...)` | `sp.mr_egger(...)` | |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## Machine Learning Causal Inference |
| 115 | + |
| 116 | +| R | StatsPAI | |
| 117 | +| -------------------------------------------- | --------------------------------------------------- | |
| 118 | +| `DoubleML::DoubleMLPLR$new(...)` | `sp.dml(..., model="plr")` | |
| 119 | +| `DoubleML::DoubleMLIRM$new(...)` | `sp.dml(..., model="irm")` | |
| 120 | +| `grf::causal_forest(X, Y, W)` | `sp.causal_forest(X, Y, W)` | |
| 121 | +| `grf::causal_forest(...)$predict(...)` | `forest.predict(X_new)` | |
| 122 | +| `grf::instrumental_forest(...)` | `sp.causal_forest(..., instrumental=True)` | |
| 123 | +| `SuperLearner::SuperLearner(...)` | `sp.tmle(...)` with custom learners | |
| 124 | +| `policytree::policy_tree(...)` | `sp.policy_tree(X, reward)` | |
| 125 | +| `causalTree::honest.causalTree(...)` | `sp.causal_forest(..., honest=True)` | |
| 126 | + |
| 127 | +Meta-learner suite (no single R package covers all of these): |
| 128 | + |
| 129 | +```python |
| 130 | +# Unified one-call API: |
| 131 | +sp.metalearner(data, y="y", treat="D", covariates=["x1", "x2"], learner="S") |
| 132 | +sp.metalearner(data, ..., learner="T") |
| 133 | +sp.metalearner(data, ..., learner="X") |
| 134 | +sp.metalearner(data, ..., learner="R") |
| 135 | +sp.metalearner(data, ..., learner="DR") # default |
| 136 | + |
| 137 | +# Or use the class API for finer control: |
| 138 | +from statspai import SLearner, TLearner, XLearner, RLearner, DRLearner |
| 139 | +``` |
| 140 | + |
| 141 | +--- |
| 142 | + |
| 143 | +## Bounds, Conformal, Discovery, Policy |
| 144 | + |
| 145 | +| R | StatsPAI | |
| 146 | +| -------------------------------------- | --------------------------------------------------- | |
| 147 | +| `bounds::bounds(...)` (manual) | `sp.manski_bounds(...)`, `sp.lee_bounds(...)` | |
| 148 | +| `EValue::evalues.OR(...)` | `sp.evalue(...)` | |
| 149 | +| `pcalg::pc(suffStat, ...)` | `sp.pc_algorithm(data)` | |
| 150 | +| `pcalg::fci(...)` | `sp.causal_discovery(data, method="fci")` | |
| 151 | +| — (NOTEARS: Python only historically) | `sp.notears(data)` | |
| 152 | +| `policytree::policy_tree(...)` | `sp.policy_tree(...)` | |
| 153 | +| — | `sp.conformal_cate(...)` — conformal CATE | |
| 154 | + |
| 155 | +--- |
| 156 | + |
| 157 | +## Post-estimation, Diagnostics, Robustness |
| 158 | + |
| 159 | +| R | StatsPAI | |
| 160 | +| ------------------------------------------------ | -------------------------------------------------- | |
| 161 | +| `modelsummary::modelsummary(list(m1, m2))` | `sp.outreg2([m1, m2])` or `sp.modelsummary([m1, m2])` | |
| 162 | +| `sandwich::vcovCL(m, cluster = ~id)` | `vcov={"CRV1": "id"}` in `sp.feols` / `sp.regress` | |
| 163 | +| `lmtest::coeftest(m, vcov = vcovHC)` | `result.robust()` / `result.summary(vcov="HC3")` | |
| 164 | +| `car::linearHypothesis(m, "x1 = x2")` | `result.test("x1 = x2")` | |
| 165 | +| `marginaleffects::avg_slopes(m)` | `result.marginal_effects()` | |
| 166 | +| `multiwayvcov::cluster.vcov(m, ~c1 + c2)` | `vcov={"CRV1": ["c1", "c2"]}` | |
| 167 | +| `specr::specr(...)` / `spec_curve` | `sp.spec_curve(...)` — native implementation | |
| 168 | + |
| 169 | +--- |
| 170 | + |
| 171 | +## What StatsPAI Adds That R Cannot |
| 172 | + |
| 173 | +These are not migration items — they are **net-new capabilities** that the |
| 174 | +R ecosystem does not currently offer in unified form: |
| 175 | + |
| 176 | +- **`sp.list_functions()` / `sp.describe_function()` / `sp.function_schema()`** — agent-native introspection for LLM workflows |
| 177 | +- **`sp.interactive(fig)`** — Jupyter WYSIWYG plot editor with reproducible code export |
| 178 | +- **`sp.cs_report(..., save_to=...)`** — one-call bundle: txt + md + tex + xlsx + png |
| 179 | +- **Unified `CausalResult`** across all 390+ functions with `.summary()`, `.plot()`, `.to_latex()`, `.cite()` |
| 180 | +- **sklearn Pipeline / JAX / PyTorch integration** — neural causal models natively |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +## Quick Install |
| 185 | + |
| 186 | +```bash |
| 187 | +pip install statspai |
| 188 | +# Optional HDFE backend (for sp.feols / sp.fepois at scale): |
| 189 | +pip install pyfixest |
| 190 | +``` |
| 191 | + |
| 192 | +--- |
| 193 | + |
| 194 | +If you find a missing R function that should be mapped here, please open |
| 195 | +an issue at [github.com/brycewang-stanford/statspai/issues](https://github.com/brycewang-stanford/statspai/issues). |
0 commit comments