test_period.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213
  1. # -*- coding: utf-8 -*-
  2. # Arithmetc tests for DataFrame/Series/Index/Array classes that should
  3. # behave identically.
  4. # Specifically for Period dtype
  5. import operator
  6. import numpy as np
  7. import pytest
  8. from pandas._libs.tslibs.period import IncompatibleFrequency
  9. from pandas.errors import PerformanceWarning
  10. import pandas as pd
  11. from pandas import Period, PeriodIndex, Series, period_range
  12. from pandas.core import ops
  13. import pandas.util.testing as tm
  14. from pandas.tseries.frequencies import to_offset
  15. # ------------------------------------------------------------------
  16. # Comparisons
  17. class TestPeriodIndexComparisons(object):
  18. @pytest.mark.parametrize("other", ["2017", 2017])
  19. def test_eq(self, other):
  20. idx = PeriodIndex(['2017', '2017', '2018'], freq="D")
  21. expected = np.array([True, True, False])
  22. result = idx == other
  23. tm.assert_numpy_array_equal(result, expected)
  24. def test_pi_cmp_period(self):
  25. idx = period_range('2007-01', periods=20, freq='M')
  26. result = idx < idx[10]
  27. exp = idx.values < idx.values[10]
  28. tm.assert_numpy_array_equal(result, exp)
  29. # TODO: moved from test_datetime64; de-duplicate with version below
  30. def test_parr_cmp_period_scalar2(self, box_with_array):
  31. xbox = box_with_array if box_with_array is not pd.Index else np.ndarray
  32. pi = pd.period_range('2000-01-01', periods=10, freq='D')
  33. val = Period('2000-01-04', freq='D')
  34. expected = [x > val for x in pi]
  35. ser = tm.box_expected(pi, box_with_array)
  36. expected = tm.box_expected(expected, xbox)
  37. result = ser > val
  38. tm.assert_equal(result, expected)
  39. val = pi[5]
  40. result = ser > val
  41. expected = [x > val for x in pi]
  42. expected = tm.box_expected(expected, xbox)
  43. tm.assert_equal(result, expected)
  44. @pytest.mark.parametrize('freq', ['M', '2M', '3M'])
  45. def test_parr_cmp_period_scalar(self, freq, box_with_array):
  46. # GH#13200
  47. xbox = np.ndarray if box_with_array is pd.Index else box_with_array
  48. base = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'],
  49. freq=freq)
  50. base = tm.box_expected(base, box_with_array)
  51. per = Period('2011-02', freq=freq)
  52. exp = np.array([False, True, False, False])
  53. exp = tm.box_expected(exp, xbox)
  54. tm.assert_equal(base == per, exp)
  55. tm.assert_equal(per == base, exp)
  56. exp = np.array([True, False, True, True])
  57. exp = tm.box_expected(exp, xbox)
  58. tm.assert_equal(base != per, exp)
  59. tm.assert_equal(per != base, exp)
  60. exp = np.array([False, False, True, True])
  61. exp = tm.box_expected(exp, xbox)
  62. tm.assert_equal(base > per, exp)
  63. tm.assert_equal(per < base, exp)
  64. exp = np.array([True, False, False, False])
  65. exp = tm.box_expected(exp, xbox)
  66. tm.assert_equal(base < per, exp)
  67. tm.assert_equal(per > base, exp)
  68. exp = np.array([False, True, True, True])
  69. exp = tm.box_expected(exp, xbox)
  70. tm.assert_equal(base >= per, exp)
  71. tm.assert_equal(per <= base, exp)
  72. exp = np.array([True, True, False, False])
  73. exp = tm.box_expected(exp, xbox)
  74. tm.assert_equal(base <= per, exp)
  75. tm.assert_equal(per >= base, exp)
  76. @pytest.mark.parametrize('freq', ['M', '2M', '3M'])
  77. def test_parr_cmp_pi(self, freq, box_with_array):
  78. # GH#13200
  79. xbox = np.ndarray if box_with_array is pd.Index else box_with_array
  80. base = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'],
  81. freq=freq)
  82. base = tm.box_expected(base, box_with_array)
  83. # TODO: could also box idx?
  84. idx = PeriodIndex(['2011-02', '2011-01', '2011-03', '2011-05'],
  85. freq=freq)
  86. exp = np.array([False, False, True, False])
  87. exp = tm.box_expected(exp, xbox)
  88. tm.assert_equal(base == idx, exp)
  89. exp = np.array([True, True, False, True])
  90. exp = tm.box_expected(exp, xbox)
  91. tm.assert_equal(base != idx, exp)
  92. exp = np.array([False, True, False, False])
  93. exp = tm.box_expected(exp, xbox)
  94. tm.assert_equal(base > idx, exp)
  95. exp = np.array([True, False, False, True])
  96. exp = tm.box_expected(exp, xbox)
  97. tm.assert_equal(base < idx, exp)
  98. exp = np.array([False, True, True, False])
  99. exp = tm.box_expected(exp, xbox)
  100. tm.assert_equal(base >= idx, exp)
  101. exp = np.array([True, False, True, True])
  102. exp = tm.box_expected(exp, xbox)
  103. tm.assert_equal(base <= idx, exp)
  104. @pytest.mark.parametrize('freq', ['M', '2M', '3M'])
  105. def test_parr_cmp_pi_mismatched_freq_raises(self, freq, box_with_array):
  106. # GH#13200
  107. # different base freq
  108. base = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'],
  109. freq=freq)
  110. base = tm.box_expected(base, box_with_array)
  111. msg = "Input has different freq=A-DEC from "
  112. with pytest.raises(IncompatibleFrequency, match=msg):
  113. base <= Period('2011', freq='A')
  114. with pytest.raises(IncompatibleFrequency, match=msg):
  115. Period('2011', freq='A') >= base
  116. # TODO: Could parametrize over boxes for idx?
  117. idx = PeriodIndex(['2011', '2012', '2013', '2014'], freq='A')
  118. rev_msg = (r'Input has different freq=(M|2M|3M) from '
  119. r'PeriodArray\(freq=A-DEC\)')
  120. idx_msg = rev_msg if box_with_array is tm.to_array else msg
  121. with pytest.raises(IncompatibleFrequency, match=idx_msg):
  122. base <= idx
  123. # Different frequency
  124. msg = "Input has different freq=4M from "
  125. with pytest.raises(IncompatibleFrequency, match=msg):
  126. base <= Period('2011', freq='4M')
  127. with pytest.raises(IncompatibleFrequency, match=msg):
  128. Period('2011', freq='4M') >= base
  129. idx = PeriodIndex(['2011', '2012', '2013', '2014'], freq='4M')
  130. rev_msg = (r'Input has different freq=(M|2M|3M) from '
  131. r'PeriodArray\(freq=4M\)')
  132. idx_msg = rev_msg if box_with_array is tm.to_array else msg
  133. with pytest.raises(IncompatibleFrequency, match=idx_msg):
  134. base <= idx
  135. @pytest.mark.parametrize('freq', ['M', '2M', '3M'])
  136. def test_pi_cmp_nat(self, freq):
  137. idx1 = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-05'], freq=freq)
  138. result = idx1 > Period('2011-02', freq=freq)
  139. exp = np.array([False, False, False, True])
  140. tm.assert_numpy_array_equal(result, exp)
  141. result = Period('2011-02', freq=freq) < idx1
  142. tm.assert_numpy_array_equal(result, exp)
  143. result = idx1 == Period('NaT', freq=freq)
  144. exp = np.array([False, False, False, False])
  145. tm.assert_numpy_array_equal(result, exp)
  146. result = Period('NaT', freq=freq) == idx1
  147. tm.assert_numpy_array_equal(result, exp)
  148. result = idx1 != Period('NaT', freq=freq)
  149. exp = np.array([True, True, True, True])
  150. tm.assert_numpy_array_equal(result, exp)
  151. result = Period('NaT', freq=freq) != idx1
  152. tm.assert_numpy_array_equal(result, exp)
  153. idx2 = PeriodIndex(['2011-02', '2011-01', '2011-04', 'NaT'], freq=freq)
  154. result = idx1 < idx2
  155. exp = np.array([True, False, False, False])
  156. tm.assert_numpy_array_equal(result, exp)
  157. result = idx1 == idx2
  158. exp = np.array([False, False, False, False])
  159. tm.assert_numpy_array_equal(result, exp)
  160. result = idx1 != idx2
  161. exp = np.array([True, True, True, True])
  162. tm.assert_numpy_array_equal(result, exp)
  163. result = idx1 == idx1
  164. exp = np.array([True, True, False, True])
  165. tm.assert_numpy_array_equal(result, exp)
  166. result = idx1 != idx1
  167. exp = np.array([False, False, True, False])
  168. tm.assert_numpy_array_equal(result, exp)
  169. @pytest.mark.parametrize('freq', ['M', '2M', '3M'])
  170. def test_pi_cmp_nat_mismatched_freq_raises(self, freq):
  171. idx1 = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-05'], freq=freq)
  172. diff = PeriodIndex(['2011-02', '2011-01', '2011-04', 'NaT'], freq='4M')
  173. msg = "Input has different freq=4M from Period(Array|Index)"
  174. with pytest.raises(IncompatibleFrequency, match=msg):
  175. idx1 > diff
  176. with pytest.raises(IncompatibleFrequency, match=msg):
  177. idx1 == diff
  178. # TODO: De-duplicate with test_pi_cmp_nat
  179. @pytest.mark.parametrize('dtype', [object, None])
  180. def test_comp_nat(self, dtype):
  181. left = pd.PeriodIndex([pd.Period('2011-01-01'), pd.NaT,
  182. pd.Period('2011-01-03')])
  183. right = pd.PeriodIndex([pd.NaT, pd.NaT, pd.Period('2011-01-03')])
  184. if dtype is not None:
  185. left = left.astype(dtype)
  186. right = right.astype(dtype)
  187. result = left == right
  188. expected = np.array([False, False, True])
  189. tm.assert_numpy_array_equal(result, expected)
  190. result = left != right
  191. expected = np.array([True, True, False])
  192. tm.assert_numpy_array_equal(result, expected)
  193. expected = np.array([False, False, False])
  194. tm.assert_numpy_array_equal(left == pd.NaT, expected)
  195. tm.assert_numpy_array_equal(pd.NaT == right, expected)
  196. expected = np.array([True, True, True])
  197. tm.assert_numpy_array_equal(left != pd.NaT, expected)
  198. tm.assert_numpy_array_equal(pd.NaT != left, expected)
  199. expected = np.array([False, False, False])
  200. tm.assert_numpy_array_equal(left < pd.NaT, expected)
  201. tm.assert_numpy_array_equal(pd.NaT > left, expected)
  202. class TestPeriodSeriesComparisons(object):
  203. def test_cmp_series_period_series_mixed_freq(self):
  204. # GH#13200
  205. base = Series([Period('2011', freq='A'),
  206. Period('2011-02', freq='M'),
  207. Period('2013', freq='A'),
  208. Period('2011-04', freq='M')])
  209. ser = Series([Period('2012', freq='A'),
  210. Period('2011-01', freq='M'),
  211. Period('2013', freq='A'),
  212. Period('2011-05', freq='M')])
  213. exp = Series([False, False, True, False])
  214. tm.assert_series_equal(base == ser, exp)
  215. exp = Series([True, True, False, True])
  216. tm.assert_series_equal(base != ser, exp)
  217. exp = Series([False, True, False, False])
  218. tm.assert_series_equal(base > ser, exp)
  219. exp = Series([True, False, False, True])
  220. tm.assert_series_equal(base < ser, exp)
  221. exp = Series([False, True, True, False])
  222. tm.assert_series_equal(base >= ser, exp)
  223. exp = Series([True, False, True, True])
  224. tm.assert_series_equal(base <= ser, exp)
  225. class TestPeriodIndexSeriesComparisonConsistency(object):
  226. """ Test PeriodIndex and Period Series Ops consistency """
  227. # TODO: needs parametrization+de-duplication
  228. def _check(self, values, func, expected):
  229. # Test PeriodIndex and Period Series Ops consistency
  230. idx = pd.PeriodIndex(values)
  231. result = func(idx)
  232. # check that we don't pass an unwanted type to tm.assert_equal
  233. assert isinstance(expected, (pd.Index, np.ndarray))
  234. tm.assert_equal(result, expected)
  235. s = pd.Series(values)
  236. result = func(s)
  237. exp = pd.Series(expected, name=values.name)
  238. tm.assert_series_equal(result, exp)
  239. def test_pi_comp_period(self):
  240. idx = PeriodIndex(['2011-01', '2011-02', '2011-03',
  241. '2011-04'], freq='M', name='idx')
  242. f = lambda x: x == pd.Period('2011-03', freq='M')
  243. exp = np.array([False, False, True, False], dtype=np.bool)
  244. self._check(idx, f, exp)
  245. f = lambda x: pd.Period('2011-03', freq='M') == x
  246. self._check(idx, f, exp)
  247. f = lambda x: x != pd.Period('2011-03', freq='M')
  248. exp = np.array([True, True, False, True], dtype=np.bool)
  249. self._check(idx, f, exp)
  250. f = lambda x: pd.Period('2011-03', freq='M') != x
  251. self._check(idx, f, exp)
  252. f = lambda x: pd.Period('2011-03', freq='M') >= x
  253. exp = np.array([True, True, True, False], dtype=np.bool)
  254. self._check(idx, f, exp)
  255. f = lambda x: x > pd.Period('2011-03', freq='M')
  256. exp = np.array([False, False, False, True], dtype=np.bool)
  257. self._check(idx, f, exp)
  258. f = lambda x: pd.Period('2011-03', freq='M') >= x
  259. exp = np.array([True, True, True, False], dtype=np.bool)
  260. self._check(idx, f, exp)
  261. def test_pi_comp_period_nat(self):
  262. idx = PeriodIndex(['2011-01', 'NaT', '2011-03',
  263. '2011-04'], freq='M', name='idx')
  264. f = lambda x: x == pd.Period('2011-03', freq='M')
  265. exp = np.array([False, False, True, False], dtype=np.bool)
  266. self._check(idx, f, exp)
  267. f = lambda x: pd.Period('2011-03', freq='M') == x
  268. self._check(idx, f, exp)
  269. f = lambda x: x == pd.NaT
  270. exp = np.array([False, False, False, False], dtype=np.bool)
  271. self._check(idx, f, exp)
  272. f = lambda x: pd.NaT == x
  273. self._check(idx, f, exp)
  274. f = lambda x: x != pd.Period('2011-03', freq='M')
  275. exp = np.array([True, True, False, True], dtype=np.bool)
  276. self._check(idx, f, exp)
  277. f = lambda x: pd.Period('2011-03', freq='M') != x
  278. self._check(idx, f, exp)
  279. f = lambda x: x != pd.NaT
  280. exp = np.array([True, True, True, True], dtype=np.bool)
  281. self._check(idx, f, exp)
  282. f = lambda x: pd.NaT != x
  283. self._check(idx, f, exp)
  284. f = lambda x: pd.Period('2011-03', freq='M') >= x
  285. exp = np.array([True, False, True, False], dtype=np.bool)
  286. self._check(idx, f, exp)
  287. f = lambda x: x < pd.Period('2011-03', freq='M')
  288. exp = np.array([True, False, False, False], dtype=np.bool)
  289. self._check(idx, f, exp)
  290. f = lambda x: x > pd.NaT
  291. exp = np.array([False, False, False, False], dtype=np.bool)
  292. self._check(idx, f, exp)
  293. f = lambda x: pd.NaT >= x
  294. exp = np.array([False, False, False, False], dtype=np.bool)
  295. self._check(idx, f, exp)
  296. # ------------------------------------------------------------------
  297. # Arithmetic
  298. class TestPeriodFrameArithmetic(object):
  299. def test_ops_frame_period(self):
  300. # GH#13043
  301. df = pd.DataFrame({'A': [pd.Period('2015-01', freq='M'),
  302. pd.Period('2015-02', freq='M')],
  303. 'B': [pd.Period('2014-01', freq='M'),
  304. pd.Period('2014-02', freq='M')]})
  305. assert df['A'].dtype == 'Period[M]'
  306. assert df['B'].dtype == 'Period[M]'
  307. p = pd.Period('2015-03', freq='M')
  308. off = p.freq
  309. # dtype will be object because of original dtype
  310. exp = pd.DataFrame({'A': np.array([2 * off, 1 * off], dtype=object),
  311. 'B': np.array([14 * off, 13 * off], dtype=object)})
  312. tm.assert_frame_equal(p - df, exp)
  313. tm.assert_frame_equal(df - p, -1 * exp)
  314. df2 = pd.DataFrame({'A': [pd.Period('2015-05', freq='M'),
  315. pd.Period('2015-06', freq='M')],
  316. 'B': [pd.Period('2015-05', freq='M'),
  317. pd.Period('2015-06', freq='M')]})
  318. assert df2['A'].dtype == 'Period[M]'
  319. assert df2['B'].dtype == 'Period[M]'
  320. exp = pd.DataFrame({'A': np.array([4 * off, 4 * off], dtype=object),
  321. 'B': np.array([16 * off, 16 * off], dtype=object)})
  322. tm.assert_frame_equal(df2 - df, exp)
  323. tm.assert_frame_equal(df - df2, -1 * exp)
  324. class TestPeriodIndexArithmetic(object):
  325. # ---------------------------------------------------------------
  326. # __add__/__sub__ with PeriodIndex
  327. # PeriodIndex + other is defined for integers and timedelta-like others
  328. # PeriodIndex - other is defined for integers, timedelta-like others,
  329. # and PeriodIndex (with matching freq)
  330. def test_parr_add_iadd_parr_raises(self, box_with_array):
  331. rng = pd.period_range('1/1/2000', freq='D', periods=5)
  332. other = pd.period_range('1/6/2000', freq='D', periods=5)
  333. # TODO: parametrize over boxes for other?
  334. rng = tm.box_expected(rng, box_with_array)
  335. # An earlier implementation of PeriodIndex addition performed
  336. # a set operation (union). This has since been changed to
  337. # raise a TypeError. See GH#14164 and GH#13077 for historical
  338. # reference.
  339. with pytest.raises(TypeError):
  340. rng + other
  341. with pytest.raises(TypeError):
  342. rng += other
  343. def test_pi_sub_isub_pi(self):
  344. # GH#20049
  345. # For historical reference see GH#14164, GH#13077.
  346. # PeriodIndex subtraction originally performed set difference,
  347. # then changed to raise TypeError before being implemented in GH#20049
  348. rng = pd.period_range('1/1/2000', freq='D', periods=5)
  349. other = pd.period_range('1/6/2000', freq='D', periods=5)
  350. off = rng.freq
  351. expected = pd.Index([-5 * off] * 5)
  352. result = rng - other
  353. tm.assert_index_equal(result, expected)
  354. rng -= other
  355. tm.assert_index_equal(rng, expected)
  356. def test_pi_sub_pi_with_nat(self):
  357. rng = pd.period_range('1/1/2000', freq='D', periods=5)
  358. other = rng[1:].insert(0, pd.NaT)
  359. assert other[1:].equals(rng[1:])
  360. result = rng - other
  361. off = rng.freq
  362. expected = pd.Index([pd.NaT, 0 * off, 0 * off, 0 * off, 0 * off])
  363. tm.assert_index_equal(result, expected)
  364. def test_parr_sub_pi_mismatched_freq(self, box_with_array):
  365. rng = pd.period_range('1/1/2000', freq='D', periods=5)
  366. other = pd.period_range('1/6/2000', freq='H', periods=5)
  367. # TODO: parametrize over boxes for other?
  368. rng = tm.box_expected(rng, box_with_array)
  369. with pytest.raises(IncompatibleFrequency):
  370. rng - other
  371. @pytest.mark.parametrize('n', [1, 2, 3, 4])
  372. def test_sub_n_gt_1_ticks(self, tick_classes, n):
  373. # GH 23878
  374. p1_d = '19910905'
  375. p2_d = '19920406'
  376. p1 = pd.PeriodIndex([p1_d], freq=tick_classes(n))
  377. p2 = pd.PeriodIndex([p2_d], freq=tick_classes(n))
  378. expected = (pd.PeriodIndex([p2_d], freq=p2.freq.base)
  379. - pd.PeriodIndex([p1_d], freq=p1.freq.base))
  380. tm.assert_index_equal((p2 - p1), expected)
  381. @pytest.mark.parametrize('n', [1, 2, 3, 4])
  382. @pytest.mark.parametrize('offset, kwd_name', [
  383. (pd.offsets.YearEnd, 'month'),
  384. (pd.offsets.QuarterEnd, 'startingMonth'),
  385. (pd.offsets.MonthEnd, None),
  386. (pd.offsets.Week, 'weekday')
  387. ])
  388. def test_sub_n_gt_1_offsets(self, offset, kwd_name, n):
  389. # GH 23878
  390. kwds = {kwd_name: 3} if kwd_name is not None else {}
  391. p1_d = '19910905'
  392. p2_d = '19920406'
  393. freq = offset(n, normalize=False, **kwds)
  394. p1 = pd.PeriodIndex([p1_d], freq=freq)
  395. p2 = pd.PeriodIndex([p2_d], freq=freq)
  396. result = p2 - p1
  397. expected = (pd.PeriodIndex([p2_d], freq=freq.base)
  398. - pd.PeriodIndex([p1_d], freq=freq.base))
  399. tm.assert_index_equal(result, expected)
  400. # -------------------------------------------------------------
  401. # Invalid Operations
  402. @pytest.mark.parametrize('other', [3.14, np.array([2.0, 3.0])])
  403. @pytest.mark.parametrize('op', [operator.add, ops.radd,
  404. operator.sub, ops.rsub])
  405. def test_parr_add_sub_float_raises(self, op, other, box_with_array):
  406. dti = pd.DatetimeIndex(['2011-01-01', '2011-01-02'], freq='D')
  407. pi = dti.to_period('D')
  408. pi = tm.box_expected(pi, box_with_array)
  409. with pytest.raises(TypeError):
  410. op(pi, other)
  411. @pytest.mark.parametrize('other', [pd.Timestamp.now(),
  412. pd.Timestamp.now().to_pydatetime(),
  413. pd.Timestamp.now().to_datetime64()])
  414. def test_parr_add_sub_datetime_scalar(self, other, box_with_array):
  415. # GH#23215
  416. rng = pd.period_range('1/1/2000', freq='D', periods=3)
  417. rng = tm.box_expected(rng, box_with_array)
  418. with pytest.raises(TypeError):
  419. rng + other
  420. with pytest.raises(TypeError):
  421. other + rng
  422. with pytest.raises(TypeError):
  423. rng - other
  424. with pytest.raises(TypeError):
  425. other - rng
  426. # -----------------------------------------------------------------
  427. # __add__/__sub__ with ndarray[datetime64] and ndarray[timedelta64]
  428. def test_parr_add_sub_dt64_array_raises(self, box_with_array):
  429. rng = pd.period_range('1/1/2000', freq='D', periods=3)
  430. dti = pd.date_range('2016-01-01', periods=3)
  431. dtarr = dti.values
  432. rng = tm.box_expected(rng, box_with_array)
  433. with pytest.raises(TypeError):
  434. rng + dtarr
  435. with pytest.raises(TypeError):
  436. dtarr + rng
  437. with pytest.raises(TypeError):
  438. rng - dtarr
  439. with pytest.raises(TypeError):
  440. dtarr - rng
  441. def test_pi_add_sub_td64_array_non_tick_raises(self):
  442. rng = pd.period_range('1/1/2000', freq='Q', periods=3)
  443. tdi = pd.TimedeltaIndex(['-1 Day', '-1 Day', '-1 Day'])
  444. tdarr = tdi.values
  445. with pytest.raises(IncompatibleFrequency):
  446. rng + tdarr
  447. with pytest.raises(IncompatibleFrequency):
  448. tdarr + rng
  449. with pytest.raises(IncompatibleFrequency):
  450. rng - tdarr
  451. with pytest.raises(TypeError):
  452. tdarr - rng
  453. def test_pi_add_sub_td64_array_tick(self):
  454. # PeriodIndex + Timedelta-like is allowed only with
  455. # tick-like frequencies
  456. rng = pd.period_range('1/1/2000', freq='90D', periods=3)
  457. tdi = pd.TimedeltaIndex(['-1 Day', '-1 Day', '-1 Day'])
  458. tdarr = tdi.values
  459. expected = pd.period_range('12/31/1999', freq='90D', periods=3)
  460. result = rng + tdi
  461. tm.assert_index_equal(result, expected)
  462. result = rng + tdarr
  463. tm.assert_index_equal(result, expected)
  464. result = tdi + rng
  465. tm.assert_index_equal(result, expected)
  466. result = tdarr + rng
  467. tm.assert_index_equal(result, expected)
  468. expected = pd.period_range('1/2/2000', freq='90D', periods=3)
  469. result = rng - tdi
  470. tm.assert_index_equal(result, expected)
  471. result = rng - tdarr
  472. tm.assert_index_equal(result, expected)
  473. with pytest.raises(TypeError):
  474. tdarr - rng
  475. with pytest.raises(TypeError):
  476. tdi - rng
  477. # -----------------------------------------------------------------
  478. # operations with array/Index of DateOffset objects
  479. @pytest.mark.parametrize('box', [np.array, pd.Index])
  480. def test_pi_add_offset_array(self, box):
  481. # GH#18849
  482. pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('2016Q2')])
  483. offs = box([pd.offsets.QuarterEnd(n=1, startingMonth=12),
  484. pd.offsets.QuarterEnd(n=-2, startingMonth=12)])
  485. expected = pd.PeriodIndex([pd.Period('2015Q2'), pd.Period('2015Q4')])
  486. with tm.assert_produces_warning(PerformanceWarning):
  487. res = pi + offs
  488. tm.assert_index_equal(res, expected)
  489. with tm.assert_produces_warning(PerformanceWarning):
  490. res2 = offs + pi
  491. tm.assert_index_equal(res2, expected)
  492. unanchored = np.array([pd.offsets.Hour(n=1),
  493. pd.offsets.Minute(n=-2)])
  494. # addition/subtraction ops with incompatible offsets should issue
  495. # a PerformanceWarning and _then_ raise a TypeError.
  496. with pytest.raises(IncompatibleFrequency):
  497. with tm.assert_produces_warning(PerformanceWarning):
  498. pi + unanchored
  499. with pytest.raises(IncompatibleFrequency):
  500. with tm.assert_produces_warning(PerformanceWarning):
  501. unanchored + pi
  502. @pytest.mark.parametrize('box', [np.array, pd.Index])
  503. def test_pi_sub_offset_array(self, box):
  504. # GH#18824
  505. pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('2016Q2')])
  506. other = box([pd.offsets.QuarterEnd(n=1, startingMonth=12),
  507. pd.offsets.QuarterEnd(n=-2, startingMonth=12)])
  508. expected = PeriodIndex([pi[n] - other[n] for n in range(len(pi))])
  509. with tm.assert_produces_warning(PerformanceWarning):
  510. res = pi - other
  511. tm.assert_index_equal(res, expected)
  512. anchored = box([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)])
  513. # addition/subtraction ops with anchored offsets should issue
  514. # a PerformanceWarning and _then_ raise a TypeError.
  515. with pytest.raises(IncompatibleFrequency):
  516. with tm.assert_produces_warning(PerformanceWarning):
  517. pi - anchored
  518. with pytest.raises(IncompatibleFrequency):
  519. with tm.assert_produces_warning(PerformanceWarning):
  520. anchored - pi
  521. def test_pi_add_iadd_int(self, one):
  522. # Variants of `one` for #19012
  523. rng = pd.period_range('2000-01-01 09:00', freq='H', periods=10)
  524. result = rng + one
  525. expected = pd.period_range('2000-01-01 10:00', freq='H', periods=10)
  526. tm.assert_index_equal(result, expected)
  527. rng += one
  528. tm.assert_index_equal(rng, expected)
  529. def test_pi_sub_isub_int(self, one):
  530. """
  531. PeriodIndex.__sub__ and __isub__ with several representations of
  532. the integer 1, e.g. int, long, np.int64, np.uint8, ...
  533. """
  534. rng = pd.period_range('2000-01-01 09:00', freq='H', periods=10)
  535. result = rng - one
  536. expected = pd.period_range('2000-01-01 08:00', freq='H', periods=10)
  537. tm.assert_index_equal(result, expected)
  538. rng -= one
  539. tm.assert_index_equal(rng, expected)
  540. @pytest.mark.parametrize('five', [5, np.array(5, dtype=np.int64)])
  541. def test_pi_sub_intlike(self, five):
  542. rng = period_range('2007-01', periods=50)
  543. result = rng - five
  544. exp = rng + (-five)
  545. tm.assert_index_equal(result, exp)
  546. def test_pi_sub_isub_offset(self):
  547. # offset
  548. # DateOffset
  549. rng = pd.period_range('2014', '2024', freq='A')
  550. result = rng - pd.offsets.YearEnd(5)
  551. expected = pd.period_range('2009', '2019', freq='A')
  552. tm.assert_index_equal(result, expected)
  553. rng -= pd.offsets.YearEnd(5)
  554. tm.assert_index_equal(rng, expected)
  555. rng = pd.period_range('2014-01', '2016-12', freq='M')
  556. result = rng - pd.offsets.MonthEnd(5)
  557. expected = pd.period_range('2013-08', '2016-07', freq='M')
  558. tm.assert_index_equal(result, expected)
  559. rng -= pd.offsets.MonthEnd(5)
  560. tm.assert_index_equal(rng, expected)
  561. def test_pi_add_offset_n_gt1(self, box_transpose_fail):
  562. # GH#23215
  563. # add offset to PeriodIndex with freq.n > 1
  564. box, transpose = box_transpose_fail
  565. per = pd.Period('2016-01', freq='2M')
  566. pi = pd.PeriodIndex([per])
  567. expected = pd.PeriodIndex(['2016-03'], freq='2M')
  568. pi = tm.box_expected(pi, box, transpose=transpose)
  569. expected = tm.box_expected(expected, box, transpose=transpose)
  570. result = pi + per.freq
  571. tm.assert_equal(result, expected)
  572. result = per.freq + pi
  573. tm.assert_equal(result, expected)
  574. def test_pi_add_offset_n_gt1_not_divisible(self, box_with_array):
  575. # GH#23215
  576. # PeriodIndex with freq.n > 1 add offset with offset.n % freq.n != 0
  577. pi = pd.PeriodIndex(['2016-01'], freq='2M')
  578. expected = pd.PeriodIndex(['2016-04'], freq='2M')
  579. # FIXME: with transposing these tests fail
  580. pi = tm.box_expected(pi, box_with_array, transpose=False)
  581. expected = tm.box_expected(expected, box_with_array, transpose=False)
  582. result = pi + to_offset('3M')
  583. tm.assert_equal(result, expected)
  584. result = to_offset('3M') + pi
  585. tm.assert_equal(result, expected)
  586. # ---------------------------------------------------------------
  587. # __add__/__sub__ with integer arrays
  588. @pytest.mark.parametrize('int_holder', [np.array, pd.Index])
  589. @pytest.mark.parametrize('op', [operator.add, ops.radd])
  590. def test_pi_add_intarray(self, int_holder, op):
  591. # GH#19959
  592. pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('NaT')])
  593. other = int_holder([4, -1])
  594. result = op(pi, other)
  595. expected = pd.PeriodIndex([pd.Period('2016Q1'), pd.Period('NaT')])
  596. tm.assert_index_equal(result, expected)
  597. @pytest.mark.parametrize('int_holder', [np.array, pd.Index])
  598. def test_pi_sub_intarray(self, int_holder):
  599. # GH#19959
  600. pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('NaT')])
  601. other = int_holder([4, -1])
  602. result = pi - other
  603. expected = pd.PeriodIndex([pd.Period('2014Q1'), pd.Period('NaT')])
  604. tm.assert_index_equal(result, expected)
  605. with pytest.raises(TypeError):
  606. other - pi
  607. # ---------------------------------------------------------------
  608. # Timedelta-like (timedelta, timedelta64, Timedelta, Tick)
  609. # TODO: Some of these are misnomers because of non-Tick DateOffsets
  610. def test_pi_add_timedeltalike_minute_gt1(self, three_days):
  611. # GH#23031 adding a time-delta-like offset to a PeriodArray that has
  612. # minute frequency with n != 1. A more general case is tested below
  613. # in test_pi_add_timedeltalike_tick_gt1, but here we write out the
  614. # expected result more explicitly.
  615. other = three_days
  616. rng = pd.period_range('2014-05-01', periods=3, freq='2D')
  617. expected = pd.PeriodIndex(['2014-05-04', '2014-05-06', '2014-05-08'],
  618. freq='2D')
  619. result = rng + other
  620. tm.assert_index_equal(result, expected)
  621. result = other + rng
  622. tm.assert_index_equal(result, expected)
  623. # subtraction
  624. expected = pd.PeriodIndex(['2014-04-28', '2014-04-30', '2014-05-02'],
  625. freq='2D')
  626. result = rng - other
  627. tm.assert_index_equal(result, expected)
  628. with pytest.raises(TypeError):
  629. other - rng
  630. @pytest.mark.parametrize('freqstr', ['5ns', '5us', '5ms',
  631. '5s', '5T', '5h', '5d'])
  632. def test_pi_add_timedeltalike_tick_gt1(self, three_days, freqstr):
  633. # GH#23031 adding a time-delta-like offset to a PeriodArray that has
  634. # tick-like frequency with n != 1
  635. other = three_days
  636. rng = pd.period_range('2014-05-01', periods=6, freq=freqstr)
  637. expected = pd.period_range(rng[0] + other, periods=6, freq=freqstr)
  638. result = rng + other
  639. tm.assert_index_equal(result, expected)
  640. result = other + rng
  641. tm.assert_index_equal(result, expected)
  642. # subtraction
  643. expected = pd.period_range(rng[0] - other, periods=6, freq=freqstr)
  644. result = rng - other
  645. tm.assert_index_equal(result, expected)
  646. with pytest.raises(TypeError):
  647. other - rng
  648. def test_pi_add_iadd_timedeltalike_daily(self, three_days):
  649. # Tick
  650. other = three_days
  651. rng = pd.period_range('2014-05-01', '2014-05-15', freq='D')
  652. expected = pd.period_range('2014-05-04', '2014-05-18', freq='D')
  653. result = rng + other
  654. tm.assert_index_equal(result, expected)
  655. rng += other
  656. tm.assert_index_equal(rng, expected)
  657. def test_pi_sub_isub_timedeltalike_daily(self, three_days):
  658. # Tick-like 3 Days
  659. other = three_days
  660. rng = pd.period_range('2014-05-01', '2014-05-15', freq='D')
  661. expected = pd.period_range('2014-04-28', '2014-05-12', freq='D')
  662. result = rng - other
  663. tm.assert_index_equal(result, expected)
  664. rng -= other
  665. tm.assert_index_equal(rng, expected)
  666. def test_pi_add_sub_timedeltalike_freq_mismatch_daily(self, not_daily):
  667. other = not_daily
  668. rng = pd.period_range('2014-05-01', '2014-05-15', freq='D')
  669. msg = 'Input has different freq(=.+)? from Period.*?\\(freq=D\\)'
  670. with pytest.raises(IncompatibleFrequency, match=msg):
  671. rng + other
  672. with pytest.raises(IncompatibleFrequency, match=msg):
  673. rng += other
  674. with pytest.raises(IncompatibleFrequency, match=msg):
  675. rng - other
  676. with pytest.raises(IncompatibleFrequency, match=msg):
  677. rng -= other
  678. def test_pi_add_iadd_timedeltalike_hourly(self, two_hours):
  679. other = two_hours
  680. rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00', freq='H')
  681. expected = pd.period_range('2014-01-01 12:00', '2014-01-05 12:00',
  682. freq='H')
  683. result = rng + other
  684. tm.assert_index_equal(result, expected)
  685. rng += other
  686. tm.assert_index_equal(rng, expected)
  687. def test_pi_add_timedeltalike_mismatched_freq_hourly(self, not_hourly):
  688. other = not_hourly
  689. rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00', freq='H')
  690. msg = 'Input has different freq(=.+)? from Period.*?\\(freq=H\\)'
  691. with pytest.raises(IncompatibleFrequency, match=msg):
  692. rng + other
  693. with pytest.raises(IncompatibleFrequency, match=msg):
  694. rng += other
  695. def test_pi_sub_isub_timedeltalike_hourly(self, two_hours):
  696. other = two_hours
  697. rng = pd.period_range('2014-01-01 10:00', '2014-01-05 10:00', freq='H')
  698. expected = pd.period_range('2014-01-01 08:00', '2014-01-05 08:00',
  699. freq='H')
  700. result = rng - other
  701. tm.assert_index_equal(result, expected)
  702. rng -= other
  703. tm.assert_index_equal(rng, expected)
  704. def test_add_iadd_timedeltalike_annual(self):
  705. # offset
  706. # DateOffset
  707. rng = pd.period_range('2014', '2024', freq='A')
  708. result = rng + pd.offsets.YearEnd(5)
  709. expected = pd.period_range('2019', '2029', freq='A')
  710. tm.assert_index_equal(result, expected)
  711. rng += pd.offsets.YearEnd(5)
  712. tm.assert_index_equal(rng, expected)
  713. def test_pi_add_sub_timedeltalike_freq_mismatch_annual(self,
  714. mismatched_freq):
  715. other = mismatched_freq
  716. rng = pd.period_range('2014', '2024', freq='A')
  717. msg = ('Input has different freq(=.+)? '
  718. 'from Period.*?\\(freq=A-DEC\\)')
  719. with pytest.raises(IncompatibleFrequency, match=msg):
  720. rng + other
  721. with pytest.raises(IncompatibleFrequency, match=msg):
  722. rng += other
  723. with pytest.raises(IncompatibleFrequency, match=msg):
  724. rng - other
  725. with pytest.raises(IncompatibleFrequency, match=msg):
  726. rng -= other
  727. def test_pi_add_iadd_timedeltalike_M(self):
  728. rng = pd.period_range('2014-01', '2016-12', freq='M')
  729. expected = pd.period_range('2014-06', '2017-05', freq='M')
  730. result = rng + pd.offsets.MonthEnd(5)
  731. tm.assert_index_equal(result, expected)
  732. rng += pd.offsets.MonthEnd(5)
  733. tm.assert_index_equal(rng, expected)
  734. def test_pi_add_sub_timedeltalike_freq_mismatch_monthly(self,
  735. mismatched_freq):
  736. other = mismatched_freq
  737. rng = pd.period_range('2014-01', '2016-12', freq='M')
  738. msg = 'Input has different freq(=.+)? from Period.*?\\(freq=M\\)'
  739. with pytest.raises(IncompatibleFrequency, match=msg):
  740. rng + other
  741. with pytest.raises(IncompatibleFrequency, match=msg):
  742. rng += other
  743. with pytest.raises(IncompatibleFrequency, match=msg):
  744. rng - other
  745. with pytest.raises(IncompatibleFrequency, match=msg):
  746. rng -= other
  747. def test_parr_add_sub_td64_nat(self, box_transpose_fail):
  748. # GH#23320 special handling for timedelta64("NaT")
  749. box, transpose = box_transpose_fail
  750. pi = pd.period_range("1994-04-01", periods=9, freq="19D")
  751. other = np.timedelta64("NaT")
  752. expected = pd.PeriodIndex(["NaT"] * 9, freq="19D")
  753. obj = tm.box_expected(pi, box, transpose=transpose)
  754. expected = tm.box_expected(expected, box, transpose=transpose)
  755. result = obj + other
  756. tm.assert_equal(result, expected)
  757. result = other + obj
  758. tm.assert_equal(result, expected)
  759. result = obj - other
  760. tm.assert_equal(result, expected)
  761. with pytest.raises(TypeError):
  762. other - obj
  763. class TestPeriodSeriesArithmetic(object):
  764. def test_ops_series_timedelta(self):
  765. # GH#13043
  766. ser = pd.Series([pd.Period('2015-01-01', freq='D'),
  767. pd.Period('2015-01-02', freq='D')], name='xxx')
  768. assert ser.dtype == 'Period[D]'
  769. expected = pd.Series([pd.Period('2015-01-02', freq='D'),
  770. pd.Period('2015-01-03', freq='D')], name='xxx')
  771. result = ser + pd.Timedelta('1 days')
  772. tm.assert_series_equal(result, expected)
  773. result = pd.Timedelta('1 days') + ser
  774. tm.assert_series_equal(result, expected)
  775. result = ser + pd.tseries.offsets.Day()
  776. tm.assert_series_equal(result, expected)
  777. result = pd.tseries.offsets.Day() + ser
  778. tm.assert_series_equal(result, expected)
  779. def test_ops_series_period(self):
  780. # GH#13043
  781. ser = pd.Series([pd.Period('2015-01-01', freq='D'),
  782. pd.Period('2015-01-02', freq='D')], name='xxx')
  783. assert ser.dtype == "Period[D]"
  784. per = pd.Period('2015-01-10', freq='D')
  785. off = per.freq
  786. # dtype will be object because of original dtype
  787. expected = pd.Series([9 * off, 8 * off], name='xxx', dtype=object)
  788. tm.assert_series_equal(per - ser, expected)
  789. tm.assert_series_equal(ser - per, -1 * expected)
  790. s2 = pd.Series([pd.Period('2015-01-05', freq='D'),
  791. pd.Period('2015-01-04', freq='D')], name='xxx')
  792. assert s2.dtype == "Period[D]"
  793. expected = pd.Series([4 * off, 2 * off], name='xxx', dtype=object)
  794. tm.assert_series_equal(s2 - ser, expected)
  795. tm.assert_series_equal(ser - s2, -1 * expected)
  796. class TestPeriodIndexSeriesMethods(object):
  797. """ Test PeriodIndex and Period Series Ops consistency """
  798. def _check(self, values, func, expected):
  799. idx = pd.PeriodIndex(values)
  800. result = func(idx)
  801. tm.assert_equal(result, expected)
  802. ser = pd.Series(values)
  803. result = func(ser)
  804. exp = pd.Series(expected, name=values.name)
  805. tm.assert_series_equal(result, exp)
  806. def test_pi_ops(self):
  807. idx = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'],
  808. freq='M', name='idx')
  809. expected = PeriodIndex(['2011-03', '2011-04', '2011-05', '2011-06'],
  810. freq='M', name='idx')
  811. self._check(idx, lambda x: x + 2, expected)
  812. self._check(idx, lambda x: 2 + x, expected)
  813. self._check(idx + 2, lambda x: x - 2, idx)
  814. result = idx - Period('2011-01', freq='M')
  815. off = idx.freq
  816. exp = pd.Index([0 * off, 1 * off, 2 * off, 3 * off], name='idx')
  817. tm.assert_index_equal(result, exp)
  818. result = Period('2011-01', freq='M') - idx
  819. exp = pd.Index([0 * off, -1 * off, -2 * off, -3 * off], name='idx')
  820. tm.assert_index_equal(result, exp)
  821. @pytest.mark.parametrize('ng', ["str", 1.5])
  822. def test_parr_ops_errors(self, ng, box_with_array):
  823. idx = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'],
  824. freq='M', name='idx')
  825. obj = tm.box_expected(idx, box_with_array)
  826. msg = r"unsupported operand type\(s\)"
  827. with pytest.raises(TypeError, match=msg):
  828. obj + ng
  829. with pytest.raises(TypeError):
  830. # error message differs between PY2 and 3
  831. ng + obj
  832. with pytest.raises(TypeError, match=msg):
  833. obj - ng
  834. with pytest.raises(TypeError):
  835. np.add(obj, ng)
  836. with pytest.raises(TypeError):
  837. np.add(ng, obj)
  838. with pytest.raises(TypeError):
  839. np.subtract(obj, ng)
  840. with pytest.raises(TypeError):
  841. np.subtract(ng, obj)
  842. def test_pi_ops_nat(self):
  843. idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
  844. freq='M', name='idx')
  845. expected = PeriodIndex(['2011-03', '2011-04', 'NaT', '2011-06'],
  846. freq='M', name='idx')
  847. self._check(idx, lambda x: x + 2, expected)
  848. self._check(idx, lambda x: 2 + x, expected)
  849. self._check(idx, lambda x: np.add(x, 2), expected)
  850. self._check(idx + 2, lambda x: x - 2, idx)
  851. self._check(idx + 2, lambda x: np.subtract(x, 2), idx)
  852. # freq with mult
  853. idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
  854. freq='2M', name='idx')
  855. expected = PeriodIndex(['2011-07', '2011-08', 'NaT', '2011-10'],
  856. freq='2M', name='idx')
  857. self._check(idx, lambda x: x + 3, expected)
  858. self._check(idx, lambda x: 3 + x, expected)
  859. self._check(idx, lambda x: np.add(x, 3), expected)
  860. self._check(idx + 3, lambda x: x - 3, idx)
  861. self._check(idx + 3, lambda x: np.subtract(x, 3), idx)
  862. def test_pi_ops_array_int(self):
  863. idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
  864. freq='M', name='idx')
  865. f = lambda x: x + np.array([1, 2, 3, 4])
  866. exp = PeriodIndex(['2011-02', '2011-04', 'NaT', '2011-08'],
  867. freq='M', name='idx')
  868. self._check(idx, f, exp)
  869. f = lambda x: np.add(x, np.array([4, -1, 1, 2]))
  870. exp = PeriodIndex(['2011-05', '2011-01', 'NaT', '2011-06'],
  871. freq='M', name='idx')
  872. self._check(idx, f, exp)
  873. f = lambda x: x - np.array([1, 2, 3, 4])
  874. exp = PeriodIndex(['2010-12', '2010-12', 'NaT', '2010-12'],
  875. freq='M', name='idx')
  876. self._check(idx, f, exp)
  877. f = lambda x: np.subtract(x, np.array([3, 2, 3, -2]))
  878. exp = PeriodIndex(['2010-10', '2010-12', 'NaT', '2011-06'],
  879. freq='M', name='idx')
  880. self._check(idx, f, exp)
  881. def test_pi_ops_offset(self):
  882. idx = PeriodIndex(['2011-01-01', '2011-02-01', '2011-03-01',
  883. '2011-04-01'], freq='D', name='idx')
  884. f = lambda x: x + pd.offsets.Day()
  885. exp = PeriodIndex(['2011-01-02', '2011-02-02', '2011-03-02',
  886. '2011-04-02'], freq='D', name='idx')
  887. self._check(idx, f, exp)
  888. f = lambda x: x + pd.offsets.Day(2)
  889. exp = PeriodIndex(['2011-01-03', '2011-02-03', '2011-03-03',
  890. '2011-04-03'], freq='D', name='idx')
  891. self._check(idx, f, exp)
  892. f = lambda x: x - pd.offsets.Day(2)
  893. exp = PeriodIndex(['2010-12-30', '2011-01-30', '2011-02-27',
  894. '2011-03-30'], freq='D', name='idx')
  895. self._check(idx, f, exp)
  896. def test_pi_offset_errors(self):
  897. idx = PeriodIndex(['2011-01-01', '2011-02-01', '2011-03-01',
  898. '2011-04-01'], freq='D', name='idx')
  899. ser = pd.Series(idx)
  900. # Series op is applied per Period instance, thus error is raised
  901. # from Period
  902. for obj in [idx, ser]:
  903. msg = r"Input has different freq=2H from Period.*?\(freq=D\)"
  904. with pytest.raises(IncompatibleFrequency, match=msg):
  905. obj + pd.offsets.Hour(2)
  906. with pytest.raises(IncompatibleFrequency, match=msg):
  907. pd.offsets.Hour(2) + obj
  908. msg = r"Input has different freq=-2H from Period.*?\(freq=D\)"
  909. with pytest.raises(IncompatibleFrequency, match=msg):
  910. obj - pd.offsets.Hour(2)
  911. def test_pi_sub_period(self):
  912. # GH#13071
  913. idx = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'],
  914. freq='M', name='idx')
  915. result = idx - pd.Period('2012-01', freq='M')
  916. off = idx.freq
  917. exp = pd.Index([-12 * off, -11 * off, -10 * off, -9 * off], name='idx')
  918. tm.assert_index_equal(result, exp)
  919. result = np.subtract(idx, pd.Period('2012-01', freq='M'))
  920. tm.assert_index_equal(result, exp)
  921. result = pd.Period('2012-01', freq='M') - idx
  922. exp = pd.Index([12 * off, 11 * off, 10 * off, 9 * off], name='idx')
  923. tm.assert_index_equal(result, exp)
  924. result = np.subtract(pd.Period('2012-01', freq='M'), idx)
  925. tm.assert_index_equal(result, exp)
  926. exp = pd.TimedeltaIndex([np.nan, np.nan, np.nan, np.nan], name='idx')
  927. tm.assert_index_equal(idx - pd.Period('NaT', freq='M'), exp)
  928. tm.assert_index_equal(pd.Period('NaT', freq='M') - idx, exp)
  929. def test_pi_sub_pdnat(self):
  930. # GH#13071
  931. idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
  932. freq='M', name='idx')
  933. exp = pd.TimedeltaIndex([pd.NaT] * 4, name='idx')
  934. tm.assert_index_equal(pd.NaT - idx, exp)
  935. tm.assert_index_equal(idx - pd.NaT, exp)
  936. def test_pi_sub_period_nat(self):
  937. # GH#13071
  938. idx = PeriodIndex(['2011-01', 'NaT', '2011-03', '2011-04'],
  939. freq='M', name='idx')
  940. result = idx - pd.Period('2012-01', freq='M')
  941. off = idx.freq
  942. exp = pd.Index([-12 * off, pd.NaT, -10 * off, -9 * off], name='idx')
  943. tm.assert_index_equal(result, exp)
  944. result = pd.Period('2012-01', freq='M') - idx
  945. exp = pd.Index([12 * off, pd.NaT, 10 * off, 9 * off], name='idx')
  946. tm.assert_index_equal(result, exp)
  947. exp = pd.TimedeltaIndex([np.nan, np.nan, np.nan, np.nan], name='idx')
  948. tm.assert_index_equal(idx - pd.Period('NaT', freq='M'), exp)
  949. tm.assert_index_equal(pd.Period('NaT', freq='M') - idx, exp)