goad-toolkit 0.2.13__tar.gz → 0.2.14__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.13 → goad_toolkit-0.2.14}/.remember/logs/memory-2026-08-31.log +33 -0
- goad_toolkit-0.2.14/.remember/tmp/last-save-ts +1 -0
- goad_toolkit-0.2.14/.remember/tmp/last-summary-failure +1 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/CHANGELOG.md +21 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/PKG-INFO +1 -1
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/03-plot-composition.md +11 -1
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/08-api-reference.md +5 -1
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/pyproject.toml +1 -1
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/visualizer.py +92 -9
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/tests/test_visualizer.py +55 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/uv.lock +1 -1
- goad_toolkit-0.2.13/.remember/tmp/last-save-ts +0 -1
- goad_toolkit-0.2.13/.remember/tmp/last-summary-failure +0 -1
- goad_toolkit-0.2.13/.remember/tmp/save-session.pid +0 -1
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/settings.local.json +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/.claude/settings.local.json +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/.gitignore +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/.python-version +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/CHANGELOG.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/MCP_SERVER.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/README.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/demo/linear.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/01-goal-oriented-analysis.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/02-pipelines.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/03-plot-composition.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/04-five-families.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/05-distributions.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/06-models-and-residuals.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/07-visual-critique.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/08-api-reference.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/09-analysis-method.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/10-teaching-path.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/docs/README.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/goad_mcp.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/img/distribution_fit.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/img/goaded.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/img/linear_results.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/img/null-distribution.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/img/residuals.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/img/zscores.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/pyproject.toml +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/__init__.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/analytics.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/cli.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/config.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/dataprocessor.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/datatransforms.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/distributions.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/filehandler.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/models.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/src/goad_toolkit/visualizer.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/tests/test_cli.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/tests/test_datatransforms.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/tests/test_distributions.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/tests/test_filehandler.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/tests/test_nulldistribution.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/tests/test_visualizer.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/uv.lock +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.github/workflows/release.yml +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.gitignore +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.python-version +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/.gitignore +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/archive.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-170051.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-170257.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-172752.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-172954.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-174001.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-174204.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-174415.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-174621.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-183124.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-183325.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-183803.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-184713.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/autonomous/save-184913.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/hook-errors.log +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/logs/logs-2026-08.tar.gz +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/now.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/recent.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/capture-alive +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/capture-alive.d/16b4788a-5507-4216-98ec-06bac13a9aba +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/capture-alive.d/309ee39a-6171-47ea-88cf-ee1ad9433f68 +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/capture-alive.d/90574e63-97cd-4c11-87e5-23b656c56fd9 +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/case-divergence +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/last-ndc.ts +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/last-save.json +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/post-tool-ran +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/tmp/session-slug +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/today-2026-08-10.done.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.remember/today-2026-08-10.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/MCP_SERVER.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/README.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/demo/linear.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/01-goal-oriented-analysis.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/02-pipelines.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/04-five-families.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/05-distributions.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/06-models-and-residuals.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/07-visual-critique.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/09-analysis-method.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/10-teaching-path.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/docs/README.md +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/goad_mcp.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/img/distribution_fit.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/img/goaded.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/img/linear_results.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/img/null-distribution.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/img/residuals.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/img/zscores.png +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/__init__.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/analytics.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/cli.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/config.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/dataprocessor.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/datatransforms.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/distributions.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/filehandler.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/src/goad_toolkit/models.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/tests/test_cli.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/tests/test_datatransforms.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/tests/test_distributions.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/tests/test_filehandler.py +0 -0
- {goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/tests/test_nulldistribution.py +0 -0
|
@@ -342,3 +342,36 @@ RuntimeError: claude exited 1: Failed to authenticate: OAuth session expired and
|
|
|
342
342
|
18:49:15 [haiku] call exited 1; tokens already spent are unknown — the failure carried no usage block, so this run's reported cost is lower than what it actually cost
|
|
343
343
|
18:49:15 [haiku] ERROR: call-haiku error: claude exited 1: Failed to authenticate: OAuth session expired and could not be refreshed
|
|
344
344
|
18:49:15 [haiku] failure 1/3 on this span — will retry next run
|
|
345
|
+
21:09:54 [hook] session-start: PROJECT_DIR=/Users/rgrouls/code/courses/goad_toolkit PIPELINE_DIR=/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0 REMEMBER_DIR=/Users/rgrouls/code/courses/goad_toolkit/.remember
|
|
346
|
+
21:09:54 [hook] save-session: PROJECT_DIR=/Users/rgrouls/code/courses/goad_toolkit PIPELINE_DIR=/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0 PYTHON=python3 REMEMBER_DIR=/Users/rgrouls/code/courses/goad_toolkit/.remember
|
|
347
|
+
21:09:54 [force] bypassing cooldown + min msgs
|
|
348
|
+
21:09:54 [hook] run-consolidation: PROJECT_DIR=/Users/rgrouls/code/courses/goad_toolkit PIPELINE_DIR=/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0 PYTHON=python3 REMEMBER_DIR=/Users/rgrouls/code/courses/goad_toolkit/.remember
|
|
349
|
+
21:09:54 [extract] session 309ee39a-6171-47ea-88cf-ee1ad9433f68
|
|
350
|
+
21:09:54 [extract] 204 exchanges (10 human)
|
|
351
|
+
21:09:54 [consolidation] start
|
|
352
|
+
21:09:54 [haiku] calling (branch: main)
|
|
353
|
+
21:09:54 [hook] session-start: PROJECT_DIR=/Users/rgrouls/code/courses/goad_toolkit PIPELINE_DIR=/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0 REMEMBER_DIR=/Users/rgrouls/code/courses/goad_toolkit/.remember
|
|
354
|
+
21:09:54 [hook] run-consolidation: PROJECT_DIR=/Users/rgrouls/code/courses/goad_toolkit PIPELINE_DIR=/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0 PYTHON=python3 REMEMBER_DIR=/Users/rgrouls/code/courses/goad_toolkit/.remember
|
|
355
|
+
21:09:54 [consolidation] another consolidation holds the lock, skip
|
|
356
|
+
21:09:56 [haiku] WARNING: this CLI rejected --setting-sources (Failed to authenticate: OAuth session expired and could not be refreshed); retrying WITHOUT hook isolation so saves keep working. The nested call will run with your hooks registered — a hook that blocks it returns its block message as if it were the model's reply (#202).
|
|
357
|
+
21:09:56 [haiku] WARNING: this CLI rejected --setting-sources (Failed to authenticate: OAuth session expired and could not be refreshed); retrying WITHOUT hook isolation so saves keep working. The nested call will run with your hooks registered — a hook that blocks it returns its block message as if it were the model's reply (#202).
|
|
358
|
+
21:09:57 [haiku] call exited 1; tokens already spent are unknown — the failure carried no usage block, so this run's reported cost is lower than what it actually cost
|
|
359
|
+
21:09:57 [haiku] call exited 1; tokens already spent are unknown — the failure carried no usage block, so this run's reported cost is lower than what it actually cost
|
|
360
|
+
21:09:57 [haiku] ERROR: call-haiku error: claude exited 1: Failed to authenticate: OAuth session expired and could not be refreshed
|
|
361
|
+
21:09:57 [haiku] failure 1/3 on this span — will retry next run
|
|
362
|
+
21:09:57 [consolidation] ERROR: pipeline failed — Traceback (most recent call last):
|
|
363
|
+
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/runpy.py", line 197, in _run_module_as_main
|
|
364
|
+
return _run_code(code, main_globals, None,
|
|
365
|
+
File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/runpy.py", line 87, in _run_code
|
|
366
|
+
exec(code, run_globals)
|
|
367
|
+
File "/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0/pipeline/shell.py", line 683, in <module>
|
|
368
|
+
main()
|
|
369
|
+
File "/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0/pipeline/shell.py", line 670, in main
|
|
370
|
+
cmd_consolidate(
|
|
371
|
+
File "/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0/pipeline/shell.py", line 525, in cmd_consolidate
|
|
372
|
+
result = consolidate(staging_contents, recent, archive,
|
|
373
|
+
File "/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0/pipeline/consolidate.py", line 324, in consolidate
|
|
374
|
+
result = call_haiku(prompt, timeout=180)
|
|
375
|
+
File "/Users/rgrouls/.claude/plugins/cache/claude-plugins-official/remember/0.20.0/pipeline/haiku.py", line 735, in call_haiku
|
|
376
|
+
raise RuntimeError(
|
|
377
|
+
RuntimeError: claude exited 1: Failed to authenticate: OAuth session expired and could not be refreshed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1788203394
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
309ee39a-6171-47ea-88cf-ee1ad9433f68:747 1
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.14
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `PlotSettings.subplot_xlabels` / `subplot_ylabels`: per-panel axis labels, the
|
|
8
|
+
`subplot_titles` idea extended to labels. On the panels they cover they win over the
|
|
9
|
+
figure-wide `xlabel`/`ylabel`, so a multi-panel figure with panels showing different
|
|
10
|
+
quantities needs no `ax.set_ylabel` calls.
|
|
11
|
+
- `HorizontalLine`: a layer marking one value on the y-axis — the reference line a residual
|
|
12
|
+
plot is read against. Defaults to a quiet grey line rather than `VerticalLine`'s crimson
|
|
13
|
+
marker, because a baseline is context, not a finding.
|
|
14
|
+
- `BarPlot.build` takes `xtick_rotation` for category labels too long to sit flat.
|
|
15
|
+
- `PlotSettings.xtick_rotation`: the same rotation as a figure-wide setting, applied to
|
|
16
|
+
every axis — for date axes whose labels overlap when flat.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Multi-panel grids from `create_figure` use constrained layout, which spaces panels,
|
|
21
|
+
labels and the suptitle at draw time — after every layer has landed — so figures no
|
|
22
|
+
longer need a trailing `fig.tight_layout()`.
|
|
23
|
+
|
|
3
24
|
## 0.2.1
|
|
4
25
|
|
|
5
26
|
### Breaking
|
|
@@ -51,6 +51,9 @@ class PlotSettings:
|
|
|
51
51
|
grid: bool = False
|
|
52
52
|
grid_alpha: Optional[float] = None
|
|
53
53
|
subplot_titles: List[str] = field(default_factory=list)
|
|
54
|
+
subplot_xlabels: List[str] = field(default_factory=list)
|
|
55
|
+
subplot_ylabels: List[str] = field(default_factory=list)
|
|
56
|
+
xtick_rotation: Optional[float] = None
|
|
54
57
|
```
|
|
55
58
|
|
|
56
59
|
`xlabel`/`ylabel` left as `None` are not blank — `create_figure` skips setting them at all,
|
|
@@ -66,7 +69,11 @@ title.
|
|
|
66
69
|
|
|
67
70
|
`subplot_titles` is what makes multi-panel figures work: when a plot creates more than one
|
|
68
71
|
axis, each gets the corresponding entry. `PlotFits` uses this to label each fitted
|
|
69
|
-
distribution with its own test statistics.
|
|
72
|
+
distribution with its own test statistics. `subplot_xlabels` and `subplot_ylabels` are the
|
|
73
|
+
same idea for axis labels — panels showing different quantities get their own labels from
|
|
74
|
+
the settings, not from `ax.set_ylabel` calls after the fact. On the panels they cover, the
|
|
75
|
+
lists win over the figure-wide `xlabel`/`ylabel`. `xtick_rotation` rotates the x tick
|
|
76
|
+
labels on every axis, for the date axes and long category names that overlap when flat.
|
|
70
77
|
|
|
71
78
|
## 3.3 `BasePlot`
|
|
72
79
|
|
|
@@ -89,6 +96,8 @@ The contract:
|
|
|
89
96
|
|
|
90
97
|
`create_figure(n_plots=...)` handles the layout: one axis for a single plot, otherwise a grid
|
|
91
98
|
wrapping at `settings.max_cols` columns — three by default — with the unused axes hidden.
|
|
99
|
+
The grid uses constrained layout, which spaces panels, labels and the suptitle at draw
|
|
100
|
+
time — after every layer has landed — so a figure never needs a `tight_layout()` call.
|
|
92
101
|
Labels, grid and titles are applied to every visible axis. Set `max_cols=4` for a quartet or
|
|
93
102
|
`max_cols=2` for a square grid; four panels at the default wrap to 3 + 1, which is rarely
|
|
94
103
|
what a set of four wants.
|
|
@@ -158,6 +167,7 @@ step 3.
|
|
|
158
167
|
| `ComparePlot` | two lines with labels | `LinePlot` twice via `plot_on` |
|
|
159
168
|
| `VerticalDate` | a labelled vertical line at a date | a layer, not a plot |
|
|
160
169
|
| `VerticalLine` | the same at a number | a layer, not a plot |
|
|
170
|
+
| `HorizontalLine` | a reference level on the y-axis, usually zero | a layer; the line a residual is read against |
|
|
161
171
|
| `ComparePlotDate` | `ComparePlot` + `VerticalDate` | |
|
|
162
172
|
| `BarPlot` | `sns.barplot`, one bar per category | see [Five families §4.1](04-five-families.md) |
|
|
163
173
|
| `GroupedBarPlot` | bars split by `hue` | two categorical variables at once |
|
|
@@ -300,6 +300,9 @@ class PlotSettings:
|
|
|
300
300
|
grid: bool = False
|
|
301
301
|
grid_alpha: Optional[float] = None
|
|
302
302
|
subplot_titles: List[str] = field(default_factory=list)
|
|
303
|
+
subplot_xlabels: List[str] = field(default_factory=list) # per-panel, wins over xlabel
|
|
304
|
+
subplot_ylabels: List[str] = field(default_factory=list) # per-panel, wins over ylabel
|
|
305
|
+
xtick_rotation: Optional[float] = None # rotate x tick labels, every axis
|
|
303
306
|
max_cols: int = 3 # columns before wrapping
|
|
304
307
|
highlight: List[str] = field(default_factory=list) # defaults for HighlightCategory
|
|
305
308
|
base_color: str = "lightgrey"
|
|
@@ -332,8 +335,9 @@ class BasePlot(ABC):
|
|
|
332
335
|
| `ComparePlot` | `data, x, y1, y2, **kwargs` |
|
|
333
336
|
| `VerticalDate` | `date: str, label: str` |
|
|
334
337
|
| `VerticalLine` | `x: float, label=None, color="crimson", linestyle="--", linewidth=2, **kwargs` |
|
|
338
|
+
| `HorizontalLine` | `y: float, label=None, color="grey", linestyle="-", linewidth=1, **kwargs` |
|
|
335
339
|
| `ComparePlotDate` | `data, x, y1, y2, date, datelabel, **kwargs` |
|
|
336
|
-
| `BarPlot` | `data, x, y, order=None, **kwargs` → `sns.barplot` |
|
|
340
|
+
| `BarPlot` | `data, x, y, order=None, xtick_rotation=None, **kwargs` → `sns.barplot` |
|
|
337
341
|
| `GroupedBarPlot` | `data, x, y, hue, order=None, hue_order=None, **kwargs` |
|
|
338
342
|
| `HeatmapPlot` | `data, index=None, columns=None, values=None, aggfunc="mean", cmap="rocket_r", annot=True, fmt=".1f", **kwargs` |
|
|
339
343
|
| `BarbellPlot` | `data, category, start, end, start_label="before", end_label="after", start_color="lightgrey", end_color="crimson", line_color="lightgrey", markersize=80, **kwargs` |
|
|
@@ -30,6 +30,9 @@ class PlotSettings:
|
|
|
30
30
|
grid: bool = False # Added grid option
|
|
31
31
|
grid_alpha: Optional[float] = None # Grid transparency
|
|
32
32
|
subplot_titles: List[str] = field(default_factory=list) # Titles for subplots
|
|
33
|
+
subplot_xlabels: List[str] = field(default_factory=list) # One xlabel per subplot
|
|
34
|
+
subplot_ylabels: List[str] = field(default_factory=list) # One ylabel per subplot
|
|
35
|
+
xtick_rotation: Optional[float] = None # Rotate x tick labels on every axis
|
|
33
36
|
max_cols: int = 3 # Columns before create_figure wraps to a new row
|
|
34
37
|
highlight: List[str] = field(default_factory=list) # Categories to pick out
|
|
35
38
|
base_color: str = "lightgrey" # Everything that is not highlighted
|
|
@@ -76,9 +79,15 @@ class BasePlot(ABC):
|
|
|
76
79
|
# Determine grid layout
|
|
77
80
|
grid_cols = min(self.settings.max_cols, n_plots)
|
|
78
81
|
grid_rows = int(np.ceil(n_plots / grid_cols))
|
|
79
|
-
# Create subplot grid
|
|
82
|
+
# Create subplot grid. Constrained layout keeps panels, labels and the
|
|
83
|
+
# suptitle from colliding, and recomputes at draw time -- after every
|
|
84
|
+
# layer has landed -- so no tight_layout() call is needed afterwards.
|
|
80
85
|
self.fig, axes = plt.subplots(
|
|
81
|
-
grid_rows,
|
|
86
|
+
grid_rows,
|
|
87
|
+
grid_cols,
|
|
88
|
+
figsize=self.settings.figsize,
|
|
89
|
+
squeeze=False,
|
|
90
|
+
layout="constrained",
|
|
82
91
|
)
|
|
83
92
|
axes = axes.flatten()
|
|
84
93
|
# Hide unused axes
|
|
@@ -92,14 +101,23 @@ class BasePlot(ABC):
|
|
|
92
101
|
if ax.get_visible():
|
|
93
102
|
# None means "let the plotting call decide" -- seaborn fills the axis
|
|
94
103
|
# label in from the column name whenever the label is still empty, so
|
|
95
|
-
# only an explicit xlabel/ylabel should pre-empt that.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if self.settings.
|
|
99
|
-
|
|
104
|
+
# only an explicit xlabel/ylabel should pre-empt that. The per-subplot
|
|
105
|
+
# lists win over the figure-wide label on the panels they cover.
|
|
106
|
+
xlabel = self.settings.xlabel
|
|
107
|
+
if i < len(self.settings.subplot_xlabels):
|
|
108
|
+
xlabel = self.settings.subplot_xlabels[i]
|
|
109
|
+
if xlabel is not None:
|
|
110
|
+
ax.set_xlabel(xlabel)
|
|
111
|
+
ylabel = self.settings.ylabel
|
|
112
|
+
if i < len(self.settings.subplot_ylabels):
|
|
113
|
+
ylabel = self.settings.subplot_ylabels[i]
|
|
114
|
+
if ylabel is not None:
|
|
115
|
+
ax.set_ylabel(ylabel)
|
|
100
116
|
# Apply grid if requested
|
|
101
117
|
if self.settings.grid:
|
|
102
118
|
ax.grid(True, alpha=self.settings.grid_alpha)
|
|
119
|
+
if self.settings.xtick_rotation is not None:
|
|
120
|
+
ax.tick_params(axis="x", rotation=self.settings.xtick_rotation)
|
|
103
121
|
|
|
104
122
|
# Set subplot titles if available
|
|
105
123
|
if i < len(self.settings.subplot_titles):
|
|
@@ -111,7 +129,6 @@ class BasePlot(ABC):
|
|
|
111
129
|
self.ax.set_title(self.settings.title)
|
|
112
130
|
else:
|
|
113
131
|
self.fig.suptitle(self.settings.title, fontsize=16)
|
|
114
|
-
self.fig.tight_layout(rect=(0, 0, 1, 0.96)) # Make room for suptitle
|
|
115
132
|
|
|
116
133
|
if self.settings.legend_title is not None:
|
|
117
134
|
self.ax.legend(title=self.settings.legend_title)
|
|
@@ -176,7 +193,13 @@ class BasePlot(ABC):
|
|
|
176
193
|
|
|
177
194
|
|
|
178
195
|
class LinePlot(BasePlot):
|
|
179
|
-
"""Plot a line plot using seaborn.
|
|
196
|
+
"""Plot a line plot using seaborn.
|
|
197
|
+
|
|
198
|
+
Seaborn drops rows with missing values before drawing, so a gap in the data
|
|
199
|
+
is connected straight through rather than left open. When absent data must
|
|
200
|
+
look absent — a reindexed series with `NaN` holes — draw with `ax.plot`,
|
|
201
|
+
which breaks the line at every `NaN`.
|
|
202
|
+
"""
|
|
180
203
|
|
|
181
204
|
def build(self, data: pd.DataFrame, **kwargs):
|
|
182
205
|
sns.lineplot(data=data, ax=self.ax, **kwargs)
|
|
@@ -266,6 +289,61 @@ class VerticalLine(BasePlot):
|
|
|
266
289
|
return self.fig, self.ax
|
|
267
290
|
|
|
268
291
|
|
|
292
|
+
class HorizontalLine(BasePlot):
|
|
293
|
+
"""Mark one value on the y-axis — a baseline, zero more often than not.
|
|
294
|
+
|
|
295
|
+
`VerticalLine` marks a moment; this marks a level. A residual plot without
|
|
296
|
+
a zero line asks the reader to eyeball where "no effect" sits, so the
|
|
297
|
+
defaults here are a quiet reference line rather than a crimson marker.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
def build(
|
|
301
|
+
self,
|
|
302
|
+
y: float,
|
|
303
|
+
label: Optional[str] = None,
|
|
304
|
+
color: str = "grey",
|
|
305
|
+
linestyle: str = "-",
|
|
306
|
+
linewidth: float = 1,
|
|
307
|
+
**kwargs,
|
|
308
|
+
):
|
|
309
|
+
"""
|
|
310
|
+
Draw a horizontal line at `y`.
|
|
311
|
+
|
|
312
|
+
Parameters:
|
|
313
|
+
-----------
|
|
314
|
+
y : float
|
|
315
|
+
Position on the y-axis.
|
|
316
|
+
label : Optional[str]
|
|
317
|
+
Legend label. The line is drawn without a legend when this is None.
|
|
318
|
+
color : str
|
|
319
|
+
Line colour.
|
|
320
|
+
linestyle : str
|
|
321
|
+
Line style.
|
|
322
|
+
linewidth : float
|
|
323
|
+
Line width.
|
|
324
|
+
**kwargs : Additional keyword arguments passed to `ax.axhline`.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
--------
|
|
328
|
+
fig, ax : The created figure and axes.
|
|
329
|
+
"""
|
|
330
|
+
if self.ax is None:
|
|
331
|
+
raise ValueError("No axes available for plotting")
|
|
332
|
+
|
|
333
|
+
self.ax.axhline(
|
|
334
|
+
y=y,
|
|
335
|
+
color=color,
|
|
336
|
+
linestyle=linestyle,
|
|
337
|
+
linewidth=linewidth,
|
|
338
|
+
label=label,
|
|
339
|
+
**kwargs,
|
|
340
|
+
)
|
|
341
|
+
if label is not None:
|
|
342
|
+
self.ax.legend()
|
|
343
|
+
|
|
344
|
+
return self.fig, self.ax
|
|
345
|
+
|
|
346
|
+
|
|
269
347
|
class HighlightCategory(BasePlot):
|
|
270
348
|
"""Grey everything, then colour the categories the plot is actually about.
|
|
271
349
|
|
|
@@ -477,6 +555,7 @@ class BarPlot(BasePlot):
|
|
|
477
555
|
x: str,
|
|
478
556
|
y: str,
|
|
479
557
|
order: Optional[List[str]] = None,
|
|
558
|
+
xtick_rotation: Optional[float] = None,
|
|
480
559
|
**kwargs,
|
|
481
560
|
):
|
|
482
561
|
"""
|
|
@@ -494,6 +573,8 @@ class BarPlot(BasePlot):
|
|
|
494
573
|
order : Optional[List[str]]
|
|
495
574
|
Category order. Seaborn's own order (first appearance) is used
|
|
496
575
|
when this is None.
|
|
576
|
+
xtick_rotation : Optional[float]
|
|
577
|
+
Rotate the category labels, for names too long to sit flat.
|
|
497
578
|
**kwargs : Additional keyword arguments passed to `sns.barplot`,
|
|
498
579
|
including `estimator`, `errorbar`, `color` and `hue`.
|
|
499
580
|
|
|
@@ -502,6 +583,8 @@ class BarPlot(BasePlot):
|
|
|
502
583
|
fig, ax : The created figure and axes.
|
|
503
584
|
"""
|
|
504
585
|
sns.barplot(data=data, x=x, y=y, order=order, ax=self.ax, **kwargs)
|
|
586
|
+
if xtick_rotation is not None and self.ax is not None:
|
|
587
|
+
self.ax.tick_params(axis="x", rotation=xtick_rotation)
|
|
505
588
|
return self.fig, self.ax
|
|
506
589
|
|
|
507
590
|
|
|
@@ -25,6 +25,7 @@ from goad_toolkit.visualizer import ( # noqa: E402
|
|
|
25
25
|
GroupedBarPlot,
|
|
26
26
|
HeatmapPlot,
|
|
27
27
|
HighlightCategory,
|
|
28
|
+
HorizontalLine,
|
|
28
29
|
LinePlot,
|
|
29
30
|
NullPlot,
|
|
30
31
|
PlotSettings,
|
|
@@ -235,6 +236,21 @@ def test_vertical_line_without_a_label_has_no_legend():
|
|
|
235
236
|
assert ax.get_legend() is None
|
|
236
237
|
|
|
237
238
|
|
|
239
|
+
def test_horizontal_line_draws_at_the_requested_y():
|
|
240
|
+
fig, ax = HorizontalLine(PlotSettings()).plot(y=0.0, label="baseline")
|
|
241
|
+
|
|
242
|
+
assert fig is not None
|
|
243
|
+
assert ax.lines[0].get_ydata()[0] == pytest.approx(0.0)
|
|
244
|
+
legend_labels = [t.get_text() for t in ax.get_legend().get_texts()]
|
|
245
|
+
assert legend_labels == ["baseline"]
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def test_horizontal_line_without_a_label_has_no_legend():
|
|
249
|
+
fig, ax = HorizontalLine(PlotSettings()).plot(y=1.0)
|
|
250
|
+
|
|
251
|
+
assert ax.get_legend() is None
|
|
252
|
+
|
|
253
|
+
|
|
238
254
|
@pytest.fixture
|
|
239
255
|
def null_result() -> NullResult:
|
|
240
256
|
rng = np.random.default_rng(4)
|
|
@@ -305,6 +321,36 @@ def test_max_cols_of_two_gives_a_square_grid():
|
|
|
305
321
|
assert fig.axes[0].get_subplotspec().get_gridspec().get_geometry() == (2, 2)
|
|
306
322
|
|
|
307
323
|
|
|
324
|
+
def test_subplot_labels_apply_per_panel():
|
|
325
|
+
settings = PlotSettings(
|
|
326
|
+
subplot_xlabels=["author", "messages"],
|
|
327
|
+
subplot_ylabels=["count", "length"],
|
|
328
|
+
)
|
|
329
|
+
host = LinePlot(settings)
|
|
330
|
+
fig, axes = host.create_figure(n_plots=2)
|
|
331
|
+
|
|
332
|
+
assert [ax.get_xlabel() for ax in axes] == ["author", "messages"]
|
|
333
|
+
assert [ax.get_ylabel() for ax in axes] == ["count", "length"]
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def test_xtick_rotation_setting_applies_to_every_axis():
|
|
337
|
+
host = LinePlot(PlotSettings(xtick_rotation=45))
|
|
338
|
+
fig, axes = host.create_figure(n_plots=2)
|
|
339
|
+
|
|
340
|
+
fig.canvas.draw()
|
|
341
|
+
for ax in axes:
|
|
342
|
+
assert all(t.get_rotation() == pytest.approx(45) for t in ax.get_xticklabels())
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_subplot_labels_win_over_the_figure_wide_label():
|
|
346
|
+
settings = PlotSettings(ylabel="shared", subplot_ylabels=["only the first"])
|
|
347
|
+
host = LinePlot(settings)
|
|
348
|
+
fig, axes = host.create_figure(n_plots=2)
|
|
349
|
+
|
|
350
|
+
assert axes[0].get_ylabel() == "only the first"
|
|
351
|
+
assert axes[1].get_ylabel() == "shared"
|
|
352
|
+
|
|
353
|
+
|
|
308
354
|
def test_annotate_writes_the_text_on_the_axis():
|
|
309
355
|
host = LinePlot(PlotSettings())
|
|
310
356
|
host.create_figure()
|
|
@@ -430,6 +476,15 @@ def test_bar_plot_draws_one_bar_per_category(categories):
|
|
|
430
476
|
]
|
|
431
477
|
|
|
432
478
|
|
|
479
|
+
def test_bar_plot_rotates_its_tick_labels_on_request(categories):
|
|
480
|
+
fig, ax = BarPlot(PlotSettings()).plot(
|
|
481
|
+
data=categories, x="species", y="mass", xtick_rotation=45
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
fig.canvas.draw()
|
|
485
|
+
assert all(t.get_rotation() == pytest.approx(45) for t in ax.get_xticklabels())
|
|
486
|
+
|
|
487
|
+
|
|
433
488
|
def test_bar_plot_takes_an_explicit_order(categories):
|
|
434
489
|
order = ["Gentoo", "Adelie", "Chinstrap"]
|
|
435
490
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1788194953
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
309ee39a-6171-47ea-88cf-ee1ad9433f68:745 1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
20713
|
|
File without changes
|
|
File without changes
|
{goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/.gitignore
RENAMED
|
File without changes
|
|
File without changes
|
{goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/CHANGELOG.md
RENAMED
|
File without changes
|
{goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/MCP_SERVER.md
RENAMED
|
File without changes
|
{goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/README.md
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
|
{goad_toolkit-0.2.13 → goad_toolkit-0.2.14}/.claude/worktrees/quizzical-solomon-b62511/goad_mcp.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
|
|
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.13 → goad_toolkit-0.2.14}/.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
|
|
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
|