diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index 0b4acdc3e89bb..4d020c4fc768f 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -1154,3 +1154,4 @@ Bug Fixes - Remove use of some deprecated numpy comparison operations, mainly in tests. (:issue:`10569`) - Bug in ``Index`` dtype may not applied properly (:issue:`11017`) - Bug in ``io.gbq`` when testing for minimum google api client version (:issue:`10652`) +- Bug in ``DataFrame`` construction from nested ``dict`` with ``timedelta`` keys (:issue:`11129`) diff --git a/pandas/core/common.py b/pandas/core/common.py index 8ffffae6bd160..0d9baad9f5d5e 100644 --- a/pandas/core/common.py +++ b/pandas/core/common.py @@ -1851,9 +1851,9 @@ def _maybe_box(indexer, values, obj, key): def _maybe_box_datetimelike(value): # turn a datetime like into a Timestamp/timedelta as needed - if isinstance(value, np.datetime64): + if isinstance(value, (np.datetime64, datetime)): value = tslib.Timestamp(value) - elif isinstance(value, np.timedelta64): + elif isinstance(value, (np.timedelta64, timedelta)): value = tslib.Timedelta(value) return value diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py index 24de36d95794c..f46918dd88f47 100644 --- a/pandas/tests/test_frame.py +++ b/pandas/tests/test_frame.py @@ -3037,6 +3037,29 @@ def create_data(constructor): assert_frame_equal(result_datetime, expected) assert_frame_equal(result_Timestamp, expected) + def test_constructor_dict_timedelta64_index(self): + # GH 10160 + td_as_int = [1, 2, 3, 4] + + def create_data(constructor): + return dict((i, {constructor(s): 2*i}) for i, s in enumerate(td_as_int)) + + data_timedelta64 = create_data(lambda x: np.timedelta64(x, 'D')) + data_timedelta = create_data(lambda x: timedelta(days=x)) + data_Timedelta = create_data(lambda x: Timedelta(x, 'D')) + + expected = DataFrame([{0: 0, 1: None, 2: None, 3: None}, + {0: None, 1: 2, 2: None, 3: None}, + {0: None, 1: None, 2: 4, 3: None}, + {0: None, 1: None, 2: None, 3: 6}], + index=[Timedelta(td, 'D') for td in td_as_int]) + + result_timedelta64 = DataFrame(data_timedelta64) + result_timedelta = DataFrame(data_timedelta) + result_Timedelta = DataFrame(data_Timedelta) + assert_frame_equal(result_timedelta64, expected) + assert_frame_equal(result_timedelta, expected) + assert_frame_equal(result_Timedelta, expected) def _check_basic_constructor(self, empty): "mat: 2d matrix with shpae (3, 2) to input. empty - makes sized objects" diff --git a/pandas/tseries/tests/test_timedeltas.py b/pandas/tseries/tests/test_timedeltas.py index 69dc70698ca28..45b98b0f85b1c 100644 --- a/pandas/tseries/tests/test_timedeltas.py +++ b/pandas/tseries/tests/test_timedeltas.py @@ -885,6 +885,24 @@ def test_pickle(self): v_p = self.round_trip_pickle(v) self.assertEqual(v,v_p) + def test_timedelta_hash_equality(self): + #GH 11129 + v = Timedelta(1, 'D') + td = timedelta(days=1) + self.assertEqual(hash(v), hash(td)) + + d = {td: 2} + self.assertEqual(d[v], 2) + + tds = timedelta_range('1 second', periods=20) + self.assertTrue( + all(hash(td) == hash(td.to_pytimedelta()) for td in tds)) + + # python timedeltas drop ns resolution + ns_td = Timedelta(1, 'ns') + self.assertNotEqual(hash(ns_td), hash(ns_td.to_pytimedelta())) + + class TestTimedeltaIndex(tm.TestCase): _multiprocess_can_split_ = True diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index def3764c1113c..7b3e404f7504c 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -2061,7 +2061,10 @@ cdef class _Timedelta(timedelta): int64_t _sign, _d, _h, _m, _s, _ms, _us, _ns def __hash__(_Timedelta self): - return hash(self.value) + if self._has_ns(): + return hash(self.value) + else: + return timedelta.__hash__(self) def __richcmp__(_Timedelta self, object other, int op): cdef: @@ -2110,63 +2113,63 @@ cdef class _Timedelta(timedelta): cdef float64_t frac if self.is_populated: - return + return # put frac in seconds - frac = float(ivalue)/1e9 + frac = float(ivalue)/1e9 if frac < 0: - self._sign = -1 - - # even fraction - if int(-frac/86400) != -frac/86400.0: - self._d = int(-frac/86400.0+1) - frac += 86400*self._d - else: - frac = -frac + self._sign = -1 + + # even fraction + if int(-frac/86400) != -frac/86400.0: + self._d = int(-frac/86400.0+1) + frac += 86400*self._d + else: + frac = -frac else: - self._sign = 1 - self._d = 0 + self._sign = 1 + self._d = 0 if frac >= 86400: - self._d += int(frac / 86400) - frac -= self._d * 86400 + self._d += int(frac / 86400) + frac -= self._d * 86400 if frac >= 3600: - self._h = int(frac / 3600) - frac -= self._h * 3600 + self._h = int(frac / 3600) + frac -= self._h * 3600 else: - self._h = 0 + self._h = 0 if frac >= 60: - self._m = int(frac / 60) - frac -= self._m * 60 + self._m = int(frac / 60) + frac -= self._m * 60 else: - self._m = 0 + self._m = 0 if frac >= 0: - self._s = int(frac) - frac -= self._s + self._s = int(frac) + frac -= self._s else: - self._s = 0 + self._s = 0 if frac != 0: - # reset so we don't lose precision - sfrac = int((self._h*3600 + self._m*60 + self._s)*1e9) - if self._sign < 0: - ifrac = ivalue + self._d*DAY_NS - sfrac - else: - ifrac = ivalue - (self._d*DAY_NS + sfrac) - - self._ms = int(ifrac/1e6) - ifrac -= self._ms*1000*1000 - self._us = int(ifrac/1e3) - ifrac -= self._us*1000 - self._ns = ifrac + # reset so we don't lose precision + sfrac = int((self._h*3600 + self._m*60 + self._s)*1e9) + if self._sign < 0: + ifrac = ivalue + self._d*DAY_NS - sfrac + else: + ifrac = ivalue - (self._d*DAY_NS + sfrac) + + self._ms = int(ifrac/1e6) + ifrac -= self._ms*1000*1000 + self._us = int(ifrac/1e3) + ifrac -= self._us*1000 + self._ns = ifrac else: - self._ms = 0 - self._us = 0 - self._ns = 0 + self._ms = 0 + self._us = 0 + self._ns = 0 self.is_populated = 1 @@ -2177,6 +2180,9 @@ cdef class _Timedelta(timedelta): """ return timedelta(microseconds=int(self.value)/1000) + cpdef bint _has_ns(self): + return self.value % 1000 != 0 + # components named tuple Components = collections.namedtuple('Components',['days','hours','minutes','seconds','milliseconds','microseconds','nanoseconds']) @@ -2433,7 +2439,7 @@ class Timedelta(_Timedelta): """ self._ensure_components() return self._ns - + def total_seconds(self): """ Total duration of timedelta in seconds (to ns precision)