From f24330d848f73e48fd52766a742165571098b945 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 2 Aug 2016 23:44:21 -0400 Subject: [PATCH] py.test, updating axis ticks, and unitData as class, removed test_category from init, and updated tox --- .mailmap | 8 +- .travis.yml | 1 - lib/matplotlib/__init__.py | 1 - lib/matplotlib/axis.py | 8 +- lib/matplotlib/category.py | 110 +++++----- lib/matplotlib/tests/test_category.py | 278 ++++++++++++-------------- tox.ini | 3 +- 7 files changed, 181 insertions(+), 228 deletions(-) diff --git a/.mailmap b/.mailmap index 4b6bf30788aa..9b552fff752c 100644 --- a/.mailmap +++ b/.mailmap @@ -3,11 +3,11 @@ Andrew Dawson anykraus Ariel Hernán Curiale Ben Cohen -Ben Root +Ben Root Benjamin Root Casper van der Wel -Christoph Gohlke -Cimarron Mittelsteadt -Daniel Hyams +Christoph Gohlke cgohlke +Christoph Gohlke C. Gohlke +Cimarron Mittelsteadt Cimarron Daniel Hyams Daniel Hyams David Kua endolith diff --git a/.travis.yml b/.travis.yml index 5d91a7cd0e87..7142ac9e3111 100644 --- a/.travis.yml +++ b/.travis.yml @@ -118,7 +118,6 @@ install: # Install nose from a build which has partial # support for python36 and suport for coverage output suppressing pip install git+https://github.com/jenshnielsen/nose.git@matplotlibnose - # pytest-cov>=2.3.1 due to https://github.com/pytest-dev/pytest-cov/issues/124 pip install $PRE pytest 'pytest-cov>=2.3.1' pytest-timeout pytest-xdist pytest-faulthandler diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index e45a5b0e98bb..cbc694db34a6 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1499,7 +1499,6 @@ def _jupyter_nbextension_paths(): 'matplotlib.tests.test_backend_svg', 'matplotlib.tests.test_basic', 'matplotlib.tests.test_bbox_tight', - 'matplotlib.tests.test_category', 'matplotlib.tests.test_cbook', 'matplotlib.tests.test_coding_standards', 'matplotlib.tests.test_collections', diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 276c09605a70..cbccdb695489 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -642,7 +642,7 @@ def __init__(self, axes, pickradius=15): self.offsetText = self._get_offset_text() self.majorTicks = [] self.minorTicks = [] - self.unit_data = [] + self.unit_data = None self.pickradius = pickradius # Initialize here for testing; later add API @@ -695,14 +695,14 @@ def limit_range_for_scale(self, vmin, vmax): @property def unit_data(self): - """Holds data that a ConversionInterface subclass relys on + """Holds data that a ConversionInterface subclass uses to convert between labels and indexes """ return self._unit_data @unit_data.setter - def unit_data(self, data): - self._unit_data = data + def unit_data(self, unit_data): + self._unit_data = unit_data def get_children(self): children = [self.label, self.offsetText] diff --git a/lib/matplotlib/category.py b/lib/matplotlib/category.py index bfac242149c3..0f9009bfafc0 100644 --- a/lib/matplotlib/category.py +++ b/lib/matplotlib/category.py @@ -9,6 +9,7 @@ import numpy as np +import matplotlib.cbook as cbook import matplotlib.units as units import matplotlib.ticker as ticker @@ -22,10 +23,12 @@ def to_array(data, maxlen=100): if NP_NEW: return np.array(data, dtype=np.unicode) + if cbook.is_scalar_or_string(data): + data = [data] try: vals = np.array(data, dtype=('|S', maxlen)) except UnicodeEncodeError: - # pure hack + # this yields gibberish vals = np.array([convert_to_string(d) for d in data]) return vals @@ -36,40 +39,44 @@ def convert(value, unit, axis): """Uses axis.unit_data map to encode data as floats """ - vmap = dict(axis.unit_data) + vmap = dict(zip(axis.unit_data.seq, axis.unit_data.locs)) if isinstance(value, six.string_types): return vmap[value] vals = to_array(value) - for lab, loc in axis.unit_data: + for lab, loc in vmap.items(): vals[vals == lab] = loc return vals.astype('float') @staticmethod def axisinfo(unit, axis): - seq, locs = zip(*axis.unit_data) - majloc = StrCategoryLocator(locs) - majfmt = StrCategoryFormatter(seq) + majloc = StrCategoryLocator(axis.unit_data.locs) + majfmt = StrCategoryFormatter(axis.unit_data.seq) return units.AxisInfo(majloc=majloc, majfmt=majfmt) @staticmethod def default_units(data, axis): # the conversion call stack is: # default_units->axis_info->convert - axis.unit_data = map_categories(data, axis.unit_data) + if axis.unit_data is None: + axis.unit_data = UnitData(data) + else: + axis.unit_data.update(data) return None class StrCategoryLocator(ticker.FixedLocator): def __init__(self, locs): - super(StrCategoryLocator, self).__init__(locs, None) + self.locs = locs + self.nbins = None class StrCategoryFormatter(ticker.FixedFormatter): def __init__(self, seq): - super(StrCategoryFormatter, self).__init__(seq) + self.seq = seq + self.offset_string = '' def convert_to_string(value): @@ -77,8 +84,8 @@ def convert_to_string(value): np.array(...,dtype=unicode) for all later versions of numpy""" if isinstance(value, six.string_types): - return value - if np.isfinite(value): + pass + elif np.isfinite(value): value = np.asarray(value, dtype=str)[np.newaxis][0] elif np.isnan(value): value = 'nan' @@ -91,59 +98,38 @@ def convert_to_string(value): return value -def map_categories(data, old_map=None): - """Create mapping between unique categorical - values and numerical identifier. - - Paramters - --------- - data: iterable - sequence of values - old_map: list of tuple, optional - if not `None`, than old_mapping will be updated with new values and - previous mappings will remain unchanged) - sort: bool, optional - sort keys by ASCII value - - Returns - ------- - list of tuple - [(label, ticklocation),...] - - """ - - # code typical missing data in the negative range because - # everything else will always have positive encoding - # question able if it even makes sense +class UnitData(object): + # debatable makes sense to special code missing values spdict = {'nan': -1.0, 'inf': -2.0, '-inf': -3.0} - if isinstance(data, six.string_types): - data = [data] - - # will update this post cbook/dict support - strdata = to_array(data) - uniq = np.unique(strdata) - - if old_map: - olabs, okeys = zip(*old_map) - svalue = max(okeys) + 1 - else: - old_map, olabs, okeys = [], [], [] - svalue = 0 - - category_map = old_map[:] - - new_labs = [u for u in uniq if u not in olabs] - missing = [nl for nl in new_labs if nl in spdict.keys()] - - category_map.extend([(m, spdict[m]) for m in missing]) - - new_labs = [nl for nl in new_labs if nl not in missing] - - new_locs = np.arange(svalue, svalue + len(new_labs), dtype='float') - category_map.extend(list(zip(new_labs, new_locs))) - return category_map - + def __init__(self, data): + """Create mapping between unique categorical values + and numerical identifier + Paramters + --------- + data: iterable + sequence of values + """ + self.seq, self.locs = [], [] + self._set_seq_locs(data, 0) + + def update(self, new_data): + # so as not to conflict with spdict + value = max(max(self.locs) + 1, 0) + self._set_seq_locs(new_data, value) + + def _set_seq_locs(self, data, value): + # magic to make it work under np1.6 + strdata = to_array(data) + # np.unique makes dateframes work + new_s = [d for d in np.unique(strdata) if d not in self.seq] + for ns in new_s: + self.seq.append(convert_to_string(ns)) + if ns in UnitData.spdict.keys(): + self.locs.append(UnitData.spdict[ns]) + else: + self.locs.append(value) + value += 1 # Connects the convertor to matplotlib units.registry[str] = StrCategoryConverter() diff --git a/lib/matplotlib/tests/test_category.py b/lib/matplotlib/tests/test_category.py index 02db774e4ff6..aa6afff11f41 100644 --- a/lib/matplotlib/tests/test_category.py +++ b/lib/matplotlib/tests/test_category.py @@ -2,254 +2,222 @@ """Catch all for categorical functions""" from __future__ import (absolute_import, division, print_function, unicode_literals) -import unittest +import pytest import numpy as np import matplotlib.pyplot as plt from matplotlib.testing.decorators import cleanup import matplotlib.category as cat +import unittest -class TestConvertToString(unittest.TestCase): - def setUp(self): - pass - - def test_string(self): - self.assertEqual("abc", cat.convert_to_string("abc")) - def test_unicode(self): - self.assertEqual("Здравствуйте мир", - cat.convert_to_string("Здравствуйте мир")) +class TestConvertToString(object): + testdata = [("abc", "abc"), ("Здравствуйте мир", "Здравствуйте мир"), + ("3.14", 3.14), ("nan", np.nan), + ("inf", np.inf), ("-inf", -np.inf)] + ids = ["string", "unicode", "decimal", "nan", "posinf", "neginf", ] - def test_decimal(self): - self.assertEqual("3.14", cat.convert_to_string(3.14)) + @pytest.mark.parametrize("expected, test", testdata, ids=ids) + def test_convert_to_string(self, expected, test): + assert expected == cat.convert_to_string(test) - def test_nan(self): - self.assertEqual("nan", cat.convert_to_string(np.nan)) - def test_posinf(self): - self.assertEqual("inf", cat.convert_to_string(np.inf)) +class TestUnitData(object): + testdata = [("hello world", ["hello world"], [0]), + ("Здравствуйте мир", ["Здравствуйте мир"], [0]), + (['A', 'A', np.nan, 'B', -np.inf, 3.14, np.inf], + ['-inf', '3.14', 'A', 'B', 'inf', 'nan'], + [-3.0, 0, 1, 2, -2.0, -1.0])] - def test_neginf(self): - self.assertEqual("-inf", cat.convert_to_string(-np.inf)) + ids = ["single", "unicode", "mixed"] + @pytest.mark.parametrize("data, seq, locs", testdata, ids=ids) + def test_unit(self, data, seq, locs): + act = cat.UnitData(data) + assert act.seq == seq + assert act.locs == locs -class TestMapCategories(unittest.TestCase): - def test_map_unicode(self): - act = cat.map_categories("Здравствуйте мир") - exp = [("Здравствуйте мир", 0)] - self.assertListEqual(act, exp) + def test_update_map(self): + data = ['a', 'd'] + oseq = ['a', 'd'] + olocs = [0, 1] - def test_map_data(self): - act = cat.map_categories("hello world") - exp = [("hello world", 0)] - self.assertListEqual(act, exp) + data_update = ['b', 'd', 'e', np.inf] + useq = ['a', 'd', 'b', 'e', 'inf'] + ulocs = [0, 1, 2, 3, -2] - def test_map_data_basic(self): - data = ['a', 'b', 'b', 'a', 'a', 'c', 'c', 'c'] - exp = [('a', 0), ('b', 1), ('c', 2)] - act = cat.map_categories(data) - self.assertListEqual(sorted(act), sorted(exp)) + unitdata = cat.UnitData(data) + assert unitdata.seq == oseq + assert unitdata.locs == olocs - def test_map_data_mixed(self): - data = ['A', 'A', np.nan, 'B', -np.inf, 3.14, np.inf] - exp = [('nan', -1), ('3.14', 0), - ('A', 1), ('B', 2), ('-inf', -3), ('inf', -2)] + unitdata.update(data_update) + assert unitdata.seq == useq + assert unitdata.locs == ulocs - act = cat.map_categories(data) - self.assertListEqual(sorted(act), sorted(exp)) - @unittest.SkipTest - def test_update_map(self): - data = ['b', 'd', 'e', np.inf] - old_map = [('a', 0), ('d', 1)] - exp = [('inf', -2), ('a', 0), ('d', 1), - ('b', 2), ('e', 3)] - act = cat.map_categories(data, old_map) - self.assertListEqual(sorted(act), sorted(exp)) +class FakeAxis(object): + def __init__(self, unit_data): + self.unit_data = unit_data -class FakeAxis(object): - def __init__(self): - self.unit_data = [] +class MockUnitData(object): + def __init__(self, data): + seq, locs = zip(*data) + self.seq = list(seq) + self.locs = list(locs) -class TestStrCategoryConverter(unittest.TestCase): +class TestStrCategoryConverter(object): """Based on the pandas conversion and factorization tests: ref: /pandas/tseries/tests/test_converter.py /pandas/tests/test_algos.py:TestFactorize """ - - def setUp(self): + testdata = [("Здравствуйте мир", [("Здравствуйте мир", 42)], 42), + ("hello world", [("hello world", 42)], 42), + (['a', 'b', 'b', 'a', 'a', 'c', 'c', 'c'], + [('a', 0), ('b', 1), ('c', 2)], + [0, 1, 1, 0, 0, 2, 2, 2]), + (['A', 'A', np.nan, 'B', -np.inf, 3.14, np.inf], + [('nan', -1), ('3.14', 0), ('A', 1), ('B', 2), + ('-inf', 100), ('inf', 200)], + [1, 1, -1, 2, 100, 0, 200])] + ids = ["unicode", "single", "basic", "mixed"] + + @pytest.fixture(autouse=True) + def mock_axis(self, request): self.cc = cat.StrCategoryConverter() - self.axis = FakeAxis() - - def test_convert_unicode(self): - self.axis.unit_data = [("Здравствуйте мир", 42)] - act = self.cc.convert("Здравствуйте мир", None, self.axis) - exp = 42 - self.assertEqual(act, exp) - - def test_convert_single(self): - self.axis.unit_data = [("hello world", 42)] - act = self.cc.convert("hello world", None, self.axis) - exp = 42 - self.assertEqual(act, exp) - - def test_convert_basic(self): - data = ['a', 'b', 'b', 'a', 'a', 'c', 'c', 'c'] - exp = [0, 1, 1, 0, 0, 2, 2, 2] - self.axis.unit_data = [('a', 0), ('b', 1), ('c', 2)] - act = self.cc.convert(data, None, self.axis) - np.testing.assert_array_equal(act, exp) - def test_convert_mixed(self): - data = ['A', 'A', np.nan, 'B', -np.inf, 3.14, np.inf] - exp = [1, 1, -1, 2, 100, 0, 200] - self.axis.unit_data = [('nan', -1), ('3.14', 0), - ('A', 1), ('B', 2), - ('-inf', 100), ('inf', 200)] - act = self.cc.convert(data, None, self.axis) + @pytest.mark.parametrize("data, unitmap, exp", testdata, ids=ids) + def test_convert(self, data, unitmap, exp): + MUD = MockUnitData(unitmap) + axis = FakeAxis(MUD) + act = self.cc.convert(data, None, axis) np.testing.assert_array_equal(act, exp) def test_axisinfo(self): - self.axis.unit_data = [('a', 0)] - ax = self.cc.axisinfo(None, self.axis) - self.assertTrue(isinstance(ax.majloc, cat.StrCategoryLocator)) - self.assertTrue(isinstance(ax.majfmt, cat.StrCategoryFormatter)) + MUD = MockUnitData([(None, None)]) + axis = FakeAxis(MUD) + ax = self.cc.axisinfo(None, axis) + assert isinstance(ax.majloc, cat.StrCategoryLocator) + assert isinstance(ax.majfmt, cat.StrCategoryFormatter) def test_default_units(self): - self.assertEqual(self.cc.default_units(["a"], self.axis), None) - + axis = FakeAxis(None) + assert self.cc.default_units(["a"], axis) is None -class TestStrCategoryLocator(unittest.TestCase): - def setUp(self): - self.locs = list(range(10)) +class TestStrCategoryLocator(object): def test_StrCategoryLocator(self): - ticks = cat.StrCategoryLocator(self.locs) - np.testing.assert_equal(ticks.tick_values(None, None), - self.locs) + locs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ticks = cat.StrCategoryLocator(locs) + np.testing.assert_array_equal(ticks.tick_values(None, None), locs) class TestStrCategoryFormatter(unittest.TestCase): - def setUp(self): - self.seq = ["hello", "world", "hi"] - def test_StrCategoryFormatter(self): - labels = cat.StrCategoryFormatter(self.seq) - self.assertEqual(labels('a', 1), "world") + seq = ["hello", "world", "hi"] + labels = cat.StrCategoryFormatter(seq) + assert labels('a', 1) == "world" + + def test_StrCategoryFormatterUnicode(self): + seq = ["Здравствуйте", "привет"] + labels = cat.StrCategoryFormatter(seq) + assert labels('a', 1) == "привет" def lt(tl): return [l.get_text() for l in tl] -class TestPlot(unittest.TestCase): - - def setUp(self): +class TestPlot(object): + @pytest.fixture + def data(self): self.d = ['a', 'b', 'c', 'a'] self.dticks = [0, 1, 2] self.dlabels = ['a', 'b', 'c'] - self.dunit_data = [('a', 0), ('b', 1), ('c', 2)] + unitmap = [('a', 0), ('b', 1), ('c', 2)] + self.dunit_data = MockUnitData(unitmap) + @pytest.fixture + def missing_data(self): self.dm = ['here', np.nan, 'here', 'there'] - self.dmticks = [-1, 0, 1] - self.dmlabels = ['nan', 'here', 'there'] - self.dmunit_data = [('nan', -1), ('here', 0), ('there', 1)] + self.dmticks = [0, -1, 1] + self.dmlabels = ['here', 'nan', 'there'] + unitmap = [('here', 0), ('nan', -1), ('there', 1)] + self.dmunit_data = MockUnitData(unitmap) + + def axis_test(self, axis, ticks, labels, unit_data): + np.testing.assert_array_equal(axis.get_majorticklocs(), ticks) + assert lt(axis.get_majorticklabels()) == labels + np.testing.assert_array_equal(axis.unit_data.locs, unit_data.locs) + assert axis.unit_data.seq == unit_data.seq @cleanup def test_plot_unicode(self): - # needs image test - works but - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + # Image test would fail on numpy 1.6 words = ['Здравствуйте', 'привет'] locs = [0.0, 1.0] + unit_data = MockUnitData(zip(words, locs)) + + fig, ax = plt.subplots() ax.plot(words) fig.canvas.draw() - self.assertListEqual(ax.yaxis.unit_data, - list(zip(words, locs))) - np.testing.assert_array_equal(ax.get_yticks(), locs) - self.assertListEqual(lt(ax.get_yticklabels()), words) + self.axis_test(ax.yaxis, locs, words, unit_data) @cleanup + @pytest.mark.usefixtures("data") def test_plot_1d(self): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() ax.plot(self.d) fig.canvas.draw() - np.testing.assert_array_equal(ax.get_yticks(), self.dticks) - self.assertListEqual(lt(ax.get_yticklabels()), - self.dlabels) - self.assertListEqual(ax.yaxis.unit_data, self.dunit_data) + self.axis_test(ax.yaxis, self.dticks, self.dlabels, self.dunit_data) @cleanup + @pytest.mark.usefixtures("missing_data") def test_plot_1d_missing(self): - - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() ax.plot(self.dm) fig.canvas.draw() - np.testing.assert_array_equal(ax.get_yticks(), self.dmticks) - self.assertListEqual(lt(ax.get_yticklabels()), - self.dmlabels) - self.assertListEqual(ax.yaxis.unit_data, self.dmunit_data) + self.axis_test(ax.yaxis, self.dmticks, self.dmlabels, self.dmunit_data) @cleanup + @pytest.mark.usefixtures("data", "missing_data") def test_plot_2d(self): - - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() ax.plot(self.dm, self.d) fig.canvas.draw() - np.testing.assert_array_equal(ax.get_xticks(), self.dmticks) - self.assertListEqual(lt(ax.get_xticklabels()), - self.dmlabels) - self.assertListEqual(ax.xaxis.unit_data, self.dmunit_data) - - np.testing.assert_array_equal(ax.get_yticks(), self.dticks) - self.assertListEqual(lt(ax.get_yticklabels()), - self.dlabels) - self.assertListEqual(ax.yaxis.unit_data, self.dunit_data) + self.axis_test(ax.xaxis, self.dmticks, self.dmlabels, self.dmunit_data) + self.axis_test(ax.yaxis, self.dticks, self.dlabels, self.dunit_data) @cleanup + @pytest.mark.usefixtures("data", "missing_data") def test_scatter_2d(self): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() ax.scatter(self.dm, self.d) fig.canvas.draw() - np.testing.assert_array_equal(ax.get_xticks(), self.dmticks) - self.assertListEqual(lt(ax.get_xticklabels()), - self.dmlabels) - self.assertListEqual(ax.xaxis.unit_data, self.dmunit_data) + self.axis_test(ax.xaxis, self.dmticks, self.dmlabels, self.dmunit_data) + self.axis_test(ax.yaxis, self.dticks, self.dlabels, self.dunit_data) - np.testing.assert_array_equal(ax.get_yticks(), self.dticks) - self.assertListEqual(lt(ax.get_yticklabels()), - self.dlabels) - self.assertListEqual(ax.yaxis.unit_data, self.dunit_data) - - @unittest.SkipTest @cleanup def test_plot_update(self): - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + fig, ax = plt.subplots() ax.plot(['a', 'b']) ax.plot(['a', 'b', 'd']) ax.plot(['b', 'c', 'd']) fig.canvas.draw() - labels_new = ['a', 'b', 'd', 'c'] - ticks_new = [0, 1, 2, 3] - self.assertListEqual(ax.yaxis.unit_data, - list(zip(labels_new, ticks_new))) - np.testing.assert_array_equal(ax.get_yticks(), ticks_new) - self.assertListEqual(lt(ax.get_yticklabels()), labels_new) + labels = ['a', 'b', 'd', 'c'] + ticks = [0, 1, 2, 3] + unit_data = MockUnitData(list(zip(labels, ticks))) + + self.axis_test(ax.yaxis, ticks, labels, unit_data) diff --git a/tox.ini b/tox.ini index 296cefb56281..ae8f1e90be3f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py31, py32 +envlist = py27, py34, py35 [testenv] changedir = /tmp @@ -15,3 +15,4 @@ deps = nose mock numpy + pytest \ No newline at end of file