From 0974ceee90f01c5082e8606c744d28276dfcfe19 Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Wed, 6 Feb 2019 15:03:38 +0000 Subject: [PATCH 1/5] more consistent error message for MultiIndex.from_arrays --- pandas/core/indexes/multi.py | 10 ++++++++-- pandas/tests/indexes/multi/test_constructor.py | 4 +--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index e2237afbcac0f..e90be57b4719d 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -324,20 +324,26 @@ def from_arrays(cls, arrays, sortorder=None, names=None): codes=[[0, 0, 1, 1], [1, 0, 1, 0]], names=['number', 'color']) """ + error_msg = "Input must be a list / sequence of array-likes." if not is_list_like(arrays): - raise TypeError("Input must be a list / sequence of array-likes.") + raise TypeError(error_msg) elif is_iterator(arrays): arrays = list(arrays) # Check if lengths of all arrays are equal or not, # raise ValueError, if not for i in range(1, len(arrays)): + if not is_list_like(arrays[i]): + raise TypeError(error_msg) if len(arrays[i]) != len(arrays[i - 1]): raise ValueError('all arrays must be same length') from pandas.core.arrays.categorical import _factorize_from_iterables - codes, levels = _factorize_from_iterables(arrays) + try: + codes, levels = _factorize_from_iterables(arrays) + except TypeError: + raise TypeError(error_msg) if names is None: names = [getattr(arr, "name", None) for arr in arrays] diff --git a/pandas/tests/indexes/multi/test_constructor.py b/pandas/tests/indexes/multi/test_constructor.py index 055d54c613260..9134d2d773ca4 100644 --- a/pandas/tests/indexes/multi/test_constructor.py +++ b/pandas/tests/indexes/multi/test_constructor.py @@ -256,9 +256,7 @@ def test_from_arrays_empty(): @pytest.mark.parametrize('invalid_sequence_of_arrays', [ 1, [1], [1, 2], [[1], 2], 'a', ['a'], ['a', 'b'], [['a'], 'b']]) def test_from_arrays_invalid_input(invalid_sequence_of_arrays): - msg = (r"Input must be a list / sequence of array-likes|" - r"Input must be list-like|" - r"object of type 'int' has no len\(\)") + msg = "Input must be a list / sequence of array-likes" with pytest.raises(TypeError, match=msg): MultiIndex.from_arrays(arrays=invalid_sequence_of_arrays) From 06cb448a8f89921fb95f0f3dad428b82e5066a60 Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Wed, 6 Feb 2019 15:47:12 +0000 Subject: [PATCH 2/5] add additional test case for loop check --- pandas/tests/indexes/multi/test_constructor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/multi/test_constructor.py b/pandas/tests/indexes/multi/test_constructor.py index 9134d2d773ca4..905232d443e0c 100644 --- a/pandas/tests/indexes/multi/test_constructor.py +++ b/pandas/tests/indexes/multi/test_constructor.py @@ -254,7 +254,7 @@ def test_from_arrays_empty(): @pytest.mark.parametrize('invalid_sequence_of_arrays', [ - 1, [1], [1, 2], [[1], 2], 'a', ['a'], ['a', 'b'], [['a'], 'b']]) + 1, [1], [1, 2], [[1], 2], [1, [2]], 'a', ['a'], ['a', 'b'], [['a'], 'b']]) def test_from_arrays_invalid_input(invalid_sequence_of_arrays): msg = "Input must be a list / sequence of array-likes" with pytest.raises(TypeError, match=msg): From 1438459cf4d259af70b8cd8396a2f0dcb970a217 Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Wed, 6 Feb 2019 15:47:55 +0000 Subject: [PATCH 3/5] own loop for sequence element list-like check --- pandas/core/indexes/multi.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index e90be57b4719d..9e97defac335f 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -330,20 +330,20 @@ def from_arrays(cls, arrays, sortorder=None, names=None): elif is_iterator(arrays): arrays = list(arrays) + # Check if elements of array are list-like + for array in arrays: + if not is_list_like(array): + raise TypeError(error_msg) + # Check if lengths of all arrays are equal or not, # raise ValueError, if not for i in range(1, len(arrays)): - if not is_list_like(arrays[i]): - raise TypeError(error_msg) if len(arrays[i]) != len(arrays[i - 1]): raise ValueError('all arrays must be same length') from pandas.core.arrays.categorical import _factorize_from_iterables - try: - codes, levels = _factorize_from_iterables(arrays) - except TypeError: - raise TypeError(error_msg) + codes, levels = _factorize_from_iterables(arrays) if names is None: names = [getattr(arr, "name", None) for arr in arrays] From 84993d7b6d6ec993e9116ae1f309014784a9451a Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Fri, 8 Feb 2019 11:21:09 +0000 Subject: [PATCH 4/5] test with tuples for invalid input --- pandas/tests/indexes/multi/test_constructor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/tests/indexes/multi/test_constructor.py b/pandas/tests/indexes/multi/test_constructor.py index 905232d443e0c..36783635c0775 100644 --- a/pandas/tests/indexes/multi/test_constructor.py +++ b/pandas/tests/indexes/multi/test_constructor.py @@ -254,7 +254,11 @@ def test_from_arrays_empty(): @pytest.mark.parametrize('invalid_sequence_of_arrays', [ - 1, [1], [1, 2], [[1], 2], [1, [2]], 'a', ['a'], ['a', 'b'], [['a'], 'b']]) + 1, [1], [1, 2], [[1], 2], [1, [2]], 'a', ['a'], ['a', 'b'], [['a'], 'b'], + (1,), (1, 2), ([1], 2), (1, [2]), 'a', ('a',), ('a', 'b'), (['a'], 'b'), + [(1,), 2], [1, (2,)], [('a',), 'b'], + ((1,), 2), (1, (2,)), (('a',), 'b') +]) def test_from_arrays_invalid_input(invalid_sequence_of_arrays): msg = "Input must be a list / sequence of array-likes" with pytest.raises(TypeError, match=msg): From 0974c3b8cc1b667cdf04b241d4844071299bb89f Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Fri, 8 Feb 2019 11:34:55 +0000 Subject: [PATCH 5/5] test with tuples for valid input --- pandas/tests/indexes/multi/test_constructor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pandas/tests/indexes/multi/test_constructor.py b/pandas/tests/indexes/multi/test_constructor.py index 36783635c0775..fe90e85cf93c8 100644 --- a/pandas/tests/indexes/multi/test_constructor.py +++ b/pandas/tests/indexes/multi/test_constructor.py @@ -142,6 +142,15 @@ def test_from_arrays_iterator(idx): MultiIndex.from_arrays(0) +def test_from_arrays_tuples(idx): + arrays = tuple(tuple(np.asarray(lev).take(level_codes)) + for lev, level_codes in zip(idx.levels, idx.codes)) + + # tuple of tuples as input + result = MultiIndex.from_arrays(arrays, names=idx.names) + tm.assert_index_equal(result, idx) + + def test_from_arrays_index_series_datetimetz(): idx1 = pd.date_range('2015-01-01 10:00', freq='D', periods=3, tz='US/Eastern')