# -*- coding: utf-8 -*- from datetime import timedelta import operator import pytest import numpy as np import pandas as pd import pandas.util.testing as tm from pandas import (Timedelta, period_range, Period, PeriodIndex, _np_version_under1p10) import pandas.core.indexes.period as period from pandas.core import ops from pandas.errors import PerformanceWarning _common_mismatch = [pd.offsets.YearBegin(2), pd.offsets.MonthBegin(1), pd.offsets.Minute()] @pytest.fixture(params=[timedelta(minutes=30), np.timedelta64(30, 's'), Timedelta(seconds=30)] + _common_mismatch) def not_hourly(request): """ Several timedelta-like and DateOffset instances that are _not_ compatible with Hourly frequencies. """ return request.param @pytest.fixture(params=[np.timedelta64(4, 'h'), timedelta(hours=23), Timedelta('23:00:00')] + _common_mismatch) def not_daily(request): """ Several timedelta-like and DateOffset instances that are _not_ compatible with Daily frequencies. """ return request.param @pytest.fixture(params=[np.timedelta64(365, 'D'), timedelta(365), Timedelta(days=365)] + _common_mismatch) def mismatched(request): """ Several timedelta-like and DateOffset instances that are _not_ compatible with Monthly or Annual frequencies. """ return request.param @pytest.fixture(params=[pd.offsets.Day(3), timedelta(days=3), np.timedelta64(3, 'D'), pd.offsets.Hour(72), timedelta(minutes=60 * 24 * 3), np.timedelta64(72, 'h'), Timedelta('72:00:00')]) def three_days(request): """ Several timedelta-like and DateOffset objects that each represent a 3-day timedelta """ return request.param @pytest.fixture(params=[pd.offsets.Hour(2), timedelta(hours=2), np.timedelta64(2, 'h'), pd.offsets.Minute(120), timedelta(minutes=120), np.timedelta64(120, 'm')]) def two_hours(request): """ Several timedelta-like and DateOffset objects that each represent a 2-hour timedelta """ return request.param class TestPeriodIndexComparisons(object): def test_pi_cmp_period(self): idx = period_range('2007-01', periods=20, freq='M') result = idx < idx[10] exp = idx.values < idx.values[10] tm.assert_numpy_array_equal(result, exp) @pytest.mark.parametrize('freq', ['M', '2M', '3M']) def test_pi_cmp_pi(self, freq): base = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'], freq=freq) per = Period('2011-02', freq=freq) exp = np.array([False, True, False, False]) tm.assert_numpy_array_equal(base == per, exp) tm.assert_numpy_array_equal(per == base, exp) exp = np.array([True, False, True, True]) tm.assert_numpy_array_equal(base != per, exp) tm.assert_numpy_array_equal(per != base, exp) exp = np.array([False, False, True, True]) tm.assert_numpy_array_equal(base > per, exp) tm.assert_numpy_array_equal(per < base, exp) exp = np.array([True, False, False, False]) tm.assert_numpy_array_equal(base < per, exp) tm.assert_numpy_array_equal(per > base, exp) exp = np.array([False, True, True, True]) tm.assert_numpy_array_equal(base >= per, exp) tm.assert_numpy_array_equal(per <= base, exp) exp = np.array([True, True, False, False]) tm.assert_numpy_array_equal(base <= per, exp) tm.assert_numpy_array_equal(per >= base, exp) idx = PeriodIndex(['2011-02', '2011-01', '2011-03', '2011-05'], freq=freq) exp = np.array([False, False, True, False]) tm.assert_numpy_array_equal(base == idx, exp) exp = np.array([True, True, False, True]) tm.assert_numpy_array_equal(base != idx, exp) exp = np.array([False, True, False, False]) tm.assert_numpy_array_equal(base > idx, exp) exp = np.array([True, False, False, True]) tm.assert_numpy_array_equal(base < idx, exp) exp = np.array([False, True, True, False]) tm.assert_numpy_array_equal(base >= idx, exp) exp = np.array([True, False, True, True]) tm.assert_numpy_array_equal(base <= idx, exp) @pytest.mark.parametrize('freq', ['M', '2M', '3M']) def test_pi_cmp_pi_mismatched_freq_raises(self, freq): # different base freq base = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'], freq=freq) msg = "Input has different freq=A-DEC from PeriodIndex" with tm.assert_raises_regex(period.IncompatibleFrequency, msg): base <= Period('2011', freq='A') with tm.assert_raises_regex(period.IncompatibleFrequency, msg): Period('2011', freq='A') >= base idx = PeriodIndex(['2011', '2012', '2013', '2014'], freq='A') with tm.assert_raises_regex(period.IncompatibleFrequency, msg): base <= idx # Different frequency msg = "Input has different freq=4M from PeriodIndex" with tm.assert_raises_regex(period.IncompatibleFrequency, msg): base <= Period('2011', freq='4M') with tm.assert_raises_regex(period.IncompatibleFrequency, msg): Period('2011', freq='4M') >= base idx = PeriodIndex(['2011', '2012', '2013', '2014'], freq='4M') with tm.assert_raises_regex(period.IncompatibleFrequency, msg): base <= idx @pytest.mark.parametrize('freq', ['M', '2M', '3M']) def test_pi_cmp_nat(self, freq): idx1 = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-05'], freq=freq) result = idx1 > Period('2011-02', freq=freq) exp = np.array([False, False, False, True]) tm.assert_numpy_array_equal(result, exp) result = Period('2011-02', freq=freq) < idx1 tm.assert_numpy_array_equal(result, exp) result = idx1 == Period('NaT', freq=freq) exp = np.array([False, False, False, False]) tm.assert_numpy_array_equal(result, exp) result = Period('NaT', freq=freq) == idx1 tm.assert_numpy_array_equal(result, exp) result = idx1 != Period('NaT', freq=freq) exp = np.array([True, True, True, True]) tm.assert_numpy_array_equal(result, exp) result = Period('NaT', freq=freq) != idx1 tm.assert_numpy_array_equal(result, exp) idx2 = PeriodIndex(['2011-02', '2011-01', '2011-04', 'NaT'], freq=freq) result = idx1 < idx2 exp = np.array([True, False, False, False]) tm.assert_numpy_array_equal(result, exp) result = idx1 == idx2 exp = np.array([False, False, False, False]) tm.assert_numpy_array_equal(result, exp) result = idx1 != idx2 exp = np.array([True, True, True, True]) tm.assert_numpy_array_equal(result, exp) result = idx1 == idx1 exp = np.array([True, True, False, True]) tm.assert_numpy_array_equal(result, exp) result = idx1 != idx1 exp = np.array([False, False, True, False]) tm.assert_numpy_array_equal(result, exp) @pytest.mark.parametrize('freq', ['M', '2M', '3M']) def test_pi_cmp_nat_mismatched_freq_raises(self, freq): idx1 = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-05'], freq=freq) diff = PeriodIndex(['2011-02', '2011-01', '2011-04', 'NaT'], freq='4M') msg = "Input has different freq=4M from PeriodIndex" with tm.assert_raises_regex(period.IncompatibleFrequency, msg): idx1 > diff with tm.assert_raises_regex(period.IncompatibleFrequency, msg): idx1 == diff # TODO: De-duplicate with test_pi_cmp_nat @pytest.mark.parametrize('dtype', [object, None]) def test_comp_nat(self, dtype): left = pd.PeriodIndex([pd.Period('2011-01-01'), pd.NaT, pd.Period('2011-01-03')]) right = pd.PeriodIndex([pd.NaT, pd.NaT, pd.Period('2011-01-03')]) if dtype is not None: left = left.astype(dtype) right = right.astype(dtype) result = left == right expected = np.array([False, False, True]) tm.assert_numpy_array_equal(result, expected) result = left != right expected = np.array([True, True, False]) tm.assert_numpy_array_equal(result, expected) expected = np.array([False, False, False]) tm.assert_numpy_array_equal(left == pd.NaT, expected) tm.assert_numpy_array_equal(pd.NaT == right, expected) expected = np.array([True, True, True]) tm.assert_numpy_array_equal(left != pd.NaT, expected) tm.assert_numpy_array_equal(pd.NaT != left, expected) expected = np.array([False, False, False]) tm.assert_numpy_array_equal(left < pd.NaT, expected) tm.assert_numpy_array_equal(pd.NaT > left, expected) class TestPeriodIndexArithmetic(object): # ------------------------------------------------------------- # Invalid Operations @pytest.mark.parametrize('other', [3.14, np.array([2.0, 3.0])]) @pytest.mark.parametrize('op', [operator.add, ops.radd, operator.sub, ops.rsub]) def test_pi_add_sub_float(self, op, other): dti = pd.DatetimeIndex(['2011-01-01', '2011-01-02'], freq='D') pi = dti.to_period('D') with pytest.raises(TypeError): op(pi, other) # ----------------------------------------------------------------- # __add__/__sub__ with ndarray[datetime64] and ndarray[timedelta64] def test_pi_add_sub_dt64_array_raises(self): rng = pd.period_range('1/1/2000', freq='D', periods=3) dti = pd.date_range('2016-01-01', periods=3) dtarr = dti.values with pytest.raises(TypeError): rng + dtarr with pytest.raises(TypeError): dtarr + rng with pytest.raises(TypeError): rng - dtarr with pytest.raises(TypeError): dtarr - rng def test_pi_add_sub_td64_array_non_tick_raises(self): rng = pd.period_range('1/1/2000', freq='Q', periods=3) dti = pd.date_range('2016-01-01', periods=3) tdi = dti - dti.shift(1) tdarr = tdi.values with pytest.raises(period.IncompatibleFrequency): rng + tdarr with pytest.raises(period.IncompatibleFrequency): tdarr + rng with pytest.raises(period.IncompatibleFrequency): rng - tdarr with pytest.raises(period.IncompatibleFrequency): tdarr - rng @pytest.mark.xfail(reason='op with TimedeltaIndex raises, with ndarray OK') def test_pi_add_sub_td64_array_tick(self): rng = pd.period_range('1/1/2000', freq='Q', periods=3) dti = pd.date_range('2016-01-01', periods=3) tdi = dti - dti.shift(1) tdarr = tdi.values expected = rng + tdi result = rng + tdarr tm.assert_index_equal(result, expected) result = tdarr + rng tm.assert_index_equal(result, expected) expected = rng - tdi result = rng - tdarr tm.assert_index_equal(result, expected) with pytest.raises(TypeError): tdarr - rng # ----------------------------------------------------------------- # operations with array/Index of DateOffset objects @pytest.mark.parametrize('box', [np.array, pd.Index]) def test_pi_add_offset_array(self, box): # GH#18849 pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('2016Q2')]) offs = box([pd.offsets.QuarterEnd(n=1, startingMonth=12), pd.offsets.QuarterEnd(n=-2, startingMonth=12)]) expected = pd.PeriodIndex([pd.Period('2015Q2'), pd.Period('2015Q4')]) with tm.assert_produces_warning(PerformanceWarning): res = pi + offs tm.assert_index_equal(res, expected) with tm.assert_produces_warning(PerformanceWarning): res2 = offs + pi tm.assert_index_equal(res2, expected) unanchored = np.array([pd.offsets.Hour(n=1), pd.offsets.Minute(n=-2)]) # addition/subtraction ops with incompatible offsets should issue # a PerformanceWarning and _then_ raise a TypeError. with pytest.raises(period.IncompatibleFrequency): with tm.assert_produces_warning(PerformanceWarning): pi + unanchored with pytest.raises(period.IncompatibleFrequency): with tm.assert_produces_warning(PerformanceWarning): unanchored + pi @pytest.mark.parametrize('box', [np.array, pd.Index]) def test_pi_sub_offset_array(self, box): # GH#18824 pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('2016Q2')]) other = box([pd.offsets.QuarterEnd(n=1, startingMonth=12), pd.offsets.QuarterEnd(n=-2, startingMonth=12)]) expected = PeriodIndex([pi[n] - other[n] for n in range(len(pi))]) with tm.assert_produces_warning(PerformanceWarning): res = pi - other tm.assert_index_equal(res, expected) anchored = box([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)]) # addition/subtraction ops with anchored offsets should issue # a PerformanceWarning and _then_ raise a TypeError. with pytest.raises(period.IncompatibleFrequency): with tm.assert_produces_warning(PerformanceWarning): pi - anchored with pytest.raises(period.IncompatibleFrequency): with tm.assert_produces_warning(PerformanceWarning): anchored - pi def test_pi_add_iadd_pi_raises(self): rng = pd.period_range('1/1/2000', freq='D', periods=5) other = pd.period_range('1/6/2000', freq='D', periods=5) # previously performed setop union, now raises TypeError (GH14164) with pytest.raises(TypeError): rng + other with pytest.raises(TypeError): rng += other def test_pi_add_iadd_int(self, one): # Variants of `one` for #19012 rng = pd.period_range('2000-01-01 09:00', freq='H', periods=10) result = rng + one expected = pd.period_range('2000-01-01 10:00', freq='H', periods=10) tm.assert_index_equal(result, expected) rng += one tm.assert_index_equal(rng, expected) def test_pi_sub_isub_int(self, one): """ PeriodIndex.__sub__ and __isub__ with several representations of the integer 1, e.g. int, long, np.int64, np.uint8, ... """ rng = pd.period_range('2000-01-01 09:00', freq='H', periods=10) result = rng - one expected = pd.period_range('2000-01-01 08:00', freq='H', periods=10) tm.assert_index_equal(result, expected) rng -= one tm.assert_index_equal(rng, expected) @pytest.mark.parametrize('five', [5, np.array(5, dtype=np.int64)]) def test_pi_sub_intlike(self, five): rng = period_range('2007-01', periods=50) result = rng - five exp = rng + (-five) tm.assert_index_equal(result, exp) def test_pi_sub_isub_pi_raises(self): # previously performed setop, now raises TypeError (GH14164) # TODO needs to wait on #13077 for decision on result type rng = pd.period_range('1/1/2000', freq='D', periods=5) other = pd.period_range('1/6/2000', freq='D', periods=5) with pytest.raises(TypeError): rng - other with pytest.raises(TypeError): rng -= other def test_pi_sub_isub_offset(self): # offset # DateOffset rng = pd.period_range('2014', '2024', freq='A') result = rng - pd.offsets.YearEnd(5) expected = pd.period_range('2009', '2019', freq='A') tm.assert_index_equal(result, expected) rng -= pd.offsets.YearEnd(5) tm.assert_index_equal(rng, expected) rng = pd.period_range('2014-01', '2016-12', freq='M') result = rng - pd.offsets.MonthEnd(5) expected = pd.period_range('2013-08', '2016-07', freq='M') tm.assert_index_equal(result, expected) rng -= pd.offsets.MonthEnd(5) tm.assert_index_equal(rng, expected) # --------------------------------------------------------------- # Timedelta-like (timedelta, timedelta64, Timedelta, Tick) # TODO: Some of these are misnomers because of non-Tick DateOffsets def test_pi_add_iadd_timedeltalike_daily(self, three_days): # Tick other = three_days rng = pd.period_range('2014-05-01', '2014-05-15', freq='D') expected = pd.period_range('2014-05-04', '2014-05-18', freq='D') result = rng + other tm.assert_index_equal(result, expected) rng += other tm.assert_index_equal(rng, expected) def test_pi_sub_isub_timedeltalike_daily(self, three_days): # Tick-like 3 Days other = three_days rng = pd.period_range('2014-05-01', '2014-05-15', freq='D') expected = pd.period_range('2014-04-28', '2014-05-12', freq='D') result = rng - other tm.assert_index_equal(result, expected) rng -= other tm.assert_index_equal(rng, expected) def test_pi_add_iadd_timedeltalike_freq_mismatch_daily(self, not_daily): other = not_daily rng = pd.period_range('2014-05-01', '2014-05-15', freq='D') msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=D\\)' with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng + other with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng += other def test_pi_sub_timedeltalike_freq_mismatch_daily(self, not_daily): other = not_daily rng = pd.period_range('2014-05-01', '2014-05-15', freq='D') msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=D\\)' with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng - other def test_pi_add_iadd_timedeltalike_hourly(self, two_hours): other = two_hours rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00', freq='H') expected = pd.period_range('2014-01-01 12:00', '2014-01-05 12:00', freq='H') result = rng + other tm.assert_index_equal(result, expected) rng += other tm.assert_index_equal(rng, expected) def test_pi_add_timedeltalike_mismatched_freq_hourly(self, not_hourly): other = not_hourly rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00', freq='H') msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=H\\)' with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng + other with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng += other def test_pi_sub_isub_timedeltalike_hourly(self, two_hours): other = two_hours rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00', freq='H') expected = pd.period_range('2014-01-01 08:00', '2014-01-05 08:00', freq='H') result = rng - other tm.assert_index_equal(result, expected) rng -= other tm.assert_index_equal(rng, expected) def test_add_iadd_timedeltalike_annual(self): # offset # DateOffset rng = pd.period_range('2014', '2024', freq='A') result = rng + pd.offsets.YearEnd(5) expected = pd.period_range('2019', '2029', freq='A') tm.assert_index_equal(result, expected) rng += pd.offsets.YearEnd(5) tm.assert_index_equal(rng, expected) def test_pi_add_iadd_timedeltalike_freq_mismatch_annual(self, mismatched): other = mismatched rng = pd.period_range('2014', '2024', freq='A') msg = ('Input has different freq(=.+)? ' 'from PeriodIndex\\(freq=A-DEC\\)') with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng + other with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng += other def test_pi_sub_isub_timedeltalike_freq_mismatch_annual(self, mismatched): other = mismatched rng = pd.period_range('2014', '2024', freq='A') msg = ('Input has different freq(=.+)? ' 'from PeriodIndex\\(freq=A-DEC\\)') with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng - other with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng -= other def test_pi_add_iadd_timedeltalike_M(self): rng = pd.period_range('2014-01', '2016-12', freq='M') expected = pd.period_range('2014-06', '2017-05', freq='M') result = rng + pd.offsets.MonthEnd(5) tm.assert_index_equal(result, expected) rng += pd.offsets.MonthEnd(5) tm.assert_index_equal(rng, expected) def test_pi_add_iadd_timedeltalike_freq_mismatch_monthly(self, mismatched): other = mismatched rng = pd.period_range('2014-01', '2016-12', freq='M') msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=M\\)' with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng + other with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng += other def test_pi_sub_isub_timedeltalike_freq_mismatch_monthly(self, mismatched): other = mismatched rng = pd.period_range('2014-01', '2016-12', freq='M') msg = 'Input has different freq(=.+)? from PeriodIndex\\(freq=M\\)' with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng - other with tm.assert_raises_regex(period.IncompatibleFrequency, msg): rng -= other # --------------------------------------------------------------- # PeriodIndex.shift is used by __add__ and __sub__ def test_pi_shift_ndarray(self): idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'], freq='M', name='idx') result = idx.shift(np.array([1, 2, 3, 4])) expected = PeriodIndex(['2011-02', '2011-04', 'NaT', '2011-08'], freq='M', name='idx') tm.assert_index_equal(result, expected) result = idx.shift(np.array([1, -2, 3, -4])) expected = PeriodIndex(['2011-02', '2010-12', 'NaT', '2010-12'], freq='M', name='idx') tm.assert_index_equal(result, expected) def test_shift(self): pi1 = PeriodIndex(freq='A', start='1/1/2001', end='12/1/2009') pi2 = PeriodIndex(freq='A', start='1/1/2002', end='12/1/2010') tm.assert_index_equal(pi1.shift(0), pi1) assert len(pi1) == len(pi2) tm.assert_index_equal(pi1.shift(1), pi2) pi1 = PeriodIndex(freq='A', start='1/1/2001', end='12/1/2009') pi2 = PeriodIndex(freq='A', start='1/1/2000', end='12/1/2008') assert len(pi1) == len(pi2) tm.assert_index_equal(pi1.shift(-1), pi2) pi1 = PeriodIndex(freq='M', start='1/1/2001', end='12/1/2009') pi2 = PeriodIndex(freq='M', start='2/1/2001', end='1/1/2010') assert len(pi1) == len(pi2) tm.assert_index_equal(pi1.shift(1), pi2) pi1 = PeriodIndex(freq='M', start='1/1/2001', end='12/1/2009') pi2 = PeriodIndex(freq='M', start='12/1/2000', end='11/1/2009') assert len(pi1) == len(pi2) tm.assert_index_equal(pi1.shift(-1), pi2) pi1 = PeriodIndex(freq='D', start='1/1/2001', end='12/1/2009') pi2 = PeriodIndex(freq='D', start='1/2/2001', end='12/2/2009') assert len(pi1) == len(pi2) tm.assert_index_equal(pi1.shift(1), pi2) pi1 = PeriodIndex(freq='D', start='1/1/2001', end='12/1/2009') pi2 = PeriodIndex(freq='D', start='12/31/2000', end='11/30/2009') assert len(pi1) == len(pi2) tm.assert_index_equal(pi1.shift(-1), pi2) def test_shift_corner_cases(self): # GH#9903 idx = pd.PeriodIndex([], name='xxx', freq='H') with pytest.raises(TypeError): # period shift doesn't accept freq idx.shift(1, freq='H') tm.assert_index_equal(idx.shift(0), idx) tm.assert_index_equal(idx.shift(3), idx) idx = pd.PeriodIndex(['2011-01-01 10:00', '2011-01-01 11:00' '2011-01-01 12:00'], name='xxx', freq='H') tm.assert_index_equal(idx.shift(0), idx) exp = pd.PeriodIndex(['2011-01-01 13:00', '2011-01-01 14:00' '2011-01-01 15:00'], name='xxx', freq='H') tm.assert_index_equal(idx.shift(3), exp) exp = pd.PeriodIndex(['2011-01-01 07:00', '2011-01-01 08:00' '2011-01-01 09:00'], name='xxx', freq='H') tm.assert_index_equal(idx.shift(-3), exp) def test_shift_nat(self): idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'], freq='M', name='idx') result = idx.shift(1) expected = PeriodIndex(['2011-02', '2011-03', 'NaT', '2011-05'], freq='M', name='idx') tm.assert_index_equal(result, expected) assert result.name == expected.name def test_shift_gh8083(self): # test shift for PeriodIndex # GH#8083 drange = pd.period_range('20130101', periods=5, freq='D') result = drange.shift(1) expected = PeriodIndex(['2013-01-02', '2013-01-03', '2013-01-04', '2013-01-05', '2013-01-06'], freq='D') tm.assert_index_equal(result, expected) class TestPeriodIndexSeriesMethods(object): """ Test PeriodIndex and Period Series Ops consistency """ def _check(self, values, func, expected): idx = pd.PeriodIndex(values) result = func(idx) if isinstance(expected, pd.Index): tm.assert_index_equal(result, expected) else: # comp op results in bool tm.assert_numpy_array_equal(result, expected) ser = pd.Series(values) result = func(ser) exp = pd.Series(expected, name=values.name) tm.assert_series_equal(result, exp) def test_pi_ops(self): idx = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'], freq='M', name='idx') expected = PeriodIndex(['2011-03', '2011-04', '2011-05', '2011-06'], freq='M', name='idx') self._check(idx, lambda x: x + 2, expected) self._check(idx, lambda x: 2 + x, expected) self._check(idx + 2, lambda x: x - 2, idx) result = idx - Period('2011-01', freq='M') exp = pd.Index([0, 1, 2, 3], name='idx') tm.assert_index_equal(result, exp) result = Period('2011-01', freq='M') - idx exp = pd.Index([0, -1, -2, -3], name='idx') tm.assert_index_equal(result, exp) @pytest.mark.parametrize('ng', ["str", 1.5]) def test_pi_ops_errors(self, ng): idx = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'], freq='M', name='idx') ser = pd.Series(idx) msg = r"unsupported operand type\(s\)" for obj in [idx, ser]: with tm.assert_raises_regex(TypeError, msg): obj + ng with pytest.raises(TypeError): # error message differs between PY2 and 3 ng + obj with tm.assert_raises_regex(TypeError, msg): obj - ng with pytest.raises(TypeError): np.add(obj, ng) if _np_version_under1p10: assert np.add(ng, obj) is NotImplemented else: with pytest.raises(TypeError): np.add(ng, obj) with pytest.raises(TypeError): np.subtract(obj, ng) if _np_version_under1p10: assert np.subtract(ng, obj) is NotImplemented else: with pytest.raises(TypeError): np.subtract(ng, obj) def test_pi_ops_nat(self): idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'], freq='M', name='idx') expected = PeriodIndex(['2011-03', '2011-04', 'NaT', '2011-06'], freq='M', name='idx') self._check(idx, lambda x: x + 2, expected) self._check(idx, lambda x: 2 + x, expected) self._check(idx, lambda x: np.add(x, 2), expected) self._check(idx + 2, lambda x: x - 2, idx) self._check(idx + 2, lambda x: np.subtract(x, 2), idx) # freq with mult idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'], freq='2M', name='idx') expected = PeriodIndex(['2011-07', '2011-08', 'NaT', '2011-10'], freq='2M', name='idx') self._check(idx, lambda x: x + 3, expected) self._check(idx, lambda x: 3 + x, expected) self._check(idx, lambda x: np.add(x, 3), expected) self._check(idx + 3, lambda x: x - 3, idx) self._check(idx + 3, lambda x: np.subtract(x, 3), idx) def test_pi_ops_array_int(self): idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'], freq='M', name='idx') f = lambda x: x + np.array([1, 2, 3, 4]) exp = PeriodIndex(['2011-02', '2011-04', 'NaT', '2011-08'], freq='M', name='idx') self._check(idx, f, exp) f = lambda x: np.add(x, np.array([4, -1, 1, 2])) exp = PeriodIndex(['2011-05', '2011-01', 'NaT', '2011-06'], freq='M', name='idx') self._check(idx, f, exp) f = lambda x: x - np.array([1, 2, 3, 4]) exp = PeriodIndex(['2010-12', '2010-12', 'NaT', '2010-12'], freq='M', name='idx') self._check(idx, f, exp) f = lambda x: np.subtract(x, np.array([3, 2, 3, -2])) exp = PeriodIndex(['2010-10', '2010-12', 'NaT', '2011-06'], freq='M', name='idx') self._check(idx, f, exp) def test_pi_ops_offset(self): idx = PeriodIndex(['2011-01-01', '2011-02-01', '2011-03-01', '2011-04-01'], freq='D', name='idx') f = lambda x: x + pd.offsets.Day() exp = PeriodIndex(['2011-01-02', '2011-02-02', '2011-03-02', '2011-04-02'], freq='D', name='idx') self._check(idx, f, exp) f = lambda x: x + pd.offsets.Day(2) exp = PeriodIndex(['2011-01-03', '2011-02-03', '2011-03-03', '2011-04-03'], freq='D', name='idx') self._check(idx, f, exp) f = lambda x: x - pd.offsets.Day(2) exp = PeriodIndex(['2010-12-30', '2011-01-30', '2011-02-27', '2011-03-30'], freq='D', name='idx') self._check(idx, f, exp) def test_pi_offset_errors(self): idx = PeriodIndex(['2011-01-01', '2011-02-01', '2011-03-01', '2011-04-01'], freq='D', name='idx') ser = pd.Series(idx) # Series op is applied per Period instance, thus error is raised # from Period msg_idx = r"Input has different freq from PeriodIndex\(freq=D\)" msg_s = r"Input cannot be converted to Period\(freq=D\)" for obj, msg in [(idx, msg_idx), (ser, msg_s)]: with tm.assert_raises_regex(period.IncompatibleFrequency, msg): obj + pd.offsets.Hour(2) with tm.assert_raises_regex(period.IncompatibleFrequency, msg): pd.offsets.Hour(2) + obj with tm.assert_raises_regex(period.IncompatibleFrequency, msg): obj - pd.offsets.Hour(2) def test_pi_sub_period(self): # GH 13071 idx = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'], freq='M', name='idx') result = idx - pd.Period('2012-01', freq='M') exp = pd.Index([-12, -11, -10, -9], name='idx') tm.assert_index_equal(result, exp) result = np.subtract(idx, pd.Period('2012-01', freq='M')) tm.assert_index_equal(result, exp) result = pd.Period('2012-01', freq='M') - idx exp = pd.Index([12, 11, 10, 9], name='idx') tm.assert_index_equal(result, exp) result = np.subtract(pd.Period('2012-01', freq='M'), idx) if _np_version_under1p10: assert result is NotImplemented else: tm.assert_index_equal(result, exp) exp = pd.TimedeltaIndex([np.nan, np.nan, np.nan, np.nan], name='idx') tm.assert_index_equal(idx - pd.Period('NaT', freq='M'), exp) tm.assert_index_equal(pd.Period('NaT', freq='M') - idx, exp) def test_pi_sub_pdnat(self): # GH 13071 idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'], freq='M', name='idx') exp = pd.TimedeltaIndex([pd.NaT] * 4, name='idx') tm.assert_index_equal(pd.NaT - idx, exp) tm.assert_index_equal(idx - pd.NaT, exp) def test_pi_sub_period_nat(self): # GH 13071 idx = PeriodIndex(['2011-01', 'NaT', '2011-03', '2011-04'], freq='M', name='idx') result = idx - pd.Period('2012-01', freq='M') exp = pd.Index([-12, np.nan, -10, -9], name='idx') tm.assert_index_equal(result, exp) result = pd.Period('2012-01', freq='M') - idx exp = pd.Index([12, np.nan, 10, 9], name='idx') tm.assert_index_equal(result, exp) exp = pd.TimedeltaIndex([np.nan, np.nan, np.nan, np.nan], name='idx') tm.assert_index_equal(idx - pd.Period('NaT', freq='M'), exp) tm.assert_index_equal(pd.Period('NaT', freq='M') - idx, exp)