"""
Plotting function for Pareto frontiers colored by a continuous hyperparameter.
"""
# =============================================================================
# METADATA
# =============================================================================
__author__ = "Yeremia Gunawan Adhisantoso"
__email__ = "adhisant@tnt.uni-hannover.de"
__license__ = "Clear BSD"
__version__ = "1.0.0"
# =============================================================================
# STANDARD LIBRARY IMPORTS
# =============================================================================
import itertools
import logging
import pathlib
import typing as t
# =============================================================================
# THIRD-PARTY IMPORTS
# =============================================================================
import pandas as pd
import plotly.graph_objects as go
from omegaconf import DictConfig
from optuna.study import StudyDirection
# =============================================================================
# LOCAL APPLICATION IMPORTS
# =============================================================================
from .utils import DEFAULT_PLOT_CONFIG
[docs]
def plot_pareto_continuous_color(
#? --- Data Inputs ---
metric_df: pd.DataFrame,
param_df: pd.DataFrame,
#? --- Plotting Configuration ---
objective_metrics: t.List[str],
study_directions: t.List[StudyDirection],
output_path: pathlib.Path,
continuous_params_map: t.Dict[str, str],
plot_cfg: t.Union[DictConfig, t.Dict],
) -> None:
""" Generates Pareto fronts of 2 objectives, colored by a continuous parameter. """
if not continuous_params_map:
logging.warning("Skipping continuous Pareto plots: no continuous params found.")
return
logging.info("--- Starting Continuous Pareto Plot Generation ---")
plot_output_path = output_path / "continuous_pareto"
cfg = {**DEFAULT_PLOT_CONFIG, **plot_cfg}
combined_df = pd.concat([metric_df, param_df], axis=1)
directions_map = {name: d for name, d in zip(objective_metrics, study_directions)}
for metric_x, metric_y in itertools.combinations(objective_metrics, 2):
for param_name, param_type in continuous_params_map.items():
pareto_df = find_pareto_frontier_fast(combined_df, metric_x, metric_y, directions_map[metric_x], directions_map[metric_y])
fig = go.Figure()
fig.add_trace(go.Scatter(
x=combined_df[metric_x], y=combined_df[metric_y], mode='markers',
marker=dict(color='grey', opacity=0.2, size=4), name='All Trials',
customdata=combined_df.index,
hovertemplate=f'<b>Trial</b>: %{{customdata}}<br><b>{metric_x}</b>: %{{x}}<br><b>{metric_y}</b>: %{{y}}<extra></extra>'
))
fig.add_trace(go.Scatter(
x=pareto_df[metric_x], y=pareto_df[metric_y], mode='lines+markers',
marker=dict(color=pareto_df[param_name], colorscale='Cividis', showscale=True,
colorbar_title=param_name.replace('/', '_'), size=10),
line=dict(width=3), name='Pareto Frontier', customdata=pareto_df.index,
hovertemplate=f'<b>Trial</b>: %{{customdata}}<br><b>{param_name}</b>: %{{marker.color}}<br><b>{metric_x}</b>: %{{x}}<br><b>{metric_y}</b>: %{{y}}<extra></extra>'
))
fig.update_layout(
title=f'Pareto: {metric_x} vs. {metric_y}<br><sub>Colored by {param_name}</sub>',
xaxis_title=f"{metric_x} ({directions_map[metric_x].name})",
yaxis_title=f"{metric_y} ({directions_map[metric_y].name})",
template=cfg["template"]
)
param_output_path = plot_output_path / param_name
param_output_path.mkdir(parents=True, exist_ok=True)
fname = f"{metric_x}_vs_{metric_y}.html".replace('/', '_')
fig.write_html(param_output_path / fname)