Skip to content

Commit 7f38fd5

Browse files
committed
Give user-friendly exceptions
1 parent aec3347 commit 7f38fd5

File tree

3 files changed

+68
-14
lines changed

3 files changed

+68
-14
lines changed

doc/source/whatsnew/v0.22.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Indexing
138138
- Bug in :func:`Series.truncate` which raises ``TypeError`` with a monotonic ``PeriodIndex`` (:issue:`17717`)
139139
- Bug in :func:`DataFrame.groupby` where tuples were interpreted as lists of keys rather than as keys (:issue:`17979`, :issue:`18249`)
140140
- Bug in :func:`MultiIndex.remove_unused_levels`` which would fill nan values (:issue:`18417`)
141+
- Bug in :func:`MultiIndex.from_tuples`` which would fail to take zipped tuples in python3 (:issue:`18434`)
141142
- Bug in :class:`IntervalIndex` where empty and purely NA data was constructed inconsistently depending on the construction method (:issue:`18421`)
142143
-
143144

pandas/core/indexes/multi.py

+29-12
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,11 @@ def from_arrays(cls, arrays, sortorder=None, names=None):
11621162
MultiIndex.from_product : Make a MultiIndex from cartesian product
11631163
of iterables
11641164
"""
1165+
if not is_list_like(arrays):
1166+
raise TypeError("Input must be a list / sequence of array-likes.")
1167+
elif is_iterator(arrays):
1168+
arrays = list(arrays)
1169+
11651170
# Check if lengths of all arrays are equal or not,
11661171
# raise ValueError, if not
11671172
for i in range(1, len(arrays)):
@@ -1206,6 +1211,11 @@ def from_tuples(cls, tuples, sortorder=None, names=None):
12061211
MultiIndex.from_product : Make a MultiIndex from cartesian product
12071212
of iterables
12081213
"""
1214+
if not is_list_like(tuples):
1215+
raise TypeError('Input must be a list /sequence of tuple-likes.')
1216+
elif is_iterator(tuples):
1217+
tuples = list(tuples)
1218+
12091219
if len(tuples) == 0:
12101220
if names is None:
12111221
msg = 'Cannot infer number of levels from empty list'
@@ -1260,6 +1270,11 @@ def from_product(cls, iterables, sortorder=None, names=None):
12601270
from pandas.core.categorical import _factorize_from_iterables
12611271
from pandas.core.reshape.util import cartesian_product
12621272

1273+
if not is_list_like(iterables):
1274+
raise TypeError("Input must be a list / sequence of iterables.")
1275+
elif is_iterator(iterables):
1276+
iterables = list(iterables)
1277+
12631278
labels, levels = _factorize_from_iterables(iterables)
12641279
labels = cartesian_product(labels)
12651280
return MultiIndex(levels, labels, sortorder=sortorder, names=names)
@@ -1365,29 +1380,31 @@ def remove_unused_levels(self):
13651380
new_labels = []
13661381

13671382
changed = False
1368-
for lev, lab in zip(self.levels, self.labels):
1383+
for idx, (lev, lab) in enumerate(zip(self.levels, self.labels)):
1384+
na_idxs = np.where(lab == -1)[0]
1385+
1386+
if len(na_idxs):
1387+
lab = np.delete(lab, na_idxs)
13691388

13701389
uniques = algos.unique(lab)
1371-
na_idx = np.where(uniques == -1)[0]
13721390

13731391
# nothing unused
1374-
if len(uniques) != len(lev) + len(na_idx):
1392+
if len(uniques) != len(lev):
13751393
changed = True
13761394

1377-
if len(na_idx):
1378-
# Just ensure that -1 is in first position:
1379-
uniques[[0, na_idx[0]]] = uniques[[na_idx[0], 0]]
1380-
13811395
# labels get mapped from uniques to 0:len(uniques)
1382-
# -1 (if present) is mapped to last position
1383-
label_mapping = np.zeros(len(lev) + len(na_idx))
1384-
# ... and reassigned value -1:
1385-
label_mapping[uniques] = np.arange(len(uniques)) - len(na_idx)
1396+
label_mapping = np.zeros(len(lev))
1397+
label_mapping[uniques] = np.arange(len(uniques))
13861398

13871399
lab = label_mapping[lab]
13881400

13891401
# new levels are simple
1390-
lev = lev.take(uniques[len(na_idx):])
1402+
lev = lev.take(uniques)
1403+
1404+
if len(na_idxs):
1405+
lab = np.insert(lab, na_idxs - np.arange(len(na_idxs)), -1)
1406+
else:
1407+
lab = self.labels[idx]
13911408

13921409
new_levels.append(lev)
13931410
new_labels.append(lab)

pandas/tests/indexes/test_multi.py

+38-2
Original file line numberDiff line numberDiff line change
@@ -672,9 +672,19 @@ def test_from_arrays(self):
672672
for lev, lab in zip(self.index.levels, self.index.labels):
673673
arrays.append(np.asarray(lev).take(lab))
674674

675+
# list of arrays as input
675676
result = MultiIndex.from_arrays(arrays)
676677
assert list(result) == list(self.index)
677678

679+
# iterator as input
680+
result2 = MultiIndex.from_arrays(iter(arrays))
681+
assert list(result2) == list(self.index)
682+
683+
# invlide iterator input
684+
with tm.assert_raises_regex(
685+
TypeError, "Input must be a list / sequence of array-likes."):
686+
MultiIndex.from_arrays(0)
687+
678688
# infer correctly
679689
result = MultiIndex.from_arrays([[pd.NaT, Timestamp('20130101')],
680690
['a', 'b']])
@@ -827,6 +837,16 @@ def test_from_product(self):
827837
tm.assert_index_equal(result, expected)
828838
assert result.names == names
829839

840+
# iterator as input
841+
result2 = MultiIndex.from_product(iter([first, second]), names=names)
842+
assert result2.equals(expected)
843+
assert result2.names == names
844+
845+
# Invalid non-iterable input
846+
with tm.assert_raises_regex(
847+
TypeError, "Input must be a list / sequence of iterables."):
848+
MultiIndex.from_product(0)
849+
830850
def test_from_product_empty(self):
831851
# 0 levels
832852
with tm.assert_raises_regex(
@@ -1725,8 +1745,24 @@ def test_from_tuples(self):
17251745
'from empty list',
17261746
MultiIndex.from_tuples, [])
17271747

1728-
idx = MultiIndex.from_tuples(((1, 2), (3, 4)), names=['a', 'b'])
1729-
assert len(idx) == 2
1748+
expected = MultiIndex(levels=[[1, 3], [2, 4]],
1749+
labels=[[0, 1], [0, 1]],
1750+
names=['a', 'b'])
1751+
1752+
# input tuples
1753+
res1 = MultiIndex.from_tuples(((1, 2), (3, 4)), names=['a', 'b'])
1754+
assert expected.names == res1.names
1755+
assert res1.equals(expected)
1756+
1757+
# input iterator for tuples
1758+
res2 = MultiIndex.from_tuples(zip([1, 3], [2, 4]), names=['a', 'b'])
1759+
assert expected.names == res2.names
1760+
assert res2.equals(expected)
1761+
1762+
# input non-iterables
1763+
with tm.assert_raises_regex(
1764+
TypeError, 'Input must be a list /sequence of tuple-likes.'):
1765+
MultiIndex.from_tuples(0)
17301766

17311767
def test_from_tuples_empty(self):
17321768
# GH 16777

0 commit comments

Comments
 (0)