class TimedeltaIndex(TimedeltaArray, DatetimeIndexOpsMixin, TimelikeOps, Int64Index): """ Immutable ndarray of timedelta64 data, represented internally as int64, and which can be boxed to timedelta objects Parameters ---------- data : array-like (1-dimensional), optional Optional timedelta-like data to construct index with unit : unit of the arg (D,h,m,s,ms,us,ns) denote the unit, optional which is an integer/float number freq : string or pandas offset object, optional One of pandas date offset strings or corresponding objects. The string 'infer' can be passed in order to set the frequency of the index as the inferred frequency upon creation copy : bool Make a copy of input ndarray start : starting value, timedelta-like, optional If data is None, start is used as the start point in generating regular timedelta data. periods : int, optional, > 0 Number of periods to generate, if generating index. Takes precedence over end argument end : end time, timedelta-like, optional If periods is none, generated index will extend to first conforming time on or just past end argument closed : string or None, default None Make the interval closed with respect to the given frequency to the 'left', 'right', or both sides (None) name : object Name to be stored in the index Notes ----- To learn more about the frequency strings, please see `this link <http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases>`__. See Also --------- Index : The base pandas Index type. Timedelta : Represents a duration between two dates or times. DatetimeIndex : Index of datetime64 data. PeriodIndex : Index of Period data. Attributes ---------- days seconds microseconds nanoseconds components inferred_freq Methods ------- to_pytimedelta to_series round floor ceil to_frame """ _typ = 'timedeltaindex' _join_precedence = 10 def _join_i8_wrapper(joinf, **kwargs): return DatetimeIndexOpsMixin._join_i8_wrapper(joinf, dtype='m8[ns]', **kwargs) _inner_indexer = _join_i8_wrapper(libjoin.inner_join_indexer_int64) _outer_indexer = _join_i8_wrapper(libjoin.outer_join_indexer_int64) _left_indexer = _join_i8_wrapper(libjoin.left_join_indexer_int64) _left_indexer_unique = _join_i8_wrapper( libjoin.left_join_indexer_unique_int64, with_indexers=False) # define my properties & methods for delegation _other_ops = [] _bool_ops = [] _object_ops = ['freq'] _field_ops = ['days', 'seconds', 'microseconds', 'nanoseconds'] _datetimelike_ops = _field_ops + _object_ops + _bool_ops _datetimelike_methods = [ "to_pytimedelta", "total_seconds", "round", "floor", "ceil" ] _engine_type = libindex.TimedeltaEngine _comparables = ['name', 'freq'] _attributes = ['name', 'freq'] _is_numeric_dtype = True _infer_as_myclass = True _freq = None # ------------------------------------------------------------------- # Constructors def __new__(cls, data=None, unit=None, freq=None, start=None, end=None, periods=None, closed=None, dtype=None, copy=False, name=None, verify_integrity=True): freq, freq_infer = dtl.maybe_infer_freq(freq) if data is None: # TODO: Remove this block and associated kwargs; GH#20535 result = cls._generate_range(start, end, periods, freq, closed=closed) result.name = name return result if is_scalar(data): raise TypeError( '{cls}() must be called with a ' 'collection of some kind, {data} was passed'.format( cls=cls.__name__, data=repr(data))) if isinstance(data, TimedeltaIndex) and freq is None and name is None: if copy: return data.copy() else: return data._shallow_copy() # - Cases checked above all return/raise before reaching here - # data, inferred_freq = sequence_to_td64ns(data, copy=copy, unit=unit) if inferred_freq is not None: if freq is not None and freq != inferred_freq: raise ValueError('Inferred frequency {inferred} from passed ' 'values does not conform to passed frequency ' '{passed}'.format(inferred=inferred_freq, passed=freq.freqstr)) elif freq_infer: freq = inferred_freq freq_infer = False verify_integrity = False subarr = cls._simple_new(data, name=name, freq=freq) # check that we are matching freqs if verify_integrity and len(subarr) > 0: if freq is not None and not freq_infer: cls._validate_frequency(subarr, freq) if freq_infer: subarr.freq = to_offset(subarr.inferred_freq) return subarr @classmethod def _simple_new(cls, values, name=None, freq=None, dtype=_TD_DTYPE): # `dtype` is passed by _shallow_copy in corner cases, should always # be timedelta64[ns] if present assert dtype == _TD_DTYPE assert isinstance(values, np.ndarray), type(values) if values.dtype == 'i8': values = values.view('m8[ns]') assert values.dtype == 'm8[ns]', values.dtype result = super(TimedeltaIndex, cls)._simple_new(values, freq) result.name = name result._reset_identity() return result # ------------------------------------------------------------------- def __setstate__(self, state): """Necessary for making this object picklable""" if isinstance(state, dict): super(TimedeltaIndex, self).__setstate__(state) else: raise Exception("invalid pickle state") _unpickle_compat = __setstate__ def _maybe_update_attributes(self, attrs): """ Update Index attributes (e.g. freq) depending on op """ freq = attrs.get('freq', None) if freq is not None: # no need to infer if freq is None attrs['freq'] = 'infer' return attrs def _evaluate_with_timedelta_like(self, other, op): result = TimedeltaArray._evaluate_with_timedelta_like(self, other, op) return wrap_arithmetic_op(self, other, result) # ------------------------------------------------------------------- # Rendering Methods @property def _formatter_func(self): from pandas.io.formats.format import _get_format_timedelta64 return _get_format_timedelta64(self, box=True) def _format_native_types(self, na_rep=u'NaT', date_format=None, **kwargs): from pandas.io.formats.format import Timedelta64Formatter return Timedelta64Formatter(values=self, nat_rep=na_rep, justify='all').get_result() # ------------------------------------------------------------------- # Wrapping TimedeltaArray __mul__ = Index.__mul__ __rmul__ = Index.__rmul__ __truediv__ = Index.__truediv__ __floordiv__ = Index.__floordiv__ __rfloordiv__ = Index.__rfloordiv__ if compat.PY2: __div__ = Index.__div__ days = wrap_field_accessor(TimedeltaArray.days) seconds = wrap_field_accessor(TimedeltaArray.seconds) microseconds = wrap_field_accessor(TimedeltaArray.microseconds) nanoseconds = wrap_field_accessor(TimedeltaArray.nanoseconds) total_seconds = wrap_array_method(TimedeltaArray.total_seconds, True) # ------------------------------------------------------------------- @Appender(_index_shared_docs['astype']) def astype(self, dtype, copy=True): dtype = pandas_dtype(dtype) if is_timedelta64_dtype(dtype) and not is_timedelta64_ns_dtype(dtype): # return an index (essentially this is division) result = self.values.astype(dtype, copy=copy) if self.hasnans: values = self._maybe_mask_results(result, fill_value=None, convert='float64') return Index(values, name=self.name) return Index(result.astype('i8'), name=self.name) return super(TimedeltaIndex, self).astype(dtype, copy=copy) def union(self, other): """ Specialized union for TimedeltaIndex objects. If combine overlapping ranges with the same DateOffset, will be much faster than Index.union Parameters ---------- other : TimedeltaIndex or array-like Returns ------- y : Index or TimedeltaIndex """ self._assert_can_do_setop(other) if len(other) == 0 or self.equals(other) or len(self) == 0: return super(TimedeltaIndex, self).union(other) if not isinstance(other, TimedeltaIndex): try: other = TimedeltaIndex(other) except (TypeError, ValueError): pass this, other = self, other if this._can_fast_union(other): return this._fast_union(other) else: result = Index.union(this, other) if isinstance(result, TimedeltaIndex): if result.freq is None: result.freq = to_offset(result.inferred_freq) return result def join(self, other, how='left', level=None, return_indexers=False, sort=False): """ See Index.join """ if _is_convertible_to_index(other): try: other = TimedeltaIndex(other) except (TypeError, ValueError): pass return Index.join(self, other, how=how, level=level, return_indexers=return_indexers, sort=sort) def _wrap_joined_index(self, joined, other): name = get_op_result_name(self, other) if (isinstance(other, TimedeltaIndex) and self.freq == other.freq and self._can_fast_union(other)): joined = self._shallow_copy(joined, name=name) return joined else: return self._simple_new(joined, name) def _can_fast_union(self, other): if not isinstance(other, TimedeltaIndex): return False freq = self.freq if freq is None or freq != other.freq: return False if not self.is_monotonic or not other.is_monotonic: return False if len(self) == 0 or len(other) == 0: return True # to make our life easier, "sort" the two ranges if self[0] <= other[0]: left, right = self, other else: left, right = other, self right_start = right[0] left_end = left[-1] # Only need to "adjoin", not overlap return (right_start == left_end + freq) or right_start in left def _fast_union(self, other): if len(other) == 0: return self.view(type(self)) if len(self) == 0: return other.view(type(self)) # to make our life easier, "sort" the two ranges if self[0] <= other[0]: left, right = self, other else: left, right = other, self left_end = left[-1] right_end = right[-1] # concatenate if left_end < right_end: loc = right.searchsorted(left_end, side='right') right_chunk = right.values[loc:] dates = _concat._concat_compat((left.values, right_chunk)) return self._shallow_copy(dates) else: return left def intersection(self, other): """ Specialized intersection for TimedeltaIndex objects. May be much faster than Index.intersection Parameters ---------- other : TimedeltaIndex or array-like Returns ------- y : Index or TimedeltaIndex """ self._assert_can_do_setop(other) if self.equals(other): return self._get_reconciled_name_object(other) if not isinstance(other, TimedeltaIndex): try: other = TimedeltaIndex(other) except (TypeError, ValueError): pass result = Index.intersection(self, other) return result if len(self) == 0: return self if len(other) == 0: return other # to make our life easier, "sort" the two ranges if self[0] <= other[0]: left, right = self, other else: left, right = other, self end = min(left[-1], right[-1]) start = right[0] if end < start: return type(self)(data=[]) else: lslice = slice(*left.slice_locs(start, end)) left_chunk = left.values[lslice] return self._shallow_copy(left_chunk) def _maybe_promote(self, other): if other.inferred_type == 'timedelta': other = TimedeltaIndex(other) return self, other def get_value(self, series, key): """ Fast lookup of value from 1-dimensional ndarray. Only use this if you know what you're doing """ if _is_convertible_to_td(key): key = Timedelta(key) return self.get_value_maybe_box(series, key) try: return com.maybe_box(self, Index.get_value(self, series, key), series, key) except KeyError: try: loc = self._get_string_slice(key) return series[loc] except (TypeError, ValueError, KeyError): pass try: return self.get_value_maybe_box(series, key) except (TypeError, ValueError, KeyError): raise KeyError(key) def get_value_maybe_box(self, series, key): if not isinstance(key, Timedelta): key = Timedelta(key) values = self._engine.get_value(com.values_from_object(series), key) return com.maybe_box(self, values, series, key) def get_loc(self, key, method=None, tolerance=None): """ Get integer location for requested label Returns ------- loc : int """ if is_list_like(key) or (isinstance(key, datetime) and key is not NaT): # GH#20464 datetime check here is to ensure we don't allow # datetime objects to be incorrectly treated as timedelta # objects; NaT is a special case because it plays a double role # as Not-A-Timedelta raise TypeError if isna(key): key = NaT if tolerance is not None: # try converting tolerance now, so errors don't get swallowed by # the try/except clauses below tolerance = self._convert_tolerance(tolerance, np.asarray(key)) if _is_convertible_to_td(key): key = Timedelta(key) return Index.get_loc(self, key, method, tolerance) try: return Index.get_loc(self, key, method, tolerance) except (KeyError, ValueError, TypeError): try: return self._get_string_slice(key) except (TypeError, KeyError, ValueError): pass try: stamp = Timedelta(key) return Index.get_loc(self, stamp, method, tolerance) except (KeyError, ValueError): raise KeyError(key) def _maybe_cast_slice_bound(self, label, side, kind): """ If label is a string, cast it to timedelta according to resolution. Parameters ---------- label : object side : {'left', 'right'} kind : {'ix', 'loc', 'getitem'} Returns ------- label : object """ assert kind in ['ix', 'loc', 'getitem', None] if isinstance(label, compat.string_types): parsed = _coerce_scalar_to_timedelta_type(label, box=True) lbound = parsed.round(parsed.resolution) if side == 'left': return lbound else: return (lbound + to_offset(parsed.resolution) - Timedelta(1, 'ns')) elif ((is_integer(label) or is_float(label)) and not is_timedelta64_dtype(label)): self._invalid_indexer('slice', label) return label def _get_string_slice(self, key): if is_integer(key) or is_float(key) or key is NaT: self._invalid_indexer('slice', key) loc = self._partial_td_slice(key) return loc def _partial_td_slice(self, key): # given a key, try to figure out a location for a partial slice if not isinstance(key, compat.string_types): return key raise NotImplementedError @Substitution(klass='TimedeltaIndex') @Appender(_shared_docs['searchsorted']) def searchsorted(self, value, side='left', sorter=None): if isinstance(value, (np.ndarray, Index)): value = np.array(value, dtype=_TD_DTYPE, copy=False) else: value = _to_m8(value) return self.values.searchsorted(value, side=side, sorter=sorter) def is_type_compatible(self, typ): return typ == self.inferred_type or typ == 'timedelta' @property def inferred_type(self): return 'timedelta64' @property def is_all_dates(self): return True def insert(self, loc, item): """ Make new Index inserting new item at location Parameters ---------- loc : int item : object if not either a Python datetime or a numpy integer-like, returned Index dtype will be object rather than datetime. Returns ------- new_index : Index """ # try to convert if possible if _is_convertible_to_td(item): try: item = Timedelta(item) except Exception: pass elif is_scalar(item) and isna(item): # GH 18295 item = self._na_value freq = None if isinstance(item, Timedelta) or (is_scalar(item) and isna(item)): # check freq can be preserved on edge cases if self.freq is not None: if ((loc == 0 or loc == -len(self)) and item + self.freq == self[0]): freq = self.freq elif (loc == len(self)) and item - self.freq == self[-1]: freq = self.freq item = _to_m8(item) try: new_tds = np.concatenate( (self[:loc].asi8, [item.view(np.int64)], self[loc:].asi8)) return self._shallow_copy(new_tds, freq=freq) except (AttributeError, TypeError): # fall back to object index if isinstance(item, compat.string_types): return self.astype(object).insert(loc, item) raise TypeError( "cannot insert TimedeltaIndex with incompatible label") def delete(self, loc): """ Make a new TimedeltaIndex with passed location(s) deleted. Parameters ---------- loc: int, slice or array of ints Indicate which sub-arrays to remove. Returns ------- new_index : TimedeltaIndex """ new_tds = np.delete(self.asi8, loc) freq = 'infer' if is_integer(loc): if loc in (0, -len(self), -1, len(self) - 1): freq = self.freq else: if is_list_like(loc): loc = lib.maybe_indices_to_slice(ensure_int64(np.array(loc)), len(self)) if isinstance(loc, slice) and loc.step in (1, None): if (loc.start in (0, None) or loc.stop in (len(self), None)): freq = self.freq return TimedeltaIndex(new_tds, name=self.name, freq=freq)
class PeriodIndex(PeriodArrayMixin, DatelikeOps, DatetimeIndexOpsMixin, Int64Index): """ Immutable ndarray holding ordinal values indicating regular periods in time such as particular years, quarters, months, etc. Index keys are boxed to Period objects which carries the metadata (eg, frequency information). Parameters ---------- data : array-like (1-dimensional), optional Optional period-like data to construct index with copy : bool Make a copy of input ndarray freq : string or period object, optional One of pandas period strings or corresponding objects start : starting value, period-like, optional If data is None, used as the start point in generating regular period data. periods : int, optional, > 0 Number of periods to generate, if generating index. Takes precedence over end argument end : end value, period-like, optional If periods is none, generated index will extend to first conforming period on or just past end argument year : int, array, or Series, default None month : int, array, or Series, default None quarter : int, array, or Series, default None day : int, array, or Series, default None hour : int, array, or Series, default None minute : int, array, or Series, default None second : int, array, or Series, default None tz : object, default None Timezone for converting datetime64 data to Periods dtype : str or PeriodDtype, default None Attributes ---------- day dayofweek dayofyear days_in_month daysinmonth end_time freq freqstr hour is_leap_year minute month quarter qyear second start_time week weekday weekofyear year Methods ------- asfreq strftime to_timestamp Examples -------- >>> idx = pd.PeriodIndex(year=year_arr, quarter=q_arr) >>> idx2 = pd.PeriodIndex(start='2000', end='2010', freq='A') See Also --------- Index : The base pandas Index type Period : Represents a period of time DatetimeIndex : Index with datetime64 data TimedeltaIndex : Index of timedelta64 data """ _typ = 'periodindex' _attributes = ['name', 'freq'] # define my properties & methods for delegation _other_ops = [] _bool_ops = ['is_leap_year'] _object_ops = ['start_time', 'end_time', 'freq'] _field_ops = [ 'year', 'month', 'day', 'hour', 'minute', 'second', 'weekofyear', 'weekday', 'week', 'dayofweek', 'dayofyear', 'quarter', 'qyear', 'days_in_month', 'daysinmonth' ] _datetimelike_ops = _field_ops + _object_ops + _bool_ops _datetimelike_methods = ['strftime', 'to_timestamp', 'asfreq'] _is_numeric_dtype = False _infer_as_myclass = True _freq = None _engine_type = libindex.PeriodEngine def __new__(cls, data=None, ordinal=None, freq=None, start=None, end=None, periods=None, tz=None, dtype=None, copy=False, name=None, **fields): valid_field_set = { 'year', 'month', 'day', 'quarter', 'hour', 'minute', 'second' } if not set(fields).issubset(valid_field_set): raise TypeError( '__new__() got an unexpected keyword argument {}'.format( list(set(fields) - valid_field_set)[0])) periods = dtl.validate_periods(periods) if name is None and hasattr(data, 'name'): name = data.name freq = dtl.validate_dtype_freq(dtype, freq) # coerce freq to freq object, otherwise it can be coerced elementwise # which is slow if freq: freq = Period._maybe_convert_freq(freq) if data is None: if ordinal is not None: data = np.asarray(ordinal, dtype=np.int64) else: data, freq = cls._generate_range(start, end, periods, freq, fields) return cls._simple_new(data, name=name, freq=freq) if isinstance(data, PeriodIndex): if freq is None or freq == data.freq: # no freq change freq = data.freq data = data._ndarray_values else: base1, _ = _gfc(data.freq) base2, _ = _gfc(freq) data = period.period_asfreq_arr(data._ndarray_values, base1, base2, 1) return cls._simple_new(data, name=name, freq=freq) # not array / index if not isinstance( data, (np.ndarray, PeriodIndex, DatetimeIndex, Int64Index)): if is_scalar(data): cls._scalar_data_error(data) # other iterable of some kind if not isinstance(data, (list, tuple)): data = list(data) data = np.asarray(data) # datetime other than period if is_datetime64_dtype(data.dtype): data = dt64arr_to_periodarr(data, freq, tz) return cls._simple_new(data, name=name, freq=freq) # check not floats if infer_dtype(data) == 'floating' and len(data) > 0: raise TypeError("PeriodIndex does not allow " "floating point in construction") # anything else, likely an array of strings or periods data = ensure_object(data) freq = freq or period.extract_freq(data) data = period.extract_ordinals(data, freq) return cls._simple_new(data, name=name, freq=freq) @cache_readonly def _engine(self): return self._engine_type(lambda: self, len(self)) @classmethod def _simple_new(cls, values, freq=None, name=None, **kwargs): result = super(PeriodIndex, cls)._simple_new(values, freq) result.name = name result._reset_identity() return result def _shallow_copy_with_infer(self, values, **kwargs): """ we always want to return a PeriodIndex """ return self._shallow_copy(values=values, **kwargs) def _coerce_scalar_to_index(self, item): """ we need to coerce a scalar to a compat for our index type Parameters ---------- item : scalar item to coerce """ return PeriodIndex([item], **self._get_attributes_dict()) @Appender(_index_shared_docs['__contains__']) def __contains__(self, key): if isinstance(key, Period): if key.freq != self.freq: return False else: return key.ordinal in self._engine else: try: self.get_loc(key) return True except Exception: return False contains = __contains__ @cache_readonly def _int64index(self): return Int64Index._simple_new(self.asi8, name=self.name) @property def values(self): return self.astype(object).values def __array__(self, dtype=None): if is_integer_dtype(dtype): return self.asi8 else: return self.astype(object).values def __array_wrap__(self, result, context=None): """ Gets called after a ufunc. Needs additional handling as PeriodIndex stores internal data as int dtype Replace this to __numpy_ufunc__ in future version """ if isinstance(context, tuple) and len(context) > 0: func = context[0] if func is np.add: pass elif func is np.subtract: name = self.name left = context[1][0] right = context[1][1] if (isinstance(left, PeriodIndex) and isinstance(right, PeriodIndex)): name = left.name if left.name == right.name else None return Index(result, name=name) elif isinstance(left, Period) or isinstance(right, Period): return Index(result, name=name) elif isinstance(func, np.ufunc): if 'M->M' not in func.types: msg = "ufunc '{0}' not supported for the PeriodIndex" # This should be TypeError, but TypeError cannot be raised # from here because numpy catches. raise ValueError(msg.format(func.__name__)) if is_bool_dtype(result): return result # the result is object dtype array of Period # cannot pass _simple_new as it is return type(self)(result, freq=self.freq, name=self.name) @property def size(self): # Avoid materializing self._values return self._ndarray_values.size @property def shape(self): # Avoid materializing self._values return self._ndarray_values.shape @property def _formatter_func(self): return lambda x: "'%s'" % x def asof_locs(self, where, mask): """ where : array of timestamps mask : array of booleans where data is not NA """ where_idx = where if isinstance(where_idx, DatetimeIndex): where_idx = PeriodIndex(where_idx.values, freq=self.freq) locs = self._ndarray_values[mask].searchsorted( where_idx._ndarray_values, side='right') locs = np.where(locs > 0, locs - 1, 0) result = np.arange(len(self))[mask].take(locs) first = mask.argmax() result[(locs == 0) & (where_idx._ndarray_values < self._ndarray_values[first])] = -1 return result @Appender(_index_shared_docs['astype']) def astype(self, dtype, copy=True, how='start'): dtype = pandas_dtype(dtype) if is_integer_dtype(dtype): return self._int64index.copy() if copy else self._int64index elif is_datetime64_any_dtype(dtype): tz = getattr(dtype, 'tz', None) return self.to_timestamp(how=how).tz_localize(tz) elif is_period_dtype(dtype): return self.asfreq(freq=dtype.freq) return super(PeriodIndex, self).astype(dtype, copy=copy) @Substitution(klass='PeriodIndex') @Appender(_shared_docs['searchsorted']) def searchsorted(self, value, side='left', sorter=None): if isinstance(value, Period): if value.freq != self.freq: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, value.freqstr) raise IncompatibleFrequency(msg) value = value.ordinal elif isinstance(value, compat.string_types): value = Period(value, freq=self.freq).ordinal return self._ndarray_values.searchsorted(value, side=side, sorter=sorter) @property def is_all_dates(self): return True @property def is_full(self): """ Returns True if this PeriodIndex is range-like in that all Periods between start and end are present, in order. """ if len(self) == 0: return True if not self.is_monotonic: raise ValueError('Index is not monotonic') values = self.asi8 return ((values[1:] - values[:-1]) < 2).all() year = wrap_field_accessor(PeriodArrayMixin.year) month = wrap_field_accessor(PeriodArrayMixin.month) day = wrap_field_accessor(PeriodArrayMixin.day) hour = wrap_field_accessor(PeriodArrayMixin.hour) minute = wrap_field_accessor(PeriodArrayMixin.minute) second = wrap_field_accessor(PeriodArrayMixin.second) weekofyear = wrap_field_accessor(PeriodArrayMixin.week) week = weekofyear dayofweek = wrap_field_accessor(PeriodArrayMixin.dayofweek) weekday = dayofweek dayofyear = day_of_year = wrap_field_accessor(PeriodArrayMixin.dayofyear) quarter = wrap_field_accessor(PeriodArrayMixin.quarter) qyear = wrap_field_accessor(PeriodArrayMixin.qyear) days_in_month = wrap_field_accessor(PeriodArrayMixin.days_in_month) daysinmonth = days_in_month to_timestamp = wrap_array_method(PeriodArrayMixin.to_timestamp, True) @property @Appender(PeriodArrayMixin.start_time.__doc__) def start_time(self): return PeriodArrayMixin.start_time.fget(self) @property @Appender(PeriodArrayMixin.end_time.__doc__) def end_time(self): return PeriodArrayMixin.end_time.fget(self) def _mpl_repr(self): # how to represent ourselves to matplotlib return self.astype(object).values @property def inferred_type(self): # b/c data is represented as ints make sure we can't have ambiguous # indexing return 'period' def get_value(self, series, key): """ Fast lookup of value from 1-dimensional ndarray. Only use this if you know what you're doing """ s = com.values_from_object(series) try: return com.maybe_box(self, super(PeriodIndex, self).get_value(s, key), series, key) except (KeyError, IndexError): try: asdt, parsed, reso = parse_time_string(key, self.freq) grp = resolution.Resolution.get_freq_group(reso) freqn = resolution.get_freq_group(self.freq) vals = self._ndarray_values # if our data is higher resolution than requested key, slice if grp < freqn: iv = Period(asdt, freq=(grp, 1)) ord1 = iv.asfreq(self.freq, how='S').ordinal ord2 = iv.asfreq(self.freq, how='E').ordinal if ord2 < vals[0] or ord1 > vals[-1]: raise KeyError(key) pos = np.searchsorted(self._ndarray_values, [ord1, ord2]) key = slice(pos[0], pos[1] + 1) return series[key] elif grp == freqn: key = Period(asdt, freq=self.freq).ordinal return com.maybe_box(self, self._engine.get_value(s, key), series, key) else: raise KeyError(key) except TypeError: pass key = Period(key, self.freq).ordinal return com.maybe_box(self, self._engine.get_value(s, key), series, key) @Appender(_index_shared_docs['get_indexer'] % _index_doc_kwargs) def get_indexer(self, target, method=None, limit=None, tolerance=None): target = ensure_index(target) if hasattr(target, 'freq') and target.freq != self.freq: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, target.freqstr) raise IncompatibleFrequency(msg) if isinstance(target, PeriodIndex): target = target.asi8 if tolerance is not None: tolerance = self._convert_tolerance(tolerance, target) return Index.get_indexer(self._int64index, target, method, limit, tolerance) def _get_unique_index(self, dropna=False): """ wrap Index._get_unique_index to handle NaT """ res = super(PeriodIndex, self)._get_unique_index(dropna=dropna) if dropna: res = res.dropna() return res @Appender(Index.unique.__doc__) def unique(self, level=None): # override the Index.unique method for performance GH#23083 if level is not None: # this should never occur, but is retained to make the signature # match Index.unique self._validate_index_level(level) values = self._ndarray_values result = unique1d(values) return self._shallow_copy(result) def get_loc(self, key, method=None, tolerance=None): """ Get integer location for requested label Returns ------- loc : int """ try: return self._engine.get_loc(key) except KeyError: if is_integer(key): raise try: asdt, parsed, reso = parse_time_string(key, self.freq) key = asdt except TypeError: pass try: key = Period(key, freq=self.freq) except ValueError: # we cannot construct the Period # as we have an invalid type raise KeyError(key) try: ordinal = tslib.iNaT if key is tslib.NaT else key.ordinal if tolerance is not None: tolerance = self._convert_tolerance( tolerance, np.asarray(key)) return self._int64index.get_loc(ordinal, method, tolerance) except KeyError: raise KeyError(key) def _maybe_cast_slice_bound(self, label, side, kind): """ If label is a string or a datetime, cast it to Period.ordinal according to resolution. Parameters ---------- label : object side : {'left', 'right'} kind : {'ix', 'loc', 'getitem'} Returns ------- bound : Period or object Notes ----- Value of `side` parameter should be validated in caller. """ assert kind in ['ix', 'loc', 'getitem'] if isinstance(label, datetime): return Period(label, freq=self.freq) elif isinstance(label, compat.string_types): try: _, parsed, reso = parse_time_string(label, self.freq) bounds = self._parsed_string_to_bounds(reso, parsed) return bounds[0 if side == 'left' else 1] except Exception: raise KeyError(label) elif is_integer(label) or is_float(label): self._invalid_indexer('slice', label) return label def _parsed_string_to_bounds(self, reso, parsed): if reso == 'year': t1 = Period(year=parsed.year, freq='A') elif reso == 'month': t1 = Period(year=parsed.year, month=parsed.month, freq='M') elif reso == 'quarter': q = (parsed.month - 1) // 3 + 1 t1 = Period(year=parsed.year, quarter=q, freq='Q-DEC') elif reso == 'day': t1 = Period(year=parsed.year, month=parsed.month, day=parsed.day, freq='D') elif reso == 'hour': t1 = Period(year=parsed.year, month=parsed.month, day=parsed.day, hour=parsed.hour, freq='H') elif reso == 'minute': t1 = Period(year=parsed.year, month=parsed.month, day=parsed.day, hour=parsed.hour, minute=parsed.minute, freq='T') elif reso == 'second': t1 = Period(year=parsed.year, month=parsed.month, day=parsed.day, hour=parsed.hour, minute=parsed.minute, second=parsed.second, freq='S') else: raise KeyError(reso) return (t1.asfreq(self.freq, how='start'), t1.asfreq(self.freq, how='end')) def _get_string_slice(self, key): if not self.is_monotonic: raise ValueError('Partial indexing only valid for ' 'ordered time series') key, parsed, reso = parse_time_string(key, self.freq) grp = resolution.Resolution.get_freq_group(reso) freqn = resolution.get_freq_group(self.freq) if reso in ['day', 'hour', 'minute', 'second'] and not grp < freqn: raise KeyError(key) t1, t2 = self._parsed_string_to_bounds(reso, parsed) return slice(self.searchsorted(t1.ordinal, side='left'), self.searchsorted(t2.ordinal, side='right')) def _convert_tolerance(self, tolerance, target): tolerance = DatetimeIndexOpsMixin._convert_tolerance( self, tolerance, target) if target.size != tolerance.size and tolerance.size > 1: raise ValueError('list-like tolerance size must match ' 'target index size') return self._maybe_convert_timedelta(tolerance) def insert(self, loc, item): if not isinstance(item, Period) or self.freq != item.freq: return self.astype(object).insert(loc, item) idx = np.concatenate( (self[:loc].asi8, np.array([item.ordinal]), self[loc:].asi8)) return self._shallow_copy(idx) def join(self, other, how='left', level=None, return_indexers=False, sort=False): """ See Index.join """ self._assert_can_do_setop(other) result = Int64Index.join(self, other, how=how, level=level, return_indexers=return_indexers, sort=sort) if return_indexers: result, lidx, ridx = result return self._apply_meta(result), lidx, ridx return self._apply_meta(result) def _assert_can_do_setop(self, other): super(PeriodIndex, self)._assert_can_do_setop(other) if not isinstance(other, PeriodIndex): raise ValueError('can only call with other PeriodIndex-ed objects') if self.freq != other.freq: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) raise IncompatibleFrequency(msg) def _wrap_union_result(self, other, result): name = self.name if self.name == other.name else None result = self._apply_meta(result) result.name = name return result def _apply_meta(self, rawarr): if not isinstance(rawarr, PeriodIndex): rawarr = PeriodIndex._simple_new(rawarr, freq=self.freq, name=self.name) return rawarr def _format_native_types(self, na_rep=u'NaT', date_format=None, **kwargs): values = self.astype(object).values if date_format: formatter = lambda dt: dt.strftime(date_format) else: formatter = lambda dt: u'%s' % dt if self.hasnans: mask = self._isnan values[mask] = na_rep imask = ~mask values[imask] = np.array([formatter(dt) for dt in values[imask]]) else: values = np.array([formatter(dt) for dt in values]) return values def __setstate__(self, state): """Necessary for making this object picklable""" if isinstance(state, dict): super(PeriodIndex, self).__setstate__(state) elif isinstance(state, tuple): # < 0.15 compat if len(state) == 2: nd_state, own_state = state data = np.empty(nd_state[1], dtype=nd_state[2]) np.ndarray.__setstate__(data, nd_state) # backcompat self._freq = Period._maybe_convert_freq(own_state[1]) else: # pragma: no cover data = np.empty(state) np.ndarray.__setstate__(self, state) self._data = data else: raise Exception("invalid pickle state") _unpickle_compat = __setstate__