pysofra 0.1.0a1__tar.gz → 0.1.0a3__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.
Files changed (109) hide show
  1. pysofra-0.1.0a3/.gitignore +45 -0
  2. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/CHANGELOG.md +15 -0
  3. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/PKG-INFO +23 -30
  4. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/README.md +22 -29
  5. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/pyproject.toml +4 -5
  6. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/__init__.py +1 -1
  7. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/stats.py +6 -6
  8. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_extract_edges.py +1 -1
  9. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_extras_edges.py +3 -3
  10. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_joss_api_stability.py +2 -3
  11. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_joss_property_invariants.py +1 -1
  12. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_joss_renderer_consistency.py +1 -1
  13. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_joss_statistical_correctness.py +6 -6
  14. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_misc_fixes.py +7 -8
  15. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_regressions.py +7 -7
  16. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_validation_fixes.py +2 -3
  17. pysofra-0.1.0a1/.gitignore +0 -90
  18. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/LICENSE +0 -0
  19. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/NOTICE +0 -0
  20. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/__init__.py +0 -0
  21. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/compose.py +0 -0
  22. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/format.py +0 -0
  23. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/frames.py +0 -0
  24. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/schema.py +0 -0
  25. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/core/table.py +0 -0
  26. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/io/__init__.py +0 -0
  27. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/__init__.py +0 -0
  28. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/extract.py +0 -0
  29. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/pool.py +0 -0
  30. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/regression.py +0 -0
  31. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/survival.py +0 -0
  32. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/models/uvregression.py +0 -0
  33. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/notebook/__init__.py +0 -0
  34. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/__init__.py +0 -0
  35. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/_backend.py +0 -0
  36. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/forest.py +0 -0
  37. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/inline.py +0 -0
  38. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/plot/km.py +0 -0
  39. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/__init__.py +0 -0
  40. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/_zip_determinism.py +0 -0
  41. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/base.py +0 -0
  42. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/docx.py +0 -0
  43. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/html.py +0 -0
  44. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/image.py +0 -0
  45. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/latex.py +0 -0
  46. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/markdown.py +0 -0
  47. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/pptx.py +0 -0
  48. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/render/xlsx.py +0 -0
  49. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/__init__.py +0 -0
  50. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/calibrate.py +0 -0
  51. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/design.py +0 -0
  52. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/effect_size.py +0 -0
  53. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/extras.py +0 -0
  54. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/smd.py +0 -0
  55. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/tbl_cross.py +0 -0
  56. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/tbl_one.py +0 -0
  57. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/tbl_summary.py +0 -0
  58. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/tests.py +0 -0
  59. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/typing.py +0 -0
  60. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/summary/weights.py +0 -0
  61. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/themes/__init__.py +0 -0
  62. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/src/pysofra/themes/registry.py +0 -0
  63. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/conftest.py +0 -0
  64. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/README.md +0 -0
  65. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/anova_oneway.json +0 -0
  66. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/chi_square.json +0 -0
  67. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/fisher_2x2.json +0 -0
  68. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/kruskal_wallis.json +0 -0
  69. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/student_t.json +0 -0
  70. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/svyttest.json +0 -0
  71. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/weighted_mean.json +0 -0
  72. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/welch_t_test.json +0 -0
  73. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/fixtures/scipy_validation/wilcoxon_rank_sum.json +0 -0
  74. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_compose.py +0 -0
  75. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_compose_edges.py +0 -0
  76. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_conditional_formatting.py +0 -0
  77. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_design_regression.py +0 -0
  78. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_extras_edges_2.py +0 -0
  79. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_format.py +0 -0
  80. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_latex_pptx.py +0 -0
  81. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_modifier_edges.py +0 -0
  82. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_multi_model.py +0 -0
  83. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_partial_modifiers.py +0 -0
  84. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_partials.py +0 -0
  85. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_plot_determinism.py +0 -0
  86. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_plot_embedding.py +0 -0
  87. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_plots.py +0 -0
  88. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_polars.py +0 -0
  89. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_pptx_overflow.py +0 -0
  90. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_rao_scott.py +0 -0
  91. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_regression.py +0 -0
  92. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_render_edges.py +0 -0
  93. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_render_edges_2.py +0 -0
  94. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_rendering.py +0 -0
  95. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_scipy_validation.py +0 -0
  96. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_snapshot.py +0 -0
  97. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_stats.py +0 -0
  98. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_summary_edges.py +0 -0
  99. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_summary_edges_2.py +0 -0
  100. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_survey_design.py +0 -0
  101. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_survey_extensions.py +0 -0
  102. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_survival.py +0 -0
  103. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_table_edges.py +0 -0
  104. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_tbl_one.py +0 -0
  105. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_test_overrides.py +0 -0
  106. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_uvregression_factors.py +0 -0
  107. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_weights.py +0 -0
  108. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_wishlist.py +0 -0
  109. {pysofra-0.1.0a1 → pysofra-0.1.0a3}/tests/test_xlsx.py +0 -0
@@ -0,0 +1,45 @@
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
+ paper/
24
+
25
+ # Tutorial / example export artefacts — produced on demand by
26
+ # scripts/render_tutorial.py, not part of the source tree.
27
+ *.docx
28
+ *.pptx
29
+ *.xlsx
30
+ examples/tutorial_*.docx
31
+ examples/tutorial_*.pptx
32
+ examples/tutorial_*.xlsx
33
+ examples/tutorial_*.png
34
+ !tests/fixtures/**/*.docx
35
+ !tests/fixtures/**/*.pptx
36
+ !tests/fixtures/**/*.xlsx
37
+
38
+ # Hypothesis example database (auto-managed)
39
+ .hypothesis/
40
+
41
+ # uv writes a lockfile when ``uv add`` / ``uv pip install`` is used
42
+ # locally; the project pins its runtime versions explicitly in
43
+ # pyproject.toml, so the uv lockfile is a local convenience that
44
+ # should not ship.
45
+ uv.lock
@@ -5,6 +5,21 @@ 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.0a3] — 2026-05-24
9
+
10
+ ### Changed
11
+ - Documentation polish: scrubbed internal-process references from test
12
+ docstrings and inline comments. No public API or behavioural changes.
13
+
14
+ ## [0.1.0a2] — 2026-05-23
15
+
16
+ ### Fixed
17
+ - Theme styling now survives notebook viewers that strip `<style>` blocks
18
+ (e.g. GitHub's notebook viewer). Critical theme properties (font, border,
19
+ padding) are emitted as inline `style` attributes on each table element, so
20
+ `jama` vs `nejm` vs `clinical` vs `minimal` stay visibly distinct everywhere.
21
+ - README image and link URLs are now absolute so they render on PyPI.
22
+
8
23
  ## [0.1.0a1] — 2026-05-20
9
24
 
10
25
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysofra
3
- Version: 0.1.0a1
3
+ Version: 0.1.0a3
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
@@ -72,7 +72,7 @@ Description-Content-Type: text/markdown
72
72
 
73
73
  [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/jturner-uofl/pysofra)
74
74
  [![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue.svg)](https://www.python.org/downloads/)
75
- [![License: GPL-3.0+](https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg)](LICENSE)
75
+ [![License: GPL-3.0+](https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg)](https://github.com/jturner-uofl/pysofra/blob/main/LICENSE)
76
76
  [![Style: ruff](https://img.shields.io/badge/style-ruff-purple.svg)](https://github.com/astral-sh/ruff)
77
77
  [![Types: mypy strict](https://img.shields.io/badge/types-mypy%20strict-blue.svg)](http://mypy-lang.org/)
78
78
  [![Tests: 886](https://img.shields.io/badge/tests-886%20passing-brightgreen.svg)](#status)
@@ -111,14 +111,14 @@ 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 (and cross-checked against R's `gtsummary` for the JSS paper)
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">
118
118
 
119
- **[Showcase notebook](examples/pysofra_showcase.ipynb)** · [rendered HTML](examples/pysofra_showcase.html) — *47 cells, every section a side-by-side numeric proof. Start here if you have 60 seconds.*
119
+ **[Showcase notebook](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_showcase.ipynb)** — *47 cells, every section a side-by-side numeric proof. Start here if you have 60 seconds.*
120
120
 
121
- **[End-to-end tutorial](examples/pysofra_tutorial.ipynb)** · [rendered HTML](examples/pysofra_tutorial.html) — *126 cells walking every public feature on a synthetic two-arm trial.*
121
+ **[End-to-end tutorial](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_tutorial.ipynb)** — *126 cells walking every public feature on a synthetic two-arm trial.*
122
122
 
123
123
  </div>
124
124
 
@@ -171,10 +171,10 @@ fit = sm.Logit(df["event"], X).fit(disp=False)
171
171
  )
172
172
  ```
173
173
 
174
- The full worked example from the JSS paper — baseline table by
175
- treatment arm, regression table with forest plot, and Kaplan-Meier
176
- survival summary — is in
177
- [`paper/replication/example_trial.py`](paper/replication/example_trial.py).
174
+ The end-to-end worked example — baseline table by treatment arm,
175
+ regression table with forest plot, and Kaplan-Meier survival summary —
176
+ is in the
177
+ [showcase notebook](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_showcase.ipynb).
178
178
 
179
179
  ---
180
180
 
@@ -255,47 +255,40 @@ pip install "pysofra[dev]" # testing + linting (pytest, ruff, mypy, hypot
255
255
 
256
256
  ## Status
257
257
 
258
- PySofra is in **alpha** (`0.1.0a1`). The public API surface is pinned
258
+ PySofra is in **alpha** (`0.1.0a3`). The public API surface is pinned
259
259
  by an explicit
260
- [API-stability test](tests/test_joss_api_stability.py) so that any
261
- unintended rename, removal, or signature change surfaces as a failed
262
- test. Quality bar at this release:
260
+ [API-stability test](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_api_stability.py)
261
+ so that any unintended rename, removal, or signature change surfaces as
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
- ([test_joss_statistical_correctness.py](tests/test_joss_statistical_correctness.py)).
267
+ ([test_joss_statistical_correctness.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_statistical_correctness.py)).
268
268
  * Universal invariants enforced via Hypothesis on 720 randomized
269
269
  examples per CI run
270
- ([test_joss_property_invariants.py](tests/test_joss_property_invariants.py)).
270
+ ([test_joss_property_invariants.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_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
- ([test_joss_renderer_consistency.py](tests/test_joss_renderer_consistency.py)).
274
+ ([test_joss_renderer_consistency.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_renderer_consistency.py)).
275
275
 
276
276
  Bug reports and use-case feedback are very welcome.
277
277
 
278
- A **Journal of Statistical Software** paper ([`paper/paper.tex`](paper/paper.tex),
279
- bibliography in [`paper/paper.bib`](paper/paper.bib)) is in
280
- preparation. The [`paper/replication/`](paper/replication/) directory
281
- regenerates every numeric output and figure shown in the paper and
282
- includes an R cross-check script (`example_trial.R`) using
283
- `gtsummary` for digit-level verification. The
284
- [`paper/figures/`](paper/figures/) directory holds the rendered PDF
285
- and PNG embedded in the manuscript via `\includegraphics{}`.
286
-
287
278
  ## Contributing
288
279
 
289
280
  Bug reports, feature requests, and pull requests are all very welcome.
290
- Please read [`CONTRIBUTING.md`](CONTRIBUTING.md) for the workflow, the
291
- quality gates, and the
292
- [Code of Conduct](CODE_OF_CONDUCT.md).
281
+ Please read
282
+ [`CONTRIBUTING.md`](https://github.com/jturner-uofl/pysofra/blob/main/CONTRIBUTING.md)
283
+ for the workflow, the quality gates, and the
284
+ [Code of Conduct](https://github.com/jturner-uofl/pysofra/blob/main/CODE_OF_CONDUCT.md).
293
285
 
294
286
  ## License
295
287
 
296
- GPL-3.0-or-later. See [`LICENSE`](LICENSE).
288
+ GPL-3.0-or-later. See
289
+ [`LICENSE`](https://github.com/jturner-uofl/pysofra/blob/main/LICENSE).
297
290
 
298
291
  ## Citation
299
292
 
300
293
  If you use PySofra in academic work, please cite the project — see
301
- [`CITATION.cff`](CITATION.cff).
294
+ [`CITATION.cff`](https://github.com/jturner-uofl/pysofra/blob/main/CITATION.cff).
@@ -6,7 +6,7 @@
6
6
 
7
7
  [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/jturner-uofl/pysofra)
8
8
  [![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue.svg)](https://www.python.org/downloads/)
9
- [![License: GPL-3.0+](https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg)](LICENSE)
9
+ [![License: GPL-3.0+](https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg)](https://github.com/jturner-uofl/pysofra/blob/main/LICENSE)
10
10
  [![Style: ruff](https://img.shields.io/badge/style-ruff-purple.svg)](https://github.com/astral-sh/ruff)
11
11
  [![Types: mypy strict](https://img.shields.io/badge/types-mypy%20strict-blue.svg)](http://mypy-lang.org/)
12
12
  [![Tests: 886](https://img.shields.io/badge/tests-886%20passing-brightgreen.svg)](#status)
@@ -45,14 +45,14 @@
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 (and cross-checked against R's `gtsummary` for the JSS paper)
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">
52
52
 
53
- **[Showcase notebook](examples/pysofra_showcase.ipynb)** · [rendered HTML](examples/pysofra_showcase.html) — *47 cells, every section a side-by-side numeric proof. Start here if you have 60 seconds.*
53
+ **[Showcase notebook](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_showcase.ipynb)** — *47 cells, every section a side-by-side numeric proof. Start here if you have 60 seconds.*
54
54
 
55
- **[End-to-end tutorial](examples/pysofra_tutorial.ipynb)** · [rendered HTML](examples/pysofra_tutorial.html) — *126 cells walking every public feature on a synthetic two-arm trial.*
55
+ **[End-to-end tutorial](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_tutorial.ipynb)** — *126 cells walking every public feature on a synthetic two-arm trial.*
56
56
 
57
57
  </div>
58
58
 
@@ -105,10 +105,10 @@ fit = sm.Logit(df["event"], X).fit(disp=False)
105
105
  )
106
106
  ```
107
107
 
108
- The full worked example from the JSS paper — baseline table by
109
- treatment arm, regression table with forest plot, and Kaplan-Meier
110
- survival summary — is in
111
- [`paper/replication/example_trial.py`](paper/replication/example_trial.py).
108
+ The end-to-end worked example — baseline table by treatment arm,
109
+ regression table with forest plot, and Kaplan-Meier survival summary —
110
+ is in the
111
+ [showcase notebook](https://github.com/jturner-uofl/pysofra/blob/main/examples/pysofra_showcase.ipynb).
112
112
 
113
113
  ---
114
114
 
@@ -189,47 +189,40 @@ pip install "pysofra[dev]" # testing + linting (pytest, ruff, mypy, hypot
189
189
 
190
190
  ## Status
191
191
 
192
- PySofra is in **alpha** (`0.1.0a1`). The public API surface is pinned
192
+ PySofra is in **alpha** (`0.1.0a3`). The public API surface is pinned
193
193
  by an explicit
194
- [API-stability test](tests/test_joss_api_stability.py) so that any
195
- unintended rename, removal, or signature change surfaces as a failed
196
- test. Quality bar at this release:
194
+ [API-stability test](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_api_stability.py)
195
+ so that any unintended rename, removal, or signature change surfaces as
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
- ([test_joss_statistical_correctness.py](tests/test_joss_statistical_correctness.py)).
201
+ ([test_joss_statistical_correctness.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_statistical_correctness.py)).
202
202
  * Universal invariants enforced via Hypothesis on 720 randomized
203
203
  examples per CI run
204
- ([test_joss_property_invariants.py](tests/test_joss_property_invariants.py)).
204
+ ([test_joss_property_invariants.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_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
- ([test_joss_renderer_consistency.py](tests/test_joss_renderer_consistency.py)).
208
+ ([test_joss_renderer_consistency.py](https://github.com/jturner-uofl/pysofra/blob/main/tests/test_joss_renderer_consistency.py)).
209
209
 
210
210
  Bug reports and use-case feedback are very welcome.
211
211
 
212
- A **Journal of Statistical Software** paper ([`paper/paper.tex`](paper/paper.tex),
213
- bibliography in [`paper/paper.bib`](paper/paper.bib)) is in
214
- preparation. The [`paper/replication/`](paper/replication/) directory
215
- regenerates every numeric output and figure shown in the paper and
216
- includes an R cross-check script (`example_trial.R`) using
217
- `gtsummary` for digit-level verification. The
218
- [`paper/figures/`](paper/figures/) directory holds the rendered PDF
219
- and PNG embedded in the manuscript via `\includegraphics{}`.
220
-
221
212
  ## Contributing
222
213
 
223
214
  Bug reports, feature requests, and pull requests are all very welcome.
224
- Please read [`CONTRIBUTING.md`](CONTRIBUTING.md) for the workflow, the
225
- quality gates, and the
226
- [Code of Conduct](CODE_OF_CONDUCT.md).
215
+ Please read
216
+ [`CONTRIBUTING.md`](https://github.com/jturner-uofl/pysofra/blob/main/CONTRIBUTING.md)
217
+ for the workflow, the quality gates, and the
218
+ [Code of Conduct](https://github.com/jturner-uofl/pysofra/blob/main/CODE_OF_CONDUCT.md).
227
219
 
228
220
  ## License
229
221
 
230
- GPL-3.0-or-later. See [`LICENSE`](LICENSE).
222
+ GPL-3.0-or-later. See
223
+ [`LICENSE`](https://github.com/jturner-uofl/pysofra/blob/main/LICENSE).
231
224
 
232
225
  ## Citation
233
226
 
234
227
  If you use PySofra in academic work, please cite the project — see
235
- [`CITATION.cff`](CITATION.cff).
228
+ [`CITATION.cff`](https://github.com/jturner-uofl/pysofra/blob/main/CITATION.cff).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pysofra"
7
- version = "0.1.0a1"
7
+ version = "0.1.0a3"
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" }
@@ -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. The JSS paper bundle (paper/) is intentionally NOT
105
- # included: it lives in git for source consumers and reviewers
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,13 @@ 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
  ".claude",
122
+ "paper",
124
123
  "uv.lock",
125
124
  ]
126
125
 
@@ -50,7 +50,7 @@ from .summary.tbl_summary import tbl_summary
50
50
  from .summary.tests import available_tests
51
51
  from .themes.registry import available_themes, register_theme
52
52
 
53
- __version__ = "0.1.0a1"
53
+ __version__ = "0.1.0a3"
54
54
 
55
55
  __all__ = [
56
56
  "CellPart",
@@ -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. The R6
49
- # audit fixed the ``int(np.inf) → OverflowError`` path in
50
- # ``infer_kind`` but didn't reach this downstream stats site.
51
- # Wrap arithmetic in ``np.errstate`` + ``catch_warnings`` so the
52
- # stats compute cleanly to ``nan`` / ``inf`` (which the formatters
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))
@@ -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 a JOSS reviewer flagged as misleading).
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 by the
357
- # Fix is verified in
358
- # tests/test_audit_regressions.py::TestInferKindWarnsOnTemporal.
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,12 +1,11 @@
1
- """JOSS-grade API-stability snapshot.
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. This is the contract with
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
@@ -1,4 +1,4 @@
1
- """JOSS-grade property-based invariant testing.
1
+ """Property-based invariant testing via Hypothesis.
2
2
 
3
3
  Uses Hypothesis to generate hundreds of random DataFrames and exercise
4
4
  PySofra against invariants that must hold *for every input*:
@@ -1,4 +1,4 @@
1
- """JOSS-grade cross-renderer consistency validation.
1
+ """Cross-renderer consistency validation.
2
2
 
3
3
  A single ``SofraTable`` must render to HTML / Markdown / LaTeX / DOCX /
4
4
  PPTX / XLSX with **consistent content**: the same cell texts, the
@@ -1,13 +1,13 @@
1
- """JOSS-grade statistical correctness validation.
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. A JOSS reviewer
6
- should be able to point at any number rendered by ``tbl_one`` /
7
- ``tbl_regression`` / ``tbl_survival`` / ``tbl_uvregression`` and have
8
- this file demonstrate where it came from.
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 in this file names its reference source in the docstring.
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
- """Audit-discovered defects pinned by regression tests.
1
+ """Targeted regression tests for previously-fixed defects.
2
2
 
3
- Each test pins a finding from the most recent god-mode audit pass.
4
- The names are deliberately literal so that any regression is easy to
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
- # Audit P4 — predictor / adjust_for overlap raises cleanly
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
- # Audit P2 — formula-API model works through the design refit
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
- # Audit P3 — high-cardinality factor scales reasonably
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
- # Audit P1 — Newcombe handles imbalanced n
93
+ # Newcombe handles imbalanced n
95
94
  # ----------------------------------------------------------------------
96
95
  class TestNewcombeImbalanced:
97
96
  def test_imbalanced_n_produces_valid_ci(self):
@@ -1,7 +1,7 @@
1
- """Regression tests for issues caught by the full-system audit.
1
+ """Targeted regression tests for previously-fixed defects.
2
2
 
3
- Every test here corresponds to a real bug the audit surfaced and the
4
- patch that closed it. Each test should fail if the patch is reverted.
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 R6 fix. ``test_inf_does_not_leak_runtime_warning``
634
- below pins the strict-warning behaviour so the inf path is
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 ( audit, §F.2)
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,8 +1,7 @@
1
1
  """Regression tests for input-validation edge cases.
2
2
 
3
- Each test pins a specific defect the external auditor flagged, so any
4
- future regression surfaces with a clear name pointing back at the
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
@@ -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/
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes