Skip to content

omopy.vis

Visualization and formatting for OMOP CDM summarised results — format estimates, render tables, and create plots from SummarisedResult objects.

This module is the Python equivalent of the R visOmopResults package. Table rendering uses great_tables; plot rendering uses plotly.

Format Pipeline

Composable functions for transforming summarised results into display-ready data. The typical pipeline is: format_estimate_value → format_estimate_name → format_header → format_table.

format_estimate_value

format_estimate_value(
    result: SummarisedResult,
    *,
    decimals: dict[str, int] | None = None,
    decimal_mark: str = ".",
    big_mark: str = ",",
) -> SummarisedResult

Format numeric precision of estimate_value based on estimate_type.

For each row, rounds the value to the number of decimal places specified by the row's estimate_type and applies decimal/big marks.

Parameters:

Name Type Description Default
result SummarisedResult

A :class:SummarisedResult.

required
decimals dict[str, int] | None

Mapping of estimate_type -> number of decimals. Defaults to {integer: 0, numeric: 2, percentage: 1, proportion: 3}.

None
decimal_mark str

Character to use as decimal separator (default ".").

'.'
big_mark str

Thousands separator (default ","). Use "" for none.

','

Returns:

Type Description
SummarisedResult

A new :class:SummarisedResult with formatted estimate_value.

format_estimate_name

format_estimate_name(
    result: SummarisedResult,
    *,
    estimate_name: dict[str, str] | None = None,
    keep_not_formatted: bool = True,
    use_format_order: bool = True,
) -> SummarisedResult

Combine/rename estimate values using template patterns.

Each key in estimate_name is a display label; each value is a pattern containing <estimate_name> placeholders, e.g.::

{"N (%)": "<count> (<percentage>%)"}

Rows whose estimate_name appears in a pattern are merged; the resulting row gets the display label as its estimate_name and the interpolated string as estimate_value.

Parameters:

Name Type Description Default
result SummarisedResult

A :class:SummarisedResult.

required
estimate_name dict[str, str] | None

Mapping of display_label -> pattern. Patterns use <name> to reference estimate values by their estimate_name. If None, returns result unchanged.

None
keep_not_formatted bool

Whether to keep rows whose estimate_name was not matched by any pattern.

True
use_format_order bool

If True, output rows follow the order of estimate_name keys; otherwise, data order is preserved.

True

Returns:

Type Description
SummarisedResult

A new :class:SummarisedResult with combined estimates.

format_header

format_header(
    result: DataFrame | SummarisedResult,
    header: list[str],
    *,
    delim: str = HEADER_DELIM,
    include_header_name: bool = True,
    include_header_key: bool = True,
) -> pl.DataFrame

Pivot columns into multi-level column headers.

Takes a DataFrame (or SummarisedResult's data) and pivots specified columns so their unique values become part of the column names, enabling multi-level headers in formatted tables.

The column names in the output encode header metadata using delim as a separator. For example, pivoting cohort_name with values ["cohort_1", "cohort_2"] produces columns like::

"[header]cohort_name\n[header_level]cohort_1"

Parameters:

Name Type Description Default
result DataFrame | SummarisedResult

The data to pivot. If a :class:SummarisedResult, uses its :attr:data attribute. Should be a "tidy" DataFrame (i.e., after split_all() + pivot_estimates()).

required
header list[str]

Column names to pivot into headers. May also contain label strings (not actual column names) that will be inserted as header group labels.

required
delim str

Delimiter between header levels (default newline).

HEADER_DELIM
include_header_name bool

Include the column name in the header.

True
include_header_key bool

Include [header]/[header_level] keys.

True

Returns:

Type Description
DataFrame

A pivoted :class:~polars.DataFrame with encoded column names.

format_min_cell_count

format_min_cell_count(
    result: SummarisedResult,
) -> SummarisedResult

Replace suppressed count values with <N display strings.

Reads min_cell_count from the result's settings. Rows where estimate_value is "-" (the suppression sentinel) are replaced with "<N" where N is the minimum cell count.

Parameters:

Name Type Description Default
result SummarisedResult

A :class:SummarisedResult (typically after :meth:SummarisedResult.suppress).

required

Returns:

Type Description
SummarisedResult

A new :class:SummarisedResult with formatted suppression labels.

Tidy Helpers

tidy_result

tidy_result(result: SummarisedResult) -> pl.DataFrame

Convert a :class:SummarisedResult to a tidy DataFrame.

Equivalent to R's tidy(<summarised_result>): 1. Add settings columns 2. Split all name-level pair columns 3. Keep estimate columns for downstream pivoting

Parameters:

Name Type Description Default
result SummarisedResult

The summarised result to tidy.

required

Returns:

Type Description
DataFrame

A wide :class:~polars.DataFrame with individual columns for

DataFrame

group, strata, and additional variables.

tidy_columns

tidy_columns(result: SummarisedResult) -> list[str]

Return the column names available after tidying.

Parameters:

Name Type Description Default
result SummarisedResult

The summarised result.

required

Returns:

Type Description
list[str]

List of column names that :func:tidy_result would produce.

High-Level Tables

vis_omop_table

vis_omop_table(
    result: SummarisedResult,
    *,
    estimate_name: dict[str, str] | None = None,
    header: list[str] | None = None,
    settings_columns: list[str] | None = None,
    group_column: list[str] | None = None,
    rename: dict[str, str] | None = None,
    type: TableType | None = None,
    hide: list[str] | None = None,
    column_order: list[str] | None = None,
    style: TableStyle | None = None,
    show_min_cell_count: bool = True,
    decimals: dict[str, int] | None = None,
    decimal_mark: str = ".",
    big_mark: str = ",",
    title: str | None = None,
    subtitle: str | None = None,
) -> Any

Create a formatted table from a :class:SummarisedResult.

This is the main high-level entry point, equivalent to R's visOmopTable(). It executes the full format pipeline:

  1. Format estimate values (numeric precision)
  2. Show min cell count markers if suppressed
  3. Format estimate names (combine estimates)
  4. Split name-level pairs and add settings
  5. Apply header pivoting
  6. Render to table

Parameters:

Name Type Description Default
result SummarisedResult

A :class:SummarisedResult.

required
estimate_name dict[str, str] | None

Mapping of display_label -> pattern for combining estimates (e.g., {"N (%)": "<count> (<percentage>%)"})

None
header list[str] | None

Columns to pivot into multi-level column headers.

None
settings_columns list[str] | None

Settings columns to include in output.

None
group_column list[str] | None

Columns to use for row grouping.

None
rename dict[str, str] | None

Column rename mapping ({display_name: column_name}).

None
type TableType | None

Output type: "gt" for great_tables, "polars" for plain DataFrame. Defaults to "gt" if great_tables is available, "polars" otherwise.

None
hide list[str] | None

Columns to exclude from output.

None
column_order list[str] | None

Explicit column ordering.

None
style TableStyle | None

Table style configuration.

None
show_min_cell_count bool

Show <N for suppressed counts.

True
decimals dict[str, int] | None

Override decimal places per estimate type.

None
decimal_mark str

Decimal separator character.

'.'
big_mark str

Thousands separator character.

','
title str | None

Table title.

None
subtitle str | None

Table subtitle.

None

Returns:

Type Description
Any

A great_tables.GT object (if type="gt") or a

Any

class:~polars.DataFrame (if type="polars").

vis_table

vis_table(
    result: DataFrame,
    *,
    estimate_name: dict[str, str] | None = None,
    header: list[str] | None = None,
    group_column: list[str] | None = None,
    rename: dict[str, str] | None = None,
    type: TableType | None = None,
    hide: list[str] | None = None,
    style: TableStyle | None = None,
    title: str | None = None,
    subtitle: str | None = None,
) -> Any

Create a formatted table from any DataFrame.

A lower-level function than :func:vis_omop_table — operates on any :class:~polars.DataFrame, not just :class:SummarisedResult.

Parameters:

Name Type Description Default
result DataFrame

Any Polars DataFrame.

required
estimate_name dict[str, str] | None

Not applicable for plain DataFrames (ignored).

None
header list[str] | None

Columns to pivot into headers.

None
group_column list[str] | None

Columns to use for row grouping.

None
rename dict[str, str] | None

Column rename mapping ({display_name: column_name}).

None
type TableType | None

Output type.

None
hide list[str] | None

Columns to exclude.

None
style TableStyle | None

Table style configuration.

None
title str | None

Table title.

None
subtitle str | None

Table subtitle.

None

Returns:

Type Description
Any

A great_tables.GT object or :class:~polars.DataFrame.

format_table

format_table(
    x: DataFrame,
    *,
    type: TableType | None = None,
    style: TableStyle | None = None,
    na: str | None = None,
    title: str | None = None,
    subtitle: str | None = None,
    group_column: list[str] | None = None,
    group_as_column: bool = False,
    merge: str = "all_columns",
) -> Any

Render a prepared DataFrame to a table object.

This is the low-level rendering function. Typically called via :func:vis_omop_table or :func:vis_table, but can be used directly for maximum control.

Parameters:

Name Type Description Default
x DataFrame

A :class:~polars.DataFrame to render.

required
type TableType | None

Output type ("gt" or "polars").

None
style TableStyle | None

Table style configuration.

None
na str | None

String to display for missing values.

None
title str | None

Table title.

None
subtitle str | None

Table subtitle.

None
group_column list[str] | None

Columns for row grouping.

None
group_as_column bool

If True, show groups as a column; if False, show as spanning rows.

False
merge str

Column merge strategy ("all_columns" or "none").

'all_columns'

Returns:

Type Description
Any

A great_tables.GT object or :class:~polars.DataFrame.

Plots

scatter_plot

scatter_plot(
    result: SummarisedResult | DataFrame,
    *,
    x: str,
    y: str,
    line: bool = False,
    point: bool = True,
    ribbon: bool = False,
    y_min: str | None = None,
    y_max: str | None = None,
    facet: str | list[str] | None = None,
    colour: str | None = None,
    style: PlotStyle | None = None,
    group: str | None = None,
    title: str | None = None,
    x_title: str | None = None,
    y_title: str | None = None,
) -> Any

Create a scatter/line/ribbon plot from results.

Parameters:

Name Type Description Default
result SummarisedResult | DataFrame

A :class:SummarisedResult (auto-tidied) or a :class:~polars.DataFrame.

required
x str

Column name for x-axis.

required
y str

Column name or estimate name for y-axis.

required
line bool

Whether to connect points with lines.

False
point bool

Whether to show points.

True
ribbon bool

Whether to show a ribbon (requires y_min and y_max).

False
y_min str | None

Column/estimate for ribbon lower bound.

None
y_max str | None

Column/estimate for ribbon upper bound.

None
facet str | list[str] | None

Column(s) for faceting. A single string creates a single-row facet; a list of two creates a grid (row, col).

None
colour str | None

Column for colour aesthetic.

None
style PlotStyle | None

Plot style configuration.

None
group str | None

Column for grouping (defaults to colour).

None
title str | None

Plot title.

None
x_title str | None

X-axis title. If None, derived from x.

None
y_title str | None

Y-axis title. If None, derived from y.

None

Returns:

Type Description
Any

A plotly.graph_objects.Figure.

bar_plot

bar_plot(
    result: SummarisedResult | DataFrame,
    *,
    x: str,
    y: str,
    position: Literal["dodge", "stack"] = "dodge",
    facet: str | list[str] | None = None,
    colour: str | None = None,
    style: PlotStyle | None = None,
    title: str | None = None,
    x_title: str | None = None,
    y_title: str | None = None,
) -> Any

Create a bar chart from results.

Parameters:

Name Type Description Default
result SummarisedResult | DataFrame

A :class:SummarisedResult (auto-tidied) or a :class:~polars.DataFrame.

required
x str

Column name for x-axis (categories).

required
y str

Column name or estimate for y-axis (bar height).

required
position Literal['dodge', 'stack']

Bar positioning: "dodge" (side-by-side) or "stack" (stacked).

'dodge'
facet str | list[str] | None

Column(s) for faceting.

None
colour str | None

Column for colour grouping.

None
style PlotStyle | None

Plot style configuration.

None
title str | None

Plot title.

None
x_title str | None

X-axis title.

None
y_title str | None

Y-axis title.

None

Returns:

Type Description
Any

A plotly.graph_objects.Figure.

box_plot

box_plot(
    result: SummarisedResult | DataFrame,
    *,
    x: str,
    lower: str = "q25",
    middle: str = "median",
    upper: str = "q75",
    y_min: str = "min",
    y_max: str = "max",
    facet: str | list[str] | None = None,
    colour: str | None = None,
    style: PlotStyle | None = None,
    title: str | None = None,
    x_title: str | None = None,
    y_title: str | None = None,
) -> Any

Create a box plot from pre-computed summary statistics.

Unlike traditional box plots, this renders from pre-computed quantiles (as found in summarised results), not raw data.

Parameters:

Name Type Description Default
result SummarisedResult | DataFrame

A :class:SummarisedResult (auto-tidied) or a :class:~polars.DataFrame with columns for each statistic.

required
x str

Column for x-axis categories.

required
lower str

Column/estimate name for Q1 (25th percentile).

'q25'
middle str

Column/estimate name for median.

'median'
upper str

Column/estimate name for Q3 (75th percentile).

'q75'
y_min str

Column/estimate name for whisker minimum.

'min'
y_max str

Column/estimate name for whisker maximum.

'max'
facet str | list[str] | None

Column(s) for faceting.

None
colour str | None

Column for colour grouping.

None
style PlotStyle | None

Plot style configuration.

None
title str | None

Plot title.

None
x_title str | None

X-axis title.

None
y_title str | None

Y-axis title.

None

Returns:

Type Description
Any

A plotly.graph_objects.Figure.

Style Configuration

TableStyle dataclass

TableStyle(
    title_align: str = "left",
    title_color: str = "#333333",
    header_background: str = "#4361ee",
    header_color: str = "#ffffff",
    header_align: str = "center",
    body_align: str = "right",
    group_background: str = "#e8eaf6",
    group_color: str = "#333333",
    stripe: bool = True,
    stripe_color: str = "#f5f5f5",
    na_display: str = "–",
    font_family: str = "system-ui, -apple-system, sans-serif",
    font_size: int = 14,
)

Configuration for table appearance.

Attributes:

Name Type Description
title_align str

Alignment for the table title ("left", "center", "right").

title_color str

Colour for the title text (CSS colour string).

header_background str

Background colour for column headers.

header_color str

Text colour for column headers.

header_align str

Alignment for column headers.

body_align str

Default alignment for body cells.

group_background str

Background colour for group label rows.

group_color str

Text colour for group label rows.

stripe bool

Whether to apply row striping.

stripe_color str

Background colour for striped rows.

na_display str

String to display for None/missing values.

font_family str

Font family for the table.

font_size int

Base font size in pixels.

PlotStyle dataclass

PlotStyle(
    color_palette: list[str] = (
        lambda: [
            "#4361ee",
            "#3a86ff",
            "#8338ec",
            "#ff006e",
            "#fb5607",
            "#ffbe0b",
            "#06d6a0",
            "#118ab2",
            "#073b4c",
            "#ef476f",
        ]
    )(),
    background_color: str = "#ffffff",
    text_color: str = "#333333",
    grid_color: str = "#e0e0e0",
    font_family: str = "system-ui, -apple-system, sans-serif",
    font_size: int = 12,
    title_size: int = 16,
    show_legend: bool = True,
)

Configuration for plot appearance.

Attributes:

Name Type Description
color_palette list[str]

List of hex colour strings for data series.

background_color str

Plot background colour.

text_color str

Default text colour.

grid_color str

Gridline colour.

font_family str

Font family.

font_size int

Base font size in points.

title_size int

Title font size in points.

show_legend bool

Whether to show the legend by default.

customise_text

customise_text(
    x: str | list[str],
    *,
    fun: Callable[[str], str] | None = None,
    custom: dict[str, str] | None = None,
    keep: list[str] | None = None,
) -> str | list[str]

Style text strings for display.

Default transformation: replace underscores with spaces and apply sentence case.

Parameters:

Name Type Description Default
x str | list[str]

String or list of strings to style.

required
fun Callable[[str], str] | None

Custom transformation function. If None, uses the default snake_case-to-sentence-case transform.

None
custom dict[str, str] | None

Dict of exact replacements ({old: new}).

None
keep list[str] | None

Values to keep unchanged.

None

Returns:

Type Description
str | list[str]

Styled string(s), same type as input.

Examples:

>>> customise_text("cohort_name")
'Cohort name'
>>> customise_text(["age_group", "sex"])
['Age group', 'Sex']
>>> customise_text("cdm_name", custom={"cdm_name": "Database"})
'Database'

default_table_style

default_table_style() -> TableStyle

Return the default table style.

default_plot_style

default_plot_style() -> PlotStyle

Return the default plot style.

Mock Data

mock_summarised_result

mock_summarised_result(
    *, n_cohorts: int = 2, n_strata: int = 3
) -> SummarisedResult

Generate a mock :class:SummarisedResult for testing.

Produces results with: - n_cohorts cohort groups (cohort_1, cohort_2, ...) - n_strata strata combinations drawn from overall, age_group &&& sex pairs, and single sex strata. - Variables: number subjects (count), age (mean, sd), Medications Amoxiciline (count, percentage).

Parameters:

Name Type Description Default
n_cohorts int

Number of cohort groups.

2
n_strata int

Number of strata combinations (max 9).

3

Returns:

Name Type Description
A SummarisedResult

class:SummarisedResult with realistic test data.