From a2523be85ccdbec70e5e81e779b2700567a54643 Mon Sep 17 00:00:00 2001 From: sinhrks Date: Sat, 18 Apr 2015 09:58:29 +0900 Subject: [PATCH] BUG: Index.name is lost during timedelta ops --- doc/source/whatsnew/v0.17.0.txt | 4 ++++ pandas/core/common.py | 15 +++++++++++---- pandas/tests/test_common.py | 21 +++++++++++++++++++++ pandas/tseries/index.py | 6 +++++- pandas/tseries/tdi.py | 5 ++++- pandas/tseries/tests/test_base.py | 30 +++++++++++++++--------------- 6 files changed, 60 insertions(+), 21 deletions(-) diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index 2a4a408643451..8f72a8f1240d6 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -67,3 +67,7 @@ Bug Fixes - Bug in ``NaT`` raises ``AttributeError`` when accessing to ``daysinmonth``, ``dayofweek`` properties. (:issue:`10096`) - Bug in getting timezone data with ``dateutil`` on various platforms ( :issue:`9059`, :issue:`8639`, :issue:`9663`, :issue:`10121`) + + + +- Bug in ``DatetimeIndex`` and ``TimedeltaIndex`` names are lost after timedelta arithmetics ( :issue:`9926`) diff --git a/pandas/core/common.py b/pandas/core/common.py index 3c92300d1f9a5..1c9326c047a79 100644 --- a/pandas/core/common.py +++ b/pandas/core/common.py @@ -3322,10 +3322,17 @@ def save(obj, path): # TODO remove in 0.13 def _maybe_match_name(a, b): - a_name = getattr(a, 'name', None) - b_name = getattr(b, 'name', None) - if a_name == b_name: - return a_name + a_has = hasattr(a, 'name') + b_has = hasattr(b, 'name') + if a_has and b_has: + if a.name == b.name: + return a.name + else: + return None + elif a_has: + return a.name + elif b_has: + return b.name return None def _random_state(state=None): diff --git a/pandas/tests/test_common.py b/pandas/tests/test_common.py index 3282a36bda7b8..c3d39fcdf906f 100644 --- a/pandas/tests/test_common.py +++ b/pandas/tests/test_common.py @@ -545,6 +545,27 @@ def test_random_state(): com._random_state(5.5) +def test_maybe_match_name(): + + matched = com._maybe_match_name(Series([1], name='x'), Series([2], name='x')) + assert(matched == 'x') + + matched = com._maybe_match_name(Series([1], name='x'), Series([2], name='y')) + assert(matched is None) + + matched = com._maybe_match_name(Series([1]), Series([2], name='x')) + assert(matched is None) + + matched = com._maybe_match_name(Series([1], name='x'), Series([2])) + assert(matched is None) + + matched = com._maybe_match_name(Series([1], name='x'), [2]) + assert(matched == 'x') + + matched = com._maybe_match_name([1], Series([2], name='y')) + assert(matched == 'y') + + class TestTake(tm.TestCase): # standard incompatible fill error fill_error = re.compile("Incompatible type for fill_value") diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index f3803a04baf01..bd0869b9525b7 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -653,14 +653,18 @@ def _sub_datelike(self, other): def _add_delta(self, delta): from pandas import TimedeltaIndex + name = self.name + if isinstance(delta, (Tick, timedelta, np.timedelta64)): new_values = self._add_delta_td(delta) elif isinstance(delta, TimedeltaIndex): new_values = self._add_delta_tdi(delta) + # update name when delta is Index + name = com._maybe_match_name(self, delta) else: new_values = self.astype('O') + delta tz = 'UTC' if self.tz is not None else None - result = DatetimeIndex(new_values, tz=tz, freq='infer') + result = DatetimeIndex(new_values, tz=tz, name=name, freq='infer') utc = _utc() if self.tz is not None and self.tz is not utc: result = result.tz_convert(self.tz) diff --git a/pandas/tseries/tdi.py b/pandas/tseries/tdi.py index fd97ca4c45fc0..1443c22909689 100644 --- a/pandas/tseries/tdi.py +++ b/pandas/tseries/tdi.py @@ -281,12 +281,15 @@ def __setstate__(self, state): def _add_delta(self, delta): if isinstance(delta, (Tick, timedelta, np.timedelta64)): new_values = self._add_delta_td(delta) + name = self.name elif isinstance(delta, TimedeltaIndex): new_values = self._add_delta_tdi(delta) + # update name when delta is index + name = com._maybe_match_name(self, delta) else: raise ValueError("cannot add the type {0} to a TimedeltaIndex".format(type(delta))) - result = TimedeltaIndex(new_values, freq='infer') + result = TimedeltaIndex(new_values, freq='infer', name=name) return result def _evaluate_with_timedelta_like(self, other, op, opstr): diff --git a/pandas/tseries/tests/test_base.py b/pandas/tseries/tests/test_base.py index d1b986e7a7a1c..55482401a20f4 100644 --- a/pandas/tseries/tests/test_base.py +++ b/pandas/tseries/tests/test_base.py @@ -634,27 +634,27 @@ def test_dti_dti_deprecated_ops(self): def test_dti_tdi_numeric_ops(self): # These are normally union/diff set-like ops - tdi = TimedeltaIndex(['1 days',pd.NaT,'2 days'], name='foo') - dti = date_range('20130101',periods=3, name='bar') + tdi = TimedeltaIndex(['1 days', pd.NaT, '2 days'], name='foo') + dti = date_range('20130101', periods=3, name='bar') td = Timedelta('1 days') dt = Timestamp('20130101') result = tdi - tdi expected = TimedeltaIndex(['0 days', pd.NaT, '0 days'], name='foo') - tm.assert_index_equal(result, expected, check_names=False) # must be foo + tm.assert_index_equal(result, expected) result = tdi + tdi expected = TimedeltaIndex(['2 days', pd.NaT, '4 days'], name='foo') - tm.assert_index_equal(result, expected, check_names=False) # must be foo + tm.assert_index_equal(result, expected) - result = dti - tdi + result = dti - tdi # name will be reset expected = DatetimeIndex(['20121231', pd.NaT, '20130101']) tm.assert_index_equal(result, expected) def test_addition_ops(self): # with datetimes/timedelta and tdi/dti - tdi = TimedeltaIndex(['1 days',pd.NaT,'2 days'], name='foo') + tdi = TimedeltaIndex(['1 days', pd.NaT, '2 days'], name='foo') dti = date_range('20130101', periods=3, name='bar') td = Timedelta('1 days') dt = Timestamp('20130101') @@ -669,11 +669,11 @@ def test_addition_ops(self): result = td + tdi expected = TimedeltaIndex(['2 days', pd.NaT, '3 days'], name='foo') - tm.assert_index_equal(result, expected, check_names=False) # must be foo + tm.assert_index_equal(result, expected) result = tdi + td expected = TimedeltaIndex(['2 days', pd.NaT, '3 days'], name='foo') - tm.assert_index_equal(result,expected, check_names=False) # must be foo + tm.assert_index_equal(result, expected) # unequal length self.assertRaises(ValueError, lambda : tdi + dti[0:1]) @@ -685,21 +685,21 @@ def test_addition_ops(self): # this is a union! #self.assertRaises(TypeError, lambda : Int64Index([1,2,3]) + tdi) - result = tdi + dti + result = tdi + dti # name will be reset expected = DatetimeIndex(['20130102', pd.NaT, '20130105']) - tm.assert_index_equal(result,expected) + tm.assert_index_equal(result, expected) - result = dti + tdi - expected = DatetimeIndex(['20130102',pd.NaT,'20130105']) - tm.assert_index_equal(result,expected) + result = dti + tdi # name will be reset + expected = DatetimeIndex(['20130102', pd.NaT, '20130105']) + tm.assert_index_equal(result, expected) result = dt + td expected = Timestamp('20130102') - self.assertEqual(result,expected) + self.assertEqual(result, expected) result = td + dt expected = Timestamp('20130102') - self.assertEqual(result,expected) + self.assertEqual(result, expected) def test_value_counts_unique(self): # GH 7735