diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 8e6382c18343e..988da470eda35 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -147,6 +147,7 @@ Indexing - Bug in :func:`DataFrame.groupby` where tuples were interpreted as lists of keys rather than as keys (:issue:`17979`, :issue:`18249`) - Bug in :func:`MultiIndex.remove_unused_levels`` which would fill nan values (:issue:`18417`) - Bug in :func:`MultiIndex.from_tuples`` which would fail to take zipped tuples in python3 (:issue:`18434`) +- Bug in :class:`Index`` construction from list of mixed type tuples (:issue:`18505`) - Bug in :class:`IntervalIndex` where empty and purely NA data was constructed inconsistently depending on the construction method (:issue:`18421`) - Bug in ``IntervalIndex.symmetric_difference()`` where the symmetric difference with a non-``IntervalIndex`` did not raise (:issue:`18475`) diff --git a/pandas/core/base.py b/pandas/core/base.py index cce0f384cb983..ae92b62ce1d11 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -874,8 +874,9 @@ def _map_values(self, mapper, na_action=None): # convert to an Series for efficiency. # we specify the keys here to handle the # possibility that they are tuples - from pandas import Series - mapper = Series(mapper, index=mapper.keys()) + from pandas import Series, Index + index = Index(mapper, tupleize_cols=False) + mapper = Series(mapper, index=index) if isinstance(mapper, ABCSeries): # Since values were input this means we came from either diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index f4332ac244af4..10f9022e2666b 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -353,22 +353,15 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, elif data is None or is_scalar(data): cls._scalar_data_error(data) else: - if (tupleize_cols and isinstance(data, list) and data and - isinstance(data[0], tuple)): - + if tupleize_cols and is_list_like(data) and data: + if is_iterator(data): + data = list(data) # we must be all tuples, otherwise don't construct # 10697 if all(isinstance(e, tuple) for e in data): - try: - # must be orderable in py3 - if compat.PY3: - sorted(data) - from .multi import MultiIndex - return MultiIndex.from_tuples( - data, names=name or kwargs.get('names')) - except (TypeError, KeyError): - # python2 - MultiIndex fails on mixed types - pass + from .multi import MultiIndex + return MultiIndex.from_tuples( + data, names=name or kwargs.get('names')) # other iterable of some kind subarr = _asarray_tuplesafe(data, dtype=object) return Index(subarr, dtype=dtype, copy=copy, name=name, **kwargs) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 372c11b296d9e..0b71f6bb3fb01 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -106,6 +106,15 @@ def test_construction_list_mixed_tuples(self): assert isinstance(idx2, Index) assert not isinstance(idx2, MultiIndex) + @pytest.mark.parametrize('na_value', [None, np.nan]) + @pytest.mark.parametrize('vtype', [list, tuple, iter]) + def test_construction_list_tuples_nan(self, na_value, vtype): + # GH 18505 : valid tuples containing NaN + values = [(1, 'two'), (3., na_value)] + result = Index(vtype(values)) + expected = MultiIndex.from_tuples(values) + tm.assert_index_equal(result, expected) + def test_constructor_from_index_datetimetz(self): idx = pd.date_range('2015-01-01 10:00', freq='D', periods=3, tz='US/Eastern') diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 7ffda3a58ac1c..ccc04da3299fe 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -658,6 +658,16 @@ def test_constructor_tuple_of_tuples(self): s = Series(data) assert tuple(s) == data + @pytest.mark.xfail(reason='GH 18480 (Series initialization from dict with ' + 'NaN keys') + def test_constructor_dict_of_tuples(self): + data = {(1, 2): 3, + (None, 5): 6} + result = Series(data).sort_values() + expected = Series([3, 6], + index=MultiIndex.from_tuples([(1, 2), (None, 5)])) + tm.assert_series_equal(result, expected) + def test_constructor_set(self): values = set([1, 2, 3, 4, 5]) pytest.raises(TypeError, Series, values)