Skip to content

Commit 4522dfe

Browse files
committed
Merge remote-tracking branch 'upstream/master' into disown-tz-only-rebased
2 parents b046791 + 71332c4 commit 4522dfe

File tree

15 files changed

+1002
-927
lines changed

15 files changed

+1002
-927
lines changed

pandas/core/arrays/datetimelike.py

+35-34
Original file line numberDiff line numberDiff line change
@@ -371,40 +371,6 @@ def _box_values(self, values):
371371
"""
372372
return lib.map_infer(values, self._box_func)
373373

374-
def _ensure_localized(self, arg, ambiguous="raise", nonexistent="raise",
375-
from_utc=False):
376-
"""
377-
ensure that we are re-localized
378-
379-
This is for compat as we can then call this on all datetimelike
380-
arrays generally (ignored for Period/Timedelta)
381-
382-
Parameters
383-
----------
384-
arg : Union[DatetimeLikeArray, DatetimeIndexOpsMixin, ndarray]
385-
ambiguous : str, bool, or bool-ndarray, default 'raise'
386-
nonexistent : str, default 'raise'
387-
from_utc : bool, default False
388-
If True, localize the i8 ndarray to UTC first before converting to
389-
the appropriate tz. If False, localize directly to the tz.
390-
391-
Returns
392-
-------
393-
localized DTI
394-
"""
395-
# reconvert to local tz
396-
tz = getattr(self, 'tz', None)
397-
if tz is not None:
398-
if not isinstance(arg, type(self)):
399-
arg = self._simple_new(arg)
400-
if from_utc:
401-
arg = arg.tz_localize('UTC').tz_convert(tz)
402-
else:
403-
arg = arg.tz_localize(
404-
tz, ambiguous=ambiguous, nonexistent=nonexistent
405-
)
406-
return arg
407-
408374
def __iter__(self):
409375
return (self._box_func(v) for v in self.asi8)
410376

@@ -1405,6 +1371,41 @@ def _evaluate_compare(self, other, op):
14051371
result[mask] = filler
14061372
return result
14071373

1374+
def _ensure_localized(self, arg, ambiguous='raise', nonexistent='raise',
1375+
from_utc=False):
1376+
"""
1377+
Ensure that we are re-localized.
1378+
1379+
This is for compat as we can then call this on all datetimelike
1380+
arrays generally (ignored for Period/Timedelta)
1381+
1382+
Parameters
1383+
----------
1384+
arg : Union[DatetimeLikeArray, DatetimeIndexOpsMixin, ndarray]
1385+
ambiguous : str, bool, or bool-ndarray, default 'raise'
1386+
nonexistent : str, default 'raise'
1387+
from_utc : bool, default False
1388+
If True, localize the i8 ndarray to UTC first before converting to
1389+
the appropriate tz. If False, localize directly to the tz.
1390+
1391+
Returns
1392+
-------
1393+
localized array
1394+
"""
1395+
1396+
# reconvert to local tz
1397+
tz = getattr(self, 'tz', None)
1398+
if tz is not None:
1399+
if not isinstance(arg, type(self)):
1400+
arg = self._simple_new(arg)
1401+
if from_utc:
1402+
arg = arg.tz_localize('UTC').tz_convert(self.tz)
1403+
else:
1404+
arg = arg.tz_localize(
1405+
self.tz, ambiguous=ambiguous, nonexistent=nonexistent
1406+
)
1407+
return arg
1408+
14081409
# --------------------------------------------------------------
14091410
# Reductions
14101411

pandas/core/indexes/datetimelike.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,13 @@ def _evaluate_compare(self, other, op):
153153

154154
def _ensure_localized(self, arg, ambiguous='raise', nonexistent='raise',
155155
from_utc=False):
156-
result = self._data._ensure_localized(arg,
157-
ambiguous=ambiguous,
158-
nonexistent=nonexistent,
159-
from_utc=from_utc)
156+
# See DatetimeLikeArrayMixin._ensure_localized.__doc__
160157
if getattr(self, 'tz', None):
158+
# ensure_localized is only relevant for tz-aware DTI
159+
result = self._data._ensure_localized(arg,
160+
ambiguous=ambiguous,
161+
nonexistent=nonexistent,
162+
from_utc=from_utc)
161163
return type(self)._simple_new(result, name=self.name)
162164
return arg
163165

pandas/tests/arithmetic/test_datetime64.py

+120
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from pandas._libs.tslibs.conversion import localize_pydatetime
2121
from pandas._libs.tslibs.offsets import shift_months
2222

23+
from pandas.core.indexes.datetimes import _to_m8
24+
2325
from pandas import (
2426
Timestamp, Timedelta, Period, Series, date_range, NaT,
2527
DatetimeIndex, TimedeltaIndex)
@@ -61,6 +63,50 @@ def test_dt64_nat_comparison(self):
6163

6264
class TestDatetime64SeriesComparison(object):
6365
# TODO: moved from tests.series.test_operators; needs cleanup
66+
67+
@pytest.mark.parametrize('pair', [
68+
([pd.Timestamp('2011-01-01'), NaT, pd.Timestamp('2011-01-03')],
69+
[NaT, NaT, pd.Timestamp('2011-01-03')]),
70+
71+
([pd.Timedelta('1 days'), NaT, pd.Timedelta('3 days')],
72+
[NaT, NaT, pd.Timedelta('3 days')]),
73+
74+
([pd.Period('2011-01', freq='M'), NaT,
75+
pd.Period('2011-03', freq='M')],
76+
[NaT, NaT, pd.Period('2011-03', freq='M')]),
77+
78+
])
79+
@pytest.mark.parametrize('reverse', [True, False])
80+
@pytest.mark.parametrize('box', [Series, pd.Index])
81+
@pytest.mark.parametrize('dtype', [None, object])
82+
def test_nat_comparisons(self, dtype, box, reverse, pair):
83+
l, r = pair
84+
if reverse:
85+
# add lhs / rhs switched data
86+
l, r = r, l
87+
88+
left = Series(l, dtype=dtype)
89+
right = box(r, dtype=dtype)
90+
# Series, Index
91+
92+
expected = Series([False, False, True])
93+
tm.assert_series_equal(left == right, expected)
94+
95+
expected = Series([True, True, False])
96+
tm.assert_series_equal(left != right, expected)
97+
98+
expected = Series([False, False, False])
99+
tm.assert_series_equal(left < right, expected)
100+
101+
expected = Series([False, False, False])
102+
tm.assert_series_equal(left > right, expected)
103+
104+
expected = Series([False, False, True])
105+
tm.assert_series_equal(left >= right, expected)
106+
107+
expected = Series([False, False, True])
108+
tm.assert_series_equal(left <= right, expected)
109+
64110
def test_comparison_invalid(self, box_with_array):
65111
# GH#4968
66112
# invalid date/int comparisons
@@ -272,8 +318,46 @@ def test_dt64arr_timestamp_equality(self, box_with_array):
272318
expected = tm.box_expected([False, False], xbox)
273319
tm.assert_equal(result, expected)
274320

321+
@pytest.mark.parametrize('op', [operator.eq, operator.ne,
322+
operator.gt, operator.ge,
323+
operator.lt, operator.le])
324+
def test_comparison_tzawareness_compat(self, op):
325+
# GH#18162
326+
dr = pd.date_range('2016-01-01', periods=6)
327+
dz = dr.tz_localize('US/Pacific')
328+
329+
# Check that there isn't a problem aware-aware and naive-naive do not
330+
# raise
331+
naive_series = Series(dr)
332+
aware_series = Series(dz)
333+
with pytest.raises(TypeError):
334+
op(dz, naive_series)
335+
with pytest.raises(TypeError):
336+
op(dr, aware_series)
337+
338+
# TODO: implement _assert_tzawareness_compat for the reverse
339+
# comparison with the Series on the left-hand side
340+
275341

276342
class TestDatetimeIndexComparisons(object):
343+
344+
# TODO: moved from tests.indexes.test_base; parametrize and de-duplicate
345+
@pytest.mark.parametrize("op", [
346+
operator.eq, operator.ne, operator.gt, operator.lt,
347+
operator.ge, operator.le
348+
])
349+
def test_comparators(self, op):
350+
index = tm.makeDateIndex(100)
351+
element = index[len(index) // 2]
352+
element = _to_m8(element)
353+
354+
arr = np.array(index)
355+
arr_result = op(arr, element)
356+
index_result = op(index, element)
357+
358+
assert isinstance(index_result, np.ndarray)
359+
tm.assert_numpy_array_equal(arr_result, index_result)
360+
277361
@pytest.mark.parametrize('other', [datetime(2016, 1, 1),
278362
Timestamp('2016-01-01'),
279363
np.datetime64('2016-01-01')])
@@ -1450,6 +1534,42 @@ def test_datetimeindex_sub_datetimeindex_overflow(self):
14501534

14511535
class TestTimestampSeriesArithmetic(object):
14521536

1537+
def test_empty_series_add_sub(self):
1538+
# GH#13844
1539+
a = Series(dtype='M8[ns]')
1540+
b = Series(dtype='m8[ns]')
1541+
tm.assert_series_equal(a, a + b)
1542+
tm.assert_series_equal(a, a - b)
1543+
tm.assert_series_equal(a, b + a)
1544+
with pytest.raises(TypeError):
1545+
b - a
1546+
1547+
def test_operators_datetimelike(self):
1548+
1549+
# ## timedelta64 ###
1550+
td1 = Series([timedelta(minutes=5, seconds=3)] * 3)
1551+
td1.iloc[2] = np.nan
1552+
1553+
# ## datetime64 ###
1554+
dt1 = Series([pd.Timestamp('20111230'), pd.Timestamp('20120101'),
1555+
pd.Timestamp('20120103')])
1556+
dt1.iloc[2] = np.nan
1557+
dt2 = Series([pd.Timestamp('20111231'), pd.Timestamp('20120102'),
1558+
pd.Timestamp('20120104')])
1559+
dt1 - dt2
1560+
dt2 - dt1
1561+
1562+
# ## datetime64 with timetimedelta ###
1563+
dt1 + td1
1564+
td1 + dt1
1565+
dt1 - td1
1566+
# TODO: Decide if this ought to work.
1567+
# td1 - dt1
1568+
1569+
# ## timetimedelta with datetime64 ###
1570+
td1 + dt1
1571+
dt1 + td1
1572+
14531573
def test_dt64ser_sub_datetime_dtype(self):
14541574
ts = Timestamp(datetime(1993, 1, 7, 13, 30, 00))
14551575
dt = datetime(1993, 6, 22, 13, 30)

pandas/tests/arithmetic/test_numeric.py

+121-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# behave identically.
44
# Specifically for numeric dtypes
55
from decimal import Decimal
6+
from itertools import combinations
67
import operator
78

89
import pytest
@@ -846,11 +847,15 @@ def check(series, other):
846847
class TestUFuncCompat(object):
847848

848849
@pytest.mark.parametrize('holder', [pd.Int64Index, pd.UInt64Index,
849-
pd.Float64Index, pd.Series])
850+
pd.Float64Index, pd.RangeIndex,
851+
pd.Series])
850852
def test_ufunc_compat(self, holder):
851853
box = pd.Series if holder is pd.Series else pd.Index
852854

853-
idx = holder(np.arange(5, dtype='int64'))
855+
if holder is pd.RangeIndex:
856+
idx = pd.RangeIndex(0, 5)
857+
else:
858+
idx = holder(np.arange(5, dtype='int64'))
854859
result = np.sin(idx)
855860
expected = box(np.sin(np.arange(5, dtype='int64')))
856861
tm.assert_equal(result, expected)
@@ -940,3 +945,117 @@ def test_operators_reverse_object(self, op):
940945
result = op(1., arr)
941946
expected = op(1., arr.astype(float))
942947
tm.assert_series_equal(result.astype(float), expected)
948+
949+
950+
class TestNumericArithmeticUnsorted(object):
951+
# Tests in this class have been moved from type-specific test modules
952+
# but not yet sorted, parametrized, and de-duplicated
953+
954+
def check_binop(self, ops, scalars, idxs):
955+
for op in ops:
956+
for a, b in combinations(idxs, 2):
957+
result = op(a, b)
958+
expected = op(pd.Int64Index(a), pd.Int64Index(b))
959+
tm.assert_index_equal(result, expected)
960+
for idx in idxs:
961+
for scalar in scalars:
962+
result = op(idx, scalar)
963+
expected = op(pd.Int64Index(idx), scalar)
964+
tm.assert_index_equal(result, expected)
965+
966+
def test_binops(self):
967+
ops = [operator.add, operator.sub, operator.mul, operator.floordiv,
968+
operator.truediv]
969+
scalars = [-1, 1, 2]
970+
idxs = [pd.RangeIndex(0, 10, 1), pd.RangeIndex(0, 20, 2),
971+
pd.RangeIndex(-10, 10, 2), pd.RangeIndex(5, -5, -1)]
972+
self.check_binop(ops, scalars, idxs)
973+
974+
def test_binops_pow(self):
975+
# later versions of numpy don't allow powers of negative integers
976+
# so test separately
977+
# https://github.com/numpy/numpy/pull/8127
978+
ops = [pow]
979+
scalars = [1, 2]
980+
idxs = [pd.RangeIndex(0, 10, 1), pd.RangeIndex(0, 20, 2)]
981+
self.check_binop(ops, scalars, idxs)
982+
983+
# TODO: mod, divmod?
984+
@pytest.mark.parametrize('op', [operator.add, operator.sub,
985+
operator.mul, operator.floordiv,
986+
operator.truediv, operator.pow])
987+
def test_arithmetic_with_frame_or_series(self, op):
988+
# check that we return NotImplemented when operating with Series
989+
# or DataFrame
990+
index = pd.RangeIndex(5)
991+
other = pd.Series(np.random.randn(5))
992+
993+
expected = op(pd.Series(index), other)
994+
result = op(index, other)
995+
tm.assert_series_equal(result, expected)
996+
997+
other = pd.DataFrame(np.random.randn(2, 5))
998+
expected = op(pd.DataFrame([index, index]), other)
999+
result = op(index, other)
1000+
tm.assert_frame_equal(result, expected)
1001+
1002+
def test_numeric_compat2(self):
1003+
# validate that we are handling the RangeIndex overrides to numeric ops
1004+
# and returning RangeIndex where possible
1005+
1006+
idx = pd.RangeIndex(0, 10, 2)
1007+
1008+
result = idx * 2
1009+
expected = pd.RangeIndex(0, 20, 4)
1010+
tm.assert_index_equal(result, expected, exact=True)
1011+
1012+
result = idx + 2
1013+
expected = pd.RangeIndex(2, 12, 2)
1014+
tm.assert_index_equal(result, expected, exact=True)
1015+
1016+
result = idx - 2
1017+
expected = pd.RangeIndex(-2, 8, 2)
1018+
tm.assert_index_equal(result, expected, exact=True)
1019+
1020+
# truediv under PY3
1021+
result = idx / 2
1022+
1023+
if PY3:
1024+
expected = pd.RangeIndex(0, 5, 1).astype('float64')
1025+
else:
1026+
expected = pd.RangeIndex(0, 5, 1)
1027+
tm.assert_index_equal(result, expected, exact=True)
1028+
1029+
result = idx / 4
1030+
expected = pd.RangeIndex(0, 10, 2) / 4
1031+
tm.assert_index_equal(result, expected, exact=True)
1032+
1033+
result = idx // 1
1034+
expected = idx
1035+
tm.assert_index_equal(result, expected, exact=True)
1036+
1037+
# __mul__
1038+
result = idx * idx
1039+
expected = Index(idx.values * idx.values)
1040+
tm.assert_index_equal(result, expected, exact=True)
1041+
1042+
# __pow__
1043+
idx = pd.RangeIndex(0, 1000, 2)
1044+
result = idx ** 2
1045+
expected = idx._int64index ** 2
1046+
tm.assert_index_equal(Index(result.values), expected, exact=True)
1047+
1048+
# __floordiv__
1049+
cases_exact = [
1050+
(pd.RangeIndex(0, 1000, 2), 2, pd.RangeIndex(0, 500, 1)),
1051+
(pd.RangeIndex(-99, -201, -3), -3, pd.RangeIndex(33, 67, 1)),
1052+
(pd.RangeIndex(0, 1000, 1), 2,
1053+
pd.RangeIndex(0, 1000, 1)._int64index // 2),
1054+
(pd.RangeIndex(0, 100, 1), 2.0,
1055+
pd.RangeIndex(0, 100, 1)._int64index // 2.0),
1056+
(pd.RangeIndex(0), 50, pd.RangeIndex(0)),
1057+
(pd.RangeIndex(2, 4, 2), 3, pd.RangeIndex(0, 1, 1)),
1058+
(pd.RangeIndex(-5, -10, -6), 4, pd.RangeIndex(-2, -1, 1)),
1059+
(pd.RangeIndex(-100, -200, 3), 2, pd.RangeIndex(0))]
1060+
for idx, div, expected in cases_exact:
1061+
tm.assert_index_equal(idx // div, expected, exact=True)

0 commit comments

Comments
 (0)