|
18 | 18 | from pandas.core.algorithms import checked_add_with_arr
|
19 | 19 |
|
20 | 20 |
|
21 |
| -class DatetimeLikeArrayMixin(object): |
| 21 | +class AttributesMixin(object): |
| 22 | + |
| 23 | + @property |
| 24 | + def _attributes(self): |
| 25 | + # Inheriting subclass should implement _attributes as a list of strings |
| 26 | + from pandas.errors import AbstractMethodError |
| 27 | + raise AbstractMethodError(self) |
| 28 | + |
| 29 | + @classmethod |
| 30 | + def _simple_new(cls, values, **kwargs): |
| 31 | + from pandas.errors import AbstractMethodError |
| 32 | + raise AbstractMethodError(cls) |
| 33 | + |
| 34 | + def _get_attributes_dict(self): |
| 35 | + """return an attributes dict for my class""" |
| 36 | + return {k: getattr(self, k, None) for k in self._attributes} |
| 37 | + |
| 38 | + def _shallow_copy(self, values=None, **kwargs): |
| 39 | + if values is None: |
| 40 | + # Note: slightly different from Index implementation which defaults |
| 41 | + # to self.values |
| 42 | + values = self._ndarray_values |
| 43 | + |
| 44 | + attributes = self._get_attributes_dict() |
| 45 | + attributes.update(kwargs) |
| 46 | + if not len(values) and 'dtype' not in kwargs: |
| 47 | + attributes['dtype'] = self.dtype |
| 48 | + return self._simple_new(values, **attributes) |
| 49 | + |
| 50 | + |
| 51 | +class DatetimeLikeArrayMixin(AttributesMixin): |
22 | 52 | """
|
23 | 53 | Shared Base/Mixin class for DatetimeArray, TimedeltaArray, PeriodArray
|
24 | 54 |
|
@@ -56,9 +86,61 @@ def asi8(self):
|
56 | 86 | # do not cache or you'll create a memory leak
|
57 | 87 | return self.values.view('i8')
|
58 | 88 |
|
| 89 | + # ------------------------------------------------------------------ |
| 90 | + # Array-like Methods |
| 91 | + |
59 | 92 | def __len__(self):
|
60 | 93 | return len(self._data)
|
61 | 94 |
|
| 95 | + def __getitem__(self, key): |
| 96 | + """ |
| 97 | + This getitem defers to the underlying array, which by-definition can |
| 98 | + only handle list-likes, slices, and integer scalars |
| 99 | + """ |
| 100 | + |
| 101 | + is_int = lib.is_integer(key) |
| 102 | + if lib.is_scalar(key) and not is_int: |
| 103 | + raise IndexError("only integers, slices (`:`), ellipsis (`...`), " |
| 104 | + "numpy.newaxis (`None`) and integer or boolean " |
| 105 | + "arrays are valid indices") |
| 106 | + |
| 107 | + getitem = self._data.__getitem__ |
| 108 | + if is_int: |
| 109 | + val = getitem(key) |
| 110 | + return self._box_func(val) |
| 111 | + else: |
| 112 | + if com.is_bool_indexer(key): |
| 113 | + key = np.asarray(key) |
| 114 | + if key.all(): |
| 115 | + key = slice(0, None, None) |
| 116 | + else: |
| 117 | + key = lib.maybe_booleans_to_slice(key.view(np.uint8)) |
| 118 | + |
| 119 | + attribs = self._get_attributes_dict() |
| 120 | + |
| 121 | + is_period = is_period_dtype(self) |
| 122 | + if is_period: |
| 123 | + freq = self.freq |
| 124 | + else: |
| 125 | + freq = None |
| 126 | + if isinstance(key, slice): |
| 127 | + if self.freq is not None and key.step is not None: |
| 128 | + freq = key.step * self.freq |
| 129 | + else: |
| 130 | + freq = self.freq |
| 131 | + |
| 132 | + attribs['freq'] = freq |
| 133 | + |
| 134 | + result = getitem(key) |
| 135 | + if result.ndim > 1: |
| 136 | + # To support MPL which performs slicing with 2 dim |
| 137 | + # even though it only has 1 dim by definition |
| 138 | + if is_period: |
| 139 | + return self._simple_new(result, **attribs) |
| 140 | + return result |
| 141 | + |
| 142 | + return self._simple_new(result, **attribs) |
| 143 | + |
62 | 144 | # ------------------------------------------------------------------
|
63 | 145 | # Null Handling
|
64 | 146 |
|
@@ -97,6 +179,27 @@ def _maybe_mask_results(self, result, fill_value=None, convert=None):
|
97 | 179 | result[self._isnan] = fill_value
|
98 | 180 | return result
|
99 | 181 |
|
| 182 | + def _nat_new(self, box=True): |
| 183 | + """ |
| 184 | + Return Array/Index or ndarray filled with NaT which has the same |
| 185 | + length as the caller. |
| 186 | +
|
| 187 | + Parameters |
| 188 | + ---------- |
| 189 | + box : boolean, default True |
| 190 | + - If True returns a Array/Index as the same as caller. |
| 191 | + - If False returns ndarray of np.int64. |
| 192 | + """ |
| 193 | + result = np.zeros(len(self), dtype=np.int64) |
| 194 | + result.fill(iNaT) |
| 195 | + if not box: |
| 196 | + return result |
| 197 | + |
| 198 | + attribs = self._get_attributes_dict() |
| 199 | + if not is_period_dtype(self): |
| 200 | + attribs['freq'] = None |
| 201 | + return self._simple_new(result, **attribs) |
| 202 | + |
100 | 203 | # ------------------------------------------------------------------
|
101 | 204 | # Frequency Properties/Methods
|
102 | 205 |
|
@@ -195,6 +298,17 @@ def _add_delta_tdi(self, other):
|
195 | 298 | new_values[mask] = iNaT
|
196 | 299 | return new_values.view('i8')
|
197 | 300 |
|
| 301 | + def _add_nat(self): |
| 302 | + """Add pd.NaT to self""" |
| 303 | + if is_period_dtype(self): |
| 304 | + raise TypeError('Cannot add {cls} and {typ}' |
| 305 | + .format(cls=type(self).__name__, |
| 306 | + typ=type(NaT).__name__)) |
| 307 | + |
| 308 | + # GH#19124 pd.NaT is treated like a timedelta for both timedelta |
| 309 | + # and datetime dtypes |
| 310 | + return self._nat_new(box=True) |
| 311 | + |
198 | 312 | def _sub_nat(self):
|
199 | 313 | """Subtract pd.NaT from self"""
|
200 | 314 | # GH#19124 Timedelta - datetime is not in general well-defined.
|
|
0 commit comments