Data contract

forestploter does not calculate effect sizes, confidence intervals, or meta-analysis models. Store already calculated values in the input file; the package reads, validates, lays out, and renders them.

Two independent column orders

Do not confuse the plot order with the physical file order:

columns sequence

The only authority for the final left-to-right image order. A CI plotting column may be first, last, or between display columns. Text can therefore be appended after a CI without changing the reader or the source-field names.

XLSX/CSV header order

An entry-time convention for human readability. It never controls the plot layout. The recommended order is:

  1. display fields that appear before the first CI;

  2. the shared estimate, lower, upper triple, representing the first CI position in a physical file;

  3. display fields that appear after the first CI;

  4. the six controls, always last and in this exact order: _plot_row, _series, _ci_column, _row_type, _indent, _is_summary.

For example, this file order supports a rendered “study → CI → effect text” layout:

study, estimate, lower, upper, effect_text,
_plot_row, _series, _ci_column, _row_type, _indent, _is_summary

The matching plot order is explicit:

columns = (
    ForestColumn("study", "Study", "text", 2.0),
    ForestColumn("ci", "Treatment effect", "ci", 3.0, "center"),
    ForestColumn("effect_text", "Effect [95% CI]", "numeric", 2.0, "right"),
)

Field dictionary

One source record means “one series in one target CI column”. The following rules use the default forestploter.ForestDataMapping names.

Field

Accepted type

Allowed values

Required and blank rules

display fields

Any scalar suitable for str()

Text, number, boolean, date-like value, or blank

Required only when referenced by a text or numeric forestploter.ForestColumn. All nonblank values within one _plot_row must agree. Blanks are not forward-filled.

estimate

Finite number

Any finite numeric value

On estimate and summary records, the three statistics must be all present or all blank. A fully blank triple explicitly skips drawing.

lower

Finite number

lower <= estimate

Uses the same all-present/all-blank rule as estimate.

upper

Finite number

estimate <= upper

Uses the same all-present/all-blank rule as estimate.

_plot_row

Nonblank hashable scalar

Any stable identifier

Required on every record. Equal values share exactly one y coordinate, and all records for one value must be contiguous. Stable strings are recommended.

_series

String-like scalar

A key used by series_styles

Required and nonblank for estimate and summary; blank for header and spacer. Records sharing _plot_row must agree.

_ci_column

String-like scalar

The key of a ForestColumn(role="ci")

Required and nonblank for estimate and summary; blank for header and spacer. A (_plot_row, _ci_column) target may occur only once.

_row_type

String enum

header, estimate, summary, spacer

Required on every record and identical within one _plot_row.

_indent

Finite non-negative number

>= 0

Required on every record and identical within one _plot_row. Integer levels are recommended, although decimals are currently accepted.

_is_summary

Actual boolean

TRUE/FALSE in XLSX; True/False in CSV

Required on every record and identical within one _plot_row. It controls marker shape only. Strings and numeric 0/1 are rejected.

text and numeric display roles currently both render text; the role communicates intent and default alignment rather than coercing source values.

Row-type recipes

_row_type

_series

_ci_column

Statistics

_indent

_is_summary

header

blank

blank

all blank

usually 0

FALSE

estimate

required

required

all present, or all blank to skip

>= 0

normally FALSE

summary

required

required

all present, or all blank to skip

usually 0

normally TRUE

spacer

blank

blank

all blank

usually 0

FALSE

_row_type determines row semantics, while _is_summary determines only the marker form. They are intentionally separate fields.

Complete single-CI example

The displayed effect text follows the CI in the image even though the CI itself has no physical source field.

study

estimate

lower

upper

effect_text

_plot_row

_series

_ci_column

_row_type

_indent

_is_summary

Study A

0.72

0.58

0.90

0.72 [0.58, 0.90]

os-a

Treatment

ci

estimate

0

FALSE

Pooled result

0.79

0.71

0.88

0.79 [0.71, 0.88]

pooled

Treatment

ci

summary

0

TRUE

Multiple series: header row and indented children

The recommended structure uses one header record for shared outcome-level data, followed by one indented visual row per series. Put the outcome, participant count, and other shared values on the header. Put the series label and its CI text on the child row with _indent=1. This conventional hierarchy uses no merges and has the same structure in XLSX and CSV.

label

participants

estimate

lower

upper

series_ci_text

_plot_row

_series

_ci_column

_row_type

_indent

_is_summary

Overall survival

1240

os-header

header

0

FALSE

Series A

0.72

0.58

0.90

0.72 [0.58, 0.90]

os-a

Series A

ci

estimate

1

FALSE

Series B

0.81

0.67

0.98

0.81 [0.67, 0.98]

os-b

Series B

ci

estimate

1

FALSE

Series C

0.95

0.79

1.14

0.95 [0.79, 1.14]

os-c

Series C

ci

estimate

1

FALSE

Do not create estimate_a/lower_a/upper_a and similar 3*k wide-form fields. Do not combine several series’ CI text into one multiline cell. Supported vertical XLSX merges are still accepted for specialized layouts; see XLSX merge rules. They are not needed to express this parent-child hierarchy.

Multiple CI columns: duplicate records, share a visual row

One series shown in crude and adjusted CI columns uses two records. The records share _plot_row and _series but have different _ci_column values. They therefore share the exact same y coordinate.

For a display value shared by those two source records, either fill it once and leave the other cell blank, or repeat the same value in both records. The renderer aggregates non-conflicting values within that _plot_row; it does not forward-fill source data.

outcome

estimate

lower

upper

_plot_row

_series

_ci_column

_row_type

_indent

_is_summary

Readmission

0.74

0.63

0.87

readmit-a

Cohort A

ci_crude

estimate

0

FALSE

Readmission

0.79

0.67

0.93

readmit-a

Cohort A

ci_adjusted

estimate

0

FALSE

Multiple series, multiple CI columns, and trailing text

The following table is a specialized merged-layout example, not the recommended way to express a routine parent-child hierarchy. Use it only when a display value specifically needs to be centered across several source records.

Repeat the two-record pattern for each series, giving each series a different _plot_row. A trailing display field such as cohort may be merged over the two records for that visual row. It is rendered after both CI columns when listed last in columns:

columns = (
    ForestColumn("outcome", "Outcome", "text", 2.4),
    ForestColumn("ci_crude", "Crude", "ci", 3.4, "center"),
    ForestColumn("ci_adjusted", "Adjusted", "ci", 3.4, "center"),
    ForestColumn("cohort", "Cohort", "text", 1.1),
)

outcome

estimate

lower

upper

cohort

_plot_row

_series

_ci_column

Readmission (merge 4 rows)

0.74

0.63

0.87

Cohort A (merge 2 rows)

readmit-a

Cohort A

ci_crude

0.79

0.67

0.93

readmit-a

Cohort A

ci_adjusted

0.86

0.73

1.01

Cohort B (merge 2 rows)

readmit-b

Cohort B

ci_crude

0.89

0.75

1.05

readmit-b

Cohort B

ci_adjusted

This structure prevents independent per-CI series offsets: alignment is stated directly by (_plot_row, _series) in the data.

XLSX merge rules

forestploter.read_forest_data() accepts a merge only when it is:

  • below the header;

  • vertical and confined to one display-field column;

  • at least two physical records tall; and

  • aligned with complete _plot_row record groups.

Merges in statistics or controls, horizontal merges, rectangles, header crossings, and partial visual-row groups raise an error. Merged text is drawn once at the span center, with internal horizontal rules suppressed in that column.

CSV compatibility

CSV uses exactly the same fields and record ordering. It cannot store merge ranges, so repeat shared display values explicitly. Normalized plot rows, observations, clipping counts, and y positions should remain equivalent between the two formats.

Clipping semantics

An arrow replaces the vertical cap on every clipped side. An interval spanning both limits shows its visible line and two arrows, with no endpoint caps. An interval entirely left or right of the range shows only the nearest outward arrow and no line, cap, or fabricated marker. clipped_intervals counts affected intervals, so each interval contributes at most one.