Skip to content

Commit 4bf1862

Browse files
committed
Fixups
* min / max * astype * clean
1 parent b901c3d commit 4bf1862

File tree

8 files changed

+199
-26
lines changed

8 files changed

+199
-26
lines changed

pandas/core/arrays/datetimelike.py

+18-26
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
DIFFERENT_FREQ_INDEX, IncompatibleFrequency, Period)
1111
from pandas._libs.tslibs.timedeltas import Timedelta, delta_to_nanoseconds
1212
from pandas._libs.tslibs.timestamps import (
13-
RoundTo, Timestamp, maybe_integer_op_deprecated, round_nsint64)
13+
RoundTo, maybe_integer_op_deprecated, round_nsint64)
1414
import pandas.compat as compat
1515
from pandas.compat.numpy import function as nv
1616
from pandas.errors import (
@@ -29,7 +29,7 @@
2929
from pandas.core.dtypes.inference import is_array_like
3030
from pandas.core.dtypes.missing import isna
3131

32-
from pandas.core import missing
32+
from pandas.core import missing, nanops
3333
from pandas.core.algorithms import (
3434
checked_add_with_arr, take, unique1d, value_counts)
3535
import pandas.core.common as com
@@ -72,8 +72,6 @@ def cmp_method(self, other):
7272

7373
class AttributesMixin(object):
7474

75-
_scalar_types = (Period, Timestamp, Timedelta)
76-
7775
@property
7876
def _attributes(self):
7977
# Inheriting subclass should implement _attributes as a list of strings
@@ -1098,7 +1096,7 @@ def _time_shift(self, periods, freq=None):
10981096
freq = frequencies.to_offset(freq)
10991097
offset = periods * freq
11001098
result = self + offset
1101-
if getattr(self, 'tz', None):
1099+
if hasattr(self, 'tz'):
11021100
result._dtype = DatetimeTZDtype(tz=self.tz)
11031101
return result
11041102

@@ -1310,38 +1308,32 @@ def _evaluate_compare(self, other, op):
13101308
result[mask] = filler
13111309
return result
13121310

1311+
# --------------------------------------------------------------
1312+
# Reductions
1313+
13131314
def _reduce(self, name, skipna=True, **kwargs):
13141315
op = getattr(self, name, None)
13151316
if op:
13161317
return op(skipna=skipna)
13171318
else:
1318-
return super()._reduce(name, skipna, **kwargs)
1319-
1320-
# --------------------------------------------------------------
1321-
# Reductions
1322-
1323-
def _values_for_reduction(self, skipna=True):
1324-
if skipna:
1325-
values = self[~self._isnan]
1326-
else:
1327-
values = self
1328-
return values.asi8
1319+
return super(DatetimeLikeArrayMixin, self)._reduce(
1320+
name, skipna, **kwargs
1321+
)
13291322

13301323
def min(self, skipna=True):
1331-
# TODO: Deduplicate with Datetimelike.
1332-
# they get to take some shortcuts based on monotonicity.
1333-
i8 = self._values_for_reduction(skipna=skipna)
1334-
if len(i8):
1335-
return self._box_func(i8.min())
1336-
else:
1324+
result = nanops.nanmin(self.asi8, skipna=skipna, mask=self.isna())
1325+
if isna(result):
1326+
# Period._from_ordinal does not handle np.nan gracefully
13371327
return NaT
1328+
return self._box_func(result)
13381329

13391330
def max(self, skipna=True):
1340-
i8 = self._values_for_reduction(skipna=skipna)
1341-
if len(i8):
1342-
return self._box_func(i8.max())
1343-
else:
1331+
# TODO: skipna is broken with max.
1332+
result = nanops.nanmax(self.asi8, skipna=skipna, mask=self.isna())
1333+
if isna(result):
1334+
# Period._from_ordinal does not handle np.nan gracefully
13441335
return NaT
1336+
return self._box_func(result)
13451337

13461338

13471339
DatetimeLikeArrayMixin._add_comparison_ops()

pandas/tests/arrays/test_datetimelike.py

+7
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ def test_scalar_from_string(self):
130130
result = arr._scalar_from_string(str(arr[0]))
131131
assert result == arr[0]
132132

133+
def test_reduce_invalid(self):
134+
data = np.arange(10, dtype='i8')
135+
arr = self.array_cls(data, freq='D')
136+
137+
with pytest.raises(TypeError, match='cannot perform'):
138+
arr._reduce("not a method")
139+
133140

134141
class TestDatetimeArray(SharedTests):
135142
index_cls = pd.DatetimeIndex

pandas/tests/arrays/test_datetimes.py

+38
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,41 @@ def test_tz_dtype_matches(self):
120120
result, _, _ = sequence_to_dt64ns(
121121
arr, dtype=DatetimeTZDtype(tz="US/Central"))
122122
tm.assert_extension_array_equal(arr, result)
123+
124+
125+
class TestReductions(object):
126+
127+
@pytest.mark.parametrize("tz", [None, "US/Central"])
128+
def test_min_max(self, tz):
129+
arr = DatetimeArray._from_sequence([
130+
'2000-01-03',
131+
'2000-01-03',
132+
'NaT',
133+
'2000-01-02',
134+
'2000-01-05',
135+
'2000-01-04',
136+
], tz=tz)
137+
138+
result = arr.min()
139+
expected = pd.Timestamp('2000-01-02', tz=tz)
140+
assert result == expected
141+
142+
result = arr.max()
143+
expected = pd.Timestamp('2000-01-05', tz=tz)
144+
assert result == expected
145+
146+
result = arr.min(skipna=False)
147+
assert result is pd.NaT
148+
149+
result = arr.max(skipna=False)
150+
assert result is pd.NaT
151+
152+
@pytest.mark.parametrize("tz", [None, "US/Central"])
153+
@pytest.mark.parametrize('skipna', [True, False])
154+
def test_min_max_empty(self, skipna, tz):
155+
arr = DatetimeArray._from_sequence([], tz=tz)
156+
result = arr.min(skipna=skipna)
157+
assert result is pd.NaT
158+
159+
result = arr.max(skipna=skipna)
160+
assert result is pd.NaT

pandas/tests/arrays/test_period.py

+39
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,42 @@ def test_repr_large():
246246
"Length: 1000, dtype: period[D]"
247247
)
248248
assert result == expected
249+
250+
251+
# ----------------------------------------------------------------------------
252+
# Reductions
253+
254+
class TestReductions(object):
255+
256+
def test_min_max(self):
257+
arr = period_array([
258+
'2000-01-03',
259+
'2000-01-03',
260+
'NaT',
261+
'2000-01-02',
262+
'2000-01-05',
263+
'2000-01-04',
264+
], freq='D')
265+
266+
result = arr.min()
267+
expected = pd.Period('2000-01-02', freq='D')
268+
assert result == expected
269+
270+
result = arr.max()
271+
expected = pd.Period('2000-01-05', freq='D')
272+
assert result == expected
273+
274+
result = arr.min(skipna=False)
275+
assert result is pd.NaT
276+
277+
result = arr.max(skipna=False)
278+
assert result is pd.NaT
279+
280+
@pytest.mark.parametrize('skipna', [True, False])
281+
def test_min_max_empty(self, skipna):
282+
arr = period_array([], freq='D')
283+
result = arr.min(skipna=skipna)
284+
assert result is pd.NaT
285+
286+
result = arr.max(skipna=skipna)
287+
assert result is pd.NaT

pandas/tests/arrays/test_timedeltas.py

+31
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,34 @@ def test_neg_freq(self):
7373

7474
result = -arr
7575
tm.assert_timedelta_array_equal(result, expected)
76+
77+
78+
class TestReductions(object):
79+
80+
def test_min_max(self):
81+
arr = TimedeltaArray._from_sequence([
82+
'3H', '3H', 'NaT', '2H', '5H', '4H',
83+
])
84+
85+
result = arr.min()
86+
expected = pd.Timedelta('2H')
87+
assert result == expected
88+
89+
result = arr.max()
90+
expected = pd.Timedelta('5H')
91+
assert result == expected
92+
93+
result = arr.min(skipna=False)
94+
assert result is pd.NaT
95+
96+
result = arr.max(skipna=False)
97+
assert result is pd.NaT
98+
99+
@pytest.mark.parametrize('skipna', [True, False])
100+
def test_min_max_empty(self, skipna):
101+
arr = TimedeltaArray._from_sequence([])
102+
result = arr.min(skipna=skipna)
103+
assert result is pd.NaT
104+
105+
result = arr.max(skipna=skipna)
106+
assert result is pd.NaT

pandas/tests/indexes/datetimes/test_astype.py

+23
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,26 @@ def test_to_period_nofreq(self):
299299
idx = DatetimeIndex(['2000-01-01', '2000-01-02', '2000-01-03'])
300300
assert idx.freqstr is None
301301
tm.assert_index_equal(idx.to_period(), expected)
302+
303+
@pytest.mark.parametrize('tz', [None, 'US/Central'])
304+
def test_astype_category(self, tz):
305+
obj = pd.date_range("2000", periods=2, tz=tz)
306+
result = obj.astype('category')
307+
expected = pd.CategoricalIndex([pd.Timestamp('2000-01-01', tz=tz),
308+
pd.Timestamp('2000-01-02', tz=tz)])
309+
tm.assert_index_equal(result, expected)
310+
311+
result = obj._data.astype('category')
312+
expected = expected.values
313+
tm.assert_categorical_equal(result, expected)
314+
315+
@pytest.mark.parametrize('tz', [None, 'US/Central'])
316+
def test_astype_array_fallback(self, tz):
317+
obj = pd.date_range("2000", periods=2, tz=tz)
318+
result = obj.astype(bool)
319+
expected = pd.Index(np.array([True, True]))
320+
tm.assert_index_equal(result, expected)
321+
322+
result = obj._data.astype(bool)
323+
expected = np.array([True, True])
324+
tm.assert_numpy_array_equal(result, expected)

pandas/tests/indexes/period/test_astype.py

+21
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,24 @@ def test_astype_object2(self):
9797
for i in [0, 1, 3]:
9898
assert result_list[i] == expected_list[i]
9999
assert result_list[2] is pd.NaT
100+
101+
def test_astype_category(self):
102+
obj = pd.period_range("2000", periods=2)
103+
result = obj.astype('category')
104+
expected = pd.CategoricalIndex([pd.Period('2000-01-01', freq="D"),
105+
pd.Period('2000-01-02', freq="D")])
106+
tm.assert_index_equal(result, expected)
107+
108+
result = obj._data.astype('category')
109+
expected = expected.values
110+
tm.assert_categorical_equal(result, expected)
111+
112+
def test_astype_array_fallback(self):
113+
obj = pd.period_range("2000", periods=2)
114+
result = obj.astype(bool)
115+
expected = pd.Index(np.array([True, True]))
116+
tm.assert_index_equal(result, expected)
117+
118+
result = obj._data.astype(bool)
119+
expected = np.array([True, True])
120+
tm.assert_numpy_array_equal(result, expected)

pandas/tests/indexes/timedeltas/test_astype.py

+22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
import pandas.util.testing as tm
7+
import pandas as pd
78
from pandas import (
89
Float64Index, Index, Int64Index, NaT, Timedelta, TimedeltaIndex,
910
timedelta_range
@@ -77,3 +78,24 @@ def test_astype_raises(self, dtype):
7778
msg = 'Cannot cast TimedeltaArray(Mixin)? to dtype'
7879
with pytest.raises(TypeError, match=msg):
7980
idx.astype(dtype)
81+
82+
def test_astype_category(self):
83+
obj = pd.timedelta_range("1H", periods=2, freq='H')
84+
result = obj.astype('category')
85+
expected = pd.CategoricalIndex([pd.Timedelta('1H'),
86+
pd.Timedelta('2H')])
87+
tm.assert_index_equal(result, expected)
88+
89+
result = obj._data.astype('category')
90+
expected = expected.values
91+
tm.assert_categorical_equal(result, expected)
92+
93+
def test_astype_array_fallback(self):
94+
obj = pd.timedelta_range("1H", periods=2)
95+
result = obj.astype(bool)
96+
expected = pd.Index(np.array([True, True]))
97+
tm.assert_index_equal(result, expected)
98+
99+
result = obj._data.astype(bool)
100+
expected = np.array([True, True])
101+
tm.assert_numpy_array_equal(result, expected)

0 commit comments

Comments
 (0)