From 369aaeb3c33303488fdd4efe173382678c4daa0f Mon Sep 17 00:00:00 2001 From: frreiss Date: Fri, 24 Apr 2020 09:14:23 -0700 Subject: [PATCH 1/4] Fix bug in displaying non-numeric arrays with >2 dimensions, and add tests --- pandas/io/formats/format.py | 6 +++++- pandas/tests/io/formats/test_format.py | 28 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 59542a8da535e..c7eb3eeedcadf 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -1228,7 +1228,11 @@ def _format(x): vals = extract_array(self.values, extract_numpy=True) - is_float_type = lib.map_infer(vals, is_float) & notna(vals) + is_float_type = ( + lib.map_infer(vals, is_float) + # vals may have 2 or more dimensions + & np.all(notna(vals), axis=tuple(range(1, len(vals.shape)))) + ) leading_space = self.leading_space if leading_space is None: leading_space = is_float_type.any() diff --git a/pandas/tests/io/formats/test_format.py b/pandas/tests/io/formats/test_format.py index f3c3344992942..3efa25429019d 100644 --- a/pandas/tests/io/formats/test_format.py +++ b/pandas/tests/io/formats/test_format.py @@ -2810,6 +2810,34 @@ def test_to_string_multindex_header(self): assert res == exp +class TestGenericArrayFormatter: + def test_1d_array(self): + # GenericArrayFormatter is used on types for which there isn't a dedicated + # formatter. np.bool is one of those types. + obj = fmt.GenericArrayFormatter(np.array([True, False])) + res = obj.get_result() + assert len(res) == 2 + # Results should be right-justified. + assert res[0] == " True" + assert res[1] == " False" + + def test_2d_array(self): + obj = fmt.GenericArrayFormatter(np.array([[True, False], [False, True]])) + res = obj.get_result() + assert len(res) == 2 + assert res[0] == " [True, False]" + assert res[1] == " [False, True]" + + def test_3d_array(self): + obj = fmt.GenericArrayFormatter( + np.array([[[True, True], [False, False]], [[False, True], [True, False]]]) + ) + res = obj.get_result() + assert len(res) == 2 + assert res[0] == " [[True, True], [False, False]]" + assert res[1] == " [[False, True], [True, False]]" + + def _three_digit_exp(): return f"{1.7e8:.4g}" == "1.7e+008" From 9614fe37f614be2e5a8e00c93c7f70bb54110d78 Mon Sep 17 00:00:00 2001 From: frreiss Date: Fri, 24 Apr 2020 10:22:50 -0700 Subject: [PATCH 2/4] Add whatsnew entry --- doc/source/whatsnew/v1.1.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index cd1cb0b64f74a..2354f2b43a8c7 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -642,7 +642,7 @@ ExtensionArray ^^^^^^^^^^^^^^ - Fixed bug where :meth:`Serires.value_counts` would raise on empty input of ``Int64`` dtype (:issue:`33317`) -- +- Fixed bug in :meth:`GenericArrayFormatter._format_strings` that caused :meth:`Series.__repr__()` to crash for some extension types (:issue:`33770`). Other From 17f167a7ac6a339d54d45dc38efbff834a5d6428 Mon Sep 17 00:00:00 2001 From: frreiss Date: Mon, 27 Apr 2020 15:45:28 -0700 Subject: [PATCH 3/4] Add test case that more closely replicates original error --- pandas/tests/io/formats/test_format.py | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pandas/tests/io/formats/test_format.py b/pandas/tests/io/formats/test_format.py index 3efa25429019d..c1850826926d8 100644 --- a/pandas/tests/io/formats/test_format.py +++ b/pandas/tests/io/formats/test_format.py @@ -2837,6 +2837,35 @@ def test_3d_array(self): assert res[0] == " [[True, True], [False, False]]" assert res[1] == " [[False, True], [True, False]]" + def test_2d_extension_type(self): + # GH 33770 + + # Define a stub extension type with just enough code to run Series.__repr__() + class DtypeStub(pd.api.extensions.ExtensionDtype): + @property + def type(self): + return np.ndarray + + @property + def name(self): + return "DtypeStub" + + class ExtTypeStub(pd.api.extensions.ExtensionArray): + def __len__(self): + return 2 + + def __getitem__(self, ix): + return [ix == 1, ix == 0] + + @property + def dtype(self): + return DtypeStub() + + series = pd.Series(ExtTypeStub()) + res = repr(series) # This line crashed before #33770 was fixed. + expected = "0 [False True]\n" + "1 [ True False]\n" + "dtype: DtypeStub" + assert res == expected + def _three_digit_exp(): return f"{1.7e8:.4g}" == "1.7e+008" From 75d22c82b4ab2da0f86237521a20e480c67bec45 Mon Sep 17 00:00:00 2001 From: frreiss Date: Mon, 27 Apr 2020 16:45:23 -0700 Subject: [PATCH 4/4] Make description of fix more clear to end-users --- doc/source/whatsnew/v1.1.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 2354f2b43a8c7..44ff696cf8806 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -642,7 +642,7 @@ ExtensionArray ^^^^^^^^^^^^^^ - Fixed bug where :meth:`Serires.value_counts` would raise on empty input of ``Int64`` dtype (:issue:`33317`) -- Fixed bug in :meth:`GenericArrayFormatter._format_strings` that caused :meth:`Series.__repr__()` to crash for some extension types (:issue:`33770`). +- Fixed bug that caused :meth:`Series.__repr__()` to crash for extension types whose elements are multidimensional arrays (:issue:`33770`). Other