Skip to content

Commit 8ce6740

Browse files
authored
DEPR: DatetimeArray/TimedeltaArray.__init__ (#56043)
* DEPR: DatetimeArray/TimedeltaArray.__init__ * mypy fixup * fix PeriodArray test * update doctest * remove accidental
1 parent 4ac340e commit 8ce6740

26 files changed

+258
-193
lines changed

doc/source/whatsnew/v2.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ Other Deprecations
443443
- Deprecated :func:`pd.core.internals.api.make_block`, use public APIs instead (:issue:`40226`)
444444
- Deprecated :func:`read_gbq` and :meth:`DataFrame.to_gbq`. Use ``pandas_gbq.read_gbq`` and ``pandas_gbq.to_gbq`` instead https://pandas-gbq.readthedocs.io/en/latest/api.html (:issue:`55525`)
445445
- Deprecated :meth:`.DataFrameGroupBy.fillna` and :meth:`.SeriesGroupBy.fillna`; use :meth:`.DataFrameGroupBy.ffill`, :meth:`.DataFrameGroupBy.bfill` for forward and backward filling or :meth:`.DataFrame.fillna` to fill with a single value (or the Series equivalents) (:issue:`55718`)
446+
- Deprecated :meth:`DatetimeArray.__init__` and :meth:`TimedeltaArray.__init__`, use :func:`array` instead (:issue:`55623`)
446447
- Deprecated :meth:`Index.format`, use ``index.astype(str)`` or ``index.map(formatter)`` instead (:issue:`55413`)
447448
- Deprecated :meth:`Series.ravel`, the underlying array is already 1D, so ravel is not necessary (:issue:`52511`)
448449
- Deprecated :meth:`Series.resample` and :meth:`DataFrame.resample` with a :class:`PeriodIndex` (and the 'convention' keyword), convert to :class:`DatetimeIndex` (with ``.to_timestamp()``) before resampling instead (:issue:`53481`)

pandas/core/arrays/_mixins.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -133,21 +133,20 @@ def view(self, dtype: Dtype | None = None) -> ArrayLike:
133133
cls = dtype.construct_array_type()
134134
return cls(arr.view("i8"), dtype=dtype)
135135
elif isinstance(dtype, DatetimeTZDtype):
136-
# error: Incompatible types in assignment (expression has type
137-
# "type[DatetimeArray]", variable has type "type[PeriodArray]")
138-
cls = dtype.construct_array_type() # type: ignore[assignment]
136+
dt_cls = dtype.construct_array_type()
139137
dt64_values = arr.view(f"M8[{dtype.unit}]")
140-
return cls(dt64_values, dtype=dtype)
138+
return dt_cls._simple_new(dt64_values, dtype=dtype)
141139
elif lib.is_np_dtype(dtype, "M") and is_supported_dtype(dtype):
142140
from pandas.core.arrays import DatetimeArray
143141

144142
dt64_values = arr.view(dtype)
145-
return DatetimeArray(dt64_values, dtype=dtype)
143+
return DatetimeArray._simple_new(dt64_values, dtype=dtype)
144+
146145
elif lib.is_np_dtype(dtype, "m") and is_supported_dtype(dtype):
147146
from pandas.core.arrays import TimedeltaArray
148147

149148
td64_values = arr.view(dtype)
150-
return TimedeltaArray(td64_values, dtype=dtype)
149+
return TimedeltaArray._simple_new(td64_values, dtype=dtype)
151150

152151
# error: Argument "dtype" to "view" of "_ArrayOrScalarCommon" has incompatible
153152
# type "Union[ExtensionDtype, dtype[Any]]"; expected "Union[dtype[Any], None,

pandas/core/arrays/datetimelike.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def _unbox_scalar(
269269
270270
Examples
271271
--------
272-
>>> arr = pd.arrays.DatetimeArray(np.array(['1970-01-01'], 'datetime64[ns]'))
272+
>>> arr = pd.array(np.array(['1970-01-01'], 'datetime64[ns]'))
273273
>>> arr._unbox_scalar(arr[0])
274274
numpy.datetime64('1970-01-01T00:00:00.000000000')
275275
"""
@@ -1409,7 +1409,7 @@ def __add__(self, other):
14091409
if isinstance(result, np.ndarray) and lib.is_np_dtype(result.dtype, "m"):
14101410
from pandas.core.arrays import TimedeltaArray
14111411

1412-
return TimedeltaArray(result)
1412+
return TimedeltaArray._from_sequence(result)
14131413
return result
14141414

14151415
def __radd__(self, other):
@@ -1469,7 +1469,7 @@ def __sub__(self, other):
14691469
if isinstance(result, np.ndarray) and lib.is_np_dtype(result.dtype, "m"):
14701470
from pandas.core.arrays import TimedeltaArray
14711471

1472-
return TimedeltaArray(result)
1472+
return TimedeltaArray._from_sequence(result)
14731473
return result
14741474

14751475
def __rsub__(self, other):
@@ -1488,7 +1488,7 @@ def __rsub__(self, other):
14881488
# Avoid down-casting DatetimeIndex
14891489
from pandas.core.arrays import DatetimeArray
14901490

1491-
other = DatetimeArray(other)
1491+
other = DatetimeArray._from_sequence(other)
14921492
return other - self
14931493
elif self.dtype.kind == "M" and hasattr(other, "dtype") and not other_is_dt64:
14941494
# GH#19959 datetime - datetime is well-defined as timedelta,
@@ -1725,7 +1725,7 @@ def _groupby_op(
17251725
self = cast("DatetimeArray | TimedeltaArray", self)
17261726
new_dtype = f"m8[{self.unit}]"
17271727
res_values = res_values.view(new_dtype)
1728-
return TimedeltaArray(res_values)
1728+
return TimedeltaArray._simple_new(res_values, dtype=res_values.dtype)
17291729

17301730
res_values = res_values.view(self._ndarray.dtype)
17311731
return self._from_backing_data(res_values)
@@ -1944,6 +1944,13 @@ class TimelikeOps(DatetimeLikeArrayMixin):
19441944
def __init__(
19451945
self, values, dtype=None, freq=lib.no_default, copy: bool = False
19461946
) -> None:
1947+
warnings.warn(
1948+
# GH#55623
1949+
f"{type(self).__name__}.__init__ is deprecated and will be "
1950+
"removed in a future version. Use pd.array instead.",
1951+
FutureWarning,
1952+
stacklevel=find_stack_level(),
1953+
)
19471954
if dtype is not None:
19481955
dtype = pandas_dtype(dtype)
19491956

pandas/core/arrays/datetimes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ class DatetimeArray(dtl.TimelikeOps, dtl.DatelikeOps): # type: ignore[misc]
202202
203203
Examples
204204
--------
205-
>>> pd.arrays.DatetimeArray(pd.DatetimeIndex(['2023-01-01', '2023-01-02']),
206-
... freq='D')
205+
>>> pd.arrays.DatetimeArray._from_sequence(
206+
... pd.DatetimeIndex(['2023-01-01', '2023-01-02'], freq='D'))
207207
<DatetimeArray>
208208
['2023-01-01 00:00:00', '2023-01-02 00:00:00']
209209
Length: 2, dtype: datetime64[ns]

pandas/core/arrays/period.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ def to_timestamp(self, freq=None, how: str = "start") -> DatetimeArray:
664664
new_parr = self.asfreq(freq, how=how)
665665

666666
new_data = libperiod.periodarr_to_dt64arr(new_parr.asi8, base)
667-
dta = DatetimeArray(new_data)
667+
dta = DatetimeArray._from_sequence(new_data)
668668

669669
if self.freq.name == "B":
670670
# See if we can retain BDay instead of Day in cases where

pandas/core/arrays/timedeltas.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class TimedeltaArray(dtl.TimelikeOps):
132132
133133
Examples
134134
--------
135-
>>> pd.arrays.TimedeltaArray(pd.TimedeltaIndex(['1h', '2h']))
135+
>>> pd.arrays.TimedeltaArray._from_sequence(pd.TimedeltaIndex(['1h', '2h']))
136136
<TimedeltaArray>
137137
['0 days 01:00:00', '0 days 02:00:00']
138138
Length: 2, dtype: timedelta64[ns]
@@ -709,11 +709,13 @@ def __neg__(self) -> TimedeltaArray:
709709
return type(self)._simple_new(-self._ndarray, dtype=self.dtype, freq=freq)
710710

711711
def __pos__(self) -> TimedeltaArray:
712-
return type(self)(self._ndarray.copy(), freq=self.freq)
712+
return type(self)._simple_new(
713+
self._ndarray.copy(), dtype=self.dtype, freq=self.freq
714+
)
713715

714716
def __abs__(self) -> TimedeltaArray:
715717
# Note: freq is not preserved
716-
return type(self)(np.abs(self._ndarray))
718+
return type(self)._simple_new(np.abs(self._ndarray), dtype=self.dtype)
717719

718720
# ----------------------------------------------------------------
719721
# Conversion Methods - Vectorized analogues of Timedelta methods

pandas/core/dtypes/dtypes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,7 @@ def __from_arrow__(self, array: pa.Array | pa.ChunkedArray) -> DatetimeArray:
919919
else:
920920
np_arr = array.to_numpy()
921921

922-
return DatetimeArray(np_arr, dtype=self, copy=False)
922+
return DatetimeArray._from_sequence(np_arr, dtype=self, copy=False)
923923

924924
def __setstate__(self, state) -> None:
925925
# for pickle compat. __get_state__ is defined in the

pandas/core/indexes/datetimelike.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,10 @@ def _fast_union(self, other: Self, sort=None) -> Self:
697697
dates = concat_compat([left._values, right_chunk])
698698
# The can_fast_union check ensures that the result.freq
699699
# should match self.freq
700-
dates = type(self._data)(dates, freq=self.freq)
700+
assert isinstance(dates, type(self._data))
701+
# error: Item "ExtensionArray" of "ExtensionArray |
702+
# ndarray[Any, Any]" has no attribute "_freq"
703+
assert dates._freq == self.freq # type: ignore[union-attr]
701704
result = type(self)._simple_new(dates)
702705
return result
703706
else:

pandas/core/internals/managers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2345,7 +2345,7 @@ def make_na_array(dtype: DtypeObj, shape: Shape, fill_value) -> ArrayLike:
23452345
ts = Timestamp(fill_value).as_unit(dtype.unit)
23462346
i8values = np.full(shape, ts._value)
23472347
dt64values = i8values.view(f"M8[{dtype.unit}]")
2348-
return DatetimeArray(dt64values, dtype=dtype)
2348+
return DatetimeArray._simple_new(dt64values, dtype=dtype)
23492349

23502350
elif is_1d_only_ea_dtype(dtype):
23512351
dtype = cast(ExtensionDtype, dtype)

pandas/core/ops/array_ops.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ def maybe_prepare_scalar_for_op(obj, shape: Shape):
545545
new_dtype = get_supported_dtype(obj.dtype)
546546
obj = obj.astype(new_dtype)
547547
right = np.broadcast_to(obj, shape)
548-
return DatetimeArray(right)
548+
return DatetimeArray._simple_new(right, dtype=right.dtype)
549549

550550
return Timestamp(obj)
551551

@@ -563,7 +563,7 @@ def maybe_prepare_scalar_for_op(obj, shape: Shape):
563563
new_dtype = get_supported_dtype(obj.dtype)
564564
obj = obj.astype(new_dtype)
565565
right = np.broadcast_to(obj, shape)
566-
return TimedeltaArray(right)
566+
return TimedeltaArray._simple_new(right, dtype=right.dtype)
567567

568568
# In particular non-nanosecond timedelta64 needs to be cast to
569569
# nanoseconds, or else we get undesired behavior like

pandas/tests/arrays/datetimes/test_constructors.py

+63-40
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ def test_from_sequence_invalid_type(self):
1919
def test_only_1dim_accepted(self):
2020
arr = np.array([0, 1, 2, 3], dtype="M8[h]").astype("M8[ns]")
2121

22-
with pytest.raises(ValueError, match="Only 1-dimensional"):
23-
# 3-dim, we allow 2D to sneak in for ops purposes GH#29853
24-
DatetimeArray(arr.reshape(2, 2, 1))
22+
depr_msg = "DatetimeArray.__init__ is deprecated"
23+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
24+
with pytest.raises(ValueError, match="Only 1-dimensional"):
25+
# 3-dim, we allow 2D to sneak in for ops purposes GH#29853
26+
DatetimeArray(arr.reshape(2, 2, 1))
2527

26-
with pytest.raises(ValueError, match="Only 1-dimensional"):
27-
# 0-dim
28-
DatetimeArray(arr[[0]].squeeze())
28+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
29+
with pytest.raises(ValueError, match="Only 1-dimensional"):
30+
# 0-dim
31+
DatetimeArray(arr[[0]].squeeze())
2932

3033
def test_freq_validation(self):
3134
# GH#24623 check that invalid instances cannot be created with the
@@ -36,8 +39,10 @@ def test_freq_validation(self):
3639
"Inferred frequency h from passed values does not "
3740
"conform to passed frequency W-SUN"
3841
)
39-
with pytest.raises(ValueError, match=msg):
40-
DatetimeArray(arr, freq="W")
42+
depr_msg = "DatetimeArray.__init__ is deprecated"
43+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
44+
with pytest.raises(ValueError, match=msg):
45+
DatetimeArray(arr, freq="W")
4146

4247
@pytest.mark.parametrize(
4348
"meth",
@@ -72,31 +77,40 @@ def test_from_pandas_array(self):
7277
tm.assert_datetime_array_equal(result, expected)
7378

7479
def test_mismatched_timezone_raises(self):
75-
arr = DatetimeArray(
76-
np.array(["2000-01-01T06:00:00"], dtype="M8[ns]"),
77-
dtype=DatetimeTZDtype(tz="US/Central"),
78-
)
80+
depr_msg = "DatetimeArray.__init__ is deprecated"
81+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
82+
arr = DatetimeArray(
83+
np.array(["2000-01-01T06:00:00"], dtype="M8[ns]"),
84+
dtype=DatetimeTZDtype(tz="US/Central"),
85+
)
7986
dtype = DatetimeTZDtype(tz="US/Eastern")
8087
msg = r"dtype=datetime64\[ns.*\] does not match data dtype datetime64\[ns.*\]"
81-
with pytest.raises(TypeError, match=msg):
82-
DatetimeArray(arr, dtype=dtype)
88+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
89+
with pytest.raises(TypeError, match=msg):
90+
DatetimeArray(arr, dtype=dtype)
8391

8492
# also with mismatched tzawareness
85-
with pytest.raises(TypeError, match=msg):
86-
DatetimeArray(arr, dtype=np.dtype("M8[ns]"))
87-
with pytest.raises(TypeError, match=msg):
88-
DatetimeArray(arr.tz_localize(None), dtype=arr.dtype)
93+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
94+
with pytest.raises(TypeError, match=msg):
95+
DatetimeArray(arr, dtype=np.dtype("M8[ns]"))
96+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
97+
with pytest.raises(TypeError, match=msg):
98+
DatetimeArray(arr.tz_localize(None), dtype=arr.dtype)
8999

90100
def test_non_array_raises(self):
91-
with pytest.raises(ValueError, match="list"):
92-
DatetimeArray([1, 2, 3])
101+
depr_msg = "DatetimeArray.__init__ is deprecated"
102+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
103+
with pytest.raises(ValueError, match="list"):
104+
DatetimeArray([1, 2, 3])
93105

94106
def test_bool_dtype_raises(self):
95107
arr = np.array([1, 2, 3], dtype="bool")
96108

109+
depr_msg = "DatetimeArray.__init__ is deprecated"
97110
msg = "Unexpected value for 'dtype': 'bool'. Must be"
98-
with pytest.raises(ValueError, match=msg):
99-
DatetimeArray(arr)
111+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
112+
with pytest.raises(ValueError, match=msg):
113+
DatetimeArray(arr)
100114

101115
msg = r"dtype bool cannot be converted to datetime64\[ns\]"
102116
with pytest.raises(TypeError, match=msg):
@@ -109,43 +123,52 @@ def test_bool_dtype_raises(self):
109123
pd.to_datetime(arr)
110124

111125
def test_incorrect_dtype_raises(self):
112-
with pytest.raises(ValueError, match="Unexpected value for 'dtype'."):
113-
DatetimeArray(np.array([1, 2, 3], dtype="i8"), dtype="category")
126+
depr_msg = "DatetimeArray.__init__ is deprecated"
127+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
128+
with pytest.raises(ValueError, match="Unexpected value for 'dtype'."):
129+
DatetimeArray(np.array([1, 2, 3], dtype="i8"), dtype="category")
114130

115-
with pytest.raises(ValueError, match="Unexpected value for 'dtype'."):
116-
DatetimeArray(np.array([1, 2, 3], dtype="i8"), dtype="m8[s]")
131+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
132+
with pytest.raises(ValueError, match="Unexpected value for 'dtype'."):
133+
DatetimeArray(np.array([1, 2, 3], dtype="i8"), dtype="m8[s]")
117134

118-
with pytest.raises(ValueError, match="Unexpected value for 'dtype'."):
119-
DatetimeArray(np.array([1, 2, 3], dtype="i8"), dtype="M8[D]")
135+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
136+
with pytest.raises(ValueError, match="Unexpected value for 'dtype'."):
137+
DatetimeArray(np.array([1, 2, 3], dtype="i8"), dtype="M8[D]")
120138

121139
def test_mismatched_values_dtype_units(self):
122140
arr = np.array([1, 2, 3], dtype="M8[s]")
123141
dtype = np.dtype("M8[ns]")
124142
msg = "Values resolution does not match dtype."
143+
depr_msg = "DatetimeArray.__init__ is deprecated"
125144

126-
with pytest.raises(ValueError, match=msg):
127-
DatetimeArray(arr, dtype=dtype)
145+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
146+
with pytest.raises(ValueError, match=msg):
147+
DatetimeArray(arr, dtype=dtype)
128148

129149
dtype2 = DatetimeTZDtype(tz="UTC", unit="ns")
130-
with pytest.raises(ValueError, match=msg):
131-
DatetimeArray(arr, dtype=dtype2)
150+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
151+
with pytest.raises(ValueError, match=msg):
152+
DatetimeArray(arr, dtype=dtype2)
132153

133154
def test_freq_infer_raises(self):
134-
with pytest.raises(ValueError, match="Frequency inference"):
135-
DatetimeArray(np.array([1, 2, 3], dtype="i8"), freq="infer")
155+
depr_msg = "DatetimeArray.__init__ is deprecated"
156+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
157+
with pytest.raises(ValueError, match="Frequency inference"):
158+
DatetimeArray(np.array([1, 2, 3], dtype="i8"), freq="infer")
136159

137160
def test_copy(self):
138161
data = np.array([1, 2, 3], dtype="M8[ns]")
139-
arr = DatetimeArray(data, copy=False)
162+
arr = DatetimeArray._from_sequence(data, copy=False)
140163
assert arr._ndarray is data
141164

142-
arr = DatetimeArray(data, copy=True)
165+
arr = DatetimeArray._from_sequence(data, copy=True)
143166
assert arr._ndarray is not data
144167

145168
@pytest.mark.parametrize("unit", ["s", "ms", "us", "ns"])
146169
def test_numpy_datetime_unit(self, unit):
147170
data = np.array([1, 2, 3], dtype=f"M8[{unit}]")
148-
arr = DatetimeArray(data)
171+
arr = DatetimeArray._from_sequence(data)
149172
assert arr.unit == unit
150173
assert arr[0].unit == unit
151174

@@ -210,7 +233,7 @@ def test_from_arrowtest_from_arrow_with_different_units_and_timezones_with_(
210233
dtype = DatetimeTZDtype(unit=pd_unit, tz=pd_tz)
211234

212235
result = dtype.__from_arrow__(arr)
213-
expected = DatetimeArray(
236+
expected = DatetimeArray._from_sequence(
214237
np.array(data, dtype=f"datetime64[{pa_unit}]").astype(f"datetime64[{pd_unit}]"),
215238
dtype=dtype,
216239
)
@@ -238,7 +261,7 @@ def test_from_arrow_from_empty(unit, tz):
238261
dtype = DatetimeTZDtype(unit=unit, tz=tz)
239262

240263
result = dtype.__from_arrow__(arr)
241-
expected = DatetimeArray(np.array(data, dtype=f"datetime64[{unit}]"))
264+
expected = DatetimeArray._from_sequence(np.array(data, dtype=f"datetime64[{unit}]"))
242265
expected = expected.tz_localize(tz=tz)
243266
tm.assert_extension_array_equal(result, expected)
244267

@@ -254,7 +277,7 @@ def test_from_arrow_from_integers():
254277
dtype = DatetimeTZDtype(unit="ns", tz="UTC")
255278

256279
result = dtype.__from_arrow__(arr)
257-
expected = DatetimeArray(np.array(data, dtype="datetime64[ns]"))
280+
expected = DatetimeArray._from_sequence(np.array(data, dtype="datetime64[ns]"))
258281
expected = expected.tz_localize("UTC")
259282
tm.assert_extension_array_equal(result, expected)
260283

pandas/tests/arrays/test_array.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def test_array_copy():
296296
),
297297
(
298298
np.array([1, 2], dtype="M8[ns]"),
299-
DatetimeArray(np.array([1, 2], dtype="M8[ns]")),
299+
DatetimeArray._from_sequence(np.array([1, 2], dtype="M8[ns]")),
300300
),
301301
(
302302
np.array([1, 2], dtype="M8[us]"),
@@ -327,11 +327,11 @@ def test_array_copy():
327327
),
328328
(
329329
np.array([1, 2], dtype="m8[ns]"),
330-
TimedeltaArray(np.array([1, 2], dtype="m8[ns]")),
330+
TimedeltaArray._from_sequence(np.array([1, 2], dtype="m8[ns]")),
331331
),
332332
(
333333
np.array([1, 2], dtype="m8[us]"),
334-
TimedeltaArray(np.array([1, 2], dtype="m8[us]")),
334+
TimedeltaArray._from_sequence(np.array([1, 2], dtype="m8[us]")),
335335
),
336336
# integer
337337
([1, 2], IntegerArray._from_sequence([1, 2], dtype="Int64")),

0 commit comments

Comments
 (0)