From 407249b0b0bd52d75b578d199feb6d1f5edc51be Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 12 Jul 2016 08:03:06 -0500 Subject: [PATCH 1/2] COMPAT/TST Matplotlib 2.0 compatability --- ci/install_travis.sh | 2 +- ci/requirements-2.7_SLOW.pip | 1 + ci/requirements-2.7_SLOW.run | 1 - doc/source/whatsnew/v0.19.0.txt | 2 +- pandas/tests/plotting/common.py | 18 +++-- pandas/tests/plotting/test_datetimelike.py | 16 +++-- pandas/tests/plotting/test_frame.py | 76 ++++++++++++++++------ pandas/tests/plotting/test_misc.py | 10 ++- pandas/tests/plotting/test_series.py | 5 +- pandas/tools/plotting.py | 8 +++ 10 files changed, 104 insertions(+), 35 deletions(-) diff --git a/ci/install_travis.sh b/ci/install_travis.sh index 3d9651d4f579b..679501e9242dc 100755 --- a/ci/install_travis.sh +++ b/ci/install_travis.sh @@ -124,7 +124,7 @@ else echo "pip installs" REQ="ci/requirements-${TRAVIS_PYTHON_VERSION}${JOB_TAG}.pip" if [ -e ${REQ} ]; then - pip install --upgrade -r $REQ + pip install --pre --upgrade -r $REQ fi # remove any installed pandas package diff --git a/ci/requirements-2.7_SLOW.pip b/ci/requirements-2.7_SLOW.pip index e69de29bb2d1d..58955b979a24d 100644 --- a/ci/requirements-2.7_SLOW.pip +++ b/ci/requirements-2.7_SLOW.pip @@ -0,0 +1 @@ +matplotlib==2.0.0b3 diff --git a/ci/requirements-2.7_SLOW.run b/ci/requirements-2.7_SLOW.run index f02a7cb8a309a..513e0fd4ac5cf 100644 --- a/ci/requirements-2.7_SLOW.run +++ b/ci/requirements-2.7_SLOW.run @@ -1,7 +1,6 @@ python-dateutil pytz numpy=1.8.2 -matplotlib=1.3.1 scipy patsy statsmodels diff --git a/doc/source/whatsnew/v0.19.0.txt b/doc/source/whatsnew/v0.19.0.txt index 222bd250034d8..0483cb184ee19 100644 --- a/doc/source/whatsnew/v0.19.0.txt +++ b/doc/source/whatsnew/v0.19.0.txt @@ -427,7 +427,7 @@ Other enhancements - Added documentation to :ref:`I/O` regarding the perils of reading in columns with mixed dtypes and how to handle it (:issue:`13746`) - Raise ``ImportError`` in the sql functions when ``sqlalchemy`` is not installed and a connection string is used (:issue:`11920`). - +- Compatibility with matplotlib 2.0. Older versions of pandas should also work with matplotlib 2.0 (:issue:`13333`) .. _whatsnew_0190.api: diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py index faf16430fc94f..7dcc3d6e5734f 100644 --- a/pandas/tests/plotting/common.py +++ b/pandas/tests/plotting/common.py @@ -52,6 +52,7 @@ def setUp(self): self.mpl_ge_1_3_1 = plotting._mpl_ge_1_3_1() self.mpl_ge_1_4_0 = plotting._mpl_ge_1_4_0() self.mpl_ge_1_5_0 = plotting._mpl_ge_1_5_0() + self.mpl_ge_2_0_0 = plotting._mpl_ge_2_0_0() if self.mpl_ge_1_4_0: self.bp_n_objects = 7 @@ -64,6 +65,11 @@ def setUp(self): else: self.polycollection_factor = 1 + if self.mpl_ge_2_0_0: + self.default_figsize = (6.4, 4.8) + else: + self.default_figsize = (8.0, 6.0) + self.default_tick_position = 'left' if self.mpl_ge_2_0_0 else 'default' # common test data from pandas import read_csv path = os.path.join(os.path.dirname(curpath()), 'data', 'iris.csv') @@ -189,7 +195,9 @@ def _check_colors(self, collections, linecolors=None, facecolors=None, """ from matplotlib.lines import Line2D - from matplotlib.collections import Collection, PolyCollection + from matplotlib.collections import ( + Collection, PolyCollection, LineCollection + ) conv = self.colorconverter if linecolors is not None: @@ -203,7 +211,7 @@ def _check_colors(self, collections, linecolors=None, facecolors=None, result = patch.get_color() # Line2D may contains string color expression result = conv.to_rgba(result) - elif isinstance(patch, PolyCollection): + elif isinstance(patch, (PolyCollection, LineCollection)): result = tuple(patch.get_edgecolor()[0]) else: result = patch.get_edgecolor() @@ -318,7 +326,7 @@ def _check_ax_scales(self, axes, xaxis='linear', yaxis='linear'): self.assertEqual(ax.yaxis.get_scale(), yaxis) def _check_axes_shape(self, axes, axes_num=None, layout=None, - figsize=(8.0, 6.0)): + figsize=None): """ Check expected number of axes is drawn in expected layout @@ -333,6 +341,8 @@ def _check_axes_shape(self, axes, axes_num=None, layout=None, figsize : tuple expected figsize. default is matplotlib default """ + if figsize is None: + figsize = self.default_figsize visible_axes = self._flatten_visible(axes) if axes_num is not None: @@ -346,7 +356,7 @@ def _check_axes_shape(self, axes, axes_num=None, layout=None, self.assertEqual(result, layout) self.assert_numpy_array_equal( - np.round(visible_axes[0].figure.get_size_inches()), + visible_axes[0].figure.get_size_inches(), np.array(figsize, dtype=np.float64)) def _get_axes_layout(self, axes): diff --git a/pandas/tests/plotting/test_datetimelike.py b/pandas/tests/plotting/test_datetimelike.py index 492b9edff0122..0f7bc02e24915 100644 --- a/pandas/tests/plotting/test_datetimelike.py +++ b/pandas/tests/plotting/test_datetimelike.py @@ -26,6 +26,7 @@ class TestTSPlot(TestPlotBase): def setUp(self): TestPlotBase.setUp(self) + freq = ['S', 'T', 'H', 'D', 'W', 'M', 'Q', 'A'] idx = [period_range('12/31/1999', freq=x, periods=100) for x in freq] self.period_ser = [Series(np.random.randn(len(x)), x) for x in idx] @@ -122,7 +123,8 @@ def test_tsplot(self): _check_plot_works(s.plot, ax=ax) ax = ts.plot(style='k') - self.assertEqual((0., 0., 0.), ax.get_lines()[0].get_color()) + color = (0., 0., 0., 1) if self.mpl_ge_2_0_0 else (0., 0., 0.) + self.assertEqual(color, ax.get_lines()[0].get_color()) def test_both_style_and_color(self): import matplotlib.pyplot as plt # noqa @@ -575,7 +577,8 @@ def test_secondary_y(self): plt.close(fig) ax2 = ser2.plot() - self.assertEqual(ax2.get_yaxis().get_ticks_position(), 'default') + self.assertEqual(ax2.get_yaxis().get_ticks_position(), + self.default_tick_position) plt.close(ax2.get_figure()) ax = ser2.plot() @@ -605,7 +608,8 @@ def test_secondary_y_ts(self): plt.close(fig) ax2 = ser2.plot() - self.assertEqual(ax2.get_yaxis().get_ticks_position(), 'default') + self.assertEqual(ax2.get_yaxis().get_ticks_position(), + self.default_tick_position) plt.close(ax2.get_figure()) ax = ser2.plot() @@ -639,7 +643,8 @@ def test_secondary_frame(self): df = DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c']) axes = df.plot(secondary_y=['a', 'c'], subplots=True) self.assertEqual(axes[0].get_yaxis().get_ticks_position(), 'right') - self.assertEqual(axes[1].get_yaxis().get_ticks_position(), 'default') + self.assertEqual(axes[1].get_yaxis().get_ticks_position(), + self.default_tick_position) self.assertEqual(axes[2].get_yaxis().get_ticks_position(), 'right') @slow @@ -647,7 +652,8 @@ def test_secondary_bar_frame(self): df = DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c']) axes = df.plot(kind='bar', secondary_y=['a', 'c'], subplots=True) self.assertEqual(axes[0].get_yaxis().get_ticks_position(), 'right') - self.assertEqual(axes[1].get_yaxis().get_ticks_position(), 'default') + self.assertEqual(axes[1].get_yaxis().get_ticks_position(), + self.default_tick_position) self.assertEqual(axes[2].get_yaxis().get_ticks_position(), 'right') def test_mixed_freq_regular_first(self): diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 11180c3e9b4f7..91be0a7a73e35 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -10,6 +10,7 @@ import pandas as pd from pandas import (Series, DataFrame, MultiIndex, PeriodIndex, date_range, bdate_range) +from pandas.types.api import is_list_like from pandas.compat import (range, lrange, StringIO, lmap, lzip, u, zip, PY3) from pandas.formats.printing import pprint_thing import pandas.util.testing as tm @@ -952,9 +953,12 @@ def test_scatter_colors(self): with tm.assertRaises(TypeError): df.plot.scatter(x='a', y='b', c='c', color='green') + default_colors = self._maybe_unpack_cycler(self.plt.rcParams) + ax = df.plot.scatter(x='a', y='b', c='c') - tm.assert_numpy_array_equal(ax.collections[0].get_facecolor()[0], - np.array([0, 0, 1, 1], dtype=np.float64)) + tm.assert_numpy_array_equal( + ax.collections[0].get_facecolor()[0], + np.array(self.colorconverter.to_rgba(default_colors[0]))) ax = df.plot.scatter(x='a', y='b', color='white') tm.assert_numpy_array_equal(ax.collections[0].get_facecolor()[0], @@ -1623,6 +1627,8 @@ def test_line_colors_and_styles_subplots(self): axes = df.plot(subplots=True) for ax, c in zip(axes, list(default_colors)): + if self.mpl_ge_2_0_0: + c = [c] self._check_colors(ax.get_lines(), linecolors=c) tm.close() @@ -1703,9 +1709,14 @@ def test_area_colors(self): self._check_colors(poly, facecolors=custom_colors) handles, labels = ax.get_legend_handles_labels() - # legend is stored as Line2D, thus check linecolors - linehandles = [x for x in handles if not isinstance(x, PolyCollection)] - self._check_colors(linehandles, linecolors=custom_colors) + if self.mpl_ge_1_5_0: + self._check_colors(handles, facecolors=custom_colors) + else: + # legend is stored as Line2D, thus check linecolors + linehandles = [x for x in handles + if not isinstance(x, PolyCollection)] + self._check_colors(linehandles, linecolors=custom_colors) + for h in handles: self.assertTrue(h.get_alpha() is None) tm.close() @@ -1717,8 +1728,12 @@ def test_area_colors(self): self._check_colors(poly, facecolors=jet_colors) handles, labels = ax.get_legend_handles_labels() - linehandles = [x for x in handles if not isinstance(x, PolyCollection)] - self._check_colors(linehandles, linecolors=jet_colors) + if self.mpl_ge_1_5_0: + self._check_colors(handles, facecolors=jet_colors) + else: + linehandles = [x for x in handles + if not isinstance(x, PolyCollection)] + self._check_colors(linehandles, linecolors=jet_colors) for h in handles: self.assertTrue(h.get_alpha() is None) tm.close() @@ -1731,8 +1746,12 @@ def test_area_colors(self): self._check_colors(poly, facecolors=jet_with_alpha) handles, labels = ax.get_legend_handles_labels() - # Line2D can't have alpha in its linecolor - self._check_colors(handles[:len(jet_colors)], linecolors=jet_colors) + if self.mpl_ge_1_5_0: + linecolors = jet_with_alpha + else: + # Line2D can't have alpha in its linecolor + linecolors = jet_colors + self._check_colors(handles[:len(jet_colors)], linecolors=linecolors) for h in handles: self.assertEqual(h.get_alpha(), 0.5) @@ -1855,7 +1874,10 @@ def test_kde_colors_and_styles_subplots(self): @slow def test_boxplot_colors(self): def _check_colors(bp, box_c, whiskers_c, medians_c, caps_c='k', - fliers_c='b'): + fliers_c=None): + # TODO: outside this func? + if fliers_c is None: + fliers_c = 'k' if self.mpl_ge_2_0_0 else 'b' self._check_colors(bp['boxes'], linecolors=[box_c] * len(bp['boxes'])) self._check_colors(bp['whiskers'], @@ -2232,16 +2254,24 @@ def test_errorbar_asymmetrical(self): np.random.seed(0) err = np.random.rand(3, 2, 5) - data = np.random.randn(5, 3) - df = DataFrame(data) + # each column is [0, 1, 2, 3, 4], [3, 4, 5, 6, 7]... + df = DataFrame(np.arange(15).reshape(3, 5)).T + data = df.values ax = df.plot(yerr=err, xerr=err / 2) - self.assertEqual(ax.lines[7].get_ydata()[0], data[0, 1] - err[1, 0, 0]) - self.assertEqual(ax.lines[8].get_ydata()[0], data[0, 1] + err[1, 1, 0]) + if self.mpl_ge_2_0_0: + yerr_0_0 = ax.collections[1].get_paths()[0].vertices[:, 1] + expected_0_0 = err[0, :, 0] * np.array([-1, 1]) + tm.assert_almost_equal(yerr_0_0, expected_0_0) + else: + self.assertEqual(ax.lines[7].get_ydata()[0], + data[0, 1] - err[1, 0, 0]) + self.assertEqual(ax.lines[8].get_ydata()[0], + data[0, 1] + err[1, 1, 0]) - self.assertEqual(ax.lines[5].get_xdata()[0], -err[1, 0, 0] / 2) - self.assertEqual(ax.lines[6].get_xdata()[0], err[1, 1, 0] / 2) + self.assertEqual(ax.lines[5].get_xdata()[0], -err[1, 0, 0] / 2) + self.assertEqual(ax.lines[6].get_xdata()[0], err[1, 1, 0] / 2) with tm.assertRaises(ValueError): df.plot(yerr=err.T) @@ -2277,9 +2307,17 @@ def test_errorbar_scatter(self): self._check_has_errorbars(ax, xerr=1, yerr=1) def _check_errorbar_color(containers, expected, has_err='has_xerr'): - errs = [c.lines[1][0] - for c in ax.containers if getattr(c, has_err, False)] - self._check_colors(errs, linecolors=[expected] * len(errs)) + lines = [] + errs = [c.lines + for c in ax.containers if getattr(c, has_err, False)][0] + for el in errs: + if is_list_like(el): + lines.extend(el) + else: + lines.append(el) + err_lines = [x for x in lines if x in ax.collections] + self._check_colors( + err_lines, linecolors=np.array([expected] * len(err_lines))) # GH 8081 df = DataFrame( diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index 8b9a4fe05bb2e..a484217da5969 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -103,7 +103,10 @@ def test_scatter_matrix_axis(self): axes0_labels = axes[0][0].yaxis.get_majorticklabels() # GH 5662 - expected = ['-2', '-1', '0', '1', '2'] + if self.mpl_ge_2_0_0: + expected = ['-2', '0', '2'] + else: + expected = ['-2', '-1', '0', '1', '2'] self._check_text_labels(axes0_labels, expected) self._check_ticks_props( axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0) @@ -115,7 +118,10 @@ def test_scatter_matrix_axis(self): axes = _check_plot_works(scatter_matrix, filterwarnings='always', frame=df, range_padding=.1) axes0_labels = axes[0][0].yaxis.get_majorticklabels() - expected = ['-1.2', '-1.0', '-0.8', '-0.6', '-0.4', '-0.2', '0.0'] + if self.mpl_ge_2_0_0: + expected = ['-1.0', '-0.5', '0.0'] + else: + expected = ['-1.2', '-1.0', '-0.8', '-0.6', '-0.4', '-0.2', '0.0'] self._check_text_labels(axes0_labels, expected) self._check_ticks_props( axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0) diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index 2bd2f8255569d..e752197c6ad77 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -218,12 +218,13 @@ def test_bar_log(self): expected = np.hstack((1.0e-04, expected, 1.0e+01)) ax = Series([0.1, 0.01, 0.001]).plot(log=True, kind='bar') - self.assertEqual(ax.get_ylim(), (0.001, 0.10000000000000001)) + ymax = 0.12589254117941673 if self.mpl_ge_2_0_0 else .10000000000000001 + self.assertEqual(ax.get_ylim(), (0.001, ymax)) tm.assert_numpy_array_equal(ax.yaxis.get_ticklocs(), expected) tm.close() ax = Series([0.1, 0.01, 0.001]).plot(log=True, kind='barh') - self.assertEqual(ax.get_xlim(), (0.001, 0.10000000000000001)) + self.assertEqual(ax.get_xlim(), (0.001, ymax)) tm.assert_numpy_array_equal(ax.xaxis.get_ticklocs(), expected) @slow diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index a61a21d259e57..1abd11017dbfe 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -141,6 +141,14 @@ def _mpl_ge_1_5_0(): except ImportError: return False + +def _mpl_ge_2_0_0(): + try: + import matplotlib + return matplotlib.__version__ >= LooseVersion('2.0') + except ImportError: + return False + if _mpl_ge_1_5_0(): # Compat with mp 1.5, which uses cycler. import cycler From fdedc500796f673552e771893690779b478b90b7 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 13 Aug 2016 11:06:24 -0500 Subject: [PATCH 2/2] CI: install mpl-dev --- ci/install_travis.sh | 6 +++++- ci/requirements-2.7_SLOW.pip | 1 - ci/requirements-2.7_SLOW.run | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ci/install_travis.sh b/ci/install_travis.sh index 679501e9242dc..98ce36acc096e 100755 --- a/ci/install_travis.sh +++ b/ci/install_travis.sh @@ -124,7 +124,7 @@ else echo "pip installs" REQ="ci/requirements-${TRAVIS_PYTHON_VERSION}${JOB_TAG}.pip" if [ -e ${REQ} ]; then - pip install --pre --upgrade -r $REQ + pip install --upgrade -r $REQ fi # remove any installed pandas package @@ -138,5 +138,9 @@ else fi +if [ "$JOB_NAME" == "34_slow" ]; then + conda install -c conda-forge/label/rc -c conda-forge matplotlib +fi + echo "done" exit 0 diff --git a/ci/requirements-2.7_SLOW.pip b/ci/requirements-2.7_SLOW.pip index 58955b979a24d..e69de29bb2d1d 100644 --- a/ci/requirements-2.7_SLOW.pip +++ b/ci/requirements-2.7_SLOW.pip @@ -1 +0,0 @@ -matplotlib==2.0.0b3 diff --git a/ci/requirements-2.7_SLOW.run b/ci/requirements-2.7_SLOW.run index 513e0fd4ac5cf..f02a7cb8a309a 100644 --- a/ci/requirements-2.7_SLOW.run +++ b/ci/requirements-2.7_SLOW.run @@ -1,6 +1,7 @@ python-dateutil pytz numpy=1.8.2 +matplotlib=1.3.1 scipy patsy statsmodels