pysofra 0.1.0a2__tar.gz → 0.1.0a4__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.
- pysofra-0.1.0a4/.gitignore +48 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/CHANGELOG.md +26 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/PKG-INFO +8 -8
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/README.md +6 -6
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/pyproject.toml +18 -7
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/__init__.py +1 -1
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/core/table.py +1 -1
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/extras.py +18 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/stats.py +6 -6
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/tbl_one.py +12 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/themes/registry.py +2 -2
- pysofra-0.1.0a2/tests/test_joss_api_stability.py → pysofra-0.1.0a4/tests/test_api_stability.py +7 -8
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_extract_edges.py +1 -1
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_extras_edges.py +3 -3
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_misc_fixes.py +7 -8
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_plot_determinism.py +1 -1
- pysofra-0.1.0a2/tests/test_joss_property_invariants.py → pysofra-0.1.0a4/tests/test_property_invariants.py +1 -1
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_regressions.py +7 -7
- pysofra-0.1.0a2/tests/test_joss_renderer_consistency.py → pysofra-0.1.0a4/tests/test_renderer_consistency.py +1 -1
- pysofra-0.1.0a2/tests/test_joss_statistical_correctness.py → pysofra-0.1.0a4/tests/test_statistical_correctness.py +6 -6
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_validation_fixes.py +7 -8
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_wishlist.py +2 -2
- pysofra-0.1.0a2/.gitignore +0 -90
- pysofra-0.1.0a2/src/pysofra/io/__init__.py +0 -1
- pysofra-0.1.0a2/src/pysofra/notebook/__init__.py +0 -6
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/LICENSE +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/NOTICE +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/core/__init__.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/core/compose.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/core/format.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/core/frames.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/core/schema.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/models/__init__.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/models/extract.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/models/pool.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/models/regression.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/models/survival.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/models/uvregression.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/plot/__init__.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/plot/_backend.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/plot/forest.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/plot/inline.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/plot/km.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/__init__.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/_zip_determinism.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/base.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/docx.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/html.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/image.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/latex.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/markdown.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/pptx.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/render/xlsx.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/__init__.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/calibrate.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/design.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/effect_size.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/smd.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/tbl_cross.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/tbl_summary.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/tests.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/typing.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/summary/weights.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/src/pysofra/themes/__init__.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/conftest.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/README.md +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/anova_oneway.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/chi_square.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/fisher_2x2.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/kruskal_wallis.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/student_t.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/svyttest.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/weighted_mean.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/welch_t_test.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/fixtures/scipy_validation/wilcoxon_rank_sum.json +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_compose.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_compose_edges.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_conditional_formatting.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_design_regression.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_extras_edges_2.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_format.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_latex_pptx.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_modifier_edges.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_multi_model.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_partial_modifiers.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_partials.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_plot_embedding.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_plots.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_polars.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_pptx_overflow.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_rao_scott.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_regression.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_render_edges.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_render_edges_2.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_rendering.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_scipy_validation.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_snapshot.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_stats.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_summary_edges.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_summary_edges_2.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_survey_design.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_survey_extensions.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_survival.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_table_edges.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_tbl_one.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_test_overrides.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_uvregression_factors.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_weights.py +0 -0
- {pysofra-0.1.0a2 → pysofra-0.1.0a4}/tests/test_xlsx.py +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
.venv/
|
|
7
|
+
venv/
|
|
8
|
+
.env
|
|
9
|
+
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
|
|
16
|
+
.DS_Store
|
|
17
|
+
.idea/
|
|
18
|
+
.vscode/
|
|
19
|
+
|
|
20
|
+
site/
|
|
21
|
+
docs/_build/
|
|
22
|
+
|
|
23
|
+
# Local working directories not part of the source distribution.
|
|
24
|
+
paper/
|
|
25
|
+
_local/
|
|
26
|
+
_private/
|
|
27
|
+
|
|
28
|
+
# Tutorial / example export artefacts — produced on demand by
|
|
29
|
+
# scripts/render_tutorial.py, not part of the source tree.
|
|
30
|
+
*.docx
|
|
31
|
+
*.pptx
|
|
32
|
+
*.xlsx
|
|
33
|
+
examples/tutorial_*.docx
|
|
34
|
+
examples/tutorial_*.pptx
|
|
35
|
+
examples/tutorial_*.xlsx
|
|
36
|
+
examples/tutorial_*.png
|
|
37
|
+
!tests/fixtures/**/*.docx
|
|
38
|
+
!tests/fixtures/**/*.pptx
|
|
39
|
+
!tests/fixtures/**/*.xlsx
|
|
40
|
+
|
|
41
|
+
# Hypothesis example database (auto-managed)
|
|
42
|
+
.hypothesis/
|
|
43
|
+
|
|
44
|
+
# uv writes a lockfile when ``uv add`` / ``uv pip install`` is used
|
|
45
|
+
# locally; the project pins its runtime versions explicitly in
|
|
46
|
+
# pyproject.toml, so the uv lockfile is a local convenience that
|
|
47
|
+
# should not ship.
|
|
48
|
+
uv.lock
|
|
@@ -5,6 +5,32 @@ All notable changes to PySofra will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.0a4] — 2026-05-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Input validation for duplicate names in `variables=` (now raises
|
|
12
|
+
`ValueError` instead of silently accepting duplicates).
|
|
13
|
+
- Confidence-level range check in `.add_ci()` and related modifiers
|
|
14
|
+
(must lie in `(0, 1)`).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Renamed several test files for clarity. No public API changes.
|
|
18
|
+
|
|
19
|
+
## [0.1.0a3] — 2026-05-24
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Documentation polish across README, changelog, and inline docstrings.
|
|
23
|
+
No public API or behavioural changes.
|
|
24
|
+
|
|
25
|
+
## [0.1.0a2] — 2026-05-23
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- Theme styling now survives notebook viewers that strip `<style>` blocks
|
|
29
|
+
(e.g. GitHub's notebook viewer). Critical theme properties (font, border,
|
|
30
|
+
padding) are emitted as inline `style` attributes on each table element, so
|
|
31
|
+
`jama` vs `nejm` vs `clinical` vs `minimal` stay visibly distinct everywhere.
|
|
32
|
+
- README image and link URLs are now absolute so they render on PyPI.
|
|
33
|
+
|
|
8
34
|
## [0.1.0a1] — 2026-05-20
|
|
9
35
|
|
|
10
36
|
### Added
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pysofra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a4
|
|
4
4
|
Summary: Statistical reporting and table preparation framework for Python — the missing reporting layer.
|
|
5
5
|
Project-URL: Homepage, https://github.com/jturner-uofl/pysofra
|
|
6
6
|
Project-URL: Documentation, https://github.com/jturner-uofl/pysofra
|
|
7
7
|
Project-URL: Repository, https://github.com/jturner-uofl/pysofra
|
|
8
8
|
Project-URL: Issues, https://github.com/jturner-uofl/pysofra/issues
|
|
9
|
-
Author-email: Jason Turner <jason.
|
|
9
|
+
Author-email: Jason Turner <jason.turner@louisville.edu>
|
|
10
10
|
License: GPL-3.0-or-later
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
License-File: NOTICE
|
|
@@ -111,7 +111,7 @@ Description-Content-Type: text/markdown
|
|
|
111
111
|
- **One immutable object, seven output formats** — build a `SofraTable` once, render to HTML / Markdown / LaTeX / DOCX / PPTX / XLSX / PNG, all byte-deterministic across processes
|
|
112
112
|
- **Auto-dispatched statistical tests** — Welch, Wilcoxon, ANOVA, Kruskal–Wallis, Fisher, χ², Rao–Scott, design-adjusted *t* — picked by variable kind, overridable per-row
|
|
113
113
|
- **Inline forest plots and KM curves** — embed matplotlib figures directly into the table; the same `SofraTable` renders them across every backend
|
|
114
|
-
- **Statistically correct** — every numeric output validated against `scipy` / `statsmodels` / `lifelines` at machine precision
|
|
114
|
+
- **Statistically correct** — every numeric output validated against `scipy` / `statsmodels` / `lifelines` at machine precision, with cross-checks against R's `gtsummary`
|
|
115
115
|
- **Method-chainable and immutable** — every modifier returns a new table; no in-place mutation, no global state, fully reproducible
|
|
116
116
|
|
|
117
117
|
<div align="center">
|
|
@@ -255,23 +255,23 @@ pip install "pysofra[dev]" # testing + linting (pytest, ruff, mypy, hypot
|
|
|
255
255
|
|
|
256
256
|
## Status
|
|
257
257
|
|
|
258
|
-
PySofra is in **alpha** (`0.1.
|
|
258
|
+
PySofra is in **alpha** (`0.1.0a4`). The public API surface is pinned
|
|
259
259
|
by an explicit
|
|
260
|
-
[API-stability test](https://github.com/jturner-uofl/pysofra/blob/main/tests/
|
|
260
|
+
[API-stability test](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_api_stability.py)
|
|
261
261
|
so that any unintended rename, removal, or signature change surfaces as
|
|
262
262
|
a failed test. Quality bar at this release:
|
|
263
263
|
|
|
264
264
|
* **More than 800 tests passing**, **100% line coverage**, mypy strict, ruff clean.
|
|
265
265
|
* Every numeric output is validated against `scipy`, `lifelines`,
|
|
266
266
|
`statsmodels`, or a hand-computed textbook formula
|
|
267
|
-
([
|
|
267
|
+
([test_statistical_correctness.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_statistical_correctness.py)).
|
|
268
268
|
* Universal invariants enforced via Hypothesis on 720 randomized
|
|
269
269
|
examples per CI run
|
|
270
|
-
([
|
|
270
|
+
([test_property_invariants.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_property_invariants.py)).
|
|
271
271
|
* Renderer output is byte-deterministic — identical input always
|
|
272
272
|
produces identical HTML/Markdown/LaTeX, required for reproducible
|
|
273
273
|
publication artifacts
|
|
274
|
-
([
|
|
274
|
+
([test_renderer_consistency.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_renderer_consistency.py)).
|
|
275
275
|
|
|
276
276
|
Bug reports and use-case feedback are very welcome.
|
|
277
277
|
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
- **One immutable object, seven output formats** — build a `SofraTable` once, render to HTML / Markdown / LaTeX / DOCX / PPTX / XLSX / PNG, all byte-deterministic across processes
|
|
46
46
|
- **Auto-dispatched statistical tests** — Welch, Wilcoxon, ANOVA, Kruskal–Wallis, Fisher, χ², Rao–Scott, design-adjusted *t* — picked by variable kind, overridable per-row
|
|
47
47
|
- **Inline forest plots and KM curves** — embed matplotlib figures directly into the table; the same `SofraTable` renders them across every backend
|
|
48
|
-
- **Statistically correct** — every numeric output validated against `scipy` / `statsmodels` / `lifelines` at machine precision
|
|
48
|
+
- **Statistically correct** — every numeric output validated against `scipy` / `statsmodels` / `lifelines` at machine precision, with cross-checks against R's `gtsummary`
|
|
49
49
|
- **Method-chainable and immutable** — every modifier returns a new table; no in-place mutation, no global state, fully reproducible
|
|
50
50
|
|
|
51
51
|
<div align="center">
|
|
@@ -189,23 +189,23 @@ pip install "pysofra[dev]" # testing + linting (pytest, ruff, mypy, hypot
|
|
|
189
189
|
|
|
190
190
|
## Status
|
|
191
191
|
|
|
192
|
-
PySofra is in **alpha** (`0.1.
|
|
192
|
+
PySofra is in **alpha** (`0.1.0a4`). The public API surface is pinned
|
|
193
193
|
by an explicit
|
|
194
|
-
[API-stability test](https://github.com/jturner-uofl/pysofra/blob/main/tests/
|
|
194
|
+
[API-stability test](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_api_stability.py)
|
|
195
195
|
so that any unintended rename, removal, or signature change surfaces as
|
|
196
196
|
a failed test. Quality bar at this release:
|
|
197
197
|
|
|
198
198
|
* **More than 800 tests passing**, **100% line coverage**, mypy strict, ruff clean.
|
|
199
199
|
* Every numeric output is validated against `scipy`, `lifelines`,
|
|
200
200
|
`statsmodels`, or a hand-computed textbook formula
|
|
201
|
-
([
|
|
201
|
+
([test_statistical_correctness.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_statistical_correctness.py)).
|
|
202
202
|
* Universal invariants enforced via Hypothesis on 720 randomized
|
|
203
203
|
examples per CI run
|
|
204
|
-
([
|
|
204
|
+
([test_property_invariants.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_property_invariants.py)).
|
|
205
205
|
* Renderer output is byte-deterministic — identical input always
|
|
206
206
|
produces identical HTML/Markdown/LaTeX, required for reproducible
|
|
207
207
|
publication artifacts
|
|
208
|
-
([
|
|
208
|
+
([test_renderer_consistency.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_renderer_consistency.py)).
|
|
209
209
|
|
|
210
210
|
Bug reports and use-case feedback are very welcome.
|
|
211
211
|
|
|
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pysofra"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.0a4"
|
|
8
8
|
description = "Statistical reporting and table preparation framework for Python — the missing reporting layer."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "GPL-3.0-or-later" }
|
|
11
|
-
authors = [{ name = "Jason Turner", email = "jason.
|
|
11
|
+
authors = [{ name = "Jason Turner", email = "jason.turner@louisville.edu" }]
|
|
12
12
|
requires-python = ">=3.11"
|
|
13
13
|
keywords = [
|
|
14
14
|
"statistics",
|
|
@@ -101,9 +101,8 @@ packages = ["src/pysofra"]
|
|
|
101
101
|
|
|
102
102
|
[tool.hatch.build.targets.sdist]
|
|
103
103
|
# The sdist that goes on PyPI ships the library + tests + license +
|
|
104
|
-
# changelog.
|
|
105
|
-
#
|
|
106
|
-
# download it from the submission tarball, not from PyPI.
|
|
104
|
+
# changelog. Docs, examples, build scripts, and CI config are excluded
|
|
105
|
+
# — users get the importable package, not the development surround.
|
|
107
106
|
include = [
|
|
108
107
|
"src/pysofra",
|
|
109
108
|
"tests",
|
|
@@ -114,13 +113,14 @@ include = [
|
|
|
114
113
|
"pyproject.toml",
|
|
115
114
|
]
|
|
116
115
|
exclude = [
|
|
117
|
-
"paper",
|
|
118
116
|
"docs",
|
|
119
117
|
"examples",
|
|
120
118
|
"scripts",
|
|
121
119
|
"site",
|
|
122
120
|
".github",
|
|
123
|
-
"
|
|
121
|
+
"paper",
|
|
122
|
+
"_local",
|
|
123
|
+
"_private",
|
|
124
124
|
"uv.lock",
|
|
125
125
|
]
|
|
126
126
|
|
|
@@ -158,6 +158,17 @@ exclude_lines = [
|
|
|
158
158
|
[tool.ruff]
|
|
159
159
|
line-length = 100
|
|
160
160
|
target-version = "py311"
|
|
161
|
+
# Notebooks are tutorial / pedagogical material, not library source.
|
|
162
|
+
# Their lint cleanliness is checked separately on demand; the project's
|
|
163
|
+
# "ruff clean" claim applies to the importable package and test suite.
|
|
164
|
+
extend-exclude = [
|
|
165
|
+
"examples/*.ipynb",
|
|
166
|
+
"examples/*.html",
|
|
167
|
+
"site",
|
|
168
|
+
".venv",
|
|
169
|
+
"_local",
|
|
170
|
+
"_private",
|
|
171
|
+
]
|
|
161
172
|
|
|
162
173
|
[tool.ruff.lint]
|
|
163
174
|
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
@@ -571,7 +571,7 @@ class SofraTable:
|
|
|
571
571
|
|
|
572
572
|
Uses matplotlib under the hood; the result is a faithful raster
|
|
573
573
|
of the HTML output. Useful for quick previews, Slack attachments,
|
|
574
|
-
and
|
|
574
|
+
and document figures where a static image is preferable.
|
|
575
575
|
|
|
576
576
|
``scale`` multiplies the pixel density (>= 1 recommended);
|
|
577
577
|
``dpi`` controls the output resolution (defaults to 300, the
|
|
@@ -330,6 +330,20 @@ def add_difference(
|
|
|
330
330
|
*,
|
|
331
331
|
digits: int = 2,
|
|
332
332
|
conf_level: float = 0.95,
|
|
333
|
+
) -> SofraTable:
|
|
334
|
+
"""Add an absolute-difference column with CI for a 2-group Table 1."""
|
|
335
|
+
if not (0.0 < conf_level < 1.0):
|
|
336
|
+
raise ValueError(
|
|
337
|
+
f"conf_level must lie in the open interval (0, 1); got {conf_level!r}."
|
|
338
|
+
)
|
|
339
|
+
return _add_difference_impl(table, digits=digits, conf_level=conf_level)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _add_difference_impl(
|
|
343
|
+
table: SofraTable,
|
|
344
|
+
*,
|
|
345
|
+
digits: int = 2,
|
|
346
|
+
conf_level: float = 0.95,
|
|
333
347
|
) -> SofraTable:
|
|
334
348
|
"""Add an absolute-difference column with CI for a 2-group Table 1.
|
|
335
349
|
|
|
@@ -486,6 +500,10 @@ def add_ci(
|
|
|
486
500
|
mean. For dichotomous rows the ``n (%)`` cell gains a Wilson-score
|
|
487
501
|
CI for the proportion. Multi-level categorical rows are unchanged.
|
|
488
502
|
"""
|
|
503
|
+
if not (0.0 < conf_level < 1.0):
|
|
504
|
+
raise ValueError(
|
|
505
|
+
f"conf_level must lie in the open interval (0, 1); got {conf_level!r}."
|
|
506
|
+
)
|
|
489
507
|
if table._spec is None or table._rebuild is None:
|
|
490
508
|
raise ValueError(
|
|
491
509
|
"add_ci needs access to the source data — only tables built "
|
|
@@ -45,12 +45,12 @@ def continuous_stats(series: pd.Series) -> ContinuousStats:
|
|
|
45
45
|
# subtract`` (and similar). Under ``filterwarnings = error`` — which
|
|
46
46
|
# this project's own pyproject.toml sets and which is a common
|
|
47
47
|
# user-side ``-W error`` posture — those warnings escalate to
|
|
48
|
-
# exceptions and crash ``tbl_one`` on perfectly legal data.
|
|
49
|
-
#
|
|
50
|
-
# ``
|
|
51
|
-
# Wrap arithmetic in ``np.errstate`` + ``catch_warnings`` so
|
|
52
|
-
# stats compute cleanly to ``nan`` / ``inf`` (which the
|
|
53
|
-
# then render as em-dash).
|
|
48
|
+
# exceptions and crash ``tbl_one`` on perfectly legal data. An
|
|
49
|
+
# earlier fix in ``infer_kind`` handled the ``int(np.inf) →
|
|
50
|
+
# OverflowError`` path but did not reach this downstream stats
|
|
51
|
+
# site. Wrap arithmetic in ``np.errstate`` + ``catch_warnings`` so
|
|
52
|
+
# the stats compute cleanly to ``nan`` / ``inf`` (which the
|
|
53
|
+
# formatters then render as em-dash).
|
|
54
54
|
with np.errstate(invalid="ignore", over="ignore"), warnings.catch_warnings():
|
|
55
55
|
warnings.simplefilter("ignore", RuntimeWarning)
|
|
56
56
|
mean = float(np.mean(arr))
|
|
@@ -176,6 +176,18 @@ def tbl_one(
|
|
|
176
176
|
missing_cols = [v for v in variables if v not in data.columns]
|
|
177
177
|
if missing_cols:
|
|
178
178
|
raise KeyError(f"variables not in data: {missing_cols}")
|
|
179
|
+
# Reject duplicate entries early — silently de-duplicating would
|
|
180
|
+
# produce a table whose row count doesn't match the user's list,
|
|
181
|
+
# while keeping duplicates would emit identical rows.
|
|
182
|
+
seen: dict[str, int] = {}
|
|
183
|
+
for v in variables:
|
|
184
|
+
seen[v] = seen.get(v, 0) + 1
|
|
185
|
+
dupes = [v for v, n in seen.items() if n > 1]
|
|
186
|
+
if dupes:
|
|
187
|
+
raise ValueError(
|
|
188
|
+
f"variables contains duplicate names: {dupes}. "
|
|
189
|
+
"Each variable may appear at most once."
|
|
190
|
+
)
|
|
179
191
|
# Warn when the user-supplied variables list overlaps the design /
|
|
180
192
|
# stratification columns; silently dropping them is surprising.
|
|
181
193
|
overlap = [v for v in variables if v in excluded]
|
|
@@ -53,8 +53,8 @@ _DEFAULT = Theme(
|
|
|
53
53
|
"font-size": "14px",
|
|
54
54
|
"line-height": "1.45",
|
|
55
55
|
# Inherit the surrounding text colour so we always have contrast
|
|
56
|
-
# against the actual page background — no
|
|
57
|
-
#
|
|
56
|
+
# against the actual page background — no media-query rules
|
|
57
|
+
# that compete with the host application's own theme.
|
|
58
58
|
"color": "inherit",
|
|
59
59
|
"background": "transparent",
|
|
60
60
|
"margin": "0.75em 0",
|
pysofra-0.1.0a2/tests/test_joss_api_stability.py → pysofra-0.1.0a4/tests/test_api_stability.py
RENAMED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Public API-stability snapshot.
|
|
2
2
|
|
|
3
3
|
Freezes the public surface of ``pysofra`` so any unintended rename,
|
|
4
4
|
removal, or signature change surfaces as a failed test instead of a
|
|
5
5
|
silent breakage downstream.
|
|
6
6
|
|
|
7
7
|
If you intentionally change the API, update the constants in this file
|
|
8
|
-
**and** bump the project version per semver.
|
|
9
|
-
downstream users that JOSS will scrutinise.
|
|
8
|
+
**and** bump the project version per semver.
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
11
|
from __future__ import annotations
|
|
@@ -135,9 +134,9 @@ def test_survey_design_dataclass_fields():
|
|
|
135
134
|
# ----------------------------------------------------------------------
|
|
136
135
|
# SofraTable user-facing methods + dataclass attributes.
|
|
137
136
|
#
|
|
138
|
-
# Both sets are part of the
|
|
139
|
-
# call them in their reporting code, attributes because they
|
|
140
|
-
# the documented schema (
|
|
137
|
+
# Both sets are part of the documented public API: methods because
|
|
138
|
+
# users call them in their reporting code, attributes because they
|
|
139
|
+
# appear in the documented schema (README and notebook examples
|
|
141
140
|
# enumerate ``rows``, ``headers``, ``footnotes``, etc.).
|
|
142
141
|
# ----------------------------------------------------------------------
|
|
143
142
|
EXPECTED_SOFRATABLE_METHODS = frozenset({
|
|
@@ -187,7 +186,7 @@ def test_sofratable_attribute_surface():
|
|
|
187
186
|
def test_sofratable_no_undocumented_public_surface():
|
|
188
187
|
"""
|
|
189
188
|
Reject silently-added public names. Anything new must be added to
|
|
190
|
-
one of the two expected-sets above (so
|
|
189
|
+
one of the two expected-sets above (so downstream users see the API
|
|
191
190
|
change in the diff).
|
|
192
191
|
"""
|
|
193
192
|
public = {m for m in dir(SofraTable) if not m.startswith("_")}
|
|
@@ -201,6 +200,6 @@ def test_sofratable_no_undocumented_public_surface():
|
|
|
201
200
|
|
|
202
201
|
|
|
203
202
|
def test_sofratable_to_image_signature_stable():
|
|
204
|
-
"""The PNG renderer's public kwargs are part of the
|
|
203
|
+
"""The PNG renderer's public kwargs are part of the documented public API."""
|
|
205
204
|
actual = tuple(p[0] for p in _params(SofraTable.to_image))
|
|
206
205
|
assert actual == ("self", "path", "scale", "dpi")
|
|
@@ -236,7 +236,7 @@ class TestExtrasFinal:
|
|
|
236
236
|
def test_add_global_p_metadata_missing_f_test(self):
|
|
237
237
|
# A SofraTable whose metadata['model'] doesn't have .f_test now
|
|
238
238
|
# raises NotImplementedError rather than silently inserting an
|
|
239
|
-
# em-dash column (which
|
|
239
|
+
# em-dash column (which would be misleading).
|
|
240
240
|
from pysofra.core.schema import Cell, HeaderCell, HeaderRow, Row
|
|
241
241
|
from pysofra.core.table import SofraTable
|
|
242
242
|
|
|
@@ -353,9 +353,9 @@ class TestTypingCorners:
|
|
|
353
353
|
def test_datetime_falls_through_to_categorical(self):
|
|
354
354
|
# Backwards-compatible fallback: PySofra doesn't natively
|
|
355
355
|
# summarise datetimes, so it returns ``"categorical"`` (so the
|
|
356
|
-
# caller's table doesn't crash). The warning emitted
|
|
357
|
-
#
|
|
358
|
-
# tests/
|
|
356
|
+
# caller's table doesn't crash). The warning emitted alongside
|
|
357
|
+
# the fallback is verified in
|
|
358
|
+
# tests/test_regressions.py::TestInferKindWarnsOnTemporal.
|
|
359
359
|
import pytest
|
|
360
360
|
|
|
361
361
|
from pysofra.summary.typing import infer_kind
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Targeted regression tests for previously-fixed defects.
|
|
2
2
|
|
|
3
|
-
Each test pins a
|
|
4
|
-
|
|
5
|
-
locate.
|
|
3
|
+
Each test pins a specific bug so that any regression is easy to
|
|
4
|
+
locate by name.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from __future__ import annotations
|
|
@@ -15,7 +14,7 @@ import pysofra as ps
|
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
# ----------------------------------------------------------------------
|
|
18
|
-
#
|
|
17
|
+
# Predictor / adjust_for overlap raises cleanly
|
|
19
18
|
# ----------------------------------------------------------------------
|
|
20
19
|
class TestUvregressionOverlapErrors:
|
|
21
20
|
def test_predictor_also_in_adjust_for_raises(self):
|
|
@@ -46,7 +45,7 @@ class TestUvregressionOverlapErrors:
|
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
# ----------------------------------------------------------------------
|
|
49
|
-
#
|
|
48
|
+
# Formula-API model works through the design refit
|
|
50
49
|
# ----------------------------------------------------------------------
|
|
51
50
|
class TestFormulaAPIRoundTrip:
|
|
52
51
|
def test_design_refit_with_smf_ols(self):
|
|
@@ -69,7 +68,7 @@ class TestFormulaAPIRoundTrip:
|
|
|
69
68
|
|
|
70
69
|
|
|
71
70
|
# ----------------------------------------------------------------------
|
|
72
|
-
#
|
|
71
|
+
# High-cardinality factor scales reasonably
|
|
73
72
|
# ----------------------------------------------------------------------
|
|
74
73
|
class TestHighCardinalityFactor:
|
|
75
74
|
def test_50_level_factor_completes_quickly(self):
|
|
@@ -91,7 +90,7 @@ class TestHighCardinalityFactor:
|
|
|
91
90
|
|
|
92
91
|
|
|
93
92
|
# ----------------------------------------------------------------------
|
|
94
|
-
#
|
|
93
|
+
# Newcombe handles imbalanced n
|
|
95
94
|
# ----------------------------------------------------------------------
|
|
96
95
|
class TestNewcombeImbalanced:
|
|
97
96
|
def test_imbalanced_n_produces_valid_ci(self):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Determinism tests for plot-embedded tables.
|
|
2
2
|
|
|
3
|
-
The renderer-consistency suite (`
|
|
3
|
+
The renderer-consistency suite (`test_renderer_consistency.py`)
|
|
4
4
|
verifies determinism for plain text-only tables. This file extends
|
|
5
5
|
that guarantee to tables that have an inline matplotlib plot
|
|
6
6
|
(`with_forest_plot`, `with_km_plot`) — historically the source of
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Targeted regression tests for previously-fixed defects.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Each test corresponds to a real bug and the patch that closed it.
|
|
4
|
+
Each test should fail if the patch is reverted.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -630,9 +630,9 @@ class TestInferKindInfSafe:
|
|
|
630
630
|
through. Under ``filterwarnings = error`` (the project's own
|
|
631
631
|
pyproject.toml gate, and a common user ``-W error`` posture), that
|
|
632
632
|
warning escalates to an exception and the table build crashes
|
|
633
|
-
despite the
|
|
634
|
-
below pins the
|
|
635
|
-
end-to-end clean.
|
|
633
|
+
despite the earlier ``int(np.inf)`` fix in ``infer_kind``.
|
|
634
|
+
``test_inf_does_not_leak_runtime_warning`` below pins the
|
|
635
|
+
strict-warning behaviour so the inf path is end-to-end clean.
|
|
636
636
|
"""
|
|
637
637
|
|
|
638
638
|
def test_inf_in_numeric_column_does_not_crash(self):
|
|
@@ -685,7 +685,7 @@ class TestInferKindInfSafe:
|
|
|
685
685
|
|
|
686
686
|
|
|
687
687
|
# ----------------------------------------------------------------------
|
|
688
|
-
# SofraTable must round-trip through pickle
|
|
688
|
+
# SofraTable must round-trip through pickle
|
|
689
689
|
# ----------------------------------------------------------------------
|
|
690
690
|
class TestSofraTablePicklability:
|
|
691
691
|
"""A SofraTable produced by a builder must survive ``pickle.dumps``/
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Statistical correctness validation against independent references.
|
|
2
2
|
|
|
3
3
|
Every numeric routine in PySofra is verified against an independent
|
|
4
4
|
reference: scipy / lifelines / statsmodels direct calls, hand-computed
|
|
5
|
-
textbook formulas, or published reference values.
|
|
6
|
-
|
|
7
|
-
``
|
|
8
|
-
|
|
5
|
+
textbook formulas, or published reference values. Any number rendered
|
|
6
|
+
by ``tbl_one`` / ``tbl_regression`` / ``tbl_survival`` /
|
|
7
|
+
``tbl_uvregression`` can be traced back through this file to where it
|
|
8
|
+
came from.
|
|
9
9
|
|
|
10
|
-
Each test
|
|
10
|
+
Each test names its reference source in the docstring.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Regression tests for input-validation edge cases.
|
|
2
2
|
|
|
3
|
-
Each test pins a specific defect
|
|
4
|
-
|
|
5
|
-
finding.
|
|
3
|
+
Each test pins a specific defect so any future regression surfaces
|
|
4
|
+
with a clear name pointing back at the finding.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
from __future__ import annotations
|
|
@@ -17,7 +16,7 @@ import pysofra as ps
|
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
# ======================================================================
|
|
20
|
-
#
|
|
19
|
+
# add_difference uses Newcombe (Wilson-based) CI
|
|
21
20
|
# ======================================================================
|
|
22
21
|
class TestNewcombeDifferenceCI:
|
|
23
22
|
def test_matches_statsmodels_newcomb(self):
|
|
@@ -76,7 +75,7 @@ class TestNewcombeDifferenceCI:
|
|
|
76
75
|
|
|
77
76
|
|
|
78
77
|
# ======================================================================
|
|
79
|
-
#
|
|
78
|
+
# Markdown spanners + escaping
|
|
80
79
|
# ======================================================================
|
|
81
80
|
class TestMarkdownSpannersAndEscape:
|
|
82
81
|
def test_spanning_header_not_inserted_as_pipe_row(self):
|
|
@@ -138,7 +137,7 @@ class TestMarkdownSpannersAndEscape:
|
|
|
138
137
|
|
|
139
138
|
|
|
140
139
|
# ======================================================================
|
|
141
|
-
#
|
|
140
|
+
# labels= no longer breaks downstream modifiers
|
|
142
141
|
# ======================================================================
|
|
143
142
|
class TestLabelsPreservedDownstream:
|
|
144
143
|
def _df(self):
|
|
@@ -171,7 +170,7 @@ class TestLabelsPreservedDownstream:
|
|
|
171
170
|
|
|
172
171
|
|
|
173
172
|
# ======================================================================
|
|
174
|
-
#
|
|
173
|
+
# N at risk uses standard convention
|
|
175
174
|
# ======================================================================
|
|
176
175
|
class TestNAtRisk:
|
|
177
176
|
def test_n_at_risk_matches_manual_count(self):
|
|
@@ -196,7 +195,7 @@ class TestNAtRisk:
|
|
|
196
195
|
|
|
197
196
|
|
|
198
197
|
# ======================================================================
|
|
199
|
-
#
|
|
198
|
+
# add_global_p raises clearly on tbl_one
|
|
200
199
|
# ======================================================================
|
|
201
200
|
class TestAddGlobalPOnTblOne:
|
|
202
201
|
# ``add_global_p()`` is implemented for tbl_one / tbl_summary
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Tests for
|
|
1
|
+
"""Tests for R-ecosystem feature parity: tbl_cross, effect sizes,
|
|
2
2
|
add_significance_stars / add_n / add_stat_label / color_scale_if,
|
|
3
|
-
MixedLM/GEE extractor, and
|
|
3
|
+
MixedLM/GEE extractor, and multiple-imputation pooling (Rubin's rules)."""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
pysofra-0.1.0a2/.gitignore
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
__pycache__/
|
|
2
|
-
*.py[cod]
|
|
3
|
-
*.egg-info/
|
|
4
|
-
dist/
|
|
5
|
-
build/
|
|
6
|
-
.venv/
|
|
7
|
-
venv/
|
|
8
|
-
.env
|
|
9
|
-
|
|
10
|
-
.pytest_cache/
|
|
11
|
-
.mypy_cache/
|
|
12
|
-
.ruff_cache/
|
|
13
|
-
.coverage
|
|
14
|
-
htmlcov/
|
|
15
|
-
|
|
16
|
-
.DS_Store
|
|
17
|
-
.idea/
|
|
18
|
-
.vscode/
|
|
19
|
-
.claude/
|
|
20
|
-
|
|
21
|
-
site/
|
|
22
|
-
docs/_build/
|
|
23
|
-
|
|
24
|
-
# Tutorial / example export artefacts — produced on demand by
|
|
25
|
-
# scripts/render_tutorial.py, not part of the source tree.
|
|
26
|
-
*.docx
|
|
27
|
-
*.pptx
|
|
28
|
-
*.xlsx
|
|
29
|
-
examples/tutorial_*.docx
|
|
30
|
-
examples/tutorial_*.pptx
|
|
31
|
-
examples/tutorial_*.xlsx
|
|
32
|
-
examples/tutorial_*.png
|
|
33
|
-
!tests/fixtures/**/*.docx
|
|
34
|
-
!tests/fixtures/**/*.pptx
|
|
35
|
-
!tests/fixtures/**/*.xlsx
|
|
36
|
-
|
|
37
|
-
# Hypothesis example database (auto-managed)
|
|
38
|
-
.hypothesis/
|
|
39
|
-
|
|
40
|
-
# uv writes a lockfile when ``uv add`` / ``uv pip install`` is used
|
|
41
|
-
# locally; the project pins its runtime versions explicitly in
|
|
42
|
-
# pyproject.toml + paper/replication/requirements.txt, so the uv
|
|
43
|
-
# lockfile is a local convenience that should not ship.
|
|
44
|
-
uv.lock
|
|
45
|
-
|
|
46
|
-
# JSS paper bundle — paper/ contains the manuscript, bibliography,
|
|
47
|
-
# embedded figures, and replication archive. The .tex / .bib / .py /
|
|
48
|
-
# .R sources are ALWAYS tracked. Replication outputs are tracked in
|
|
49
|
-
# the text formats reviewers diff (json / tex / md / html / png /
|
|
50
|
-
# pdf / svg) so the archive ships with a working baseline; only the
|
|
51
|
-
# heavy regenerable Office binaries (docx / pptx / xlsx) and the
|
|
52
|
-
# fontconfig / mpl cache directories are ignored.
|
|
53
|
-
paper/replication/trial.csv
|
|
54
|
-
paper/replication/table*.docx
|
|
55
|
-
paper/replication/table*.pptx
|
|
56
|
-
paper/replication/table*.xlsx
|
|
57
|
-
paper/replication/.mplconfig/
|
|
58
|
-
|
|
59
|
-
# Submission-required text outputs and figures: explicit re-include.
|
|
60
|
-
# The blanket repo-wide ``*.docx`` rule at the top of this file would
|
|
61
|
-
# otherwise eat ``paper/replication/table*.html`` etc. — these
|
|
62
|
-
# negations keep them tracked.
|
|
63
|
-
!paper/replication/paper_outputs*.json
|
|
64
|
-
!paper/replication/table*.html
|
|
65
|
-
!paper/replication/table*.tex
|
|
66
|
-
!paper/replication/table*.md
|
|
67
|
-
!paper/replication/figures/
|
|
68
|
-
!paper/replication/figures/*
|
|
69
|
-
|
|
70
|
-
# The `paper/figures/` directory IS tracked because paper.tex embeds
|
|
71
|
-
# its PDFs/PNGs via \includegraphics. To regenerate, run
|
|
72
|
-
# paper/replication/example_trial.py then copy the desired files in.
|
|
73
|
-
!paper/figures/
|
|
74
|
-
!paper/figures/*
|
|
75
|
-
|
|
76
|
-
# LaTeX build artefacts (in case anyone compiles paper.tex locally).
|
|
77
|
-
paper/*.aux
|
|
78
|
-
paper/*.log
|
|
79
|
-
paper/*.out
|
|
80
|
-
paper/*.toc
|
|
81
|
-
paper/*.bbl
|
|
82
|
-
paper/*.blg
|
|
83
|
-
paper/*.synctex.gz
|
|
84
|
-
paper/paper.pdf
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# paper/ is excluded from the public repo until JSS submission lands.
|
|
88
|
-
# It stays in the local working tree for editing; the replication
|
|
89
|
-
# archive moves into the repo at submission time.
|
|
90
|
-
paper/
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""I/O utilities — reserved for future readers (Stata/SAS/Excel)."""
|
|
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
|
|
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
|