Repository: optuna/optuna

Issue:
matplotlib plot_rank bug
### Expected behavior

The `plot_rank`, with matplotlib backend raises. The other plot functions with matplotlib backend work well.

### Environment

- Optuna version: 3.4.0
- Python version: 3.8.15
- OS: Linux-6.1.0-12-amd64-x86_64-with-glibc2.10
- (Optional) Other libraries and their versions:


### Error messages, stack traces, or logs

```shell
NameError                                 Traceback (most recent call last)
Cell In[29], line 1
----> 1 plot_rank(study)
      2 plt.show()

File /opt/conda/envs/deep-learning-2023.01/lib/python3.8/site-packages/optuna/_experimental.py:74, in experimental_func.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
     63 @functools.wraps(func)
     64 def wrapper(*args: Any, **kwargs: Any) -> "FT":
     65     warnings.warn(
     66         "{} is experimental (supported from v{}). "
     67         "The interface can change in the future.".format(
   (...)
     71         stacklevel=2,
     72     )
---> 74     return func(*args, **kwargs)

File /opt/conda/envs/deep-learning-2023.01/lib/python3.8/site-packages/optuna/visualization/matplotlib/_rank.py:97, in plot_rank(study, params, target, target_name)
     92 _imports.check()
     93 _logger.warning(
     94     "Output figures of this Matplotlib-based `plot_rank` function would be different from "
     95     "those of the Plotly-based `plot_rank`."
     96 )
---> 97 info = _get_rank_info(study, params, target, target_name)
     98 return _get_rank_plot(info)

File /opt/conda/envs/deep-learning-2023.01/lib/python3.8/site-packages/optuna/visualization/_rank.py:172, in _get_rank_info(study, params, target, target_name)
    170 raw_ranks = _get_order_with_same_order_averaging(target_values)
    171 color_idxs = raw_ranks / (len(trials) - 1) if len(trials) >= 2 else np.array([0.5])
--> 172 colors = _convert_color_idxs_to_scaled_rgb_colors(color_idxs)
    174 sub_plot_infos: list[list[_RankSubplotInfo]]
    175 if len(params) == 2:

File /opt/conda/envs/deep-learning-2023.01/lib/python3.8/site-packages/optuna/visualization/_rank.py:426, in _convert_color_idxs_to_scaled_rgb_colors(color_idxs)
    424 colormap = "RdYlBu_r"
    425 # sample_colorscale requires plotly >= 5.0.0.
--> 426 labeled_colors = plotly.colors.sample_colorscale(colormap, color_idxs)
    427 scaled_rgb_colors = np.array([plotly.colors.unlabel_rgb(cl) for cl in labeled_colors])
    428 return scaled_rgb_colors

NameError: name 'plotly' is not defined
```


### Steps to reproduce

1. Create and launch a study
2. Try to use `plot_rank` with matplotlib backend
```python
from optuna.visualization.matplotlib import plot_rank
import matplotlib.pyplot as plt

plot_rank(study)
plt.show()
```


### Additional context (optional)

_No response_


Discussion:
@guilllaumeB Thank you for your report 👍  We'll fix this issue by the next release.
I'm considering working on this issue.

Resolving patch:
diff --git a/optuna/visualization/_rank.py b/optuna/visualization/_rank.py
--- a/optuna/visualization/_rank.py
+++ b/optuna/visualization/_rank.py
@@ -18,13 +18,19 @@
 from optuna.visualization._utils import _check_plot_args
 from optuna.visualization._utils import _is_log_scale
 from optuna.visualization._utils import _is_numerical
+from optuna.visualization.matplotlib._matplotlib_imports import _imports as matplotlib_imports
 
 
-if _imports.is_successful():
+plotly_is_available = _imports.is_successful()
+if plotly_is_available:
     from optuna.visualization._plotly_imports import go
     from optuna.visualization._plotly_imports import make_subplots
     from optuna.visualization._plotly_imports import plotly
     from optuna.visualization._plotly_imports import Scatter
+if matplotlib_imports.is_successful():
+    # TODO(c-bata): Refactor to remove matplotlib and plotly dependencies in `_get_rank_info()`.
+    # See https://github.com/optuna/optuna/pull/5133#discussion_r1414761672 for the discussion.
+    from optuna.visualization.matplotlib._matplotlib_imports import plt as matplotlib_plt
 
 _logger = get_logger(__name__)
 
@@ -212,7 +218,7 @@ def _get_rank_subplot_info(
         if constraints is not None and any([x > 0.0 for x in constraints]):
             infeasible_trial_ids.append(i)
 
-    colors[infeasible_trial_ids] = plotly.colors.hex_to_rgb("#cccccc")
+    colors[infeasible_trial_ids] = (204, 204, 204)  # equal to "#CCCCCC"
 
     filtered_ids = [
         i
@@ -422,7 +428,13 @@ def _get_rank_plot(
 
 def _convert_color_idxs_to_scaled_rgb_colors(color_idxs: np.ndarray) -> np.ndarray:
     colormap = "RdYlBu_r"
-    # sample_colorscale requires plotly >= 5.0.0.
-    labeled_colors = plotly.colors.sample_colorscale(colormap, color_idxs)
-    scaled_rgb_colors = np.array([plotly.colors.unlabel_rgb(cl) for cl in labeled_colors])
-    return scaled_rgb_colors
+    if plotly_is_available:
+        # sample_colorscale requires plotly >= 5.0.0.
+        labeled_colors = plotly.colors.sample_colorscale(colormap, color_idxs)
+        scaled_rgb_colors = np.array([plotly.colors.unlabel_rgb(cl) for cl in labeled_colors])
+        return scaled_rgb_colors
+    else:
+        cmap = matplotlib_plt.get_cmap(colormap)
+        colors = cmap(color_idxs)[:, :3]  # Drop alpha values.
+        rgb_colors = np.asarray(colors * 255, dtype=int)
+        return rgb_colors


Test patch: