goad-toolkit 0.2.2__tar.gz → 0.2.3__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.
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/MCP_SERVER.md +7 -7
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/PKG-INFO +3 -2
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/02-pipelines.md +5 -7
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/03-plot-composition.md +7 -3
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/04-five-families.md +6 -4
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/08-api-reference.md +16 -2
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/pyproject.toml +2 -1
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/datatransforms.py +40 -1
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/visualizer.py +114 -0
- goad_toolkit-0.2.3/tests/test_datatransforms.py +66 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/tests/test_visualizer.py +36 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/uv.lock +48 -1
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/settings.local.json +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/.gitignore +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/.python-version +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/CHANGELOG.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/MCP_SERVER.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/README.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/demo/linear.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/01-goal-oriented-analysis.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/02-pipelines.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/03-plot-composition.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/04-five-families.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/05-distributions.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/06-models-and-residuals.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/07-visual-critique.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/08-api-reference.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/09-analysis-method.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/10-teaching-path.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/README.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/goad_mcp.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/img/distribution_fit.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/img/goaded.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/img/linear_results.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/img/residuals.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/img/zscores.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/pyproject.toml +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/__init__.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/analytics.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/cli.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/config.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/dataprocessor.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/datatransforms.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/distributions.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/filehandler.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/models.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/visualizer.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/tests/test_cli.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/tests/test_distributions.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/tests/test_filehandler.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/tests/test_visualizer.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/uv.lock +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.gitignore +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.python-version +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/.gitignore +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/logs/autonomous/save-195516.log +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/logs/autonomous/save-195720.log +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/logs/autonomous/save-195933.log +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/logs/autonomous/save-200831.log +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/logs/autonomous/save-201034.log +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/logs/hook-errors.log +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/logs/memory-2026-08-10.log +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/now.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/capture-alive +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/capture-alive.d/90574e63-97cd-4c11-87e5-23b656c56fd9 +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/case-divergence +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/last-ndc.ts +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/last-save-ts +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/last-save.json +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/now-day +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/post-tool-ran +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/save-session.pid +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/tmp/session-slug +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.remember/today-2026-08-10.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/CHANGELOG.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/README.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/demo/linear.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/01-goal-oriented-analysis.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/05-distributions.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/06-models-and-residuals.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/07-visual-critique.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/09-analysis-method.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/10-teaching-path.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/docs/README.md +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/goad_mcp.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/img/distribution_fit.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/img/goaded.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/img/linear_results.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/img/residuals.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/img/zscores.png +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/__init__.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/analytics.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/cli.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/config.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/dataprocessor.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/distributions.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/filehandler.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/src/goad_toolkit/models.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/tests/test_cli.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/tests/test_distributions.py +0 -0
- {goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/tests/test_filehandler.py +0 -0
|
@@ -103,13 +103,13 @@ You pin a version with `GOAD_REF` — a git ref (branch, tag, or commit). That o
|
|
|
103
103
|
selects both the script (in the URL) and the docs it fetches (inside the script), so a whole
|
|
104
104
|
cohort runs exactly the same thing and the two cannot drift apart.
|
|
105
105
|
|
|
106
|
-
> **Note:** the docs and this server land in **v0.2.
|
|
107
|
-
> `GOAD_REF`
|
|
106
|
+
> **Note:** the docs and this server land in **v0.2.2**.
|
|
107
|
+
> You can also point `GOAD_REF` to a branch or work from a local clone (see below).
|
|
108
108
|
|
|
109
109
|
### Claude Code
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
claude mcp add goad -e GOAD_REF=v0.2.
|
|
112
|
+
claude mcp add goad -e GOAD_REF=v0.2.2 -- \
|
|
113
113
|
sh -c 'uv run --no-project https://raw.githubusercontent.com/raoulg/goad_toolkit/$GOAD_REF/goad_mcp.py'
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -123,7 +123,7 @@ Add to `.cursor/mcp.json` (per-project) or `~/.cursor/mcp.json` (global):
|
|
|
123
123
|
"goad": {
|
|
124
124
|
"command": "sh",
|
|
125
125
|
"args": ["-c", "uv run --no-project https://raw.githubusercontent.com/raoulg/goad_toolkit/$GOAD_REF/goad_mcp.py"],
|
|
126
|
-
"env": { "GOAD_REF": "v0.2.
|
|
126
|
+
"env": { "GOAD_REF": "v0.2.2" }
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
}
|
|
@@ -137,7 +137,7 @@ Add to `~/.codex/config.toml`:
|
|
|
137
137
|
[mcp_servers.goad]
|
|
138
138
|
command = "sh"
|
|
139
139
|
args = ["-c", "uv run --no-project https://raw.githubusercontent.com/raoulg/goad_toolkit/$GOAD_REF/goad_mcp.py"]
|
|
140
|
-
env = { GOAD_REF = "v0.2.
|
|
140
|
+
env = { GOAD_REF = "v0.2.2" }
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
### Claude Desktop
|
|
@@ -151,7 +151,7 @@ app:
|
|
|
151
151
|
"goad": {
|
|
152
152
|
"command": "sh",
|
|
153
153
|
"args": ["-c", "uv run --no-project https://raw.githubusercontent.com/raoulg/goad_toolkit/$GOAD_REF/goad_mcp.py"],
|
|
154
|
-
"env": { "GOAD_REF": "v0.2.
|
|
154
|
+
"env": { "GOAD_REF": "v0.2.2" }
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
}
|
|
@@ -187,7 +187,7 @@ add with the new tag:
|
|
|
187
187
|
|
|
188
188
|
```bash
|
|
189
189
|
claude mcp remove goad -s user
|
|
190
|
-
claude mcp add goad -s user -e GOAD_REF=v0.2.
|
|
190
|
+
claude mcp add goad -s user -e GOAD_REF=v0.2.2 -- \
|
|
191
191
|
sh -c 'uv run --no-project https://raw.githubusercontent.com/raoulg/goad_toolkit/$GOAD_REF/goad_mcp.py'
|
|
192
192
|
```
|
|
193
193
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: goad-toolkit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: An extensible toolkit for Goal Oriented Analysis of Data
|
|
5
5
|
Project-URL: Github, https://github.com/raoulg/goad_toolkit
|
|
6
6
|
Author-email: raoul grouls <Raoul.Grouls@han.nl>
|
|
@@ -13,6 +13,7 @@ Requires-Dist: pydantic>=2.10.6
|
|
|
13
13
|
Requires-Dist: requests>=2.32.3
|
|
14
14
|
Requires-Dist: scipy>=1.15.2
|
|
15
15
|
Requires-Dist: seaborn>=0.13.2
|
|
16
|
+
Requires-Dist: statsmodels>=0.14.4
|
|
16
17
|
Requires-Dist: tqdm>=4.67.1
|
|
17
18
|
Provides-Extra: parquet
|
|
18
19
|
Requires-Dist: pyarrow>=19.0.0; extra == 'parquet'
|
|
@@ -58,6 +58,7 @@ input frame is safe, and the steps stay cheap.
|
|
|
58
58
|
| `SelectDataRange` | keep rows in a date range | `start_date`, `end_date` |
|
|
59
59
|
| `RollingAvg` | rolling mean, drops the leading NaNs | `column`, `window`, `rename` |
|
|
60
60
|
| `ZScaler` | standardise to mean 0, std 1 | `column`, `rename` |
|
|
61
|
+
| `TimeFeatures` | derive calendar columns from a timestamp | `column`, `features` |
|
|
61
62
|
|
|
62
63
|
`rename=True` writes to a new column (`deaths_shifted`, `deaths_zscore`, …) instead of
|
|
63
64
|
overwriting. Prefer it. An overwritten column is a step you cannot debug, and the whole
|
|
@@ -75,20 +76,17 @@ Subclass `TransformBase` and implement `transform`. That is the entire contract.
|
|
|
75
76
|
from goad_toolkit.datatransforms import TransformBase
|
|
76
77
|
import pandas as pd
|
|
77
78
|
|
|
78
|
-
class
|
|
79
|
-
"""
|
|
79
|
+
class IsWeekend(TransformBase):
|
|
80
|
+
"""Flag Saturday/Sunday from a timestamp column."""
|
|
80
81
|
|
|
81
82
|
def transform(self, data: pd.DataFrame, column: str) -> pd.DataFrame:
|
|
82
83
|
ts = pd.to_datetime(data[column])
|
|
83
|
-
data["
|
|
84
|
-
data["hour"] = ts.dt.hour
|
|
85
|
-
data["day_name"] = ts.dt.day_name()
|
|
86
|
-
data["isoweek"] = ts.dt.isocalendar().week
|
|
84
|
+
data["is_weekend"] = ts.dt.dayofweek >= 5
|
|
87
85
|
return data
|
|
88
86
|
```
|
|
89
87
|
|
|
90
88
|
```python
|
|
91
|
-
pipeline.add(
|
|
89
|
+
pipeline.add(IsWeekend, column="timestamp")
|
|
92
90
|
```
|
|
93
91
|
|
|
94
92
|
What the base class does for you:
|
|
@@ -151,6 +151,8 @@ step 3.
|
|
|
151
151
|
| `ComparePlotDate` | `ComparePlot` + `VerticalDate` | |
|
|
152
152
|
| `BarWithDates` | bars with a month locator on the x axis | |
|
|
153
153
|
| `ResidualPlot` | `BarWithDates` + `VerticalDate` | the residual-over-time view |
|
|
154
|
+
| `DecomposePlot` | `seasonal_decompose`, one panel each | observed/trend/seasonal/residual, overrides `plot` |
|
|
155
|
+
| `ACFPlot` | `acf` as bars with a confidence band | see [Five families §4.2](04-five-families.md) |
|
|
154
156
|
| `HistogramPlot` | `sns.histplot`, `stat="density"` | density so a pdf can be overlaid |
|
|
155
157
|
| `DistPlot` | a scipy distribution's pdf or pmf | estimates its own x-range from `ppf` |
|
|
156
158
|
| `PlotFits` | histogram + fitted pdf, one panel per fit | see [Distributions](05-distributions.md) |
|
|
@@ -216,9 +218,11 @@ lines of code.
|
|
|
216
218
|
`self.ax.axvline(...)`, `self.fig.suptitle(...)`. Keeping to them is what makes
|
|
217
219
|
`plot_on_axes` reliable.
|
|
218
220
|
|
|
219
|
-
`PlotFits`
|
|
220
|
-
create before the figure exists
|
|
221
|
-
|
|
221
|
+
`PlotFits` and `DecomposePlot` override `plot` rather than `build`, because both need to know
|
|
222
|
+
how many panels to create before the figure exists — variable for `PlotFits` (one per fit),
|
|
223
|
+
fixed at four for `DecomposePlot`. Either way, `build` alone cannot decide the panel count
|
|
224
|
+
before `create_figure` runs, so both leave it as a no-op. This is the exception, not a pattern
|
|
225
|
+
to copy for a plot that only ever needs one axis.
|
|
222
226
|
|
|
223
227
|
---
|
|
224
228
|
|
|
@@ -76,10 +76,12 @@ the period is long relative to your intuition.
|
|
|
76
76
|
the same data support different stories. Choose the window from the mechanism you are
|
|
77
77
|
studying, before you look.
|
|
78
78
|
|
|
79
|
-
**In GOAD:** `ShiftValues`, `DiffValues`, `RollingAvg`, `SelectDataRange` are
|
|
80
|
-
steps, so the smoothing window is a named parameter you can change in one line
|
|
81
|
-
`ComparePlot` puts raw and smoothed on the same axes; `VerticalDate` marks the
|
|
82
|
-
claim something changed, with a label, on the plot.
|
|
79
|
+
**In GOAD:** `ShiftValues`, `DiffValues`, `RollingAvg`, `SelectDataRange`, `TimeFeatures` are
|
|
80
|
+
all pipeline steps, so the smoothing window is a named parameter you can change in one line
|
|
81
|
+
and rerun. `ComparePlot` puts raw and smoothed on the same axes; `VerticalDate` marks the
|
|
82
|
+
moment you claim something changed, with a label, on the plot. `DecomposePlot` wraps
|
|
83
|
+
`seasonal_decompose` into the four-panel observed/trend/seasonal/residual view; `ACFPlot`
|
|
84
|
+
wraps `acf` with its confidence band, for finding a period you did not already suspect.
|
|
83
85
|
|
|
84
86
|
---
|
|
85
87
|
|
|
@@ -83,10 +83,15 @@ class TransformBase(ABC):
|
|
|
83
83
|
| `SelectDataRange` | `start_date: str, end_date: str` |
|
|
84
84
|
| `RollingAvg` | `column: str, window: int, rename: bool = False` |
|
|
85
85
|
| `ZScaler` | `column: str, rename: bool = False` |
|
|
86
|
+
| `TimeFeatures` | `column: str, features: Optional[List[str]] = None` |
|
|
86
87
|
|
|
87
88
|
`rename=True` writes to `{column}_shifted` / `_diff` / `_rolling_avg` / `_zscore` instead of
|
|
88
89
|
overwriting.
|
|
89
90
|
|
|
91
|
+
`TimeFeatures.AVAILABLE` is `("date", "hour", "day_name", "isoweek", "year_week")`;
|
|
92
|
+
`TimeFeatures.DEFAULT` — used when `features` is omitted — is `("day_name", "isoweek",
|
|
93
|
+
"year_week")`. A feature named the same as `column` overwrites it.
|
|
94
|
+
|
|
90
95
|
```python
|
|
91
96
|
class Pipeline:
|
|
92
97
|
def add(self, transform_class: Type[T], name: Optional[str] = None, **kwargs) -> "Pipeline"
|
|
@@ -267,12 +272,17 @@ class BasePlot(ABC):
|
|
|
267
272
|
| `ResidualPlot` | `data, x, y, date, datelabel, interval: int = 1` |
|
|
268
273
|
| `HistogramPlot` | `data: np.ndarray, bins=None, kde=False, color="skyblue", alpha=0.7, **kwargs` |
|
|
269
274
|
| `DistPlot` | `distribution, x_range=None, samples=1000, color="crimson", linewidth=2, label=None, **kwargs` |
|
|
275
|
+
| `ACFPlot` | `data, nlags=40, alpha=0.05, color="steelblue", band_color="gray", **kwargs` |
|
|
270
276
|
|
|
271
277
|
```python
|
|
272
278
|
class PlotFits(BasePlot):
|
|
273
279
|
def plot(self, data: np.ndarray,
|
|
274
280
|
fit_results: List[Result],
|
|
275
281
|
fitplotsettings: FitPlotSettings) -> Figure
|
|
282
|
+
|
|
283
|
+
class DecomposePlot(BasePlot):
|
|
284
|
+
def plot(self, data: pd.DataFrame, column: str, period: int,
|
|
285
|
+
model: str = "additive", **kwargs) -> Figure
|
|
276
286
|
```
|
|
277
287
|
|
|
278
288
|
Every `build` returns `(self.fig, self.ax)`, and every plot draws on `self.ax` / `self.fig`
|
|
@@ -309,12 +319,16 @@ of `likelihood`, `ks`, `combined`; `--method` is passed to `scipy.stats.fit`.
|
|
|
309
319
|
|
|
310
320
|
## 8.10 Rough edges worth knowing
|
|
311
321
|
|
|
312
|
-
- `PlotFits`
|
|
313
|
-
|
|
322
|
+
- `PlotFits` and `DecomposePlot` override `plot` rather than `build`; their `build` is a
|
|
323
|
+
no-op. Both need the panel count decided before `create_figure` runs — from the fit results
|
|
324
|
+
for `PlotFits`, always four for `DecomposePlot` — which `build` alone cannot do.
|
|
314
325
|
- Fits that fail come back as `FailedFit` values in the result list rather than raising, so
|
|
315
326
|
a list of results can contain both kinds and needs an `isinstance` check.
|
|
316
327
|
- `Pipeline.apply` copies the frame once and then mutates that copy in place, so transforms
|
|
317
328
|
see each other's columns — the order you add steps in matters.
|
|
329
|
+
- `ACFPlot` raises `ValueError` if the data contains NaNs, rather than passing them through.
|
|
330
|
+
`statsmodels.tsa.stattools.acf` does not raise on missing values — it silently returns an
|
|
331
|
+
all-NaN result — so a decomposition's residual needs `.dropna()` before it reaches `ACFPlot`.
|
|
318
332
|
|
|
319
333
|
---
|
|
320
334
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "goad-toolkit"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "An extensible toolkit for Goal Oriented Analysis of Data"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -16,6 +16,7 @@ dependencies = [
|
|
|
16
16
|
"requests>=2.32.3",
|
|
17
17
|
"scipy>=1.15.2",
|
|
18
18
|
"seaborn>=0.13.2",
|
|
19
|
+
"statsmodels>=0.14.4",
|
|
19
20
|
"tqdm>=4.67.1",
|
|
20
21
|
]
|
|
21
22
|
urls = {Github = "https://github.com/raoulg/goad_toolkit"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Any, Dict, Optional, Type, TypeVar
|
|
2
|
+
from typing import Any, Dict, List, Optional, Type, TypeVar
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from tqdm import tqdm
|
|
@@ -122,6 +122,45 @@ class ZScaler(TransformBase):
|
|
|
122
122
|
return data
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
class TimeFeatures(TransformBase):
|
|
126
|
+
"""Derive calendar features from a timestamp column.
|
|
127
|
+
|
|
128
|
+
`features` picks which columns to write, from `"date"`, `"hour"`,
|
|
129
|
+
`"day_name"`, `"isoweek"`, `"year_week"`. Defaults to `"day_name"`,
|
|
130
|
+
`"isoweek"`, `"year_week"` — the three lesson 1 already relies on.
|
|
131
|
+
A feature named the same as `column` overwrites it.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
AVAILABLE = ("date", "hour", "day_name", "isoweek", "year_week")
|
|
135
|
+
DEFAULT = ("day_name", "isoweek", "year_week")
|
|
136
|
+
|
|
137
|
+
def transform(
|
|
138
|
+
self,
|
|
139
|
+
data: pd.DataFrame,
|
|
140
|
+
column: str,
|
|
141
|
+
features: Optional[List[str]] = None,
|
|
142
|
+
) -> pd.DataFrame:
|
|
143
|
+
selected = list(features) if features is not None else list(self.DEFAULT)
|
|
144
|
+
unknown = set(selected) - set(self.AVAILABLE)
|
|
145
|
+
if unknown:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"Unknown feature(s) {sorted(unknown)}; choose from {self.AVAILABLE}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
ts = pd.to_datetime(data[column])
|
|
151
|
+
if "date" in selected:
|
|
152
|
+
data["date"] = ts.dt.date
|
|
153
|
+
if "hour" in selected:
|
|
154
|
+
data["hour"] = ts.dt.hour
|
|
155
|
+
if "day_name" in selected:
|
|
156
|
+
data["day_name"] = ts.dt.day_name()
|
|
157
|
+
if "isoweek" in selected:
|
|
158
|
+
data["isoweek"] = ts.dt.isocalendar().week
|
|
159
|
+
if "year_week" in selected:
|
|
160
|
+
data["year_week"] = ts.dt.strftime("%Y-%W")
|
|
161
|
+
return data
|
|
162
|
+
|
|
163
|
+
|
|
125
164
|
class Pipeline:
|
|
126
165
|
"""Pipeline for chaining data transformations."""
|
|
127
166
|
|
|
@@ -11,6 +11,8 @@ import seaborn as sns
|
|
|
11
11
|
from loguru import logger
|
|
12
12
|
from matplotlib.axes import Axes
|
|
13
13
|
from matplotlib.figure import Figure
|
|
14
|
+
from statsmodels.tsa.seasonal import seasonal_decompose
|
|
15
|
+
from statsmodels.tsa.stattools import acf
|
|
14
16
|
|
|
15
17
|
from goad_toolkit.analytics import FitResult, Result
|
|
16
18
|
|
|
@@ -248,6 +250,118 @@ class ResidualPlot(BasePlot):
|
|
|
248
250
|
return self.fig, self.ax
|
|
249
251
|
|
|
250
252
|
|
|
253
|
+
class DecomposePlot(BasePlot):
|
|
254
|
+
"""Decompose a series into observed, trend, seasonal and residual panels."""
|
|
255
|
+
|
|
256
|
+
def plot(
|
|
257
|
+
self,
|
|
258
|
+
data: pd.DataFrame,
|
|
259
|
+
column: str,
|
|
260
|
+
period: int,
|
|
261
|
+
model: str = "additive",
|
|
262
|
+
**kwargs,
|
|
263
|
+
) -> Figure:
|
|
264
|
+
"""
|
|
265
|
+
Run `statsmodels.tsa.seasonal.seasonal_decompose` and draw each
|
|
266
|
+
component on its own panel.
|
|
267
|
+
|
|
268
|
+
Parameters:
|
|
269
|
+
-----------
|
|
270
|
+
data : pd.DataFrame
|
|
271
|
+
Frame with a datetime index and the series to decompose.
|
|
272
|
+
column : str
|
|
273
|
+
Column to decompose.
|
|
274
|
+
period : int
|
|
275
|
+
Number of observations per cycle (e.g. 24 for hourly data with a
|
|
276
|
+
daily cycle, 7 for daily data with a weekly cycle).
|
|
277
|
+
model : str
|
|
278
|
+
`"additive"` or `"multiplicative"`, passed to `seasonal_decompose`.
|
|
279
|
+
**kwargs : Additional keyword arguments passed to `seasonal_decompose`.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
--------
|
|
283
|
+
fig : The created figure, with one panel per component.
|
|
284
|
+
"""
|
|
285
|
+
result = seasonal_decompose(data[column], model=model, period=period, **kwargs)
|
|
286
|
+
components = [
|
|
287
|
+
("Observed", result.observed),
|
|
288
|
+
("Trend", result.trend),
|
|
289
|
+
("Seasonal", result.seasonal),
|
|
290
|
+
("Residual", result.resid),
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
self.settings.subplot_titles = [name for name, _ in components]
|
|
294
|
+
self.fig, axes = self.create_figure(n_plots=len(components))
|
|
295
|
+
|
|
296
|
+
for ax, (_, values) in zip(axes, components):
|
|
297
|
+
comp_data = values.rename("value").reset_index()
|
|
298
|
+
index_col = comp_data.columns[0]
|
|
299
|
+
line = LinePlot(self.settings)
|
|
300
|
+
self.plot_on_axes(line, ax=ax, data=comp_data, x=index_col, y="value")
|
|
301
|
+
|
|
302
|
+
return self.fig
|
|
303
|
+
|
|
304
|
+
def build(self):
|
|
305
|
+
"""No-op: `DecomposePlot` overrides `plot` because the panel count is fixed at four."""
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class ACFPlot(BasePlot):
|
|
310
|
+
"""Bar plot of the autocorrelation function with a Bartlett confidence band."""
|
|
311
|
+
|
|
312
|
+
def build(
|
|
313
|
+
self,
|
|
314
|
+
data: Any,
|
|
315
|
+
nlags: int = 40,
|
|
316
|
+
alpha: float = 0.05,
|
|
317
|
+
color: str = "steelblue",
|
|
318
|
+
band_color: str = "gray",
|
|
319
|
+
**kwargs,
|
|
320
|
+
):
|
|
321
|
+
"""
|
|
322
|
+
Plot the autocorrelation function for a series.
|
|
323
|
+
|
|
324
|
+
Parameters:
|
|
325
|
+
-----------
|
|
326
|
+
data : array-like
|
|
327
|
+
Series to compute the ACF for. Must not contain missing values —
|
|
328
|
+
drop NaNs first (a decomposition's residual has them at the edges).
|
|
329
|
+
nlags : int
|
|
330
|
+
Number of lags to compute and plot.
|
|
331
|
+
alpha : float
|
|
332
|
+
Significance level for the confidence band.
|
|
333
|
+
color : str
|
|
334
|
+
Bar color.
|
|
335
|
+
band_color : str
|
|
336
|
+
Confidence band color.
|
|
337
|
+
**kwargs : Additional keyword arguments passed to `ax.bar`.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
--------
|
|
341
|
+
fig, ax : The created figure and axes.
|
|
342
|
+
"""
|
|
343
|
+
if self.ax is None:
|
|
344
|
+
raise ValueError("No axes available for plotting")
|
|
345
|
+
|
|
346
|
+
series = pd.Series(data)
|
|
347
|
+
if series.isna().any():
|
|
348
|
+
raise ValueError(
|
|
349
|
+
"ACFPlot got missing values; statsmodels.tsa.stattools.acf does not "
|
|
350
|
+
"raise on NaN, it silently returns an all-NaN result. Drop NaNs first "
|
|
351
|
+
"(a decomposition's residual has them at the edges)."
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
values, confint = acf(series, nlags=nlags, alpha=alpha, fft=True) # type: ignore
|
|
355
|
+
lags = np.arange(len(values))
|
|
356
|
+
band = (confint[:, 1] - confint[:, 0]) / 2
|
|
357
|
+
|
|
358
|
+
self.ax.fill_between(lags, -band, band, color=band_color, alpha=0.2)
|
|
359
|
+
self.ax.axhline(0, color="black", linewidth=0.8)
|
|
360
|
+
self.ax.bar(lags, values, color=color, width=0.3, **kwargs)
|
|
361
|
+
|
|
362
|
+
return self.fig, self.ax
|
|
363
|
+
|
|
364
|
+
|
|
251
365
|
class HistogramPlot(BasePlot):
|
|
252
366
|
"""Plot a histogram using seaborn."""
|
|
253
367
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from goad_toolkit.datatransforms import Pipeline, TimeFeatures
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture
|
|
8
|
+
def data() -> pd.DataFrame:
|
|
9
|
+
# 2024-01-01 is a Monday
|
|
10
|
+
timestamps = pd.date_range("2024-01-01 08:00", periods=10, freq="6h")
|
|
11
|
+
return pd.DataFrame({"timestamp": timestamps, "value": range(10)})
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_default_features_match_lesson_one(data):
|
|
15
|
+
result = TimeFeatures(column="timestamp")(data.copy())
|
|
16
|
+
|
|
17
|
+
assert "day_name" in result.columns
|
|
18
|
+
assert "isoweek" in result.columns
|
|
19
|
+
assert "year_week" in result.columns
|
|
20
|
+
assert "date" not in result.columns
|
|
21
|
+
assert "hour" not in result.columns
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_selected_features_only(data):
|
|
25
|
+
result = TimeFeatures(column="timestamp", features=["date", "hour"])(data.copy())
|
|
26
|
+
|
|
27
|
+
assert "date" in result.columns
|
|
28
|
+
assert "hour" in result.columns
|
|
29
|
+
assert "day_name" not in result.columns
|
|
30
|
+
assert result["hour"].tolist() == [8, 14, 20, 2, 8, 14, 20, 2, 8, 14]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_all_features(data):
|
|
34
|
+
result = TimeFeatures(
|
|
35
|
+
column="timestamp",
|
|
36
|
+
features=list(TimeFeatures.AVAILABLE),
|
|
37
|
+
)(data.copy())
|
|
38
|
+
|
|
39
|
+
for feature in TimeFeatures.AVAILABLE:
|
|
40
|
+
assert feature in result.columns
|
|
41
|
+
assert result["day_name"].iloc[0] == "Monday"
|
|
42
|
+
assert result["isoweek"].iloc[0] == 1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_unknown_feature_raises(data):
|
|
46
|
+
with pytest.raises(ValueError, match="Unknown feature"):
|
|
47
|
+
TimeFeatures(column="timestamp", features=["not_a_feature"])(data.copy())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_feature_overwrites_same_named_source_column():
|
|
51
|
+
# mirrors lesson 1's own pipeline: source column is called "date"
|
|
52
|
+
df = pd.DataFrame({"date": pd.date_range("2024-01-01", periods=3, freq="D")})
|
|
53
|
+
|
|
54
|
+
result = TimeFeatures(column="date", features=["date"])(df)
|
|
55
|
+
|
|
56
|
+
assert result["date"].iloc[0] == pd.Timestamp("2024-01-01").date()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_works_in_a_pipeline(data):
|
|
60
|
+
pipeline = Pipeline()
|
|
61
|
+
pipeline.add(TimeFeatures, column="timestamp", features=["day_name", "hour"])
|
|
62
|
+
|
|
63
|
+
result = pipeline.apply(data)
|
|
64
|
+
|
|
65
|
+
assert "day_name" in result.columns
|
|
66
|
+
assert "hour" in result.columns
|
|
@@ -7,9 +7,11 @@ import pandas as pd # noqa: E402
|
|
|
7
7
|
import pytest # noqa: E402
|
|
8
8
|
|
|
9
9
|
from goad_toolkit.visualizer import ( # noqa: E402
|
|
10
|
+
ACFPlot,
|
|
10
11
|
BarWithDates,
|
|
11
12
|
ComparePlot,
|
|
12
13
|
ComparePlotDate,
|
|
14
|
+
DecomposePlot,
|
|
13
15
|
LinePlot,
|
|
14
16
|
PlotSettings,
|
|
15
17
|
ResidualPlot,
|
|
@@ -111,3 +113,37 @@ def test_compare_plot_date_returns_fig_and_ax(data):
|
|
|
111
113
|
|
|
112
114
|
assert fig is not None
|
|
113
115
|
assert ax is not None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.fixture
|
|
119
|
+
def weekly_cycle() -> pd.DataFrame:
|
|
120
|
+
dates = pd.date_range("2021-01-01", periods=56, freq="D")
|
|
121
|
+
day_of_week = dates.dayofweek.to_numpy()
|
|
122
|
+
seasonal = [0, 1, 2, 3, 4, -8, -8][:7] # weekday-shaped, weekend dip
|
|
123
|
+
values = [10 + i * 0.1 + seasonal[d] for i, d in enumerate(day_of_week)]
|
|
124
|
+
return pd.DataFrame({"value": values}, index=dates)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_decompose_plot_returns_one_panel_per_component(weekly_cycle):
|
|
128
|
+
fig = DecomposePlot(PlotSettings()).plot(
|
|
129
|
+
data=weekly_cycle, column="value", period=7
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
visible_axes = [ax for ax in fig.axes if ax.get_visible()]
|
|
133
|
+
assert len(visible_axes) == 4
|
|
134
|
+
titles = [ax.get_title() for ax in visible_axes]
|
|
135
|
+
assert titles == ["Observed", "Trend", "Seasonal", "Residual"]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_acf_plot_returns_fig_and_ax(weekly_cycle):
|
|
139
|
+
fig, ax = ACFPlot(PlotSettings()).plot(data=weekly_cycle["value"], nlags=14)
|
|
140
|
+
|
|
141
|
+
assert fig is not None
|
|
142
|
+
assert len(ax.patches) == 15 # lags 0..14 inclusive
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_acf_plot_rejects_missing_values():
|
|
146
|
+
series = pd.Series([1.0, 2.0, float("nan"), 4.0, 5.0])
|
|
147
|
+
|
|
148
|
+
with pytest.raises(ValueError, match="missing values"):
|
|
149
|
+
ACFPlot(PlotSettings()).plot(data=series, nlags=2)
|
|
@@ -383,7 +383,7 @@ wheels = [
|
|
|
383
383
|
|
|
384
384
|
[[package]]
|
|
385
385
|
name = "goad-toolkit"
|
|
386
|
-
version = "0.2.
|
|
386
|
+
version = "0.2.2"
|
|
387
387
|
source = { editable = "." }
|
|
388
388
|
dependencies = [
|
|
389
389
|
{ name = "loguru" },
|
|
@@ -394,6 +394,7 @@ dependencies = [
|
|
|
394
394
|
{ name = "requests" },
|
|
395
395
|
{ name = "scipy" },
|
|
396
396
|
{ name = "seaborn" },
|
|
397
|
+
{ name = "statsmodels" },
|
|
397
398
|
{ name = "tqdm" },
|
|
398
399
|
]
|
|
399
400
|
|
|
@@ -423,6 +424,7 @@ requires-dist = [
|
|
|
423
424
|
{ name = "requests", specifier = ">=2.32.3" },
|
|
424
425
|
{ name = "scipy", specifier = ">=1.15.2" },
|
|
425
426
|
{ name = "seaborn", specifier = ">=0.13.2" },
|
|
427
|
+
{ name = "statsmodels", specifier = ">=0.14.4" },
|
|
426
428
|
{ name = "tqdm", specifier = ">=4.67.1" },
|
|
427
429
|
]
|
|
428
430
|
provides-extras = ["parquet"]
|
|
@@ -1236,6 +1238,18 @@ wheels = [
|
|
|
1236
1238
|
{ url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" },
|
|
1237
1239
|
]
|
|
1238
1240
|
|
|
1241
|
+
[[package]]
|
|
1242
|
+
name = "patsy"
|
|
1243
|
+
version = "1.0.2"
|
|
1244
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1245
|
+
dependencies = [
|
|
1246
|
+
{ name = "numpy" },
|
|
1247
|
+
]
|
|
1248
|
+
sdist = { url = "https://files.pythonhosted.org/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" }
|
|
1249
|
+
wheels = [
|
|
1250
|
+
{ url = "https://files.pythonhosted.org/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301, upload-time = "2025-10-20T16:17:36.563Z" },
|
|
1251
|
+
]
|
|
1252
|
+
|
|
1239
1253
|
[[package]]
|
|
1240
1254
|
name = "pexpect"
|
|
1241
1255
|
version = "4.9.0"
|
|
@@ -1857,6 +1871,39 @@ wheels = [
|
|
|
1857
1871
|
{ url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
|
|
1858
1872
|
]
|
|
1859
1873
|
|
|
1874
|
+
[[package]]
|
|
1875
|
+
name = "statsmodels"
|
|
1876
|
+
version = "0.14.6"
|
|
1877
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1878
|
+
dependencies = [
|
|
1879
|
+
{ name = "numpy" },
|
|
1880
|
+
{ name = "packaging" },
|
|
1881
|
+
{ name = "pandas" },
|
|
1882
|
+
{ name = "patsy" },
|
|
1883
|
+
{ name = "scipy" },
|
|
1884
|
+
]
|
|
1885
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" }
|
|
1886
|
+
wheels = [
|
|
1887
|
+
{ url = "https://files.pythonhosted.org/packages/25/ce/308e5e5da57515dd7cab3ec37ea2d5b8ff50bef1fcc8e6d31456f9fae08e/statsmodels-0.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe76140ae7adc5ff0e60a3f0d56f4fffef484efa803c3efebf2fcd734d72ecb5", size = 10091932, upload-time = "2025-12-05T19:28:55.446Z" },
|
|
1888
|
+
{ url = "https://files.pythonhosted.org/packages/05/30/affbabf3c27fb501ec7b5808230c619d4d1a4525c07301074eb4bda92fa9/statsmodels-0.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26d4f0ed3b31f3c86f83a92f5c1f5cbe63fc992cd8915daf28ca49be14463a1c", size = 9997345, upload-time = "2025-12-05T19:29:10.278Z" },
|
|
1889
|
+
{ url = "https://files.pythonhosted.org/packages/48/f5/3a73b51e6450c31652c53a8e12e24eac64e3824be816c0c2316e7dbdcb7d/statsmodels-0.14.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c00a42863e4f4733ac9d078bbfad816249c01451740e6f5053ecc7db6d6368", size = 10058649, upload-time = "2025-12-05T23:10:12.775Z" },
|
|
1890
|
+
{ url = "https://files.pythonhosted.org/packages/81/68/dddd76117df2ef14c943c6bbb6618be5c9401280046f4ddfc9fb4596a1b8/statsmodels-0.14.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b58cf7474aa9e7e3b0771a66537148b2df9b5884fbf156096c0e6c1ff0469d", size = 10339446, upload-time = "2025-12-05T23:10:28.503Z" },
|
|
1891
|
+
{ url = "https://files.pythonhosted.org/packages/56/4a/dce451c74c4050535fac1ec0c14b80706d8fc134c9da22db3c8a0ec62c33/statsmodels-0.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e7dcc5e9587f2567e52deaff5220b175bf2f648951549eae5fc9383b62bc37", size = 10368705, upload-time = "2025-12-05T23:10:44.339Z" },
|
|
1892
|
+
{ url = "https://files.pythonhosted.org/packages/60/15/3daba2df40be8b8a9a027d7f54c8dedf24f0d81b96e54b52293f5f7e3418/statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f", size = 9543991, upload-time = "2025-12-05T23:10:58.536Z" },
|
|
1893
|
+
{ url = "https://files.pythonhosted.org/packages/81/59/a5aad5b0cc266f5be013db8cde563ac5d2a025e7efc0c328d83b50c72992/statsmodels-0.14.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47ee7af083623d2091954fa71c7549b8443168f41b7c5dce66510274c50fd73e", size = 10072009, upload-time = "2025-12-05T23:11:14.021Z" },
|
|
1894
|
+
{ url = "https://files.pythonhosted.org/packages/53/dd/d8cfa7922fc6dc3c56fa6c59b348ea7de829a94cd73208c6f8202dd33f17/statsmodels-0.14.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa60d82e29fcd0a736e86feb63a11d2380322d77a9369a54be8b0965a3985f71", size = 9980018, upload-time = "2025-12-05T23:11:30.907Z" },
|
|
1895
|
+
{ url = "https://files.pythonhosted.org/packages/ee/77/0ec96803eba444efd75dba32f2ef88765ae3e8f567d276805391ec2c98c6/statsmodels-0.14.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89ee7d595f5939cc20bf946faedcb5137d975f03ae080f300ebb4398f16a5bd4", size = 10060269, upload-time = "2025-12-05T23:11:46.338Z" },
|
|
1896
|
+
{ url = "https://files.pythonhosted.org/packages/10/b9/fd41f1f6af13a1a1212a06bb377b17762feaa6d656947bf666f76300fc05/statsmodels-0.14.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:730f3297b26749b216a06e4327fe0be59b8d05f7d594fb6caff4287b69654589", size = 10324155, upload-time = "2025-12-05T23:12:01.805Z" },
|
|
1897
|
+
{ url = "https://files.pythonhosted.org/packages/ee/0f/a6900e220abd2c69cd0a07e3ad26c71984be6061415a60e0f17b152ecf08/statsmodels-0.14.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f1c08befa85e93acc992b72a390ddb7bd876190f1360e61d10cf43833463bc9c", size = 10349765, upload-time = "2025-12-05T23:12:18.018Z" },
|
|
1898
|
+
{ url = "https://files.pythonhosted.org/packages/98/08/b79f0c614f38e566eebbdcff90c0bcacf3c6ba7a5bbb12183c09c29ca400/statsmodels-0.14.6-cp313-cp313-win_amd64.whl", hash = "sha256:8021271a79f35b842c02a1794465a651a9d06ec2080f76ebc3b7adce77d08233", size = 9540043, upload-time = "2025-12-05T23:12:33.887Z" },
|
|
1899
|
+
{ url = "https://files.pythonhosted.org/packages/71/de/09540e870318e0c7b58316561d417be45eff731263b4234fdd2eee3511a8/statsmodels-0.14.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:00781869991f8f02ad3610da6627fd26ebe262210287beb59761982a8fa88cae", size = 10069403, upload-time = "2025-12-05T23:12:48.424Z" },
|
|
1900
|
+
{ url = "https://files.pythonhosted.org/packages/ab/f0/63c1bfda75dc53cee858006e1f46bd6d6f883853bea1b97949d0087766ca/statsmodels-0.14.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:73f305fbf31607b35ce919fae636ab8b80d175328ed38fdc6f354e813b86ee37", size = 9989253, upload-time = "2025-12-05T23:13:05.274Z" },
|
|
1901
|
+
{ url = "https://files.pythonhosted.org/packages/c1/98/b0dfb4f542b2033a3341aa5f1bdd97024230a4ad3670c5b0839d54e3dcab/statsmodels-0.14.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e443e7077a6e2d3faeea72f5a92c9f12c63722686eb80bb40a0f04e4a7e267ad", size = 10090802, upload-time = "2025-12-05T23:13:20.653Z" },
|
|
1902
|
+
{ url = "https://files.pythonhosted.org/packages/34/0e/2408735aca9e764643196212f9069912100151414dd617d39ffc72d77eee/statsmodels-0.14.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3414e40c073d725007a6603a18247ab7af3467e1af4a5e5a24e4c27bc26673b4", size = 10337587, upload-time = "2025-12-05T23:13:37.597Z" },
|
|
1903
|
+
{ url = "https://files.pythonhosted.org/packages/0f/36/4d44f7035ab3c0b2b6a4c4ebb98dedf36246ccbc1b3e2f51ebcd7ac83abb/statsmodels-0.14.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a518d3f9889ef920116f9fa56d0338069e110f823926356946dae83bc9e33e19", size = 10363350, upload-time = "2025-12-05T23:13:53.08Z" },
|
|
1904
|
+
{ url = "https://files.pythonhosted.org/packages/26/33/f1652d0c59fa51de18492ee2345b65372550501ad061daa38f950be390b6/statsmodels-0.14.6-cp314-cp314-win_amd64.whl", hash = "sha256:151b73e29f01fe619dbce7f66d61a356e9d1fe5e906529b78807df9189c37721", size = 9588010, upload-time = "2025-12-05T23:14:07.28Z" },
|
|
1905
|
+
]
|
|
1906
|
+
|
|
1860
1907
|
[[package]]
|
|
1861
1908
|
name = "terminado"
|
|
1862
1909
|
version = "0.18.1"
|
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/.gitignore
RENAMED
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/.python-version
RENAMED
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/CHANGELOG.md
RENAMED
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/MCP_SERVER.md
RENAMED
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/README.md
RENAMED
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/demo/linear.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/docs/README.md
RENAMED
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/goad_mcp.py
RENAMED
|
File without changes
|
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/img/goaded.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/img/zscores.png
RENAMED
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{goad_toolkit-0.2.2 → goad_toolkit-0.2.3}/.claude/worktrees/quizzical-solomon-b62511/uv.lock
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|