py-flexplot 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_flexplot-0.8.2/LICENSE +21 -0
- py_flexplot-0.8.2/PKG-INFO +272 -0
- py_flexplot-0.8.2/README.md +250 -0
- py_flexplot-0.8.2/pyproject.toml +44 -0
- py_flexplot-0.8.2/setup.cfg +4 -0
- py_flexplot-0.8.2/src/py_flexplot.egg-info/PKG-INFO +272 -0
- py_flexplot-0.8.2/src/py_flexplot.egg-info/SOURCES.txt +52 -0
- py_flexplot-0.8.2/src/py_flexplot.egg-info/dependency_links.txt +1 -0
- py_flexplot-0.8.2/src/py_flexplot.egg-info/requires.txt +8 -0
- py_flexplot-0.8.2/src/py_flexplot.egg-info/top_level.txt +1 -0
- py_flexplot-0.8.2/src/pyflexplot/__init__.py +60 -0
- py_flexplot-0.8.2/src/pyflexplot/bluepill.py +461 -0
- py_flexplot-0.8.2/src/pyflexplot/core.py +3410 -0
- py_flexplot-0.8.2/src/pyflexplot/descriptives.py +287 -0
- py_flexplot-0.8.2/src/pyflexplot/ebbr.py +148 -0
- py_flexplot-0.8.2/src/pyflexplot/flex_nn.py +583 -0
- py_flexplot-0.8.2/src/pyflexplot/ml.py +175 -0
- py_flexplot-0.8.2/src/pyflexplot/quality.py +372 -0
- py_flexplot-0.8.2/src/pyflexplot/sem.py +195 -0
- py_flexplot-0.8.2/src/pyflexplot/stats.py +803 -0
- py_flexplot-0.8.2/src/pyflexplot/uncertainty.py +185 -0
- py_flexplot-0.8.2/tests/test_binning.py +217 -0
- py_flexplot-0.8.2/tests/test_bluepill.py +280 -0
- py_flexplot-0.8.2/tests/test_bluepill_correctness.py +159 -0
- py_flexplot-0.8.2/tests/test_core.py +514 -0
- py_flexplot-0.8.2/tests/test_design_followups.py +239 -0
- py_flexplot-0.8.2/tests/test_ebbr.py +70 -0
- py_flexplot-0.8.2/tests/test_eta_squared.py +271 -0
- py_flexplot-0.8.2/tests/test_flex_nn.py +308 -0
- py_flexplot-0.8.2/tests/test_flex_nn_correctness.py +163 -0
- py_flexplot-0.8.2/tests/test_flex_nn_integration.py +52 -0
- py_flexplot-0.8.2/tests/test_flex_nn_keras.py +238 -0
- py_flexplot-0.8.2/tests/test_flexplot_extras.py +276 -0
- py_flexplot-0.8.2/tests/test_formula_edge_cases_2026_06_21.py +146 -0
- py_flexplot-0.8.2/tests/test_formula_transforms.py +265 -0
- py_flexplot-0.8.2/tests/test_ghost_reference.py +204 -0
- py_flexplot-0.8.2/tests/test_integration_v06x.py +260 -0
- py_flexplot-0.8.2/tests/test_interaction_model.py +209 -0
- py_flexplot-0.8.2/tests/test_meansplot.py +162 -0
- py_flexplot-0.8.2/tests/test_mixed_models.py +60 -0
- py_flexplot-0.8.2/tests/test_ml_adapter.py +183 -0
- py_flexplot-0.8.2/tests/test_overlay.py +256 -0
- py_flexplot-0.8.2/tests/test_parametric_smooth.py +222 -0
- py_flexplot-0.8.2/tests/test_plot_string_related.py +183 -0
- py_flexplot-0.8.2/tests/test_property_based.py +531 -0
- py_flexplot-0.8.2/tests/test_quality.py +228 -0
- py_flexplot-0.8.2/tests/test_r_parity.py +243 -0
- py_flexplot-0.8.2/tests/test_r_squared_ci.py +176 -0
- py_flexplot-0.8.2/tests/test_scatter3d.py +151 -0
- py_flexplot-0.8.2/tests/test_sem.py +82 -0
- py_flexplot-0.8.2/tests/test_spread.py +163 -0
- py_flexplot-0.8.2/tests/test_stats.py +205 -0
- py_flexplot-0.8.2/tests/test_uncertainty.py +371 -0
- py_flexplot-0.8.2/tests/test_v080_parity.py +371 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EzraAir555
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-flexplot
|
|
3
|
+
Version: 0.8.2
|
|
4
|
+
Summary: Python port of Dustin Fife's R visualization and analysis packages (flexplot, fifer, etc.)
|
|
5
|
+
Author-email: EzraAir555 <ezraair555@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ezraair555/py-flexplot
|
|
8
|
+
Project-URL: Documentation, https://github.com/ezraair555/py-flexplot/tree/main/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/ezraair555/py-flexplot
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=1.23
|
|
14
|
+
Requires-Dist: pandas>=1.5
|
|
15
|
+
Requires-Dist: scipy>=1.11
|
|
16
|
+
Requires-Dist: plotnine>=0.12
|
|
17
|
+
Requires-Dist: statsmodels>=0.14
|
|
18
|
+
Requires-Dist: patsy>=0.5
|
|
19
|
+
Requires-Dist: scikit-learn>=1.2
|
|
20
|
+
Requires-Dist: semopy>=2.3
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# py-flexplot
|
|
24
|
+
|
|
25
|
+
A **partial** Python port of Dustin Fife's [`flexplot`](https://github.com/dustinfife/flexplot) and related R packages (`fifer`, `flexplavaan`, `ebbr`, `bluepill`).
|
|
26
|
+
|
|
27
|
+
`py-flexplot` provides intelligent data visualization using a formula-based syntax, similar to the original R implementation but powered by `plotnine` for a consistent "grammar of graphics" look and feel in Python.
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## What's covered (and what isn't)
|
|
32
|
+
|
|
33
|
+
This is **not** a 1:1 port. The Python port covers the parts of R's `flexplot` and friends that translate cleanly onto `plotnine` + `statsmodels`; some R-only features are deferred or unsupported. See [`docs/api/coverage.md`](docs/api/coverage.md) for the full coverage matrix vs the R packages. Highlights:
|
|
34
|
+
|
|
35
|
+
- ✅ `flexplot()` core dispatch + `bins` / `breaks` / `labels` (auto-bin), `spread`, `overlay`, `uncertainty` (CI / prediction / bootstrap), `ghost_line` / `ghost_reference`, `plot.string`, `plot_type` override, `sample`, `return_data`.
|
|
36
|
+
- ✅ `model_comparison()` (AIC / BIC / R² / adj.R² / **Bayes factor**), `estimates()` (structured effect-size reporter), `compare_fits()` (with `return_preds` / `pred_type`).
|
|
37
|
+
- ✅ `visualize()` with `plot='model' | 'residuals' | 'all'`.
|
|
38
|
+
- ✅ `diagnose()` (missingness, Cook's D, Ramsey RESET, Breusch-Pagan).
|
|
39
|
+
- ⚠️ R-style interaction syntax (`y ~ x*z`) is parsed but the fit remains additive — pass `interaction_model=True` (v0.7.0+) for non-parallel slopes per color group.
|
|
40
|
+
- ✅ `randomForest` (and any sklearn estimator with `.predict()`) — use `pyflexplot.ml.RFAdapter` to wrap a fitted estimator and pass it to `compare_fits()`. See [`docs/api/ml.md`](docs/api/ml.md).
|
|
41
|
+
- ⚠️ Mixed-effects models are now available in `flexplot()` via `method="mixedlm"|"lmer"|"glmer"` with `random_effects=...` (v0.8.2+). This is a practical Python bridge, not a full `lme4` clone. For stricter `lme4` parity, use `pymer4`/`rpy2`.
|
|
42
|
+
|
|
43
|
+
## Included R Packages
|
|
44
|
+
- **flexplot**: Intelligent multivariate graphics via formulas.
|
|
45
|
+
- **fifer/fifer2**: Biostatistical toolbox for data cleanup and analysis.
|
|
46
|
+
- **flexplavaan**: Visualizing latent variable models (SEM).
|
|
47
|
+
- **flex_nn**: Neural-network visualization wrappers. **torch** is the default backend; **Keras 3** is supported transparently via the same `NeuralNetFit` class. Drop any `torch.nn.Module` or `keras.Model` (Sequential, Functional, or subclassed) into `compare_fits()` alongside statsmodels fits.
|
|
48
|
+
- **bluepill**: Synthetic mixed-model data generator. `mixed_model(...)` produces clustered data with fixed and random effects, interactions, and polynomial terms.
|
|
49
|
+
- **descriptives** (Python-native, port of `fifer::meansplot()`): `meansplot(formula, data, error=...)` for mean + error-bar visualizations across categorical or ordinal groups. `scatter3D(formula, data, type=...)` for 2D projection of `y ~ x + z`.
|
|
50
|
+
- **ml** (Python-native, no R analog): Adapters so scikit-learn estimators (`RandomForestRegressor`, `RandomForestClassifier`, and any estimator with `.predict()`) can be used with `compare_fits()`. Optional — requires `pip install scikit-learn`.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
Install the released package from PyPI:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install py-flexplot
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For development from a checkout:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/ezraair555/py-flexplot.git
|
|
64
|
+
cd py-flexplot
|
|
65
|
+
pip install -e .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Optional backends for `flex_nn`
|
|
69
|
+
|
|
70
|
+
- **torch** is the default and is required for the torch paths to run. `pip install torch`.
|
|
71
|
+
- **Keras 3** is supported opportunistically. Install `pip install "keras[jax]"` (or `keras[tensorflow]` / `keras[torch]`), set `KERAS_BACKEND=jax` (or your chosen backend), and `from pyflexplot.flex_nn import NeuralNetFit` will route Keras models through the same wrapper. No keras import is required when torch is the only backend.
|
|
72
|
+
- The `tests/test_flex_nn_keras.py` and the keras section of `examples/notebooks/flex_nn_example.ipynb` exercise the keras path; both skip cleanly when keras isn't installed.
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import pandas as pd
|
|
78
|
+
from pyflexplot import flexplot, visualize, compare_fits
|
|
79
|
+
import statsmodels.formula.api as smf
|
|
80
|
+
|
|
81
|
+
# Load data
|
|
82
|
+
df = pd.read_csv("data.csv")
|
|
83
|
+
|
|
84
|
+
# 1. Formula-based visualization
|
|
85
|
+
# y ~ x | z (y by x, faceted by z)
|
|
86
|
+
p = flexplot("y ~ x | z", data=df)
|
|
87
|
+
p.draw()
|
|
88
|
+
|
|
89
|
+
# 2. Model visualization
|
|
90
|
+
model = smf.ols("y ~ x", data=df).fit()
|
|
91
|
+
p_viz = visualize(model, data=df)
|
|
92
|
+
p_viz.draw()
|
|
93
|
+
|
|
94
|
+
# 3. Compare two models side-by-side (statsmodels or scikit-learn)
|
|
95
|
+
p_cmp = compare_fits("y ~ x", data=df, model1=model, model2=model)
|
|
96
|
+
|
|
97
|
+
# 4. Drop a fitted neural network into compare_fits
|
|
98
|
+
from pyflexplot.flex_nn import NeuralNetFit, set_response_var
|
|
99
|
+
import torch
|
|
100
|
+
|
|
101
|
+
torch_model = torch.nn.Sequential(torch.nn.Linear(3, 8), torch.nn.ReLU(),
|
|
102
|
+
torch.nn.Linear(8, 1)).eval()
|
|
103
|
+
set_response_var(torch_model, "y")
|
|
104
|
+
nn_fit = NeuralNetFit(model=torch_model, response_var="y",
|
|
105
|
+
predictor_names=["x1", "x2", "x3"])
|
|
106
|
+
p_nn = compare_fits("y ~ x1", data=df, model1=model, model2=nn_fit)
|
|
107
|
+
|
|
108
|
+
# 5. Generate a synthetic clustered dataset for demos or power analyses
|
|
109
|
+
from pyflexplot import mixed_model
|
|
110
|
+
|
|
111
|
+
df_sim = mixed_model(
|
|
112
|
+
fixed=[0.0, 0.2, 0.5, 0.3, 0.2],
|
|
113
|
+
random=[0.1, 0.1, 0.0, 0.2, 0.1],
|
|
114
|
+
sigma=0.3, clusters=15, n_per=[11, 3],
|
|
115
|
+
vars={"depression": (10.0, 3.0, 0),
|
|
116
|
+
"stress": (22.0, 7.0, 0),
|
|
117
|
+
"life_events": ["no", "yes"],
|
|
118
|
+
"ses": (55.0, 15.0, 0),
|
|
119
|
+
"therapist": [f"Dr. {chr(65 + i)}" for i in range(15)]},
|
|
120
|
+
seed=42,
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
See `examples/notebooks/flex_nn_example.ipynb` for an end-to-end
|
|
125
|
+
walk-through of the new functionality.
|
|
126
|
+
|
|
127
|
+
## Features
|
|
128
|
+
- **Formula Syntax**: Uses `y ~ x + z | a` to automatically determine plot types.
|
|
129
|
+
- **Model Visualization**: Directly `visualize(model)` to see predicted vs actuals.
|
|
130
|
+
- **Model Comparison**: Use `compare_fits(formula, data, m1, m2)` to see performance side-by-side.
|
|
131
|
+
- **Uncertainty Layers (v0.4.0+)**: First-class confidence / prediction / bootstrap bands around every fitted line via `uncertainty=`, `level=`, and `bands=` on `flexplot()`. Pick the band type that fits your modeling claim.
|
|
132
|
+
- **Model-Compare Overlay (v0.5.0+)**: Overlay multiple smoothers (`lm`, `loess`, `rlm`, etc.) on the same chart via `overlay=...` so the user can *see* which fit the data prefers.
|
|
133
|
+
- **Auto Data-Quality Diagnostics (v0.6.0+)**: `diagnose("y ~ x + z", data)` runs missingness / Cook's distance / Ramsey RESET / Breusch-Pagan and prints a one-paragraph summary of why your fit might be off.
|
|
134
|
+
- **R-Style Interaction Syntax (v0.6.2+)**: Formulas accept `y ~ x*z` and `y ~ x:z` (parsed, validated, with a `UserWarning` noting that the v0.6.x fit is additive; v0.7.0 will add `interaction_model=True`).
|
|
135
|
+
- **Neural-Network Integration (torch + Keras 3)**: Wrap a fitted `torch.nn.Module` or `keras.Model` with `NeuralNetFit` to drop it into `compare_fits` next to a statsmodels fit. Keras 3 models are evaluated with `training=False` so Dropout/BatchNorm behave deterministically; torch models use `torch.no_grad()`. `permutation_importance()` provides column-shuffling variable ranking that works against either backend.
|
|
136
|
+
- **Synthetic Data Generation**: `mixed_model(...)` produces clustered data with fixed + random effects for demos, teaching, and power analyses. `estimate_sd(mean, min, max)` recovers an SD from a known range.
|
|
137
|
+
- **Biostats Utilities**: Ported functions from `fifer` for common statistical tasks.
|
|
138
|
+
|
|
139
|
+
## Typical workflow (v0.6.x)
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import pandas as pd
|
|
143
|
+
from pyflexplot import flexplot, diagnose
|
|
144
|
+
|
|
145
|
+
df = pd.read_csv("data.csv")
|
|
146
|
+
|
|
147
|
+
# 1. Diagnose the model fit before plotting.
|
|
148
|
+
diag = diagnose("y ~ x + z", data=df)
|
|
149
|
+
|
|
150
|
+
# 2. Plot with uncertainty bands and overlay competing smoothers.
|
|
151
|
+
p = flexplot(
|
|
152
|
+
"y ~ x + z", data=df,
|
|
153
|
+
uncertainty="ci", # or "prediction" / "bootstrap"
|
|
154
|
+
level=0.95,
|
|
155
|
+
bands=[0.5, 0.8, 0.95], # nested ribbons (Tufte-style)
|
|
156
|
+
overlay=[
|
|
157
|
+
{"method": "loess", "label": "LOESS smoother"},
|
|
158
|
+
{"method": "rlm", "label": "Robust regression"},
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
p.draw()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
See `docs/examples/diagnostics_workflow.md` for a longer walk-through.
|
|
165
|
+
|
|
166
|
+
## Continuous Integration
|
|
167
|
+
|
|
168
|
+
Three GitHub Actions workflows cover the test surface, kept independent so
|
|
169
|
+
each runs in its own clean environment:
|
|
170
|
+
|
|
171
|
+
* `.github/workflows/python-app.yml` -- core test matrix across Python
|
|
172
|
+
3.10, 3.11, 3.12, 3.13. No torch or keras required; tests that need
|
|
173
|
+
them skip cleanly via `pytest.importorskip`.
|
|
174
|
+
* `.github/workflows/torch.yml` -- installs `torch` (CPU build) and runs
|
|
175
|
+
the torch-flex_nn tests. Triggered on every push to `main`, on PRs
|
|
176
|
+
touching `src/pyflexplot/flex_nn.py` or the torch tests, and on a
|
|
177
|
+
weekly schedule so we catch upstream torch regressions.
|
|
178
|
+
* `.github/workflows/keras3.yml` -- installs `keras[jax]` and runs
|
|
179
|
+
`tests/test_flex_nn_keras.py` against a Keras 3 install. Same
|
|
180
|
+
trigger pattern as `torch.yml` plus a weekly schedule.
|
|
181
|
+
|
|
182
|
+
All workflows upload coverage via `pytest-cov`.
|
|
183
|
+
|
|
184
|
+
## Changelog
|
|
185
|
+
|
|
186
|
+
`README.md` now includes a concise release log. The canonical full history
|
|
187
|
+
remains in [`CHANGELOG.md`](CHANGELOG.md).
|
|
188
|
+
|
|
189
|
+
### 0.8.2 (2026-08-31)
|
|
190
|
+
- Added mixed-effects support in `flexplot()`:
|
|
191
|
+
- `method="mixedlm"` / `method="lmer"` for linear mixed models via `statsmodels.MixedLM`
|
|
192
|
+
- `method="glmer"` for binomial mixed models via `statsmodels.BinomialBayesMixedGLM`
|
|
193
|
+
- `random_effects=` supports a group column name or compact forms like `(1|group)` and `(1 + x|group)`.
|
|
194
|
+
- Added mixed-model tests in `tests/test_mixed_models.py`.
|
|
195
|
+
- Updated parity docs to reflect that mixed models are now available with explicit `lme4`-parity caveats.
|
|
196
|
+
|
|
197
|
+
### 0.8.1 (2026-08-31)
|
|
198
|
+
- Added formula-function transformations in `flexplot()` (`log(x)`, `sqrt(x)`, `exp(x)`, `poly(x, 2)`, `I(...)`) with a safe whitelisted evaluator.
|
|
199
|
+
- Added multivariate numeric slotting parity for `y ~ x1 + x2` / `y ~ x1 + x2 | g` by auto-binning slot-2+/given numeric variables into `<var>_binned`.
|
|
200
|
+
- Added R-style defaults/parity behavior: categorical-vs-numeric alpha defaults, categorical jitter semantics, and low-cardinality numeric auto-categorization (`<5` unique).
|
|
201
|
+
- Added explicit R-style `compare_fits()` compatibility args (`report_se`, `re`, `num_points`, `clusters`) with transparent no-op warning.
|
|
202
|
+
- Added `third_eye()` placeholder endpoint (exported in package API) that raises `NotImplementedError` with guidance.
|
|
203
|
+
- Added/updated parity tests; test suite status at release: `509 passed, 4 skipped`.
|
|
204
|
+
|
|
205
|
+
### 0.8.0 (2026-08-31)
|
|
206
|
+
- Implemented the major parity batch from the v0.8.0 review:
|
|
207
|
+
- Non-nested `model_comparison()` support and `pred_difference`.
|
|
208
|
+
- `estimates()` factor-level tables + mean differences.
|
|
209
|
+
- `added_plot()` R semantics alignment.
|
|
210
|
+
- R spread tokens/defaults, `ghost_line` panel semantics, and standalone accessors.
|
|
211
|
+
- Included release cleanup (`.gitignore` hardening and parity script addition).
|
|
212
|
+
|
|
213
|
+
### 0.6.2 (2026-08-30)
|
|
214
|
+
- **R-style interaction syntax accepted by the formula parser.** `y ~ x*z` and `y ~ x:z` no longer raise "missing column"; the parser expands `*` to `+` + `:` for column lookup and preserves interaction terms in `all_x`. `flexplot()` emits a `UserWarning` reminding the user that v0.6.x fits remain additive; v0.7.0 will add `interaction_model=True`. 6 new tests in `tests/test_core.py`.
|
|
215
|
+
|
|
216
|
+
### 0.6.1 (2026-08-30)
|
|
217
|
+
- **Fixed dead binomial branch in `flexplot()`.** `pd.api.types.is_numeric_dtype([0, 1])` returns True, so int/float binary y was always routed to the LM/loess branch and the binomial GLM branch was unreachable. Added a binary pre-check that detects unique values ⊆ {0, 1} *before* the numeric-dtype dispatch. Numeric `[0, 1]` y now draws a sigmoid curve (was a straight LM line); string `["yes", "no"]` and multi-level numeric `[0, 1, 2]` behavior unchanged. 3 new tests + 1 updated regression test.
|
|
218
|
+
|
|
219
|
+
### 0.6.0 (2026-08-30)
|
|
220
|
+
- **`diagnose(formula, data)` — auto data-quality diagnostics.** Runs missingness (per-column counts and pattern heuristic), Cook's distance for outliers (default `4/n`), Ramsey RESET for functional form, and Breusch-Pagan for heteroscedasticity. Returns a structured dict; pass `verbose=True` for a one-paragraph terminal/email/log summary. New module `pyflexplot.quality`. 19 new tests.
|
|
221
|
+
|
|
222
|
+
### 0.5.0 (2026-08-30)
|
|
223
|
+
- **`overlay` parameter on `flexplot()`.** Overlay multiple smoothers (`lm`, `loess`, `rlm`, `glm`, ...) on the same axes with per-smoother uncertainty bands. Each entry takes a `color` (cycles through a 5-color palette) and optional `label` / `uncertainty` / `level`. When any entry has a `label`, a manual color scale adds a legend. The binomial branch restricts overlay to `method="glm"`; other methods raise. 14 new tests.
|
|
224
|
+
|
|
225
|
+
### 0.4.0 (2026-08-30)
|
|
226
|
+
- **`uncertainty` parameter on `flexplot()`.** First-class confidence / prediction / bootstrap bands around every fitted line. New module `pyflexplot.uncertainty` exposes `validate_uncertainty_params`, `compute_bootstrap_ci`, `compute_prediction_band`, `format_band`.`..- 35 new tests, full suite 199 passed / 1 skipped (keras not installed), no regressions.
|
|
227
|
+
|
|
228
|
+
### 0.3.0 (2026-08-28)
|
|
229
|
+
- **`visualize()` now accepts `NeuralNetFit` wrappers** (DESIGN-7 from the v0.2.2 review). The duck-type dispatch avoids importing `flex_nn` at module load time, so the core module stays cheap when neural-net support isn't needed. The output mirrors the statsmodels `visualize()`: predicted-vs-actual line on top of a scatter. 7 new tests in `tests/test_design_followups.py::TestVisualizeNeuralNetFit`.
|
|
230
|
+
- **`flexplot()` method validation** (DESIGN-4) — unknown `method` values now raise `ValueError` instead of silently producing no smooth. The `method` parameter is checked against a `{auto, lm, loess}` whitelist at entry.
|
|
231
|
+
- **`flexplot()` given-variable validation** (DESIGN-3) — formulas with 3+ variables after `|` now raise `ValueError` instead of silently dropping `given[2:]`. Two-given is the maximum; `facet_grid` only supports row+column.
|
|
232
|
+
- **`bluepill.mixed_model(polynomials=...)` no longer requires `to`** (DESIGN-6). Split the interaction/polynomial validator into two: interactions still require `from`/`to`/`coef`; polynomials only need `from`/`coef`. The R-compatible shape (`from`/`to`/`coef`) is still accepted on polynomials but `to` is ignored for backwards compatibility.
|
|
233
|
+
- **Hypothesis property tests** — 14 new property-based tests in `tests/test_property_based.py` covering the formula parser (round-trip identity, deterministic parsing, malformed-input rejection across hundreds of generated formulas) and `mixed_model` rescaling invariants (output mean/SD match the declared spec within sampling tolerance; categorical columns only take declared levels). Each test runs 10-50 generated examples.
|
|
234
|
+
- Total test surface: 132 → 164 (32 new). All tests pass; no API breakage.
|
|
235
|
+
|
|
236
|
+
### 0.2.2 (2026-08-28)
|
|
237
|
+
- **Critical bug fix (bluepill)**: `mixed_model()` had an off-by-one column index that made the last predictor a constant column (its declared mean, zero variance) and shifted all other predictors by one column. The README's example produced `ses = 55.0` for every row. Fixed.
|
|
238
|
+
- **Critical bug fix (flex_nn)**: `permutation_importance()` crashed with `UnboundLocalError` on five of the eleven declared metric names (`auc`, `precision`, `recall`, `f1`, `loss`) because the scorer dispatch branches were missing. Added rank-based AUC, thresholded binary precision/recall/F1, and `loss` (MSE) scorers; the unreachable `if direction is None:` fallback block is gone.
|
|
239
|
+
- **Critical bug fix (bluepill)**: tuple-of-strings categorical specs (valid per the `VarSpec` type hint) were misidentified as continuous specs and crashed with `ValueError`. Extracted the numeric-detection logic into a shared `_is_continuous_spec()` helper so validation and execution agree.
|
|
240
|
+
- Added 20 contract-level regression tests in `tests/test_bluepill_correctness.py` and `tests/test_flex_nn_correctness.py`. They check that predictors have non-zero variance, that the strongest coefficient ranks first in permutation importance, that all declared metrics work end-to-end, and that tuple specs round-trip. Each of these tests fails on the pre-v0.2.2 code path; all 20 pass now. Total: 132 tests passing.
|
|
241
|
+
- Other cleanups from the v0.2.2 review: replaced `from plotnine import *` with explicit imports in `core.py` and `sem.py`, removed the unused `patsy` import, restored the model's original `training` flag in `_keras_predict()` (was permanently mutating caller state), and added an explicit "experimental / not yet implemented" note to `estimates()`.
|
|
242
|
+
|
|
243
|
+
### 0.2.1 (2026-08-28)
|
|
244
|
+
- Hardened the Keras 3 path in `pyflexplot.flex_nn`: predictions now go through a dedicated `_keras_predict()` helper that passes `training=False` (so `Dropout`/`BatchNorm` behave deterministically) and falls back gracefully for custom `Model` subclasses whose `predict()` doesn't accept the `training` kwarg.
|
|
245
|
+
- Added `tests/test_flex_nn_keras.py` with 14 keras-specific tests (skip when keras isn't installed; verified against `keras==3.15.1` + `jax` backend). Total test surface: 110 (core + torch) + 14 (keras when available) = 124.
|
|
246
|
+
- CI: split into three workflows -- `python-app.yml` (core, no optional deps, Python 3.10-3.13), `torch.yml` (torch CPU install, weekly schedule to catch upstream regressions), `keras3.yml` (keras[jax] install, weekly schedule, PRs touching flex_nn).
|
|
247
|
+
- Extended the example notebook with a Keras 3 walk-through and added a README section describing the optional install + backend selection.
|
|
248
|
+
|
|
249
|
+
### 0.2.0 (2026-08-28)
|
|
250
|
+
- Added `pyflexplot.flex_nn` — torch-default wrappers for fitting and visualizing neural networks. `NeuralNetFit` bundles a fitted `torch.nn.Module` (or `keras.Model`) with the metadata needed to plug into `compare_fits`. `permutation_importance()` provides column-shuffling variable importance.
|
|
251
|
+
- Added `pyflexplot.bluepill` — port of Dustin Fife's `bluepill` R package. `estimate_sd()` recovers an SD from a known mean and min/max range; `mixed_model()` generates clustered synthetic data with fixed + random effects, interactions, and polynomial terms.
|
|
252
|
+
- Dropped the aspirational `flexifiers` bullet from the "Included R Packages" list (no corresponding R package was found).
|
|
253
|
+
- Added 50 new tests across the two modules (60 → 110). Test suite uses `pytest.importorskip("torch")` so the package still imports cleanly without torch installed, but `flex_nn` tests skip when torch is absent.
|
|
254
|
+
|
|
255
|
+
### 0.1.1 (2026-06-20)
|
|
256
|
+
- Hardened `parse_flexplot_formula()` validation (exactly one `~`, at most one `|`, trimmed tokens, intercept-only handling, empty outcome/predictor rejection).
|
|
257
|
+
- Added input validation to `flexplot()` for empty DataFrames, missing columns, and numeric column types; color/group aesthetics are now included in the initial `aes()` so all geoms receive them.
|
|
258
|
+
- Fixed `hopper_plot()` against current `semopy` (`calc_sigma()` and `mx_cov` handling) and added real `semopy` smoke tests.
|
|
259
|
+
- Hardened `fit_beta_prior()` with range checks for `successes`/`totals`, zero-variance guard, optimizer success, and finite parameter validation.
|
|
260
|
+
- Fixed `added_plot()` residual alignment using index-aware `pd.concat(..., join='inner')` with length validation.
|
|
261
|
+
- Fixed `model_comparison()` LRT to enforce correct order and validate required model attributes.
|
|
262
|
+
- Fixed `visualize()` to identify the first non-intercept term robustly and raise exceptions instead of returning strings.
|
|
263
|
+
- Fixed `compare_fits()` prediction alignment to `data.index` with length validation.
|
|
264
|
+
- Fixed `add_ebb_estimate()` to use scalar/array addition and validate columns/dtypes.
|
|
265
|
+
- Fixed `sem.py` functions to raise typed exceptions instead of returning error strings.
|
|
266
|
+
- Expanded test coverage for all P0 and selected P1 paths.
|
|
267
|
+
|
|
268
|
+
### 0.1.0
|
|
269
|
+
- Initial package skeleton with `flexplot`, `visualize`, `compare_fits`, SEM helpers, and `fifer` utilities.
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
MIT
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# py-flexplot
|
|
2
|
+
|
|
3
|
+
A **partial** Python port of Dustin Fife's [`flexplot`](https://github.com/dustinfife/flexplot) and related R packages (`fifer`, `flexplavaan`, `ebbr`, `bluepill`).
|
|
4
|
+
|
|
5
|
+
`py-flexplot` provides intelligent data visualization using a formula-based syntax, similar to the original R implementation but powered by `plotnine` for a consistent "grammar of graphics" look and feel in Python.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## What's covered (and what isn't)
|
|
10
|
+
|
|
11
|
+
This is **not** a 1:1 port. The Python port covers the parts of R's `flexplot` and friends that translate cleanly onto `plotnine` + `statsmodels`; some R-only features are deferred or unsupported. See [`docs/api/coverage.md`](docs/api/coverage.md) for the full coverage matrix vs the R packages. Highlights:
|
|
12
|
+
|
|
13
|
+
- ✅ `flexplot()` core dispatch + `bins` / `breaks` / `labels` (auto-bin), `spread`, `overlay`, `uncertainty` (CI / prediction / bootstrap), `ghost_line` / `ghost_reference`, `plot.string`, `plot_type` override, `sample`, `return_data`.
|
|
14
|
+
- ✅ `model_comparison()` (AIC / BIC / R² / adj.R² / **Bayes factor**), `estimates()` (structured effect-size reporter), `compare_fits()` (with `return_preds` / `pred_type`).
|
|
15
|
+
- ✅ `visualize()` with `plot='model' | 'residuals' | 'all'`.
|
|
16
|
+
- ✅ `diagnose()` (missingness, Cook's D, Ramsey RESET, Breusch-Pagan).
|
|
17
|
+
- ⚠️ R-style interaction syntax (`y ~ x*z`) is parsed but the fit remains additive — pass `interaction_model=True` (v0.7.0+) for non-parallel slopes per color group.
|
|
18
|
+
- ✅ `randomForest` (and any sklearn estimator with `.predict()`) — use `pyflexplot.ml.RFAdapter` to wrap a fitted estimator and pass it to `compare_fits()`. See [`docs/api/ml.md`](docs/api/ml.md).
|
|
19
|
+
- ⚠️ Mixed-effects models are now available in `flexplot()` via `method="mixedlm"|"lmer"|"glmer"` with `random_effects=...` (v0.8.2+). This is a practical Python bridge, not a full `lme4` clone. For stricter `lme4` parity, use `pymer4`/`rpy2`.
|
|
20
|
+
|
|
21
|
+
## Included R Packages
|
|
22
|
+
- **flexplot**: Intelligent multivariate graphics via formulas.
|
|
23
|
+
- **fifer/fifer2**: Biostatistical toolbox for data cleanup and analysis.
|
|
24
|
+
- **flexplavaan**: Visualizing latent variable models (SEM).
|
|
25
|
+
- **flex_nn**: Neural-network visualization wrappers. **torch** is the default backend; **Keras 3** is supported transparently via the same `NeuralNetFit` class. Drop any `torch.nn.Module` or `keras.Model` (Sequential, Functional, or subclassed) into `compare_fits()` alongside statsmodels fits.
|
|
26
|
+
- **bluepill**: Synthetic mixed-model data generator. `mixed_model(...)` produces clustered data with fixed and random effects, interactions, and polynomial terms.
|
|
27
|
+
- **descriptives** (Python-native, port of `fifer::meansplot()`): `meansplot(formula, data, error=...)` for mean + error-bar visualizations across categorical or ordinal groups. `scatter3D(formula, data, type=...)` for 2D projection of `y ~ x + z`.
|
|
28
|
+
- **ml** (Python-native, no R analog): Adapters so scikit-learn estimators (`RandomForestRegressor`, `RandomForestClassifier`, and any estimator with `.predict()`) can be used with `compare_fits()`. Optional — requires `pip install scikit-learn`.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
Install the released package from PyPI:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install py-flexplot
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For development from a checkout:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/ezraair555/py-flexplot.git
|
|
42
|
+
cd py-flexplot
|
|
43
|
+
pip install -e .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Optional backends for `flex_nn`
|
|
47
|
+
|
|
48
|
+
- **torch** is the default and is required for the torch paths to run. `pip install torch`.
|
|
49
|
+
- **Keras 3** is supported opportunistically. Install `pip install "keras[jax]"` (or `keras[tensorflow]` / `keras[torch]`), set `KERAS_BACKEND=jax` (or your chosen backend), and `from pyflexplot.flex_nn import NeuralNetFit` will route Keras models through the same wrapper. No keras import is required when torch is the only backend.
|
|
50
|
+
- The `tests/test_flex_nn_keras.py` and the keras section of `examples/notebooks/flex_nn_example.ipynb` exercise the keras path; both skip cleanly when keras isn't installed.
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import pandas as pd
|
|
56
|
+
from pyflexplot import flexplot, visualize, compare_fits
|
|
57
|
+
import statsmodels.formula.api as smf
|
|
58
|
+
|
|
59
|
+
# Load data
|
|
60
|
+
df = pd.read_csv("data.csv")
|
|
61
|
+
|
|
62
|
+
# 1. Formula-based visualization
|
|
63
|
+
# y ~ x | z (y by x, faceted by z)
|
|
64
|
+
p = flexplot("y ~ x | z", data=df)
|
|
65
|
+
p.draw()
|
|
66
|
+
|
|
67
|
+
# 2. Model visualization
|
|
68
|
+
model = smf.ols("y ~ x", data=df).fit()
|
|
69
|
+
p_viz = visualize(model, data=df)
|
|
70
|
+
p_viz.draw()
|
|
71
|
+
|
|
72
|
+
# 3. Compare two models side-by-side (statsmodels or scikit-learn)
|
|
73
|
+
p_cmp = compare_fits("y ~ x", data=df, model1=model, model2=model)
|
|
74
|
+
|
|
75
|
+
# 4. Drop a fitted neural network into compare_fits
|
|
76
|
+
from pyflexplot.flex_nn import NeuralNetFit, set_response_var
|
|
77
|
+
import torch
|
|
78
|
+
|
|
79
|
+
torch_model = torch.nn.Sequential(torch.nn.Linear(3, 8), torch.nn.ReLU(),
|
|
80
|
+
torch.nn.Linear(8, 1)).eval()
|
|
81
|
+
set_response_var(torch_model, "y")
|
|
82
|
+
nn_fit = NeuralNetFit(model=torch_model, response_var="y",
|
|
83
|
+
predictor_names=["x1", "x2", "x3"])
|
|
84
|
+
p_nn = compare_fits("y ~ x1", data=df, model1=model, model2=nn_fit)
|
|
85
|
+
|
|
86
|
+
# 5. Generate a synthetic clustered dataset for demos or power analyses
|
|
87
|
+
from pyflexplot import mixed_model
|
|
88
|
+
|
|
89
|
+
df_sim = mixed_model(
|
|
90
|
+
fixed=[0.0, 0.2, 0.5, 0.3, 0.2],
|
|
91
|
+
random=[0.1, 0.1, 0.0, 0.2, 0.1],
|
|
92
|
+
sigma=0.3, clusters=15, n_per=[11, 3],
|
|
93
|
+
vars={"depression": (10.0, 3.0, 0),
|
|
94
|
+
"stress": (22.0, 7.0, 0),
|
|
95
|
+
"life_events": ["no", "yes"],
|
|
96
|
+
"ses": (55.0, 15.0, 0),
|
|
97
|
+
"therapist": [f"Dr. {chr(65 + i)}" for i in range(15)]},
|
|
98
|
+
seed=42,
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
See `examples/notebooks/flex_nn_example.ipynb` for an end-to-end
|
|
103
|
+
walk-through of the new functionality.
|
|
104
|
+
|
|
105
|
+
## Features
|
|
106
|
+
- **Formula Syntax**: Uses `y ~ x + z | a` to automatically determine plot types.
|
|
107
|
+
- **Model Visualization**: Directly `visualize(model)` to see predicted vs actuals.
|
|
108
|
+
- **Model Comparison**: Use `compare_fits(formula, data, m1, m2)` to see performance side-by-side.
|
|
109
|
+
- **Uncertainty Layers (v0.4.0+)**: First-class confidence / prediction / bootstrap bands around every fitted line via `uncertainty=`, `level=`, and `bands=` on `flexplot()`. Pick the band type that fits your modeling claim.
|
|
110
|
+
- **Model-Compare Overlay (v0.5.0+)**: Overlay multiple smoothers (`lm`, `loess`, `rlm`, etc.) on the same chart via `overlay=...` so the user can *see* which fit the data prefers.
|
|
111
|
+
- **Auto Data-Quality Diagnostics (v0.6.0+)**: `diagnose("y ~ x + z", data)` runs missingness / Cook's distance / Ramsey RESET / Breusch-Pagan and prints a one-paragraph summary of why your fit might be off.
|
|
112
|
+
- **R-Style Interaction Syntax (v0.6.2+)**: Formulas accept `y ~ x*z` and `y ~ x:z` (parsed, validated, with a `UserWarning` noting that the v0.6.x fit is additive; v0.7.0 will add `interaction_model=True`).
|
|
113
|
+
- **Neural-Network Integration (torch + Keras 3)**: Wrap a fitted `torch.nn.Module` or `keras.Model` with `NeuralNetFit` to drop it into `compare_fits` next to a statsmodels fit. Keras 3 models are evaluated with `training=False` so Dropout/BatchNorm behave deterministically; torch models use `torch.no_grad()`. `permutation_importance()` provides column-shuffling variable ranking that works against either backend.
|
|
114
|
+
- **Synthetic Data Generation**: `mixed_model(...)` produces clustered data with fixed + random effects for demos, teaching, and power analyses. `estimate_sd(mean, min, max)` recovers an SD from a known range.
|
|
115
|
+
- **Biostats Utilities**: Ported functions from `fifer` for common statistical tasks.
|
|
116
|
+
|
|
117
|
+
## Typical workflow (v0.6.x)
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import pandas as pd
|
|
121
|
+
from pyflexplot import flexplot, diagnose
|
|
122
|
+
|
|
123
|
+
df = pd.read_csv("data.csv")
|
|
124
|
+
|
|
125
|
+
# 1. Diagnose the model fit before plotting.
|
|
126
|
+
diag = diagnose("y ~ x + z", data=df)
|
|
127
|
+
|
|
128
|
+
# 2. Plot with uncertainty bands and overlay competing smoothers.
|
|
129
|
+
p = flexplot(
|
|
130
|
+
"y ~ x + z", data=df,
|
|
131
|
+
uncertainty="ci", # or "prediction" / "bootstrap"
|
|
132
|
+
level=0.95,
|
|
133
|
+
bands=[0.5, 0.8, 0.95], # nested ribbons (Tufte-style)
|
|
134
|
+
overlay=[
|
|
135
|
+
{"method": "loess", "label": "LOESS smoother"},
|
|
136
|
+
{"method": "rlm", "label": "Robust regression"},
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
p.draw()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
See `docs/examples/diagnostics_workflow.md` for a longer walk-through.
|
|
143
|
+
|
|
144
|
+
## Continuous Integration
|
|
145
|
+
|
|
146
|
+
Three GitHub Actions workflows cover the test surface, kept independent so
|
|
147
|
+
each runs in its own clean environment:
|
|
148
|
+
|
|
149
|
+
* `.github/workflows/python-app.yml` -- core test matrix across Python
|
|
150
|
+
3.10, 3.11, 3.12, 3.13. No torch or keras required; tests that need
|
|
151
|
+
them skip cleanly via `pytest.importorskip`.
|
|
152
|
+
* `.github/workflows/torch.yml` -- installs `torch` (CPU build) and runs
|
|
153
|
+
the torch-flex_nn tests. Triggered on every push to `main`, on PRs
|
|
154
|
+
touching `src/pyflexplot/flex_nn.py` or the torch tests, and on a
|
|
155
|
+
weekly schedule so we catch upstream torch regressions.
|
|
156
|
+
* `.github/workflows/keras3.yml` -- installs `keras[jax]` and runs
|
|
157
|
+
`tests/test_flex_nn_keras.py` against a Keras 3 install. Same
|
|
158
|
+
trigger pattern as `torch.yml` plus a weekly schedule.
|
|
159
|
+
|
|
160
|
+
All workflows upload coverage via `pytest-cov`.
|
|
161
|
+
|
|
162
|
+
## Changelog
|
|
163
|
+
|
|
164
|
+
`README.md` now includes a concise release log. The canonical full history
|
|
165
|
+
remains in [`CHANGELOG.md`](CHANGELOG.md).
|
|
166
|
+
|
|
167
|
+
### 0.8.2 (2026-08-31)
|
|
168
|
+
- Added mixed-effects support in `flexplot()`:
|
|
169
|
+
- `method="mixedlm"` / `method="lmer"` for linear mixed models via `statsmodels.MixedLM`
|
|
170
|
+
- `method="glmer"` for binomial mixed models via `statsmodels.BinomialBayesMixedGLM`
|
|
171
|
+
- `random_effects=` supports a group column name or compact forms like `(1|group)` and `(1 + x|group)`.
|
|
172
|
+
- Added mixed-model tests in `tests/test_mixed_models.py`.
|
|
173
|
+
- Updated parity docs to reflect that mixed models are now available with explicit `lme4`-parity caveats.
|
|
174
|
+
|
|
175
|
+
### 0.8.1 (2026-08-31)
|
|
176
|
+
- Added formula-function transformations in `flexplot()` (`log(x)`, `sqrt(x)`, `exp(x)`, `poly(x, 2)`, `I(...)`) with a safe whitelisted evaluator.
|
|
177
|
+
- Added multivariate numeric slotting parity for `y ~ x1 + x2` / `y ~ x1 + x2 | g` by auto-binning slot-2+/given numeric variables into `<var>_binned`.
|
|
178
|
+
- Added R-style defaults/parity behavior: categorical-vs-numeric alpha defaults, categorical jitter semantics, and low-cardinality numeric auto-categorization (`<5` unique).
|
|
179
|
+
- Added explicit R-style `compare_fits()` compatibility args (`report_se`, `re`, `num_points`, `clusters`) with transparent no-op warning.
|
|
180
|
+
- Added `third_eye()` placeholder endpoint (exported in package API) that raises `NotImplementedError` with guidance.
|
|
181
|
+
- Added/updated parity tests; test suite status at release: `509 passed, 4 skipped`.
|
|
182
|
+
|
|
183
|
+
### 0.8.0 (2026-08-31)
|
|
184
|
+
- Implemented the major parity batch from the v0.8.0 review:
|
|
185
|
+
- Non-nested `model_comparison()` support and `pred_difference`.
|
|
186
|
+
- `estimates()` factor-level tables + mean differences.
|
|
187
|
+
- `added_plot()` R semantics alignment.
|
|
188
|
+
- R spread tokens/defaults, `ghost_line` panel semantics, and standalone accessors.
|
|
189
|
+
- Included release cleanup (`.gitignore` hardening and parity script addition).
|
|
190
|
+
|
|
191
|
+
### 0.6.2 (2026-08-30)
|
|
192
|
+
- **R-style interaction syntax accepted by the formula parser.** `y ~ x*z` and `y ~ x:z` no longer raise "missing column"; the parser expands `*` to `+` + `:` for column lookup and preserves interaction terms in `all_x`. `flexplot()` emits a `UserWarning` reminding the user that v0.6.x fits remain additive; v0.7.0 will add `interaction_model=True`. 6 new tests in `tests/test_core.py`.
|
|
193
|
+
|
|
194
|
+
### 0.6.1 (2026-08-30)
|
|
195
|
+
- **Fixed dead binomial branch in `flexplot()`.** `pd.api.types.is_numeric_dtype([0, 1])` returns True, so int/float binary y was always routed to the LM/loess branch and the binomial GLM branch was unreachable. Added a binary pre-check that detects unique values ⊆ {0, 1} *before* the numeric-dtype dispatch. Numeric `[0, 1]` y now draws a sigmoid curve (was a straight LM line); string `["yes", "no"]` and multi-level numeric `[0, 1, 2]` behavior unchanged. 3 new tests + 1 updated regression test.
|
|
196
|
+
|
|
197
|
+
### 0.6.0 (2026-08-30)
|
|
198
|
+
- **`diagnose(formula, data)` — auto data-quality diagnostics.** Runs missingness (per-column counts and pattern heuristic), Cook's distance for outliers (default `4/n`), Ramsey RESET for functional form, and Breusch-Pagan for heteroscedasticity. Returns a structured dict; pass `verbose=True` for a one-paragraph terminal/email/log summary. New module `pyflexplot.quality`. 19 new tests.
|
|
199
|
+
|
|
200
|
+
### 0.5.0 (2026-08-30)
|
|
201
|
+
- **`overlay` parameter on `flexplot()`.** Overlay multiple smoothers (`lm`, `loess`, `rlm`, `glm`, ...) on the same axes with per-smoother uncertainty bands. Each entry takes a `color` (cycles through a 5-color palette) and optional `label` / `uncertainty` / `level`. When any entry has a `label`, a manual color scale adds a legend. The binomial branch restricts overlay to `method="glm"`; other methods raise. 14 new tests.
|
|
202
|
+
|
|
203
|
+
### 0.4.0 (2026-08-30)
|
|
204
|
+
- **`uncertainty` parameter on `flexplot()`.** First-class confidence / prediction / bootstrap bands around every fitted line. New module `pyflexplot.uncertainty` exposes `validate_uncertainty_params`, `compute_bootstrap_ci`, `compute_prediction_band`, `format_band`.`..- 35 new tests, full suite 199 passed / 1 skipped (keras not installed), no regressions.
|
|
205
|
+
|
|
206
|
+
### 0.3.0 (2026-08-28)
|
|
207
|
+
- **`visualize()` now accepts `NeuralNetFit` wrappers** (DESIGN-7 from the v0.2.2 review). The duck-type dispatch avoids importing `flex_nn` at module load time, so the core module stays cheap when neural-net support isn't needed. The output mirrors the statsmodels `visualize()`: predicted-vs-actual line on top of a scatter. 7 new tests in `tests/test_design_followups.py::TestVisualizeNeuralNetFit`.
|
|
208
|
+
- **`flexplot()` method validation** (DESIGN-4) — unknown `method` values now raise `ValueError` instead of silently producing no smooth. The `method` parameter is checked against a `{auto, lm, loess}` whitelist at entry.
|
|
209
|
+
- **`flexplot()` given-variable validation** (DESIGN-3) — formulas with 3+ variables after `|` now raise `ValueError` instead of silently dropping `given[2:]`. Two-given is the maximum; `facet_grid` only supports row+column.
|
|
210
|
+
- **`bluepill.mixed_model(polynomials=...)` no longer requires `to`** (DESIGN-6). Split the interaction/polynomial validator into two: interactions still require `from`/`to`/`coef`; polynomials only need `from`/`coef`. The R-compatible shape (`from`/`to`/`coef`) is still accepted on polynomials but `to` is ignored for backwards compatibility.
|
|
211
|
+
- **Hypothesis property tests** — 14 new property-based tests in `tests/test_property_based.py` covering the formula parser (round-trip identity, deterministic parsing, malformed-input rejection across hundreds of generated formulas) and `mixed_model` rescaling invariants (output mean/SD match the declared spec within sampling tolerance; categorical columns only take declared levels). Each test runs 10-50 generated examples.
|
|
212
|
+
- Total test surface: 132 → 164 (32 new). All tests pass; no API breakage.
|
|
213
|
+
|
|
214
|
+
### 0.2.2 (2026-08-28)
|
|
215
|
+
- **Critical bug fix (bluepill)**: `mixed_model()` had an off-by-one column index that made the last predictor a constant column (its declared mean, zero variance) and shifted all other predictors by one column. The README's example produced `ses = 55.0` for every row. Fixed.
|
|
216
|
+
- **Critical bug fix (flex_nn)**: `permutation_importance()` crashed with `UnboundLocalError` on five of the eleven declared metric names (`auc`, `precision`, `recall`, `f1`, `loss`) because the scorer dispatch branches were missing. Added rank-based AUC, thresholded binary precision/recall/F1, and `loss` (MSE) scorers; the unreachable `if direction is None:` fallback block is gone.
|
|
217
|
+
- **Critical bug fix (bluepill)**: tuple-of-strings categorical specs (valid per the `VarSpec` type hint) were misidentified as continuous specs and crashed with `ValueError`. Extracted the numeric-detection logic into a shared `_is_continuous_spec()` helper so validation and execution agree.
|
|
218
|
+
- Added 20 contract-level regression tests in `tests/test_bluepill_correctness.py` and `tests/test_flex_nn_correctness.py`. They check that predictors have non-zero variance, that the strongest coefficient ranks first in permutation importance, that all declared metrics work end-to-end, and that tuple specs round-trip. Each of these tests fails on the pre-v0.2.2 code path; all 20 pass now. Total: 132 tests passing.
|
|
219
|
+
- Other cleanups from the v0.2.2 review: replaced `from plotnine import *` with explicit imports in `core.py` and `sem.py`, removed the unused `patsy` import, restored the model's original `training` flag in `_keras_predict()` (was permanently mutating caller state), and added an explicit "experimental / not yet implemented" note to `estimates()`.
|
|
220
|
+
|
|
221
|
+
### 0.2.1 (2026-08-28)
|
|
222
|
+
- Hardened the Keras 3 path in `pyflexplot.flex_nn`: predictions now go through a dedicated `_keras_predict()` helper that passes `training=False` (so `Dropout`/`BatchNorm` behave deterministically) and falls back gracefully for custom `Model` subclasses whose `predict()` doesn't accept the `training` kwarg.
|
|
223
|
+
- Added `tests/test_flex_nn_keras.py` with 14 keras-specific tests (skip when keras isn't installed; verified against `keras==3.15.1` + `jax` backend). Total test surface: 110 (core + torch) + 14 (keras when available) = 124.
|
|
224
|
+
- CI: split into three workflows -- `python-app.yml` (core, no optional deps, Python 3.10-3.13), `torch.yml` (torch CPU install, weekly schedule to catch upstream regressions), `keras3.yml` (keras[jax] install, weekly schedule, PRs touching flex_nn).
|
|
225
|
+
- Extended the example notebook with a Keras 3 walk-through and added a README section describing the optional install + backend selection.
|
|
226
|
+
|
|
227
|
+
### 0.2.0 (2026-08-28)
|
|
228
|
+
- Added `pyflexplot.flex_nn` — torch-default wrappers for fitting and visualizing neural networks. `NeuralNetFit` bundles a fitted `torch.nn.Module` (or `keras.Model`) with the metadata needed to plug into `compare_fits`. `permutation_importance()` provides column-shuffling variable importance.
|
|
229
|
+
- Added `pyflexplot.bluepill` — port of Dustin Fife's `bluepill` R package. `estimate_sd()` recovers an SD from a known mean and min/max range; `mixed_model()` generates clustered synthetic data with fixed + random effects, interactions, and polynomial terms.
|
|
230
|
+
- Dropped the aspirational `flexifiers` bullet from the "Included R Packages" list (no corresponding R package was found).
|
|
231
|
+
- Added 50 new tests across the two modules (60 → 110). Test suite uses `pytest.importorskip("torch")` so the package still imports cleanly without torch installed, but `flex_nn` tests skip when torch is absent.
|
|
232
|
+
|
|
233
|
+
### 0.1.1 (2026-06-20)
|
|
234
|
+
- Hardened `parse_flexplot_formula()` validation (exactly one `~`, at most one `|`, trimmed tokens, intercept-only handling, empty outcome/predictor rejection).
|
|
235
|
+
- Added input validation to `flexplot()` for empty DataFrames, missing columns, and numeric column types; color/group aesthetics are now included in the initial `aes()` so all geoms receive them.
|
|
236
|
+
- Fixed `hopper_plot()` against current `semopy` (`calc_sigma()` and `mx_cov` handling) and added real `semopy` smoke tests.
|
|
237
|
+
- Hardened `fit_beta_prior()` with range checks for `successes`/`totals`, zero-variance guard, optimizer success, and finite parameter validation.
|
|
238
|
+
- Fixed `added_plot()` residual alignment using index-aware `pd.concat(..., join='inner')` with length validation.
|
|
239
|
+
- Fixed `model_comparison()` LRT to enforce correct order and validate required model attributes.
|
|
240
|
+
- Fixed `visualize()` to identify the first non-intercept term robustly and raise exceptions instead of returning strings.
|
|
241
|
+
- Fixed `compare_fits()` prediction alignment to `data.index` with length validation.
|
|
242
|
+
- Fixed `add_ebb_estimate()` to use scalar/array addition and validate columns/dtypes.
|
|
243
|
+
- Fixed `sem.py` functions to raise typed exceptions instead of returning error strings.
|
|
244
|
+
- Expanded test coverage for all P0 and selected P1 paths.
|
|
245
|
+
|
|
246
|
+
### 0.1.0
|
|
247
|
+
- Initial package skeleton with `flexplot`, `visualize`, `compare_fits`, SEM helpers, and `fifer` utilities.
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
MIT
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=67", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "py-flexplot"
|
|
7
|
+
version = "0.8.2"
|
|
8
|
+
description = "Python port of Dustin Fife's R visualization and analysis packages (flexplot, fifer, etc.)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "EzraAir555", email = "ezraair555@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy>=1.23",
|
|
17
|
+
"pandas>=1.5",
|
|
18
|
+
"scipy>=1.11",
|
|
19
|
+
"plotnine>=0.12",
|
|
20
|
+
"statsmodels>=0.14",
|
|
21
|
+
"patsy>=0.5",
|
|
22
|
+
"scikit-learn>=1.2",
|
|
23
|
+
"semopy>=2.3"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/ezraair555/py-flexplot"
|
|
28
|
+
Documentation = "https://github.com/ezraair555/py-flexplot/tree/main/docs"
|
|
29
|
+
Repository = "https://github.com/ezraair555/py-flexplot"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
package-dir = {"" = "src"}
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
pythonpath = ["src"]
|
|
40
|
+
addopts = "-ra --strict-markers"
|
|
41
|
+
# Docstring doctests are run explicitly via:
|
|
42
|
+
# python3 -m pytest --doctest-modules src/pyflexplot/core.py src/pyflexplot/quality.py
|
|
43
|
+
# (We don't add --doctest-modules to addopts because it would re-collect
|
|
44
|
+
# existing test files under different names and slow the suite.)
|