Source code for optuna.visualization._param_importances

from __future__ import annotations

from typing import NamedTuple
from typing import TYPE_CHECKING

import optuna
from optuna.logging import get_logger
from optuna.trial import TrialState
from optuna.visualization._plotly_imports import _imports
from optuna.visualization._utils import _check_plot_args
from optuna.visualization._utils import _filter_nonfinite


if TYPE_CHECKING:
    from collections.abc import Callable

    from optuna.distributions import BaseDistribution
    from optuna.importance._base import BaseImportanceEvaluator
    from optuna.study import Study
    from optuna.trial import FrozenTrial


if _imports.is_successful():
    from optuna.visualization._plotly_imports import go


logger = get_logger(__name__)


class _ImportancesInfo(NamedTuple):
    importance_values: list[float]
    param_names: list[str]
    importance_labels: list[str]
    target_name: str


def _get_importances_info(
    study: Study,
    evaluator: BaseImportanceEvaluator | None,
    params: list[str] | None,
    target: Callable[[FrozenTrial], float] | None,
    target_name: str,
) -> _ImportancesInfo:
    _check_plot_args(study, target, target_name)

    trials = _filter_nonfinite(
        study.get_trials(deepcopy=False, states=(TrialState.COMPLETE,)), target=target
    )

    if len(trials) == 0:
        logger.warning("Study instance does not contain completed trials.")
        return _ImportancesInfo(
            importance_values=[],
            param_names=[],
            importance_labels=[],
            target_name=target_name,
        )

    importances = optuna.importance.get_param_importances(
        study, evaluator=evaluator, params=params, target=target
    )

    importances = dict(reversed(list(importances.items())))
    importance_values = list(importances.values())
    param_names = list(importances.keys())
    importance_labels = [f"{val:.2f}" if val >= 0.01 else "<0.01" for val in importance_values]

    return _ImportancesInfo(
        importance_values=importance_values,
        param_names=param_names,
        importance_labels=importance_labels,
        target_name=target_name,
    )


def _get_importances_infos(
    study: Study,
    evaluator: BaseImportanceEvaluator | None,
    params: list[str] | None,
    target: Callable[[FrozenTrial], float] | None,
    target_name: str,
) -> tuple[_ImportancesInfo, ...]:
    metric_names = study.metric_names
    if target or not study._is_multi_objective():
        target_name = metric_names[0] if metric_names is not None and not target else target_name
        importances_infos: tuple[_ImportancesInfo, ...] = (
            _get_importances_info(
                study,
                evaluator,
                params,
                target=target,
                target_name=target_name,
            ),
        )

    else:
        n_objectives = len(study.directions)
        target_names = (
            metric_names
            if metric_names is not None
            else (f"{target_name} {objective_id}" for objective_id in range(n_objectives))
        )

        importances_infos = tuple(
            _get_importances_info(
                study,
                evaluator,
                params,
                target=lambda t: t.values[objective_id],
                target_name=target_name,
            )
            for objective_id, target_name in enumerate(target_names)
        )

    return importances_infos


[docs] def plot_param_importances( study: Study, evaluator: BaseImportanceEvaluator | None = None, params: list[str] | None = None, *, target: Callable[[FrozenTrial], float] | None = None, target_name: str = "Objective Value", ) -> "go.Figure": """Plot hyperparameter importances (:class:`~optuna.importance.PedAnovaImportanceEvaluator` by default). .. seealso:: This function visualizes the results of :func:`optuna.importance.get_param_importances`. Args: study: An optimized study. evaluator: An importance evaluator object that specifies which algorithm to base the importance assessment on. Defaults to :class:`~optuna.importance.PedAnovaImportanceEvaluator`. For details on this evaluator, please refer to the following papers: - `PED-ANOVA: Efficiently Quantifying Hyperparameter Importance in Arbitrary Subspaces <https://arxiv.org/abs/2304.10255>`__ (IJCAI 2023) - `Conditional PED-ANOVA: Hyperparameter Importance in Hierarchical & Dynamic Search Spaces <https://arxiv.org/abs/2601.20800>`__ (KDD 2026) When using this evaluator in your project, please consider citing both papers. .. note:: Optuna Dashboard also uses :class:`~optuna.importance.PedAnovaImportanceEvaluator`, the default importance evaluator. .. NOTE(nabe): Since Optuna Dashboard implicitly uses the default importance evaluator and does not have a standalone documentation for its visualization, the Optuna documentation should cover the used evaluator explicitly. Otherwise, users need to read the corresponding Optuna Dashboard code to confirm. params: A list of names of parameters to assess. If :obj:`None`, :class:`~optuna.importance.PedAnovaImportanceEvaluator` assesses all parameters that appear in completed trials, including conditional parameters, while other evaluators assess parameters present in all completed trials. If specified, only the specified parameters are assessed. When using :class:`~optuna.importance.PedAnovaImportanceEvaluator`, each specified parameter must appear in at least one completed trial. When using other evaluators, at least one completed trial must contain all specified parameters. target: A function that returns the value used to evaluate and display importances. If :obj:`None`, objective values are used for single-objective optimization. For multi-objective optimization, all objectives will be plotted if ``target`` is :obj:`None`. Specify ``target``, for example ``target=lambda t: t.values[0]``, to plot importances for a specific objective. target_name: Target's name to display on the legend. Names set via :meth:`~optuna.study.Study.set_metric_names` will be used if ``target`` is :obj:`None`, overriding this argument. Returns: A :class:`plotly.graph_objects.Figure` object. """ _imports.check() importances_infos = _get_importances_infos(study, evaluator, params, target, target_name) return _get_importances_plot(importances_infos, study)
def _get_importances_plot(infos: tuple[_ImportancesInfo, ...], study: Study) -> "go.Figure": layout = go.Layout( title="Hyperparameter Importances", xaxis={"title": "Hyperparameter Importance"}, yaxis={"title": "Hyperparameter"}, ) data: list[go.Bar] = [] for info in infos: if not info.importance_values: continue data.append( go.Bar( x=info.importance_values, y=info.param_names, name=info.target_name, text=info.importance_labels, textposition="outside", cliponaxis=False, # Ensure text is not clipped. hovertemplate=_get_hover_template(info, study), orientation="h", ) ) return go.Figure(data, layout) def _get_distribution(param_name: str, study: Study) -> BaseDistribution: for trial in study.trials: if param_name in trial.distributions: return trial.distributions[param_name] assert False def _make_hovertext(param_name: str, importance: float, study: Study) -> str: class_name = _get_distribution(param_name, study).__class__.__name__ return f"{param_name} ({class_name}): {importance}<extra></extra>" def _get_hover_template(importances_info: _ImportancesInfo, study: Study) -> list[str]: return [ _make_hovertext(param_name, importance, study) for param_name, importance in zip( importances_info.param_names, importances_info.importance_values ) ]