From b3445fc67bcfab41086738d0c70ff4929510af04 Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Thu, 7 Mar 2019 21:56:45 +0800 Subject: [PATCH 01/10] Cast `ExtensionArray` to `nd.array` before plot. Signed-off-by: HE, Tao --- doc/source/whatsnew/v0.24.2.rst | 1 + pandas/plotting/_core.py | 9 ++++++++- pandas/tests/plotting/test_frame.py | 6 ++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.2.rst b/doc/source/whatsnew/v0.24.2.rst index 4ca9d57f3a2e5..b3e69aca7d720 100644 --- a/doc/source/whatsnew/v0.24.2.rst +++ b/doc/source/whatsnew/v0.24.2.rst @@ -31,6 +31,7 @@ Fixed Regressions - Fixed regression in ``IntervalDtype`` construction where passing an incorrect string with 'Interval' as a prefix could result in a ``RecursionError``. (:issue:`25338`) - Fixed regression in :class:`Categorical`, where constructing it from a categorical ``Series`` and an explicit ``categories=`` that differed from that in the ``Series`` created an invalid object which could trigger segfaults. (:issue:`25318`) - Fixed pip installing from source into an environment without NumPy (:issue:`25193`) +- Fixed bug that :class:`ExtensionArray` cannot be used to matplotlib plotting (:issue:`25587`) .. _whatsnew_0242.enhancements: diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 48d870bfc2e03..ed09cdb1f0cca 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -20,6 +20,7 @@ ABCDataFrame, ABCIndexClass, ABCMultiIndex, ABCPeriodIndex, ABCSeries) from pandas.core.dtypes.missing import isna, notna, remove_na_arraylike +from pandas.core.arrays import ExtensionArray from pandas.core.base import PandasObject import pandas.core.common as com from pandas.core.config import get_option @@ -574,6 +575,13 @@ def _get_xticks(self, convert_period=False): @classmethod def _plot(cls, ax, x, y, style=None, is_errorbar=False, **kwds): + # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to + # np.ndarray before plot. + if isinstance(x, ExtensionArray): + x = x.__array__() + if isinstance(y, ExtensionArray): + y = y.__array__() + mask = isna(y) if mask.any(): y = np.ma.array(y) @@ -1792,7 +1800,6 @@ def _plot(data, x=None, y=None, subplots=False, ) label_name = label_kw or data.columns data.columns = label_name - plot_obj = klass(data, subplots=subplots, ax=ax, kind=kind, **kwds) plot_obj.generate() diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 98b241f5c8206..344d56f128b2b 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -144,6 +144,12 @@ def test_plot(self): result = ax.axes assert result is axes[0] + # GH 25587 + def test_integer_array_plot(self): + s = Series([4, 5, 3, 2], dtype="UInt32") + _check_plot_works(s.plot, yticks=[1, 2, 3, 4]) + _check_plot_works(s.plot, xticks=[4, 5, 3, 2]) + # GH 15516 def test_mpl2_color_cycle_str(self): colors = ['C' + str(x) for x in range(10)] From 7072085b82ed30a79b612962dba3d17a60d2c352 Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Fri, 8 Mar 2019 12:40:51 +0800 Subject: [PATCH 02/10] Revise. Signed-off-by: HE, Tao --- doc/source/whatsnew/v0.24.2.rst | 2 +- pandas/plotting/_core.py | 11 ++++++----- pandas/tests/plotting/test_frame.py | 13 ++++++++----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/doc/source/whatsnew/v0.24.2.rst b/doc/source/whatsnew/v0.24.2.rst index b3e69aca7d720..fba3834ff7ead 100644 --- a/doc/source/whatsnew/v0.24.2.rst +++ b/doc/source/whatsnew/v0.24.2.rst @@ -31,7 +31,7 @@ Fixed Regressions - Fixed regression in ``IntervalDtype`` construction where passing an incorrect string with 'Interval' as a prefix could result in a ``RecursionError``. (:issue:`25338`) - Fixed regression in :class:`Categorical`, where constructing it from a categorical ``Series`` and an explicit ``categories=`` that differed from that in the ``Series`` created an invalid object which could trigger segfaults. (:issue:`25318`) - Fixed pip installing from source into an environment without NumPy (:issue:`25193`) -- Fixed bug that :class:`ExtensionArray` cannot be used to matplotlib plotting (:issue:`25587`) +- Fixed bug where :class:`api.extensions.ExtensionArray` could not be used in matplotlib plotting (:issue:`25587`) .. _whatsnew_0242.enhancements: diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index ed09cdb1f0cca..cd1b92e9bf13f 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -15,7 +15,8 @@ from pandas.util._decorators import Appender, cache_readonly from pandas.core.dtypes.common import ( - is_hashable, is_integer, is_iterator, is_list_like, is_number) + is_hashable, is_integer, is_iterator, is_list_like, is_number, + is_extension_array_dtype) from pandas.core.dtypes.generic import ( ABCDataFrame, ABCIndexClass, ABCMultiIndex, ABCPeriodIndex, ABCSeries) from pandas.core.dtypes.missing import isna, notna, remove_na_arraylike @@ -577,10 +578,10 @@ def _get_xticks(self, convert_period=False): def _plot(cls, ax, x, y, style=None, is_errorbar=False, **kwds): # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to # np.ndarray before plot. - if isinstance(x, ExtensionArray): - x = x.__array__() - if isinstance(y, ExtensionArray): - y = y.__array__() + if is_extension_array_dtype(x): + x = np.asarray(x) + if is_extension_array_dtype(y): + y = np.asarray(y) mask = isna(y) if mask.any(): diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 344d56f128b2b..563ca309d5268 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -144,14 +144,17 @@ def test_plot(self): result = ax.axes assert result is axes[0] - # GH 25587 - def test_integer_array_plot(self): + @pytest.mark.parametrize("kwargs", [ + dict(yticks=[1, 2, 3, 4]), + dict(xticks=[4, 5, 3, 2]) + ]) + def test_integer_array_plot(self, kwargs): + # GH 25587 s = Series([4, 5, 3, 2], dtype="UInt32") - _check_plot_works(s.plot, yticks=[1, 2, 3, 4]) - _check_plot_works(s.plot, xticks=[4, 5, 3, 2]) + _check_plot_works(s.plot, **kwargs) - # GH 15516 def test_mpl2_color_cycle_str(self): + # GH 15516 colors = ['C' + str(x) for x in range(10)] df = DataFrame(randn(10, 3), columns=['a', 'b', 'c']) for c in colors: From ff7314d562354da899a0f727677c273235ab66a2 Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Fri, 8 Mar 2019 23:45:24 +0800 Subject: [PATCH 03/10] Remove unused import to fix the lint error. Signed-off-by: HE, Tao --- pandas/plotting/_core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index cd1b92e9bf13f..06653f96457ed 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -21,7 +21,6 @@ ABCDataFrame, ABCIndexClass, ABCMultiIndex, ABCPeriodIndex, ABCSeries) from pandas.core.dtypes.missing import isna, notna, remove_na_arraylike -from pandas.core.arrays import ExtensionArray from pandas.core.base import PandasObject import pandas.core.common as com from pandas.core.config import get_option From f97772ff9eb217782e30bb21d42a1b65481e964c Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Sat, 9 Mar 2019 09:41:44 +0800 Subject: [PATCH 04/10] Fix import order, revise the patch and add test with more kinds of plotting. Signed-off-by: HE, Tao --- pandas/plotting/_core.py | 17 ++++++++--------- pandas/tests/plotting/test_frame.py | 25 ++++++++++++++++++------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 06653f96457ed..6ca17d284fe01 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -15,8 +15,8 @@ from pandas.util._decorators import Appender, cache_readonly from pandas.core.dtypes.common import ( - is_hashable, is_integer, is_iterator, is_list_like, is_number, - is_extension_array_dtype) + is_extension_array_dtype, is_hashable, is_integer, is_iterator, + is_list_like, is_number) from pandas.core.dtypes.generic import ( ABCDataFrame, ABCIndexClass, ABCMultiIndex, ABCPeriodIndex, ABCSeries) from pandas.core.dtypes.missing import isna, notna, remove_na_arraylike @@ -364,6 +364,12 @@ def _compute_plot_data(self): raise TypeError('Empty {0!r}: no numeric data to ' 'plot'.format(numeric_data.__class__.__name__)) + # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to + # np.ndarray before plot. + for col in numeric_data: + if is_extension_array_dtype(numeric_data[col]): + numeric_data[col] = np.asarray(numeric_data[col]) + self.data = numeric_data def _make_plot(self): @@ -575,13 +581,6 @@ def _get_xticks(self, convert_period=False): @classmethod def _plot(cls, ax, x, y, style=None, is_errorbar=False, **kwds): - # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to - # np.ndarray before plot. - if is_extension_array_dtype(x): - x = np.asarray(x) - if is_extension_array_dtype(y): - y = np.asarray(y) - mask = isna(y) if mask.any(): y = np.ma.array(y) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 563ca309d5268..966a6ac062d91 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -13,6 +13,7 @@ from pandas.compat import PY3, lmap, lrange, lzip, range, u, zip import pandas.util._test_decorators as td +from pandas.core.arrays import integer_array from pandas.core.dtypes.api import is_list_like import pandas as pd @@ -144,14 +145,24 @@ def test_plot(self): result = ax.axes assert result is axes[0] - @pytest.mark.parametrize("kwargs", [ - dict(yticks=[1, 2, 3, 4]), - dict(xticks=[4, 5, 3, 2]) - ]) - def test_integer_array_plot(self, kwargs): + def test_integer_array_plot(self): # GH 25587 - s = Series([4, 5, 3, 2], dtype="UInt32") - _check_plot_works(s.plot, **kwargs) + arr = integer_array([1, 2, 3, 4], dtype="UInt32") + + s = Series(arr) + _check_plot_works(s.plot.line) + _check_plot_works(s.plot.bar) + _check_plot_works(s.plot.hist) + _check_plot_works(s.plot.pie) + + df = DataFrame({'x': arr, 'y': arr}) + _check_plot_works(df.plot.line) + _check_plot_works(df.plot.bar) + _check_plot_works(df.plot.hist) + _check_plot_works(df.plot.area) + _check_plot_works(df.plot.pie, y='y') + _check_plot_works(df.plot.scatter, x='x', y='y') + _check_plot_works(df.plot.hexbin, x='x', y='y') def test_mpl2_color_cycle_str(self): # GH 15516 From 755e8da6ea5721f17ed6d594a63739133e30562d Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Tue, 12 Mar 2019 12:54:30 +0800 Subject: [PATCH 05/10] Fix the import order in tests. Signed-off-by: HE, Tao --- pandas/tests/plotting/test_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index e700d29300693..eeccedf28855e 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -13,12 +13,12 @@ from pandas.compat import PY3, lmap, lrange, lzip, range, u, zip import pandas.util._test_decorators as td -from pandas.core.arrays import integer_array from pandas.core.dtypes.api import is_list_like import pandas as pd from pandas import ( DataFrame, MultiIndex, PeriodIndex, Series, bdate_range, date_range) +from pandas.core.arrays import integer_array from pandas.tests.plotting.common import ( TestPlotBase, _check_plot_works, _ok_for_gaussian_kde, _skip_if_no_scipy_gaussian_kde) From 6cf16e63baca4e549f82053ef103ebc63235d2d1 Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Tue, 12 Mar 2019 13:59:07 +0800 Subject: [PATCH 06/10] Fix CI. Signed-off-by: HE, Tao --- pandas/tests/plotting/test_frame.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index eeccedf28855e..7346a3b09aecf 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -159,7 +159,6 @@ def test_integer_array_plot(self): _check_plot_works(df.plot.line) _check_plot_works(df.plot.bar) _check_plot_works(df.plot.hist) - _check_plot_works(df.plot.area) _check_plot_works(df.plot.pie, y='y') _check_plot_works(df.plot.scatter, x='x', y='y') _check_plot_works(df.plot.hexbin, x='x', y='y') From 10e9b54cab1de0e98196692edc6b9155e803eeb8 Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Wed, 13 Mar 2019 21:37:17 +0800 Subject: [PATCH 07/10] Move to 0.25.0. Signed-off-by: HE, Tao --- doc/source/whatsnew/v0.24.2.rst | 1 - doc/source/whatsnew/v0.25.0.rst | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.24.2.rst b/doc/source/whatsnew/v0.24.2.rst index 1b8d102a1e6bd..8da33a46e79c6 100644 --- a/doc/source/whatsnew/v0.24.2.rst +++ b/doc/source/whatsnew/v0.24.2.rst @@ -33,7 +33,6 @@ Fixed Regressions - Fixed regression in :class:`Categorical`, where constructing it from a categorical ``Series`` and an explicit ``categories=`` that differed from that in the ``Series`` created an invalid object which could trigger segfaults. (:issue:`25318`) - Fixed pip installing from source into an environment without NumPy (:issue:`25193`) - Fixed regression in :meth:`DataFrame.to_csv` writing duplicate line endings with gzip compress (:issue:`25311`) -- Fixed bug where :class:`api.extensions.ExtensionArray` could not be used in matplotlib plotting (:issue:`25587`) .. _whatsnew_0242.enhancements: diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 284943cf49070..3a114f629f631 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -122,7 +122,7 @@ Bug Fixes ~~~~~~~~~ - Bug in :func:`to_datetime` which would raise an (incorrect) ``ValueError`` when called with a date far into the future and the ``format`` argument specified instead of raising ``OutOfBoundsDatetime`` (:issue:`23830`) - Bug in an error message in :meth:`DataFrame.plot`. Improved the error message if non-numerics are passed to :meth:`DataFrame.plot` (:issue:`25481`) -- +- Fixed bug where :class:`api.extensions.ExtensionArray` could not be used in matplotlib plotting (:issue:`25587`) Categorical ^^^^^^^^^^^ From 2c1fd7dbfe65f160309324f5bf2859eb91c72989 Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Thu, 14 Mar 2019 21:18:30 +0800 Subject: [PATCH 08/10] Copy the df and transform all columns to np.array. Signed-off-by: HE, Tao --- pandas/plotting/_core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 242d9da25e83d..af2db64cfa8a9 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -368,11 +368,11 @@ def _compute_plot_data(self): # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to # np.ndarray before plot. + numeric_data_np = numeric_data.copy() for col in numeric_data: - if is_extension_array_dtype(numeric_data[col]): - numeric_data[col] = np.asarray(numeric_data[col]) + numeric_data_np[col] = np.asarray(numeric_data[col]) - self.data = numeric_data + self.data = numeric_data_np def _make_plot(self): raise AbstractMethodError(self) From aafd21494db51bcccab737c30b59de2358f2e2fd Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Thu, 14 Mar 2019 23:02:59 +0800 Subject: [PATCH 09/10] Remove unused import. Signed-off-by: HE, Tao --- pandas/plotting/_core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index af2db64cfa8a9..86390729167b2 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -15,8 +15,7 @@ from pandas.util._decorators import Appender, cache_readonly from pandas.core.dtypes.common import ( - is_extension_array_dtype, is_hashable, is_integer, is_iterator, - is_list_like, is_number) + is_hashable, is_integer, is_iterator, is_list_like, is_number) from pandas.core.dtypes.generic import ( ABCDataFrame, ABCIndexClass, ABCMultiIndex, ABCPeriodIndex, ABCSeries) from pandas.core.dtypes.missing import isna, notna, remove_na_arraylike From 3afc9ebad2f2e7f5c280773a36168e1b44ad2c5f Mon Sep 17 00:00:00 2001 From: "HE, Tao" Date: Fri, 15 Mar 2019 00:09:58 +0800 Subject: [PATCH 10/10] Rename the variable. Signed-off-by: HE, Tao --- pandas/plotting/_core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 86390729167b2..68d5f30a399ac 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -367,11 +367,11 @@ def _compute_plot_data(self): # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to # np.ndarray before plot. - numeric_data_np = numeric_data.copy() + numeric_data = numeric_data.copy() for col in numeric_data: - numeric_data_np[col] = np.asarray(numeric_data[col]) + numeric_data[col] = np.asarray(numeric_data[col]) - self.data = numeric_data_np + self.data = numeric_data def _make_plot(self): raise AbstractMethodError(self)