Skip to content

Commit b585e3b

Browse files
jbrockmendeljreback
authored andcommitted
implement add_offset_array for PeriodIndex (#19826)
1 parent a6183a2 commit b585e3b

File tree

2 files changed

+63
-14
lines changed

2 files changed

+63
-14
lines changed

pandas/core/indexes/period.py

+23
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from pandas.util._decorators import (Appender, Substitution, cache_readonly,
4545
deprecate_kwarg)
4646
from pandas.compat import zip, u
47+
from pandas.errors import PerformanceWarning
4748

4849
import pandas.core.indexes.base as ibase
4950
_index_doc_kwargs = dict(ibase._index_doc_kwargs)
@@ -746,6 +747,28 @@ def _sub_period(self, other):
746747
# result must be Int64Index or Float64Index
747748
return Index(new_data)
748749

750+
def _add_offset_array(self, other):
751+
# Array/Index of DateOffset objects
752+
if len(other) == 1:
753+
return self + other[0]
754+
else:
755+
warnings.warn("Adding/subtracting array of DateOffsets to "
756+
"{cls} not vectorized"
757+
.format(cls=type(self).__name__), PerformanceWarning)
758+
res_values = self.astype('O').values + np.array(other)
759+
return self.__class__(res_values)
760+
761+
def _sub_offset_array(self, other):
762+
# Array/Index of DateOffset objects
763+
if len(other) == 1:
764+
return self - other[0]
765+
else:
766+
warnings.warn("Adding/subtracting array of DateOffsets to "
767+
"{cls} not vectorized"
768+
.format(cls=type(self).__name__), PerformanceWarning)
769+
res_values = self.astype('O').values - np.array(other)
770+
return self.__class__(res_values)
771+
749772
def shift(self, n):
750773
"""
751774
Specialized shift which produces an PeriodIndex

pandas/tests/indexes/period/test_arithmetic.py

+40-14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
period_range, Period, PeriodIndex,
1010
_np_version_under1p10)
1111
import pandas.core.indexes.period as period
12+
from pandas.errors import PerformanceWarning
1213

1314

1415
_common_mismatch = [pd.offsets.YearBegin(2),
@@ -254,32 +255,57 @@ def test_comp_nat(self, dtype):
254255

255256

256257
class TestPeriodIndexArithmetic(object):
257-
def test_pi_add_offset_array(self):
258+
@pytest.mark.parametrize('box', [np.array, pd.Index])
259+
def test_pi_add_offset_array(self, box):
258260
# GH#18849
259261
pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('2016Q2')])
260-
offs = np.array([pd.offsets.QuarterEnd(n=1, startingMonth=12),
261-
pd.offsets.QuarterEnd(n=-2, startingMonth=12)])
262-
res = pi + offs
262+
offs = box([pd.offsets.QuarterEnd(n=1, startingMonth=12),
263+
pd.offsets.QuarterEnd(n=-2, startingMonth=12)])
263264
expected = pd.PeriodIndex([pd.Period('2015Q2'), pd.Period('2015Q4')])
265+
266+
with tm.assert_produces_warning(PerformanceWarning):
267+
res = pi + offs
264268
tm.assert_index_equal(res, expected)
265269

270+
with tm.assert_produces_warning(PerformanceWarning):
271+
res2 = offs + pi
272+
tm.assert_index_equal(res2, expected)
273+
266274
unanchored = np.array([pd.offsets.Hour(n=1),
267275
pd.offsets.Minute(n=-2)])
276+
# addition/subtraction ops with incompatible offsets should issue
277+
# a PerformanceWarning and _then_ raise a TypeError.
268278
with pytest.raises(period.IncompatibleFrequency):
269-
pi + unanchored
270-
with pytest.raises(TypeError):
271-
unanchored + pi
279+
with tm.assert_produces_warning(PerformanceWarning):
280+
pi + unanchored
281+
with pytest.raises(period.IncompatibleFrequency):
282+
with tm.assert_produces_warning(PerformanceWarning):
283+
unanchored + pi
272284

273-
@pytest.mark.xfail(reason='GH#18824 radd doesnt implement this case')
274-
def test_pi_radd_offset_array(self):
275-
# GH#18849
285+
@pytest.mark.parametrize('box', [np.array, pd.Index])
286+
def test_pi_sub_offset_array(self, box):
287+
# GH#18824
276288
pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('2016Q2')])
277-
offs = np.array([pd.offsets.QuarterEnd(n=1, startingMonth=12),
278-
pd.offsets.QuarterEnd(n=-2, startingMonth=12)])
279-
res = offs + pi
280-
expected = pd.PeriodIndex([pd.Period('2015Q2'), pd.Period('2015Q4')])
289+
other = box([pd.offsets.QuarterEnd(n=1, startingMonth=12),
290+
pd.offsets.QuarterEnd(n=-2, startingMonth=12)])
291+
292+
expected = PeriodIndex([pi[n] - other[n] for n in range(len(pi))])
293+
294+
with tm.assert_produces_warning(PerformanceWarning):
295+
res = pi - other
281296
tm.assert_index_equal(res, expected)
282297

298+
anchored = box([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)])
299+
300+
# addition/subtraction ops with anchored offsets should issue
301+
# a PerformanceWarning and _then_ raise a TypeError.
302+
with pytest.raises(period.IncompatibleFrequency):
303+
with tm.assert_produces_warning(PerformanceWarning):
304+
pi - anchored
305+
with pytest.raises(period.IncompatibleFrequency):
306+
with tm.assert_produces_warning(PerformanceWarning):
307+
anchored - pi
308+
283309
def test_pi_add_iadd_pi_raises(self):
284310
rng = pd.period_range('1/1/2000', freq='D', periods=5)
285311
other = pd.period_range('1/6/2000', freq='D', periods=5)

0 commit comments

Comments
 (0)