Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from datetime import datetime, timedelta 

2import weakref 

3 

4import numpy as np 

5 

6from pandas._libs import index as libindex 

7from pandas._libs.tslibs import NaT, frequencies as libfrequencies, iNaT, resolution 

8from pandas._libs.tslibs.period import Period 

9from pandas.util._decorators import Appender, Substitution, cache_readonly 

10 

11from pandas.core.dtypes.common import ( 

12 ensure_platform_int, 

13 is_bool_dtype, 

14 is_datetime64_any_dtype, 

15 is_dtype_equal, 

16 is_float, 

17 is_float_dtype, 

18 is_integer, 

19 is_integer_dtype, 

20 is_object_dtype, 

21 pandas_dtype, 

22) 

23 

24from pandas.core.accessor import delegate_names 

25from pandas.core.arrays.period import ( 

26 PeriodArray, 

27 period_array, 

28 raise_on_incompatible, 

29 validate_dtype_freq, 

30) 

31from pandas.core.base import _shared_docs 

32import pandas.core.common as com 

33import pandas.core.indexes.base as ibase 

34from pandas.core.indexes.base import ( 

35 _index_shared_docs, 

36 ensure_index, 

37 maybe_extract_name, 

38) 

39from pandas.core.indexes.datetimelike import ( 

40 DatetimeIndexOpsMixin, 

41 DatetimelikeDelegateMixin, 

42) 

43from pandas.core.indexes.datetimes import DatetimeIndex, Index 

44from pandas.core.indexes.numeric import Int64Index 

45from pandas.core.missing import isna 

46from pandas.core.ops import get_op_result_name 

47from pandas.core.tools.datetimes import DateParseError, parse_time_string 

48 

49from pandas.tseries import frequencies 

50from pandas.tseries.offsets import DateOffset, Tick 

51 

52_index_doc_kwargs = dict(ibase._index_doc_kwargs) 

53_index_doc_kwargs.update(dict(target_klass="PeriodIndex or list of Periods")) 

54 

55 

56# --- Period index sketch 

57 

58 

59def _new_PeriodIndex(cls, **d): 

60 # GH13277 for unpickling 

61 values = d.pop("data") 

62 if values.dtype == "int64": 

63 freq = d.pop("freq", None) 

64 values = PeriodArray(values, freq=freq) 

65 return cls._simple_new(values, **d) 

66 else: 

67 return cls(values, **d) 

68 

69 

70class PeriodDelegateMixin(DatetimelikeDelegateMixin): 

71 """ 

72 Delegate from PeriodIndex to PeriodArray. 

73 """ 

74 

75 _raw_methods = {"_format_native_types"} 

76 _raw_properties = {"is_leap_year", "freq"} 

77 

78 _delegated_properties = PeriodArray._datetimelike_ops + list(_raw_properties) 

79 _delegated_methods = set(PeriodArray._datetimelike_methods) | _raw_methods 

80 

81 

82@delegate_names(PeriodArray, PeriodDelegateMixin._delegated_properties, typ="property") 

83@delegate_names( 

84 PeriodArray, PeriodDelegateMixin._delegated_methods, typ="method", overwrite=True 

85) 

86class PeriodIndex(DatetimeIndexOpsMixin, Int64Index, PeriodDelegateMixin): 

87 """ 

88 Immutable ndarray holding ordinal values indicating regular periods in time. 

89 

90 Index keys are boxed to Period objects which carries the metadata (eg, 

91 frequency information). 

92 

93 Parameters 

94 ---------- 

95 data : array-like (1d int np.ndarray or PeriodArray), optional 

96 Optional period-like data to construct index with. 

97 copy : bool 

98 Make a copy of input ndarray. 

99 freq : str or period object, optional 

100 One of pandas period strings or corresponding objects 

101 year : int, array, or Series, default None 

102 month : int, array, or Series, default None 

103 quarter : int, array, or Series, default None 

104 day : int, array, or Series, default None 

105 hour : int, array, or Series, default None 

106 minute : int, array, or Series, default None 

107 second : int, array, or Series, default None 

108 tz : object, default None 

109 Timezone for converting datetime64 data to Periods. 

110 dtype : str or PeriodDtype, default None 

111 

112 Attributes 

113 ---------- 

114 day 

115 dayofweek 

116 dayofyear 

117 days_in_month 

118 daysinmonth 

119 end_time 

120 freq 

121 freqstr 

122 hour 

123 is_leap_year 

124 minute 

125 month 

126 quarter 

127 qyear 

128 second 

129 start_time 

130 week 

131 weekday 

132 weekofyear 

133 year 

134 

135 Methods 

136 ------- 

137 asfreq 

138 strftime 

139 to_timestamp 

140 

141 See Also 

142 -------- 

143 Index : The base pandas Index type. 

144 Period : Represents a period of time. 

145 DatetimeIndex : Index with datetime64 data. 

146 TimedeltaIndex : Index of timedelta64 data. 

147 period_range : Create a fixed-frequency PeriodIndex. 

148 

149 Examples 

150 -------- 

151 >>> idx = pd.PeriodIndex(year=year_arr, quarter=q_arr) 

152 """ 

153 

154 _typ = "periodindex" 

155 _attributes = ["name", "freq"] 

156 

157 # define my properties & methods for delegation 

158 _is_numeric_dtype = False 

159 _infer_as_myclass = True 

160 

161 _data: PeriodArray 

162 

163 _engine_type = libindex.PeriodEngine 

164 _supports_partial_string_indexing = True 

165 

166 # ------------------------------------------------------------------------ 

167 # Index Constructors 

168 

169 def __new__( 

170 cls, 

171 data=None, 

172 ordinal=None, 

173 freq=None, 

174 tz=None, 

175 dtype=None, 

176 copy=False, 

177 name=None, 

178 **fields, 

179 ): 

180 

181 valid_field_set = { 

182 "year", 

183 "month", 

184 "day", 

185 "quarter", 

186 "hour", 

187 "minute", 

188 "second", 

189 } 

190 

191 if not set(fields).issubset(valid_field_set): 

192 argument = list(set(fields) - valid_field_set)[0] 

193 raise TypeError(f"__new__() got an unexpected keyword argument {argument}") 

194 

195 name = maybe_extract_name(name, data, cls) 

196 

197 if data is None and ordinal is None: 

198 # range-based. 

199 data, freq2 = PeriodArray._generate_range(None, None, None, freq, fields) 

200 # PeriodArray._generate range does validation that fields is 

201 # empty when really using the range-based constructor. 

202 freq = freq2 

203 

204 data = PeriodArray(data, freq=freq) 

205 else: 

206 freq = validate_dtype_freq(dtype, freq) 

207 

208 # PeriodIndex allow PeriodIndex(period_index, freq=different) 

209 # Let's not encourage that kind of behavior in PeriodArray. 

210 

211 if freq and isinstance(data, cls) and data.freq != freq: 

212 # TODO: We can do some of these with no-copy / coercion? 

213 # e.g. D -> 2D seems to be OK 

214 data = data.asfreq(freq) 

215 

216 if data is None and ordinal is not None: 

217 # we strangely ignore `ordinal` if data is passed. 

218 ordinal = np.asarray(ordinal, dtype=np.int64) 

219 data = PeriodArray(ordinal, freq) 

220 else: 

221 # don't pass copy here, since we copy later. 

222 data = period_array(data=data, freq=freq) 

223 

224 if copy: 

225 data = data.copy() 

226 

227 return cls._simple_new(data, name=name) 

228 

229 @classmethod 

230 def _simple_new(cls, values, name=None, freq=None, **kwargs): 

231 """ 

232 Create a new PeriodIndex. 

233 

234 Parameters 

235 ---------- 

236 values : PeriodArray, PeriodIndex, Index[int64], ndarray[int64] 

237 Values that can be converted to a PeriodArray without inference 

238 or coercion. 

239 

240 """ 

241 # TODO: raising on floats is tested, but maybe not useful. 

242 # Should the callers know not to pass floats? 

243 # At the very least, I think we can ensure that lists aren't passed. 

244 if isinstance(values, list): 

245 values = np.asarray(values) 

246 if is_float_dtype(values): 

247 raise TypeError("PeriodIndex._simple_new does not accept floats.") 

248 if freq: 

249 freq = Period._maybe_convert_freq(freq) 

250 values = PeriodArray(values, freq=freq) 

251 

252 if not isinstance(values, PeriodArray): 

253 raise TypeError("PeriodIndex._simple_new only accepts PeriodArray") 

254 result = object.__new__(cls) 

255 result._data = values 

256 # For groupby perf. See note in indexes/base about _index_data 

257 result._index_data = values._data 

258 result.name = name 

259 result._reset_identity() 

260 return result 

261 

262 # ------------------------------------------------------------------------ 

263 # Data 

264 

265 @property 

266 def values(self): 

267 return np.asarray(self) 

268 

269 @property 

270 def _has_complex_internals(self): 

271 # used to avoid libreduction code paths, which raise or require conversion 

272 return True 

273 

274 def _shallow_copy(self, values=None, **kwargs): 

275 # TODO: simplify, figure out type of values 

276 if values is None: 

277 values = self._data 

278 

279 if isinstance(values, type(self)): 

280 values = values._data 

281 

282 if not isinstance(values, PeriodArray): 

283 if isinstance(values, np.ndarray) and values.dtype == "i8": 

284 values = PeriodArray(values, freq=self.freq) 

285 else: 

286 # GH#30713 this should never be reached 

287 raise TypeError(type(values), getattr(values, "dtype", None)) 

288 

289 # We don't allow changing `freq` in _shallow_copy. 

290 validate_dtype_freq(self.dtype, kwargs.get("freq")) 

291 attributes = self._get_attributes_dict() 

292 

293 attributes.update(kwargs) 

294 if not len(values) and "dtype" not in kwargs: 

295 attributes["dtype"] = self.dtype 

296 return self._simple_new(values, **attributes) 

297 

298 def _shallow_copy_with_infer(self, values=None, **kwargs): 

299 """ we always want to return a PeriodIndex """ 

300 return self._shallow_copy(values=values, **kwargs) 

301 

302 @property 

303 def _box_func(self): 

304 """Maybe box an ordinal or Period""" 

305 # TODO(DatetimeArray): Avoid double-boxing 

306 # PeriodArray takes care of boxing already, so we need to check 

307 # whether we're given an ordinal or a Period. It seems like some 

308 # places outside of indexes/period.py are calling this _box_func, 

309 # but passing data that's already boxed. 

310 def func(x): 

311 if isinstance(x, Period) or x is NaT: 

312 return x 

313 else: 

314 return Period._from_ordinal(ordinal=x, freq=self.freq) 

315 

316 return func 

317 

318 def _maybe_convert_timedelta(self, other): 

319 """ 

320 Convert timedelta-like input to an integer multiple of self.freq 

321 

322 Parameters 

323 ---------- 

324 other : timedelta, np.timedelta64, DateOffset, int, np.ndarray 

325 

326 Returns 

327 ------- 

328 converted : int, np.ndarray[int64] 

329 

330 Raises 

331 ------ 

332 IncompatibleFrequency : if the input cannot be written as a multiple 

333 of self.freq. Note IncompatibleFrequency subclasses ValueError. 

334 """ 

335 if isinstance(other, (timedelta, np.timedelta64, Tick, np.ndarray)): 

336 offset = frequencies.to_offset(self.freq.rule_code) 

337 if isinstance(offset, Tick): 

338 # _check_timedeltalike_freq_compat will raise if incompatible 

339 delta = self._data._check_timedeltalike_freq_compat(other) 

340 return delta 

341 elif isinstance(other, DateOffset): 

342 freqstr = other.rule_code 

343 base = libfrequencies.get_base_alias(freqstr) 

344 if base == self.freq.rule_code: 

345 return other.n 

346 

347 raise raise_on_incompatible(self, other) 

348 elif is_integer(other): 

349 # integer is passed to .shift via 

350 # _add_datetimelike_methods basically 

351 # but ufunc may pass integer to _add_delta 

352 return other 

353 

354 # raise when input doesn't have freq 

355 raise raise_on_incompatible(self, None) 

356 

357 # ------------------------------------------------------------------------ 

358 # Rendering Methods 

359 

360 def _mpl_repr(self): 

361 # how to represent ourselves to matplotlib 

362 return self.astype(object).values 

363 

364 @property 

365 def _formatter_func(self): 

366 return self.array._formatter(boxed=False) 

367 

368 # ------------------------------------------------------------------------ 

369 # Indexing 

370 

371 @cache_readonly 

372 def _engine(self): 

373 # To avoid a reference cycle, pass a weakref of self to _engine_type. 

374 period = weakref.ref(self) 

375 return self._engine_type(period, len(self)) 

376 

377 @Appender(_index_shared_docs["contains"]) 

378 def __contains__(self, key) -> bool: 

379 if isinstance(key, Period): 

380 if key.freq != self.freq: 

381 return False 

382 else: 

383 return key.ordinal in self._engine 

384 else: 

385 try: 

386 self.get_loc(key) 

387 return True 

388 except (TypeError, KeyError): 

389 # TypeError can be reached if we pass a tuple that is not hashable 

390 return False 

391 

392 @cache_readonly 

393 def _int64index(self): 

394 return Int64Index._simple_new(self.asi8, name=self.name) 

395 

396 # ------------------------------------------------------------------------ 

397 # Index Methods 

398 

399 def __array__(self, dtype=None) -> np.ndarray: 

400 if is_integer_dtype(dtype): 

401 return self.asi8 

402 else: 

403 return self.astype(object).values 

404 

405 def __array_wrap__(self, result, context=None): 

406 """ 

407 Gets called after a ufunc. Needs additional handling as 

408 PeriodIndex stores internal data as int dtype 

409 

410 Replace this to __numpy_ufunc__ in future version 

411 """ 

412 if isinstance(context, tuple) and len(context) > 0: 

413 func = context[0] 

414 if func is np.add: 

415 pass 

416 elif func is np.subtract: 

417 name = self.name 

418 left = context[1][0] 

419 right = context[1][1] 

420 if isinstance(left, PeriodIndex) and isinstance(right, PeriodIndex): 

421 name = left.name if left.name == right.name else None 

422 return Index(result, name=name) 

423 elif isinstance(left, Period) or isinstance(right, Period): 

424 return Index(result, name=name) 

425 elif isinstance(func, np.ufunc): 

426 if "M->M" not in func.types: 

427 msg = f"ufunc '{func.__name__}' not supported for the PeriodIndex" 

428 # This should be TypeError, but TypeError cannot be raised 

429 # from here because numpy catches. 

430 raise ValueError(msg) 

431 

432 if is_bool_dtype(result): 

433 return result 

434 # the result is object dtype array of Period 

435 # cannot pass _simple_new as it is 

436 return type(self)(result, freq=self.freq, name=self.name) 

437 

438 def asof_locs(self, where, mask): 

439 """ 

440 where : array of timestamps 

441 mask : array of booleans where data is not NA 

442 

443 """ 

444 where_idx = where 

445 if isinstance(where_idx, DatetimeIndex): 

446 where_idx = PeriodIndex(where_idx.values, freq=self.freq) 

447 

448 locs = self._ndarray_values[mask].searchsorted( 

449 where_idx._ndarray_values, side="right" 

450 ) 

451 

452 locs = np.where(locs > 0, locs - 1, 0) 

453 result = np.arange(len(self))[mask].take(locs) 

454 

455 first = mask.argmax() 

456 result[ 

457 (locs == 0) & (where_idx._ndarray_values < self._ndarray_values[first]) 

458 ] = -1 

459 

460 return result 

461 

462 @Appender(_index_shared_docs["astype"]) 

463 def astype(self, dtype, copy=True, how="start"): 

464 dtype = pandas_dtype(dtype) 

465 

466 if is_datetime64_any_dtype(dtype): 

467 # 'how' is index-specific, isn't part of the EA interface. 

468 tz = getattr(dtype, "tz", None) 

469 return self.to_timestamp(how=how).tz_localize(tz) 

470 

471 # TODO: should probably raise on `how` here, so we don't ignore it. 

472 return super().astype(dtype, copy=copy) 

473 

474 @Substitution(klass="PeriodIndex") 

475 @Appender(_shared_docs["searchsorted"]) 

476 def searchsorted(self, value, side="left", sorter=None): 

477 if isinstance(value, Period) or value is NaT: 

478 self._data._check_compatible_with(value) 

479 elif isinstance(value, str): 

480 try: 

481 value = Period(value, freq=self.freq) 

482 except DateParseError: 

483 raise KeyError(f"Cannot interpret '{value}' as period") 

484 elif not isinstance(value, PeriodArray): 

485 raise TypeError( 

486 "PeriodIndex.searchsorted requires either a Period or PeriodArray" 

487 ) 

488 

489 return self._data.searchsorted(value, side=side, sorter=sorter) 

490 

491 @property 

492 def is_full(self) -> bool: 

493 """ 

494 Returns True if this PeriodIndex is range-like in that all Periods 

495 between start and end are present, in order. 

496 """ 

497 if len(self) == 0: 

498 return True 

499 if not self.is_monotonic: 

500 raise ValueError("Index is not monotonic") 

501 values = self.asi8 

502 return ((values[1:] - values[:-1]) < 2).all() 

503 

504 @property 

505 def inferred_type(self) -> str: 

506 # b/c data is represented as ints make sure we can't have ambiguous 

507 # indexing 

508 return "period" 

509 

510 def get_value(self, series, key): 

511 """ 

512 Fast lookup of value from 1-dimensional ndarray. Only use this if you 

513 know what you're doing 

514 """ 

515 s = com.values_from_object(series) 

516 try: 

517 value = super().get_value(s, key) 

518 except (KeyError, IndexError): 

519 if isinstance(key, str): 

520 asdt, parsed, reso = parse_time_string(key, self.freq) 

521 grp = resolution.Resolution.get_freq_group(reso) 

522 freqn = resolution.get_freq_group(self.freq) 

523 

524 vals = self._ndarray_values 

525 

526 # if our data is higher resolution than requested key, slice 

527 if grp < freqn: 

528 iv = Period(asdt, freq=(grp, 1)) 

529 ord1 = iv.asfreq(self.freq, how="S").ordinal 

530 ord2 = iv.asfreq(self.freq, how="E").ordinal 

531 

532 if ord2 < vals[0] or ord1 > vals[-1]: 

533 raise KeyError(key) 

534 

535 pos = np.searchsorted(self._ndarray_values, [ord1, ord2]) 

536 key = slice(pos[0], pos[1] + 1) 

537 return series[key] 

538 elif grp == freqn: 

539 key = Period(asdt, freq=self.freq).ordinal 

540 return com.maybe_box( 

541 self, self._int64index.get_value(s, key), series, key 

542 ) 

543 else: 

544 raise KeyError(key) 

545 

546 period = Period(key, self.freq) 

547 key = period.value if isna(period) else period.ordinal 

548 return com.maybe_box(self, self._int64index.get_value(s, key), series, key) 

549 else: 

550 return com.maybe_box(self, value, series, key) 

551 

552 @Appender(_index_shared_docs["get_indexer"] % _index_doc_kwargs) 

553 def get_indexer(self, target, method=None, limit=None, tolerance=None): 

554 target = ensure_index(target) 

555 

556 if isinstance(target, PeriodIndex): 

557 if target.freq != self.freq: 

558 # No matches 

559 no_matches = -1 * np.ones(self.shape, dtype=np.intp) 

560 return no_matches 

561 

562 target = target.asi8 

563 self_index = self._int64index 

564 else: 

565 self_index = self 

566 

567 if tolerance is not None: 

568 tolerance = self._convert_tolerance(tolerance, target) 

569 return Index.get_indexer(self_index, target, method, limit, tolerance) 

570 

571 @Appender(_index_shared_docs["get_indexer_non_unique"] % _index_doc_kwargs) 

572 def get_indexer_non_unique(self, target): 

573 target = ensure_index(target) 

574 

575 if isinstance(target, PeriodIndex): 

576 if target.freq != self.freq: 

577 no_matches = -1 * np.ones(self.shape, dtype=np.intp) 

578 return no_matches, no_matches 

579 

580 target = target.asi8 

581 

582 indexer, missing = self._int64index.get_indexer_non_unique(target) 

583 return ensure_platform_int(indexer), missing 

584 

585 def get_loc(self, key, method=None, tolerance=None): 

586 """ 

587 Get integer location for requested label 

588 

589 Returns 

590 ------- 

591 loc : int 

592 """ 

593 try: 

594 return self._engine.get_loc(key) 

595 except KeyError: 

596 if is_integer(key): 

597 raise 

598 

599 try: 

600 asdt, parsed, reso = parse_time_string(key, self.freq) 

601 key = asdt 

602 except TypeError: 

603 pass 

604 except DateParseError: 

605 # A string with invalid format 

606 raise KeyError(f"Cannot interpret '{key}' as period") 

607 

608 try: 

609 key = Period(key, freq=self.freq) 

610 except ValueError: 

611 # we cannot construct the Period 

612 # as we have an invalid type 

613 raise KeyError(key) 

614 

615 try: 

616 ordinal = iNaT if key is NaT else key.ordinal 

617 if tolerance is not None: 

618 tolerance = self._convert_tolerance(tolerance, np.asarray(key)) 

619 return self._int64index.get_loc(ordinal, method, tolerance) 

620 

621 except KeyError: 

622 raise KeyError(key) 

623 

624 def _maybe_cast_slice_bound(self, label, side, kind): 

625 """ 

626 If label is a string or a datetime, cast it to Period.ordinal according 

627 to resolution. 

628 

629 Parameters 

630 ---------- 

631 label : object 

632 side : {'left', 'right'} 

633 kind : {'ix', 'loc', 'getitem'} 

634 

635 Returns 

636 ------- 

637 bound : Period or object 

638 

639 Notes 

640 ----- 

641 Value of `side` parameter should be validated in caller. 

642 

643 """ 

644 assert kind in ["ix", "loc", "getitem"] 

645 

646 if isinstance(label, datetime): 

647 return Period(label, freq=self.freq) 

648 elif isinstance(label, str): 

649 try: 

650 _, parsed, reso = parse_time_string(label, self.freq) 

651 bounds = self._parsed_string_to_bounds(reso, parsed) 

652 return bounds[0 if side == "left" else 1] 

653 except ValueError: 

654 # string cannot be parsed as datetime-like 

655 # TODO: we need tests for this case 

656 raise KeyError(label) 

657 elif is_integer(label) or is_float(label): 

658 self._invalid_indexer("slice", label) 

659 

660 return label 

661 

662 def _parsed_string_to_bounds(self, reso, parsed): 

663 if reso == "year": 

664 t1 = Period(year=parsed.year, freq="A") 

665 elif reso == "month": 

666 t1 = Period(year=parsed.year, month=parsed.month, freq="M") 

667 elif reso == "quarter": 

668 q = (parsed.month - 1) // 3 + 1 

669 t1 = Period(year=parsed.year, quarter=q, freq="Q-DEC") 

670 elif reso == "day": 

671 t1 = Period(year=parsed.year, month=parsed.month, day=parsed.day, freq="D") 

672 elif reso == "hour": 

673 t1 = Period( 

674 year=parsed.year, 

675 month=parsed.month, 

676 day=parsed.day, 

677 hour=parsed.hour, 

678 freq="H", 

679 ) 

680 elif reso == "minute": 

681 t1 = Period( 

682 year=parsed.year, 

683 month=parsed.month, 

684 day=parsed.day, 

685 hour=parsed.hour, 

686 minute=parsed.minute, 

687 freq="T", 

688 ) 

689 elif reso == "second": 

690 t1 = Period( 

691 year=parsed.year, 

692 month=parsed.month, 

693 day=parsed.day, 

694 hour=parsed.hour, 

695 minute=parsed.minute, 

696 second=parsed.second, 

697 freq="S", 

698 ) 

699 else: 

700 raise KeyError(reso) 

701 return (t1.asfreq(self.freq, how="start"), t1.asfreq(self.freq, how="end")) 

702 

703 def _get_string_slice(self, key): 

704 if not self.is_monotonic: 

705 raise ValueError("Partial indexing only valid for ordered time series") 

706 

707 key, parsed, reso = parse_time_string(key, self.freq) 

708 grp = resolution.Resolution.get_freq_group(reso) 

709 freqn = resolution.get_freq_group(self.freq) 

710 if reso in ["day", "hour", "minute", "second"] and not grp < freqn: 

711 raise KeyError(key) 

712 

713 t1, t2 = self._parsed_string_to_bounds(reso, parsed) 

714 return slice( 

715 self.searchsorted(t1, side="left"), self.searchsorted(t2, side="right") 

716 ) 

717 

718 def _convert_tolerance(self, tolerance, target): 

719 tolerance = DatetimeIndexOpsMixin._convert_tolerance(self, tolerance, target) 

720 if target.size != tolerance.size and tolerance.size > 1: 

721 raise ValueError("list-like tolerance size must match target index size") 

722 return self._maybe_convert_timedelta(tolerance) 

723 

724 def insert(self, loc, item): 

725 if not isinstance(item, Period) or self.freq != item.freq: 

726 return self.astype(object).insert(loc, item) 

727 

728 idx = np.concatenate( 

729 (self[:loc].asi8, np.array([item.ordinal]), self[loc:].asi8) 

730 ) 

731 return self._shallow_copy(idx) 

732 

733 def join(self, other, how="left", level=None, return_indexers=False, sort=False): 

734 """ 

735 See Index.join 

736 """ 

737 self._assert_can_do_setop(other) 

738 

739 if not isinstance(other, PeriodIndex): 

740 return self.astype(object).join( 

741 other, how=how, level=level, return_indexers=return_indexers, sort=sort 

742 ) 

743 

744 result = Int64Index.join( 

745 self, 

746 other, 

747 how=how, 

748 level=level, 

749 return_indexers=return_indexers, 

750 sort=sort, 

751 ) 

752 

753 if return_indexers: 

754 result, lidx, ridx = result 

755 return self._apply_meta(result), lidx, ridx 

756 return self._apply_meta(result) 

757 

758 # ------------------------------------------------------------------------ 

759 # Set Operation Methods 

760 

761 def _assert_can_do_setop(self, other): 

762 super()._assert_can_do_setop(other) 

763 

764 # *Can't* use PeriodIndexes of different freqs 

765 # *Can* use PeriodIndex/DatetimeIndex 

766 if isinstance(other, PeriodIndex) and self.freq != other.freq: 

767 raise raise_on_incompatible(self, other) 

768 

769 def intersection(self, other, sort=False): 

770 self._validate_sort_keyword(sort) 

771 self._assert_can_do_setop(other) 

772 res_name = get_op_result_name(self, other) 

773 other = ensure_index(other) 

774 

775 if self.equals(other): 

776 return self._get_reconciled_name_object(other) 

777 

778 if not is_dtype_equal(self.dtype, other.dtype): 

779 # TODO: fastpath for if we have a different PeriodDtype 

780 this = self.astype("O") 

781 other = other.astype("O") 

782 return this.intersection(other, sort=sort) 

783 

784 i8self = Int64Index._simple_new(self.asi8) 

785 i8other = Int64Index._simple_new(other.asi8) 

786 i8result = i8self.intersection(i8other, sort=sort) 

787 

788 result = self._shallow_copy(np.asarray(i8result, dtype=np.int64), name=res_name) 

789 return result 

790 

791 def difference(self, other, sort=None): 

792 self._validate_sort_keyword(sort) 

793 self._assert_can_do_setop(other) 

794 res_name = get_op_result_name(self, other) 

795 other = ensure_index(other) 

796 

797 if self.equals(other): 

798 # pass an empty PeriodArray with the appropriate dtype 

799 return self._shallow_copy(self._data[:0]) 

800 

801 if is_object_dtype(other): 

802 return self.astype(object).difference(other).astype(self.dtype) 

803 

804 elif not is_dtype_equal(self.dtype, other.dtype): 

805 return self 

806 

807 i8self = Int64Index._simple_new(self.asi8) 

808 i8other = Int64Index._simple_new(other.asi8) 

809 i8result = i8self.difference(i8other, sort=sort) 

810 

811 result = self._shallow_copy(np.asarray(i8result, dtype=np.int64), name=res_name) 

812 return result 

813 

814 def _union(self, other, sort): 

815 if not len(other) or self.equals(other) or not len(self): 

816 return super()._union(other, sort=sort) 

817 

818 # We are called by `union`, which is responsible for this validation 

819 assert isinstance(other, type(self)) 

820 

821 if not is_dtype_equal(self.dtype, other.dtype): 

822 this = self.astype("O") 

823 other = other.astype("O") 

824 return this._union(other, sort=sort) 

825 

826 i8self = Int64Index._simple_new(self.asi8) 

827 i8other = Int64Index._simple_new(other.asi8) 

828 i8result = i8self._union(i8other, sort=sort) 

829 

830 res_name = get_op_result_name(self, other) 

831 result = self._shallow_copy(np.asarray(i8result, dtype=np.int64), name=res_name) 

832 return result 

833 

834 # ------------------------------------------------------------------------ 

835 

836 def _apply_meta(self, rawarr): 

837 if not isinstance(rawarr, PeriodIndex): 

838 rawarr = PeriodIndex._simple_new(rawarr, freq=self.freq, name=self.name) 

839 return rawarr 

840 

841 def memory_usage(self, deep=False): 

842 result = super().memory_usage(deep=deep) 

843 if hasattr(self, "_cache") and "_int64index" in self._cache: 

844 result += self._int64index.memory_usage(deep=deep) 

845 return result 

846 

847 

848PeriodIndex._add_numeric_methods_disabled() 

849PeriodIndex._add_logical_methods_disabled() 

850 

851 

852def period_range( 

853 start=None, end=None, periods=None, freq=None, name=None 

854) -> PeriodIndex: 

855 """ 

856 Return a fixed frequency PeriodIndex. 

857 

858 The day (calendar) is the default frequency. 

859 

860 Parameters 

861 ---------- 

862 start : str or period-like, default None 

863 Left bound for generating periods. 

864 end : str or period-like, default None 

865 Right bound for generating periods. 

866 periods : int, default None 

867 Number of periods to generate. 

868 freq : str or DateOffset, optional 

869 Frequency alias. By default the freq is taken from `start` or `end` 

870 if those are Period objects. Otherwise, the default is ``"D"`` for 

871 daily frequency. 

872 name : str, default None 

873 Name of the resulting PeriodIndex. 

874 

875 Returns 

876 ------- 

877 PeriodIndex 

878 

879 Notes 

880 ----- 

881 Of the three parameters: ``start``, ``end``, and ``periods``, exactly two 

882 must be specified. 

883 

884 To learn more about the frequency strings, please see `this link 

885 <https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases>`__. 

886 

887 Examples 

888 -------- 

889 

890 >>> pd.period_range(start='2017-01-01', end='2018-01-01', freq='M') 

891 PeriodIndex(['2017-01', '2017-02', '2017-03', '2017-04', '2017-05', 

892 '2017-06', '2017-06', '2017-07', '2017-08', '2017-09', 

893 '2017-10', '2017-11', '2017-12', '2018-01'], 

894 dtype='period[M]', freq='M') 

895 

896 If ``start`` or ``end`` are ``Period`` objects, they will be used as anchor 

897 endpoints for a ``PeriodIndex`` with frequency matching that of the 

898 ``period_range`` constructor. 

899 

900 >>> pd.period_range(start=pd.Period('2017Q1', freq='Q'), 

901 ... end=pd.Period('2017Q2', freq='Q'), freq='M') 

902 PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], 

903 dtype='period[M]', freq='M') 

904 """ 

905 if com.count_not_none(start, end, periods) != 2: 

906 raise ValueError( 

907 "Of the three parameters: start, end, and periods, " 

908 "exactly two must be specified" 

909 ) 

910 if freq is None and (not isinstance(start, Period) and not isinstance(end, Period)): 

911 freq = "D" 

912 

913 data, freq = PeriodArray._generate_range(start, end, periods, freq, fields={}) 

914 data = PeriodArray(data, freq=freq) 

915 return PeriodIndex(data, name=name)