Skip to content

Commit 3b7916f

Browse files
committed
Merge pull request #7609 from sinhrks/pminmax
BUG: PeriodIndex.min/max returns int
2 parents 0db1ef2 + 1f90a1d commit 3b7916f

File tree

5 files changed

+101
-40
lines changed

5 files changed

+101
-40
lines changed

doc/source/v0.14.1.txt

+1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ Bug Fixes
185185
- Bug in ``DatetimeIndex.asobject`` doesn't preserve ``name`` (:issue:`7299`)
186186
- Bug in multi-index slicing with datetimelike ranges (strings and Timestamps), (:issue:`7429`)
187187
- Bug in ``Index.min`` and ``max`` doesn't handle ``nan`` and ``NaT`` properly (:issue:`7261`)
188+
- Bug in ``PeriodIndex.min/max`` results in ``int`` (:issue:`7609`)
188189
- Bug in ``resample`` where ``fill_method`` was ignored if you passed ``how`` (:issue:`7261`)
189190
- Bug in ``TimeGrouper`` doesn't exclude column specified by ``key`` (:issue:`7227`)
190191
- Bug in ``DataFrame`` and ``Series`` bar and barh plot raises ``TypeError`` when ``bottom``

pandas/core/base.py

+31
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,34 @@ def tolist(self):
402402
"""
403403
return list(self.asobject)
404404

405+
def min(self, axis=None):
406+
"""
407+
Overridden ndarray.min to return an object
408+
"""
409+
import pandas.tslib as tslib
410+
mask = self.asi8 == tslib.iNaT
411+
masked = self[~mask]
412+
if len(masked) == 0:
413+
return self._na_value
414+
elif self.is_monotonic:
415+
return masked[0]
416+
else:
417+
min_stamp = masked.asi8.min()
418+
return self._box_func(min_stamp)
419+
420+
def max(self, axis=None):
421+
"""
422+
Overridden ndarray.max to return an object
423+
"""
424+
import pandas.tslib as tslib
425+
mask = self.asi8 == tslib.iNaT
426+
masked = self[~mask]
427+
if len(masked) == 0:
428+
return self._na_value
429+
elif self.is_monotonic:
430+
return masked[-1]
431+
else:
432+
max_stamp = masked.asi8.max()
433+
return self._box_func(max_stamp)
434+
435+

pandas/tests/test_base.py

+65-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pandas.util.testing import assertRaisesRegexp, assert_isinstance
99
from pandas import Series, Index, Int64Index, DatetimeIndex, PeriodIndex
1010
from pandas import _np_version_under1p7
11+
import pandas.tslib as tslib
1112
import nose
1213

1314
import pandas.util.testing as tm
@@ -202,7 +203,10 @@ def test_ops(self):
202203
for op in ['max','min']:
203204
for o in self.objs:
204205
result = getattr(o,op)()
205-
expected = getattr(o.values,op)()
206+
if not isinstance(o, PeriodIndex):
207+
expected = getattr(o.values, op)()
208+
else:
209+
expected = pd.Period(ordinal=getattr(o.values, op)(), freq=o.freq)
206210
try:
207211
self.assertEqual(result, expected)
208212
except ValueError:
@@ -232,17 +236,6 @@ def test_nanops(self):
232236
# check DatetimeIndex non-monotonic path
233237
self.assertEqual(getattr(obj, op)(), datetime(2011, 11, 1))
234238

235-
# explicitly create DatetimeIndex
236-
obj = DatetimeIndex([])
237-
self.assertTrue(pd.isnull(getattr(obj, op)()))
238-
239-
obj = DatetimeIndex([pd.NaT])
240-
self.assertTrue(pd.isnull(getattr(obj, op)()))
241-
242-
obj = DatetimeIndex([pd.NaT, pd.NaT, pd.NaT])
243-
self.assertTrue(pd.isnull(getattr(obj, op)()))
244-
245-
246239
def test_value_counts_unique_nunique(self):
247240
for o in self.objs:
248241
klass = type(o)
@@ -552,6 +545,33 @@ def test_asobject_tolist(self):
552545
self.assertEqual(result.name, expected.name)
553546
self.assertEqual(idx.tolist(), expected_list)
554547

548+
def test_minmax(self):
549+
for tz in [None, 'Asia/Tokyo', 'US/Eastern']:
550+
# monotonic
551+
idx1 = pd.DatetimeIndex([pd.NaT, '2011-01-01', '2011-01-02',
552+
'2011-01-03'], tz=tz)
553+
self.assertTrue(idx1.is_monotonic)
554+
555+
# non-monotonic
556+
idx2 = pd.DatetimeIndex(['2011-01-01', pd.NaT, '2011-01-03',
557+
'2011-01-02', pd.NaT], tz=tz)
558+
self.assertFalse(idx2.is_monotonic)
559+
560+
for idx in [idx1, idx2]:
561+
self.assertEqual(idx.min(), pd.Timestamp('2011-01-01', tz=tz))
562+
self.assertEqual(idx.max(), pd.Timestamp('2011-01-03', tz=tz))
563+
564+
for op in ['min', 'max']:
565+
# Return NaT
566+
obj = DatetimeIndex([])
567+
self.assertTrue(pd.isnull(getattr(obj, op)()))
568+
569+
obj = DatetimeIndex([pd.NaT])
570+
self.assertTrue(pd.isnull(getattr(obj, op)()))
571+
572+
obj = DatetimeIndex([pd.NaT, pd.NaT, pd.NaT])
573+
self.assertTrue(pd.isnull(getattr(obj, op)()))
574+
555575

556576
class TestPeriodIndexOps(Ops):
557577
_allowed = '_allow_period_index_ops'
@@ -597,6 +617,39 @@ def test_asobject_tolist(self):
597617
self.assertTrue(result_list[2].ordinal, pd.tslib.iNaT)
598618
self.assertTrue(result_list[2].freq, 'D')
599619

620+
def test_minmax(self):
621+
622+
# monotonic
623+
idx1 = pd.PeriodIndex([pd.NaT, '2011-01-01', '2011-01-02',
624+
'2011-01-03'], freq='D')
625+
self.assertTrue(idx1.is_monotonic)
626+
627+
# non-monotonic
628+
idx2 = pd.PeriodIndex(['2011-01-01', pd.NaT, '2011-01-03',
629+
'2011-01-02', pd.NaT], freq='D')
630+
self.assertFalse(idx2.is_monotonic)
631+
632+
for idx in [idx1, idx2]:
633+
self.assertEqual(idx.min(), pd.Period('2011-01-01', freq='D'))
634+
self.assertEqual(idx.max(), pd.Period('2011-01-03', freq='D'))
635+
636+
for op in ['min', 'max']:
637+
# Return NaT
638+
obj = PeriodIndex([], freq='M')
639+
result = getattr(obj, op)()
640+
self.assertEqual(result.ordinal, tslib.iNaT)
641+
self.assertEqual(result.freq, 'M')
642+
643+
obj = PeriodIndex([pd.NaT], freq='M')
644+
result = getattr(obj, op)()
645+
self.assertEqual(result.ordinal, tslib.iNaT)
646+
self.assertEqual(result.freq, 'M')
647+
648+
obj = PeriodIndex([pd.NaT, pd.NaT, pd.NaT], freq='M')
649+
result = getattr(obj, op)()
650+
self.assertEqual(result.ordinal, tslib.iNaT)
651+
self.assertEqual(result.freq, 'M')
652+
600653

601654
if __name__ == '__main__':
602655
import nose

pandas/tseries/index.py

-28
Original file line numberDiff line numberDiff line change
@@ -1783,34 +1783,6 @@ def indexer_between_time(self, start_time, end_time, include_start=True,
17831783

17841784
return mask.nonzero()[0]
17851785

1786-
def min(self, axis=None):
1787-
"""
1788-
Overridden ndarray.min to return a Timestamp
1789-
"""
1790-
mask = self.asi8 == tslib.iNaT
1791-
masked = self[~mask]
1792-
if len(masked) == 0:
1793-
return tslib.NaT
1794-
elif self.is_monotonic:
1795-
return masked[0]
1796-
else:
1797-
min_stamp = masked.asi8.min()
1798-
return Timestamp(min_stamp, tz=self.tz)
1799-
1800-
def max(self, axis=None):
1801-
"""
1802-
Overridden ndarray.max to return a Timestamp
1803-
"""
1804-
mask = self.asi8 == tslib.iNaT
1805-
masked = self[~mask]
1806-
if len(masked) == 0:
1807-
return tslib.NaT
1808-
elif self.is_monotonic:
1809-
return masked[-1]
1810-
else:
1811-
max_stamp = masked.asi8.max()
1812-
return Timestamp(max_stamp, tz=self.tz)
1813-
18141786
def to_julian_date(self):
18151787
"""
18161788
Convert DatetimeIndex to Float64Index of Julian Dates.

pandas/tseries/period.py

+4
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,10 @@ def _simple_new(cls, values, name, freq=None, **kwargs):
712712
result.freq = freq
713713
return result
714714

715+
@property
716+
def _na_value(self):
717+
return self._box_func(tslib.iNaT)
718+
715719
def __contains__(self, key):
716720
if not isinstance(key, Period) or key.freq != self.freq:
717721
if isinstance(key, compat.string_types):

0 commit comments

Comments
 (0)