Skip to content

Core types

The frozen dataclass surface every renderer consumes.

SofraTable dataclass

SofraTable(rows: tuple[Row, ...] = (), headers: tuple[HeaderRow, ...] = (), spanning_headers: tuple[SpanningHeader, ...] = (), caption: str | None = None, footnotes: tuple[str, ...] = (), theme_name: str = 'default', metadata: dict[str, Any] = dict(), inline_svg: str | None = None, inline_svg_position: str = 'above', inline_plot: Any = None, _spec: TableSpec | None = None, _rebuild: RebuildFn | None = None)

A backend-agnostic publication-ready table.

Attributes:

Name Type Description
rows tuple[Row, ...]

The table body, one :class:~pysofra.core.schema.Row per visible line.

headers tuple[HeaderRow, ...]

Column header rows, top-to-bottom. Most tables have a single header row; multi-level headers are supported via additional rows.

spanning_headers tuple[SpanningHeader, ...]

Optional spanning headers above the column headers.

caption str | None

Optional table caption (rendered as a title above the table).

footnotes tuple[str, ...]

Tuple of footnote strings rendered below the table.

theme_name str

Theme key registered in :mod:pysofra.themes. Defaults to "default".

metadata dict[str, Any]

Free-form metadata dict carried for downstream consumers and tests (e.g. raw p-values, SMDs, model objects).

theme

theme(name: str) -> SofraTable

Apply a theme by name. See :mod:pysofra.themes for available themes.

set_caption

set_caption(text: str | None) -> SofraTable

Set the table caption, replacing any existing one.

The caption renders above the table in every backend (<caption> in HTML, \caption{} in LaTeX, a bold paragraph above the table in DOCX/PPTX, sheet header in XLSX). Pass None to clear an existing caption.

Parameters:

Name Type Description Default
text str | None

The caption string, or None to clear.

required

Returns:

Type Description
SofraTable

A new SofraTable with the caption set. The original is unchanged.

add_footnote

add_footnote(text: str) -> SofraTable

Append a single footnote to the existing footnote list.

Footnotes render below the table in every backend, in the order they were appended. Builders such as :func:tbl_one emit footnotes automatically (e.g. "Tests:", "n (%) for categorical variables.") describing test choices and formatting; user-supplied footnotes appended via this method appear after the auto-generated ones.

Parameters:

Name Type Description Default
text str

The footnote text. Plain text; renderers escape special characters appropriately for their backend.

required

Returns:

Type Description
SofraTable

A new SofraTable with the footnote appended. To replace the footnote list wholesale, use :meth:with_footnotes instead.

with_footnotes

with_footnotes(footnotes: list[str] | tuple[str, ...]) -> SofraTable

Replace the footnote list entirely.

add_p

add_p(**overrides: Any) -> SofraTable

Add a p-value column.

Behaviour depends on the builder that produced the table. For tbl_one / tbl_summary this triggers automatic test selection per row (see :mod:pysofra.summary.tests); for tbl_regression p-values are already present and this is a no-op.

add_smd

add_smd() -> SofraTable

Add a standardized-mean-difference column (Table 1 only).

add_q

add_q(method: str = 'fdr_bh') -> SofraTable

Add a multiplicity-adjusted q-value column.

method is passed through to statsmodels.stats.multitest.multipletests; common choices are fdr_bh (Benjamini–Hochberg, default), fdr_by, bonferroni, holm, hommel, sidak. Implicitly enables p-values when not already on.

References

Benjamini, Y., & Hochberg, Y. (1995). Controlling the false discovery rate: a practical and powerful approach to multiple testing. J. R. Stat. Soc. B, 57(1), 289–300. (fdr_bh) Benjamini, Y., & Yekutieli, D. (2001). The control of the false discovery rate in multiple testing under dependency. Ann. Stat., 29(4), 1165–1188. (fdr_by) Holm, S. (1979). A simple sequentially rejective multiple test procedure. Scand. J. Stat., 6(2), 65–70. (holm) Hommel, G. (1988). A stagewise rejective multiple test procedure based on a modified Bonferroni test. Biometrika, 75(2), 383–386. (hommel) Šidák, Z. (1967). Rectangular confidence regions for the means of multivariate normal distributions. J. Am. Stat. Assoc., 62(318), 626–633. (sidak)

add_significance_stars

add_significance_stars(*, thresholds: tuple[tuple[float, str], ...] = ((0.001, '***'), (0.01, '**'), (0.05, '*'))) -> SofraTable

Append a stars column with *** / ** / * significance markers.

thresholds is a tuple of (cutoff, marker) pairs sorted smallest-cutoff first; each p-value is marked with the first marker whose cutoff it falls below.

add_n

add_n() -> SofraTable

Append a per-row N column with the non-missing sample size.

add_stat_label

add_stat_label() -> SofraTable

Append a Statistic column describing each row's summary form.

color_scale_if

color_scale_if(*, column: int, palette: tuple[str, str, str] = ('#fff5f0', '#fcae91', '#cb181d'), skip_blank: bool = True) -> SofraTable

Heatmap-style cell colouring for one numeric column (HTML only).

add_global_p

add_global_p(*, adjust_for: list[str] | tuple[str, ...] | None = None) -> SofraTable

Add a joint Type-III p-value column.

Supported on both :func:tbl_regression and :func:tbl_one / :func:tbl_summary tables, via two paths:

  • tbl_regression — for each multi-level categorical predictor in the model, the joint Wald-F p-value is computed via model.f_test on the contrast matrix that zeroes out every level simultaneously. Single-level coefficients receive their existing p-value duplicated.
  • tbl_one / tbl_summary — for each variable in the table, a logistic regression is fit on the source data: Logit(by == reference_level ~ variable [+ adjust_for]). The joint Wald p-value across the variable's coefficients is the new "global p" cell. Adjustment covariates passed via adjust_for= apply to every variable's fit, giving covariate-adjusted joint p-values.

Parameters:

Name Type Description Default
adjust_for list[str] | tuple[str, ...] | None

(tbl_one / tbl_summary only) Optional list of covariate column names to include in each per-variable regression. Continuous numeric covariates enter as-is; non-numeric covariates are dummy-coded. Ignored on :func:tbl_regression tables.

None

Raises:

Type Description
NotImplementedError

On composition primitives (tbl_merge / tbl_stack) and directly-constructed tables that carry neither a fitted model nor a re-runnable builder spec.

add_difference

add_difference(*, digits: int = 2, conf_level: float = 0.95) -> SofraTable

Add a between-group difference column (continuous + dichotomous).

Requires a 2-group Table 1. Continuous rows get the Welch mean-difference + CI; dichotomous rows get the proportion difference with Wilson-score-based CI; multi-level categorical rows show .

add_ci

add_ci(*, conf_level: float = 0.95) -> SofraTable

Append a confidence interval to each summary cell.

Continuous cells gain [lo, hi] for the mean; dichotomous cells gain [lo%, hi%] for the proportion (Wilson score).

with_pvalue_fmt

with_pvalue_fmt(fn: Callable[[float], str]) -> SofraTable

Re-format every p-value cell with the supplied callable.

with_estimate_fmt

with_estimate_fmt(fn: Callable[[float], str]) -> SofraTable

Re-format every numeric estimate cell with the supplied callable.

autofit

autofit(*, enable: bool = True) -> SofraTable

Hint every renderer to size columns to content.

Stored as metadata['autofit']. HTML uses content-based sizing natively; XLSX auto-sizes column widths to the widest cell; the DOCX renderer sets table.autofit = True when this flag is on.

compose

compose(row: int | str, column: int | str, parts: Any) -> SofraTable

Replace a cell's content with multiple typographically distinct parts.

parts is an iterable of :class:~pysofra.core.schema.CellPart — each carries its own bold / italic / superscript / subscript / color / link flags. Renderers concatenate the parts inside the same cell, honouring whichever flags the backend supports; the fallback text is set to the concatenated plain text so non-rich backends still print something readable.

modify_spanning_header

modify_spanning_header(label: str, *, start: int, end: int) -> SofraTable

Add (or replace at the same range) a spanning header above columns.

Columns are 0-indexed and the range is inclusive on both ends. Overlapping a previous span removes it.

inline_text

inline_text(*, row: int | str, column: int | str) -> str

Pull the text of a single cell for inline use.

row and column accept either a 0-indexed integer or a string matched against the first cell of each row / each header cell text. Raises KeyError if no match is found.

to_image

to_image(path: str | Path, *, scale: float = 2.0, dpi: int = 300) -> Path

Render the table to a PNG image.

Uses matplotlib under the hood; the result is a faithful raster of the HTML output. Useful for quick previews, Slack attachments, and document figures where a static image is preferable.

scale multiplies the pixel density (>= 1 recommended); dpi controls the output resolution (defaults to 300, the usual print-quality target).

add_overall

add_overall(label: str = 'Overall') -> SofraTable

Add an overall (unstratified) column.

bold_p

bold_p(threshold: float = 0.05) -> SofraTable

Bold rows whose p-value cell carries a value below threshold.

This is a presentational modifier — it works on any SofraTable whose body rows contain a cell of kind p_value with a numeric value.

bold_if

bold_if(predicate: Callable[[Row], bool]) -> SofraTable

Bold every cell of rows satisfying predicate(row) -> bool.

Example::

table.bold_if(lambda r: r.cells[0].text.startswith('age'))

highlight_if

highlight_if(predicate: Callable[[Row], bool], *, color: str = '#fff3cd') -> SofraTable

Highlight rows (background colour) satisfying predicate.

Adds an html_style metadata entry consumed by the HTML renderer; ignored by Markdown / LaTeX. color accepts any CSS colour string.

style_if

style_if(predicate: Callable[[Row], bool], *, bold: bool = False, italic: bool = False, color: str | None = None) -> SofraTable

General-purpose conditional row styling.

Combines :meth:bold_if, italic toggling, and an optional row background highlight in one call.

to_html

to_html(*, sticky_header: bool = False, max_height: str | None = None) -> str

Render the table as a standalone HTML fragment.

sticky_header=True keeps the column headers in view as the body scrolls — pair with max_height (a CSS length like "60vh" or "400px") to enable the vertical scroll container.

to_markdown

to_markdown() -> str

Render the table as GitHub-flavored Markdown.

to_docx

to_docx(path: str | Path) -> Path

Write the table to a .docx file. Returns the resolved path.

to_latex

to_latex(*, booktabs: bool = True, float_position: str = 'ht', centering: bool = True) -> str

Render the table as a LaTeX table float (booktabs by default).

Requires \usepackage{booktabs} in the consumer document preamble. Returns the LaTeX source as a string; write it to a .tex file with :func:pathlib.Path.write_text if needed.

Inline plots are embedded only when using :meth:to_latex_file (which writes a sidecar PDF). For a plain LaTeX string call this method and ignore any attached plot.

to_latex_file

to_latex_file(path: str | Path, *, booktabs: bool = True, float_position: str = 'ht', centering: bool = True) -> Path

Write a .tex file plus a sidecar PDF for any inline plot.

If the table carries an :class:~pysofra.plot.InlinePlot, the plot is written as <stem>_plot.pdf next to the .tex file and embedded with \includegraphics. Requires graphicx in the consuming document preamble.

to_pptx

to_pptx(path: str | Path, *, slide_title: str | None = None) -> Path

Write the table to a single-slide .pptx file.

Requires the optional python-pptx dependency (pip install pysofra[pptx]). If slide_title is omitted, the table's caption is used.

to_xlsx

to_xlsx(path: str | Path, *, sheet_name: str = 'Table') -> Path

Write the table to an .xlsx file via xlsxwriter.

to_quarto

to_quarto(*, format: str = 'html', label: str | None = None, caption: str | None = None) -> str

Render the table as a Quarto-fenced block.

Quarto (https://quarto.org) is the dominant reproducible- research authoring framework across Python and R. A Quarto block emitted here can be include-d directly in a .qmd document and Quarto will render it natively for the target output format.

Parameters:

Name Type Description Default
format str

One of "html" or "latex". html emits a :::{=html} fence containing the HTML render — used when the parent Quarto document targets HTML / EPUB. latex emits a :::{=latex} fence containing the LaTeX render — used when targeting PDF.

'html'
label str | None

Optional Quarto cross-reference label of the form "tbl-XXX". When given, the block is wrapped in {#tbl-XXX} so @tbl-XXX cross-references elsewhere in the document resolve.

None
caption str | None

Optional caption text. When label is also set, this becomes the table's official Quarto caption.

None

Returns:

Type Description
str

A Quarto-source string ready to paste into a .qmd file or pass to quarto render.

to_typst

to_typst() -> str

Render the table as Typst markup.

Typst (https://typst.app/) is a modern document-preparation system positioned as a faster, simpler alternative to LaTeX. The returned string is a #table(...) block ready to paste into a .typ document.

to_typst_file

to_typst_file(path: str | Path) -> Path

Write the table to a .typ file (Typst source).

snapshot_hash

snapshot_hash() -> str

SHA-256 of the table's logical content (Markdown + footnotes).

See :mod:pysofra.core.snapshot for the precise content policy.

lock_snapshot

lock_snapshot(path: str | Path) -> dict[str, Any]

Write a snapshot lock file pinning this table's content.

Use assert_snapshot later (in CI, in unit tests, in the analysis script's smoke check) to verify the table hasn't drifted from the pinned version.

assert_snapshot

assert_snapshot(path: str | Path) -> None

Raise AssertionError if the table differs from path.

The error message includes a unified diff between the pinned content and the current content, so the author can see exactly which row / footnote / spanning header changed.

check_safety

check_safety() -> list

Scan the table for retraction-prone patterns.

Returns a list of :class:pysofra.core.safety.SafetyWarning objects describing any flagged rows (extreme proportions, suspect SDs, sparse- cell p-values, extreme effect sizes, dominant missingness). An empty list means no flags.

with_safety_warnings

with_safety_warnings() -> SofraTable

Append a "Safety warnings" footnote summarising flagged rows.

Equivalent to::

for w in t.check_safety():
    t = t.with_footnote(f"SAFETY: {w}")

Use this in published-analysis scripts so the rendered table carries the diagnostic flags into the paper alongside the numbers.

with_inline_svg

with_inline_svg(svg: str, *, position: str = 'above') -> SofraTable

Attach a raw inline-SVG string to this table.

The HTML renderer embeds the SVG above (default) or below the table. Markdown ignores the SVG (no in-line image syntax for SVG strings). For a plot that needs to travel through DOCX / LaTeX / PPTX as well, use :meth:with_forest_plot or :meth:with_km_plot instead — those serialise a matplotlib figure into SVG + PNG + PDF and each renderer picks the format it supports.

with_forest_plot

with_forest_plot(*, log_x: bool | None = None, null_line: float | None = None, position: str = 'above', **plot_kwargs: Any) -> SofraTable

Attach a forest plot rendered from this regression table's coefficients.

Only valid for tables produced by :func:tbl_regression. Reads the point estimate + CI cells directly from the table body so the plot is guaranteed to match the displayed numbers. The attached plot carries SVG / PNG / PDF serialisations so it embeds in HTML, DOCX, PPTX, and LaTeX output consistently.

log_x and null_line default to None and are auto-detected from the table's coefficient column header: exponentiated families (OR / HR / TR / IRR / RR) get log_x=True, null_line=1.0; raw-coefficient families (β / Estimate) get log_x=False, null_line=0.0. Pass an explicit value to override.

with_km_plot

with_km_plot(*, position: str = 'above', **plot_kwargs: Any) -> SofraTable

Attach a Kaplan–Meier curve to a :func:tbl_survival result.

Refits the KM curves from the original data using lifelines and embeds SVG + PNG + PDF serialisations so the same plot renders in HTML, DOCX, PPTX, and LaTeX exports.

to_dict

to_dict() -> dict[str, Any]

Dump the table as a plain dict (useful for snapshot tests).

CellPart dataclass

CellPart(text: str, bold: bool = False, italic: bool = False, superscript: bool = False, subscript: bool = False, code: bool = False, color: str | None = None, link: str | None = None)

A typographically distinct run inside a single cell.

Used by SofraTable.compose() to embed multi-format content (bold + italic + colour) inside one cell. Renderers honour CellPart.bold, italic, superscript, subscript, code, color, and link where the backend supports them; unsupported flags degrade to plain text.