Skip to content

Commit 2243629

Browse files
PLOT: Add option to specify the plotting backend (pandas-dev#26753)
1 parent c275dbf commit 2243629

File tree

7 files changed

+84
-14
lines changed

7 files changed

+84
-14
lines changed

doc/source/user_guide/options.rst

+6
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@ compute.use_bottleneck True Use the bottleneck library
431431
computation if it is installed.
432432
compute.use_numexpr True Use the numexpr library to accelerate
433433
computation if it is installed.
434+
plotting.backend matplotlib Change the plotting backend to a different
435+
backend than the current matplotlib one.
436+
Backends can be implemented as third-party
437+
libraries implementing the pandas plotting
438+
API. They can use other plotting libraries
439+
like Bokeh, Altair, etc.
434440
plotting.matplotlib.register_converters True Register custom converters with
435441
matplotlib. Set to False to de-register.
436442
======================================= ============ ==================================

doc/source/whatsnew/v0.25.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ Other Enhancements
132132
- :class:`DatetimeIndex` and :class:`TimedeltaIndex` now have a ``mean`` method (:issue:`24757`)
133133
- :meth:`DataFrame.describe` now formats integer percentiles without decimal point (:issue:`26660`)
134134
- Added support for reading SPSS .sav files using :func:`read_spss` (:issue:`26537`)
135+
- Added new option ``plotting.backend`` to be able to select a plotting backend different than the existing ``matplotlib`` one. Use ``pandas.set_option('plotting.backend', '<backend-module>')`` where ``<backend-module`` is a library implementing the pandas plotting API (:issue:`14130`)
135136

136137
.. _whatsnew_0250.api_breaking:
137138

pandas/core/config_init.py

+36
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
module is imported, register them here rather then in the module.
1010
1111
"""
12+
import importlib
13+
1214
import pandas._config.config as cf
1315
from pandas._config.config import (
1416
is_bool, is_callable, is_instance_factory, is_int, is_one_of_factory,
@@ -460,6 +462,40 @@ def use_inf_as_na_cb(key):
460462
# Plotting
461463
# ---------
462464

465+
plotting_backend_doc = """
466+
: str
467+
The plotting backend to use. The default value is "matplotlib", the
468+
backend provided with pandas. Other backends can be specified by
469+
prodiving the name of the module that implements the backend.
470+
"""
471+
472+
473+
def register_plotting_backend_cb(key):
474+
backend_str = cf.get_option(key)
475+
if backend_str == 'matplotlib':
476+
try:
477+
import pandas.plotting._matplotlib # noqa
478+
except ImportError:
479+
raise ImportError('matplotlib is required for plotting when the '
480+
'default backend "matplotlib" is selected.')
481+
else:
482+
return
483+
484+
try:
485+
importlib.import_module(backend_str)
486+
except ImportError:
487+
raise ValueError('"{}" does not seem to be an installed module. '
488+
'A pandas plotting backend must be a module that '
489+
'can be imported'.format(backend_str))
490+
491+
492+
with cf.config_prefix('plotting'):
493+
cf.register_option('backend', defval='matplotlib',
494+
doc=plotting_backend_doc,
495+
validator=str,
496+
cb=register_plotting_backend_cb)
497+
498+
463499
register_converter_doc = """
464500
: bool
465501
Whether to register converters with matplotlib's units registry for

pandas/plotting/_core.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import importlib
12
from typing import List, Type # noqa
23

34
from pandas.util._decorators import Appender
45

56
from pandas.core.dtypes.common import is_integer, is_list_like
67
from pandas.core.dtypes.generic import ABCDataFrame, ABCSeries
78

9+
import pandas
810
from pandas.core.base import PandasObject
911
from pandas.core.generic import _shared_doc_kwargs, _shared_docs
1012

@@ -622,11 +624,10 @@ def _get_plot_backend():
622624
The backend is imported lazily, as matplotlib is a soft dependency, and
623625
pandas can be used without it being installed.
624626
"""
625-
try:
626-
import pandas.plotting._matplotlib as plot_backend
627-
except ImportError:
628-
raise ImportError("matplotlib is required for plotting.")
629-
return plot_backend
627+
backend_str = pandas.get_option('plotting.backend')
628+
if backend_str == 'matplotlib':
629+
backend_str = 'pandas.plotting._matplotlib'
630+
return importlib.import_module(backend_str)
630631

631632

632633
def _plot_classes():

pandas/plotting/_misc.py

+1-8
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,7 @@
33

44
from pandas.util._decorators import deprecate_kwarg
55

6-
7-
def _get_plot_backend():
8-
# TODO unify with the same function in `_core.py`
9-
try:
10-
import pandas.plotting._matplotlib as plot_backend
11-
except ImportError:
12-
raise ImportError("matplotlib is required for plotting.")
13-
return plot_backend
6+
from pandas.plotting._core import _get_plot_backend
147

158

169
def table(ax, data, rowLabels=None, colLabels=None, **kwargs):

pandas/tests/plotting/test_backend.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import pytest
2+
3+
import pandas
4+
5+
6+
def test_matplotlib_backend_error():
7+
msg = ('matplotlib is required for plotting when the default backend '
8+
'"matplotlib" is selected.')
9+
try:
10+
import matplotlib # noqa
11+
except ImportError:
12+
with pytest.raises(ImportError, match=msg):
13+
pandas.set_option('plotting.backend', 'matplotlib')
14+
15+
16+
def test_backend_is_not_module():
17+
msg = ('"not_an_existing_module" does not seem to be an installed module. '
18+
'A pandas plotting backend must be a module that can be imported')
19+
with pytest.raises(ValueError, match=msg):
20+
pandas.set_option('plotting.backend', 'not_an_existing_module')
21+
22+
23+
def test_backend_is_correct(monkeypatch):
24+
monkeypatch.setattr('pandas.core.config_init.importlib.import_module',
25+
lambda name: None)
26+
pandas.set_option('plotting.backend', 'correct_backend')
27+
assert pandas.get_option('plotting.backend') == 'correct_backend'
28+
29+
# Restore backend for other tests (matplotlib can be not installed)
30+
try:
31+
pandas.set_option('plotting.backend', 'matplotlib')
32+
except ImportError:
33+
pass

pandas/tests/plotting/test_misc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_import_error_message():
2121
# GH-19810
2222
df = DataFrame({"A": [1, 2]})
2323

24-
with pytest.raises(ImportError, match='matplotlib is required'):
24+
with pytest.raises(ImportError, match="No module named 'matplotlib'"):
2525
df.plot()
2626

2727

0 commit comments

Comments
 (0)