Skip to content

BUG: listlike comparisons for DTA and TDA #30705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 5, 2020
20 changes: 6 additions & 14 deletions pandas/core/arrays/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,9 @@ def wrapper(self, other):
raise ValueError("Lengths must match")
else:
if isinstance(other, list):
try:
other = type(self)._from_sequence(other)
except ValueError:
other = np.array(other, dtype=np.object_)
elif not isinstance(other, (np.ndarray, DatetimeArray)):
other = np.array(other)

if not isinstance(other, (np.ndarray, cls)):
# Following Timestamp convention, __eq__ is all-False
# and __ne__ is all True, others raise TypeError.
return invalid_comparison(self, other, op)
Expand All @@ -179,20 +177,14 @@ def wrapper(self, other):
op, self.astype(object), other
)
o_mask = isna(other)

elif not (is_datetime64_dtype(other) or is_datetime64tz_dtype(other)):
# e.g. is_timedelta64_dtype(other)
return invalid_comparison(self, other, op)

else:
self._assert_tzawareness_compat(other)

if (
is_datetime64_dtype(other)
and not is_datetime64_ns_dtype(other)
or not hasattr(other, "asi8")
):
# e.g. other.dtype == 'datetime64[s]'
# or an object-dtype ndarray
other = type(self)._from_sequence(other)
other = type(self)._from_sequence(other)

result = op(self.view("i8"), other.view("i8"))
o_mask = other._isnan
Expand Down
28 changes: 21 additions & 7 deletions pandas/core/arrays/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
)
from pandas.core.dtypes.missing import isna

from pandas.core import nanops
from pandas.core import nanops, ops
from pandas.core.algorithms import checked_add_with_arr
import pandas.core.common as com
from pandas.core.ops.common import unpack_zerodim_and_defer
Expand Down Expand Up @@ -103,15 +103,29 @@ def wrapper(self, other):
raise ValueError("Lengths must match")

else:
try:
other = type(self)._from_sequence(other)._data
except (ValueError, TypeError):
if isinstance(other, list):
other = np.array(other)

if not isinstance(other, (np.ndarray, cls)):
return invalid_comparison(self, other, op)

if is_object_dtype(other):
with np.errstate(all="ignore"):
result = ops.comp_method_OBJECT_ARRAY(
op, self.astype(object), other
)
o_mask = isna(other)

elif not is_timedelta64_dtype(other):
# e.g. other is datetimearray
return invalid_comparison(self, other, op)

result = op(self.view("i8"), other.view("i8"))
result = com.values_from_object(result)
else:
other = type(self)._from_sequence(other)

result = op(self.view("i8"), other.view("i8"))
o_mask = other._isnan

o_mask = np.array(isna(other))
if o_mask.any():
result[o_mask] = nat_result

Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/arithmetic/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def assert_invalid_comparison(left, right, box):
result = right != left
tm.assert_equal(result, ~expected)

msg = "Invalid comparison between"
msg = "Invalid comparison between|Cannot compare type|not supported between"
with pytest.raises(TypeError, match=msg):
left < right
with pytest.raises(TypeError, match=msg):
Expand Down
46 changes: 46 additions & 0 deletions pandas/tests/arithmetic/test_datetime64.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,52 @@ def test_dt64arr_cmp_scalar_invalid(self, other, tz_naive_fixture, box_with_arra
dtarr = tm.box_expected(rng, box_with_array)
assert_invalid_comparison(dtarr, other, box_with_array)

@pytest.mark.parametrize(
"other",
[
list(range(10)),
np.arange(10),
np.arange(10).astype(np.float32),
np.arange(10).astype(object),
pd.timedelta_range("1ns", periods=10).array,
np.array(pd.timedelta_range("1ns", periods=10)),
list(pd.timedelta_range("1ns", periods=10)),
pd.timedelta_range("1 Day", periods=10).astype(object),
pd.period_range("1971-01-01", freq="D", periods=10).array,
pd.period_range("1971-01-01", freq="D", periods=10).astype(object),
],
)
def test_dt64arr_cmp_arraylike_invalid(self, other, tz_naive_fixture):
# We don't parametrize this over box_with_array because listlike
# other plays poorly with assert_invalid_comparison reversed checks
tz = tz_naive_fixture

dta = date_range("1970-01-01", freq="ns", periods=10, tz=tz)._data
assert_invalid_comparison(dta, other, tm.to_array)

def test_dt64arr_cmp_mixed_invalid(self, tz_naive_fixture):
tz = tz_naive_fixture

dta = date_range("1970-01-01", freq="h", periods=5, tz=tz)._data

other = np.array([0, 1, 2, dta[3], pd.Timedelta(days=1)])
result = dta == other
expected = np.array([False, False, False, True, False])
tm.assert_numpy_array_equal(result, expected)

result = dta != other
tm.assert_numpy_array_equal(result, ~expected)

msg = "Invalid comparison between|Cannot compare type|not supported between"
with pytest.raises(TypeError, match=msg):
dta < other
with pytest.raises(TypeError, match=msg):
dta > other
with pytest.raises(TypeError, match=msg):
dta <= other
with pytest.raises(TypeError, match=msg):
dta >= other

def test_dt64arr_nat_comparison(self, tz_naive_fixture, box_with_array):
# GH#22242, GH#22163 DataFrame considered NaT == ts incorrectly
tz = tz_naive_fixture
Expand Down
43 changes: 43 additions & 0 deletions pandas/tests/arithmetic/test_timedelta64.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,49 @@ def test_td64_comparisons_invalid(self, box_with_array, invalid):

assert_invalid_comparison(obj, invalid, box)

@pytest.mark.parametrize(
"other",
[
list(range(10)),
np.arange(10),
np.arange(10).astype(np.float32),
np.arange(10).astype(object),
pd.date_range("1970-01-01", periods=10, tz="UTC").array,
np.array(pd.date_range("1970-01-01", periods=10)),
list(pd.date_range("1970-01-01", periods=10)),
pd.date_range("1970-01-01", periods=10).astype(object),
pd.period_range("1971-01-01", freq="D", periods=10).array,
pd.period_range("1971-01-01", freq="D", periods=10).astype(object),
],
)
def test_td64arr_cmp_arraylike_invalid(self, other):
# We don't parametrize this over box_with_array because listlike
# other plays poorly with assert_invalid_comparison reversed checks

rng = timedelta_range("1 days", periods=10)._data
assert_invalid_comparison(rng, other, tm.to_array)

def test_td64arr_cmp_mixed_invalid(self):
rng = timedelta_range("1 days", periods=5)._data

other = np.array([0, 1, 2, rng[3], pd.Timestamp.now()])
result = rng == other
expected = np.array([False, False, False, True, False])
tm.assert_numpy_array_equal(result, expected)

result = rng != other
tm.assert_numpy_array_equal(result, ~expected)

msg = "Invalid comparison between|Cannot compare type|not supported between"
with pytest.raises(TypeError, match=msg):
rng < other
with pytest.raises(TypeError, match=msg):
rng > other
with pytest.raises(TypeError, match=msg):
rng <= other
with pytest.raises(TypeError, match=msg):
rng >= other


class TestTimedelta64ArrayComparisons:
# TODO: All of these need to be parametrized over box
Expand Down