Skip to content

Commit 7463f86

Browse files
toobazjorisvandenbossche
authored andcommitted
BUG: Index constructor support tupleization for mixed levels (#18514)
1 parent 88ab693 commit 7463f86

File tree

5 files changed

+29
-15
lines changed

5 files changed

+29
-15
lines changed

doc/source/whatsnew/v0.22.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ Indexing
147147
- Bug in :func:`DataFrame.groupby` where tuples were interpreted as lists of keys rather than as keys (:issue:`17979`, :issue:`18249`)
148148
- Bug in :func:`MultiIndex.remove_unused_levels`` which would fill nan values (:issue:`18417`)
149149
- Bug in :func:`MultiIndex.from_tuples`` which would fail to take zipped tuples in python3 (:issue:`18434`)
150+
- Bug in :class:`Index`` construction from list of mixed type tuples (:issue:`18505`)
150151
- Bug in :class:`IntervalIndex` where empty and purely NA data was constructed inconsistently depending on the construction method (:issue:`18421`)
151152
- Bug in ``IntervalIndex.symmetric_difference()`` where the symmetric difference with a non-``IntervalIndex`` did not raise (:issue:`18475`)
152153

pandas/core/base.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -874,8 +874,9 @@ def _map_values(self, mapper, na_action=None):
874874
# convert to an Series for efficiency.
875875
# we specify the keys here to handle the
876876
# possibility that they are tuples
877-
from pandas import Series
878-
mapper = Series(mapper, index=mapper.keys())
877+
from pandas import Series, Index
878+
index = Index(mapper, tupleize_cols=False)
879+
mapper = Series(mapper, index=index)
879880

880881
if isinstance(mapper, ABCSeries):
881882
# Since values were input this means we came from either

pandas/core/indexes/base.py

+6-13
Original file line numberDiff line numberDiff line change
@@ -353,22 +353,15 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None,
353353
elif data is None or is_scalar(data):
354354
cls._scalar_data_error(data)
355355
else:
356-
if (tupleize_cols and isinstance(data, list) and data and
357-
isinstance(data[0], tuple)):
358-
356+
if tupleize_cols and is_list_like(data) and data:
357+
if is_iterator(data):
358+
data = list(data)
359359
# we must be all tuples, otherwise don't construct
360360
# 10697
361361
if all(isinstance(e, tuple) for e in data):
362-
try:
363-
# must be orderable in py3
364-
if compat.PY3:
365-
sorted(data)
366-
from .multi import MultiIndex
367-
return MultiIndex.from_tuples(
368-
data, names=name or kwargs.get('names'))
369-
except (TypeError, KeyError):
370-
# python2 - MultiIndex fails on mixed types
371-
pass
362+
from .multi import MultiIndex
363+
return MultiIndex.from_tuples(
364+
data, names=name or kwargs.get('names'))
372365
# other iterable of some kind
373366
subarr = _asarray_tuplesafe(data, dtype=object)
374367
return Index(subarr, dtype=dtype, copy=copy, name=name, **kwargs)

pandas/tests/indexes/test_base.py

+9
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ def test_construction_list_mixed_tuples(self):
106106
assert isinstance(idx2, Index)
107107
assert not isinstance(idx2, MultiIndex)
108108

109+
@pytest.mark.parametrize('na_value', [None, np.nan])
110+
@pytest.mark.parametrize('vtype', [list, tuple, iter])
111+
def test_construction_list_tuples_nan(self, na_value, vtype):
112+
# GH 18505 : valid tuples containing NaN
113+
values = [(1, 'two'), (3., na_value)]
114+
result = Index(vtype(values))
115+
expected = MultiIndex.from_tuples(values)
116+
tm.assert_index_equal(result, expected)
117+
109118
def test_constructor_from_index_datetimetz(self):
110119
idx = pd.date_range('2015-01-01 10:00', freq='D', periods=3,
111120
tz='US/Eastern')

pandas/tests/series/test_constructors.py

+10
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,16 @@ def test_constructor_tuple_of_tuples(self):
658658
s = Series(data)
659659
assert tuple(s) == data
660660

661+
@pytest.mark.xfail(reason='GH 18480 (Series initialization from dict with '
662+
'NaN keys')
663+
def test_constructor_dict_of_tuples(self):
664+
data = {(1, 2): 3,
665+
(None, 5): 6}
666+
result = Series(data).sort_values()
667+
expected = Series([3, 6],
668+
index=MultiIndex.from_tuples([(1, 2), (None, 5)]))
669+
tm.assert_series_equal(result, expected)
670+
661671
def test_constructor_set(self):
662672
values = set([1, 2, 3, 4, 5])
663673
pytest.raises(TypeError, Series, values)

0 commit comments

Comments
 (0)