Skip to content

Heatmap Scored Strategy

heatmap_scored_strategy

Heatmap Scored Strategy - Completeness and Compliance Scoring.

This module implements the HeatmapScoredStrategy for creating heatmap visualizations showing scoring/completeness metrics across samples and various categories (compounds, pathways, agencies).

Classes:

Name Description
HeatmapScoredStrategy

Strategy for scored heatmap generation with completeness metrics.

Notes
  • Supports KO completeness scoring (unique KO counts)
  • Supports compound compliance scoring (regulatory agencies)
  • Scores displayed as percentages (0-100%)

For supported use cases, refer to the official documentation.

Classes

HeatmapScoredStrategy

HeatmapScoredStrategy(config: Dict[str, Any])

Bases: BasePlotStrategy

Strategy for scored heatmap completeness/compliance visualizations.

This strategy creates heatmaps showing scoring metrics (0-100%) where rows represent samples, columns represent categories, and cell values represent completeness or compliance scores.

Parameters:

Name Type Description Default
config Dict[str, Any]

Complete configuration from YAML file.

required

Attributes:

Name Type Description
data_config Dict[str, Any]

Data processing configuration.

plotly_config Dict[str, Any]

Plotly-specific configuration.

scoring_mode str

Scoring algorithm: 'ko_completeness' or 'compound_compliance'.

category_column str

Column containing categories (e.g., 'Pathway', 'referenceAG').

sample_column str

Column containing sample identifiers (default: 'Sample').

value_column str

Column for aggregation ('KO' for completeness, 'compoundname' for compliance).

Methods:

Name Description
validate_data

Validate input data for heatmap requirements

process_data

Process data and calculate scoring matrix

create_figure

Create heatmap figure from scoring matrix

Notes
  • Supports two scoring modes: KO completeness and compound compliance
  • Scores displayed as percentages (0-100%)
  • Heatmap uses color scale to represent score intensity

Initialize strategy with configuration.

Parameters:

Name Type Description Default
config Dict[str, Any]

Complete configuration from YAML file.

required
Source code in src/domain/plot_strategies/charts/heatmap_scored_strategy.py
def __init__(self, config: Dict[str, Any]):
    """
    Initialize strategy with configuration.

    Parameters
    ----------
    config : Dict[str, Any]
        Complete configuration from YAML file.
    """
    super().__init__(config)
    self.data_config = config.get("data", {})
    self.plotly_config = self.viz_config.get("plotly", {})

    # Extract strategy-specific parameters
    self.scoring_mode = self.plotly_config.get("scoring_mode", "ko_completeness")
    self.category_column = self.plotly_config.get("category_column", "Pathway")
    self.sample_column = self.plotly_config.get("sample_column", "Sample")
    self.value_column = self.plotly_config.get("value_column", "KO")

    logger.info(
        f"HeatmapScoredStrategy initialized for {self.metadata.get('use_case_id', 'unknown')}: "
        f"mode='{self.scoring_mode}', category='{self.category_column}', "
        f"sample='{self.sample_column}', value='{self.value_column}'"
    )
Functions
validate_data
validate_data(df: DataFrame) -> None

Validate input data for heatmap requirements.

Parameters:

Name Type Description Default
df DataFrame

Input data to validate.

required

Raises:

Type Description
ValueError

If DataFrame is empty, required columns missing, or no valid samples/categories found.

Source code in src/domain/plot_strategies/charts/heatmap_scored_strategy.py
def validate_data(self, df: pd.DataFrame) -> None:
    """
    Validate input data for heatmap requirements.

    Parameters
    ----------
    df : pd.DataFrame
        Input data to validate.

    Raises
    ------
    ValueError
        If DataFrame is empty, required columns missing, or no valid
        samples/categories found.
    """
    logger.debug(
        f"Validating data - Shape: {df.shape}, " f"Columns: {df.columns.tolist()}"
    )

    # Check DataFrame not empty
    if df.empty:
        raise ValueError("Input DataFrame is empty")

    # Determine required columns based on scoring mode
    if self.scoring_mode == "compound_compliance":
        required_cols = [
            self.sample_column,
            self.category_column,
            self.value_column,  # compoundname
        ]
    else:  # ko_completeness
        required_cols = [
            self.sample_column,
            self.category_column,
            self.value_column,  # KO
        ]

    # Check required columns exist
    missing_cols = [col for col in required_cols if col not in df.columns]
    if missing_cols:
        raise ValueError(
            f"Missing required columns: {missing_cols}. "
            f"Available columns: {df.columns.tolist()}"
        )

    # Drop rows with null values in critical columns
    df_clean = df.dropna(subset=required_cols)
    if df_clean.empty:
        raise ValueError(
            f"No valid data after removing nulls in columns: {required_cols}"
        )

    # Check at least one sample and category
    n_samples = df_clean[self.sample_column].nunique()
    n_categories = df_clean[self.category_column].nunique()

    if n_samples == 0:
        raise ValueError(f"No samples found in column '{self.sample_column}'")
    if n_categories == 0:
        raise ValueError(f"No categories found in column '{self.category_column}'")

    logger.info(
        f"✓ Data validation passed - "
        f"{n_samples} samples, {n_categories} categories, "
        f"{len(df_clean)} records"
    )
process_data
process_data(df: DataFrame) -> pd.DataFrame

Process data and calculate scoring matrix.

Implements scoring algorithms based on configured mode: KO completeness or compound compliance.

Parameters:

Name Type Description Default
df DataFrame

Input data with required columns.

required

Returns:

Type Description
DataFrame

Heatmap matrix with samples as rows, categories as columns, and scores (0-100%) as values.

Source code in src/domain/plot_strategies/charts/heatmap_scored_strategy.py
def process_data(self, df: pd.DataFrame) -> pd.DataFrame:
    """
    Process data and calculate scoring matrix.

    Implements scoring algorithms based on configured mode: KO completeness
    or compound compliance.

    Parameters
    ----------
    df : pd.DataFrame
        Input data with required columns.

    Returns
    -------
    pd.DataFrame
        Heatmap matrix with samples as rows, categories as columns,
        and scores (0-100%) as values.
    """
    logger.info(f"Processing data with {self.scoring_mode} scoring mode...")

    # Clean data: remove nulls and empty strings
    df_clean = df.dropna(
        subset=[self.sample_column, self.category_column, self.value_column]
    ).copy()

    # Remove empty strings in value column
    df_clean = df_clean[df_clean[self.value_column] != ""]

    logger.debug(
        f"After cleaning: {len(df_clean)} records "
        f"({len(df) - len(df_clean)} removed)"
    )

    if self.scoring_mode == "compound_compliance":
        heatmap_matrix = self._calculate_compound_compliance(df_clean)
    else:  # ko_completeness (default)
        heatmap_matrix = self._calculate_ko_completeness(df_clean)

    logger.info(
        f"✓ Scoring matrix created - "
        f"Shape: {heatmap_matrix.shape}, "
        f"Score range: [{heatmap_matrix.min().min():.1f}%, "
        f"{heatmap_matrix.max().max():.1f}%]"
    )

    return heatmap_matrix
create_figure
create_figure(processed_df: DataFrame) -> go.Figure

Create heatmap figure from scoring matrix.

Parameters:

Name Type Description Default
processed_df DataFrame

Scoring matrix (samples × categories).

required

Returns:

Type Description
Figure

Configured Plotly heatmap.

Source code in src/domain/plot_strategies/charts/heatmap_scored_strategy.py
def create_figure(self, processed_df: pd.DataFrame) -> go.Figure:
    """
    Create heatmap figure from scoring matrix.

    Parameters
    ----------
    processed_df : pd.DataFrame
        Scoring matrix (samples × categories).

    Returns
    -------
    go.Figure
        Configured Plotly heatmap.
    """
    logger.debug("Creating heatmap figure...")

    # Extract chart configuration
    chart_config = self.plotly_config.get("chart", {})
    layout_config = self.plotly_config.get("layout", {})

    # Get title configuration
    title_config = chart_config.get("title", {})
    title_text = title_config.get("text", "Completeness Scorecard")

    # Get axis labels
    x_label = chart_config.get("xaxis", {}).get("title", "Category")
    y_label = chart_config.get("yaxis", {}).get("title", "Sample")
    color_label = chart_config.get("color_label", "Completeness (%)")

    # Get text display settings
    show_values = chart_config.get("show_values", True)
    text_auto = chart_config.get("text_auto", ".1f") if show_values else False

    # Get color scale
    color_scale = chart_config.get("color_continuous_scale", "Greens")

    # Create heatmap using plotly express
    fig = px.imshow(
        processed_df,
        labels=dict(x=x_label, y=y_label, color=color_label),
        text_auto=text_auto,
        aspect="auto",
        color_continuous_scale=color_scale,
        zmin=0,
        zmax=100,
    )

    # Apply layout configuration
    template = layout_config.get("template", "simple_white")
    height = layout_config.get("height", 600)
    width = layout_config.get("width", 1000)

    # Get margin configuration
    margin_config = layout_config.get("margin", {})
    margin = dict(
        l=margin_config.get("l", 80),
        r=margin_config.get("r", 100),
        t=margin_config.get("t", 100),
        b=margin_config.get("b", 120),
    )

    # Get axis angles
    xaxis_tickangle = chart_config.get("xaxis_tickangle", -45)
    yaxis_tickangle = chart_config.get("yaxis_tickangle", 0)

    # Get colorbar configuration
    colorbar_config = chart_config.get("colorbar", {})
    colorbar_title = colorbar_config.get("title", color_label)

    # Get title display setting
    show_title = title_config.get("show", True)

    # Build layout update dict
    layout_update = {
        "height": height,
        "template": template,
        "xaxis_tickangle": xaxis_tickangle,
        "yaxis_tickangle": yaxis_tickangle,
        "margin": margin,
        "coloraxis_colorbar": dict(title=colorbar_title),
    }

    # Add title if enabled
    if show_title and title_text:
        layout_update["title"] = {
            "text": title_text,
            "x": title_config.get("x", 0.5),
            "xanchor": title_config.get("xanchor", "center"),
        }

    # Only set width if not using autosize
    if not layout_config.get("autosize", False):
        layout_update["width"] = width

    fig.update_layout(**layout_update)

    # Update text font size if configured
    text_font_size = chart_config.get("text_font_size", 10)
    fig.update_traces(textfont_size=text_font_size)

    # Set text color based on value for better contrast
    # Black text for light cells, white for dark cells
    text_font_color = chart_config.get("text_font_color", "black")
    fig.update_traces(textfont_color=text_font_color)

    logger.info(
        f"✓ Heatmap figure created - "
        f"Size: {width}×{height}px, "
        f"Template: {template}"
    )

    return fig
apply_filters
apply_filters(df: DataFrame, filters: Optional[Dict[str, Any]] = None) -> pd.DataFrame

Apply filters to data.

This is a common implementation that can be overridden by subclasses if needed.

Parameters:

Name Type Description Default
df DataFrame

Data to filter.

required
filters Optional[Dict[str, Any]]

Filter specifications.

None

Returns:

Type Description
DataFrame

Filtered data.

Source code in src/domain/plot_strategies/base/base_plot_strategy.py
def apply_filters(
    self, df: pd.DataFrame, filters: Optional[Dict[str, Any]] = None
) -> pd.DataFrame:
    """
    Apply filters to data.

    This is a common implementation that can be overridden
    by subclasses if needed.

    Parameters
    ----------
    df : pd.DataFrame
        Data to filter.
    filters : Optional[Dict[str, Any]], default=None
        Filter specifications.

    Returns
    -------
    pd.DataFrame
        Filtered data.
    """
    import logging

    logger = logging.getLogger(__name__)

    if not filters:
        logger.debug("No filters provided, returning original data")
        return df

    logger.info(
        f"Applying filters - Input shape: {df.shape}, "
        f"Columns: {df.columns.tolist()}"
    )
    logger.info(f"Filters to apply: {filters}")

    filtered_df = df.copy()

    # Get filter configurations
    filter_configs = self.config.get("filters", [])

    for filter_config in filter_configs:
        filter_id = filter_config.get("filter_id")
        filter_type = filter_config.get("type")

        if filter_id not in filters:
            continue

        filter_value = filters[filter_id]
        data_binding = filter_config.get("data_binding", {})
        column = data_binding.get("column")

        if not column or column not in filtered_df.columns:
            logger.warning(
                f"Filter '{filter_id}': Column '{column}' not found. "
                f"Available: {filtered_df.columns.tolist()}"
            )
            continue

        # Apply range filter
        if filter_type == "range" and isinstance(filter_value, list):
            min_val, max_val = filter_value
            logger.info(
                f"Applying range filter on '{column}': " f"[{min_val}, {max_val}]"
            )
            filtered_df = filtered_df[
                (filtered_df[column] >= min_val) & (filtered_df[column] <= max_val)
            ]
            logger.info(f"After filter: {len(filtered_df)} rows remaining")

    logger.info(f"Final filtered shape: {filtered_df.shape}")
    return filtered_df
apply_customizations
apply_customizations(fig: Figure, customizations: Optional[Any] = None) -> go.Figure

Apply custom styling to figure.

This is a hook for future customization features (FLEXIVEL and FLEXIVEL2).

Parameters:

Name Type Description Default
fig Figure

Base figure.

required
customizations Optional[Any]

Customization specifications.

None

Returns:

Type Description
Figure

Customized figure.

Source code in src/domain/plot_strategies/base/base_plot_strategy.py
def apply_customizations(
    self, fig: go.Figure, customizations: Optional[Any] = None
) -> go.Figure:
    """
    Apply custom styling to figure.

    This is a hook for future customization features
    (FLEXIVEL and FLEXIVEL2).

    Parameters
    ----------
    fig : go.Figure
        Base figure.
    customizations : Optional[Any], default=None
        Customization specifications.

    Returns
    -------
    go.Figure
        Customized figure.
    """
    # Hook for future implementation
    return fig
generate_plot
generate_plot(data: DataFrame, filters: Optional[Dict[str, Any]] = None, customizations: Optional[Any] = None) -> go.Figure

Generate complete plot (Template Method).

This method orchestrates the entire plot generation process: 1. Validate input data 2. Process data 3. Apply filters 4. Create figure 5. Apply customizations

Parameters:

Name Type Description Default
data DataFrame

Input data.

required
filters Optional[Dict[str, Any]]

Filters to apply.

None
customizations Optional[Any]

Customizations to apply.

None

Returns:

Type Description
Figure

Complete Plotly figure.

Raises:

Type Description
ValueError

If validation fails.

Source code in src/domain/plot_strategies/base/base_plot_strategy.py
def generate_plot(
    self,
    data: pd.DataFrame,
    filters: Optional[Dict[str, Any]] = None,
    customizations: Optional[Any] = None,
) -> go.Figure:
    """
    Generate complete plot (Template Method).

    This method orchestrates the entire plot generation process:
    1. Validate input data
    2. Process data
    3. Apply filters
    4. Create figure
    5. Apply customizations

    Parameters
    ----------
    data : pd.DataFrame
        Input data.
    filters : Optional[Dict[str, Any]], default=None
        Filters to apply.
    customizations : Optional[Any], default=None
        Customizations to apply.

    Returns
    -------
    go.Figure
        Complete Plotly figure.

    Raises
    ------
    ValueError
        If validation fails.
    """
    # 1. Validate
    self.validate_data(data)

    # 2. Process
    processed_df = self.process_data(data)

    # 3. Filter
    filtered_df = self.apply_filters(processed_df, filters)

    # 4. Create figure
    figure = self.create_figure(filtered_df)

    # 5. Apply customizations (hook for future)
    figure = self.apply_customizations(figure, customizations)

    return figure