Skip to content

Commit 6785475

Browse files
committed
Merge pull request #11134 from chris-b1/timedelta-hash
BUG: nested construction with timedelta #11129
2 parents 1b2583b + 961dd47 commit 6785475

File tree

5 files changed

+90
-42
lines changed

5 files changed

+90
-42
lines changed

doc/source/whatsnew/v0.17.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1155,3 +1155,4 @@ Bug Fixes
11551155
- Remove use of some deprecated numpy comparison operations, mainly in tests. (:issue:`10569`)
11561156
- Bug in ``Index`` dtype may not applied properly (:issue:`11017`)
11571157
- Bug in ``io.gbq`` when testing for minimum google api client version (:issue:`10652`)
1158+
- Bug in ``DataFrame`` construction from nested ``dict`` with ``timedelta`` keys (:issue:`11129`)

pandas/core/common.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1848,9 +1848,9 @@ def _maybe_box(indexer, values, obj, key):
18481848
def _maybe_box_datetimelike(value):
18491849
# turn a datetime like into a Timestamp/timedelta as needed
18501850

1851-
if isinstance(value, np.datetime64):
1851+
if isinstance(value, (np.datetime64, datetime)):
18521852
value = tslib.Timestamp(value)
1853-
elif isinstance(value, np.timedelta64):
1853+
elif isinstance(value, (np.timedelta64, timedelta)):
18541854
value = tslib.Timedelta(value)
18551855

18561856
return value

pandas/tests/test_frame.py

+23
Original file line numberDiff line numberDiff line change
@@ -3037,6 +3037,29 @@ def create_data(constructor):
30373037
assert_frame_equal(result_datetime, expected)
30383038
assert_frame_equal(result_Timestamp, expected)
30393039

3040+
def test_constructor_dict_timedelta64_index(self):
3041+
# GH 10160
3042+
td_as_int = [1, 2, 3, 4]
3043+
3044+
def create_data(constructor):
3045+
return dict((i, {constructor(s): 2*i}) for i, s in enumerate(td_as_int))
3046+
3047+
data_timedelta64 = create_data(lambda x: np.timedelta64(x, 'D'))
3048+
data_timedelta = create_data(lambda x: timedelta(days=x))
3049+
data_Timedelta = create_data(lambda x: Timedelta(x, 'D'))
3050+
3051+
expected = DataFrame([{0: 0, 1: None, 2: None, 3: None},
3052+
{0: None, 1: 2, 2: None, 3: None},
3053+
{0: None, 1: None, 2: 4, 3: None},
3054+
{0: None, 1: None, 2: None, 3: 6}],
3055+
index=[Timedelta(td, 'D') for td in td_as_int])
3056+
3057+
result_timedelta64 = DataFrame(data_timedelta64)
3058+
result_timedelta = DataFrame(data_timedelta)
3059+
result_Timedelta = DataFrame(data_Timedelta)
3060+
assert_frame_equal(result_timedelta64, expected)
3061+
assert_frame_equal(result_timedelta, expected)
3062+
assert_frame_equal(result_Timedelta, expected)
30403063

30413064
def _check_basic_constructor(self, empty):
30423065
"mat: 2d matrix with shpae (3, 2) to input. empty - makes sized objects"

pandas/tseries/tests/test_timedeltas.py

+18
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,24 @@ def test_pickle(self):
885885
v_p = self.round_trip_pickle(v)
886886
self.assertEqual(v,v_p)
887887

888+
def test_timedelta_hash_equality(self):
889+
#GH 11129
890+
v = Timedelta(1, 'D')
891+
td = timedelta(days=1)
892+
self.assertEqual(hash(v), hash(td))
893+
894+
d = {td: 2}
895+
self.assertEqual(d[v], 2)
896+
897+
tds = timedelta_range('1 second', periods=20)
898+
self.assertTrue(
899+
all(hash(td) == hash(td.to_pytimedelta()) for td in tds))
900+
901+
# python timedeltas drop ns resolution
902+
ns_td = Timedelta(1, 'ns')
903+
self.assertNotEqual(hash(ns_td), hash(ns_td.to_pytimedelta()))
904+
905+
888906
class TestTimedeltaIndex(tm.TestCase):
889907
_multiprocess_can_split_ = True
890908

pandas/tslib.pyx

+46-40
Original file line numberDiff line numberDiff line change
@@ -2061,7 +2061,10 @@ cdef class _Timedelta(timedelta):
20612061
int64_t _sign, _d, _h, _m, _s, _ms, _us, _ns
20622062

20632063
def __hash__(_Timedelta self):
2064-
return hash(self.value)
2064+
if self._has_ns():
2065+
return hash(self.value)
2066+
else:
2067+
return timedelta.__hash__(self)
20652068

20662069
def __richcmp__(_Timedelta self, object other, int op):
20672070
cdef:
@@ -2110,63 +2113,63 @@ cdef class _Timedelta(timedelta):
21102113
cdef float64_t frac
21112114

21122115
if self.is_populated:
2113-
return
2116+
return
21142117

21152118
# put frac in seconds
2116-
frac = float(ivalue)/1e9
2119+
frac = float(ivalue)/1e9
21172120
if frac < 0:
2118-
self._sign = -1
2119-
2120-
# even fraction
2121-
if int(-frac/86400) != -frac/86400.0:
2122-
self._d = int(-frac/86400.0+1)
2123-
frac += 86400*self._d
2124-
else:
2125-
frac = -frac
2121+
self._sign = -1
2122+
2123+
# even fraction
2124+
if int(-frac/86400) != -frac/86400.0:
2125+
self._d = int(-frac/86400.0+1)
2126+
frac += 86400*self._d
2127+
else:
2128+
frac = -frac
21262129
else:
2127-
self._sign = 1
2128-
self._d = 0
2130+
self._sign = 1
2131+
self._d = 0
21292132

21302133
if frac >= 86400:
2131-
self._d += int(frac / 86400)
2132-
frac -= self._d * 86400
2134+
self._d += int(frac / 86400)
2135+
frac -= self._d * 86400
21332136

21342137
if frac >= 3600:
2135-
self._h = int(frac / 3600)
2136-
frac -= self._h * 3600
2138+
self._h = int(frac / 3600)
2139+
frac -= self._h * 3600
21372140
else:
2138-
self._h = 0
2141+
self._h = 0
21392142

21402143
if frac >= 60:
2141-
self._m = int(frac / 60)
2142-
frac -= self._m * 60
2144+
self._m = int(frac / 60)
2145+
frac -= self._m * 60
21432146
else:
2144-
self._m = 0
2147+
self._m = 0
21452148

21462149
if frac >= 0:
2147-
self._s = int(frac)
2148-
frac -= self._s
2150+
self._s = int(frac)
2151+
frac -= self._s
21492152
else:
2150-
self._s = 0
2153+
self._s = 0
21512154

21522155
if frac != 0:
21532156

2154-
# reset so we don't lose precision
2155-
sfrac = int((self._h*3600 + self._m*60 + self._s)*1e9)
2156-
if self._sign < 0:
2157-
ifrac = ivalue + self._d*DAY_NS - sfrac
2158-
else:
2159-
ifrac = ivalue - (self._d*DAY_NS + sfrac)
2160-
2161-
self._ms = int(ifrac/1e6)
2162-
ifrac -= self._ms*1000*1000
2163-
self._us = int(ifrac/1e3)
2164-
ifrac -= self._us*1000
2165-
self._ns = ifrac
2157+
# reset so we don't lose precision
2158+
sfrac = int((self._h*3600 + self._m*60 + self._s)*1e9)
2159+
if self._sign < 0:
2160+
ifrac = ivalue + self._d*DAY_NS - sfrac
2161+
else:
2162+
ifrac = ivalue - (self._d*DAY_NS + sfrac)
2163+
2164+
self._ms = int(ifrac/1e6)
2165+
ifrac -= self._ms*1000*1000
2166+
self._us = int(ifrac/1e3)
2167+
ifrac -= self._us*1000
2168+
self._ns = ifrac
21662169
else:
2167-
self._ms = 0
2168-
self._us = 0
2169-
self._ns = 0
2170+
self._ms = 0
2171+
self._us = 0
2172+
self._ns = 0
21702173

21712174
self.is_populated = 1
21722175

@@ -2177,6 +2180,9 @@ cdef class _Timedelta(timedelta):
21772180
"""
21782181
return timedelta(microseconds=int(self.value)/1000)
21792182

2183+
cpdef bint _has_ns(self):
2184+
return self.value % 1000 != 0
2185+
21802186
# components named tuple
21812187
Components = collections.namedtuple('Components',['days','hours','minutes','seconds','milliseconds','microseconds','nanoseconds'])
21822188

@@ -2433,7 +2439,7 @@ class Timedelta(_Timedelta):
24332439
"""
24342440
self._ensure_components()
24352441
return self._ns
2436-
2442+
24372443
def total_seconds(self):
24382444
"""
24392445
Total duration of timedelta in seconds (to ns precision)

0 commit comments

Comments
 (0)