Repository: pandas-dev/pandas

Issue:
Allow users to pass backend option to `.plot()`
Once you have installed another plotting library with a pandas.plotting entrypoint it would be nice to be able to toggle back and forth between different backends even within on session.

## What currently exists
```python
pd.options.plotting.backend = 'holoviews'
```

## Proposed additional API

```python
df.plot(backend='holoviews')

```

This would be particularly useful when a particular backend doesn't support a certain type of plot, or you need to use a backend to get some specific functionality but don't want to use if for your whole notebook.
 
@datapythonista - @TomAugspurger mentioned that you might have opinions on this.



Discussion:
I'm ok with this, sounds reasonable.

Resolving patch:
diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py
--- a/pandas/plotting/_core.py
+++ b/pandas/plotting/_core.py
@@ -3,7 +3,7 @@
 
 from pandas._config import get_option
 
-from pandas.util._decorators import Appender
+from pandas.util._decorators import Appender, Substitution
 
 from pandas.core.dtypes.common import is_integer, is_list_like
 from pandas.core.dtypes.generic import ABCDataFrame, ABCSeries
@@ -22,6 +22,7 @@ def hist_series(
     yrot=None,
     figsize=None,
     bins=10,
+    backend=None,
     **kwargs
 ):
     """
@@ -50,6 +51,14 @@ def hist_series(
         bin edges are calculated and returned. If bins is a sequence, gives
         bin edges, including left edge of first bin and right edge of last
         bin. In this case, bins is returned unmodified.
+    backend : str, default None
+        Backend to use instead of the backend specified in the option
+        ``plotting.backend``. For instance, 'matplotlib'. Alternatively, to
+        specify the ``plotting.backend`` for the whole session, set
+        ``pd.options.plotting.backend``.
+
+        .. versionadded:: 1.0.0
+
     **kwargs
         To be passed to the actual plotting function.
 
@@ -62,7 +71,7 @@ def hist_series(
     --------
     matplotlib.axes.Axes.hist : Plot a histogram using matplotlib.
     """
-    plot_backend = _get_plot_backend()
+    plot_backend = _get_plot_backend(backend)
     return plot_backend.hist_series(
         self,
         by=by,
@@ -93,6 +102,7 @@ def hist_frame(
     figsize=None,
     layout=None,
     bins=10,
+    backend=None,
     **kwargs
 ):
     """
@@ -145,6 +155,14 @@ def hist_frame(
         bin edges are calculated and returned. If bins is a sequence, gives
         bin edges, including left edge of first bin and right edge of last
         bin. In this case, bins is returned unmodified.
+    backend : str, default None
+        Backend to use instead of the backend specified in the option
+        ``plotting.backend``. For instance, 'matplotlib'. Alternatively, to
+        specify the ``plotting.backend`` for the whole session, set
+        ``pd.options.plotting.backend``.
+
+        .. versionadded:: 1.0.0
+
     **kwargs
         All other plotting keyword arguments to be passed to
         :meth:`matplotlib.pyplot.hist`.
@@ -172,7 +190,7 @@ def hist_frame(
         ...     }, index=['pig', 'rabbit', 'duck', 'chicken', 'horse'])
         >>> hist = df.hist(bins=3)
     """
-    plot_backend = _get_plot_backend()
+    plot_backend = _get_plot_backend(backend)
     return plot_backend.hist_frame(
         data,
         column=column,
@@ -192,181 +210,198 @@ def hist_frame(
     )
 
 
-def boxplot(
-    data,
-    column=None,
-    by=None,
-    ax=None,
-    fontsize=None,
-    rot=0,
-    grid=True,
-    figsize=None,
-    layout=None,
-    return_type=None,
-    **kwargs
-):
-    """
-    Make a box plot from DataFrame columns.
-
-    Make a box-and-whisker plot from DataFrame columns, optionally grouped
-    by some other columns. A box plot is a method for graphically depicting
-    groups of numerical data through their quartiles.
-    The box extends from the Q1 to Q3 quartile values of the data,
-    with a line at the median (Q2). The whiskers extend from the edges
-    of box to show the range of the data. The position of the whiskers
-    is set by default to `1.5 * IQR (IQR = Q3 - Q1)` from the edges of the box.
-    Outlier points are those past the end of the whiskers.
-
-    For further details see
-    Wikipedia's entry for `boxplot <https://en.wikipedia.org/wiki/Box_plot>`_.
-
-    Parameters
-    ----------
-    column : str or list of str, optional
-        Column name or list of names, or vector.
-        Can be any valid input to :meth:`pandas.DataFrame.groupby`.
-    by : str or array-like, optional
-        Column in the DataFrame to :meth:`pandas.DataFrame.groupby`.
-        One box-plot will be done per value of columns in `by`.
-    ax : object of class matplotlib.axes.Axes, optional
-        The matplotlib axes to be used by boxplot.
-    fontsize : float or str
-        Tick label font size in points or as a string (e.g., `large`).
-    rot : int or float, default 0
-        The rotation angle of labels (in degrees)
-        with respect to the screen coordinate system.
-    grid : bool, default True
-        Setting this to True will show the grid.
-    figsize : A tuple (width, height) in inches
-        The size of the figure to create in matplotlib.
-    layout : tuple (rows, columns), optional
-        For example, (3, 5) will display the subplots
-        using 3 columns and 5 rows, starting from the top-left.
-    return_type : {'axes', 'dict', 'both'} or None, default 'axes'
-        The kind of object to return. The default is ``axes``.
-
-        * 'axes' returns the matplotlib axes the boxplot is drawn on.
-        * 'dict' returns a dictionary whose values are the matplotlib
-          Lines of the boxplot.
-        * 'both' returns a namedtuple with the axes and dict.
-        * when grouping with ``by``, a Series mapping columns to
-          ``return_type`` is returned.
-
-          If ``return_type`` is `None`, a NumPy array
-          of axes with the same shape as ``layout`` is returned.
-    **kwargs
-        All other plotting keyword arguments to be passed to
-        :func:`matplotlib.pyplot.boxplot`.
-
-    Returns
-    -------
-    result
-        See Notes.
-
-    See Also
-    --------
-    Series.plot.hist: Make a histogram.
-    matplotlib.pyplot.boxplot : Matplotlib equivalent plot.
-
-    Notes
-    -----
-    The return type depends on the `return_type` parameter:
-
-    * 'axes' : object of class matplotlib.axes.Axes
-    * 'dict' : dict of matplotlib.lines.Line2D objects
-    * 'both' : a namedtuple with structure (ax, lines)
-
-    For data grouped with ``by``, return a Series of the above or a numpy
-    array:
-
-    * :class:`~pandas.Series`
-    * :class:`~numpy.array` (for ``return_type = None``)
+_boxplot_doc = """
+Make a box plot from DataFrame columns.
+
+Make a box-and-whisker plot from DataFrame columns, optionally grouped
+by some other columns. A box plot is a method for graphically depicting
+groups of numerical data through their quartiles.
+The box extends from the Q1 to Q3 quartile values of the data,
+with a line at the median (Q2). The whiskers extend from the edges
+of box to show the range of the data. The position of the whiskers
+is set by default to `1.5 * IQR (IQR = Q3 - Q1)` from the edges of the box.
+Outlier points are those past the end of the whiskers.
+
+For further details see
+Wikipedia's entry for `boxplot <https://en.wikipedia.org/wiki/Box_plot>`_.
+
+Parameters
+----------
+column : str or list of str, optional
+    Column name or list of names, or vector.
+    Can be any valid input to :meth:`pandas.DataFrame.groupby`.
+by : str or array-like, optional
+    Column in the DataFrame to :meth:`pandas.DataFrame.groupby`.
+    One box-plot will be done per value of columns in `by`.
+ax : object of class matplotlib.axes.Axes, optional
+    The matplotlib axes to be used by boxplot.
+fontsize : float or str
+    Tick label font size in points or as a string (e.g., `large`).
+rot : int or float, default 0
+    The rotation angle of labels (in degrees)
+    with respect to the screen coordinate system.
+grid : bool, default True
+    Setting this to True will show the grid.
+figsize : A tuple (width, height) in inches
+    The size of the figure to create in matplotlib.
+layout : tuple (rows, columns), optional
+    For example, (3, 5) will display the subplots
+    using 3 columns and 5 rows, starting from the top-left.
+return_type : {'axes', 'dict', 'both'} or None, default 'axes'
+    The kind of object to return. The default is ``axes``.
+
+    * 'axes' returns the matplotlib axes the boxplot is drawn on.
+    * 'dict' returns a dictionary whose values are the matplotlib
+      Lines of the boxplot.
+    * 'both' returns a namedtuple with the axes and dict.
+    * when grouping with ``by``, a Series mapping columns to
+      ``return_type`` is returned.
+
+      If ``return_type`` is `None`, a NumPy array
+      of axes with the same shape as ``layout`` is returned.
+%(backend)s\
+
+**kwargs
+    All other plotting keyword arguments to be passed to
+    :func:`matplotlib.pyplot.boxplot`.
+
+Returns
+-------
+result
+    See Notes.
+
+See Also
+--------
+Series.plot.hist: Make a histogram.
+matplotlib.pyplot.boxplot : Matplotlib equivalent plot.
+
+Notes
+-----
+The return type depends on the `return_type` parameter:
+
+* 'axes' : object of class matplotlib.axes.Axes
+* 'dict' : dict of matplotlib.lines.Line2D objects
+* 'both' : a namedtuple with structure (ax, lines)
+
+For data grouped with ``by``, return a Series of the above or a numpy
+array:
+
+* :class:`~pandas.Series`
+* :class:`~numpy.array` (for ``return_type = None``)
+
+Use ``return_type='dict'`` when you want to tweak the appearance
+of the lines after plotting. In this case a dict containing the Lines
+making up the boxes, caps, fliers, medians, and whiskers is returned.
+
+Examples
+--------
+
+Boxplots can be created for every column in the dataframe
+by ``df.boxplot()`` or indicating the columns to be used:
+
+.. plot::
+    :context: close-figs
+
+    >>> np.random.seed(1234)
+    >>> df = pd.DataFrame(np.random.randn(10, 4),
+    ...                   columns=['Col1', 'Col2', 'Col3', 'Col4'])
+    >>> boxplot = df.boxplot(column=['Col1', 'Col2', 'Col3'])
+
+Boxplots of variables distributions grouped by the values of a third
+variable can be created using the option ``by``. For instance:
+
+.. plot::
+    :context: close-figs
+
+    >>> df = pd.DataFrame(np.random.randn(10, 2),
+    ...                   columns=['Col1', 'Col2'])
+    >>> df['X'] = pd.Series(['A', 'A', 'A', 'A', 'A',
+    ...                      'B', 'B', 'B', 'B', 'B'])
+    >>> boxplot = df.boxplot(by='X')
+
+A list of strings (i.e. ``['X', 'Y']``) can be passed to boxplot
+in order to group the data by combination of the variables in the x-axis:
+
+.. plot::
+    :context: close-figs
+
+    >>> df = pd.DataFrame(np.random.randn(10, 3),
+    ...                   columns=['Col1', 'Col2', 'Col3'])
+    >>> df['X'] = pd.Series(['A', 'A', 'A', 'A', 'A',
+    ...                      'B', 'B', 'B', 'B', 'B'])
+    >>> df['Y'] = pd.Series(['A', 'B', 'A', 'B', 'A',
+    ...                      'B', 'A', 'B', 'A', 'B'])
+    >>> boxplot = df.boxplot(column=['Col1', 'Col2'], by=['X', 'Y'])
+
+The layout of boxplot can be adjusted giving a tuple to ``layout``:
+
+.. plot::
+    :context: close-figs
+
+    >>> boxplot = df.boxplot(column=['Col1', 'Col2'], by='X',
+    ...                      layout=(2, 1))
 
-    Use ``return_type='dict'`` when you want to tweak the appearance
-    of the lines after plotting. In this case a dict containing the Lines
-    making up the boxes, caps, fliers, medians, and whiskers is returned.
+Additional formatting can be done to the boxplot, like suppressing the grid
+(``grid=False``), rotating the labels in the x-axis (i.e. ``rot=45``)
+or changing the fontsize (i.e. ``fontsize=15``):
 
-    Examples
-    --------
-
-    Boxplots can be created for every column in the dataframe
-    by ``df.boxplot()`` or indicating the columns to be used:
-
-    .. plot::
-        :context: close-figs
-
-        >>> np.random.seed(1234)
-        >>> df = pd.DataFrame(np.random.randn(10,4),
-        ...                   columns=['Col1', 'Col2', 'Col3', 'Col4'])
-        >>> boxplot = df.boxplot(column=['Col1', 'Col2', 'Col3'])
-
-    Boxplots of variables distributions grouped by the values of a third
-    variable can be created using the option ``by``. For instance:
-
-    .. plot::
-        :context: close-figs
-
-        >>> df = pd.DataFrame(np.random.randn(10, 2),
-        ...                   columns=['Col1', 'Col2'])
-        >>> df['X'] = pd.Series(['A', 'A', 'A', 'A', 'A',
-        ...                      'B', 'B', 'B', 'B', 'B'])
-        >>> boxplot = df.boxplot(by='X')
-
-    A list of strings (i.e. ``['X', 'Y']``) can be passed to boxplot
-    in order to group the data by combination of the variables in the x-axis:
-
-    .. plot::
-        :context: close-figs
+.. plot::
+    :context: close-figs
 
-        >>> df = pd.DataFrame(np.random.randn(10,3),
-        ...                   columns=['Col1', 'Col2', 'Col3'])
-        >>> df['X'] = pd.Series(['A', 'A', 'A', 'A', 'A',
-        ...                      'B', 'B', 'B', 'B', 'B'])
-        >>> df['Y'] = pd.Series(['A', 'B', 'A', 'B', 'A',
-        ...                      'B', 'A', 'B', 'A', 'B'])
-        >>> boxplot = df.boxplot(column=['Col1', 'Col2'], by=['X', 'Y'])
+    >>> boxplot = df.boxplot(grid=False, rot=45, fontsize=15)
 
-    The layout of boxplot can be adjusted giving a tuple to ``layout``:
+The parameter ``return_type`` can be used to select the type of element
+returned by `boxplot`.  When ``return_type='axes'`` is selected,
+the matplotlib axes on which the boxplot is drawn are returned:
 
-    .. plot::
-        :context: close-figs
+    >>> boxplot = df.boxplot(column=['Col1', 'Col2'], return_type='axes')
+    >>> type(boxplot)
+    <class 'matplotlib.axes._subplots.AxesSubplot'>
 
-        >>> boxplot = df.boxplot(column=['Col1', 'Col2'], by='X',
-        ...                      layout=(2, 1))
+When grouping with ``by``, a Series mapping columns to ``return_type``
+is returned:
 
-    Additional formatting can be done to the boxplot, like suppressing the grid
-    (``grid=False``), rotating the labels in the x-axis (i.e. ``rot=45``)
-    or changing the fontsize (i.e. ``fontsize=15``):
+    >>> boxplot = df.boxplot(column=['Col1', 'Col2'], by='X',
+    ...                      return_type='axes')
+    >>> type(boxplot)
+    <class 'pandas.core.series.Series'>
 
-    .. plot::
-        :context: close-figs
+If ``return_type`` is `None`, a NumPy array of axes with the same shape
+as ``layout`` is returned:
 
-        >>> boxplot = df.boxplot(grid=False, rot=45, fontsize=15)
+    >>> boxplot = df.boxplot(column=['Col1', 'Col2'], by='X',
+    ...                      return_type=None)
+    >>> type(boxplot)
+    <class 'numpy.ndarray'>
+"""
 
-    The parameter ``return_type`` can be used to select the type of element
-    returned by `boxplot`.  When ``return_type='axes'`` is selected,
-    the matplotlib axes on which the boxplot is drawn are returned:
 
-        >>> boxplot = df.boxplot(column=['Col1','Col2'], return_type='axes')
-        >>> type(boxplot)
-        <class 'matplotlib.axes._subplots.AxesSubplot'>
+_backend_doc = """\
+backend : str, default None
+    Backend to use instead of the backend specified in the option
+    ``plotting.backend``. For instance, 'matplotlib'. Alternatively, to
+    specify the ``plotting.backend`` for the whole session, set
+    ``pd.options.plotting.backend``.
 
-    When grouping with ``by``, a Series mapping columns to ``return_type``
-    is returned:
+    .. versionadded:: 1.0.0
+"""
 
-        >>> boxplot = df.boxplot(column=['Col1', 'Col2'], by='X',
-        ...                      return_type='axes')
-        >>> type(boxplot)
-        <class 'pandas.core.series.Series'>
 
-    If ``return_type`` is `None`, a NumPy array of axes with the same shape
-    as ``layout`` is returned:
-
-        >>> boxplot = df.boxplot(column=['Col1', 'Col2'], by='X',
-        ...                      return_type=None)
-        >>> type(boxplot)
-        <class 'numpy.ndarray'>
-    """
+@Substitution(backend="")
+@Appender(_boxplot_doc)
+def boxplot(
+    data,
+    column=None,
+    by=None,
+    ax=None,
+    fontsize=None,
+    rot=0,
+    grid=True,
+    figsize=None,
+    layout=None,
+    return_type=None,
+    **kwargs
+):
     plot_backend = _get_plot_backend("matplotlib")
     return plot_backend.boxplot(
         data,
@@ -383,7 +418,8 @@ def boxplot(
     )
 
 
-@Appender(boxplot.__doc__)
+@Substitution(backend=_backend_doc)
+@Appender(_boxplot_doc)
 def boxplot_frame(
     self,
     column=None,
@@ -395,9 +431,10 @@ def boxplot_frame(
     figsize=None,
     layout=None,
     return_type=None,
+    backend=None,
     **kwargs
 ):
-    plot_backend = _get_plot_backend()
+    plot_backend = _get_plot_backend(backend)
     return plot_backend.boxplot_frame(
         self,
         column=column,
@@ -425,6 +462,7 @@ def boxplot_frame_groupby(
     layout=None,
     sharex=False,
     sharey=True,
+    backend=None,
     **kwargs
 ):
     """
@@ -454,6 +492,14 @@ def boxplot_frame_groupby(
         Whether y-axes will be shared among subplots.
 
         .. versionadded:: 0.23.1
+    backend : str, default None
+        Backend to use instead of the backend specified in the option
+        ``plotting.backend``. For instance, 'matplotlib'. Alternatively, to
+        specify the ``plotting.backend`` for the whole session, set
+        ``pd.options.plotting.backend``.
+
+        .. versionadded:: 1.0.0
+
     **kwargs
         All other plotting keyword arguments to be passed to
         matplotlib's boxplot function.
@@ -477,7 +523,7 @@ def boxplot_frame_groupby(
     >>> grouped = df.unstack(level='lvl1').groupby(level=0, axis=1)
     >>> boxplot_frame_groupby(grouped, subplots=False)
     """
-    plot_backend = _get_plot_backend()
+    plot_backend = _get_plot_backend(backend)
     return plot_backend.boxplot_frame_groupby(
         grouped,
         subplots=subplots,
@@ -586,6 +632,14 @@ class PlotAccessor(PandasObject):
         labels with "(right)" in the legend.
     include_bool : bool, default is False
         If True, boolean values can be plotted.
+    backend : str, default None
+        Backend to use instead of the backend specified in the option
+        ``plotting.backend``. For instance, 'matplotlib'. Alternatively, to
+        specify the ``plotting.backend`` for the whole session, set
+        ``pd.options.plotting.backend``.
+
+        .. versionadded:: 1.0.0
+
     **kwargs
         Options to pass to matplotlib plotting method.
 
@@ -715,7 +769,7 @@ def _get_call_args(backend_name, data, args, kwargs):
         return x, y, kind, kwargs
 
     def __call__(self, *args, **kwargs):
-        plot_backend = _get_plot_backend()
+        plot_backend = _get_plot_backend(kwargs.pop("backend", None))
 
         x, y, kind, kwargs = self._get_call_args(
             plot_backend.__name__, self._parent, args, kwargs


Test patch:
diff --git a/pandas/tests/plotting/test_backend.py b/pandas/tests/plotting/test_backend.py
--- a/pandas/tests/plotting/test_backend.py
+++ b/pandas/tests/plotting/test_backend.py
@@ -9,7 +9,7 @@
 import pandas
 
 dummy_backend = types.ModuleType("pandas_dummy_backend")
-setattr(dummy_backend, "plot", lambda *args, **kwargs: None)
+setattr(dummy_backend, "plot", lambda *args, **kwargs: "used_dummy")
 
 
 @pytest.fixture
@@ -38,6 +38,14 @@ def test_backend_is_correct(monkeypatch, restore_backend):
     )
 
 
+def test_backend_can_be_set_in_plot_call(monkeypatch, restore_backend):
+    monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
+    df = pandas.DataFrame([1, 2, 3])
+
+    assert pandas.get_option("plotting.backend") == "matplotlib"
+    assert df.plot(backend="pandas_dummy_backend") == "used_dummy"
+
+
 @td.skip_if_no_mpl
 def test_register_entrypoint(restore_backend):