Skip to content

Commit 3f509ac

Browse files
committed
BUG: allow Timestamp comparisons on the left
Now tested with Series and DataFrame
1 parent 21364c7 commit 3f509ac

File tree

4 files changed

+156
-20
lines changed

4 files changed

+156
-20
lines changed

doc/source/release.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ Bug Fixes
487487
- Fix repr for DateOffset. No longer show duplicate entries in kwds.
488488
Removed unused offset fields. (:issue:`4638`)
489489
- Fixed wrong index name during read_csv if using usecols. Applies to c parser only. (:issue:`4201`)
490+
- ``Timestamp`` objects can now appear in the left hand side of a comparison
491+
operation with a ``Series`` or ``DataFrame`` object (:issue:`4982`).
490492

491493
pandas 0.12.0
492494
-------------

pandas/tests/test_frame.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4335,6 +4335,31 @@ def check(df,df2):
43354335
df2 = DataFrame({'a': date_range('20010101', periods=len(df)), 'b': date_range('20100101', periods=len(df))})
43364336
check(df,df2)
43374337

4338+
def test_timestamp_compare(self):
4339+
# make sure we can compare Timestamps on the right AND left hand side
4340+
# GH4982
4341+
df = DataFrame({'dates1': date_range('20010101', periods=10),
4342+
'dates2': date_range('20010102', periods=10),
4343+
'intcol': np.random.randint(1000000000, size=10),
4344+
'floatcol': np.random.randn(10),
4345+
'stringcol': list(tm.rands(10))})
4346+
df.loc[np.random.rand(len(df)) > 0.5, 'dates2'] = pd.NaT
4347+
ops = {'gt': 'lt', 'lt': 'gt', 'ge': 'le', 'le': 'ge', 'eq': 'eq',
4348+
'ne': 'ne'}
4349+
for left, right in ops.items():
4350+
left_f = getattr(operator, left)
4351+
right_f = getattr(operator, right)
4352+
4353+
# no nats
4354+
expected = left_f(df, Timestamp('20010109'))
4355+
result = right_f(Timestamp('20010109'), df)
4356+
tm.assert_frame_equal(result, expected)
4357+
4358+
# nats
4359+
expected = left_f(df, Timestamp('nat'))
4360+
result = right_f(Timestamp('nat'), df)
4361+
tm.assert_frame_equal(result, expected)
4362+
43384363
def test_modulo(self):
43394364

43404365
# GH3590, modulo as ints

pandas/tests/test_series.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,6 +2059,42 @@ def check_comparators(series, other):
20592059
check_comparators(self.ts, 5)
20602060
check_comparators(self.ts, self.ts + 1)
20612061

2062+
def test_timestamp_compare(self):
2063+
# make sure we can compare Timestamps on the right AND left hand side
2064+
# GH4982
2065+
df = DataFrame({'dates': date_range('20010101', periods=10)})
2066+
s = df.dates.copy()
2067+
2068+
s[0] = pd.Timestamp('nat')
2069+
s[3] = pd.Timestamp('nat')
2070+
2071+
ops = {'lt': 'gt', 'le': 'ge', 'eq': 'eq', 'ne': 'ne'}
2072+
2073+
for left, right in ops.items():
2074+
left_f = getattr(operator, left)
2075+
right_f = getattr(operator, right)
2076+
2077+
# no nats
2078+
expected = left_f(df.dates, Timestamp('20010109'))
2079+
result = right_f(Timestamp('20010109'), df.dates)
2080+
tm.assert_series_equal(result, expected)
2081+
2082+
# nats
2083+
expected = left_f(df.dates, Timestamp('nat'))
2084+
result = right_f(Timestamp('nat'), df.dates)
2085+
tm.assert_series_equal(result, expected)
2086+
2087+
# compare to timestamp with series containing nats
2088+
expected = left_f(s, Timestamp('20010109'))
2089+
result = right_f(Timestamp('20010109'), s)
2090+
tm.assert_series_equal(result, expected)
2091+
2092+
# compare to nat with series containing nats
2093+
expected = left_f(s, Timestamp('nat'))
2094+
result = right_f(Timestamp('nat'), s)
2095+
tm.assert_series_equal(result, expected)
2096+
2097+
20622098
def test_operators_empty_int_corner(self):
20632099
s1 = Series([], [], dtype=np.int32)
20642100
s2 = Series({'x': 0.})
@@ -4990,6 +5026,39 @@ def test_numpy_unique(self):
49905026
result = np.unique(self.ts)
49915027

49925028

5029+
def test_timestamp_compare_scalars():
5030+
# case where ndim == 0
5031+
lhs = np.datetime64(datetime(2013, 12, 6))
5032+
rhs = Timestamp('now')
5033+
nat = Timestamp('nat')
5034+
5035+
ops = {'gt': 'lt', 'lt': 'gt', 'ge': 'le', 'le': 'ge', 'eq': 'eq',
5036+
'ne': 'ne'}
5037+
5038+
for left, right in ops.items():
5039+
left_f = getattr(operator, left)
5040+
right_f = getattr(operator, right)
5041+
5042+
if pd._np_version_under1p7:
5043+
# you have to convert to timestamp for this to work with numpy
5044+
# scalars
5045+
expected = left_f(Timestamp(lhs), rhs)
5046+
5047+
# otherwise a TypeError is thrown
5048+
if left not in ('eq', 'ne'):
5049+
with tm.assertRaises(TypeError):
5050+
left_f(lhs, rhs)
5051+
else:
5052+
expected = left_f(lhs, rhs)
5053+
5054+
result = right_f(rhs, lhs)
5055+
tm.assert_equal(result, expected)
5056+
5057+
expected = left_f(rhs, nat)
5058+
result = right_f(nat, rhs)
5059+
tm.assert_equal(result, expected)
5060+
5061+
49935062
class TestSeriesNonUnique(unittest.TestCase):
49945063

49955064
_multiprocess_can_split_ = True

pandas/tslib.pyx

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ cdef class _Timestamp(datetime):
457457

458458
def __richcmp__(_Timestamp self, object other, int op):
459459
cdef _Timestamp ots
460+
cdef int ndim = getattr(other, 'ndim', -1)
460461

461462
if isinstance(other, _Timestamp):
462463
ots = other
@@ -470,13 +471,33 @@ cdef class _Timestamp(datetime):
470471
except ValueError:
471472
return self._compare_outside_nanorange(other, op)
472473
else:
473-
if op == 2:
474-
return False
475-
elif op == 3:
476-
return True
474+
if ndim != -1:
475+
if ndim == 0:
476+
if isinstance(other, np.datetime64):
477+
other = Timestamp(other)
478+
else:
479+
raise TypeError("Cannot compare Timestamp with type"
480+
" %r" % type(other).__name__)
481+
if op == 2: # ==
482+
return other == self
483+
elif op == 3: # !=
484+
return other != self
485+
elif op == 0: # <
486+
return other > self
487+
elif op == 1: # <=
488+
return other >= self
489+
elif op == 4: # >
490+
return other < self
491+
elif op == 5: # >=
492+
return other <= self
477493
else:
478-
raise TypeError('Cannot compare Timestamp with '
479-
'{0!r}'.format(other.__class__.__name__))
494+
if op == 2:
495+
return False
496+
elif op == 3:
497+
return True
498+
else:
499+
raise TypeError('Cannot compare Timestamp with '
500+
'{0!r}'.format(other.__class__.__name__))
480501

481502
self._assert_tzawareness_compat(other)
482503

@@ -589,21 +610,40 @@ cdef class _NaT(_Timestamp):
589610
def __richcmp__(_NaT self, object other, int op):
590611
# if not isinstance(other, (_NaT, _Timestamp)):
591612
# raise TypeError('Cannot compare %s with NaT' % type(other))
613+
cdef int ndim = getattr(other, 'ndim', -1)
592614

593-
if op == 2: # ==
594-
return False
595-
elif op == 3: # !=
596-
return True
597-
elif op == 0: # <
598-
return False
599-
elif op == 1: # <=
600-
return False
601-
elif op == 4: # >
602-
return False
603-
elif op == 5: # >=
604-
return False
605-
606-
615+
if ndim != -1:
616+
if ndim == 0:
617+
if isinstance(other, np.datetime64):
618+
other = Timestamp(other)
619+
else:
620+
raise TypeError("Cannot compare NaT with type "
621+
"%r" % type(other).__name__)
622+
if op == 2: # ==
623+
return other == self
624+
elif op == 3: # !=
625+
return other != self
626+
elif op == 0: # <
627+
return other > self
628+
elif op == 1: # <=
629+
return other >= self
630+
elif op == 4: # >
631+
return other < self
632+
elif op == 5: # >=
633+
return other <= self
634+
else:
635+
if op == 2: # ==
636+
return False
637+
elif op == 3: # !=
638+
return True
639+
elif op == 0: # <
640+
return False
641+
elif op == 1: # <=
642+
return False
643+
elif op == 4: # >
644+
return False
645+
elif op == 5: # >=
646+
return False
607647

608648

609649
def _delta_to_nanoseconds(delta):

0 commit comments

Comments
 (0)