Repository: pandas-dev/pandas

Issue:
Creating series with multiindex from dict with tuple keys with None
#### Code Sample

```python
import pandas as pd

data1={(1, 2): 3,
       (4, None): 6}

s1 = pd.Series(data=data1)

data2={(1, 2): 3,
       (None, 5): 6}

s2 = pd.Series(data=data2)
```

#### Problem description

`s1` has MultiIndex while `s2` has Index of tuple just because the second element of the `data2` has `None` as the first element of the key tuple.

```python
>>> s1
1  2      3.0
4  NaN    NaN
dtype: float64
>>> s2
(1, 2)       3
(None, 5)    6
dtype: int64
```
Is this a bug? or is this how it's supposed to be?

FYI, both of `mi1` and `mi2` below yield MultiIndex as I expect.

```python
mi1 = pd.MultiIndex.from_tuples([(1, 2), (4, None)])
mi2 = pd.MultiIndex.from_tuples([(1, 2), (None, 5)])
```



Discussion:
This comes from
```
In [3]: pd.Index(list(data2))
Out[3]: Index([(1, 2), (None, 5)], dtype='object')
```

(so won't be fixed by #18496 ).
Related to #18484 

Resolving patch:
diff --git a/pandas/core/base.py b/pandas/core/base.py
--- 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
--- 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)


Test patch:
diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py
--- 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
--- 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)