From 5797522f0e77d71b7129675e0c7a8ef72ab0150c Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 19 Dec 2018 21:54:45 -0800 Subject: [PATCH 1/5] MNT: add a warning if a categroical string array is all convertible to numbers or dates --- lib/matplotlib/category.py | 25 +++++++++++++++++++++++++ lib/matplotlib/tests/test_category.py | 5 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/category.py b/lib/matplotlib/category.py index e755bfed0330..1adda2af1b2d 100644 --- a/lib/matplotlib/category.py +++ b/lib/matplotlib/category.py @@ -12,10 +12,12 @@ """ from collections import OrderedDict +import dateutil.parser import itertools import numpy as np +import matplotlib.cbook as cbook import matplotlib.units as units import matplotlib.ticker as ticker @@ -174,6 +176,20 @@ def __init__(self, data=None): if data is not None: self.update(data) + @staticmethod + def _str_is_convertable(val): + """ + Helper method to see if string can be cast to float or parsed as date. + """ + try: + float(val) + except ValueError: + try: + dateutil.parser.parse(val) + except ValueError: + return False + return True + def update(self, data): """Maps new values to integer identifiers. @@ -189,11 +205,20 @@ def update(self, data): """ data = np.atleast_1d(np.array(data, dtype=object)) + convertable = True for val in OrderedDict.fromkeys(data): + # OrderedDict just iterates over unique values in data. if not isinstance(val, (str, bytes)): raise TypeError("{val!r} is not a string".format(val=val)) + # check if we can convert string to number or date... + convertable = (convertable and self._str_is_convertable(val)) if val not in self._mapping: self._mapping[val] = next(self._counter) + if convertable: + cbook._warn_external('using category units to plot a list of ' + 'strings that is a;; floats or parsable as dates. ' + 'If you do not mean these to be categories, cast ' + 'to the approriate data type before plotting.') # Register the converter with Matplotlib's unit framework diff --git a/lib/matplotlib/tests/test_category.py b/lib/matplotlib/tests/test_category.py index ee5d3ec7888d..010aa1cab33b 100644 --- a/lib/matplotlib/tests/test_category.py +++ b/lib/matplotlib/tests/test_category.py @@ -203,7 +203,8 @@ class TestPlotNumlike(object): @pytest.mark.parametrize("ndata", numlike_data, ids=numlike_ids) def test_plot_numlike(self, ax, plotter, ndata): counts = np.array([4, 6, 5]) - plotter(ax, ndata, counts) + with pytest.warns(UserWarning, match='using category units to plot'): + plotter(ax, ndata, counts) axis_test(ax.xaxis, ndata) @@ -261,12 +262,14 @@ def test_update_plot(self, ax, plotter): PLOT_BROKEN_IDS = ["scatter", "plot", "bar"] + @pytest.mark.filterwarnings('ignore::UserWarning') @pytest.mark.parametrize("plotter", PLOT_BROKEN_LIST, ids=PLOT_BROKEN_IDS) @pytest.mark.parametrize("xdata", fvalues, ids=fids) def test_mixed_type_exception(self, ax, plotter, xdata): with pytest.raises(TypeError): plotter(ax, xdata, [1, 2]) + @pytest.mark.filterwarnings('ignore::UserWarning') @pytest.mark.parametrize("plotter", PLOT_BROKEN_LIST, ids=PLOT_BROKEN_IDS) @pytest.mark.parametrize("xdata", fvalues, ids=fids) def test_mixed_type_update_exception(self, ax, plotter, xdata): From 6bad665b2018efbee932914a8ab2be1eb53a4aac Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 21 Dec 2018 14:45:11 -0800 Subject: [PATCH 2/5] FIX --- lib/matplotlib/category.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/category.py b/lib/matplotlib/category.py index 1adda2af1b2d..45fdb32606ec 100644 --- a/lib/matplotlib/category.py +++ b/lib/matplotlib/category.py @@ -177,17 +177,20 @@ def __init__(self, data=None): self.update(data) @staticmethod - def _str_is_convertable(val): + def _strs_are_convertible(vals): """ - Helper method to see if string can be cast to float or parsed as date. + Helper method to see if list of strings can all be cast to float or + parsed as date. """ - try: - float(val) - except ValueError: + + for val in vals: try: - dateutil.parser.parse(val) + float(val) except ValueError: - return False + try: + dateutil.parser.parse(val) + except ValueError: + return False return True def update(self, data): @@ -205,16 +208,14 @@ def update(self, data): """ data = np.atleast_1d(np.array(data, dtype=object)) - convertable = True for val in OrderedDict.fromkeys(data): # OrderedDict just iterates over unique values in data. if not isinstance(val, (str, bytes)): raise TypeError("{val!r} is not a string".format(val=val)) - # check if we can convert string to number or date... - convertable = (convertable and self._str_is_convertable(val)) if val not in self._mapping: self._mapping[val] = next(self._counter) - if convertable: + # check if we can convert all strings to number or date... + if self._strs_are_convertible(data): cbook._warn_external('using category units to plot a list of ' 'strings that is a;; floats or parsable as dates. ' 'If you do not mean these to be categories, cast ' From 5c4c41dd5e3f33b2d4f0c8bff95743c20d2060ef Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 7 Jan 2019 12:59:44 -0800 Subject: [PATCH 3/5] FIX: make the warning a logging.info --- lib/matplotlib/category.py | 12 ++++++++---- lib/matplotlib/tests/test_category.py | 5 +---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/category.py b/lib/matplotlib/category.py index 45fdb32606ec..7d98f8688fc0 100644 --- a/lib/matplotlib/category.py +++ b/lib/matplotlib/category.py @@ -14,6 +14,7 @@ from collections import OrderedDict import dateutil.parser import itertools +import logging import numpy as np @@ -22,6 +23,9 @@ import matplotlib.ticker as ticker +_log = logging.getLogger(__name__) + + class StrCategoryConverter(units.ConversionInterface): @staticmethod def convert(value, unit, axis): @@ -216,10 +220,10 @@ def update(self, data): self._mapping[val] = next(self._counter) # check if we can convert all strings to number or date... if self._strs_are_convertible(data): - cbook._warn_external('using category units to plot a list of ' - 'strings that is a;; floats or parsable as dates. ' - 'If you do not mean these to be categories, cast ' - 'to the approriate data type before plotting.') + _log.info('using category units to plot a list of ' + 'strings that is a;; floats or parsable as dates. ' + 'If you do not mean these to be categories, cast ' + 'to the approriate data type before plotting.') # Register the converter with Matplotlib's unit framework diff --git a/lib/matplotlib/tests/test_category.py b/lib/matplotlib/tests/test_category.py index 010aa1cab33b..ee5d3ec7888d 100644 --- a/lib/matplotlib/tests/test_category.py +++ b/lib/matplotlib/tests/test_category.py @@ -203,8 +203,7 @@ class TestPlotNumlike(object): @pytest.mark.parametrize("ndata", numlike_data, ids=numlike_ids) def test_plot_numlike(self, ax, plotter, ndata): counts = np.array([4, 6, 5]) - with pytest.warns(UserWarning, match='using category units to plot'): - plotter(ax, ndata, counts) + plotter(ax, ndata, counts) axis_test(ax.xaxis, ndata) @@ -262,14 +261,12 @@ def test_update_plot(self, ax, plotter): PLOT_BROKEN_IDS = ["scatter", "plot", "bar"] - @pytest.mark.filterwarnings('ignore::UserWarning') @pytest.mark.parametrize("plotter", PLOT_BROKEN_LIST, ids=PLOT_BROKEN_IDS) @pytest.mark.parametrize("xdata", fvalues, ids=fids) def test_mixed_type_exception(self, ax, plotter, xdata): with pytest.raises(TypeError): plotter(ax, xdata, [1, 2]) - @pytest.mark.filterwarnings('ignore::UserWarning') @pytest.mark.parametrize("plotter", PLOT_BROKEN_LIST, ids=PLOT_BROKEN_IDS) @pytest.mark.parametrize("xdata", fvalues, ids=fids) def test_mixed_type_update_exception(self, ax, plotter, xdata): From 17a7c544166cf4720db46638a3cf31557f26723e Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 18 Jan 2019 14:05:11 -0800 Subject: [PATCH 4/5] FIX: one-loop version of categorical logging call --- lib/matplotlib/category.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/category.py b/lib/matplotlib/category.py index 7d98f8688fc0..73e50c1bbcbb 100644 --- a/lib/matplotlib/category.py +++ b/lib/matplotlib/category.py @@ -181,20 +181,18 @@ def __init__(self, data=None): self.update(data) @staticmethod - def _strs_are_convertible(vals): + def _str_is_convertible(val): """ - Helper method to see if list of strings can all be cast to float or + Helper method to see if a string can be cast to float or parsed as date. """ - - for val in vals: + try: + float(val) + except ValueError: try: - float(val) + dateutil.parser.parse(val) except ValueError: - try: - dateutil.parser.parse(val) - except ValueError: - return False + return False return True def update(self, data): @@ -212,18 +210,22 @@ def update(self, data): """ data = np.atleast_1d(np.array(data, dtype=object)) + # check if convertable to number: + convertable = True for val in OrderedDict.fromkeys(data): # OrderedDict just iterates over unique values in data. if not isinstance(val, (str, bytes)): raise TypeError("{val!r} is not a string".format(val=val)) + if convertable: + # this will only be called so long as convertable is True. + convertable = self._str_is_convertible(val) if val not in self._mapping: self._mapping[val] = next(self._counter) - # check if we can convert all strings to number or date... - if self._strs_are_convertible(data): - _log.info('using category units to plot a list of ' - 'strings that is a;; floats or parsable as dates. ' - 'If you do not mean these to be categories, cast ' - 'to the approriate data type before plotting.') + if convertable: + _log.info('Using categrocical units to plot a list of strings ' + 'that are all parsable as floats or dates. If these ' + 'strings should be plotted as numbers, cast to the ' + 'approriate data type before plotting.') # Register the converter with Matplotlib's unit framework From 12edae9851cc9b789db2aa4b4cb372362067200d Mon Sep 17 00:00:00 2001 From: hannah Date: Sat, 19 Jan 2019 18:39:40 -0500 Subject: [PATCH 5/5] spelling --- lib/matplotlib/category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/category.py b/lib/matplotlib/category.py index 73e50c1bbcbb..cf625926ee69 100644 --- a/lib/matplotlib/category.py +++ b/lib/matplotlib/category.py @@ -222,7 +222,7 @@ def update(self, data): if val not in self._mapping: self._mapping[val] = next(self._counter) if convertable: - _log.info('Using categrocical units to plot a list of strings ' + _log.info('Using categorical units to plot a list of strings ' 'that are all parsable as floats or dates. If these ' 'strings should be plotted as numbers, cast to the ' 'approriate data type before plotting.')