Bits & Flames bitsandflames/fyron

Clinical Plotting

Fyron's clinical plotting helpers are exposed through the dedicated fyron.plotting facade. They remain plain Matplotlib functions, so every figure can be saved, customized, combined into panels, or audited through returned tables.

Use these plots to explain clinical data science decisions: survival differences, model discrimination, calibration, thresholds, cohort flow, biomarker distributions, correlations, and manuscript-ready effect estimates.

Use This Page For

NeedStart here
Choose colors for manuscript figuresScientific Palettes And Styles
Make bars readable without colorHatch And Schraffierung Styles
Add p-value brackets or starsStatistical Annotations
Plot time-to-event endpointsKaplan-Meier Curves
Compare model and cohort outputsPlot Families
Browse generated examplesVisual Figure Gallery

For CT/MR slice comparison figures, use BOA Visualization. Those helpers validate image geometry and return slice-level difference summary tables.

Install

bash
uv add "fyron[ml]"
uv add "fyron[survival]"

Import

python
from fyron import plotting as fp

Existing imports continue to work:

python
from fyron import ml
from fyron.survival import plot_kaplan_meier

Core Concepts

ConceptMeaning
fig, axStandard Matplotlib figure and axes returned by most plotting helpers.
result objectSome workflow plots, such as Kaplan-Meier, return a named object with figure, axes, and computed statistics.
save_pathOptional file path for writing the figure directly.
fmtOptional output format when saving, such as png, pdf, or svg.
group_col / hueClinical grouping variables used to stratify curves, boxes, bars, or densities.
title, xlabel, ylabelExplicit labels for manuscript-ready figures.

API Contract

TopicContract
Input shapeArrays, result dictionaries, or DataFrames depending on the plot family.
Required columnsNamed x/y/group/hue/time/event/metric columns must exist when using DataFrame plotting helpers.
Return shapeUsually (fig, ax); table-backed plots return (fig, ax, table) or named result objects for survival plots.
Saved artifactsFigures via save_path, plus returned audit tables for statistics, correlations, annotations, and summary plots.
Failure modesMissing columns, invalid palette/colormap names, mismatched lengths, unavailable optional survival extras, cramped labels requiring figure-size adjustment, or inappropriate statistical tests.

Plotting Philosophy

Clinical figures often need to be clear in slides, manuscripts, and grayscale printouts. Fyron therefore:

  • uses Matplotlib, avoiding a large plotting stack,
  • returns figure and axes objects for downstream customization,
  • supports save_path on plotting functions,
  • includes hatch patterns for grouped bar plots,
  • defaults categorical plots to the colorblind-safe Okabe-Ito palette,
  • keeps BOARISK/Bits & Flames colors available as named palettes such as bf, bf_vibrant, and bf_vibrant_muted,
  • defaults correlation plots to Spearman for clinical exploratory work,
  • includes grouped boxplots with patient-level jitter for cohort and hue comparisons,
  • includes smoothed density plots for distributions by event, treatment, or risk group,
  • includes grouped bar plots with statistical significance notation for manuscript summaries,
  • includes Kaplan-Meier curves for clinical time-to-event endpoints,
  • includes calibration and precision-recall plots, not only ROC curves.

Clinical Figure Preview

All previews below use synthetic data. They demonstrate figure layout, palette choice, and export style rather than clinical results.

Plot Selection Guide

QuestionRecommended plot
Can the model rank positives above negatives?ROC curve, AUC
How does performance behave under class imbalance?Precision-recall curve
Are predicted probabilities trustworthy?Calibration curve
What mistakes occur at a threshold?Confusion matrix
Do biomarker values differ across groups and subgroups?Grouped boxplot
Do subgroup means/medians differ and need manuscript notation?Grouped statistical bar plot
How does a continuous marker distribute by event or cohort?Density plot
Which features move together?Correlation heatmap
Which features combine large effects with strong p-values?Volcano plot
How do patient-level treatment durations and events unfold?Swimmer plot
How do cohort members move across states or decisions?Alluvial flow
How do discrete patient-feature marker bins compare?Binned marker heatmap
How do event-free or overall survival curves differ by group?Kaplan-Meier curve
Which model is best for a clinical metric?Patterned metric bars
Do risk scores separate event groups?Risk score distribution
Do marker distributions differ while preserving all patient points?Raincloud plot
Which covariates increase or reduce risk?Signed feature weights
Which endpoints/subgroups are statistically strongest?Significance bars
What is the estimate and uncertainty per subgroup?Forest estimates

Function Summary

FunctionUse WhenReturns
plot_roc_curveBinary model discrimination with probabilities(fig, ax)
plot_cross_validated_rocDiscrimination across out-of-fold cross-validation predictions(fig, ax, table)
plot_precision_recall_curveImbalanced binary endpoints where PPV/recall matter(fig, ax)
plot_calibration_curveChecking whether predicted probabilities match observed risk(fig, ax)
plot_confusion_matrixReviewing TP/FP/FN/TN counts(fig, ax)
plot_kaplan_meierVisualizing time-to-event outcomes by groupKaplanMeierPlotResult
plot_feature_importanceShowing tree-model feature importances(fig, ax)
plot_grouped_boxplotComparing continuous biomarkers across groups and hue categories(fig, ax) or (fig, ax, table)
plot_swarmplotShowing patient-level points across groups and hue categories(fig, ax) or (fig, ax, table)
plot_histogramShowing binned distributions by cohort or hue(fig, ax)
plot_grouped_bar_with_significanceGrouped/hue bar summaries with p-value brackets and notation(fig, ax, table)
plot_densityShowing smoothed distributions for a continuous marker(fig, ax)
plot_correlation_heatmapExploring numeric feature relationships(fig, ax, corr_df)
plot_grouped_correlation_heatmapsComparing correlation structures by subgroup(fig, axes, corr_df)
plot_missingness_barVisualizing missingness summaries(fig, ax)
plot_feature_selection_summaryShowing selected/rejected feature-selection results(fig, ax)
plot_stability_selectionShowing stability-selection frequencies(fig, ax)
plot_bland_altmanAgreement between two measurement methods(fig, ax)
plot_waterfallOrdered patient-level response or score-change values(fig, ax)
plot_table_one_summaryCompact Table 1 p-value overview(fig, ax)
plot_survival_calibrationFixed-horizon observed versus predicted survival/event risk(fig, ax)
plot_decision_curveDecision-curve net benefit over thresholds(fig, ax)
plot_time_dependent_aucPrecomputed time-dependent AUC values(fig, ax)
plot_cohort_flowCONSORT/STROBE-style cohort count flow(fig, ax)
plot_segmentation_overlay_gridImage/mask overlay grid for segmentation QC(fig, axes)
plot_dice_by_labelLabel-wise Dice score bars(fig, ax)
plot_external_validation_metricsExternal validation metric bars(fig, ax)
plot_patterned_barPublication-safe grouped bars with hatches(fig, ax)
plot_metric_barsComparing model metrics across models(fig, ax)
plot_likert_scaleReader-study, usability, or survey responses on ordered scales(fig, ax, table)
plot_waffleCohort composition, endpoint mix, or class balance as unit squares(fig, ax, table)
plot_risk_score_distributionShowing score distributions by event/censoring group(fig, ax)
plot_signed_feature_weightsShowing signed model coefficients or feature weights(fig, ax)
plot_significance_barsShowing p-values as -log10(p) bars(fig, ax, table)
plot_volcanoScreening features by effect size and p-value(fig, ax)
plot_swimmerPatient-level treatment timelines with events(fig, ax)
plot_raincloudDistribution summaries with half-violin, box, and patient points(fig, ax)
plot_alluvial_flowCategorical transitions across ordered stages(fig, ax)
plot_binned_marker_heatmapDiscrete patient-feature marker grids(fig, ax)
plot_directional_ratio_rowsShowing odds or hazard ratios with protective/hazardous direction bands(fig, ax)
plot_forest_estimatesShowing estimates with confidence intervals(fig, ax)
plot_hazard_ratiosPlotting Cox hazard ratios from a Fyron Cox result(fig, ax)
get_paletteGetting a named scientific palette as hex colorslist
list_palettesReviewing available palettes and safety metadataDataFrame
get_colormapGetting a named sequential/diverging Matplotlib colormapcolormap
set_plot_styleApplying Fyron paper/poster/minimal Matplotlib defaultsNone
preview_paletteRendering palette swatches for review(fig, ax)
palette_luminance_tableAuditing luminance and text-color contrastDataFrame
palette_grayscale_contrastChecking adjacent grayscale differencesDataFrame
stat_test_pairsComputing pairwise p-values for plot annotationsDataFrame
add_stat_annotationsAdding bracket annotations to categorical Matplotlib plotsAxes
format_p_value_annotationFormatting p-values as stars or textstring
plot_schoenfeld_residualsCox proportional-hazards residual diagnostics(fig, ax) from fyron.survival

Function Parameter Reference

FunctionRequiredOptionalReturns
plot_roc_curvey_true, y_probtitle, ax, save_path, fmt, pos_label(fig, ax)
plot_cross_validated_rocprediction table with y_true, y_prob, foldcolumn names, mean_grid_points, palette, title, save options(fig, ax, table)
plot_precision_recall_curvey_true, y_probtitle, ax, save_path, fmt, pos_label(fig, ax)
plot_calibration_curvey_true, y_probn_bins, strategy, title, ax, save_path, fmt, pos_label(fig, ax)
plot_confusion_matrixy_true, y_predtitle, ax, save_path, fmt, pos_label(fig, ax)
plot_kaplan_meierdf, duration_col, event_colgroup_col, split_column, CI/risk count/style/save/RMST optionsKaplanMeierPlotResult
plot_feature_importanceimportance table or model wrapper pathtop_n, title, ax, save_path, fmt(fig, ax)
plot_grouped_boxplotdata, x, yhue, order, labels, palette, points, comparisons, stat_test, p_adjust, annotation options, return_stats, save options(fig, ax) or (fig, ax, table)
plot_swarmplotdata, x, yhue, orders, dodge, point style, comparisons, stat_test, p_adjust, annotation options, return_stats, save options(fig, ax) or (fig, ax, table)
plot_histogramdata, xhue/group, bins, stat, multiple, labels, palette, save options(fig, ax)
plot_grouped_bar_with_significancedata, x, yhue, orders, estimator, error, test, comparisons, notation thresholds, labels, save options(fig, ax, table)
plot_densitydata, xhue, hue_order, labels, palette, fill, alpha, rug, bandwidth, gridsize, ax, save_path, fmt(fig, ax)
plot_correlation_heatmapdatacolumns, method, group_col, title, annotate, significance overlay, mask_upper, color scale, save options(fig, ax, corr_df) or grouped output
plot_grouped_correlation_heatmapsdata, group_colcolumns, method, annotation/significance options, color scale, save options(fig, axes, corr_df)
plot_missingness_barmissingness summary tablecolumn/group/value column names, title, ax, save_path, fmt(fig, ax)
plot_feature_selection_summaryfeature-selection summary tablefeature/selected/score columns, top_n, save options(fig, ax)
plot_stability_selectionstability-selection tablefeature/frequency columns, threshold, top_n, save options(fig, ax)
plot_bland_altmanmethod_a, method_btitle, ax, save_path, fmt(fig, ax)
plot_waterfallvalueslabels, title, y label, threshold, save options(fig, ax)
plot_table_one_summaryTable 1 long table with p-valuesvariable/p-value column names, save options(fig, ax)
plot_survival_calibrationcalibration bin tableobserved/predicted column names, save options(fig, ax)
plot_decision_curvedecision-curve tablethreshold/model/treat-all/treat-none columns, save options(fig, ax)
plot_time_dependent_auctime/AUC tabletime and AUC column names, save options(fig, ax)
plot_cohort_flowcohort flow tablestep/count column names, save options(fig, ax)
plot_segmentation_overlay_grid3D image and mask arraysslice indices, label, alpha, save options(fig, axes)
plot_dice_by_labelsegmentation metric tablelabel and Dice column names, save options(fig, ax)
plot_external_validation_metricsmetric dict or long tablemetric/value column names, save options(fig, ax)
plot_patterned_bardata, x, ygroup, error, labels, orientation, palette, hatches, annotate, ax, save_path, fmt(fig, ax)
plot_metric_barsmetricsmetrics_to_plot, model_col, title, ax, save_path, fmt(fig, ax)
plot_likert_scalelong table with item and responsecount_col, ordering, neutral responses, normalization, palette, save options(fig, ax, table)
plot_wafflecategory/value tablecolumn names, total, rows, palette, save options(fig, ax, table)
plot_risk_score_distributiondata with risk_colevent_col, event_labels, title, ylabel, jitter, violin, palette, random_state, ax, save_path, fmt(fig, ax)
plot_signed_feature_weightsdata with feature_col and weight_coltop_n, title, xlabel, legend labels, ax, save_path, fmt(fig, ax)
plot_significance_barsdata with label_col and p_colthreshold, title, xlabel, annotate, ax, save_path, fmt(fig, ax, table)
plot_volcanofeature, effect, and p-value columnsgroup/color column, thresholds, top labels, palette, save options(fig, ax)
plot_swimmerpatient, start, and end columnsgroup, event time/type columns, labels, palette, save options(fig, ax)
plot_raincloudgroup and numeric value columnsorder, labels, palette, random state, save options(fig, ax)
plot_alluvial_flowpath, stage, category, and value columnsstage order, palette, save options(fig, ax)
plot_binned_marker_heatmaprow, column, and numeric bin columnsrow/column order, colormap, missing color, save options(fig, ax)
plot_directional_ratio_rowsdata with feature_col and ratio_coloptional CI, p-value, group, n columns, OR/HR table headers, reference, log scale, x limits, annotations, save options(fig, ax)
plot_forest_estimatesdata with label_col, estimate_col, lower_col, upper_colp_col, p_adjust_col, n_col, group_col, table columns, reference, log_scale, labels, save options(fig, ax)
plot_hazard_ratiosCox result or fitted Cox objectlabel/title/table/save options(fig, ax)
get_palettenonename, n, reverselist
list_palettesnonekindDataFrame
get_colormapnonename, reverseMatplotlib colormap
set_plot_stylenonestyle, paletteNone
preview_palettenamen, save_path(fig, ax)
palette_luminance_tablepalette name or colorsnoneDataFrame
palette_grayscale_contrastpalette name or colorsnoneDataFrame
stat_test_pairsdata, x, y, pairshue, test, p-value correction, custom p-values, annotation formatDataFrame
add_stat_annotationsax, stats_tablepositions, heights, annotation column, style, inside/outside placementAxes
plot_schoenfeld_residualsSchoenfeld residual tablecovariates, column names, save options(fig, ax)

Parameter Notes

ParameterPractical guidance
dataUse a DataFrame with explicitly named clinical columns.
y_true, y_pred, y_probUse the same held-out set when comparing model evaluation plots.
duration_col, event_colSurvival plots expect positive durations and 1 event / 0 censored coding.
group_col, hue, order, hue_orderSet these explicitly for reproducible manuscript layouts.
axPass an existing Matplotlib axis when composing multi-panel figures.
save_pathUse for direct artifact generation in scripts or CLI-like jobs.

Return Values

Most plotting functions return (fig, ax). Some functions return (fig, ax, table) when the computed table is part of the result, such as statistical bar plots or correlation heatmaps. Kaplan-Meier functions return a result object with plot handles and survival statistics. Keep returned tables with exported figures so visual annotations can be audited.

Scientific Palettes And Styles

Fyron uses named palettes so a study can keep the same visual language across notebooks, figures, slides, and supplements. The defaults follow scientific color guidance from SciFig, Datawrapper, and Molecular Ecologist: use qualitative colors for categories, sequential colors for ordered magnitude, and diverging colors when values have a meaningful midpoint. Avoid rainbow/jet maps for continuous data, keep categorical palettes small, and pair color with hatches, points, labels, or small multiples when a figure has many groups.

The default categorical palette is okabe_ito, a colorblind-safe palette widely used for scientific figures. BOARISK/Bits & Flames colors remain available as palette="bf", palette="bf_vibrant", and palette="bf_vibrant_muted" when brand continuity matters.

python
from fyron import plotting as fp

fp.list_palettes()

fig, ax = fp.preview_palette("okabe_ito")
audit = fp.palette_luminance_table("okabe_ito")
contrast = fp.palette_grayscale_contrast("okabe_ito")

fp.set_plot_style(style="paper", palette="okabe_ito")

Use named palettes and colormaps directly in plotting functions:

python
fig, ax = fp.plot_grouped_boxplot(
    clinical_features,
    x="risk_group",
    y="l3_sma_cm2",
    palette="okabe_ito",
)

km = fp.plot_kaplan_meier(
    cohort,
    duration_col="time",
    event_col="event",
    group_col="risk_group",
    palette="okabe_ito",
)

fig, ax, corr = fp.plot_correlation_heatmap(
    clinical_features,
    columns=["age", "tumor_volume", "risk_score"],
    cmap="fyron_diverging_blue_red",
)

Practical color rules:

  • Use okabe_ito or wong for categorical clinical groups.
  • Use muted_scientific or nature_cool for restrained journal-style categorical plots.
  • Use bf_vibrant_muted for branded figures that should feel like Bits & Flames without overpowering manuscript layouts.
  • Use fyron_sequential_blue or fyron_sequential_teal for ordered magnitude.
  • Use fyron_diverging_blue_red or fyron_diverging_teal_orange for centered effects such as correlations, residuals, or signed changes.
  • If a categorical palette needs more colors than recommended, Fyron warns. Consider hatches, marker shapes, faceting, or small multiples.
  • Use external tools such as Viz Palette, Coblis, Sim Daltonism, or Datawrapper-style checks for full color-vision simulation before submission.

Hatch And Schraffierung Styles

Schraffierungen make grouped figures more robust in grayscale, print, slide decks, and color-vision-sensitive settings. Use them when color alone would make groups hard to distinguish, especially for bar plots with multiple cohorts, endpoints, models, or scanner groups. For swarmplots, density plots, and line plots, prefer points, markers, direct labels, or small multiples instead of hatch patterns.

Fyron uses these default hatch styles:

PositionHatch
1none
2///
3\\\
4xx
5..
6++
7--
8oo

Default Fyron hatch styles

Use the hatches= parameter when you want to lock a specific visual language:

python
from fyron import plotting as fp

fig, ax = fp.plot_patterned_bar(
    summary_df,
    x="endpoint",
    y="rate",
    group="cohort",
    error="ci_half_width",
    palette="bf_vibrant_muted",
    hatches=["", "///", "\\\\\\"],
    annotate=True,
    title="Endpoint rates by cohort",
)

plot_metric_bars builds on the same patterned-bar style, so model-comparison bars inherit black outlines, grouped hatches, and grayscale-safe visual separation. For manuscript figures, combine hatches with restrained palettes, black outlines, direct labels, and exported vector formats such as PDF or SVG.

Statistical Annotations

Fyron includes a Matplotlib-native annotation layer inspired by trevismd/statannotations. It keeps the useful manuscript behavior, including pairwise tests, multiple-testing correction, custom p-values, star labels, explicit p-value labels, and bracket layout, without adding Seaborn as a required dependency.

Use stat_test_pairs when you need the statistics table separately, and use add_stat_annotations when you already have an axis to annotate:

python
stats = fp.stat_test_pairs(
    clinical_features,
    x="risk_group",
    y="risk_score",
    pairs=[("Low", "High")],
    test="mannwhitney",
    p_adjust="fdr_bh",
)

fp.add_stat_annotations(ax, stats)

For built-in categorical plots, prefer return_stats=True so the figure and its statistical audit table are produced together.

Model Evaluation Plot Set

python
from fyron import ml, plotting as fp

rf = ml.train_random_forest(
    X_train=X_train,
    y_train=y_train,
    X_test=X_test,
    y_test=y_test,
    n_estimators=300,
    random_state=42,
)

fig_roc, _ = fp.plot_roc_curve(
    y_true=y_test,
    y_prob=rf["y_prob"],
    title="Random Forest ROC",
)
fig_pr, _ = fp.plot_precision_recall_curve(
    y_true=y_test,
    y_prob=rf["y_prob"],
)
fig_cal, _ = fp.plot_calibration_curve(
    y_true=y_test,
    y_prob=rf["y_prob"],
    n_bins=10,
)
fig_cm, _ = fp.plot_confusion_matrix(
    y_true=y_test,
    y_pred=rf["y_pred"],
)

When plot=True is passed to train_random_forest or train_xgboost, these figures are created automatically:

python
rf = ml.train_random_forest(
    X_train=X_train,
    y_train=y_train,
    X_test=X_test,
    y_test=y_test,
    n_estimators=300,
    random_state=42,
    plot=True,
)

rf["figures"].keys()
# dict_keys(["roc", "precision_recall", "calibration", "confusion_matrix"])

For cross-validation, pass a table of out-of-fold predictions instead of a fitted model. This keeps model fitting separate from figure generation and makes the AUC table easy to save with the manuscript supplement.

python
cv_predictions = pd.DataFrame(
    {
        "fold": fold_ids,
        "y_true": y_true_oof,
        "y_prob": y_prob_oof,
    }
)

fig, ax, auc_table = fp.plot_cross_validated_roc(
    cv_predictions,
    title="Five-fold cross-validated ROC",
    palette="okabe_ito",
)

Correlation Heatmap

Use a correlation heatmap for feature screening, redundancy checks, and sanity checks before modeling. Fyron uses method="spearman" by default because clinical variables are often skewed, ordinal, or monotonic without being normally distributed.

python
fig, ax, corr = fp.plot_correlation_heatmap(
    data=clinical_features,
    columns=["age", "tumor_volume", "risk_score", "lab_value"],
    method="spearman",
    mask_upper=True,
    annotate_significance=True,
    p_adjust="fdr_bh",
    title="Feature correlation",
    cmap="fyron_diverging_teal_orange",
)

For subgroup analyses, use group_col or call plot_grouped_correlation_heatmaps directly:

python
fig, axes, corr_long = fp.plot_grouped_correlation_heatmaps(
    data=clinical_features,
    group_col="treatment_arm",
    columns=["age", "tumor_volume", "risk_score", "lab_value"],
    annotate_significance=True,
    cmap="fyron_diverging_teal_orange",
)

Tips:

  • Use method="spearman" for ordinal or non-linear monotonic relationships.
  • Use method="pearson" when a linear relationship is the actual assumption.
  • Use mask_upper=True to reduce visual duplication in large matrices.
  • The returned corr DataFrame can be saved as a supplement table.

Grouped Boxplots

Use grouped boxplots when the clinical question is about a continuous biomarker across discrete patient groups: L3 muscle area by risk group, creatinine by treatment arm, VAT by sex and cohort, or a model score by response category. Fyron draws each box with a black outline, restrained color fill, and optional patient-level jitter so outliers and sample size remain inspectable.

Required columns:

  • x: categorical group column such as risk group, treatment arm, sex, stage, or endpoint.
  • y: numeric marker column such as l3_sma_cm2, risk_score, vat_ml, or albumin.

Common optional parameters:

  • hue: second grouping column drawn side by side inside each x group.
  • order and hue_order: explicit display order for manuscript-ready figures.
  • show_points: overlay patient-level jittered observations.
  • palette: custom color sequence.
python
fig, ax = fp.plot_grouped_boxplot(
    data=clinical_features,
    x="risk_group",
    y="l3_sma_cm2",
    hue="sex",
    order=["Low", "Intermediate", "High"],
    hue_order=["Female", "Male"],
    title="L3 skeletal muscle area by risk group",
    ylabel="L3 SMA (cm2)",
    palette="okabe_ito",
)

Add statannotations-style brackets when the figure needs explicit comparisons. Fyron computes the tests with SciPy, applies Fyron p-value correction, and returns the audit table when return_stats=True.

python
fig, ax, stats = fp.plot_grouped_boxplot(
    data=clinical_features,
    x="risk_group",
    y="l3_sma_cm2",
    comparisons=[("Low", "High")],
    stat_test="mannwhitney",
    p_adjust="fdr_bh",
    annotation_format="star",
    return_stats=True,
    palette="okabe_ito",
)

For small and medium cohorts, keep show_points=True so readers can see how many observations drive each box. For very large cohorts, set show_points=False and consider a density plot next to the boxplot.

Statistical Grouped Bar Plots

Use statistical grouped bar plots when the manuscript needs compact mean or median summaries with explicit significance notation. Fyron draws grouped/hue bars with black outlines, optional error bars, and bracket annotations. The returned table includes one row per plotted summary plus one row per statistical comparison, so the visual notation can be audited.

Defaults:

  • estimator="mean" and error="sem".
  • test="auto" uses Mann-Whitney U for pairwise group comparisons.
  • hue with two levels compares hue levels inside each x group.
  • two x groups without hue compare the two x groups directly.
  • notation defaults to ***, **, *, and ns.
python
fig, ax, stats_table = fp.plot_grouped_bar_with_significance(
    data=clinical_features,
    x="risk_group",
    y="l3_sma_cm2",
    hue="sex",
    estimator="mean",
    error="sem",
    title="L3 skeletal muscle area by risk group and sex",
    ylabel="L3 SMA (cm2)",
)

For small cohorts, pair this plot with a grouped boxplot or density plot so readers can inspect the distribution behind the summary bars.

Density Plots

Density plots are helpful when a boxplot hides the actual shape of the distribution. Use them to compare risk scores between event and non-event patients, lab values between treatment arms, or imaging markers between anatomical subgroups. Fyron uses a lightweight NumPy Gaussian kernel density estimate, so no Seaborn or SciPy dependency is required.

Required columns:

  • x: numeric marker or score column.

Common optional parameters:

  • hue: categorical column for separate curves.
  • hue_order: explicit curve order.
  • fill: fill the area below each curve.
  • rug: show patient-level tick marks along the baseline.
  • bandwidth: custom smoothing bandwidth when the automatic rule is too smooth or too jagged.
  • gridsize: number of x-axis evaluation points.
python
fig, ax = fp.plot_density(
    data=clinical_features,
    x="risk_score",
    hue="event",
    hue_order=[0, 1],
    title="Risk score density by event status",
    xlabel="Predicted risk score",
    rug=True,
    palette="okabe_ito",
)

Interpret density plots as distribution summaries, not counts. If absolute group size matters in the figure, use plot_histogram; if each patient-level value should remain visible, use plot_swarmplot.

python
fig, ax = fp.plot_histogram(
    clinical_features,
    x="risk_score",
    hue="event",
    stat="percent",
    multiple="dodge",
    palette="okabe_ito",
)

fig, ax, stats = fp.plot_swarmplot(
    clinical_features,
    x="risk_group",
    y="risk_score",
    hue="sex",
    comparisons=[(("High", "Female"), ("High", "Male"))],
    return_stats=True,
    palette="okabe_ito",
)

Validation And QC Plots

These plots consume tables produced by fyron.validation, fyron.reporting, or fyron.imaging.segmentation.

python
from fyron import plotting as fp

fig_cal, _ = fp.plot_survival_calibration(calibration_bins)
fig_dca, _ = fp.plot_decision_curve(decision_curve)
fig_flow, _ = fp.plot_cohort_flow(flow_table)
fig_dice, _ = fp.plot_dice_by_label(segmentation_metrics)
fig_ext, _ = fp.plot_external_validation_metrics(
    {"auc": 0.81, "sensitivity": 0.74, "specificity": 0.69}
)

When inclusion and exclusion filters are executed in code, generate the count table with fyron.flow and pass it directly to plot_cohort_flow:

python
from fyron.flow import FlowTracker
from fyron import plotting as fp

flow = FlowTracker("NSCLC CT cohort")
flow.add_step("Source cohort", raw)
flow.add_step("Has baseline CT", with_ct, reason="missing CT")

fig, ax = fp.plot_cohort_flow(flow.to_dataframe())

For segmentation visual QC:

python
fig, axes = fp.plot_segmentation_overlay_grid(
    image=ct_array,
    mask=label_map,
    slice_indices=[40, 60, 80],
    label=5,
)

Kaplan-Meier Curves

Kaplan-Meier curves belong in the clinical plotting toolkit because many cohort studies, imaging biomarkers, and risk scores are evaluated against time-to-event endpoints. You can call them through fyron.plotting.plot_kaplan_meier with the other clinical plots; internally the function delegates to fyron.survival because it fits Kaplan-Meier estimators, computes log-rank comparisons, and can annotate restricted mean survival time.

Required columns:

  • duration_col: positive follow-up time.
  • event_col: event indicator where 1 means event and 0 means censored.

Common optional columns and parameters:

  • group_col: categorical group such as stage, treatment, or risk group.
  • split_column: continuous score to split into median, tertile, quartile, or custom groups.
  • at_risk_counts: add an at-risk count table below the plot.
  • show_censors: show censoring ticks.
  • xlim: limit the time axis for a prespecified follow-up horizon.
  • annotate_logrank: add the log-rank p-value to the plot.
  • annotate_rmst: annotate restricted mean survival time when configured.
python
from fyron import plotting as fp

km = fp.plot_kaplan_meier(
    df=cohort,
    duration_col="time",
    event_col="event",
    group_col="risk_group",
    at_risk_counts=True,
    show_censors=True,
    annotate_logrank=True,
    xlim=(0, 60),
    title="Overall survival by risk group",
    figsize=(7, 4),
    palette="okabe_ito",
)

km.figure.savefig("figures/km_risk_group.png", bbox_inches="tight")
km.logrank
km.n_per_group

Use split_column when you have a continuous score and want Fyron to create the groups before plotting:

python
km = fp.plot_kaplan_meier(
    df=cohort,
    duration_col="time",
    event_col="event",
    split_column="risk_score",
    split_method="quartile",
    at_risk_counts=True,
    title="Overall survival by risk-score quartile",
    palette="okabe_ito",
)

For full survival modeling details, see Survival Analysis.

Plot Families

The BOARISK papers use a compact visual language: light grids, black outlines, restrained colors, and hatches so grouped figures remain readable in grayscale. Fyron exposes the same language in general-purpose functions for clinical data science.

Risk Score Distribution

Use this when you have a risk score, model score, or survival risk output and want to see whether event-positive patients are shifted toward higher values. It accepts a plain table and can draw one combined distribution or separate groups by event_col.

Required columns:

  • risk_col: numeric score column.

Optional columns and parameters:

  • event_col: grouping column, usually 0 for censored/non-event and 1 for event.
  • event_labels: mapping or sequence used to replace group labels.
  • jitter: overlay patient-level points.
  • violin: use violin distributions; set to False for box plots.
python
import pandas as pd
from fyron import plotting as fp

risk_df = pd.DataFrame(
    {
        "risk_score": [0.12, 0.24, 0.35, 0.61, 0.72, 0.89],
        "event": [0, 0, 0, 1, 1, 1],
    }
)

fig, ax = fp.plot_risk_score_distribution(
    data=risk_df,
    risk_col="risk_score",
    event_col="event",
    event_labels={0: "Censored", 1: "Event"},
    title="Risk score by event status",
    palette="bf_vibrant_muted",
)

Signed Feature Weights

Use this for Cox coefficients, logistic regression coefficients, SHAP-like signed summaries, or any model output where positive values push risk upward and negative values push risk downward.

Required columns:

  • feature_col: feature or covariate name.
  • weight_col: signed numeric weight.

Optional parameters:

  • top_n: keep the largest absolute weights.
  • positive_label and negative_label: legend labels for interpretation.
python
coef_df = pd.DataFrame(
    {
        "feature": ["L3 SMA", "VAT", "Age", "Albumin", "Tumor volume"],
        "coef": [-0.42, 0.38, 0.29, -0.31, 0.67],
    }
)

fig, ax = fp.plot_signed_feature_weights(
    data=coef_df,
    feature_col="feature",
    weight_col="coef",
    top_n=5,
    title="Top signed Cox coefficients",
)

Significance Bars

Use this for log-rank results, subgroup tests, univariate screening, or endpoint comparisons. The plot converts p-values to -log10(p) so smaller p-values are visually stronger. It also returns the computed table with minus_log10_p.

Required columns:

  • label_col: endpoint, subgroup, feature, or comparison name.
  • p_col: p-value column.

Optional parameters:

  • threshold: significance threshold drawn as a dashed line.
  • annotate: show readable p-value labels next to bars.
python
p_values = pd.DataFrame(
    {
        "comparison": ["L3 SMA", "VAT", "Albumin", "Age"],
        "p_value": [0.003, 0.048, 0.021, 0.19],
    }
)

fig, ax, table = fp.plot_significance_bars(
    data=p_values,
    label_col="comparison",
    p_col="p_value",
    threshold=0.05,
    title="Log-rank screening",
)

Directional Ratio Rows

Use this for odds ratios, hazard ratios, or risk ratios when the direction matters as much as the estimate. Ratios below 1.0 are shown on the protective side, ratios above 1.0 on the hazardous side, and each feature receives one row.

Required columns:

  • feature_col: feature, biomarker, subgroup, or covariate name.
  • ratio_col: numeric odds ratio, hazard ratio, or risk ratio.

Optional columns:

  • lower_col and upper_col: confidence interval bounds.
  • p_col: p-value annotation.
  • group_col: category prefix for row labels.
  • n_col: sample size appended to row labels.

The right-side information table is optional. Keep annotate=True to show estimate, 95% CI, and p value columns; set annotate=False for a pure visual ratio plot. Fyron infers HR, OR, or RR from ratio_label, and you can override the headers with estimate_label, ci_label, and p_label.

python
ratio_df = pd.DataFrame(
    {
        "feature": ["Sarcopenia", "High VAT", "Albumin", "Stage IV"],
        "category": ["Body composition", "Body composition", "Laboratory", "Clinical"],
        "hazard_ratio": [1.78, 1.41, 0.68, 2.16],
        "ci_lower": [1.18, 1.03, 0.49, 1.44],
        "ci_upper": [2.68, 1.94, 0.94, 3.24],
        "p_value": [0.006, 0.031, 0.018, 0.0004],
    }
)

fig, ax = fp.plot_directional_ratio_rows(
    data=ratio_df,
    feature_col="feature",
    ratio_col="hazard_ratio",
    lower_col="ci_lower",
    upper_col="ci_upper",
    p_col="p_value",
    group_col="category",
    ratio_label="Hazard ratio",
    estimate_label="HR",
    ci_label="95% CI",
    p_label="p value",
    title="Feature-level hazard ratios",
)

Forest Estimates

Use this for Cox hazard ratios, odds ratios, risk ratios, subgroup analyses, or any estimate with lower and upper confidence bounds. Fyron defaults to a log x-axis because ratios are usually easier to read that way.

Required columns:

  • label_col: row label.
  • estimate_col: point estimate.
  • lower_col: lower confidence interval.
  • upper_col: upper confidence interval.

Optional columns:

  • p_col: p-value annotation.
  • p_adjust_col: adjusted p-value column when you want FDR-aware labels.
  • n_col: sample size appended to the row label.
  • table_columns: append estimate, CI, and p-value text to the y-axis labels.
python
forest_df = pd.DataFrame(
    {
        "subgroup": ["Low sarcopenia", "Intermediate", "High sarcopenia"],
        "hazard_ratio": [0.82, 1.0, 1.74],
        "ci_lower": [0.52, 0.73, 1.15],
        "ci_upper": [1.29, 1.37, 2.61],
        "p_value": [0.37, 0.99, 0.008],
        "n": [118, 142, 96],
    }
)

fig, ax = fp.plot_forest_estimates(
    data=forest_df,
    label_col="subgroup",
    estimate_col="hazard_ratio",
    lower_col="ci_lower",
    upper_col="ci_upper",
    p_col="p_value",
    n_col="n",
    title="Overall survival subgroup effects",
    xlabel="Hazard ratio",
    table_columns=True,
)

For a Fyron Cox result, use the Cox-specific convenience wrapper:

python
cox = fit_multivariate_cox(
    cohort,
    duration_col="time",
    event_col="event",
    covariates=["age", "stage", "risk_score"],
)

fig, ax = fp.plot_hazard_ratios(cox, title="Cox model hazard ratios")

Likert Scale Plots

Use plot_likert_scale for reader studies, usability surveys, annotation review, or clinical expert ratings. The input should be a long table with one row per response, or one row per response category with a count column.

python
fig, ax, likert_table = fp.plot_likert_scale(
    data=reader_survey,
    item_col="item",
    response_col="response",
    count_col="count",
    response_order=[
        "Disagree",
        "Neutral",
        "Agree",
        "Strongly agree",
    ],
    neutral_values=["Neutral"],
    palette="okabe_ito",
    title="Reader study agreement",
)

The returned table contains count, percent, side, and plot_value, so the plotted values can be exported with the figure.

Waffle Plots

Use plot_waffle for simple cohort composition, class balance, endpoint mix, or review status summaries. Waffle plots are strongest when the categories are few and the total has an intuitive interpretation, such as 100 squares.

python
fig, ax, waffle_table = fp.plot_waffle(
    data=cohort_mix,
    category_col="category",
    value_col="n",
    total=100,
    rows=10,
    palette="bf_vibrant_muted",
    title="Cohort composition",
)

The returned table includes source values, proportions, and allocated cell counts. For precise reporting, use the table values in text and the waffle plot as a visual summary.

Patterned Bar Plot

Patterned bars are useful when groups need to remain distinguishable without color.

python
fig, ax = fp.plot_patterned_bar(
    data=summary_df,
    x="endpoint",
    y="rate",
    group="cohort",
    error="ci_half_width",
    palette="bf_vibrant_muted",
    hatches=["", "///"],
    title="Endpoint rates by cohort",
    ylabel="Rate",
    annotate=True,
)

Expected input:

endpointcohortrateci_half_width
ResponseControl0.310.05
ResponseTreatment0.460.06
ToxicityControl0.120.03
ToxicityTreatment0.180.04

Model Metric Bars

plot_metric_bars accepts either a nested dictionary or a DataFrame.

python
fig, ax = fp.plot_metric_bars(
    metrics={
        "Random Forest": rf["metrics"],
        "XGBoost": xgb["metrics"],
    },
    metrics_to_plot=["auc", "sensitivity", "specificity", "f1"],
    title="Model comparison",
)

This is a compact way to compare clinically meaningful metrics instead of focusing only on accuracy.

Saving Figures

Every plotting helper accepts save_path.

python
fp.plot_calibration_curve(
    y_true=y_test,
    y_prob=rf["y_prob"],
    save_path="figures/calibration_rf.png",
)

You can also use regular Matplotlib calls:

python
fig, ax = fp.plot_roc_curve(
    y_true=y_test,
    y_prob=rf["y_prob"],
)
ax.set_title("Internal validation ROC")
fig.savefig("figures/roc_internal.svg", bbox_inches="tight")

Choosing The Right Plot

QuestionRecommended plot
Can the model rank high-risk patients above low-risk patients?ROC
Does the model perform well when the event is rare?Precision-recall
Are predicted probabilities trustworthy as risk estimates?Calibration
Which errors are clinically most common?Confusion matrix
Are marker values different across clinical groups?Grouped boxplot
Do score distributions have different shapes by cohort/event?Density plot
Are features redundant or strongly associated?Correlation heatmap
Do survival or event-free probability curves differ by group?Kaplan-Meier
Which model has better sensitivity/specificity tradeoffs?Metric bars
Do subgroup rates differ across endpoints?Patterned bar plot
Are event patients visibly shifted to higher scores?Risk score distribution
Which covariates push risk upward or downward?Signed feature weights
Which tests pass a p-value threshold?Significance bars
What are the effect estimates and confidence intervals?Forest estimates