Repository: pandas-dev/pandas

Issue:
COMPAT: mpl 2.0.0
seems @tacaswell / @mdboom have been busy

https://github.com/matplotlib/matplotlib/releases/tag/v2.0.0b1

we should test with this.

is this going up on a `conda-forge` dev channel? (is there such a thing)?



Discussion:
@sinhrks @jorisvandenbossche @TomAugspurger 

`conda install -c conda-forge/label/rc -c conda-forge matplotlib`

on OSX.

```
[Tue May 31 09:45:42 ~/pandas]$ nosetests  pandas/tests/test_graphics.py pandas/tests/test_graphics_others.py
EE
======================================================================
ERROR: Failure: AttributeError ('Cycler' object has no attribute 'change_key')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/nose/loader.py", line 418, in loadTestsFromName
    addr.filename, addr.module)
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/nose/importer.py", line 94, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/Users/jreback/pandas/pandas/__init__.py", line 39, in <module>
    from pandas.core.api import *
  File "/Users/jreback/pandas/pandas/core/api.py", line 10, in <module>
    from pandas.core.groupby import Grouper
  File "/Users/jreback/pandas/pandas/core/groupby.py", line 19, in <module>
    from pandas.core.frame import DataFrame
  File "/Users/jreback/pandas/pandas/core/frame.py", line 39, in <module>
    from pandas.core.series import Series
  File "/Users/jreback/pandas/pandas/core/series.py", line 2933, in <module>
    import pandas.tools.plotting as _gfx  # noqa
  File "/Users/jreback/pandas/pandas/tools/plotting.py", line 27, in <module>
    import pandas.tseries.converter as conv
  File "/Users/jreback/pandas/pandas/tseries/converter.py", line 7, in <module>
    import matplotlib.units as units
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/matplotlib/__init__.py", line 1162, in <module>
    rcParams = rc_params()
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/matplotlib/__init__.py", line 1005, in rc_params
    return rc_params_from_file(fname, fail_on_error)
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/matplotlib/__init__.py", line 1138, in rc_params_from_file
    if key not in _all_deprecated])
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/matplotlib/__init__.py", line 889, in __init__
    self[k] = v
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/matplotlib/__init__.py", line 906, in __setitem__
    cval = self.validate[key](val)
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/matplotlib/rcsetup.py", line 840, in validate_cycler
    cycler_inst.change_key(prop, norm_prop)
AttributeError: 'Cycler' object has no attribute 'change_key'

======================================================================
ERROR: Failure: ImportError (C extension: hashtable not built. If you want to import pandas from the source directory, you may need to run 'python setup.py build_ext --inplace --force' to build the C extensions first.)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/nose/loader.py", line 418, in loadTestsFromName
    addr.filename, addr.module)
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/Users/jreback/miniconda/lib/python2.7/site-packages/nose/importer.py", line 94, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/Users/jreback/pandas/pandas/__init__.py", line 31, in <module>
    "the C extensions first.".format(module))
ImportError: C extension: hashtable not built. If you want to import pandas from the source directory, you may need to run 'python setup.py build_ext --inplace --force' to build the C extensions first.

----------------------------------------------------------------------
Ran 2 tests in 0.001s
```

pandas master

You need cycler >= 0.10.  Should have pulled that from conda-forge

hmm, maybe needs to be part of your recipe?

xref: https://github.com/conda-forge/matplotlib-feedstock/pull/27

There looks some errors / failures (most of them are caused by figure size check). Will dig in.

Can we add one more `SLOW` build using mpl2.0 on Travis with allowed failure?

```
ERROR: test_errorbar_asymmetrical (pandas.tests.test_graphics.TestDataFramePlots)
ERROR: test_errorbar_scatter (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_bar_linewidth (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_boxplot (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_boxplot_colors (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_boxplot_vertical (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_hexbin_basic (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_hist_df (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_kde_df (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_line_colors_and_styles_subplots (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_line_lim (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_plot (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_plot_scatter (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_scatter_colors (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_subplots (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_subplots_layout (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_subplots_multiple_axes (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_subplots_timeseries (pandas.tests.test_graphics.TestDataFramePlots)
FAIL: test_bar_log (pandas.tests.test_graphics.TestSeriesPlots)
FAIL: test_hist_layout_with_by (pandas.tests.test_graphics.TestSeriesPlots)
FAIL: test_plot (pandas.tests.test_graphics.TestSeriesPlots)
```

@sinhrks sure. is 2.0 rc available on a channel somewhere?

Not yet. Will try to built from source if it's not available until I need.

I think it is available on conda-forge: https://anaconda.org/conda-forge/matplotlib/labels (edit: at least the beta, not rc)

There is no rc yet, should be one 'soon'

@sinhrks for the figsize failures, I think the new default in MPL is 6.4 x 4.8. We had the old 8 x 6 as the default for `_check_axes_shape`. Will just need to make that default None, and determine it based on the MPL version.

@tacaswell does this make sense to you? Regarding `errorbar`

``` python
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
r = ax.errorbar(range(5), range(5), xerr=.2)
print(ax.lines)
```

In MPL 1.5.x

``` python
[<matplotlib.lines.Line2D object at 0x1193411d0>, <matplotlib.lines.Line2D object at 0x1193414a8>, <matplotlib.lines.Line2D object at 0x119341da0>]
```

in MPL 2.0 beta

``` python
[<matplotlib.lines.Line2D object at 0x117a79128>]
```

I found https://github.com/matplotlib/matplotlib/issues/5047 (don't draw caps by default) but not sure if that's what's going on here.

We changed the default dpi so reduced the figure size (in inches) to keep the same figure size (in pixels).

re errorbar: that does look correct, the caps are not created which does not even add the artists.  I would look at the contents of `r` rather than `ax.lines` for testing purposes.

@sinhrks I can work on this if you want.


Resolving patch:
diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py
--- 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


Test patch:
diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py
--- 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
--- 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
--- 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
--- 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
--- 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