coeftable 0.2.1__tar.gz → 0.3.1__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.
- {coeftable-0.2.1 → coeftable-0.3.1}/.github/workflows/publish.yml +18 -11
- {coeftable-0.2.1 → coeftable-0.3.1}/.github/workflows/release.yml +37 -5
- coeftable-0.3.1/CHANGELOG.md +101 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/PKG-INFO +83 -17
- {coeftable-0.2.1 → coeftable-0.3.1}/README.md +82 -16
- {coeftable-0.2.1 → coeftable-0.3.1}/pyproject.toml +6 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/_version.py +2 -2
- coeftable-0.3.1/src/coeftable/collapsible.py +166 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/frame.py +54 -4
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/grid.py +74 -48
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/render.py +13 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/series.py +105 -27
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/spec.py +416 -72
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/svg.py +311 -78
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/theme.py +83 -5
- coeftable-0.3.1/tests/test_collapsible.py +260 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_frame.py +140 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_public_api.py +21 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_render.py +55 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_series.py +117 -22
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_sparkline.py +667 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_svg.py +398 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_theme.py +42 -0
- coeftable-0.2.1/CHANGELOG.md +0 -42
- {coeftable-0.2.1 → coeftable-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/.github/release.yml +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/.github/workflows/ci.yml +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/.github/workflows/post-release.yml +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/.gitignore +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/.pre-commit-config.yaml +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/LICENSE +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/Makefile +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/docs/images/example.png +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/docs/images/trend-example.png +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/noxfile.py +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/__init__.py +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/src/coeftable/format.py +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_format.py +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_package.py +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/tests/test_spec.py +0 -0
- {coeftable-0.2.1 → coeftable-0.3.1}/uv.lock +0 -0
|
@@ -11,11 +11,6 @@ on:
|
|
|
11
11
|
description: "Git ref to build. Defaults to the triggering ref."
|
|
12
12
|
required: false
|
|
13
13
|
type: string
|
|
14
|
-
publish:
|
|
15
|
-
description: "Upload the built package to PyPI."
|
|
16
|
-
required: false
|
|
17
|
-
default: false
|
|
18
|
-
type: boolean
|
|
19
14
|
workflow_dispatch:
|
|
20
15
|
|
|
21
16
|
jobs:
|
|
@@ -40,21 +35,33 @@ jobs:
|
|
|
40
35
|
|
|
41
36
|
publish:
|
|
42
37
|
runs-on: ubuntu-latest
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
|
|
38
|
+
# PyPI Trusted Publishing does not support reusable workflows
|
|
39
|
+
# (https://github.com/pypa/gh-action-pypi-publish/issues/166): a
|
|
40
|
+
# `workflow_call` invocation of this job would present the *caller's*
|
|
41
|
+
# workflow identity, not this one, and fail to match the Trusted
|
|
42
|
+
# Publisher entry registered for this workflow. So this job never
|
|
43
|
+
# publishes when called reusably -- release.yml calls this workflow
|
|
44
|
+
# for `build-package` only and does its own top-level publish. This
|
|
45
|
+
# job runs only for a direct trigger of this workflow sitting on a
|
|
46
|
+
# tag: a tag push, or a manual run selecting a tag ref (how a
|
|
47
|
+
# stranded release gets republished). A run on a branch never does.
|
|
48
|
+
if: startsWith(github.ref, 'refs/tags')
|
|
49
49
|
environment:
|
|
50
50
|
name: publish
|
|
51
51
|
url: https://pypi.org/p/coeftable
|
|
52
52
|
needs: build-package
|
|
53
53
|
permissions:
|
|
54
|
+
contents: read
|
|
54
55
|
id-token: write
|
|
55
56
|
steps:
|
|
56
57
|
- uses: actions/download-artifact@v8
|
|
57
58
|
with:
|
|
58
59
|
name: Packages
|
|
59
60
|
path: dist
|
|
61
|
+
# Must be invoked straight from the job, never via a local composite
|
|
62
|
+
# action. This action builds a Docker trampoline whose image it names
|
|
63
|
+
# from `github.action_repository`/`github.action_ref`, and both are
|
|
64
|
+
# empty for an action nested inside another composite action
|
|
65
|
+
# (actions/runner#2473). It then falls back to this repository and its
|
|
66
|
+
# branch, and tries to pull an image that was never published.
|
|
60
67
|
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -90,10 +90,12 @@ jobs:
|
|
|
90
90
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
91
91
|
run: gh release create "${{ steps.version.outputs.tag }}" --generate-notes
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
# Called
|
|
95
|
-
#
|
|
96
|
-
#
|
|
93
|
+
build:
|
|
94
|
+
# Called reusably for the build step only: `publish.yml`'s own
|
|
95
|
+
# `publish` job is gated on `startsWith(github.ref, 'refs/tags')`,
|
|
96
|
+
# which is false here (`github.ref` reflects this run's own
|
|
97
|
+
# branch/dispatch ref, not the `ref:` input below), so it no-ops --
|
|
98
|
+
# only `build-package` executes.
|
|
97
99
|
needs: tag
|
|
98
100
|
uses: ./.github/workflows/publish.yml
|
|
99
101
|
permissions:
|
|
@@ -102,7 +104,37 @@ jobs:
|
|
|
102
104
|
id-token: write
|
|
103
105
|
with:
|
|
104
106
|
ref: ${{ needs.tag.outputs.tag }}
|
|
105
|
-
|
|
107
|
+
|
|
108
|
+
publish:
|
|
109
|
+
# A real top-level job, not delegated via `uses:`: PyPI Trusted
|
|
110
|
+
# Publishing does not support reusable workflows -- the OIDC
|
|
111
|
+
# certificate's Build Config URI reflects the top-level caller
|
|
112
|
+
# (release.yml) for a `uses:`-invoked job, which then fails to match
|
|
113
|
+
# a Trusted Publisher entry registered for the *called* workflow.
|
|
114
|
+
# See https://github.com/pypa/gh-action-pypi-publish/issues/166 and
|
|
115
|
+
# the "Non-goals" section of that action's README: "keep the job
|
|
116
|
+
# calling pypi-publish in a top-level [workflow]". `build-package`
|
|
117
|
+
# itself has no such restriction and stays reusable above.
|
|
118
|
+
needs: build
|
|
119
|
+
runs-on: ubuntu-latest
|
|
120
|
+
environment:
|
|
121
|
+
name: publish
|
|
122
|
+
url: https://pypi.org/p/coeftable
|
|
123
|
+
permissions:
|
|
124
|
+
contents: read
|
|
125
|
+
id-token: write
|
|
126
|
+
steps:
|
|
127
|
+
- uses: actions/download-artifact@v8
|
|
128
|
+
with:
|
|
129
|
+
name: Packages
|
|
130
|
+
path: dist
|
|
131
|
+
# Must be invoked straight from the job, never via a local composite
|
|
132
|
+
# action. This action builds a Docker trampoline whose image it names
|
|
133
|
+
# from `github.action_repository`/`github.action_ref`, and both are
|
|
134
|
+
# empty for an action nested inside another composite action
|
|
135
|
+
# (actions/runner#2473). It then falls back to this repository and its
|
|
136
|
+
# branch, and tries to pull an image that was never published.
|
|
137
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
106
138
|
|
|
107
139
|
changelog:
|
|
108
140
|
# Same reason as the publish job above: the release is created with the
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<a id="v0.3.1"></a>
|
|
2
|
+
# [v0.3.1](https://github.com/kylejcaron/coeftable/releases/tag/v0.3.1) - 2026-08-14
|
|
3
|
+
|
|
4
|
+
<!-- Release notes generated using configuration in .github/release.yml at v0.3.1 -->
|
|
5
|
+
|
|
6
|
+
## What's Changed
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
* Fix row-group handling: labels spanning groups, row striping, and layout key collisions by [@kylejcaron](https://github.com/kylejcaron) in [#26](https://github.com/kylejcaron/coeftable/pull/26)
|
|
9
|
+
* Build core metadata 2.4 so releases pass validation by [@kylejcaron](https://github.com/kylejcaron) in [#27](https://github.com/kylejcaron/coeftable/pull/27)
|
|
10
|
+
### Maintenance
|
|
11
|
+
* Update changelog for v0.3.0 by [@github-actions](https://github.com/github-actions)[bot] in [#21](https://github.com/kylejcaron/coeftable/pull/21)
|
|
12
|
+
* Fix automated releases: PyPI Trusted Publishing rejects reusable workflows by [@kylejcaron](https://github.com/kylejcaron) in [#22](https://github.com/kylejcaron/coeftable/pull/22)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
**Full Changelog**: https://github.com/kylejcaron/coeftable/compare/v0.3.0...v0.3.1
|
|
16
|
+
|
|
17
|
+
[Changes][v0.3.1]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
<a id="v0.3.0"></a>
|
|
21
|
+
# [v0.3.0](https://github.com/kylejcaron/coeftable/releases/tag/v0.3.0) - 2026-08-05
|
|
22
|
+
|
|
23
|
+
<!-- Release notes generated using configuration in .github/release.yml at v0.3.0 -->
|
|
24
|
+
|
|
25
|
+
## What's Changed
|
|
26
|
+
### Maintenance
|
|
27
|
+
* Update changelog for v0.2.1 by [@github-actions](https://github.com/github-actions)[bot] in [#13](https://github.com/kylejcaron/coeftable/pull/13)
|
|
28
|
+
* Document the PyPI trusted-publisher requirement for both entry points by [@kylejcaron](https://github.com/kylejcaron) in [#14](https://github.com/kylejcaron/coeftable/pull/14)
|
|
29
|
+
* Add collapsible group sections by [@kylejcaron](https://github.com/kylejcaron) in [#18](https://github.com/kylejcaron/coeftable/pull/18)
|
|
30
|
+
* Allow ref=None on Sparkline to drop the reference line and its domain constraint by [@kylejcaron](https://github.com/kylejcaron) in [#19](https://github.com/kylejcaron/coeftable/pull/19)
|
|
31
|
+
* Support multiple overlaid series in one sparkline cell by [@kylejcaron](https://github.com/kylejcaron) in [#20](https://github.com/kylejcaron/coeftable/pull/20)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
**Full Changelog**: https://github.com/kylejcaron/coeftable/compare/v0.2.1...v0.3.0
|
|
35
|
+
|
|
36
|
+
[Changes][v0.3.0]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
<a id="v0.2.1"></a>
|
|
40
|
+
# [v0.2.1](https://github.com/kylejcaron/coeftable/releases/tag/v0.2.1) - 2026-08-05
|
|
41
|
+
|
|
42
|
+
<!-- Release notes generated using configuration in .github/release.yml at v0.2.1 -->
|
|
43
|
+
|
|
44
|
+
## What's Changed
|
|
45
|
+
### Maintenance
|
|
46
|
+
* Update changelog for v0.2.0 by [@github-actions](https://github.com/github-actions)[bot] in [#11](https://github.com/kylejcaron/coeftable/pull/11)
|
|
47
|
+
* Publish releases to PyPI automatically by [@kylejcaron](https://github.com/kylejcaron) in [#12](https://github.com/kylejcaron/coeftable/pull/12)
|
|
48
|
+
|
|
49
|
+
## New Contributors
|
|
50
|
+
* [@github-actions](https://github.com/github-actions)[bot] made their first contribution in [#11](https://github.com/kylejcaron/coeftable/pull/11)
|
|
51
|
+
|
|
52
|
+
**Full Changelog**: https://github.com/kylejcaron/coeftable/compare/v0.2.0...v0.2.1
|
|
53
|
+
|
|
54
|
+
[Changes][v0.2.1]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
<a id="v0.2.0"></a>
|
|
58
|
+
# [v0.2.0](https://github.com/kylejcaron/coeftable/releases/tag/v0.2.0) - 2026-08-04
|
|
59
|
+
|
|
60
|
+
<!-- Release notes generated using configuration in .github/release.yml at v0.2.0 -->
|
|
61
|
+
|
|
62
|
+
## What's Changed
|
|
63
|
+
### Maintenance
|
|
64
|
+
* ci: add workflow_dispatch release workflow with semver bump by [@kylejcaron](https://github.com/kylejcaron) in [#7](https://github.com/kylejcaron/coeftable/pull/7)
|
|
65
|
+
* Sparkline column: inline line plots with uncertainty by [@kylejcaron](https://github.com/kylejcaron) in [#10](https://github.com/kylejcaron/coeftable/pull/10)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
**Full Changelog**: https://github.com/kylejcaron/coeftable/compare/v0.1.0...v0.2.0
|
|
69
|
+
|
|
70
|
+
[Changes][v0.2.0]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
<a id="v0.1.0"></a>
|
|
74
|
+
# [v0.1.0](https://github.com/kylejcaron/coeftable/releases/tag/v0.1.0) - 2026-07-28
|
|
75
|
+
|
|
76
|
+
<!-- Release notes generated using configuration in .github/release.yml at main -->
|
|
77
|
+
|
|
78
|
+
## What's Changed
|
|
79
|
+
### Maintenance
|
|
80
|
+
* chore: install pre-commit hooks during setup by [@kylejcaron](https://github.com/kylejcaron) in [#1](https://github.com/kylejcaron/coeftable/pull/1)
|
|
81
|
+
* ci: drop redundant lint and typecheck jobs by [@kylejcaron](https://github.com/kylejcaron) in [#2](https://github.com/kylejcaron/coeftable/pull/2)
|
|
82
|
+
* Theming by [@kylejcaron](https://github.com/kylejcaron) in [#3](https://github.com/kylejcaron/coeftable/pull/3)
|
|
83
|
+
* docs patch: restore Parameters section header on Forest docstring by [@kylejcaron](https://github.com/kylejcaron) in [#4](https://github.com/kylejcaron/coeftable/pull/4)
|
|
84
|
+
* chore: complete package metadata for PyPI publishing by [@kylejcaron](https://github.com/kylejcaron) in [#5](https://github.com/kylejcaron/coeftable/pull/5)
|
|
85
|
+
* docs: add hero image, fix stale theming section in README by [@kylejcaron](https://github.com/kylejcaron) in [#6](https://github.com/kylejcaron/coeftable/pull/6)
|
|
86
|
+
|
|
87
|
+
## New Contributors
|
|
88
|
+
* [@kylejcaron](https://github.com/kylejcaron) made their first contribution in [#1](https://github.com/kylejcaron/coeftable/pull/1)
|
|
89
|
+
|
|
90
|
+
**Full Changelog**: https://github.com/kylejcaron/coeftable/commits/v0.1.0
|
|
91
|
+
|
|
92
|
+
[Changes][v0.1.0]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
[v0.3.1]: https://github.com/kylejcaron/coeftable/compare/v0.3.0...v0.3.1
|
|
96
|
+
[v0.3.0]: https://github.com/kylejcaron/coeftable/compare/v0.2.1...v0.3.0
|
|
97
|
+
[v0.2.1]: https://github.com/kylejcaron/coeftable/compare/v0.2.0...v0.2.1
|
|
98
|
+
[v0.2.0]: https://github.com/kylejcaron/coeftable/compare/v0.1.0...v0.2.0
|
|
99
|
+
[v0.1.0]: https://github.com/kylejcaron/coeftable/tree/v0.1.0
|
|
100
|
+
|
|
101
|
+
<!-- Generated by https://github.com/rhysd/changelog-from-release v3.9.1 -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coeftable
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Publication-quality summary tables for estimates with uncertainty.
|
|
5
5
|
Project-URL: Homepage, https://github.com/kylejcaron/coeftable
|
|
6
6
|
Project-URL: Repository, https://github.com/kylejcaron/coeftable
|
|
@@ -87,10 +87,11 @@ ct.CoefTable(df, rows="metric", estimate="est", ci=("lb", "ub"))
|
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
A `CoefTable` renders itself in marimo, Jupyter, and any other `_repr_html_`-aware viewer —
|
|
90
|
-
leave it as the last expression in a cell, no extra call needed. Outside a notebook,
|
|
91
|
-
`.gt()`
|
|
92
|
-
|
|
93
|
-
image, `table.gt().tab_options(...)` to keep styling with
|
|
90
|
+
leave it as the last expression in a cell, no extra call needed. Outside a notebook,
|
|
91
|
+
`table.as_raw_html()` is the HTML string entry point. `.gt()` remains the escape hatch to the
|
|
92
|
+
underlying [great_tables](https://posit-dev.github.io/great-tables/) object itself:
|
|
93
|
+
`table.gt().save("t.png")` for an image, `table.gt().tab_options(...)` to keep styling with
|
|
94
|
+
great_tables' own API.
|
|
94
95
|
|
|
95
96
|
## Data shape
|
|
96
97
|
|
|
@@ -103,7 +104,15 @@ a `parameter` column.
|
|
|
103
104
|
**Dimensions:**
|
|
104
105
|
- `rows` — the label for each row in the table (e.g. a metric name).
|
|
105
106
|
- `nest` — an optional secondary label stacked below each row.
|
|
106
|
-
- `groups` — an optional column whose values produce section headers.
|
|
107
|
+
- `groups` — an optional column whose values produce section headers. A row
|
|
108
|
+
label may appear under more than one group, in which case it renders once per
|
|
109
|
+
section — so the same set of metrics can be reported per region without
|
|
110
|
+
inventing a `nest` column to tell them apart. Pass
|
|
111
|
+
`collapsible_groups=True` to make those sections collapsible in the rendered
|
|
112
|
+
HTML — a pure CSS toggle (relies on `:has()`, Baseline since late 2023; on an
|
|
113
|
+
older browser without it the toggle no-ops and sections stay expanded), no
|
|
114
|
+
JavaScript, sections start expanded. Applies to `as_raw_html()`/`_repr_html_`;
|
|
115
|
+
`.gt()` is unaffected.
|
|
107
116
|
- `split_columns` — an optional column whose values produce repeated column
|
|
108
117
|
groups side by side, useful for comparing methods.
|
|
109
118
|
|
|
@@ -172,8 +181,35 @@ methods = pl.DataFrame(
|
|
|
172
181
|
(
|
|
173
182
|
ct.CoefTable(
|
|
174
183
|
methods, rows="metric", split_columns="method", estimate="est", ci=("lb", "ub")
|
|
184
|
+
).header("Cohort Revenue by Method")
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Repeating metrics across sections
|
|
189
|
+
|
|
190
|
+
A row label may appear under more than one `groups` value. The same metrics can
|
|
191
|
+
therefore be reported per region, each section repeating the full set:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
import polars as pl
|
|
195
|
+
import coeftable as ct
|
|
196
|
+
|
|
197
|
+
regions = pl.DataFrame(
|
|
198
|
+
{
|
|
199
|
+
"metric": ["Revenue", "Signups", "Revenue", "Signups"],
|
|
200
|
+
"region": ["US", "US", "EU", "EU"],
|
|
201
|
+
"est": [1.2, 0.4, 2.1, 0.9],
|
|
202
|
+
"lb": [0.8, 0.1, 1.5, 0.5],
|
|
203
|
+
"ub": [1.6, 0.7, 2.7, 1.3],
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
(
|
|
208
|
+
ct.CoefTable(
|
|
209
|
+
regions, rows="metric", groups="region", collapsible_groups=True
|
|
175
210
|
)
|
|
176
|
-
.
|
|
211
|
+
.estimate("Effect", "est", ci=("lb", "ub"))
|
|
212
|
+
.forest("Effect Plot", of="Effect", ref=0.0, symmetric=True)
|
|
177
213
|
)
|
|
178
214
|
```
|
|
179
215
|
|
|
@@ -184,11 +220,11 @@ Four built-in themes are available from `coeftable.theme`:
|
|
|
184
220
|
```python
|
|
185
221
|
from coeftable.theme import BLUE, COLORBLIND, DEFAULT, MONO, TEXTUAL
|
|
186
222
|
|
|
187
|
-
DEFAULT
|
|
188
|
-
TEXTUAL
|
|
189
|
-
BLUE
|
|
190
|
-
COLORBLIND
|
|
191
|
-
MONO
|
|
223
|
+
DEFAULT # Alias for TEXTUAL -- what CoefTable uses if you don't set a theme
|
|
224
|
+
TEXTUAL # Minimal, publication-style: muted colours, light chrome
|
|
225
|
+
BLUE # The original blue-grey palette
|
|
226
|
+
COLORBLIND # Colourblind-safe palette
|
|
227
|
+
MONO # Grayscale for mono journals
|
|
192
228
|
```
|
|
193
229
|
|
|
194
230
|
Apply one with `.with_theme(...)`:
|
|
@@ -209,9 +245,8 @@ Use `with_direction` to mark rows where lower values are favourable (reusing
|
|
|
209
245
|
the `df` frame from [Quick start](#quick-start)):
|
|
210
246
|
|
|
211
247
|
```python
|
|
212
|
-
table = (
|
|
213
|
-
|
|
214
|
-
.with_direction({"Latency": "lower_is_better"})
|
|
248
|
+
table = ct.CoefTable(df, rows="metric", estimate="est", ci=("lb", "ub")).with_direction(
|
|
249
|
+
{"Latency": "lower_is_better"}
|
|
215
250
|
)
|
|
216
251
|
```
|
|
217
252
|
|
|
@@ -232,8 +267,13 @@ companion-frame choice used elsewhere in coeftable:
|
|
|
232
267
|
arrives in: a SQL export, a dbt model, an experimentation platform's daily
|
|
233
268
|
metrics table. Pass `data=` a separate frame with one row per point, and
|
|
234
269
|
`value` / `ci` / `x` name *scalar* columns on it. coeftable groups the
|
|
235
|
-
companion frame by the table's `rows` (+ `nest`, + `
|
|
236
|
-
and collapses each group into a series internally
|
|
270
|
+
companion frame by the table's `rows` (+ `nest`, + `groups`, +
|
|
271
|
+
`split_columns`) keys and collapses each group into a series internally.
|
|
272
|
+
`groups` participates only when `data` actually carries that column, so a
|
|
273
|
+
companion frame keyed on the row alone stays valid. The one case that needs
|
|
274
|
+
it is a row label appearing under more than one group: without the group
|
|
275
|
+
column in `data` those rows are indistinguishable, so coeftable reports it
|
|
276
|
+
rather than serving every section the same merged series.
|
|
237
277
|
|
|
238
278
|
```python
|
|
239
279
|
import datetime as dt
|
|
@@ -373,3 +413,29 @@ trend = pl.DataFrame(
|
|
|
373
413
|
.sparkline("Override", value="lift", ref=1.0, ylim=(0.9, 1.1))
|
|
374
414
|
)
|
|
375
415
|
```
|
|
416
|
+
|
|
417
|
+
**No reference, or a hidden one.** `ref` also drives the dashed line and
|
|
418
|
+
colour resolution; two ways to opt out, for data with no meaningful
|
|
419
|
+
zero (revenue, durations, absolute counts):
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
absolute = pl.DataFrame(
|
|
423
|
+
{
|
|
424
|
+
"metric": ["Revenue", "Latency"],
|
|
425
|
+
"value": [[282.3, 300.1, 320.0], [900.0, 910.0, 920.0]],
|
|
426
|
+
}
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
(
|
|
430
|
+
ct.CoefTable(absolute, rows="metric")
|
|
431
|
+
# ref=None: no reference at all. No dashed line, no forced domain
|
|
432
|
+
# inclusion of 0, and every cell colours neutral -- "favorable" has
|
|
433
|
+
# no meaning without something to compare against.
|
|
434
|
+
.sparkline("No reference", value="value", ref=None)
|
|
435
|
+
# ref=0.0, show_ref=False: the reference is real and still drives
|
|
436
|
+
# colour (favorable/unfavorable against 0), it just isn't drawn or
|
|
437
|
+
# forced into the domain. Opt in deliberately: a mark can then claim
|
|
438
|
+
# "above the reference" while the reference sits off-canvas.
|
|
439
|
+
.sparkline("Hidden reference", value="value", ref=0.0, show_ref=False)
|
|
440
|
+
)
|
|
441
|
+
```
|
|
@@ -33,10 +33,11 @@ ct.CoefTable(df, rows="metric", estimate="est", ci=("lb", "ub"))
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
A `CoefTable` renders itself in marimo, Jupyter, and any other `_repr_html_`-aware viewer —
|
|
36
|
-
leave it as the last expression in a cell, no extra call needed. Outside a notebook,
|
|
37
|
-
`.gt()`
|
|
38
|
-
|
|
39
|
-
image, `table.gt().tab_options(...)` to keep styling with
|
|
36
|
+
leave it as the last expression in a cell, no extra call needed. Outside a notebook,
|
|
37
|
+
`table.as_raw_html()` is the HTML string entry point. `.gt()` remains the escape hatch to the
|
|
38
|
+
underlying [great_tables](https://posit-dev.github.io/great-tables/) object itself:
|
|
39
|
+
`table.gt().save("t.png")` for an image, `table.gt().tab_options(...)` to keep styling with
|
|
40
|
+
great_tables' own API.
|
|
40
41
|
|
|
41
42
|
## Data shape
|
|
42
43
|
|
|
@@ -49,7 +50,15 @@ a `parameter` column.
|
|
|
49
50
|
**Dimensions:**
|
|
50
51
|
- `rows` — the label for each row in the table (e.g. a metric name).
|
|
51
52
|
- `nest` — an optional secondary label stacked below each row.
|
|
52
|
-
- `groups` — an optional column whose values produce section headers.
|
|
53
|
+
- `groups` — an optional column whose values produce section headers. A row
|
|
54
|
+
label may appear under more than one group, in which case it renders once per
|
|
55
|
+
section — so the same set of metrics can be reported per region without
|
|
56
|
+
inventing a `nest` column to tell them apart. Pass
|
|
57
|
+
`collapsible_groups=True` to make those sections collapsible in the rendered
|
|
58
|
+
HTML — a pure CSS toggle (relies on `:has()`, Baseline since late 2023; on an
|
|
59
|
+
older browser without it the toggle no-ops and sections stay expanded), no
|
|
60
|
+
JavaScript, sections start expanded. Applies to `as_raw_html()`/`_repr_html_`;
|
|
61
|
+
`.gt()` is unaffected.
|
|
53
62
|
- `split_columns` — an optional column whose values produce repeated column
|
|
54
63
|
groups side by side, useful for comparing methods.
|
|
55
64
|
|
|
@@ -118,8 +127,35 @@ methods = pl.DataFrame(
|
|
|
118
127
|
(
|
|
119
128
|
ct.CoefTable(
|
|
120
129
|
methods, rows="metric", split_columns="method", estimate="est", ci=("lb", "ub")
|
|
130
|
+
).header("Cohort Revenue by Method")
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Repeating metrics across sections
|
|
135
|
+
|
|
136
|
+
A row label may appear under more than one `groups` value. The same metrics can
|
|
137
|
+
therefore be reported per region, each section repeating the full set:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import polars as pl
|
|
141
|
+
import coeftable as ct
|
|
142
|
+
|
|
143
|
+
regions = pl.DataFrame(
|
|
144
|
+
{
|
|
145
|
+
"metric": ["Revenue", "Signups", "Revenue", "Signups"],
|
|
146
|
+
"region": ["US", "US", "EU", "EU"],
|
|
147
|
+
"est": [1.2, 0.4, 2.1, 0.9],
|
|
148
|
+
"lb": [0.8, 0.1, 1.5, 0.5],
|
|
149
|
+
"ub": [1.6, 0.7, 2.7, 1.3],
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
(
|
|
154
|
+
ct.CoefTable(
|
|
155
|
+
regions, rows="metric", groups="region", collapsible_groups=True
|
|
121
156
|
)
|
|
122
|
-
.
|
|
157
|
+
.estimate("Effect", "est", ci=("lb", "ub"))
|
|
158
|
+
.forest("Effect Plot", of="Effect", ref=0.0, symmetric=True)
|
|
123
159
|
)
|
|
124
160
|
```
|
|
125
161
|
|
|
@@ -130,11 +166,11 @@ Four built-in themes are available from `coeftable.theme`:
|
|
|
130
166
|
```python
|
|
131
167
|
from coeftable.theme import BLUE, COLORBLIND, DEFAULT, MONO, TEXTUAL
|
|
132
168
|
|
|
133
|
-
DEFAULT
|
|
134
|
-
TEXTUAL
|
|
135
|
-
BLUE
|
|
136
|
-
COLORBLIND
|
|
137
|
-
MONO
|
|
169
|
+
DEFAULT # Alias for TEXTUAL -- what CoefTable uses if you don't set a theme
|
|
170
|
+
TEXTUAL # Minimal, publication-style: muted colours, light chrome
|
|
171
|
+
BLUE # The original blue-grey palette
|
|
172
|
+
COLORBLIND # Colourblind-safe palette
|
|
173
|
+
MONO # Grayscale for mono journals
|
|
138
174
|
```
|
|
139
175
|
|
|
140
176
|
Apply one with `.with_theme(...)`:
|
|
@@ -155,9 +191,8 @@ Use `with_direction` to mark rows where lower values are favourable (reusing
|
|
|
155
191
|
the `df` frame from [Quick start](#quick-start)):
|
|
156
192
|
|
|
157
193
|
```python
|
|
158
|
-
table = (
|
|
159
|
-
|
|
160
|
-
.with_direction({"Latency": "lower_is_better"})
|
|
194
|
+
table = ct.CoefTable(df, rows="metric", estimate="est", ci=("lb", "ub")).with_direction(
|
|
195
|
+
{"Latency": "lower_is_better"}
|
|
161
196
|
)
|
|
162
197
|
```
|
|
163
198
|
|
|
@@ -178,8 +213,13 @@ companion-frame choice used elsewhere in coeftable:
|
|
|
178
213
|
arrives in: a SQL export, a dbt model, an experimentation platform's daily
|
|
179
214
|
metrics table. Pass `data=` a separate frame with one row per point, and
|
|
180
215
|
`value` / `ci` / `x` name *scalar* columns on it. coeftable groups the
|
|
181
|
-
companion frame by the table's `rows` (+ `nest`, + `
|
|
182
|
-
and collapses each group into a series internally
|
|
216
|
+
companion frame by the table's `rows` (+ `nest`, + `groups`, +
|
|
217
|
+
`split_columns`) keys and collapses each group into a series internally.
|
|
218
|
+
`groups` participates only when `data` actually carries that column, so a
|
|
219
|
+
companion frame keyed on the row alone stays valid. The one case that needs
|
|
220
|
+
it is a row label appearing under more than one group: without the group
|
|
221
|
+
column in `data` those rows are indistinguishable, so coeftable reports it
|
|
222
|
+
rather than serving every section the same merged series.
|
|
183
223
|
|
|
184
224
|
```python
|
|
185
225
|
import datetime as dt
|
|
@@ -319,3 +359,29 @@ trend = pl.DataFrame(
|
|
|
319
359
|
.sparkline("Override", value="lift", ref=1.0, ylim=(0.9, 1.1))
|
|
320
360
|
)
|
|
321
361
|
```
|
|
362
|
+
|
|
363
|
+
**No reference, or a hidden one.** `ref` also drives the dashed line and
|
|
364
|
+
colour resolution; two ways to opt out, for data with no meaningful
|
|
365
|
+
zero (revenue, durations, absolute counts):
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
absolute = pl.DataFrame(
|
|
369
|
+
{
|
|
370
|
+
"metric": ["Revenue", "Latency"],
|
|
371
|
+
"value": [[282.3, 300.1, 320.0], [900.0, 910.0, 920.0]],
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
(
|
|
376
|
+
ct.CoefTable(absolute, rows="metric")
|
|
377
|
+
# ref=None: no reference at all. No dashed line, no forced domain
|
|
378
|
+
# inclusion of 0, and every cell colours neutral -- "favorable" has
|
|
379
|
+
# no meaning without something to compare against.
|
|
380
|
+
.sparkline("No reference", value="value", ref=None)
|
|
381
|
+
# ref=0.0, show_ref=False: the reference is real and still drives
|
|
382
|
+
# colour (favorable/unfavorable against 0), it just isn't drawn or
|
|
383
|
+
# forced into the domain. Opt in deliberately: a mark can then claim
|
|
384
|
+
# "above the reference" while the reference sits off-canvas.
|
|
385
|
+
.sparkline("Hidden reference", value="value", ref=0.0, show_ref=False)
|
|
386
|
+
)
|
|
387
|
+
```
|
|
@@ -44,8 +44,14 @@ Homepage = "https://github.com/kylejcaron/coeftable"
|
|
|
44
44
|
Repository = "https://github.com/kylejcaron/coeftable"
|
|
45
45
|
Issues = "https://github.com/kylejcaron/coeftable/issues"
|
|
46
46
|
|
|
47
|
+
# PyPI rejects core metadata 2.5, which hatchling 1.30 defaults to.
|
|
48
|
+
# Drop once https://github.com/pypi/warehouse/issues/19083 ships.
|
|
47
49
|
[tool.hatch.build.targets.wheel]
|
|
48
50
|
packages = ["src/coeftable"]
|
|
51
|
+
core-metadata-version = "2.4"
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.sdist]
|
|
54
|
+
core-metadata-version = "2.4"
|
|
49
55
|
|
|
50
56
|
[tool.hatch.version]
|
|
51
57
|
source = "vcs"
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.3.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|