diff --git a/pandas/indexes/category.py b/pandas/indexes/category.py index 4ead02e5bd022..5f681ba618cd4 100644 --- a/pandas/indexes/category.py +++ b/pandas/indexes/category.py @@ -45,6 +45,9 @@ def __new__(cls, data=None, categories=None, ordered=None, dtype=None, if fastpath: return cls._simple_new(data, name=name) + if name is None and hasattr(data, 'name'): + name = data.name + if isinstance(data, com.ABCCategorical): data = cls._create_categorical(cls, data, categories, ordered) elif isinstance(data, CategoricalIndex): diff --git a/pandas/indexes/multi.py b/pandas/indexes/multi.py index 8138f2deb534f..d29d089871bf8 100644 --- a/pandas/indexes/multi.py +++ b/pandas/indexes/multi.py @@ -66,8 +66,10 @@ def __new__(cls, levels=None, labels=None, sortorder=None, names=None, name=None, **kwargs): # compat with Index - if name is not None: - names = name + if name is None and hasattr(levels, 'name'): + name = levels.name + if isinstance(levels, MultiIndex): + return levels.copy(name=name, names=names, deep=copy) if levels is None or labels is None: raise TypeError("Must pass both levels and labels") if len(levels) != len(labels): @@ -77,8 +79,6 @@ def __new__(cls, levels=None, labels=None, sortorder=None, names=None, if len(levels) == 1: if names: name = names[0] - else: - name = None return Index(levels[0], name=name, copy=True).take(labels[0]) result = object.__new__(MultiIndex) @@ -333,7 +333,7 @@ def set_labels(self, labels, level=None, inplace=False, labels = property(fget=_get_labels, fset=__set_labels) def copy(self, names=None, dtype=None, levels=None, labels=None, - deep=False, _set_identity=False): + deep=False, name=None, _set_identity=False): """ Make a copy of this object. Names, dtype, levels and labels can be passed and will be set on new copy. @@ -344,6 +344,7 @@ def copy(self, names=None, dtype=None, levels=None, labels=None, dtype : numpy dtype or pandas type, optional levels : sequence, optional labels : sequence, optional + name : object, optional Returns ------- @@ -366,7 +367,7 @@ def copy(self, names=None, dtype=None, levels=None, labels=None, names = self.names return MultiIndex(levels=levels, labels=labels, names=names, sortorder=self.sortorder, verify_integrity=False, - _set_identity=_set_identity) + name=name, _set_identity=_set_identity) def __array__(self, dtype=None): """ the array interface, return my values """ diff --git a/pandas/indexes/numeric.py b/pandas/indexes/numeric.py index c1817129116e2..6af6c2aaaa356 100644 --- a/pandas/indexes/numeric.py +++ b/pandas/indexes/numeric.py @@ -19,6 +19,32 @@ class NumericIndex(Index): """ _is_numeric_dtype = True + def __new__(cls, data=None, dtype=None, copy=False, name=None, + fastpath=False, **kwargs): + + if fastpath: + return cls._simple_new(data, name=name) + + # isscalar, generators handled in coerce_to_ndarray + data = cls._coerce_to_ndarray(data) + + if issubclass(data.dtype.type, compat.string_types): + cls._string_data_error(data) + + if copy or data.dtype != cls._default_dtype: + try: + subarr = np.array(data, dtype=cls._default_dtype, copy=copy) + assert((subarr == data) | np.isnan(subarr)).all() + except: + raise TypeError('Unsafe NumPy casting, you must ' + 'explicitly cast') + else: + subarr = data + + if name is None and hasattr(data, 'name'): + name = data.name + return cls._simple_new(subarr, name=name) + def _maybe_cast_slice_bound(self, label, side, kind): """ This function should be overloaded in subclasses that allow non-trivial @@ -94,35 +120,7 @@ class Int64Index(NumericIndex): _can_hold_na = False _engine_type = _index.Int64Engine - - def __new__(cls, data=None, dtype=None, copy=False, name=None, - fastpath=False, **kwargs): - - if fastpath: - return cls._simple_new(data, name=name) - - # isscalar, generators handled in coerce_to_ndarray - data = cls._coerce_to_ndarray(data) - - if issubclass(data.dtype.type, compat.string_types): - cls._string_data_error(data) - - elif issubclass(data.dtype.type, np.integer): - # don't force the upcast as we may be dealing - # with a platform int - if (dtype is None or - not issubclass(np.dtype(dtype).type, np.integer)): - dtype = np.int64 - - subarr = np.array(data, dtype=dtype, copy=copy) - else: - subarr = np.array(data, dtype=np.int64, copy=copy) - if len(data) > 0: - if (subarr != data).any(): - raise TypeError('Unsafe NumPy casting to integer, you must' - ' explicitly cast') - - return cls._simple_new(subarr, name=name) + _default_dtype = np.int64 @property def inferred_type(self): @@ -192,42 +190,7 @@ class Float64Index(NumericIndex): _inner_indexer = _algos.inner_join_indexer_float64 _outer_indexer = _algos.outer_join_indexer_float64 - def __new__(cls, data=None, dtype=None, copy=False, name=None, - fastpath=False, **kwargs): - - if fastpath: - return cls._simple_new(data, name) - - data = cls._coerce_to_ndarray(data) - - if issubclass(data.dtype.type, compat.string_types): - cls._string_data_error(data) - - if dtype is None: - dtype = np.float64 - dtype = np.dtype(dtype) - - # allow integer / object dtypes to be passed, but coerce to float64 - if dtype.kind in ['i', 'O']: - dtype = np.float64 - - elif dtype.kind in ['f']: - pass - - else: - raise TypeError("cannot support {0} dtype in " - "Float64Index".format(dtype)) - - try: - subarr = np.array(data, dtype=dtype, copy=copy) - except: - raise TypeError('Unsafe NumPy casting, you must explicitly cast') - - # coerce to float64 for storage - if subarr.dtype != np.float64: - subarr = subarr.astype(np.float64) - - return cls._simple_new(subarr, name) + _default_dtype = np.float64 @property def inferred_type(self): diff --git a/pandas/tests/frame/test_block_internals.py b/pandas/tests/frame/test_block_internals.py index f337bf48c05ee..94d5307d797e1 100644 --- a/pandas/tests/frame/test_block_internals.py +++ b/pandas/tests/frame/test_block_internals.py @@ -372,11 +372,13 @@ def test_consolidate_datetime64(self): ser_starting.index = ser_starting.values ser_starting = ser_starting.tz_localize('US/Eastern') ser_starting = ser_starting.tz_convert('UTC') + ser_starting.index.name = 'starting' ser_ending = df.ending ser_ending.index = ser_ending.values ser_ending = ser_ending.tz_localize('US/Eastern') ser_ending = ser_ending.tz_convert('UTC') + ser_ending.index.name = 'ending' df.starting = ser_starting.index df.ending = ser_ending.index diff --git a/pandas/tests/indexes/common.py b/pandas/tests/indexes/common.py index f1824267d63d8..b5dd16bdca8a0 100644 --- a/pandas/tests/indexes/common.py +++ b/pandas/tests/indexes/common.py @@ -654,3 +654,19 @@ def test_fillna(self): expected[1] = True self.assert_numpy_array_equal(idx._isnan, expected) self.assertTrue(idx.hasnans) + + def test_copy(self): + # GH12309 + for name, index in compat.iteritems(self.indices): + first = index.__class__(index, copy=True, name='mario') + second = first.__class__(first, copy=False) + self.assertTrue(index.equals(first)) + # Even though "copy=False", we want a new object: + self.assertTrue(id(first) != id(second)) + + if isinstance(index, MultiIndex) and len(index.levels) > 1: + # No unique "name" attribute (each level has its own) + continue + + self.assertEqual(first.name, 'mario') + self.assertEqual(second.name, 'mario') diff --git a/pandas/tests/indexes/test_numeric.py b/pandas/tests/indexes/test_numeric.py index 325e0df14a07e..70f75adf04ea8 100644 --- a/pandas/tests/indexes/test_numeric.py +++ b/pandas/tests/indexes/test_numeric.py @@ -831,3 +831,11 @@ def test_ufunc_coercions(self): tm.assertIsInstance(result, Float64Index) exp = Float64Index([0.5, 1., 1.5, 2., 2.5], name='x') tm.assert_index_equal(result, exp) + + def test_ensure_copied_data(self): + data = np.array([1, 2, 3], dtype='int64') + idx = Int64Index(data, copy=True) + self.assert_numpy_array_equal(data, idx._data) + tm.assertIsNot(data, idx._data) + idx2 = Int64Index(data, copy=False) + tm.assertIs(data, idx2._data) diff --git a/pandas/tests/test_common.py b/pandas/tests/test_common.py index d24e1eab1cea8..6fbf2943e3c3f 100644 --- a/pandas/tests/test_common.py +++ b/pandas/tests/test_common.py @@ -725,9 +725,9 @@ def test_ensure_platform_int(): pi = com._ensure_platform_int(x) assert (pi.dtype == np.int_) - # int32 + # int32 - "dtype" argument is irrelevant x = Int64Index([1, 2, 3], dtype='int32') - assert (x.dtype == np.int32) + assert (x.dtype == np.int64) pi = com._ensure_platform_int(x) assert (pi.dtype == np.int_) diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index 9faf6f174115c..98a19a43f30fc 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -218,6 +218,9 @@ def __new__(cls, data=None, verify_integrity=True, normalize=False, closed=None, ambiguous='raise', dtype=None, **kwargs): + if name is None and hasattr(data, 'name'): + name = data.name + dayfirst = kwargs.pop('dayfirst', None) yearfirst = kwargs.pop('yearfirst', None) diff --git a/pandas/tseries/period.py b/pandas/tseries/period.py index a25bb525c9970..33fc6d6d80a18 100644 --- a/pandas/tseries/period.py +++ b/pandas/tseries/period.py @@ -179,6 +179,9 @@ def __new__(cls, data=None, ordinal=None, freq=None, start=None, end=None, raise ValueError('Periods must be a number, got %s' % str(periods)) + if name is None and hasattr(data, 'name'): + name = data.name + if data is None: if ordinal is not None: data = np.asarray(ordinal, dtype=np.int64) diff --git a/pandas/tseries/tdi.py b/pandas/tseries/tdi.py index e74879602fa64..0e4ceec12f24b 100644 --- a/pandas/tseries/tdi.py +++ b/pandas/tseries/tdi.py @@ -133,8 +133,9 @@ def __new__(cls, data=None, unit=None, if isinstance(data, TimedeltaIndex) and freq is None and name is None: if copy: - data = data.copy() - return data + return data.copy() + else: + return data._shallow_copy() freq_infer = False if not isinstance(freq, DateOffset): diff --git a/pandas/tseries/tests/test_timedeltas.py b/pandas/tseries/tests/test_timedeltas.py index 4bdd0ed462852..d433c5a87f378 100644 --- a/pandas/tseries/tests/test_timedeltas.py +++ b/pandas/tseries/tests/test_timedeltas.py @@ -1649,7 +1649,7 @@ def test_join_self(self): kinds = 'outer', 'inner', 'left', 'right' for kind in kinds: joined = index.join(index, how=kind) - self.assertIs(index, joined) + tm.assert_index_equal(index, joined) def test_factorize(self): idx1 = TimedeltaIndex(['1 day', '1 day', '2 day', '2 day', '3 day',