Source code for bioviz.configs.grouped_bar_cfg

"""Configuration for grouped bar plots with optional confidence intervals."""

from typing import Annotated, Literal

from pydantic import BaseModel, Field


[docs] class GroupedBarConfig(BaseModel): """ Configuration for bar plots with optional grouping and confidence intervals. Supports: - Grouped bar plots (multiple bars per category) or simple bar plots - Horizontal or vertical orientation - Optional confidence intervals (Clopper-Pearson, bootstrap, or pre-computed) - Flexible styling and annotations Common use cases: - Gene prevalence comparisons across cohorts - Response rates by treatment arm - Mutation frequency at baseline vs progression - Any categorical comparison with or without error bars Attributes: category_col: Column name for categories (x-axis for vertical, y-axis for horizontal). group_col: Column for grouping within categories. None for simple (ungrouped) bars. value_col: Column name for bar values. orientation: 'horizontal' (barh) or 'vertical' (bar). ci_low_col: Column for lower CI bound. None to skip error bars. ci_high_col: Column for upper CI bound. None to skip error bars. k_col: Column for count (numerator) to compute CI from proportions. n_col: Column for total (denominator) to compute CI from proportions. ci_method: Method for CI computation: 'clopper', 'bootstrap', or 'none'. """ # ========================================================================== # Data column mappings # ========================================================================== category_col: Annotated[ str, Field( default="Category", description="Column for categories (e.g., Gene, Pathway, Treatment).", ), ] group_col: Annotated[ str | None, Field( default="Group", description="Column for grouping within categories. None for ungrouped bars.", ), ] value_col: Annotated[ str, Field(default="value", description="Column for bar values (heights/lengths)."), ] # ========================================================================== # Orientation # ========================================================================== orientation: Annotated[ Literal["horizontal", "vertical"], Field( default="horizontal", description="Bar orientation: 'horizontal' (barh) or 'vertical' (bar).", ), ] # ========================================================================== # CI columns (optional - None means no error bars) # ========================================================================== ci_low_col: Annotated[ str | None, Field( default=None, description="Column for lower CI bound. None to skip error bars.", ), ] ci_high_col: Annotated[ str | None, Field( default=None, description="Column for upper CI bound. None to skip error bars.", ), ] # ========================================================================== # CI computation from counts (for proportion CIs) # ========================================================================== k_col: Annotated[ str | None, Field( default=None, description="Column for count (numerator) to compute proportion CI.", ), ] n_col: Annotated[ str | None, Field( default=None, description="Column for total (denominator) to compute proportion CI.", ), ] ci_method: Annotated[ Literal["clopper-pearson", "bootstrap", "none"], Field( default="none", description="CI method: 'clopper-pearson', 'bootstrap', or 'none'.", ), ] alpha: Annotated[ float, Field(default=0.05, description="Significance level for CI (0.05 = 95% CI)."), ] n_boot: Annotated[ int, Field( default=10000, description="Number of bootstrap samples (if method='bootstrap').", ), ] random_state: Annotated[ int | None, Field(default=12345, description="Random seed for bootstrap reproducibility."), ] # ========================================================================== # Group configuration (for grouped bars) # ========================================================================== group_order: Annotated[ list[str] | None, Field(default=None, description="Order of groups within each category."), ] group_colors: Annotated[ dict[str, str] | None, Field(default=None, description="Mapping of group name to color."), ] group_labels: Annotated[ dict[str, str] | None, Field( default=None, description="Mapping of group name to display label (e.g., with n=X).", ), ] # ========================================================================== # Figure settings # ========================================================================== figsize: Annotated[ tuple[float, float] | None, Field( default=None, description="Figure size (width, height). Auto-computed if None.", ), ] bar_width: Annotated[ float, Field(default=0.28, description="Width/height of each bar."), ] group_spacing: Annotated[ float, Field(default=1.5, description="Spacing between category groups."), ] # ========================================================================== # Labels and title # ========================================================================== title: Annotated[ str | None, Field(default=None, description="Plot title."), ] title_fontweight: Annotated[ str, Field( default="normal", description="Font weight for title ('normal', 'bold', 'light', etc.).", ), ] xlabel: Annotated[ str | None, Field(default=None, description="X-axis label."), ] ylabel: Annotated[ str | None, Field(default=None, description="Y-axis label."), ] title_fontsize: Annotated[ float | None, Field( default=None, description="Font size for title. None uses rcParams['axes.titlesize'].", ), ] xlabel_fontsize: Annotated[ float | None, Field( default=None, description="Font size for x-axis label. None uses rcParams['axes.labelsize'].", ), ] ylabel_fontsize: Annotated[ float | None, Field( default=None, description="Font size for y-axis label. None uses rcParams['axes.labelsize'].", ), ] tick_fontsize: Annotated[ float | None, Field( default=None, description="Font size for tick labels. None uses rcParams['xtick.labelsize'].", ), ] # ========================================================================== # Annotations # ========================================================================== show_annotations: Annotated[ bool, Field(default=True, description="Whether to show value annotations on bars."), ] annot_fontsize: Annotated[ float | None, Field( default=None, description="Font size for value annotations. None uses rcParams['font.size'] * 0.9.", ), ] annot_format: Annotated[ str, Field(default="{:.1f}%", description="Format string for annotations."), ] annot_offset: Annotated[ float, Field(default=0.8, description="Offset for annotations from bar end (data units)."), ] annot_padding: Annotated[ float, Field( default=5.0, description="Extra padding (data units) added to axis limit to ensure annotations fit.", ), ] # ========================================================================== # Legend # ========================================================================== show_legend: Annotated[ bool, Field(default=True, description="Whether to show legend."), ] legend_loc: Annotated[ str, Field(default="upper left", description="Legend location."), ] legend_bbox_to_anchor: Annotated[ tuple[float, float] | None, Field(default=(1.02, 1), description="Legend bbox_to_anchor. None for auto."), ] legend_fontsize: Annotated[ float | None, Field( default=None, description="Font size for legend. None uses rcParams['legend.fontsize'].", ), ] legend_title: Annotated[ str | None, Field(default=None, description="Legend title."), ] # ========================================================================== # Bar styling # ========================================================================== default_color: Annotated[ str, Field( default="#1f77b4", description="Default bar color when no group_colors specified.", ), ] bar_edgecolor: Annotated[ str, Field(default="white", description="Edge color for bars."), ] bar_linewidth: Annotated[ float, Field(default=0.9, description="Line width for bar edges."), ] # ========================================================================== # Error bar styling # ========================================================================== capsize: Annotated[ float, Field(default=4, description="Size of error bar caps."), ] error_color: Annotated[ str, Field(default="black", description="Color for error bars."), ] # ========================================================================== # Axis limits # ========================================================================== value_min: Annotated[ float | None, Field(default=0, description="Minimum value axis limit. None for auto."), ] value_max: Annotated[ float | None, Field(default=None, description="Maximum value axis limit. None for auto."), ] value_padding_pct: Annotated[ float, Field(default=0.15, description="Padding as fraction of max value for auto limit."), ] xlim: Annotated[ tuple[float, float] | None, Field( default=None, description="Explicit (min, max) for x-axis. Overrides value_min/max for horizontal.", ), ] ylim: Annotated[ tuple[float, float] | None, Field( default=None, description="Explicit (min, max) for y-axis. Overrides value_min/max for vertical.", ), ] xticks: Annotated[ list[float] | None, Field(default=None, description="Custom x-tick positions. None uses auto ticks."), ] yticks: Annotated[ list[float] | None, Field(default=None, description="Custom y-tick positions. None uses auto ticks."), ] xtick_labels: Annotated[ list[str] | None, Field(default=None, description="Custom x-tick labels. Must match xticks length."), ] ytick_labels: Annotated[ list[str] | None, Field(default=None, description="Custom y-tick labels. Must match yticks length."), ] # ========================================================================== # Category axis settings # ========================================================================== invert_categories: Annotated[ bool, Field( default=True, description="Invert category axis (first category at top for horizontal).", ), ] model_config = {"extra": "forbid"}