diff --git a/ci/install_conda.sh b/ci/install_conda.sh index c54f5494c12ee..8d99034a86109 100755 --- a/ci/install_conda.sh +++ b/ci/install_conda.sh @@ -72,6 +72,7 @@ bash miniconda.sh -b -p $HOME/miniconda || exit 1 conda config --set always_yes yes --set changeps1 no || exit 1 conda update -q conda || exit 1 +conda config --add channels conda-forge || exit 1 conda config --add channels http://conda.binstar.org/pandas || exit 1 conda config --set ssl_verify false || exit 1 diff --git a/ci/requirements-3.4.run b/ci/requirements-3.4.run index 73209a4623a49..c984036203251 100644 --- a/ci/requirements-3.4.run +++ b/ci/requirements-3.4.run @@ -11,7 +11,6 @@ beautiful-soup scipy numexpr pytables -matplotlib=1.3.1 lxml sqlalchemy bottleneck diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index 1a976bf0d921e..4ec5281b5f2ff 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -50,6 +50,7 @@ Highlights include: - Documentation comparing SAS to *pandas*, see :ref:`here ` - Removal of the automatic TimeSeries broadcasting, deprecated since 0.8.0, see :ref:`here ` - Compatibility with Python 3.5 (:issue:`11097`) +- Compatibility with matplotlib 1.5.0 (:issue:`11111`) Check the :ref:`API Changes ` and :ref:`deprecations ` before updating. diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index ad9518116a066..27069ddfd9c49 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -74,13 +74,22 @@ def setUp(self): 'weight': random.normal(161, 32, size=n), 'category': random.randint(4, size=n)}) - if str(mpl.__version__) >= LooseVersion('1.4'): + self.mpl_le_1_2_1 = plotting._mpl_le_1_2_1() + 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() + + if self.mpl_ge_1_4_0: self.bp_n_objects = 7 else: self.bp_n_objects = 8 + if self.mpl_ge_1_5_0: + # 1.5 added PolyCollections to legend handler + # so we have twice as many items. + self.polycollection_factor = 2 + else: + self.polycollection_factor = 1 - self.mpl_le_1_2_1 = str(mpl.__version__) <= LooseVersion('1.2.1') - self.mpl_ge_1_3_1 = str(mpl.__version__) >= LooseVersion('1.3.1') def tearDown(self): tm.close() @@ -183,7 +192,7 @@ def _check_colors(self, collections, linecolors=None, facecolors=None, """ from matplotlib.lines import Line2D - from matplotlib.collections import Collection + from matplotlib.collections import Collection, PolyCollection conv = self.colorconverter if linecolors is not None: @@ -197,6 +206,8 @@ 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): + result = tuple(patch.get_edgecolor()[0]) else: result = patch.get_edgecolor() @@ -472,6 +483,21 @@ def is_grid_on(): obj.plot(kind=kind, grid=True, **kws) self.assertTrue(is_grid_on()) + def _maybe_unpack_cycler(self, rcParams, field='color'): + """ + Compat layer for MPL 1.5 change to color cycle + + Before: plt.rcParams['axes.color_cycle'] -> ['b', 'g', 'r'...] + After : plt.rcParams['axes.prop_cycle'] -> cycler(...) + """ + if self.mpl_ge_1_5_0: + cyl = rcParams['axes.prop_cycle'] + colors = [v[field] for v in cyl] + else: + colors = rcParams['axes.color_cycle'] + return colors + + @tm.mplskip class TestSeriesPlots(TestPlotBase): @@ -536,9 +562,13 @@ def test_plot_figsize_and_title(self): def test_dont_modify_rcParams(self): # GH 8242 - colors = self.plt.rcParams['axes.color_cycle'] + if self.mpl_ge_1_5_0: + key = 'axes.prop_cycle' + else: + key = 'axes.color_cycle' + colors = self.plt.rcParams[key] Series([1, 2, 3]).plot() - self.assertEqual(colors, self.plt.rcParams['axes.color_cycle']) + self.assertEqual(colors, self.plt.rcParams[key]) def test_ts_line_lim(self): ax = self.ts.plot() @@ -770,8 +800,8 @@ def test_hist_legacy(self): _check_plot_works(self.ts.hist) _check_plot_works(self.ts.hist, grid=False) _check_plot_works(self.ts.hist, figsize=(8, 10)) - _check_plot_works(self.ts.hist, by=self.ts.index.month) - _check_plot_works(self.ts.hist, by=self.ts.index.month, bins=5) + _check_plot_works(self.ts.hist, filterwarnings='ignore', by=self.ts.index.month) + _check_plot_works(self.ts.hist, filterwarnings='ignore', by=self.ts.index.month, bins=5) fig, ax = self.plt.subplots(1, 1) _check_plot_works(self.ts.hist, ax=ax) @@ -805,25 +835,32 @@ def test_hist_layout(self): def test_hist_layout_with_by(self): df = self.hist_df - axes = _check_plot_works(df.height.hist, by=df.gender, layout=(2, 1)) + axes = _check_plot_works(df.height.hist, filterwarnings='ignore', + by=df.gender, layout=(2, 1)) self._check_axes_shape(axes, axes_num=2, layout=(2, 1)) - axes = _check_plot_works(df.height.hist, by=df.gender, layout=(3, -1)) + axes = _check_plot_works(df.height.hist, filterwarnings='ignore', + by=df.gender, layout=(3, -1)) self._check_axes_shape(axes, axes_num=2, layout=(3, 1)) - axes = _check_plot_works(df.height.hist, by=df.category, layout=(4, 1)) + axes = _check_plot_works(df.height.hist, filterwarnings='ignore', + by=df.category, layout=(4, 1)) self._check_axes_shape(axes, axes_num=4, layout=(4, 1)) - axes = _check_plot_works(df.height.hist, by=df.category, layout=(2, -1)) + axes = _check_plot_works(df.height.hist, filterwarnings='ignore', + by=df.category, layout=(2, -1)) self._check_axes_shape(axes, axes_num=4, layout=(2, 2)) - axes = _check_plot_works(df.height.hist, by=df.category, layout=(3, -1)) + axes = _check_plot_works(df.height.hist, filterwarnings='ignore', + by=df.category, layout=(3, -1)) self._check_axes_shape(axes, axes_num=4, layout=(3, 2)) - axes = _check_plot_works(df.height.hist, by=df.category, layout=(-1, 4)) + axes = _check_plot_works(df.height.hist, filterwarnings='ignore', + by=df.category, layout=(-1, 4)) self._check_axes_shape(axes, axes_num=4, layout=(1, 4)) - axes = _check_plot_works(df.height.hist, by=df.classroom, layout=(2, 2)) + axes = _check_plot_works(df.height.hist, filterwarnings='ignore', + by=df.classroom, layout=(2, 2)) self._check_axes_shape(axes, axes_num=3, layout=(2, 2)) axes = df.height.hist(by=df.category, layout=(4, 2), figsize=(12, 7)) @@ -1109,7 +1146,8 @@ def test_errorbar_plot(self): s.plot(yerr=np.arange(11)) s_err = ['zzz']*10 - with tm.assertRaises(TypeError): + # in mpl 1.5+ this is a TypeError + with tm.assertRaises((ValueError, TypeError)): s.plot(yerr=s_err) def test_table(self): @@ -1183,7 +1221,10 @@ def test_time_series_plot_color_kwargs(self): def test_time_series_plot_color_with_empty_kwargs(self): import matplotlib as mpl - def_colors = mpl.rcParams['axes.color_cycle'] + if self.mpl_ge_1_5_0: + def_colors = self._maybe_unpack_cycler(mpl.rcParams) + else: + def_colors = mpl.rcParams['axes.color_cycle'] index = date_range('1/1/2000', periods=12) s = Series(np.arange(1, 13), index=index) @@ -1213,14 +1254,16 @@ def setUp(self): @slow def test_plot(self): df = self.tdf - _check_plot_works(df.plot, grid=False) - axes = _check_plot_works(df.plot, subplots=True) + _check_plot_works(df.plot, filterwarnings='ignore', grid=False) + axes = _check_plot_works(df.plot, filterwarnings='ignore', subplots=True) self._check_axes_shape(axes, axes_num=4, layout=(4, 1)) - axes = _check_plot_works(df.plot, subplots=True, layout=(-1, 2)) + axes = _check_plot_works(df.plot, filterwarnings='ignore', + subplots=True, layout=(-1, 2)) self._check_axes_shape(axes, axes_num=4, layout=(2, 2)) - axes = _check_plot_works(df.plot, subplots=True, use_index=False) + axes = _check_plot_works(df.plot, filterwarnings='ignore', + subplots=True, use_index=False) self._check_axes_shape(axes, axes_num=4, layout=(4, 1)) df = DataFrame({'x': [1, 2], 'y': [3, 4]}) @@ -1229,13 +1272,14 @@ def test_plot(self): df = DataFrame(np.random.rand(10, 3), index=list(string.ascii_letters[:10])) + _check_plot_works(df.plot, use_index=True) _check_plot_works(df.plot, sort_columns=False) _check_plot_works(df.plot, yticks=[1, 5, 10]) _check_plot_works(df.plot, xticks=[1, 5, 10]) _check_plot_works(df.plot, ylim=(-100, 100), xlim=(-100, 100)) - _check_plot_works(df.plot, subplots=True, title='blah') + _check_plot_works(df.plot, filterwarnings='ignore', subplots=True, title='blah') # We have to redo it here because _check_plot_works does two plots, once without an ax # kwarg and once with an ax kwarg and the new sharex behaviour does not remove the # visibility of the latter axis (as ax is present). @@ -1292,7 +1336,11 @@ def test_plot(self): fig, ax = self.plt.subplots() axes = df.plot.bar(subplots=True, ax=ax) self.assertEqual(len(axes), 1) - self.assertIs(ax.get_axes(), axes[0]) + if self.mpl_ge_1_5_0: + result = ax.axes + else: + result = ax.get_axes() # deprecated + self.assertIs(result, axes[0]) def test_color_and_style_arguments(self): df = DataFrame({'x': [1, 2], 'y': [3, 4]}) @@ -1802,8 +1850,7 @@ def test_area_lim(self): @slow def test_bar_colors(self): import matplotlib.pyplot as plt - - default_colors = plt.rcParams.get('axes.color_cycle') + default_colors = self._maybe_unpack_cycler(plt.rcParams) df = DataFrame(randn(5, 5)) ax = df.plot.bar() @@ -2025,6 +2072,19 @@ def test_plot_scatter_with_c(self): float_array = np.array([0.0, 1.0]) df.plot.scatter(x='A', y='B', c=float_array, cmap='spring') + def test_scatter_colors(self): + df = DataFrame({'a': [1, 2, 3], 'b': [1, 2, 3], 'c': [1, 2, 3]}) + with tm.assertRaises(TypeError): + df.plot.scatter(x='a', y='b', c='c', color='green') + + ax = df.plot.scatter(x='a', y='b', c='c') + tm.assert_numpy_array_equal(ax.collections[0].get_facecolor()[0], + (0, 0, 1, 1)) + + ax = df.plot.scatter(x='a', y='b', color='white') + tm.assert_numpy_array_equal(ax.collections[0].get_facecolor()[0], + (1, 1, 1, 1)) + @slow def test_plot_bar(self): df = DataFrame(randn(6, 4), @@ -2033,7 +2093,7 @@ def test_plot_bar(self): _check_plot_works(df.plot.bar) _check_plot_works(df.plot.bar, legend=False) - _check_plot_works(df.plot.bar, subplots=True) + _check_plot_works(df.plot.bar, filterwarnings='ignore', subplots=True) _check_plot_works(df.plot.bar, stacked=True) df = DataFrame(randn(10, 15), @@ -2250,7 +2310,7 @@ def test_boxplot_vertical(self): self._check_text_labels(ax.get_yticklabels(), labels) self.assertEqual(len(ax.lines), self.bp_n_objects * len(numeric_cols)) - axes = _check_plot_works(df.plot.box, subplots=True, + axes = _check_plot_works(df.plot.box, filterwarnings='ignore', subplots=True, vert=False, logx=True) self._check_axes_shape(axes, axes_num=3, layout=(1, 3)) self._check_ax_scales(axes, xaxis='log') @@ -2310,7 +2370,7 @@ def test_kde_df(self): ax = df.plot(kind='kde', rot=20, fontsize=5) self._check_ticks_props(ax, xrot=20, xlabelsize=5, ylabelsize=5) - axes = _check_plot_works(df.plot, kind='kde', subplots=True) + axes = _check_plot_works(df.plot, filterwarnings='ignore', kind='kde', subplots=True) self._check_axes_shape(axes, axes_num=4, layout=(4, 1)) axes = df.plot(kind='kde', logy=True, subplots=True) @@ -2326,6 +2386,7 @@ def test_kde_missing_vals(self): @slow def test_hist_df(self): + from matplotlib.patches import Rectangle if self.mpl_le_1_2_1: raise nose.SkipTest("not supported in matplotlib <= 1.2.x") @@ -2336,7 +2397,7 @@ def test_hist_df(self): expected = [com.pprint_thing(c) for c in df.columns] self._check_legend_labels(ax, labels=expected) - axes = _check_plot_works(df.plot.hist, subplots=True, logy=True) + axes = _check_plot_works(df.plot.hist, filterwarnings='ignore', subplots=True, logy=True) self._check_axes_shape(axes, axes_num=4, layout=(4, 1)) self._check_ax_scales(axes, yaxis='log') @@ -2346,11 +2407,14 @@ def test_hist_df(self): ax = series.plot.hist(normed=True, cumulative=True, bins=4) # height of last bin (index 5) must be 1.0 - self.assertAlmostEqual(ax.get_children()[5].get_height(), 1.0) + rects = [x for x in ax.get_children() if isinstance(x, Rectangle)] + self.assertAlmostEqual(rects[-1].get_height(), 1.0) tm.close() ax = series.plot.hist(cumulative=True, bins=4) - self.assertAlmostEqual(ax.get_children()[5].get_height(), 100.0) + rects = [x for x in ax.get_children() if isinstance(x, Rectangle)] + + self.assertAlmostEqual(rects[-2].get_height(), 100.0) tm.close() # if horizontal, yticklabels are rotated @@ -2626,7 +2690,7 @@ def test_line_colors(self): def test_line_colors_and_styles_subplots(self): # GH 9894 from matplotlib import cm - default_colors = self.plt.rcParams.get('axes.color_cycle') + default_colors = self._maybe_unpack_cycler(self.plt.rcParams) df = DataFrame(randn(5, 5)) @@ -2698,7 +2762,8 @@ def test_area_colors(self): handles, labels = ax.get_legend_handles_labels() # legend is stored as Line2D, thus check linecolors - self._check_colors(handles, linecolors=custom_colors) + 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() @@ -2710,12 +2775,13 @@ def test_area_colors(self): self._check_colors(poly, facecolors=jet_colors) handles, labels = ax.get_legend_handles_labels() - self._check_colors(handles, linecolors=jet_colors) + 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() - # When stacked=True, alpha is set to 0.5 + # When stacked=False, alpha is set to 0.5 ax = df.plot.area(colormap=cm.jet, stacked=False) self._check_colors(ax.get_lines(), linecolors=jet_colors) poly = [o for o in ax.get_children() if isinstance(o, PolyCollection)] @@ -2724,13 +2790,13 @@ def test_area_colors(self): handles, labels = ax.get_legend_handles_labels() # Line2D can't have alpha in its linecolor - self._check_colors(handles, linecolors=jet_colors) + self._check_colors(handles[:len(jet_colors)], linecolors=jet_colors) for h in handles: self.assertEqual(h.get_alpha(), 0.5) @slow def test_hist_colors(self): - default_colors = self.plt.rcParams.get('axes.color_cycle') + default_colors = self._maybe_unpack_cycler(self.plt.rcParams) df = DataFrame(randn(5, 5)) ax = df.plot.hist() @@ -2791,7 +2857,7 @@ def test_kde_colors_and_styles_subplots(self): _skip_if_no_scipy_gaussian_kde() from matplotlib import cm - default_colors = self.plt.rcParams.get('axes.color_cycle') + default_colors = self._maybe_unpack_cycler(self.plt.rcParams) df = DataFrame(randn(5, 5)) @@ -2853,7 +2919,7 @@ def _check_colors(bp, box_c, whiskers_c, medians_c, caps_c='k', fliers_c='b'): self._check_colors(bp['fliers'], linecolors=[fliers_c] * len(bp['fliers'])) self._check_colors(bp['caps'], linecolors=[caps_c] * len(bp['caps'])) - default_colors = self.plt.rcParams.get('axes.color_cycle') + default_colors = self._maybe_unpack_cycler(self.plt.rcParams) df = DataFrame(randn(5, 5)) bp = df.plot.box(return_type='dict') @@ -2900,12 +2966,17 @@ def _check_colors(bp, box_c, whiskers_c, medians_c, caps_c='k', fliers_c='b'): def test_default_color_cycle(self): import matplotlib.pyplot as plt - plt.rcParams['axes.color_cycle'] = list('rgbk') + colors = list('rgbk') + if self.mpl_ge_1_5_0: + import cycler + plt.rcParams['axes.prop_cycle'] = cycler.cycler('color', colors) + else: + plt.rcParams['axes.color_cycle'] = colors df = DataFrame(randn(5, 3)) ax = df.plot() - expected = plt.rcParams['axes.color_cycle'][:3] + expected = self._maybe_unpack_cycler(plt.rcParams)[:3] self._check_colors(ax.get_lines(), linecolors=expected) def test_unordered_ts(self): @@ -3032,7 +3103,7 @@ def test_pie_df(self): ax = _check_plot_works(df.plot.pie, y=2) self._check_text_labels(ax.texts, df.index) - axes = _check_plot_works(df.plot.pie, subplots=True) + axes = _check_plot_works(df.plot.pie, filterwarnings='ignore', subplots=True) self.assertEqual(len(axes), len(df.columns)) for ax in axes: self._check_text_labels(ax.texts, df.index) @@ -3041,7 +3112,7 @@ def test_pie_df(self): labels = ['A', 'B', 'C', 'D', 'E'] color_args = ['r', 'g', 'b', 'c', 'm'] - axes = _check_plot_works(df.plot.pie, subplots=True, + axes = _check_plot_works(df.plot.pie, filterwarnings='ignore', subplots=True, labels=labels, colors=color_args) self.assertEqual(len(axes), len(df.columns)) @@ -3095,7 +3166,8 @@ def test_errorbar_plot(self): self._check_has_errorbars(ax, xerr=2, yerr=2) ax = _check_plot_works(df.plot, xerr=0.2, yerr=0.2, kind=kind) self._check_has_errorbars(ax, xerr=2, yerr=2) - axes = _check_plot_works(df.plot, yerr=df_err, xerr=df_err, subplots=True, kind=kind) + axes = _check_plot_works(df.plot, filterwarnings='ignore', yerr=df_err, + xerr=df_err, subplots=True, kind=kind) self._check_has_errorbars(axes, xerr=1, yerr=1) ax = _check_plot_works((df+1).plot, yerr=df_err, xerr=df_err, kind='bar', log=True) @@ -3125,7 +3197,7 @@ def test_errorbar_plot(self): df.plot(yerr=np.random.randn(11)) df_err = DataFrame({'x': ['zzz']*12, 'y': ['zzz']*12}) - with tm.assertRaises(TypeError): + with tm.assertRaises((ValueError, TypeError)): df.plot(yerr=df_err) @slow @@ -3184,7 +3256,8 @@ def test_errorbar_timeseries(self): self._check_has_errorbars(ax, xerr=0, yerr=1) ax = _check_plot_works(tdf.plot, yerr=tdf_err, kind=kind) self._check_has_errorbars(ax, xerr=0, yerr=2) - axes = _check_plot_works(tdf.plot, kind=kind, yerr=tdf_err, subplots=True) + axes = _check_plot_works(tdf.plot, filterwarnings='ignore', kind=kind, + yerr=tdf_err, subplots=True) self._check_has_errorbars(axes, xerr=0, yerr=1) def test_errorbar_asymmetrical(self): @@ -3629,37 +3702,38 @@ def assert_is_valid_plot_return_object(objs): ''.format(objs.__class__.__name__)) -def _check_plot_works(f, *args, **kwargs): +def _check_plot_works(f, filterwarnings='always', **kwargs): import matplotlib.pyplot as plt ret = None - - try: + with warnings.catch_warnings(): + warnings.simplefilter(filterwarnings) try: - fig = kwargs['figure'] - except KeyError: - fig = plt.gcf() - - plt.clf() + try: + fig = kwargs['figure'] + except KeyError: + fig = plt.gcf() - ax = kwargs.get('ax', fig.add_subplot(211)) - ret = f(*args, **kwargs) + plt.clf() - assert_is_valid_plot_return_object(ret) + ax = kwargs.get('ax', fig.add_subplot(211)) + ret = f(**kwargs) - try: - kwargs['ax'] = fig.add_subplot(212) - ret = f(*args, **kwargs) - except Exception: - pass - else: assert_is_valid_plot_return_object(ret) - with ensure_clean(return_filelike=True) as path: - plt.savefig(path) - finally: - tm.close(fig) + try: + kwargs['ax'] = fig.add_subplot(212) + ret = f(**kwargs) + except Exception: + pass + else: + assert_is_valid_plot_return_object(ret) + + with ensure_clean(return_filelike=True) as path: + plt.savefig(path) + finally: + tm.close(fig) - return ret + return ret def _generate_4_axes_via_gridspec(): import matplotlib.pyplot as plt diff --git a/pandas/tests/test_graphics_others.py b/pandas/tests/test_graphics_others.py index 641180c8010c0..b18cbae600b43 100644 --- a/pandas/tests/test_graphics_others.py +++ b/pandas/tests/test_graphics_others.py @@ -161,8 +161,8 @@ def test_plot_fails_when_ax_differs_from_figure(self): @slow def test_autocorrelation_plot(self): from pandas.tools.plotting import autocorrelation_plot - _check_plot_works(autocorrelation_plot, self.ts) - _check_plot_works(autocorrelation_plot, self.ts.values) + _check_plot_works(autocorrelation_plot, series=self.ts) + _check_plot_works(autocorrelation_plot, series=self.ts.values) ax = autocorrelation_plot(self.ts, label='Test') self._check_legend_labels(ax, labels=['Test']) @@ -170,13 +170,13 @@ def test_autocorrelation_plot(self): @slow def test_lag_plot(self): from pandas.tools.plotting import lag_plot - _check_plot_works(lag_plot, self.ts) - _check_plot_works(lag_plot, self.ts, lag=5) + _check_plot_works(lag_plot, series=self.ts) + _check_plot_works(lag_plot, series=self.ts, lag=5) @slow def test_bootstrap_plot(self): from pandas.tools.plotting import bootstrap_plot - _check_plot_works(bootstrap_plot, self.ts, size=10) + _check_plot_works(bootstrap_plot, series=self.ts, size=10) @tm.mplskip @@ -210,7 +210,7 @@ def test_boxplot_legacy(self): _check_plot_works(df.boxplot, column='one', by=['indic', 'indic2']) _check_plot_works(df.boxplot, by='indic') _check_plot_works(df.boxplot, by=['indic', 'indic2']) - _check_plot_works(plotting.boxplot, df['one'], return_type='dict') + _check_plot_works(plotting.boxplot, data=df['one'], return_type='dict') _check_plot_works(df.boxplot, notch=1, return_type='dict') _check_plot_works(df.boxplot, by='indic', notch=1) @@ -304,6 +304,7 @@ def test_boxplot_empty_column(self): @slow def test_hist_df_legacy(self): + from matplotlib.patches import Rectangle _check_plot_works(self.hist_df.hist) # make sure layout is handled @@ -347,7 +348,8 @@ def test_hist_df_legacy(self): # make sure kwargs to hist are handled ax = ser.hist(normed=True, cumulative=True, bins=4) # height of last bin (index 5) must be 1.0 - self.assertAlmostEqual(ax.get_children()[5].get_height(), 1.0) + rects = [x for x in ax.get_children() if isinstance(x, Rectangle)] + self.assertAlmostEqual(rects[-1].get_height(), 1.0) tm.close() ax = ser.hist(log=True) @@ -413,9 +415,9 @@ def scat(**kwds): def scat2(x, y, by=None, ax=None, figsize=None): return plotting.scatter_plot(df, x, y, by, ax, figsize=None) - _check_plot_works(scat2, 0, 1) + _check_plot_works(scat2, x=0, y=1) grouper = Series(np.repeat([1, 2, 3, 4, 5], 20), df.index) - _check_plot_works(scat2, 0, 1, by=grouper) + _check_plot_works(scat2, x=0, y=1, by=grouper) def test_scatter_matrix_axis(self): tm._skip_if_no_scipy() @@ -424,7 +426,8 @@ def test_scatter_matrix_axis(self): with tm.RNGContext(42): df = DataFrame(randn(100, 3)) - axes = _check_plot_works(scatter_matrix, df, range_padding=.1) + axes = _check_plot_works(scatter_matrix, filterwarnings='always', frame=df, + range_padding=.1) axes0_labels = axes[0][0].yaxis.get_majorticklabels() # GH 5662 expected = ['-2', '-1', '0', '1', '2'] @@ -432,7 +435,8 @@ def test_scatter_matrix_axis(self): self._check_ticks_props(axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0) df[0] = ((df[0] - 2) / 3) - axes = _check_plot_works(scatter_matrix, df, range_padding=.1) + 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'] self._check_text_labels(axes0_labels, expected) @@ -445,17 +449,17 @@ def test_andrews_curves(self): df = self.iris - _check_plot_works(andrews_curves, df, 'Name') + _check_plot_works(andrews_curves, frame=df, class_column='Name') rgba = ('#556270', '#4ECDC4', '#C7F464') - ax = _check_plot_works(andrews_curves, df, 'Name', color=rgba) + ax = _check_plot_works(andrews_curves, frame=df, class_column='Name', color=rgba) self._check_colors(ax.get_lines()[:10], linecolors=rgba, mapping=df['Name'][:10]) cnames = ['dodgerblue', 'aquamarine', 'seagreen'] - ax = _check_plot_works(andrews_curves, df, 'Name', color=cnames) + ax = _check_plot_works(andrews_curves, frame=df, class_column='Name', color=cnames) self._check_colors(ax.get_lines()[:10], linecolors=cnames, mapping=df['Name'][:10]) - ax = _check_plot_works(andrews_curves, df, 'Name', colormap=cm.jet) + ax = _check_plot_works(andrews_curves, frame=df, class_column='Name', colormap=cm.jet) cmaps = lmap(cm.jet, np.linspace(0, 1, df['Name'].nunique())) self._check_colors(ax.get_lines()[:10], linecolors=cmaps, mapping=df['Name'][:10]) @@ -478,23 +482,23 @@ def test_parallel_coordinates(self): df = self.iris - ax = _check_plot_works(parallel_coordinates, df, 'Name') + ax = _check_plot_works(parallel_coordinates, frame=df, class_column='Name') nlines = len(ax.get_lines()) nxticks = len(ax.xaxis.get_ticklabels()) rgba = ('#556270', '#4ECDC4', '#C7F464') - ax = _check_plot_works(parallel_coordinates, df, 'Name', color=rgba) + ax = _check_plot_works(parallel_coordinates, frame=df, class_column='Name', color=rgba) self._check_colors(ax.get_lines()[:10], linecolors=rgba, mapping=df['Name'][:10]) cnames = ['dodgerblue', 'aquamarine', 'seagreen'] - ax = _check_plot_works(parallel_coordinates, df, 'Name', color=cnames) + ax = _check_plot_works(parallel_coordinates, frame=df, class_column='Name', color=cnames) self._check_colors(ax.get_lines()[:10], linecolors=cnames, mapping=df['Name'][:10]) - ax = _check_plot_works(parallel_coordinates, df, 'Name', colormap=cm.jet) + ax = _check_plot_works(parallel_coordinates, frame=df, class_column='Name', colormap=cm.jet) cmaps = lmap(cm.jet, np.linspace(0, 1, df['Name'].nunique())) self._check_colors(ax.get_lines()[:10], linecolors=cmaps, mapping=df['Name'][:10]) - ax = _check_plot_works(parallel_coordinates, df, 'Name', axvlines=False) + ax = _check_plot_works(parallel_coordinates, frame=df, class_column='Name', axvlines=False) assert len(ax.get_lines()) == (nlines - nxticks) colors = ['b', 'g', 'r'] @@ -517,20 +521,20 @@ def test_radviz(self): from matplotlib import cm df = self.iris - _check_plot_works(radviz, df, 'Name') + _check_plot_works(radviz, frame=df, class_column='Name') rgba = ('#556270', '#4ECDC4', '#C7F464') - ax = _check_plot_works(radviz, df, 'Name', color=rgba) + ax = _check_plot_works(radviz, frame=df, class_column='Name', color=rgba) # skip Circle drawn as ticks patches = [p for p in ax.patches[:20] if p.get_label() != ''] self._check_colors(patches[:10], facecolors=rgba, mapping=df['Name'][:10]) cnames = ['dodgerblue', 'aquamarine', 'seagreen'] - _check_plot_works(radviz, df, 'Name', color=cnames) + _check_plot_works(radviz, frame=df, class_column='Name', color=cnames) patches = [p for p in ax.patches[:20] if p.get_label() != ''] self._check_colors(patches, facecolors=cnames, mapping=df['Name'][:10]) - _check_plot_works(radviz, df, 'Name', colormap=cm.jet) + _check_plot_works(radviz, frame=df, class_column='Name', colormap=cm.jet) cmaps = lmap(cm.jet, np.linspace(0, 1, df['Name'].nunique())) patches = [p for p in ax.patches[:20] if p.get_label() != ''] self._check_colors(patches, facecolors=cmaps, mapping=df['Name'][:10]) @@ -607,6 +611,8 @@ def test_grouped_plot_fignums(self): @slow def test_grouped_hist_legacy(self): + from matplotlib.patches import Rectangle + df = DataFrame(randn(500, 2), columns=['A', 'B']) df['C'] = np.random.randint(0, 4, 500) df['D'] = ['X'] * 500 @@ -633,7 +639,8 @@ def test_grouped_hist_legacy(self): xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot) # height of last bin (index 5) must be 1.0 for ax in axes.ravel(): - height = ax.get_children()[5].get_height() + rects = [x for x in ax.get_children() if isinstance(x, Rectangle)] + height = rects[-1].get_height() self.assertAlmostEqual(height, 1.0) self._check_ticks_props(axes, xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index cd2297d6018ca..55464a7f1d23e 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -24,13 +24,13 @@ from pandas.compat import range, lrange, lmap, map, zip, string_types import pandas.compat as compat from pandas.util.decorators import Appender - try: # mpl optional import pandas.tseries.converter as conv conv.register() # needs to override so set_xlim works with str/number except ImportError: pass + # Extracted from https://gist.github.com/huyng/816622 # this is the rcParams set when setting display.with_mpl_style # to True. @@ -97,6 +97,48 @@ 'ytick.minor.size': 0.0 } + +def _mpl_le_1_2_1(): + try: + import matplotlib as mpl + return (str(mpl.__version__) <= LooseVersion('1.2.1') and + str(mpl.__version__)[0] != '0') + except ImportError: + return False + +def _mpl_ge_1_3_1(): + try: + import matplotlib + # The or v[0] == '0' is because their versioneer is + # messed up on dev + return (matplotlib.__version__ >= LooseVersion('1.3.1') + or matplotlib.__version__[0] == '0') + except ImportError: + return False + +def _mpl_ge_1_4_0(): + try: + import matplotlib + return (matplotlib.__version__ >= LooseVersion('1.4') + or matplotlib.__version__[0] == '0') + except ImportError: + return False + +def _mpl_ge_1_5_0(): + try: + import matplotlib + return (matplotlib.__version__ >= LooseVersion('1.5') + or matplotlib.__version__[0] == '0') + except ImportError: + return False + +if _mpl_ge_1_5_0(): + # Compat with mp 1.5, which uses cycler. + import cycler + colors = mpl_stylesheet.pop('axes.color_cycle') + mpl_stylesheet['axes.prop_cycle'] = cycler.cycler('color_cycle', colors) + + def _get_standard_kind(kind): return {'density': 'kde'}.get(kind, kind) @@ -784,7 +826,6 @@ def _kind(self): _layout_type = 'vertical' _default_rot = 0 orientation = None - _pop_attributes = ['label', 'style', 'logy', 'logx', 'loglog', 'mark_right', 'stacked'] _attr_defaults = {'logy': False, 'logx': False, 'loglog': False, @@ -973,8 +1014,9 @@ def _maybe_right_yaxis(self, ax, axes_num): else: # otherwise, create twin axes orig_ax, new_ax = ax, ax.twinx() - new_ax._get_lines.color_cycle = orig_ax._get_lines.color_cycle - + # TODO: use Matplotlib public API when available + new_ax._get_lines = orig_ax._get_lines + new_ax._get_patches_for_fill = orig_ax._get_patches_for_fill orig_ax.right_ax, new_ax.left_ax = new_ax, orig_ax if not self._has_plotted_object(orig_ax): # no data on left y @@ -1197,6 +1239,14 @@ def plt(self): import matplotlib.pyplot as plt return plt + @staticmethod + def mpl_ge_1_3_1(): + return _mpl_ge_1_3_1() + + @staticmethod + def mpl_ge_1_5_0(): + return _mpl_ge_1_5_0() + _need_to_set_index = False def _get_xticks(self, convert_period=False): @@ -1474,9 +1524,6 @@ def __init__(self, data, x, y, s=None, c=None, **kwargs): self.c = c def _make_plot(self): - import matplotlib as mpl - mpl_ge_1_3_1 = str(mpl.__version__) >= LooseVersion('1.3.1') - x, y, c, data = self.x, self.y, self.c, self.data ax = self.axes[0] @@ -1488,9 +1535,13 @@ def _make_plot(self): # pandas uses colormap, matplotlib uses cmap. cmap = self.colormap or 'Greys' cmap = self.plt.cm.get_cmap(cmap) - - if c is None: + color = self.kwds.pop("color", None) + if c is not None and color is not None: + raise TypeError('Specify exactly one of `c` and `color`') + elif c is None and color is None: c_values = self.plt.rcParams['patch.facecolor'] + elif color is not None: + c_values = color elif c_is_column: c_values = self.data[c].values else: @@ -1505,7 +1556,7 @@ def _make_plot(self): if cb: img = ax.collections[0] kws = dict(ax=ax) - if mpl_ge_1_3_1: + if self.mpl_ge_1_3_1(): kws['label'] = c if c_is_column else '' self.fig.colorbar(img, **kws) @@ -1636,6 +1687,11 @@ def _ts_plot(cls, ax, x, data, style=None, **kwds): # Set ax with freq info _decorate_axes(ax, freq, kwds) + # digging deeper + if hasattr(ax, 'left_ax'): + _decorate_axes(ax.left_ax, freq, kwds) + if hasattr(ax, 'right_ax'): + _decorate_axes(ax.right_ax, freq, kwds) ax._plot_data.append((data, cls._kind, kwds)) lines = cls._plot(ax, data.index, data.values, style=style, **kwds) @@ -1743,6 +1799,8 @@ def _plot(cls, ax, x, y, style=None, column_num=None, if not 'color' in kwds: kwds['color'] = lines[0].get_color() + if cls.mpl_ge_1_5_0(): # mpl 1.5 added real support for poly legends + kwds.pop('label') ax.fill_between(xdata, start, y_values, **kwds) cls._update_stacker(ax, stacking_id, y) return lines diff --git a/pandas/tseries/tests/test_plotting.py b/pandas/tseries/tests/test_plotting.py index d9b31c0a1d620..bcd1e400d3974 100644 --- a/pandas/tseries/tests/test_plotting.py +++ b/pandas/tseries/tests/test_plotting.py @@ -483,6 +483,7 @@ def test_gaps(self): ts[5:25] = np.nan ax = ts.plot() lines = ax.get_lines() + tm._skip_if_mpl_1_5() self.assertEqual(len(lines), 1) l = lines[0] data = l.get_xydata() @@ -532,6 +533,9 @@ def test_gap_upsample(self): self.assertEqual(len(ax.right_ax.get_lines()), 1) l = lines[0] data = l.get_xydata() + + tm._skip_if_mpl_1_5() + tm.assertIsInstance(data, np.ma.core.MaskedArray) mask = data.mask self.assertTrue(mask[5:25, 1].all()) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 6d443c8fbc380..d70e6349ddef1 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -196,6 +196,14 @@ def setUpClass(cls): return cls +def _skip_if_mpl_1_5(): + import matplotlib + v = matplotlib.__version__ + if v > LooseVersion('1.4.3') or v[0] == '0': + import nose + raise nose.SkipTest("matplotlib 1.5") + + def _skip_if_no_scipy(): try: import scipy.stats