From 220d554779edb968857bc2c3e957eb987e150b25 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 14 Nov 2017 13:53:50 -0600 Subject: [PATCH 01/24] API: Restore implicit converter registration --- doc/source/whatsnew/v0.21.1.txt | 41 ++++++++++++++++++++++++- pandas/plotting/_converter.py | 35 ++++++++++++++++++++- pandas/plotting/_core.py | 29 ++++++++--------- pandas/tests/plotting/test_converter.py | 33 +++++++++++++++++++- pandas/tests/plotting/test_warns.py | 35 +++++++++++++++++++++ 5 files changed, 156 insertions(+), 17 deletions(-) create mode 100644 pandas/tests/plotting/test_warns.py diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index 0ab536f2898c7..34c7a772a7656 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -7,6 +7,45 @@ This is a minor release from 0.21.1 and includes a number of deprecations, new features, enhancements, and performance improvements along with a large number of bug fixes. We recommend that all users upgrade to this version. +.. _whatsnew_0211.special: + +Restore Matplotlib datetime Converter Registration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pandas implements some matplotlib converters for nicely formatting the axis +labels on plots with ``datetime`` or ``Period`` values. Prior to pandas 0.21.0, +these were implicitly registered with matplotlib, as a side effect of +``import pandas``. In pandas 0.21.0, we required users to explicitly register +the converter. + +.. code-block:: python + + >>> from pandas.tseries import converter + >>> converter.register() + +This caused problems for some users, so we're temporarily reverting that change; +pandas will again register the converters on import. Using the converters +without explicitly registering the formatters will cause a ``FutureWarning``: + +.. code-block:: python + + >>> import pandas as pd + >>> import matplotlib.pyplot as plt + >>> fig, ax = plt.subplots() + >>> ax.plot(pd.Series(range(12), index=pd.date_range('2017', periods=12))) + FutureWarning: Using an implicitly registered datetime converter for a + matplotlib plotting method. The converter was registered by pandas on import. + Future versions of pandas will require you to explicitly register matplotlib + converters. + + To register the converters: + >>> from pandas.tseries import converter + >>> converter.register() + +As the error message says, you'll need to register the converts if you intend to +use them with matplotlib plotting functions. Pandas plotting functions, such as +``Series.plot``, will register them for you. (:issue:`18301`) + .. _whatsnew_0211.enhancements: New features @@ -30,7 +69,7 @@ Other Enhancements Deprecations ~~~~~~~~~~~~ -- +- - - diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index aadd5a1beb28b..fafa590020bb0 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -1,3 +1,4 @@ +import warnings from datetime import datetime, timedelta import datetime as pydt import numpy as np @@ -45,8 +46,15 @@ MUSEC_PER_DAY = 1e6 * SEC_PER_DAY +_WARN = True + + +def register(warn=False): + global _WARN + + if not warn: + _WARN = False -def register(): units.registry[lib.Timestamp] = DatetimeConverter() units.registry[Period] = PeriodConverter() units.registry[pydt.datetime] = DatetimeConverter() @@ -55,6 +63,21 @@ def register(): units.registry[np.datetime64] = DatetimeConverter() +def _check_implicitly_registered(): + global _WARN + + if _WARN: + msg = ("Using an implicitly registered datetime converter for a " + "matplotlib plotting method. The converter was registered " + "by pandas on import. Future versions of pandas will require " + "you to explicitly register matplotlib converters.\n\n" + "To register the converters:\n\t" + ">>> from pandas.tseries import converter\n\t" + ">>> converter.register()") + warnings.warn(msg, FutureWarning) + _WARN = False + + def _to_ordinalf(tm): tot_sec = (tm.hour * 3600 + tm.minute * 60 + tm.second + float(tm.microsecond / 1e6)) @@ -190,6 +213,7 @@ class DatetimeConverter(dates.DateConverter): @staticmethod def convert(values, unit, axis): # values might be a 1-d array, or a list-like of arrays. + _check_implicitly_registered() if is_nested_list_like(values): values = [DatetimeConverter._convert_1d(v, unit, axis) for v in values] @@ -274,6 +298,7 @@ class PandasAutoDateLocator(dates.AutoDateLocator): def get_locator(self, dmin, dmax): 'Pick the best locator based on a distance.' + _check_implicitly_registered() delta = relativedelta(dmax, dmin) num_days = (delta.years * 12.0 + delta.months) * 31.0 + delta.days @@ -315,6 +340,7 @@ def get_unit_generic(freq): def __call__(self): # if no data have been set, this will tank with a ValueError + _check_implicitly_registered() try: dmin, dmax = self.viewlim_to_dt() except ValueError: @@ -917,6 +943,8 @@ def _get_default_locs(self, vmin, vmax): def __call__(self): 'Return the locations of the ticks.' # axis calls Locator.set_axis inside set_m_formatter + _check_implicitly_registered() + vi = tuple(self.axis.get_view_interval()) if vi != self.plot_obj.view_interval: self.plot_obj.date_axis_info = None @@ -1001,6 +1029,8 @@ def set_locs(self, locs): 'Sets the locations of the ticks' # don't actually use the locs. This is just needed to work with # matplotlib. Force to use vmin, vmax + _check_implicitly_registered() + self.locs = locs (vmin, vmax) = vi = tuple(self.axis.get_view_interval()) @@ -1012,6 +1042,8 @@ def set_locs(self, locs): self._set_default_format(vmin, vmax) def __call__(self, x, pos=0): + _check_implicitly_registered() + if self.formatdict is None: return '' else: @@ -1042,6 +1074,7 @@ def format_timedelta_ticks(x, pos, n_decimals): return s def __call__(self, x, pos=0): + _check_implicitly_registered() (vmin, vmax) = tuple(self.axis.get_view_interval()) n_decimals = int(np.ceil(np.log10(100 * 1e9 / (vmax - vmin)))) if n_decimals > 9: diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 62b2899f49413..0e2dc1663b1fb 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -40,16 +40,16 @@ _get_xlim, _set_ticks_props, format_date_labels) -_registered = False - - -def _setup(): - # delay the import of matplotlib until nescessary - global _registered - if not _registered: - from pandas.plotting import _converter - _converter.register() - _registered = True +try: + # We want to warn if the formatter is called implicitly + # by `ax.plot(datetimeindex, another)` + # We don't want to warn if + # * Series.plot + # * User calls `register` explicitly + from pandas.plotting import _converter + _converter.register(warn=True) +except ImportError: + pass def _get_standard_kind(kind): @@ -99,7 +99,7 @@ def __init__(self, data, kind=None, by=None, subplots=False, sharex=None, secondary_y=False, colormap=None, table=False, layout=None, **kwds): - _setup() + _converter._WARN = False self.data = data self.by = by @@ -2059,7 +2059,7 @@ def boxplot_frame(self, column=None, by=None, ax=None, fontsize=None, rot=0, grid=True, figsize=None, layout=None, return_type=None, **kwds): import matplotlib.pyplot as plt - _setup() + _converter.WARN = False ax = boxplot(self, column=column, by=by, ax=ax, fontsize=fontsize, grid=grid, rot=rot, figsize=figsize, layout=layout, return_type=return_type, **kwds) @@ -2155,7 +2155,7 @@ def hist_frame(data, column=None, by=None, grid=True, xlabelsize=None, kwds : other plotting keyword arguments To be passed to hist function """ - _setup() + _converter.WARN = False if by is not None: axes = grouped_hist(data, column=column, by=by, ax=ax, grid=grid, figsize=figsize, sharex=sharex, sharey=sharey, @@ -2289,6 +2289,8 @@ def grouped_hist(data, column=None, by=None, ax=None, bins=50, figsize=None, ------- axes: collection of Matplotlib Axes """ + _converter.WARN = False + def plot_group(group, ax): ax.hist(group.dropna().values, bins=bins, **kwargs) @@ -2352,7 +2354,6 @@ def boxplot_frame_groupby(grouped, subplots=True, column=None, fontsize=None, >>> grouped = df.unstack(level='lvl1').groupby(level=0, axis=1) >>> boxplot_frame_groupby(grouped, subplots=False) """ - _setup() if subplots is True: naxes = len(grouped) fig, axes = _subplots(naxes=naxes, squeeze=False, diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index e1f64bed5598d..2be0c011ee34e 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -2,7 +2,7 @@ from datetime import datetime, date import numpy as np -from pandas import Timestamp, Period, Index +from pandas import Timestamp, Period, Index, date_range, Series from pandas.compat import u import pandas.util.testing as tm from pandas.tseries.offsets import Second, Milli, Micro, Day @@ -15,6 +15,37 @@ def test_timtetonum_accepts_unicode(): assert (converter.time2num("00:01") == converter.time2num(u("00:01"))) +class TestImplicitRegistration(object): + + def test_warns(self): + plt = pytest.importorskip("matplotlib.pyplot") + s = Series(range(12), index=date_range('2017', periods=12)) + _, ax = plt.subplots() + + # Set to the "warning" state, in case this isn't the first test run + converter._WARN = True + with tm.assert_produces_warning(FutureWarning, + check_stacklevel=False) as w: + ax.plot(s.index, s.values) + plt.close() + + assert len(w) == 1 + assert "Using an implicitly registered datetime converter" in str(w[0]) + + def test_registering_no_warning(self): + plt = pytest.importorskip("matplotlib.pyplot") + s = Series(range(12), index=date_range('2017', periods=12)) + _, ax = plt.subplots() + + # Set to the "no-warn" state, in case this isn't the first test run + converter._WARN = True + converter.register() + with tm.assert_produces_warning(None) as w: + ax.plot(s.index, s.values) + + assert len(w) == 0 + + class TestDateTimeConverter(object): def setup_method(self, method): diff --git a/pandas/tests/plotting/test_warns.py b/pandas/tests/plotting/test_warns.py new file mode 100644 index 0000000000000..8fcc1e4d47046 --- /dev/null +++ b/pandas/tests/plotting/test_warns.py @@ -0,0 +1,35 @@ +import pytest + +import pandas as pd +import pandas.util.testing as tm + +from pandas.plotting import _converter +from pandas.tseries import converter + + +plt = pytest.importorskip('matplotlib.pyplot') + + +def test_warns(): + s = pd.Series(range(12), index=pd.date_range('2017', periods=12)) + fig, ax = plt.subplots() + + _converter._WARN = True + with tm.assert_produces_warning(FutureWarning, + check_stacklevel=False) as w: + ax.plot(s.index, s.values) + plt.close() + + assert len(w) == 1 + + +def test_registering_no_warning(): + s = pd.Series(range(12), index=pd.date_range('2017', periods=12)) + fig, ax = plt.subplots() + + _converter._WARN = True + converter.register() + with tm.assert_produces_warning(None) as w: + ax.plot(s.index, s.values) + + assert len(w) == 0 From fba98b15eaed948cecd04fc70d0f6a2a78e614a2 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 15 Nov 2017 08:49:55 -0600 Subject: [PATCH 02/24] Remove matplotlib from blacklist --- ci/check_imports.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ci/check_imports.py b/ci/check_imports.py index a83436e7d258c..d6f24ebcc4d3e 100644 --- a/ci/check_imports.py +++ b/ci/check_imports.py @@ -9,7 +9,6 @@ 'ipython', 'jinja2' 'lxml', - 'matplotlib', 'numexpr', 'openpyxl', 'py', From ba88a043a13531f064db2705dc51aac70023c55c Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 15 Nov 2017 10:06:08 -0600 Subject: [PATCH 03/24] fixup! Remove matplotlib from blacklist --- doc/source/whatsnew/v0.21.1.txt | 9 ++++--- pandas/plotting/_core.py | 7 ++--- pandas/tests/plotting/test_converter.py | 10 +++++++ pandas/tests/plotting/test_warns.py | 35 ------------------------- 4 files changed, 19 insertions(+), 42 deletions(-) delete mode 100644 pandas/tests/plotting/test_warns.py diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index 34c7a772a7656..26be243fbb581 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -25,7 +25,7 @@ the converter. This caused problems for some users, so we're temporarily reverting that change; pandas will again register the converters on import. Using the converters -without explicitly registering the formatters will cause a ``FutureWarning``: +without explicitly registering the converters will cause a ``FutureWarning``: .. code-block:: python @@ -42,9 +42,10 @@ without explicitly registering the formatters will cause a ``FutureWarning``: >>> from pandas.tseries import converter >>> converter.register() -As the error message says, you'll need to register the converts if you intend to -use them with matplotlib plotting functions. Pandas plotting functions, such as -``Series.plot``, will register them for you. (:issue:`18301`) +As the error message says, you'll need to register the converters if you intend +to use them with matplotlib plotting functions. Pandas plotting functions, such +as ``Series.plot``, will register them for you calling ``converter.register()`` +first is not necessary. (:issue:`18301`) .. _whatsnew_0211.enhancements: diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 0e2dc1663b1fb..11bd2ed25229c 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -2059,7 +2059,7 @@ def boxplot_frame(self, column=None, by=None, ax=None, fontsize=None, rot=0, grid=True, figsize=None, layout=None, return_type=None, **kwds): import matplotlib.pyplot as plt - _converter.WARN = False + _converter._WARN = False ax = boxplot(self, column=column, by=by, ax=ax, fontsize=fontsize, grid=grid, rot=rot, figsize=figsize, layout=layout, return_type=return_type, **kwds) @@ -2155,7 +2155,7 @@ def hist_frame(data, column=None, by=None, grid=True, xlabelsize=None, kwds : other plotting keyword arguments To be passed to hist function """ - _converter.WARN = False + _converter._WARN = False if by is not None: axes = grouped_hist(data, column=column, by=by, ax=ax, grid=grid, figsize=figsize, sharex=sharex, sharey=sharey, @@ -2289,7 +2289,7 @@ def grouped_hist(data, column=None, by=None, ax=None, bins=50, figsize=None, ------- axes: collection of Matplotlib Axes """ - _converter.WARN = False + _converter._WARN = False def plot_group(group, ax): ax.hist(group.dropna().values, bins=bins, **kwargs) @@ -2354,6 +2354,7 @@ def boxplot_frame_groupby(grouped, subplots=True, column=None, fontsize=None, >>> grouped = df.unstack(level='lvl1').groupby(level=0, axis=1) >>> boxplot_frame_groupby(grouped, subplots=False) """ + _converter._WARN = False if subplots is True: naxes = len(grouped) fig, axes = _subplots(naxes=naxes, squeeze=False, diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 2be0c011ee34e..951a277388c11 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -45,6 +45,16 @@ def test_registering_no_warning(self): assert len(w) == 0 + def test_pandas_plots_register(self): + pytest.importorskip("matplotlib.pyplot") + s = Series(range(12), index=date_range('2017', periods=12)) + # Set to the "no-warn" state, in case this isn't the first test run + converter._WARN = True + with tm.assert_produces_warning(None) as w: + s.plot() + + assert len(w) == 0 + class TestDateTimeConverter(object): diff --git a/pandas/tests/plotting/test_warns.py b/pandas/tests/plotting/test_warns.py deleted file mode 100644 index 8fcc1e4d47046..0000000000000 --- a/pandas/tests/plotting/test_warns.py +++ /dev/null @@ -1,35 +0,0 @@ -import pytest - -import pandas as pd -import pandas.util.testing as tm - -from pandas.plotting import _converter -from pandas.tseries import converter - - -plt = pytest.importorskip('matplotlib.pyplot') - - -def test_warns(): - s = pd.Series(range(12), index=pd.date_range('2017', periods=12)) - fig, ax = plt.subplots() - - _converter._WARN = True - with tm.assert_produces_warning(FutureWarning, - check_stacklevel=False) as w: - ax.plot(s.index, s.values) - plt.close() - - assert len(w) == 1 - - -def test_registering_no_warning(): - s = pd.Series(range(12), index=pd.date_range('2017', periods=12)) - fig, ax = plt.subplots() - - _converter._WARN = True - converter.register() - with tm.assert_produces_warning(None) as w: - ax.plot(s.index, s.values) - - assert len(w) == 0 From 16c33f19c98365345fad3074f8399020e5ce71cd Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 15 Nov 2017 11:28:43 -0600 Subject: [PATCH 04/24] Add option for toggling formatters --- doc/source/whatsnew/v0.21.1.txt | 6 ++++- pandas/core/config_init.py | 28 ++++++++++++++++++++ pandas/plotting/__init__.py | 1 + pandas/plotting/_converter.py | 18 ++++++++----- pandas/plotting/_core.py | 10 +++---- pandas/tests/plotting/test_converter.py | 35 ++++++++++++++++++++++--- pandas/tseries/converter.py | 12 ++++++++- 7 files changed, 93 insertions(+), 17 deletions(-) diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index 26be243fbb581..2db7d85ba082a 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -45,7 +45,11 @@ without explicitly registering the converters will cause a ``FutureWarning``: As the error message says, you'll need to register the converters if you intend to use them with matplotlib plotting functions. Pandas plotting functions, such as ``Series.plot``, will register them for you calling ``converter.register()`` -first is not necessary. (:issue:`18301`) +first is not necessary. + +Finally, control the formatters, we've added a new option: +``pd.options.plotting.matplotlib.register_formatters``. By default, they are +registered. Toggling this to ``False`` removes pandas' formatters (:issue:`18301`) .. _whatsnew_0211.enhancements: diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index 59578b96807e1..cb7054d5442bb 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -480,3 +480,31 @@ def use_inf_as_na_cb(key): cf.register_option( 'engine', 'auto', parquet_engine_doc, validator=is_one_of_factory(['auto', 'pyarrow', 'fastparquet'])) + +# -------- +# Plotting +# --------- + +register_formatter_doc = """ +: bool + Whether to register formatters with matplotlib's units registry for + dates, times, datetimes, and Periods. +""" + + +def register_formatter_cb(key): + from matplotlib import units + from pandas.plotting._converter import ( + register, get_pairs) + + if cf.get_option(key): + register() + else: + for type_, cls in get_pairs(): + if isinstance(units.registry.get(type_), cls): + units.registry.pop(type_) + + +with cf.config_prefix("plotting.matplotlib"): + cf.register_option("register_formatters", True, register_formatter_doc, + validator=bool, cb=register_formatter_cb) diff --git a/pandas/plotting/__init__.py b/pandas/plotting/__init__.py index 8f98e297e3e66..85cb5a5a420d6 100644 --- a/pandas/plotting/__init__.py +++ b/pandas/plotting/__init__.py @@ -11,3 +11,4 @@ from pandas.plotting._core import boxplot from pandas.plotting._style import plot_params from pandas.plotting._tools import table +from pandas.plotting import _converter as converter diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index fafa590020bb0..7a5611fdc6f09 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -48,6 +48,16 @@ _WARN = True +def get_pairs(): + return [ + (lib.Timestamp, DatetimeConverter), + (Period, PeriodConverter), + (pydt.datetime, DatetimeConverter), + (pydt.date, DatetimeConverter), + (pydt.time, TimeConverter), + (np.datetime64, DatetimeConverter), + ] + def register(warn=False): global _WARN @@ -55,12 +65,8 @@ def register(warn=False): if not warn: _WARN = False - units.registry[lib.Timestamp] = DatetimeConverter() - units.registry[Period] = PeriodConverter() - units.registry[pydt.datetime] = DatetimeConverter() - units.registry[pydt.date] = DatetimeConverter() - units.registry[pydt.time] = TimeConverter() - units.registry[np.datetime64] = DatetimeConverter() + for type_, cls in get_pairs(): + units.registry[type_] = cls() def _check_implicitly_registered(): diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 11bd2ed25229c..6fdf5bc83e935 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -11,6 +11,7 @@ from pandas.util._decorators import cache_readonly from pandas.core.base import PandasObject +from pandas.core.config import get_option from pandas.core.dtypes.missing import isna, notna, remove_na_arraylike from pandas.core.dtypes.common import ( is_list_like, @@ -41,15 +42,12 @@ format_date_labels) try: - # We want to warn if the formatter is called implicitly - # by `ax.plot(datetimeindex, another)` - # We don't want to warn if - # * Series.plot - # * User calls `register` explicitly from pandas.plotting import _converter - _converter.register(warn=True) except ImportError: pass +else: + if get_option('plotting.matplotlib.register_formatters'): + _converter.register(warn=True) def _get_standard_kind(kind): diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 951a277388c11..c19531b3c97b6 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -4,6 +4,7 @@ import numpy as np from pandas import Timestamp, Period, Index, date_range, Series from pandas.compat import u +import pandas.core.config as cf import pandas.util.testing as tm from pandas.tseries.offsets import Second, Milli, Micro, Day from pandas.compat.numpy import np_datetime64_compat @@ -15,7 +16,7 @@ def test_timtetonum_accepts_unicode(): assert (converter.time2num("00:01") == converter.time2num(u("00:01"))) -class TestImplicitRegistration(object): +class TestRegistration(object): def test_warns(self): plt = pytest.importorskip("matplotlib.pyplot") @@ -37,7 +38,7 @@ def test_registering_no_warning(self): s = Series(range(12), index=date_range('2017', periods=12)) _, ax = plt.subplots() - # Set to the "no-warn" state, in case this isn't the first test run + # Set to the "warn" state, in case this isn't the first test run converter._WARN = True converter.register() with tm.assert_produces_warning(None) as w: @@ -48,13 +49,41 @@ def test_registering_no_warning(self): def test_pandas_plots_register(self): pytest.importorskip("matplotlib.pyplot") s = Series(range(12), index=date_range('2017', periods=12)) - # Set to the "no-warn" state, in case this isn't the first test run + # Set to the "warn" state, in case this isn't the first test run converter._WARN = True with tm.assert_produces_warning(None) as w: s.plot() assert len(w) == 0 + def test_matplotlib_formatters(self): + units = pytest.importorskip("matplotlib.units") + assert Timestamp in units.registry + + ctx = cf.option_context("plotting.matplotlib.register_formatters", + False) + with ctx: + assert Timestamp not in units.registry + + assert Timestamp in units.registry + + def test_option_no_warning(self): + pytest.importorskip("matplotlib.pyplot") + ctx = cf.option_context("plotting.matplotlib.register_formatters", + False) + plt = pytest.importorskip("matplotlib.pyplot") + s = Series(range(12), index=date_range('2017', periods=12)) + _, ax = plt.subplots() + + # Set to the "warn" state, in case this isn't the first test run + converter._WARN = True + converter.register() + with ctx: + with tm.assert_produces_warning(None) as w: + ax.plot(s.index, s.values) + + assert len(w) == 0 + class TestDateTimeConverter(object): diff --git a/pandas/tseries/converter.py b/pandas/tseries/converter.py index df603c4d880d8..adf1197419f13 100644 --- a/pandas/tseries/converter.py +++ b/pandas/tseries/converter.py @@ -1,6 +1,6 @@ # flake8: noqa -from pandas.plotting._converter import (register, time2num, +from pandas.plotting._converter import (time2num, TimeConverter, TimeFormatter, PeriodConverter, get_datevalue, DatetimeConverter, @@ -9,3 +9,13 @@ MilliSecondLocator, get_finder, TimeSeries_DateLocator, TimeSeries_DateFormatter) + + +def register(): + import warnings + + msg = ("'pandas.tseries.converter' has been moved to 'pandas.plotting'. " + "Update your import to 'from pandas.plotting import converter"') + warnings.warn(msg, FutureWarning, stacklevel=2) + from pandas.plotting import _converter + return _converter.register() From 3766b7823731f36e2a8a04125ab59324ff256ffe Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 15 Nov 2017 12:00:26 -0600 Subject: [PATCH 05/24] Remove move --- pandas/tseries/converter.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pandas/tseries/converter.py b/pandas/tseries/converter.py index adf1197419f13..df603c4d880d8 100644 --- a/pandas/tseries/converter.py +++ b/pandas/tseries/converter.py @@ -1,6 +1,6 @@ # flake8: noqa -from pandas.plotting._converter import (time2num, +from pandas.plotting._converter import (register, time2num, TimeConverter, TimeFormatter, PeriodConverter, get_datevalue, DatetimeConverter, @@ -9,13 +9,3 @@ MilliSecondLocator, get_finder, TimeSeries_DateLocator, TimeSeries_DateFormatter) - - -def register(): - import warnings - - msg = ("'pandas.tseries.converter' has been moved to 'pandas.plotting'. " - "Update your import to 'from pandas.plotting import converter"') - warnings.warn(msg, FutureWarning, stacklevel=2) - from pandas.plotting import _converter - return _converter.register() From 5be9793dbdfe8c77d394b263f494061b8f25d0cd Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 15 Nov 2017 20:26:48 -0600 Subject: [PATCH 06/24] Handle no matplotlib --- pandas/plotting/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/plotting/__init__.py b/pandas/plotting/__init__.py index 85cb5a5a420d6..725dd07bc7587 100644 --- a/pandas/plotting/__init__.py +++ b/pandas/plotting/__init__.py @@ -11,4 +11,7 @@ from pandas.plotting._core import boxplot from pandas.plotting._style import plot_params from pandas.plotting._tools import table -from pandas.plotting import _converter as converter +try: + from pandas.plotting import _converter as converter +except ImportError: + pass From 9364f60ef5d098e35dbf433b9f031567542ab05f Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 17 Nov 2017 09:26:42 -0600 Subject: [PATCH 07/24] Cleanup --- doc/source/whatsnew/v0.21.1.txt | 16 ++++++++-------- pandas/plotting/_converter.py | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index 2db7d85ba082a..bd5bc66672369 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -12,7 +12,7 @@ of bug fixes. We recommend that all users upgrade to this version. Restore Matplotlib datetime Converter Registration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pandas implements some matplotlib converters for nicely formatting the axis +Pandas implements some matplotlib formatters for nicely formatting the axis labels on plots with ``datetime`` or ``Period`` values. Prior to pandas 0.21.0, these were implicitly registered with matplotlib, as a side effect of ``import pandas``. In pandas 0.21.0, we required users to explicitly register @@ -24,8 +24,8 @@ the converter. >>> converter.register() This caused problems for some users, so we're temporarily reverting that change; -pandas will again register the converters on import. Using the converters -without explicitly registering the converters will cause a ``FutureWarning``: +pandas will again register the formatters on import. Using the formatters +without explicitly registering the formatters will cause a ``FutureWarning``: .. code-block:: python @@ -36,18 +36,18 @@ without explicitly registering the converters will cause a ``FutureWarning``: FutureWarning: Using an implicitly registered datetime converter for a matplotlib plotting method. The converter was registered by pandas on import. Future versions of pandas will require you to explicitly register matplotlib - converters. + formatters. - To register the converters: + To register the formatters: >>> from pandas.tseries import converter >>> converter.register() -As the error message says, you'll need to register the converters if you intend +As the error message says, you'll need to register the formatters if you intend to use them with matplotlib plotting functions. Pandas plotting functions, such -as ``Series.plot``, will register them for you calling ``converter.register()`` +as ``Series.plot``, will register them for you; calling ``converter.register()`` first is not necessary. -Finally, control the formatters, we've added a new option: +Finally, we've added a new option to control the formatters, ``pd.options.plotting.matplotlib.register_formatters``. By default, they are registered. Toggling this to ``False`` removes pandas' formatters (:issue:`18301`) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 7a5611fdc6f09..fa42343cc642d 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -48,6 +48,7 @@ _WARN = True + def get_pairs(): return [ (lib.Timestamp, DatetimeConverter), From 39602f94741d4ae59f44c924a52fbd80cf3c10f5 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 17 Nov 2017 09:36:18 -0600 Subject: [PATCH 08/24] Test no register --- pandas/tests/plotting/test_converter.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index c19531b3c97b6..48fac7b04bc16 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -75,7 +75,15 @@ def test_option_no_warning(self): s = Series(range(12), index=date_range('2017', periods=12)) _, ax = plt.subplots() - # Set to the "warn" state, in case this isn't the first test run + converter._WARN = True + # Test without registering first, no warning + with ctx: + with tm.assert_produces_warning(None) as w: + ax.plot(s.index, s.values) + + assert len(w) == 0 + + # Now test with registering converter._WARN = True converter.register() with ctx: From eb6388e7bd0b7f6a6ff79cecef20f37352f33d80 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 18 Nov 2017 06:13:26 -0600 Subject: [PATCH 09/24] Restore original state --- pandas/core/config_init.py | 8 ++--- pandas/plotting/_converter.py | 45 ++++++++++++++++++++++++- pandas/tests/plotting/test_converter.py | 26 ++++++++++++++ 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index cb7054d5442bb..ff4396c4a563a 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -493,16 +493,12 @@ def use_inf_as_na_cb(key): def register_formatter_cb(key): - from matplotlib import units - from pandas.plotting._converter import ( - register, get_pairs) + from pandas.plotting._converter import register, deregister if cf.get_option(key): register() else: - for type_, cls in get_pairs(): - if isinstance(units.registry.get(type_), cls): - units.registry.pop(type_) + deregister() with cf.config_prefix("plotting.matplotlib"): diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index fa42343cc642d..b7008904594ae 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -47,6 +47,7 @@ MUSEC_PER_DAY = 1e6 * SEC_PER_DAY _WARN = True +_mpl_units = {} def get_pairs(): @@ -61,13 +62,55 @@ def get_pairs(): def register(warn=False): + """Register Pandas Formatters and Converters with matplotlib + + This function modifies the global ``matplotlib.units.registry`` + dictionary. Pandas adds custom formatters for + + * pd.Timestamp + * pd.Period + * np.datetime64 + * datetime.datetime + * datetime.date + * datetime.time + + See Also + -------- + deregister + """ global _WARN if not warn: _WARN = False for type_, cls in get_pairs(): - units.registry[type_] = cls() + converter = cls() + previous = units.registry.setdefault(type_, converter) + if previous is not converter: + _mpl_units[type_] = previous + + +def deregister(): + """Remove pandas' formatters and converters + + Removes the custom formatters added by :func:`register`. This attempts + to set the state of the registry back to the state before pandas + registered its own units. Formatters for our own types like Timestamp + and Period are removed completely. Formatters like datetime.datetime, + which :func:`register` overwrites, are placed back to their original + value. + + See Also + -------- + register + """ + for type_, cls in get_pairs(): + if isinstance(units.registry.get(type_), cls): + units.registry.pop(type_) + + # restore the old keys + for unit, formatter in _mpl_units.items(): + units.registry[unit] = formatter def _check_implicitly_registered(): diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 48fac7b04bc16..79170de53f368 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -92,6 +92,32 @@ def test_option_no_warning(self): assert len(w) == 0 + def test_registry_resets(self): + units = pytest.importorskip("matplotlib.units") + dates = pytest.importorskip("matplotlib.dates") + + # make a copy, to reset to + original = dict(units.registry) + + try: + # get to a known state + units.registry.clear() + date_converter = dates.DateConverter() + units.registry[datetime] = date_converter + units.registry[date] = date_converter + + converter.register() + assert len(units.registry) > 2 + converter.deregister() + assert len(units.registry) == 2 + assert units.registry[date] is date_converter + + finally: + # restore original stater + units.registry.clear() + for k, v in original.items(): + units.registry[k] = v + class TestDateTimeConverter(object): From 444bc4dfd938981ecca28c2463e5bc6a0a1a2793 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 18 Nov 2017 06:30:23 -0600 Subject: [PATCH 10/24] Added deregister --- doc/source/api.rst | 11 +++++++++++ doc/source/options.rst | 2 ++ pandas/plotting/__init__.py | 3 ++- pandas/plotting/_converter.py | 10 +++++++--- pandas/tests/plotting/test_converter.py | 3 +-- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/doc/source/api.rst b/doc/source/api.rst index ce88aed91823c..4828882ce8369 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -2218,6 +2218,17 @@ Style Export and Import Styler.export Styler.use +Plotting +~~~~~~~~ + +.. currentmodule:: pandas + +.. autosummary:: + :toctree: generated/ + + plotting.register_converters + plotting.deregister_converters + .. currentmodule:: pandas General utility functions diff --git a/doc/source/options.rst b/doc/source/options.rst index 2da55a5a658a4..f6766686f56f1 100644 --- a/doc/source/options.rst +++ b/doc/source/options.rst @@ -430,6 +430,8 @@ compute.use_bottleneck True Use the bottleneck library to a computation if it is installed. compute.use_numexpr True Use the numexpr library to accelerate computation if it is installed. +plotting.mpl.formatters True Register custom formatters with + matplotlib. Set to false to deregister. =================================== ============ ================================== diff --git a/pandas/plotting/__init__.py b/pandas/plotting/__init__.py index 725dd07bc7587..0862717a8c36c 100644 --- a/pandas/plotting/__init__.py +++ b/pandas/plotting/__init__.py @@ -12,6 +12,7 @@ from pandas.plotting._style import plot_params from pandas.plotting._tools import table try: - from pandas.plotting import _converter as converter + from pandas.plotting._converter import register as register_converters + from pandas.plotting._converter import deregister as deregister_converters except ImportError: pass diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index b7008904594ae..4cd0fd0523ce8 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -85,9 +85,10 @@ def register(warn=False): for type_, cls in get_pairs(): converter = cls() - previous = units.registry.setdefault(type_, converter) - if previous is not converter: + if type_ in units.registry: + previous = units.registry[type_] _mpl_units[type_] = previous + units.registry[type_] = converter def deregister(): @@ -110,7 +111,10 @@ def deregister(): # restore the old keys for unit, formatter in _mpl_units.items(): - units.registry[unit] = formatter + if type(formatter) not in {DatetimeConverter, PeriodConverter, + TimeConverter}: + # make it idempotent by excluding ours. + units.registry[unit] = formatter def _check_implicitly_registered(): diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 79170de53f368..01d20002b00c9 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -107,9 +107,8 @@ def test_registry_resets(self): units.registry[date] = date_converter converter.register() - assert len(units.registry) > 2 + assert not units.registry[date] is date_converter converter.deregister() - assert len(units.registry) == 2 assert units.registry[date] is date_converter finally: From ec7f7e1f27ce9cea700f3e45ac6f41556d79af4d Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 18 Nov 2017 06:45:46 -0600 Subject: [PATCH 11/24] Doc, naming --- doc/source/whatsnew/v0.21.1.txt | 32 +++++++++++++++---------- pandas/core/config_init.py | 22 +++++++++-------- pandas/plotting/_converter.py | 21 ++++++++-------- pandas/plotting/_core.py | 2 +- pandas/tests/plotting/test_converter.py | 6 ++--- 5 files changed, 45 insertions(+), 38 deletions(-) diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index bd5bc66672369..20b1f6643557b 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -12,7 +12,7 @@ of bug fixes. We recommend that all users upgrade to this version. Restore Matplotlib datetime Converter Registration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pandas implements some matplotlib formatters for nicely formatting the axis +Pandas implements some matplotlib converters for nicely formatting the axis labels on plots with ``datetime`` or ``Period`` values. Prior to pandas 0.21.0, these were implicitly registered with matplotlib, as a side effect of ``import pandas``. In pandas 0.21.0, we required users to explicitly register @@ -20,12 +20,12 @@ the converter. .. code-block:: python - >>> from pandas.tseries import converter - >>> converter.register() + >>> from pandas.plotting import register_converters + >>> register_converters() This caused problems for some users, so we're temporarily reverting that change; -pandas will again register the formatters on import. Using the formatters -without explicitly registering the formatters will cause a ``FutureWarning``: +pandas will again register the converters on import. Using the converters +without explicitly registering them will cause a ``FutureWarning``: .. code-block:: python @@ -36,21 +36,27 @@ without explicitly registering the formatters will cause a ``FutureWarning``: FutureWarning: Using an implicitly registered datetime converter for a matplotlib plotting method. The converter was registered by pandas on import. Future versions of pandas will require you to explicitly register matplotlib - formatters. + converters. - To register the formatters: - >>> from pandas.tseries import converter - >>> converter.register() + To register the converters: + >>> from pandas.plotting import register_converters + >>> register_converters() -As the error message says, you'll need to register the formatters if you intend +As the error message says, you'll need to register the converters if you intend to use them with matplotlib plotting functions. Pandas plotting functions, such -as ``Series.plot``, will register them for you; calling ``converter.register()`` +as ``Series.plot``, will register them for you; calling ``register_converters`` first is not necessary. -Finally, we've added a new option to control the formatters, -``pd.options.plotting.matplotlib.register_formatters``. By default, they are +We've added a new option to control the converters, +``pd.options.plotting.mpl.converters``. By default, they are registered. Toggling this to ``False`` removes pandas' formatters (:issue:`18301`) +We're working with the matplotlib developers to make this easier. We're trying +to balance user convenience (automatically registering the converters) with +import performance and best practices (importing pandas shouldn't have the side +effect of overwriting any custom converters you've already set). Apologies for +any bumps along the way. + .. _whatsnew_0211.enhancements: New features diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index ff4396c4a563a..0e590600b743b 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -485,22 +485,24 @@ def use_inf_as_na_cb(key): # Plotting # --------- -register_formatter_doc = """ +register_converter_doc = """ : bool - Whether to register formatters with matplotlib's units registry for - dates, times, datetimes, and Periods. + Whether to register converters with matplotlib's units registry for + dates, times, datetimes, and Periods. Toggling to False will remove + the converters, replacing them with any converters that pandas + overwrote. """ -def register_formatter_cb(key): - from pandas.plotting._converter import register, deregister +def register_converter_cb(key): + from pandas.plotting import register_converters, deregister_converters if cf.get_option(key): - register() + register_converters() else: - deregister() + deregister_converters() -with cf.config_prefix("plotting.matplotlib"): - cf.register_option("register_formatters", True, register_formatter_doc, - validator=bool, cb=register_formatter_cb) +with cf.config_prefix("plotting.mpl"): + cf.register_option("converters", True, register_converter_doc, + validator=bool, cb=register_converter_cb) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 4cd0fd0523ce8..cd02b1da531fa 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -65,7 +65,7 @@ def register(warn=False): """Register Pandas Formatters and Converters with matplotlib This function modifies the global ``matplotlib.units.registry`` - dictionary. Pandas adds custom formatters for + dictionary. Pandas adds custom converters for * pd.Timestamp * pd.Period @@ -94,19 +94,20 @@ def register(warn=False): def deregister(): """Remove pandas' formatters and converters - Removes the custom formatters added by :func:`register`. This attempts - to set the state of the registry back to the state before pandas - registered its own units. Formatters for our own types like Timestamp - and Period are removed completely. Formatters like datetime.datetime, - which :func:`register` overwrites, are placed back to their original - value. + Removes the custom converters added by :func:`register`. This + attempts to set the state of the registry back to the state before + pandas registered its own units. Conveters for our own types like + Timestamp and Period are removed completely. Converters for, e.g., + datetime.datetime, which :func:`register` overwrites, are placed + back to their original value. See Also -------- register """ for type_, cls in get_pairs(): - if isinstance(units.registry.get(type_), cls): + # We use type to catch our classes directly, no inheritance + if type(units.registry.get(type_)) is cls: units.registry.pop(type_) # restore the old keys @@ -126,8 +127,8 @@ def _check_implicitly_registered(): "by pandas on import. Future versions of pandas will require " "you to explicitly register matplotlib converters.\n\n" "To register the converters:\n\t" - ">>> from pandas.tseries import converter\n\t" - ">>> converter.register()") + ">>> from pandas.plotting import register_conveters\n\t" + ">>> register_conveters.register()") warnings.warn(msg, FutureWarning) _WARN = False diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 6fdf5bc83e935..585bf2e444d48 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -46,7 +46,7 @@ except ImportError: pass else: - if get_option('plotting.matplotlib.register_formatters'): + if get_option('plotting.mpl.converters'): _converter.register(warn=True) diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 01d20002b00c9..67dc5ccea4b84 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -60,8 +60,7 @@ def test_matplotlib_formatters(self): units = pytest.importorskip("matplotlib.units") assert Timestamp in units.registry - ctx = cf.option_context("plotting.matplotlib.register_formatters", - False) + ctx = cf.option_context("plotting.mpl.converters", False) with ctx: assert Timestamp not in units.registry @@ -69,8 +68,7 @@ def test_matplotlib_formatters(self): def test_option_no_warning(self): pytest.importorskip("matplotlib.pyplot") - ctx = cf.option_context("plotting.matplotlib.register_formatters", - False) + ctx = cf.option_context("plotting.mpl.converters", False) plt = pytest.importorskip("matplotlib.pyplot") s = Series(range(12), index=date_range('2017', periods=12)) _, ax = plt.subplots() From 58bf2f631710cd942104fc19bec0e08395b63e55 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 18 Nov 2017 06:49:01 -0600 Subject: [PATCH 12/24] Naming --- doc/source/options.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/options.rst b/doc/source/options.rst index f6766686f56f1..b94e9a492dc72 100644 --- a/doc/source/options.rst +++ b/doc/source/options.rst @@ -430,8 +430,8 @@ compute.use_bottleneck True Use the bottleneck library to a computation if it is installed. compute.use_numexpr True Use the numexpr library to accelerate computation if it is installed. -plotting.mpl.formatters True Register custom formatters with - matplotlib. Set to false to deregister. +plotting.mpl.converters True Register custom converters with + matplotlib. Set to false to de-register. =================================== ============ ================================== From 5ee385e9af84bb75ecd1de3e03759db7a1ddb323 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 18 Nov 2017 06:57:24 -0600 Subject: [PATCH 13/24] Added deprecation --- pandas/tests/plotting/test_converter.py | 8 ++++++++ pandas/tseries/converter.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 67dc5ccea4b84..5db7da6582689 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -115,6 +115,14 @@ def test_registry_resets(self): for k, v in original.items(): units.registry[k] = v + def test_old_import_warns(self): + with tm.assert_produces_warning(FutureWarning) as w: + from pandas.tseries import converter + converter.register() + + assert len(w) + assert 'pandas.plotting.register_converters' in str(w[0].message) + class TestDateTimeConverter(object): diff --git a/pandas/tseries/converter.py b/pandas/tseries/converter.py index df603c4d880d8..7028672aefaf0 100644 --- a/pandas/tseries/converter.py +++ b/pandas/tseries/converter.py @@ -1,6 +1,7 @@ # flake8: noqa +import warnings -from pandas.plotting._converter import (register, time2num, +from pandas.plotting._converter import (time2num, TimeConverter, TimeFormatter, PeriodConverter, get_datevalue, DatetimeConverter, @@ -9,3 +10,12 @@ MilliSecondLocator, get_finder, TimeSeries_DateLocator, TimeSeries_DateFormatter) + + +def register(): + from pandas.plotting._converter import register as register_ + msg = ("'pandas.tseries.converter.register' has been moved and renamed to " + "'pandas.plotting.register_converters'. ") + warnings.warn(msg, FutureWarning, stacklevel=2) + register_() + From ca6c9454fef2d1fe2a7c200d85af3aaa0e118861 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 18 Nov 2017 06:57:53 -0600 Subject: [PATCH 14/24] PEP8 --- pandas/tseries/converter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tseries/converter.py b/pandas/tseries/converter.py index 7028672aefaf0..c3e2fa3df0125 100644 --- a/pandas/tseries/converter.py +++ b/pandas/tseries/converter.py @@ -18,4 +18,3 @@ def register(): "'pandas.plotting.register_converters'. ") warnings.warn(msg, FutureWarning, stacklevel=2) register_() - From 77f76d261e7fccd9667f87bd563538effa63fabe Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 18 Nov 2017 11:07:16 -0600 Subject: [PATCH 15/24] Fix typos --- doc/source/options.rst | 2 +- pandas/core/config_init.py | 3 +-- pandas/plotting/_converter.py | 12 ++++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/doc/source/options.rst b/doc/source/options.rst index b94e9a492dc72..bbc0cd6b5a2be 100644 --- a/doc/source/options.rst +++ b/doc/source/options.rst @@ -431,7 +431,7 @@ compute.use_bottleneck True Use the bottleneck library to a compute.use_numexpr True Use the numexpr library to accelerate computation if it is installed. plotting.mpl.converters True Register custom converters with - matplotlib. Set to false to de-register. + matplotlib. Set to False to de-register. =================================== ============ ================================== diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index 0e590600b743b..8dffdcb6f4077 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -489,8 +489,7 @@ def use_inf_as_na_cb(key): : bool Whether to register converters with matplotlib's units registry for dates, times, datetimes, and Periods. Toggling to False will remove - the converters, replacing them with any converters that pandas - overwrote. + the converters, restoring any converters that pandas overwrote. """ diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index cd02b1da531fa..99685a7140523 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -96,10 +96,10 @@ def deregister(): Removes the custom converters added by :func:`register`. This attempts to set the state of the registry back to the state before - pandas registered its own units. Conveters for our own types like - Timestamp and Period are removed completely. Converters for, e.g., - datetime.datetime, which :func:`register` overwrites, are placed - back to their original value. + pandas registered its own units. Converters for pandas' own types like + Timestamp and Period are removed completely. Converters for types + pandas overwrites, like ``datetime.datetime``, are restored to their + original value. See Also -------- @@ -127,8 +127,8 @@ def _check_implicitly_registered(): "by pandas on import. Future versions of pandas will require " "you to explicitly register matplotlib converters.\n\n" "To register the converters:\n\t" - ">>> from pandas.plotting import register_conveters\n\t" - ">>> register_conveters.register()") + ">>> from pandas.plotting import register_converters\n\t" + ">>> register_converters()") warnings.warn(msg, FutureWarning) _WARN = False From ecf4154580e9ed97893436f246b658bb00adc443 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 20 Nov 2017 07:09:59 -0600 Subject: [PATCH 16/24] Rename it all --- doc/source/options.rst | 320 ++++++++++++------------ doc/source/whatsnew/v0.21.1.txt | 10 +- pandas/core/config_init.py | 11 +- pandas/plotting/__init__.py | 6 +- pandas/plotting/_converter.py | 11 +- pandas/plotting/_core.py | 2 +- pandas/tests/plotting/test_converter.py | 16 +- 7 files changed, 193 insertions(+), 183 deletions(-) diff --git a/doc/source/options.rst b/doc/source/options.rst index bbc0cd6b5a2be..be3a3d9a55534 100644 --- a/doc/source/options.rst +++ b/doc/source/options.rst @@ -273,166 +273,166 @@ Options are 'right', and 'left'. Available Options ----------------- -=================================== ============ ================================== -Option Default Function -=================================== ============ ================================== -display.chop_threshold None If set to a float value, all float - values smaller then the given - threshold will be displayed as - exactly 0 by repr and friends. -display.colheader_justify right Controls the justification of - column headers. used by DataFrameFormatter. -display.column_space 12 No description available. -display.date_dayfirst False When True, prints and parses dates - with the day first, eg 20/01/2005 -display.date_yearfirst False When True, prints and parses dates - with the year first, eg 2005/01/20 -display.encoding UTF-8 Defaults to the detected encoding - of the console. Specifies the encoding - to be used for strings returned by - to_string, these are generally strings - meant to be displayed on the console. -display.expand_frame_repr True Whether to print out the full DataFrame - repr for wide DataFrames across - multiple lines, `max_columns` is - still respected, but the output will - wrap-around across multiple "pages" - if its width exceeds `display.width`. -display.float_format None The callable should accept a floating - point number and return a string with - the desired format of the number. - This is used in some places like - SeriesFormatter. - See core.format.EngFormatter for an example. -display.large_repr truncate For DataFrames exceeding max_rows/max_cols, - the repr (and HTML repr) can show - a truncated table (the default), - or switch to the view from df.info() - (the behaviour in earlier versions of pandas). - allowable settings, ['truncate', 'info'] -display.latex.repr False Whether to produce a latex DataFrame - representation for jupyter frontends - that support it. -display.latex.escape True Escapes special characters in DataFrames, when - using the to_latex method. -display.latex.longtable False Specifies if the to_latex method of a DataFrame - uses the longtable format. -display.latex.multicolumn True Combines columns when using a MultiIndex -display.latex.multicolumn_format 'l' Alignment of multicolumn labels -display.latex.multirow False Combines rows when using a MultiIndex. - Centered instead of top-aligned, - separated by clines. -display.max_columns 20 max_rows and max_columns are used - in __repr__() methods to decide if - to_string() or info() is used to - render an object to a string. In - case python/IPython is running in - a terminal this can be set to 0 and - pandas will correctly auto-detect - the width the terminal and swap to - a smaller format in case all columns - would not fit vertically. The IPython - notebook, IPython qtconsole, or IDLE - do not run in a terminal and hence - it is not possible to do correct - auto-detection. 'None' value means - unlimited. -display.max_colwidth 50 The maximum width in characters of - a column in the repr of a pandas - data structure. When the column overflows, - a "..." placeholder is embedded in - the output. -display.max_info_columns 100 max_info_columns is used in DataFrame.info - method to decide if per column information - will be printed. -display.max_info_rows 1690785 df.info() will usually show null-counts - for each column. For large frames - this can be quite slow. max_info_rows - and max_info_cols limit this null - check only to frames with smaller - dimensions then specified. -display.max_rows 60 This sets the maximum number of rows - pandas should output when printing - out various output. For example, - this value determines whether the - repr() for a dataframe prints out - fully or just a summary repr. - 'None' value means unlimited. -display.max_seq_items 100 when pretty-printing a long sequence, - no more then `max_seq_items` will - be printed. If items are omitted, - they will be denoted by the addition - of "..." to the resulting string. - If set to None, the number of items - to be printed is unlimited. -display.memory_usage True This specifies if the memory usage of - a DataFrame should be displayed when the - df.info() method is invoked. -display.multi_sparse True "Sparsify" MultiIndex display (don't - display repeated elements in outer - levels within groups) -display.notebook_repr_html True When True, IPython notebook will - use html representation for - pandas objects (if it is available). -display.pprint_nest_depth 3 Controls the number of nested levels - to process when pretty-printing -display.precision 6 Floating point output precision in - terms of number of places after the - decimal, for regular formatting as well - as scientific notation. Similar to - numpy's ``precision`` print option -display.show_dimensions truncate Whether to print out dimensions - at the end of DataFrame repr. - If 'truncate' is specified, only - print out the dimensions if the - frame is truncated (e.g. not display - all rows and/or columns) -display.width 80 Width of the display in characters. - In case python/IPython is running in - a terminal this can be set to None - and pandas will correctly auto-detect - the width. Note that the IPython notebook, - IPython qtconsole, or IDLE do not run in a - terminal and hence it is not possible - to correctly detect the width. -display.html.table_schema False Whether to publish a Table Schema - representation for frontends that - support it. -display.html.border 1 A ``border=value`` attribute is - inserted in the ```` tag - for the DataFrame HTML repr. -io.excel.xls.writer xlwt The default Excel writer engine for - 'xls' files. -io.excel.xlsm.writer openpyxl The default Excel writer engine for - 'xlsm' files. Available options: - 'openpyxl' (the default). -io.excel.xlsx.writer openpyxl The default Excel writer engine for - 'xlsx' files. -io.hdf.default_format None default format writing format, if - None, then put will default to - 'fixed' and append will default to - 'table' -io.hdf.dropna_table True drop ALL nan rows when appending - to a table -io.parquet.engine None The engine to use as a default for - parquet reading and writing. If None - then try 'pyarrow' and 'fastparquet' -mode.chained_assignment warn Raise an exception, warn, or no - action if trying to use chained - assignment, The default is warn -mode.sim_interactive False Whether to simulate interactive mode - for purposes of testing. -mode.use_inf_as_na False True means treat None, NaN, -INF, - INF as NA (old way), False means - None and NaN are null, but INF, -INF - are not NA (new way). -compute.use_bottleneck True Use the bottleneck library to accelerate - computation if it is installed. -compute.use_numexpr True Use the numexpr library to accelerate - computation if it is installed. -plotting.mpl.converters True Register custom converters with - matplotlib. Set to False to de-register. -=================================== ============ ================================== +======================================= ============ ================================== +Option Default Function +======================================= ============ ================================== +display.chop_threshold None If set to a float value, all float + values smaller then the given + threshold will be displayed as + exactly 0 by repr and friends. +display.colheader_justify right Controls the justification of + column headers. used by DataFrameFormatter. +display.column_space 12 No description available. +display.date_dayfirst False When True, prints and parses dates + with the day first, eg 20/01/2005 +display.date_yearfirst False When True, prints and parses dates + with the year first, eg 2005/01/20 +display.encoding UTF-8 Defaults to the detected encoding + of the console. Specifies the encoding + to be used for strings returned by + to_string, these are generally strings + meant to be displayed on the console. +display.expand_frame_repr True Whether to print out the full DataFrame + repr for wide DataFrames across + multiple lines, `max_columns` is + still respected, but the output will + wrap-around across multiple "pages" + if its width exceeds `display.width`. +display.float_format None The callable should accept a floating + point number and return a string with + the desired format of the number. + This is used in some places like + SeriesFormatter. + See core.format.EngFormatter for an example. +display.large_repr truncate For DataFrames exceeding max_rows/max_cols, + the repr (and HTML repr) can show + a truncated table (the default), + or switch to the view from df.info() + (the behaviour in earlier versions of pandas). + allowable settings, ['truncate', 'info'] +display.latex.repr False Whether to produce a latex DataFrame + representation for jupyter frontends + that support it. +display.latex.escape True Escapes special characters in DataFrames, when + using the to_latex method. +display.latex.longtable False Specifies if the to_latex method of a DataFrame + uses the longtable format. +display.latex.multicolumn True Combines columns when using a MultiIndex +display.latex.multicolumn_format 'l' Alignment of multicolumn labels +display.latex.multirow False Combines rows when using a MultiIndex. + Centered instead of top-aligned, + separated by clines. +display.max_columns 20 max_rows and max_columns are used + in __repr__() methods to decide if + to_string() or info() is used to + render an object to a string. In + case python/IPython is running in + a terminal this can be set to 0 and + pandas will correctly auto-detect + the width the terminal and swap to + a smaller format in case all columns + would not fit vertically. The IPython + notebook, IPython qtconsole, or IDLE + do not run in a terminal and hence + it is not possible to do correct + auto-detection. 'None' value means + unlimited. +display.max_colwidth 50 The maximum width in characters of + a column in the repr of a pandas + data structure. When the column overflows, + a "..." placeholder is embedded in + the output. +display.max_info_columns 100 max_info_columns is used in DataFrame.info + method to decide if per column information + will be printed. +display.max_info_rows 1690785 df.info() will usually show null-counts + for each column. For large frames + this can be quite slow. max_info_rows + and max_info_cols limit this null + check only to frames with smaller + dimensions then specified. +display.max_rows 60 This sets the maximum number of rows + pandas should output when printing + out various output. For example, + this value determines whether the + repr() for a dataframe prints out + fully or just a summary repr. + 'None' value means unlimited. +display.max_seq_items 100 when pretty-printing a long sequence, + no more then `max_seq_items` will + be printed. If items are omitted, + they will be denoted by the addition + of "..." to the resulting string. + If set to None, the number of items + to be printed is unlimited. +display.memory_usage True This specifies if the memory usage of + a DataFrame should be displayed when the + df.info() method is invoked. +display.multi_sparse True "Sparsify" MultiIndex display (don't + display repeated elements in outer + levels within groups) +display.notebook_repr_html True When True, IPython notebook will + use html representation for + pandas objects (if it is available). +display.pprint_nest_depth 3 Controls the number of nested levels + to process when pretty-printing +display.precision 6 Floating point output precision in + terms of number of places after the + decimal, for regular formatting as well + as scientific notation. Similar to + numpy's ``precision`` print option +display.show_dimensions truncate Whether to print out dimensions + at the end of DataFrame repr. + If 'truncate' is specified, only + print out the dimensions if the + frame is truncated (e.g. not display + all rows and/or columns) +display.width 80 Width of the display in characters. + In case python/IPython is running in + a terminal this can be set to None + and pandas will correctly auto-detect + the width. Note that the IPython notebook, + IPython qtconsole, or IDLE do not run in a + terminal and hence it is not possible + to correctly detect the width. +display.html.table_schema False Whether to publish a Table Schema + representation for frontends that + support it. +display.html.border 1 A ``border=value`` attribute is + inserted in the ``
`` tag + for the DataFrame HTML repr. +io.excel.xls.writer xlwt The default Excel writer engine for + 'xls' files. +io.excel.xlsm.writer openpyxl The default Excel writer engine for + 'xlsm' files. Available options: + 'openpyxl' (the default). +io.excel.xlsx.writer openpyxl The default Excel writer engine for + 'xlsx' files. +io.hdf.default_format None default format writing format, if + None, then put will default to + 'fixed' and append will default to + 'table' +io.hdf.dropna_table True drop ALL nan rows when appending + to a table +io.parquet.engine None The engine to use as a default for + parquet reading and writing. If None + then try 'pyarrow' and 'fastparquet' +mode.chained_assignment warn Raise an exception, warn, or no + action if trying to use chained + assignment, The default is warn +mode.sim_interactive False Whether to simulate interactive mode + for purposes of testing. +mode.use_inf_as_na False True means treat None, NaN, -INF, + INF as NA (old way), False means + None and NaN are null, but INF, -INF + are not NA (new way). +compute.use_bottleneck True Use the bottleneck library to accelerate + computation if it is installed. +compute.use_numexpr True Use the numexpr library to accelerate + computation if it is installed. +plotting.matplotlib.register_converters True Register custom converters with + matplotlib. Set to False to de-register. +======================================= ============ ======================================== .. _basics.console_output: diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index 20b1f6643557b..7ca801b35ae7f 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -20,8 +20,8 @@ the converter. .. code-block:: python - >>> from pandas.plotting import register_converters - >>> register_converters() + >>> from pandas.plotting import register_matplotlib_converters + >>> register_matplotlib_converters() This caused problems for some users, so we're temporarily reverting that change; pandas will again register the converters on import. Using the converters @@ -39,8 +39,8 @@ without explicitly registering them will cause a ``FutureWarning``: converters. To register the converters: - >>> from pandas.plotting import register_converters - >>> register_converters() + >>> from pandas.plotting import register_matplotlib_converters + >>> register_matplotlib_converters() As the error message says, you'll need to register the converters if you intend to use them with matplotlib plotting functions. Pandas plotting functions, such @@ -48,7 +48,7 @@ as ``Series.plot``, will register them for you; calling ``register_converters`` first is not necessary. We've added a new option to control the converters, -``pd.options.plotting.mpl.converters``. By default, they are +``pd.options.plotting.matplotlib.register_converters``. By default, they are registered. Toggling this to ``False`` removes pandas' formatters (:issue:`18301`) We're working with the matplotlib developers to make this easier. We're trying diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index 8dffdcb6f4077..c3307c60b8ed9 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -494,14 +494,15 @@ def use_inf_as_na_cb(key): def register_converter_cb(key): - from pandas.plotting import register_converters, deregister_converters + from pandas.plotting import register_matplotlib_converters + from pandas.plotting import deregister_matplotlib_converters if cf.get_option(key): - register_converters() + register_matplotlib_converters() else: - deregister_converters() + deregister_matplotlib_converters() -with cf.config_prefix("plotting.mpl"): - cf.register_option("converters", True, register_converter_doc, +with cf.config_prefix("plotting.matplotlib"): + cf.register_option("register_converters", True, register_converter_doc, validator=bool, cb=register_converter_cb) diff --git a/pandas/plotting/__init__.py b/pandas/plotting/__init__.py index 0862717a8c36c..385d4d7f047c7 100644 --- a/pandas/plotting/__init__.py +++ b/pandas/plotting/__init__.py @@ -12,7 +12,9 @@ from pandas.plotting._style import plot_params from pandas.plotting._tools import table try: - from pandas.plotting._converter import register as register_converters - from pandas.plotting._converter import deregister as deregister_converters + from pandas.plotting._converter import \ + register as register_matplotlib_converters + from pandas.plotting._converter import \ + deregister as deregister_matplotlib_converters except ImportError: pass diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 99685a7140523..b409284ce6f3c 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -76,8 +76,9 @@ def register(warn=False): See Also -------- - deregister + deregister_matplotlib_converter """ + # Renamed in pandas.plotting.__init__ global _WARN if not warn: @@ -103,8 +104,9 @@ def deregister(): See Also -------- - register + deregister_matplotlib_converters """ + # Renamed in pandas.plotting.__init__ for type_, cls in get_pairs(): # We use type to catch our classes directly, no inheritance if type(units.registry.get(type_)) is cls: @@ -127,8 +129,9 @@ def _check_implicitly_registered(): "by pandas on import. Future versions of pandas will require " "you to explicitly register matplotlib converters.\n\n" "To register the converters:\n\t" - ">>> from pandas.plotting import register_converters\n\t" - ">>> register_converters()") + ">>> from pandas.plotting import register_matplotlib_converters" + "\n\t" + ">>> register_matplotlib_converters()") warnings.warn(msg, FutureWarning) _WARN = False diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 585bf2e444d48..5b96be2bf66dd 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -46,7 +46,7 @@ except ImportError: pass else: - if get_option('plotting.mpl.converters'): + if get_option('plotting.matplotlib.register_converters'): _converter.register(warn=True) diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 5db7da6582689..3966153439719 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -10,6 +10,8 @@ from pandas.compat.numpy import np_datetime64_compat converter = pytest.importorskip('pandas.plotting._converter') +from pandas.plotting import (register_matplotlib_converters, + deregister_matplotlib_converters) def test_timtetonum_accepts_unicode(): @@ -40,7 +42,7 @@ def test_registering_no_warning(self): # Set to the "warn" state, in case this isn't the first test run converter._WARN = True - converter.register() + register_matplotlib_converters() with tm.assert_produces_warning(None) as w: ax.plot(s.index, s.values) @@ -60,7 +62,8 @@ def test_matplotlib_formatters(self): units = pytest.importorskip("matplotlib.units") assert Timestamp in units.registry - ctx = cf.option_context("plotting.mpl.converters", False) + ctx = cf.option_context("plotting.matplotlib.register_converters", + False) with ctx: assert Timestamp not in units.registry @@ -68,7 +71,8 @@ def test_matplotlib_formatters(self): def test_option_no_warning(self): pytest.importorskip("matplotlib.pyplot") - ctx = cf.option_context("plotting.mpl.converters", False) + ctx = cf.option_context("plotting.matplotlib.register_converters", + False) plt = pytest.importorskip("matplotlib.pyplot") s = Series(range(12), index=date_range('2017', periods=12)) _, ax = plt.subplots() @@ -83,7 +87,7 @@ def test_option_no_warning(self): # Now test with registering converter._WARN = True - converter.register() + register_matplotlib_converters() with ctx: with tm.assert_produces_warning(None) as w: ax.plot(s.index, s.values) @@ -104,9 +108,9 @@ def test_registry_resets(self): units.registry[datetime] = date_converter units.registry[date] = date_converter - converter.register() + register_matplotlib_converters() assert not units.registry[date] is date_converter - converter.deregister() + deregister_matplotlib_converters() assert units.registry[date] is date_converter finally: From 61fbdbbb2a0bd5ac76c7174bb8c8e684580fec45 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 20 Nov 2017 07:33:56 -0600 Subject: [PATCH 17/24] Missed one --- doc/source/api.rst | 4 ++-- doc/source/whatsnew/v0.21.1.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/api.rst b/doc/source/api.rst index 4828882ce8369..c91f1b5746e24 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -2226,8 +2226,8 @@ Plotting .. autosummary:: :toctree: generated/ - plotting.register_converters - plotting.deregister_converters + plotting.register_matplotlib_converters + plotting.deregister_matplotlib_converters .. currentmodule:: pandas diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index 7ca801b35ae7f..14587f104a06c 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -73,14 +73,14 @@ Other Enhancements - :meth:`Timestamp.timestamp` is now available in Python 2.7. (:issue:`17329`) - -- +e .. _whatsnew_0211.deprecations: Deprecations ~~~~~~~~~~~~ -- +- - - From d6798d1a3ebb595cada67e45aa11bed5d0a29302 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 29 Nov 2017 09:14:28 -0500 Subject: [PATCH 18/24] Check version --- pandas/plotting/_compat.py | 8 ++++++++ pandas/plotting/_converter.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_compat.py b/pandas/plotting/_compat.py index d527bc08e2f08..59c5bd04b00a9 100644 --- a/pandas/plotting/_compat.py +++ b/pandas/plotting/_compat.py @@ -73,3 +73,11 @@ def _mpl_ge_2_1_0(): return matplotlib.__version__ >= LooseVersion('2.1') except ImportError: return False + + +def _mpl_ge_2_2_0(): + try: + import matplotlib + return matplotlib.__version__ > LooseVersion('2.1') + except ImportError: + return False diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 6bfea8576982a..088b205f4638f 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -35,6 +35,7 @@ from pandas.core.indexes.period import Period, PeriodIndex from pandas.plotting._compat import _mpl_le_2_0_0 +from pandas.plotting._compat import _mpl_ge_2_2_0 # constants HOURS_PER_DAY = 24. @@ -51,7 +52,7 @@ def get_pairs(): - return [ + pairs = [ (lib.Timestamp, DatetimeConverter), (Period, PeriodConverter), (pydt.datetime, DatetimeConverter), @@ -59,6 +60,9 @@ def get_pairs(): (pydt.time, TimeConverter), (np.datetime64, DatetimeConverter), ] + if _mpl_ge_2_2_0(): + pairs = pairs[:2] + return pairs def register(warn=False): @@ -84,7 +88,9 @@ def register(warn=False): if not warn: _WARN = False - for type_, cls in get_pairs(): + pairs = get_pairs() + print(pairs) + for type_, cls in pairs: converter = cls() if type_ in units.registry: previous = units.registry[type_] From 04d8aa5021d70aca1684f0ce0b7fdb47e6cfcd56 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 30 Nov 2017 10:14:04 -0500 Subject: [PATCH 19/24] No warnings by default --- pandas/plotting/_compat.py | 8 -------- pandas/plotting/_converter.py | 12 ++++-------- pandas/plotting/_core.py | 2 +- pandas/tests/plotting/test_converter.py | 2 +- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/pandas/plotting/_compat.py b/pandas/plotting/_compat.py index 59c5bd04b00a9..d527bc08e2f08 100644 --- a/pandas/plotting/_compat.py +++ b/pandas/plotting/_compat.py @@ -73,11 +73,3 @@ def _mpl_ge_2_1_0(): return matplotlib.__version__ >= LooseVersion('2.1') except ImportError: return False - - -def _mpl_ge_2_2_0(): - try: - import matplotlib - return matplotlib.__version__ > LooseVersion('2.1') - except ImportError: - return False diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 76bc8067049ad..db7644a22606a 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -35,7 +35,6 @@ from pandas.core.indexes.period import Period, PeriodIndex from pandas.plotting._compat import _mpl_le_2_0_0 -from pandas.plotting._compat import _mpl_ge_2_2_0 # constants HOURS_PER_DAY = 24. @@ -47,8 +46,8 @@ MUSEC_PER_DAY = 1e6 * SEC_PER_DAY -_WARN = True -_mpl_units = {} +_WARN = True # Global for whether pandas has registered the units explicitly +_mpl_units = {} # Cache for units overwritten by us def get_pairs(): @@ -60,12 +59,10 @@ def get_pairs(): (pydt.time, TimeConverter), (np.datetime64, DatetimeConverter), ] - if _mpl_ge_2_2_0(): - pairs = pairs[:2] return pairs -def register(warn=False): +def register(explicit=False): """Register Pandas Formatters and Converters with matplotlib This function modifies the global ``matplotlib.units.registry`` @@ -85,11 +82,10 @@ def register(warn=False): # Renamed in pandas.plotting.__init__ global _WARN - if not warn: + if explicit: _WARN = False pairs = get_pairs() - print(pairs) for type_, cls in pairs: converter = cls() if type_ in units.registry: diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index e0ad7fe6fc44c..adaaa206edadd 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -47,7 +47,7 @@ pass else: if get_option('plotting.matplotlib.register_converters'): - _converter.register(warn=True) + _converter.register(explicit=True) def _get_standard_kind(kind): diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index 3966153439719..c3c16674e297f 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -109,7 +109,7 @@ def test_registry_resets(self): units.registry[date] = date_converter register_matplotlib_converters() - assert not units.registry[date] is date_converter + assert units.registry[date] is date_converter deregister_matplotlib_converters() assert units.registry[date] is date_converter From f061148fe822ccfb8fb3e957603b2f8fdcb98996 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 30 Nov 2017 13:34:34 -0500 Subject: [PATCH 20/24] Update release notes --- doc/source/whatsnew/v0.21.1.txt | 36 +++++++++------------------------ 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index b601bca1fd3da..d88ad937f858d 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -23,33 +23,16 @@ the converter. >>> from pandas.plotting import register_matplotlib_converters >>> register_matplotlib_converters() -This caused problems for some users, so we're temporarily reverting that change; -pandas will again register the converters on import. Using the converters -without explicitly registering them will cause a ``FutureWarning``: - -.. code-block:: python - - >>> import pandas as pd - >>> import matplotlib.pyplot as plt - >>> fig, ax = plt.subplots() - >>> ax.plot(pd.Series(range(12), index=pd.date_range('2017', periods=12))) - FutureWarning: Using an implicitly registered datetime converter for a - matplotlib plotting method. The converter was registered by pandas on import. - Future versions of pandas will require you to explicitly register matplotlib - converters. - - To register the converters: - >>> from pandas.plotting import register_matplotlib_converters - >>> register_matplotlib_converters() - -As the error message says, you'll need to register the converters if you intend -to use them with matplotlib plotting functions. Pandas plotting functions, such -as ``Series.plot``, will register them for you; calling ``register_converters`` -first is not necessary. +This caused problems for some users who relied on those converters being present +for regular ``matplotlib.pyplot`` plotting methods, so we're temporarily +reverting that change; pandas will again register the converters on import. +Pandas plotting functions, such as ``Series.plot``, will continue to register +them for you. We've added a new option to control the converters, ``pd.options.plotting.matplotlib.register_converters``. By default, they are -registered. Toggling this to ``False`` removes pandas' formatters (:issue:`18301`) +registered. Toggling this to ``False`` removes pandas' formatters and restore +any converters we overwrote when registering them (:issue:`18301`). We're working with the matplotlib developers to make this easier. We're trying to balance user convenience (automatically registering the converters) with @@ -80,9 +63,8 @@ Other Enhancements Deprecations ~~~~~~~~~~~~ -- -- -- +- ``pandas.tseries.register`` has been renamed to + :func:`pandas.plotting.register_matplotlib_converters`` (:issue:`18301`) .. _whatsnew_0211.performance: From 1112b28fb35c0613df9ebf741521abc41620593f Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 1 Dec 2017 08:19:05 -0600 Subject: [PATCH 21/24] Test fixup - actually switch the default to not warn - We do overwrite matplotlib's formatters --- pandas/plotting/_converter.py | 2 +- pandas/tests/plotting/test_converter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index db7644a22606a..10b1d26a5aa77 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -62,7 +62,7 @@ def get_pairs(): return pairs -def register(explicit=False): +def register(explicit=True): """Register Pandas Formatters and Converters with matplotlib This function modifies the global ``matplotlib.units.registry`` diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index c3c16674e297f..fcd9cb7671600 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -109,7 +109,7 @@ def test_registry_resets(self): units.registry[date] = date_converter register_matplotlib_converters() - assert units.registry[date] is date_converter + assert units.registry[date] is not date_converter deregister_matplotlib_converters() assert units.registry[date] is date_converter From 897c7ea64747b266448209bf752238416dca9948 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 1 Dec 2017 08:38:26 -0600 Subject: [PATCH 22/24] Doc update --- doc/source/whatsnew/v0.21.1.txt | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/doc/source/whatsnew/v0.21.1.txt b/doc/source/whatsnew/v0.21.1.txt index d88ad937f858d..55efbae68d8ef 100644 --- a/doc/source/whatsnew/v0.21.1.txt +++ b/doc/source/whatsnew/v0.21.1.txt @@ -14,22 +14,16 @@ Restore Matplotlib datetime Converter Registration Pandas implements some matplotlib converters for nicely formatting the axis labels on plots with ``datetime`` or ``Period`` values. Prior to pandas 0.21.0, -these were implicitly registered with matplotlib, as a side effect of -``import pandas``. In pandas 0.21.0, we required users to explicitly register -the converter. +these were implicitly registered with matplotlib, as a side effect of ``import +pandas``. -.. code-block:: python +In pandas 0.21.0, we required users to explicitly register the +converter. This caused problems for some users who relied on those converters +being present for regular ``matplotlib.pyplot`` plotting methods, so we're +temporarily reverting that change; pandas will again register the converters on +import. - >>> from pandas.plotting import register_matplotlib_converters - >>> register_matplotlib_converters() - -This caused problems for some users who relied on those converters being present -for regular ``matplotlib.pyplot`` plotting methods, so we're temporarily -reverting that change; pandas will again register the converters on import. -Pandas plotting functions, such as ``Series.plot``, will continue to register -them for you. - -We've added a new option to control the converters, +We've added a new option to control the converters: ``pd.options.plotting.matplotlib.register_converters``. By default, they are registered. Toggling this to ``False`` removes pandas' formatters and restore any converters we overwrote when registering them (:issue:`18301`). @@ -37,8 +31,11 @@ any converters we overwrote when registering them (:issue:`18301`). We're working with the matplotlib developers to make this easier. We're trying to balance user convenience (automatically registering the converters) with import performance and best practices (importing pandas shouldn't have the side -effect of overwriting any custom converters you've already set). Apologies for -any bumps along the way. +effect of overwriting any custom converters you've already set). In the future +we hope to have most of the datetime formatting functionality in matplotlib, +with just the pandas-specific converters in pandas. We'll then gracefully +deprecate the automatic registration of converters in favor of users explicitly +registering them when they want them. .. _whatsnew_0211.enhancements: From 2cf0601b2bc0b7f323f46d6d73d2aa9681ff690f Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 1 Dec 2017 08:40:07 -0600 Subject: [PATCH 23/24] Fix deprecation message --- pandas/tests/plotting/test_converter.py | 3 ++- pandas/tseries/converter.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index fcd9cb7671600..a28fcc2ec9345 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -125,7 +125,8 @@ def test_old_import_warns(self): converter.register() assert len(w) - assert 'pandas.plotting.register_converters' in str(w[0].message) + assert ('pandas.plotting.register_matplotlib_converters' in + str(w[0].message)) class TestDateTimeConverter(object): diff --git a/pandas/tseries/converter.py b/pandas/tseries/converter.py index c3e2fa3df0125..26d3f3cb85edc 100644 --- a/pandas/tseries/converter.py +++ b/pandas/tseries/converter.py @@ -15,6 +15,6 @@ def register(): from pandas.plotting._converter import register as register_ msg = ("'pandas.tseries.converter.register' has been moved and renamed to " - "'pandas.plotting.register_converters'. ") + "'pandas.plotting.register_matplotlib_converters'. ") warnings.warn(msg, FutureWarning, stacklevel=2) register_() From ee7b457f01c4489e351c4567340da86e22ab16a6 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 1 Dec 2017 08:53:14 -0600 Subject: [PATCH 24/24] Test added by default --- pandas/tests/plotting/test_converter.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pandas/tests/plotting/test_converter.py b/pandas/tests/plotting/test_converter.py index a28fcc2ec9345..3818c04649366 100644 --- a/pandas/tests/plotting/test_converter.py +++ b/pandas/tests/plotting/test_converter.py @@ -1,3 +1,4 @@ +import subprocess import pytest from datetime import datetime, date @@ -20,6 +21,15 @@ def test_timtetonum_accepts_unicode(): class TestRegistration(object): + def test_register_by_default(self): + # Run in subprocess to ensure a clean state + code = ("'import matplotlib.units; " + "import pandas as pd; " + "units = dict(matplotlib.units.registry); " + "assert pd.Timestamp in units)'") + call = ['python', '-c', code] + assert subprocess.check_call(call) == 0 + def test_warns(self): plt = pytest.importorskip("matplotlib.pyplot") s = Series(range(12), index=date_range('2017', periods=12))