Repository: scikit-learn/scikit-learn
File: sklearn/inspection/_plot/decision_boundary.py (Python)

Code before:
    xx0 : ndarray of shape (grid_resolution, grid_resolution)
        First output of :func:`meshgrid <numpy.meshgrid>`.

    xx1 : ndarray of shape (grid_resolution, grid_resolution)
        Second output of :func:`meshgrid <numpy.meshgrid>`.

    n_classes : int
        Expected number of unique classes or labels if `response` was generated by a
        :term:`classifier` or a :term:`clusterer`.

        For :term:`outlier detectors`, `n_classes` should be set to 2 by definition
        (inlier or outlier).

        For :term:`regressors`, `n_classes` should also be set to 2 by convention
        (continuous responses are displayed the same way as unthresholded binary
        responses).

        .. versionadded:: 1.9

    response : ndarray of shape (grid_resolution, grid_resolution) or \
            (grid_resolution, grid_resolution, n_classes)
        Values of the response function.

    multiclass_colors : list of str or str, default=None
        Specifies how to color each class when plotting all classes of
        :term:`multiclass` problems.

        Possible inputs are:

        * list: list of Matplotlib
          `color <https://matplotlib.org/stable/users/explain/colors/colors.html#colors-def>`_
          strings, of length `n_classes`
        * str: name of :class:`matplotlib.colors.Colormap`
        * None: 'tab10' colormap is used to sample colors if the number of
          classes is less than or equal to 10, otherwise 'gist_rainbow' colormap.

        Single color (fading to white) colormaps will be generated from the colors in
        the list or colors taken from the colormap, and passed to the `cmap` parameter
        of the `plot_method`.

        For :term:`binary` problems, this is ignored and `cmap` or `colors` can be
        passed as kwargs instead, otherwise, the default colormap ('viridis') is used.

        .. versionadded:: 1.7
        .. versionchanged:: 1.9
           `multiclass_colors` is now also used when `response_method="predict"`

    xlabel : str, default=None
        Default label to place on x axis.

    ylabel : str, default=None

Review comment (bug):
I think this is a fine way to differentiate between qualitative and continuous colormaps and and I can't see where it would fail - but I think a 'safer' way may be with class.

I think qualitative maps are [`ListedColormap`](https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.ListedColormap.html) (note the `colors` attribute, which we do use later) - it's hard to find documentation but it is mentioned here: https://matplotlib.org/stable/gallery/color/individual_colors_from_cmap.html#extracting-colors-from-a-discrete-colormap

Continuous colormaps are [`LinearSegmentedColormap`](https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.LinearSegmentedColormap.html) - note the default of `N=256`

WDYT?

Code after:

    Returns
    -------
    colors : list or ndarray of shape (n_classes, 4)
        RGBA colors, one per class.

    """

    if multiclass_colors is None:
        multiclass_colors = "tab10" if n_classes <= 10 else "gist_rainbow"

    if isinstance(multiclass_colors, str):
        if multiclass_colors not in mpl.pyplot.colormaps():
            raise ValueError(
                "When 'multiclass_colors' is a string, it must be a valid "
                f"Matplotlib colormap. Got: {multiclass_colors}"
            )
        cmap = mpl.pyplot.get_cmap(multiclass_colors)
        if cmap.N < n_classes:
            raise ValueError(
                f"Colormap '{multiclass_colors}' only has {cmap.N} colors, but "
                f"{n_classes} classes are to be displayed. Please specify a "
                "different colormap or provide a list of colors via "
                "'multiclass_colors'."
            )
        if cmap.N < 256:
            # Special case for the qualitative colormaps that encode a
            # discrete set of colors that are easily distinguishable, contrary to other
            # colormaps that are continuous (for which `cmap.N` >= 256).
            return mpl.colors.to_rgba_array(cmap.colors[:n_classes])
        else:
            return cmap(np.linspace(0, 1, n_classes))

    elif isinstance(multiclass_colors, list):
        if len(multiclass_colors) != n_classes:
            raise ValueError(
                "When 'multiclass_colors' is a list, it must be of the same "
                f"length as the classes or labels to plot ({n_classes}), got: "
                f"{len(multiclass_colors)}."
            )
        elif any(not mpl.colors.is_color_like(col) for col in multiclass_colors):
            raise ValueError(
                "When 'multiclass_colors' is a list, it can only contain valid"
                f" Matplotlib color names. Got: {multiclass_colors}"
            )
        return [mpl.colors.to_rgba(color) for color in multiclass_colors]

    else:
        raise TypeError("'multiclass_colors' must be a list or a str.")