diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index a5949c62ad913..907c91a742573 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -113,14 +113,18 @@ class Index(IndexOpsMixin, PandasObject): dtype : NumPy dtype (default: object) copy : bool Make a copy of input ndarray - name : object + name : object, optional Name to be stored in the index + names : sequence of objects, optional + Names for the index levels tupleize_cols : bool (default: True) When True, attempt to create a MultiIndex if possible Notes ----- - An Index instance can **only** contain hashable objects + An Index instance can **only** contain hashable objects. + + Only one of `name` and `names` can be specified at the same time. Examples -------- @@ -176,10 +180,25 @@ class Index(IndexOpsMixin, PandasObject): str = CachedAccessor("str", StringMethods) def __new__(cls, data=None, dtype=None, copy=False, name=None, - fastpath=False, tupleize_cols=True, **kwargs): + fastpath=False, tupleize_cols=True, names=None, + **kwargs): + + # The main purpose of `names` is to use it with a `MultiIndex`. + # Although for consistency it's also used to retrieve `name` for a + # one-level indices if `name` is not provided (see GH 19082). + + if names is not None and name is not None: + raise TypeError("Can provide only one of names and name arguments") + + if names is not None and not is_list_like(names): + raise TypeError("names must be list-like") - if name is None and hasattr(data, 'name'): - name = data.name + if name is None: + if hasattr(data, 'name'): + name = data.name + # extract `name` from `names` in case MultiIndex cannot be created + elif names: + name = names[0] if fastpath: return cls._simple_new(data, name) @@ -358,8 +377,7 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, # 10697 if all(isinstance(e, tuple) for e in data): from .multi import MultiIndex - return MultiIndex.from_tuples( - data, names=name or kwargs.get('names')) + return MultiIndex.from_tuples(data, names=names or name) # 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 c4e8682934369..d7247669ea43b 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -305,6 +305,30 @@ def test_constructor_simple_new(self): result = idx._simple_new(idx, 'obj') tm.assert_index_equal(result, idx) + def test_constructor_names(self): + # test both `name` and `names` provided + with pytest.raises(TypeError): + idx = Index([1, 2, 3], name='a', names=('a',)) + + # test non-list-like `names` + with pytest.raises(TypeError): + idx = Index([1, 2, 3], names='a') + + # test using `name` for a flat `Index` + idx = Index([1, 2, 3], name='a') + assert idx.name == 'a' + assert idx.names == ('a',) + + # test using `names` for a flat `Index` + idx = Index([1, 2, 3], names=('a',)) + assert idx.name == 'a' + assert idx.names == ('a',) + + # test using `names` for `MultiIndex` creation + idx = Index([('A', 1), ('A', 2)], names=('a', 'b')) + midx = MultiIndex.from_tuples([('A', 1), ('A', 2)], names=('a', 'b')) + tm.assert_index_equal(idx, midx, check_names=True) + def test_constructor_dtypes(self): for idx in [Index(np.array([1, 2, 3], dtype=int)),