Skip to content

Commit 79ae72a

Browse files
committed
Add polished docs site: hero landing page, red palette, SEO
Home page: full-width hero section (deep red gradient, PySofra title, tagline, CTA buttons, table screenshot) via MkDocs Material template override. Navigation tabs at top for website feel. Inter + JetBrains Mono fonts. Feature cards grid and validation precision table on landing page. README: SEO-optimised intro and opener; surfaces gtsummary/tableone keywords that researchers search for. Repo description updated to keyword-rich form via GitHub API. mkdocs.yml site_description updated for HTML meta tags.
1 parent 355b844 commit 79ae72a

8 files changed

Lines changed: 293 additions & 58 deletions

File tree

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414

1515
</div>
1616

17-
PySofra is a statistical reporting framework for Python. It produces the
18-
table types standard in clinical and epidemiological manuscripts —
19-
baseline-characteristics tables (Table 1), regression summaries,
20-
Kaplan–Meier tables — from a single immutable object across seven output
21-
formats: HTML, Markdown, LaTeX, DOCX, PPTX, XLSX, and PNG.
22-
23-
The R ecosystem has `gtsummary`, `tableone`, and `flextable`. Python did
24-
not — until now.
17+
PySofra is the Python equivalent of R's `gtsummary` and `tableone`.
18+
It produces the table types standard in clinical and epidemiological
19+
manuscripts — baseline-characteristics tables (Table 1), regression
20+
summaries, Kaplan–Meier survival tables — from a single immutable object
21+
across seven output formats: HTML, Markdown, LaTeX, DOCX, PPTX, XLSX,
22+
and PNG.
23+
24+
Python researchers have long reached for `gtsummary`, `tableone`, and
25+
`flextable` in R for manuscript tables. PySofra brings that workflow
26+
natively to Python, with survey-weighted designs and multiple-imputation
27+
pooling built in.
2528

2629
<div align="center">
2730
<img src="https://raw.githubusercontent.com/jturner-uofl/pysofra/main/assets/readme/table_one.png"
241 KB
Loading

docs/assets/readme/survival_km.png

301 KB
Loading

docs/assets/readme/table_one.png

326 KB
Loading

docs/index.md

Lines changed: 79 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,92 @@
1-
# PySofra
1+
---
2+
template: home.html
3+
title: PySofra — Publication-ready statistical tables for Python
4+
hide:
5+
- navigation
6+
- toc
7+
---
28

3-
**The missing statistical reporting layer for Python.**
9+
## Validated at machine precision
410

5-
PySofra transforms datasets, statistical model outputs, and summary
6-
statistics into publication-ready tables across HTML, Markdown, DOCX,
7-
LaTeX, and PPTX, with minimal friction. It brings the practical
8-
workflows of R's `tableone`, `gtsummary`, and `flextable` into a
9-
single coherent Pythonic API.
11+
Most reporting packages test that code runs. PySofra tests that numbers are correct.
12+
13+
A 54-step audit notebook runs against the 2017–18 US National Health and Nutrition Examination Survey and asserts **52 numerical contracts** against independent R reference implementations — on every push to `main`.
14+
15+
| Quantity | Reference | Observed error |
16+
|----------|-----------|----------------|
17+
| Weighted mean — Table 1 cell | `gtsummary` display value | 3.3 × 10⁻¹⁵ rel |
18+
| Weighted SD — Table 1 cell | `gtsummary` display value | 3.8 × 10⁻¹⁵ rel |
19+
| Weighted proportion (4 variables) | `gtsummary` display value | 4.6 × 10⁻¹⁵ rel |
20+
| Survey regression SE (6 coefficients) | R `svyglm` | < 0.8% rel |
21+
| MI pooled point estimate | Rubin (1987) | < 10⁻¹⁴ abs |
22+
| KM survival probability | lifelines | < 10⁻¹⁵ abs |
23+
24+
Weighted means and SDs agree with R at **floating-point machine precision** — not approximation. Nominal 95% CIs attain **94.2% and 93.8% empirical coverage** in a 1,000-replicate Monte Carlo study.
25+
26+
The [pre-executed audit notebook](https://jturner-uofl.github.io/pysofra/notebooks/jss_case_study.html) is readable without installing anything. See [AUDITOR.md](https://github.com/jturner-uofl/pysofra/blob/main/AUDITOR.md) for the single-command reproduction recipe.
27+
28+
---
29+
30+
## Features
31+
32+
<div class="grid cards" markdown>
33+
34+
- **Six table builders**
35+
36+
`tbl_one`, `tbl_summary`, `tbl_cross`, `tbl_regression`,
37+
`tbl_uvregression`, `tbl_survival` — covering the full
38+
clinical-reporting stack in one coherent API.
39+
40+
- **Seven output formats**
41+
42+
HTML · Markdown · LaTeX · DOCX · PPTX · XLSX · PNG from a single
43+
immutable `SofraTable` object. Output is byte-identical across
44+
processes.
45+
46+
- **Survey weights**
47+
48+
`SurveyDesign(weights=, strata=, cluster=)` accepted by every
49+
builder. Taylor-linearised sandwich SEs, Rao–Scott adjusted
50+
test statistics.
51+
52+
- **Multiple-imputation pooling**
53+
54+
`ps.pool(fits)` applies Rubin's rules and returns an object
55+
accepted by `tbl_regression` — no manual bookkeeping.
56+
57+
- **Safety diagnostics**
58+
59+
`with_safety_warnings()` embeds separation, PH-violation, and
60+
sparse-cell footnotes directly into DOCX, HTML, and LaTeX output.
61+
62+
- **Auto-dispatched tests**
63+
64+
Welch, Wilcoxon, ANOVA, Kruskal–Wallis, Fisher, χ², Rao–Scott —
65+
selected per row by variable type, overridable per variable.
66+
67+
</div>
68+
69+
---
70+
71+
## Quick example
1072

1173
```python
12-
import pandas as pd
1374
import pysofra as ps
1475

15-
df = pd.read_csv("trial.csv")
16-
17-
(
18-
ps.tbl_one(df, by="arm")
76+
tbl = (
77+
ps.tbl_one(df, by="arm",
78+
labels={"age": "Age (years)", "bmi": "BMI (kg/m²)"},
79+
nonnormal=["bmi"])
1980
.add_p()
2081
.add_smd()
2182
.add_overall()
2283
.theme("clinical")
23-
.to_docx("table1.docx")
2484
)
25-
```
26-
27-
## Why PySofra?
28-
29-
Python already has the statistical computation — pandas, scipy,
30-
statsmodels, lifelines, scikit-learn. What's been missing is the
31-
**reporting ergonomics layer** that turns a fitted model or a stratified
32-
summary into a manuscript-ready, journal-styled artifact. PySofra fills
33-
that gap.
34-
35-
## Highlights
3685

37-
- **One object, every format.** `SofraTable` renders in Jupyter via
38-
`_repr_html_`, exports to DOCX / LaTeX / PPTX / HTML / Markdown, and
39-
carries captions, footnotes, and themes through to each backend.
40-
- **Sensible statistical defaults.** Welch / Wilcoxon / ANOVA / Kruskal
41-
/ χ² / Fisher are auto-selected per variable, with per-variable
42-
overrides (`tests={'age': 'wilcoxon'}`) when you need them.
43-
- **Multi-model regression.** Pass a single fit or a list — statsmodels,
44-
lifelines, sklearn all work.
45-
- **Multiplicity adjustment.** `.add_q()` adds Benjamini–Hochberg /
46-
Bonferroni / Holm / Hommel / Šidák / BY q-values.
47-
- **No nonsense.** No metaprogramming, no telemetry, no network calls,
48-
deterministic by construction.
49-
50-
## Status
51-
52-
PySofra is in **alpha** (`0.1.0a1`). The MVP scope (Table 1, summary,
53-
regression, composition, HTML/MD/DOCX/LaTeX/PPTX export, themes) is
54-
stable; internal modules may change before `1.0`.
86+
tbl # renders in Jupyter / VS Code / Colab
87+
tbl.to_docx("table1.docx") # publication-quality Word document
88+
tbl.to_latex() # LaTeX fragment, ready for manuscript
89+
```
5590

56-
See [Quickstart](quickstart.md) to install and run your first table.
91+
[See the full quickstart](quickstart.md){ .md-button }
92+
[API reference](api/builders.md){ .md-button }

docs/overrides/home.html

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{% extends "main.html" %}
2+
3+
{% block tabs %}
4+
{{ super() }}
5+
6+
{# Un-sticky the header so it scrolls away with the hero on the home page #}
7+
<style>
8+
.md-header { position: initial }
9+
.md-main__inner { margin-top: 0 }
10+
.md-content__inner { padding-top: 1.5rem }
11+
.md-content__inner > h1:first-child { display: none }
12+
.md-main { background: var(--md-default-bg-color) }
13+
</style>
14+
15+
<section class="pysofra-hero">
16+
<div class="md-grid md-typeset">
17+
<div class="pysofra-hero__inner">
18+
19+
<div class="pysofra-hero__content">
20+
<h1>PySofra</h1>
21+
<p class="pysofra-hero__tagline">
22+
The missing statistical reporting layer for Python
23+
</p>
24+
<p class="pysofra-hero__claim">
25+
Publication-ready tables from a single immutable object.<br>
26+
Seven output formats. Validated at machine precision.
27+
</p>
28+
<a href="{{ 'quickstart/' | url }}" class="md-button md-button--primary">
29+
Get started
30+
</a>
31+
<a href="https://github.com/jturner-uofl/pysofra" class="md-button pysofra-hero__btn-outline">
32+
Source on GitHub
33+
</a>
34+
</div>
35+
36+
<div class="pysofra-hero__image">
37+
<img
38+
src="{{ 'assets/readme/table_one.png' | url }}"
39+
alt="Baseline-characteristics Table 1 produced by PySofra"
40+
draggable="false"
41+
>
42+
</div>
43+
44+
</div>
45+
</div>
46+
</section>
47+
48+
{% endblock %}

docs/stylesheets/extra.css

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/* ── Custom colour palette ─────────────────────────────────────────── */
2+
3+
:root {
4+
--md-primary-fg-color: #7f1d1d;
5+
--md-primary-fg-color--light: #991b1b;
6+
--md-primary-fg-color--dark: #5f1111;
7+
--md-accent-fg-color: #dc2626;
8+
--md-accent-fg-color--transparent: rgba(220, 38, 38, .1);
9+
}
10+
11+
[data-md-color-scheme="slate"] {
12+
--md-primary-fg-color: #c0392b;
13+
--md-primary-fg-color--light: #e74c3c;
14+
--md-primary-fg-color--dark: #a93226;
15+
}
16+
17+
/* ── Hero section ──────────────────────────────────────────────────── */
18+
19+
.pysofra-hero {
20+
background: linear-gradient(150deg, #7f1d1d 0%, #2d0000 100%);
21+
padding: 3.5rem 0 4.5rem;
22+
margin-top: -0.05rem; /* close any gap between tabs bar and hero */
23+
}
24+
25+
.pysofra-hero .md-grid {
26+
max-width: 1280px;
27+
}
28+
29+
.pysofra-hero__inner {
30+
display: flex;
31+
align-items: center;
32+
gap: 3rem;
33+
flex-wrap: wrap;
34+
}
35+
36+
/* Text column */
37+
.pysofra-hero__content {
38+
flex: 0 0 400px;
39+
color: #fff;
40+
}
41+
42+
.pysofra-hero__content h1 {
43+
color: #fff !important;
44+
font-size: 3.75rem;
45+
font-weight: 800;
46+
letter-spacing: -0.03em;
47+
line-height: 1;
48+
margin: 0 0 0.75rem;
49+
}
50+
51+
.pysofra-hero__tagline {
52+
font-size: 1.2rem;
53+
font-weight: 600;
54+
color: rgba(255, 255, 255, 0.95) !important;
55+
margin: 0 0 0.5rem !important;
56+
}
57+
58+
.pysofra-hero__claim {
59+
font-size: 0.95rem;
60+
color: rgba(255, 255, 255, 0.72) !important;
61+
line-height: 1.65;
62+
margin: 0 0 1.75rem !important;
63+
}
64+
65+
/* Buttons */
66+
.pysofra-hero__content .md-button {
67+
margin: 0 0.5rem 0.5rem 0;
68+
}
69+
70+
.pysofra-hero__content .md-button--primary {
71+
background-color: #fff !important;
72+
color: #7f1d1d !important;
73+
border-color: #fff !important;
74+
font-weight: 700;
75+
}
76+
77+
.pysofra-hero__content .md-button--primary:hover {
78+
background-color: rgba(255, 255, 255, 0.88) !important;
79+
}
80+
81+
.pysofra-hero__btn-outline {
82+
color: #fff !important;
83+
border-color: rgba(255, 255, 255, 0.45) !important;
84+
background: transparent !important;
85+
}
86+
87+
.pysofra-hero__btn-outline:hover {
88+
border-color: #fff !important;
89+
background: rgba(255, 255, 255, 0.1) !important;
90+
}
91+
92+
/* Screenshot column */
93+
.pysofra-hero__image {
94+
flex: 1;
95+
min-width: 260px;
96+
}
97+
98+
.pysofra-hero__image img {
99+
width: 100%;
100+
max-width: 700px;
101+
border-radius: 10px;
102+
box-shadow: 0 24px 72px rgba(0, 0, 0, 0.5);
103+
display: block;
104+
}
105+
106+
/* ── Feature cards ─────────────────────────────────────────────────── */
107+
108+
.md-typeset .grid.cards > ul > li,
109+
.md-typeset .grid.cards > ol > li {
110+
border-top: 3px solid var(--md-primary-fg-color);
111+
}
112+
113+
.md-typeset .grid.cards > ul > li:hover,
114+
.md-typeset .grid.cards > ol > li:hover {
115+
border-top-color: var(--md-accent-fg-color);
116+
}
117+
118+
/* ── Responsive ─────────────────────────────────────────────────────── */
119+
120+
@media screen and (max-width: 76.1875em) {
121+
.pysofra-hero__inner {
122+
flex-direction: column;
123+
text-align: center;
124+
}
125+
126+
.pysofra-hero__content {
127+
flex: 0 0 auto;
128+
}
129+
130+
.pysofra-hero__content h1 {
131+
font-size: 2.75rem;
132+
}
133+
134+
.pysofra-hero__image {
135+
width: 100%;
136+
max-width: 560px;
137+
margin: 0 auto;
138+
}
139+
}

0 commit comments

Comments
 (0)