Skip to content

Commit 971c60e

Browse files
authored
DTA/TDA/PA use self._data instead of self.asi8 for self._ndarray (#36171)
1 parent 7584e59 commit 971c60e

File tree

5 files changed

+43
-23
lines changed

5 files changed

+43
-23
lines changed

pandas/core/arrays/datetimelike.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from pandas.compat import set_function_name
2828
from pandas.compat.numpy import function as nv
2929
from pandas.errors import AbstractMethodError, NullFrequencyError, PerformanceWarning
30-
from pandas.util._decorators import Appender, Substitution
30+
from pandas.util._decorators import Appender, Substitution, cache_readonly
3131
from pandas.util._validators import validate_fillna_kwargs
3232

3333
from pandas.core.dtypes.common import (
@@ -175,6 +175,14 @@ def _scalar_from_string(self, value: str) -> DTScalarOrNaT:
175175
"""
176176
raise AbstractMethodError(self)
177177

178+
@classmethod
179+
def _rebox_native(cls, value: int) -> Union[int, np.datetime64, np.timedelta64]:
180+
"""
181+
Box an integer unboxed via _unbox_scalar into the native type for
182+
the underlying ndarray.
183+
"""
184+
raise AbstractMethodError(cls)
185+
178186
def _unbox_scalar(self, value: DTScalarOrNaT) -> int:
179187
"""
180188
Unbox the integer value of a scalar `value`.
@@ -458,18 +466,15 @@ class DatetimeLikeArrayMixin(
458466
# ------------------------------------------------------------------
459467
# NDArrayBackedExtensionArray compat
460468

461-
# TODO: make this a cache_readonly; need to get around _index_data
462-
# kludge in libreduction
463-
@property
469+
@cache_readonly
464470
def _ndarray(self) -> np.ndarray:
465-
# NB: A bunch of Interval tests fail if we use ._data
466-
return self.asi8
471+
return self._data
467472

468473
def _from_backing_data(self: _T, arr: np.ndarray) -> _T:
469474
# Note: we do not retain `freq`
470-
# error: Too many arguments for "NDArrayBackedExtensionArray"
471-
# error: Unexpected keyword argument "dtype" for "NDArrayBackedExtensionArray"
472-
return type(self)(arr, dtype=self.dtype) # type: ignore[call-arg]
475+
return type(self)._simple_new( # type: ignore[attr-defined]
476+
arr, dtype=self.dtype
477+
)
473478

474479
# ------------------------------------------------------------------
475480

@@ -526,7 +531,7 @@ def __array__(self, dtype=None) -> np.ndarray:
526531
# used for Timedelta/DatetimeArray, overwritten by PeriodArray
527532
if is_object_dtype(dtype):
528533
return np.array(list(self), dtype=object)
529-
return self._data
534+
return self._ndarray
530535

531536
def __getitem__(self, key):
532537
"""
@@ -536,7 +541,7 @@ def __getitem__(self, key):
536541

537542
if lib.is_integer(key):
538543
# fast-path
539-
result = self._data[key]
544+
result = self._ndarray[key]
540545
if self.ndim == 1:
541546
return self._box_func(result)
542547
return self._simple_new(result, dtype=self.dtype)
@@ -557,7 +562,7 @@ def __getitem__(self, key):
557562
key = check_array_indexer(self, key)
558563

559564
freq = self._get_getitem_freq(key)
560-
result = self._data[key]
565+
result = self._ndarray[key]
561566
if lib.is_scalar(result):
562567
return self._box_func(result)
563568
return self._simple_new(result, dtype=self.dtype, freq=freq)
@@ -612,7 +617,7 @@ def __setitem__(
612617

613618
value = self._validate_setitem_value(value)
614619
key = check_array_indexer(self, key)
615-
self._data[key] = value
620+
self._ndarray[key] = value
616621
self._maybe_clear_freq()
617622

618623
def _maybe_clear_freq(self):
@@ -663,8 +668,8 @@ def astype(self, dtype, copy=True):
663668

664669
def view(self, dtype=None):
665670
if dtype is None or dtype is self.dtype:
666-
return type(self)(self._data, dtype=self.dtype)
667-
return self._data.view(dtype=dtype)
671+
return type(self)(self._ndarray, dtype=self.dtype)
672+
return self._ndarray.view(dtype=dtype)
668673

669674
# ------------------------------------------------------------------
670675
# ExtensionArray Interface
@@ -705,7 +710,7 @@ def _from_factorized(cls, values, original):
705710
return cls(values, dtype=original.dtype)
706711

707712
def _values_for_argsort(self):
708-
return self._data
713+
return self._ndarray
709714

710715
# ------------------------------------------------------------------
711716
# Validation Methods
@@ -722,7 +727,7 @@ def _validate_fill_value(self, fill_value):
722727
723728
Returns
724729
-------
725-
fill_value : np.int64
730+
fill_value : np.int64, np.datetime64, or np.timedelta64
726731
727732
Raises
728733
------
@@ -736,7 +741,8 @@ def _validate_fill_value(self, fill_value):
736741
fill_value = self._validate_scalar(fill_value, msg)
737742
except TypeError as err:
738743
raise ValueError(msg) from err
739-
return self._unbox(fill_value)
744+
rv = self._unbox(fill_value)
745+
return self._rebox_native(rv)
740746

741747
def _validate_shift_value(self, fill_value):
742748
# TODO(2.0): once this deprecation is enforced, use _validate_fill_value
@@ -951,9 +957,9 @@ def value_counts(self, dropna=False):
951957
from pandas import Index, Series
952958

953959
if dropna:
954-
values = self[~self.isna()]._data
960+
values = self[~self.isna()]._ndarray
955961
else:
956-
values = self._data
962+
values = self._ndarray
957963

958964
cls = type(self)
959965

@@ -1044,9 +1050,9 @@ def fillna(self, value=None, method=None, limit=None):
10441050
else:
10451051
func = missing.backfill_1d
10461052

1047-
values = self._data
1053+
values = self._ndarray
10481054
if not is_period_dtype(self.dtype):
1049-
# For PeriodArray self._data is i8, which gets copied
1055+
# For PeriodArray self._ndarray is i8, which gets copied
10501056
# by `func`. Otherwise we need to make a copy manually
10511057
# to avoid modifying `self` in-place.
10521058
values = values.copy()

pandas/core/arrays/datetimes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,10 @@ def _generate_range(
446446
# -----------------------------------------------------------------
447447
# DatetimeLike Interface
448448

449+
@classmethod
450+
def _rebox_native(cls, value: int) -> np.datetime64:
451+
return np.int64(value).view("M8[ns]")
452+
449453
def _unbox_scalar(self, value):
450454
if not isinstance(value, self._scalar_type) and value is not NaT:
451455
raise ValueError("'value' should be a Timestamp.")

pandas/core/arrays/period.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ def _generate_range(cls, start, end, periods, freq, fields):
253253
# -----------------------------------------------------------------
254254
# DatetimeLike Interface
255255

256+
@classmethod
257+
def _rebox_native(cls, value: int) -> np.int64:
258+
return np.int64(value)
259+
256260
def _unbox_scalar(self, value: Union[Period, NaTType]) -> int:
257261
if value is NaT:
258262
return value.value

pandas/core/arrays/timedeltas.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ def _generate_range(cls, start, end, periods, freq, closed=None):
271271
# ----------------------------------------------------------------
272272
# DatetimeLike Interface
273273

274+
@classmethod
275+
def _rebox_native(cls, value: int) -> np.timedelta64:
276+
return np.int64(value).view("m8[ns]")
277+
274278
def _unbox_scalar(self, value):
275279
if not isinstance(value, self._scalar_type) and value is not NaT:
276280
raise ValueError("'value' should be a Timedelta.")

pandas/tests/frame/indexing/test_datetime.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ def test_setitem(self, timezone_frame):
2323
b1 = df._mgr.blocks[1]
2424
b2 = df._mgr.blocks[2]
2525
tm.assert_extension_array_equal(b1.values, b2.values)
26-
assert id(b1.values._data.base) != id(b2.values._data.base)
26+
b1base = b1.values._data.base
27+
b2base = b2.values._data.base
28+
assert b1base is None or (id(b1base) != id(b2base))
2729

2830
# with nan
2931
df2 = df.copy()

0 commit comments

Comments
 (0)