Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pandas/core/indexes/period.py : 21%

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
4import numpy as np
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
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)
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
49from pandas.tseries import frequencies
50from pandas.tseries.offsets import DateOffset, Tick
52_index_doc_kwargs = dict(ibase._index_doc_kwargs)
53_index_doc_kwargs.update(dict(target_klass="PeriodIndex or list of Periods"))
56# --- Period index sketch
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)
70class PeriodDelegateMixin(DatetimelikeDelegateMixin):
71 """
72 Delegate from PeriodIndex to PeriodArray.
73 """
75 _raw_methods = {"_format_native_types"}
76 _raw_properties = {"is_leap_year", "freq"}
78 _delegated_properties = PeriodArray._datetimelike_ops + list(_raw_properties)
79 _delegated_methods = set(PeriodArray._datetimelike_methods) | _raw_methods
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.
90 Index keys are boxed to Period objects which carries the metadata (eg,
91 frequency information).
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
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
135 Methods
136 -------
137 asfreq
138 strftime
139 to_timestamp
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.
149 Examples
150 --------
151 >>> idx = pd.PeriodIndex(year=year_arr, quarter=q_arr)
152 """
154 _typ = "periodindex"
155 _attributes = ["name", "freq"]
157 # define my properties & methods for delegation
158 _is_numeric_dtype = False
159 _infer_as_myclass = True
161 _data: PeriodArray
163 _engine_type = libindex.PeriodEngine
164 _supports_partial_string_indexing = True
166 # ------------------------------------------------------------------------
167 # Index Constructors
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 ):
181 valid_field_set = {
182 "year",
183 "month",
184 "day",
185 "quarter",
186 "hour",
187 "minute",
188 "second",
189 }
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}")
195 name = maybe_extract_name(name, data, cls)
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
204 data = PeriodArray(data, freq=freq)
205 else:
206 freq = validate_dtype_freq(dtype, freq)
208 # PeriodIndex allow PeriodIndex(period_index, freq=different)
209 # Let's not encourage that kind of behavior in PeriodArray.
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)
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)
224 if copy:
225 data = data.copy()
227 return cls._simple_new(data, name=name)
229 @classmethod
230 def _simple_new(cls, values, name=None, freq=None, **kwargs):
231 """
232 Create a new PeriodIndex.
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.
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)
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
262 # ------------------------------------------------------------------------
263 # Data
265 @property
266 def values(self):
267 return np.asarray(self)
269 @property
270 def _has_complex_internals(self):
271 # used to avoid libreduction code paths, which raise or require conversion
272 return True
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
279 if isinstance(values, type(self)):
280 values = values._data
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))
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()
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)
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)
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)
316 return func
318 def _maybe_convert_timedelta(self, other):
319 """
320 Convert timedelta-like input to an integer multiple of self.freq
322 Parameters
323 ----------
324 other : timedelta, np.timedelta64, DateOffset, int, np.ndarray
326 Returns
327 -------
328 converted : int, np.ndarray[int64]
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
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
354 # raise when input doesn't have freq
355 raise raise_on_incompatible(self, None)
357 # ------------------------------------------------------------------------
358 # Rendering Methods
360 def _mpl_repr(self):
361 # how to represent ourselves to matplotlib
362 return self.astype(object).values
364 @property
365 def _formatter_func(self):
366 return self.array._formatter(boxed=False)
368 # ------------------------------------------------------------------------
369 # Indexing
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))
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
392 @cache_readonly
393 def _int64index(self):
394 return Int64Index._simple_new(self.asi8, name=self.name)
396 # ------------------------------------------------------------------------
397 # Index Methods
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
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
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)
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)
438 def asof_locs(self, where, mask):
439 """
440 where : array of timestamps
441 mask : array of booleans where data is not NA
443 """
444 where_idx = where
445 if isinstance(where_idx, DatetimeIndex):
446 where_idx = PeriodIndex(where_idx.values, freq=self.freq)
448 locs = self._ndarray_values[mask].searchsorted(
449 where_idx._ndarray_values, side="right"
450 )
452 locs = np.where(locs > 0, locs - 1, 0)
453 result = np.arange(len(self))[mask].take(locs)
455 first = mask.argmax()
456 result[
457 (locs == 0) & (where_idx._ndarray_values < self._ndarray_values[first])
458 ] = -1
460 return result
462 @Appender(_index_shared_docs["astype"])
463 def astype(self, dtype, copy=True, how="start"):
464 dtype = pandas_dtype(dtype)
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)
471 # TODO: should probably raise on `how` here, so we don't ignore it.
472 return super().astype(dtype, copy=copy)
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 )
489 return self._data.searchsorted(value, side=side, sorter=sorter)
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()
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"
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)
524 vals = self._ndarray_values
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
532 if ord2 < vals[0] or ord1 > vals[-1]:
533 raise KeyError(key)
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)
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)
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)
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
562 target = target.asi8
563 self_index = self._int64index
564 else:
565 self_index = self
567 if tolerance is not None:
568 tolerance = self._convert_tolerance(tolerance, target)
569 return Index.get_indexer(self_index, target, method, limit, tolerance)
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)
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
580 target = target.asi8
582 indexer, missing = self._int64index.get_indexer_non_unique(target)
583 return ensure_platform_int(indexer), missing
585 def get_loc(self, key, method=None, tolerance=None):
586 """
587 Get integer location for requested label
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
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")
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)
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)
621 except KeyError:
622 raise KeyError(key)
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.
629 Parameters
630 ----------
631 label : object
632 side : {'left', 'right'}
633 kind : {'ix', 'loc', 'getitem'}
635 Returns
636 -------
637 bound : Period or object
639 Notes
640 -----
641 Value of `side` parameter should be validated in caller.
643 """
644 assert kind in ["ix", "loc", "getitem"]
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)
660 return label
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"))
703 def _get_string_slice(self, key):
704 if not self.is_monotonic:
705 raise ValueError("Partial indexing only valid for ordered time series")
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)
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 )
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)
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)
728 idx = np.concatenate(
729 (self[:loc].asi8, np.array([item.ordinal]), self[loc:].asi8)
730 )
731 return self._shallow_copy(idx)
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)
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 )
744 result = Int64Index.join(
745 self,
746 other,
747 how=how,
748 level=level,
749 return_indexers=return_indexers,
750 sort=sort,
751 )
753 if return_indexers:
754 result, lidx, ridx = result
755 return self._apply_meta(result), lidx, ridx
756 return self._apply_meta(result)
758 # ------------------------------------------------------------------------
759 # Set Operation Methods
761 def _assert_can_do_setop(self, other):
762 super()._assert_can_do_setop(other)
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)
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)
775 if self.equals(other):
776 return self._get_reconciled_name_object(other)
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)
784 i8self = Int64Index._simple_new(self.asi8)
785 i8other = Int64Index._simple_new(other.asi8)
786 i8result = i8self.intersection(i8other, sort=sort)
788 result = self._shallow_copy(np.asarray(i8result, dtype=np.int64), name=res_name)
789 return result
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)
797 if self.equals(other):
798 # pass an empty PeriodArray with the appropriate dtype
799 return self._shallow_copy(self._data[:0])
801 if is_object_dtype(other):
802 return self.astype(object).difference(other).astype(self.dtype)
804 elif not is_dtype_equal(self.dtype, other.dtype):
805 return self
807 i8self = Int64Index._simple_new(self.asi8)
808 i8other = Int64Index._simple_new(other.asi8)
809 i8result = i8self.difference(i8other, sort=sort)
811 result = self._shallow_copy(np.asarray(i8result, dtype=np.int64), name=res_name)
812 return result
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)
818 # We are called by `union`, which is responsible for this validation
819 assert isinstance(other, type(self))
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)
826 i8self = Int64Index._simple_new(self.asi8)
827 i8other = Int64Index._simple_new(other.asi8)
828 i8result = i8self._union(i8other, sort=sort)
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
834 # ------------------------------------------------------------------------
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
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
848PeriodIndex._add_numeric_methods_disabled()
849PeriodIndex._add_logical_methods_disabled()
852def period_range(
853 start=None, end=None, periods=None, freq=None, name=None
854) -> PeriodIndex:
855 """
856 Return a fixed frequency PeriodIndex.
858 The day (calendar) is the default frequency.
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.
875 Returns
876 -------
877 PeriodIndex
879 Notes
880 -----
881 Of the three parameters: ``start``, ``end``, and ``periods``, exactly two
882 must be specified.
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>`__.
887 Examples
888 --------
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')
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.
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"
913 data, freq = PeriodArray._generate_range(start, end, periods, freq, fields={})
914 data = PeriodArray(data, freq=freq)
915 return PeriodIndex(data, name=name)