Skip to content

CLN/REF: stop allowing iNaT in DatetimeBlock #27428

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
Jul 23, 2019
35 changes: 12 additions & 23 deletions pandas/core/internals/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2184,7 +2184,7 @@ def _holder(self):

@property
def fill_value(self):
return tslibs.iNaT
return np.datetime64("NaT", "ns")

def get_values(self, dtype=None):
"""
Expand Down Expand Up @@ -2266,14 +2266,9 @@ def _can_hold_element(self, element):
if self.is_datetimetz:
return tz_compare(element.tzinfo, self.dtype.tz)
return element.tzinfo is None
elif is_integer(element):
return element == tslibs.iNaT

return is_valid_nat_for_dtype(element, self.dtype)

def _coerce_values(self, values):
return values.view("i8")

def _try_coerce_args(self, other):
"""
Coerce other to dtype 'i8'. NaN and NaT convert to
Expand All @@ -2290,16 +2285,15 @@ def _try_coerce_args(self, other):
base-type other
"""
if is_valid_nat_for_dtype(other, self.dtype):
other = tslibs.iNaT
elif is_integer(other) and other == tslibs.iNaT:
pass
other = np.datetime64("NaT", "ns")
elif isinstance(other, (datetime, np.datetime64, date)):
other = self._box_func(other)
if getattr(other, "tz") is not None:
raise TypeError("cannot coerce a Timestamp with a tz on a naive Block")
other = other.asm8.view("i8")
other = other.asm8
elif hasattr(other, "dtype") and is_datetime64_dtype(other):
other = other.astype("i8", copy=False).view("i8")
# TODO: can we get here with non-nano?
pass
else:
# coercion issues
# let higher levels handle
Expand Down Expand Up @@ -2458,8 +2452,7 @@ def _slice(self, slicer):
return self.values[slicer]

def _coerce_values(self, values):
# asi8 is a view, needs copy
return _block_shape(values.view("i8"), ndim=self.ndim)
return _block_shape(values, ndim=self.ndim)

def _try_coerce_args(self, other):
"""
Expand All @@ -2484,21 +2477,17 @@ def _try_coerce_args(self, other):
other = self._holder(other, dtype=self.dtype)

elif is_valid_nat_for_dtype(other, self.dtype):
other = tslibs.iNaT
elif is_integer(other) and other == tslibs.iNaT:
pass
other = np.datetime64("NaT", "ns")
elif isinstance(other, self._holder):
if other.tz != self.values.tz:
if not tz_compare(other.tz, self.values.tz):
raise ValueError("incompatible or non tz-aware value")
other = _block_shape(other.asi8, ndim=self.ndim)

elif isinstance(other, (np.datetime64, datetime, date)):
other = tslibs.Timestamp(other)
tz = getattr(other, "tz", None)

# test we can have an equal time zone
if tz is None or str(tz) != str(self.values.tz):
if not tz_compare(other.tz, self.values.tz):
raise ValueError("incompatible or non tz-aware value")
other = other.value
else:
raise TypeError(other)

Expand Down Expand Up @@ -2654,8 +2643,8 @@ def fillna(self, value, **kwargs):

def _try_coerce_args(self, other):
"""
Coerce values and other to int64, with null values converted to
iNaT. values is always ndarray-like, other may not be
Coerce values and other to datetime64[ns], with null values
converted to datetime64("NaT", "ns").

Parameters
----------
Expand Down
18 changes: 10 additions & 8 deletions pandas/core/nanops.py
Original file line number Diff line number Diff line change
Expand Up @@ -1360,14 +1360,6 @@ def _nanpercentile_1d(values, mask, q, na_value, interpolation):
quantiles : scalar or array
"""
# mask is Union[ExtensionArray, ndarray]
if values.dtype.kind == "m":
# need to cast to integer to avoid rounding errors in numpy
result = _nanpercentile_1d(values.view("i8"), mask, q, na_value, interpolation)

# Note: we have to do do `astype` and not view because in general we
# have float result at this point, not i8
return result.astype(values.dtype)

values = values[~mask]

if len(values) == 0:
Expand Down Expand Up @@ -1399,6 +1391,16 @@ def nanpercentile(values, q, axis, na_value, mask, ndim, interpolation):
-------
quantiles : scalar or array
"""
if values.dtype.kind in ["m", "M"]:
# need to cast to integer to avoid rounding errors in numpy
result = nanpercentile(
values.view("i8"), q, axis, na_value.view("i8"), mask, ndim, interpolation
)

# Note: we have to do do `astype` and not view because in general we
# have float result at this point, not i8
return result.astype(values.dtype)

if not lib.is_scalar(mask) and mask.any():
if ndim == 1:
return _nanpercentile_1d(
Expand Down
7 changes: 5 additions & 2 deletions pandas/tests/frame/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@ def test_fancy_index_int_labels_exceptions(self, float_frame):
with pytest.raises(KeyError, match=msg):
float_frame.ix[:, ["E"]] = 1

# FIXME: don't leave commented-out
# partial setting now allows this GH2578
# pytest.raises(KeyError, float_frame.ix.__setitem__,
# (slice(None, None), 'E'), 1)
Expand Down Expand Up @@ -1676,9 +1677,11 @@ def test_setitem_single_column_mixed_datetime(self):
)
assert_series_equal(result, expected)

# set an allowable datetime64 type
# GH#16674 iNaT is treated as an integer when given by the user
df.loc["b", "timestamp"] = iNaT
assert isna(df.loc["b", "timestamp"])
assert not isna(df.loc["b", "timestamp"])
assert df["timestamp"].dtype == np.object_
assert df.loc["b", "timestamp"] == iNaT

# allow this syntax
df.loc["c", "timestamp"] = np.nan
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/internals/test_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def test_try_coerce_arg(self):
vals = (np.datetime64("2010-10-10"), datetime(2010, 10, 10), date(2010, 10, 10))
for val in vals:
coerced = block._try_coerce_args(val)
assert np.int64 == type(coerced)
assert np.datetime64 == type(coerced)
assert pd.Timestamp("2010-10-10") == pd.Timestamp(coerced)


Expand Down