Skip to content

Commit a4843cb

Browse files
committed
Merge pull request #11245 from llllllllll/dt64-reduce-to-nat
BUG: datetime64 series reduces to nan when empty instead of nat
2 parents 99bf170 + 40c8fcf commit a4843cb

File tree

7 files changed

+42
-53
lines changed

7 files changed

+42
-53
lines changed

doc/source/whatsnew/v0.17.1.txt

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Other Enhancements
2828
API changes
2929
~~~~~~~~~~~
3030

31+
- min and max reductions on ``datetime64`` and ``timedelta64`` dtyped series now
32+
result in ``NaT`` and not ``nan`` (:issue:`11245`).
33+
3134
.. _whatsnew_0171.deprecations:
3235

3336
Deprecations
@@ -74,3 +77,5 @@ Bug Fixes
7477

7578

7679
- Bugs in ``to_excel`` with duplicate columns (:issue:`11007`, :issue:`10982`, :issue:`10970`)
80+
- Fixed a bug that prevented the construction of an empty series of dtype
81+
``datetime64[ns, tz]`` (:issue:`11245`).

pandas/core/common.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1927,12 +1927,11 @@ def _possibly_cast_to_datetime(value, dtype, errors='raise'):
19271927
value = tslib.iNaT
19281928

19291929
# we have an array of datetime or timedeltas & nulls
1930-
elif np.prod(value.shape) and not is_dtype_equal(value.dtype, dtype):
1930+
elif np.prod(value.shape) or not is_dtype_equal(value.dtype, dtype):
19311931
try:
19321932
if is_datetime64:
19331933
value = to_datetime(value, errors=errors)._values
19341934
elif is_datetime64tz:
1935-
19361935
# input has to be UTC at this point, so just localize
19371936
value = to_datetime(value, errors=errors).tz_localize(dtype.tz)
19381937
elif is_timedelta64:

pandas/core/dtypes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class DatetimeTZDtype(ExtensionDtype):
138138
num = 101
139139
base = np.dtype('M8[ns]')
140140
_metadata = ['unit','tz']
141-
_match = re.compile("datetime64\[(?P<unit>.+), (?P<tz>.+)\]")
141+
_match = re.compile("(datetime64|M8)\[(?P<unit>.+), (?P<tz>.+)\]")
142142

143143
def __init__(self, unit, tz=None):
144144
"""

pandas/core/frame.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4624,7 +4624,7 @@ def _reduce(self, op, name, axis=0, skipna=True, numeric_only=None,
46244624
values = self.values
46254625
result = f(values)
46264626

4627-
if is_object_dtype(result.dtype):
4627+
if hasattr(result, 'dtype') and is_object_dtype(result.dtype):
46284628
try:
46294629
if filter_type is None or filter_type == 'numeric':
46304630
result = result.astype(np.float64)

pandas/core/nanops.py

+18-49
Original file line numberDiff line numberDiff line change
@@ -425,65 +425,34 @@ def nansem(values, axis=None, skipna=True, ddof=1):
425425
return np.sqrt(var) / np.sqrt(count)
426426

427427

428-
@bottleneck_switch()
429-
def nanmin(values, axis=None, skipna=True):
430-
values, mask, dtype, dtype_max = _get_values(values, skipna,
431-
fill_value_typ='+inf')
432-
433-
# numpy 1.6.1 workaround in Python 3.x
434-
if is_object_dtype(values) and compat.PY3:
435-
if values.ndim > 1:
436-
apply_ax = axis if axis is not None else 0
437-
result = np.apply_along_axis(builtins.min, apply_ax, values)
438-
else:
439-
try:
440-
result = builtins.min(values)
441-
except:
442-
result = np.nan
443-
else:
428+
def _nanminmax(meth, fill_value_typ):
429+
@bottleneck_switch()
430+
def reduction(values, axis=None, skipna=True):
431+
values, mask, dtype, dtype_max = _get_values(
432+
values,
433+
skipna,
434+
fill_value_typ=fill_value_typ,
435+
)
436+
444437
if ((axis is not None and values.shape[axis] == 0)
445438
or values.size == 0):
446439
try:
447-
result = ensure_float(values.sum(axis, dtype=dtype_max))
440+
result = getattr(values, meth)(axis, dtype=dtype_max)
448441
result.fill(np.nan)
449442
except:
450443
result = np.nan
451444
else:
452-
result = values.min(axis)
445+
result = getattr(values, meth)(axis)
453446

454-
result = _wrap_results(result, dtype)
455-
return _maybe_null_out(result, axis, mask)
447+
result = _wrap_results(result, dtype)
448+
return _maybe_null_out(result, axis, mask)
456449

450+
reduction.__name__ = 'nan' + meth
451+
return reduction
457452

458-
@bottleneck_switch()
459-
def nanmax(values, axis=None, skipna=True):
460-
values, mask, dtype, dtype_max = _get_values(values, skipna,
461-
fill_value_typ='-inf')
462453

463-
# numpy 1.6.1 workaround in Python 3.x
464-
if is_object_dtype(values) and compat.PY3:
465-
466-
if values.ndim > 1:
467-
apply_ax = axis if axis is not None else 0
468-
result = np.apply_along_axis(builtins.max, apply_ax, values)
469-
else:
470-
try:
471-
result = builtins.max(values)
472-
except:
473-
result = np.nan
474-
else:
475-
if ((axis is not None and values.shape[axis] == 0)
476-
or values.size == 0):
477-
try:
478-
result = ensure_float(values.sum(axis, dtype=dtype_max))
479-
result.fill(np.nan)
480-
except:
481-
result = np.nan
482-
else:
483-
result = values.max(axis)
484-
485-
result = _wrap_results(result, dtype)
486-
return _maybe_null_out(result, axis, mask)
454+
nanmin = _nanminmax('min', fill_value_typ='+inf')
455+
nanmax = _nanminmax('max', fill_value_typ='-inf')
487456

488457

489458
def nanargmax(values, axis=None, skipna=True):
@@ -637,7 +606,7 @@ def _maybe_null_out(result, axis, mask):
637606
else:
638607
result = result.astype('f8')
639608
result[null_mask] = np.nan
640-
else:
609+
elif result is not tslib.NaT:
641610
null_mask = mask.size - mask.sum()
642611
if null_mask == 0:
643612
result = np.nan

pandas/tests/test_dtypes.py

+10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: utf-8 -*-
2+
from itertools import product
23

34
import nose
45
import numpy as np
@@ -148,6 +149,15 @@ def test_dst(self):
148149
self.assertTrue(is_datetimetz(s2))
149150
self.assertEqual(s1.dtype, s2.dtype)
150151

152+
def test_parser(self):
153+
# pr #11245
154+
for tz, constructor in product(('UTC', 'US/Eastern'),
155+
('M8', 'datetime64')):
156+
self.assertEqual(
157+
DatetimeTZDtype('%s[ns, %s]' % (constructor, tz)),
158+
DatetimeTZDtype('ns', tz),
159+
)
160+
151161

152162

153163

pandas/tests/test_series.py

+6
Original file line numberDiff line numberDiff line change
@@ -7960,6 +7960,12 @@ def test_datetime_timedelta_quantiles(self):
79607960
self.assertTrue(pd.isnull(Series([],dtype='M8[ns]').quantile(.5)))
79617961
self.assertTrue(pd.isnull(Series([],dtype='m8[ns]').quantile(.5)))
79627962

7963+
def test_empty_timeseries_redections_return_nat(self):
7964+
# covers #11245
7965+
for dtype in ('m8[ns]', 'm8[ns]', 'M8[ns]', 'M8[ns, UTC]'):
7966+
self.assertIs(Series([], dtype=dtype).min(), pd.NaT)
7967+
self.assertIs(Series([], dtype=dtype).max(), pd.NaT)
7968+
79637969
if __name__ == '__main__':
79647970
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
79657971
exit=False)

0 commit comments

Comments
 (0)