Skip to content

Commit 9722fc9

Browse files
REF: remove freq arg from PeriodArray constructor (#52462)
* REF: remove freq arg from PeriodArray constructor * remove unused arg * remove warn * deprecate freq instead * Update doc/source/whatsnew/v2.1.0.rst --------- Co-authored-by: Matthew Roeschke <[email protected]>
1 parent c94f9af commit 9722fc9

File tree

16 files changed

+167
-127
lines changed

16 files changed

+167
-127
lines changed

doc/source/whatsnew/v2.1.0.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ Deprecations
173173
- Deprecated the methods :meth:`Series.bool` and :meth:`DataFrame.bool` (:issue:`51749`)
174174
- Deprecated :meth:`DataFrame.swapaxes` and :meth:`Series.swapaxes`, use :meth:`DataFrame.transpose` or :meth:`Series.transpose` instead (:issue:`51946`)
175175
- Deprecated parameter ``convert_type`` in :meth:`Series.apply` (:issue:`52140`)
176-
176+
- Deprecated ``freq`` parameter in :class:`PeriodArray` constructor, pass ``dtype`` instead (:issue:`52462`)
177+
-
177178

178179
.. ---------------------------------------------------------------------------
179180
.. _whatsnew_210.performance:

pandas/core/arrays/datetimelike.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,8 @@ def _add_period(self, other: Period) -> PeriodArray:
11071107
from pandas.core.arrays.period import PeriodArray
11081108

11091109
i8vals = np.broadcast_to(other.ordinal, self.shape)
1110-
parr = PeriodArray(i8vals, freq=other.freq)
1110+
dtype = PeriodDtype(other.freq)
1111+
parr = PeriodArray(i8vals, dtype=dtype)
11111112
return parr + self
11121113

11131114
def _add_offset(self, offset):
@@ -1283,9 +1284,7 @@ def _accumulate(self, name: str, *, skipna: bool = True, **kwargs) -> Self:
12831284
op = getattr(datetimelike_accumulations, name)
12841285
result = op(self.copy(), skipna=skipna, **kwargs)
12851286

1286-
return type(self)._simple_new(
1287-
result, freq=None, dtype=self.dtype # type: ignore[call-arg]
1288-
)
1287+
return type(self)._simple_new(result, dtype=self.dtype)
12891288

12901289
@unpack_zerodim_and_defer("__add__")
12911290
def __add__(self, other):

pandas/core/arrays/period.py

+45-26
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
Literal,
1010
Sequence,
1111
TypeVar,
12+
cast,
1213
overload,
1314
)
15+
import warnings
1416

1517
import numpy as np
1618

@@ -49,6 +51,7 @@
4951
cache_readonly,
5052
doc,
5153
)
54+
from pandas.util._exceptions import find_stack_level
5255

5356
from pandas.core.dtypes.common import (
5457
ensure_object,
@@ -116,9 +119,7 @@ class PeriodArray(dtl.DatelikeOps, libperiod.PeriodMixin): # type: ignore[misc]
116119
"""
117120
Pandas ExtensionArray for storing Period data.
118121
119-
Users should use :func:`~pandas.period_array` to create new instances.
120-
Alternatively, :func:`~pandas.array` can be used to create new instances
121-
from a sequence of Period scalars.
122+
Users should use :func:`~pandas.array` to create new instances.
122123
123124
Parameters
124125
----------
@@ -213,10 +214,21 @@ def _scalar_type(self) -> type[Period]:
213214
def __init__(
214215
self, values, dtype: Dtype | None = None, freq=None, copy: bool = False
215216
) -> None:
216-
freq = validate_dtype_freq(dtype, freq)
217-
218217
if freq is not None:
219-
freq = Period._maybe_convert_freq(freq)
218+
# GH#52462
219+
warnings.warn(
220+
"The 'freq' keyword in the PeriodArray constructor is deprecated "
221+
"and will be removed in a future version. Pass 'dtype' instead",
222+
FutureWarning,
223+
stacklevel=find_stack_level(),
224+
)
225+
freq = validate_dtype_freq(dtype, freq)
226+
dtype = PeriodDtype(freq)
227+
228+
if dtype is not None:
229+
dtype = pandas_dtype(dtype)
230+
if not isinstance(dtype, PeriodDtype):
231+
raise ValueError(f"Invalid dtype {dtype} for PeriodArray")
220232

221233
if isinstance(values, ABCSeries):
222234
values = values._values
@@ -227,36 +239,38 @@ def __init__(
227239
values = values._values
228240

229241
if isinstance(values, type(self)):
230-
if freq is not None and freq != values.freq:
231-
raise raise_on_incompatible(values, freq)
232-
values, freq = values._ndarray, values.freq
242+
if dtype is not None and dtype != values.dtype:
243+
raise raise_on_incompatible(values, dtype.freq)
244+
values, dtype = values._ndarray, values.dtype
233245

234246
values = np.array(values, dtype="int64", copy=copy)
235-
if freq is None:
236-
raise ValueError("freq is not specified and cannot be inferred")
237-
NDArrayBacked.__init__(self, values, PeriodDtype(freq))
247+
if dtype is None:
248+
raise ValueError("dtype is not specified and cannot be inferred")
249+
dtype = cast(PeriodDtype, dtype)
250+
NDArrayBacked.__init__(self, values, dtype)
238251

239252
# error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked"
240253
@classmethod
241254
def _simple_new( # type: ignore[override]
242255
cls,
243256
values: npt.NDArray[np.int64],
244-
freq: BaseOffset | None = None,
245-
dtype: Dtype | None = None,
257+
dtype: PeriodDtype,
246258
) -> Self:
247259
# alias for PeriodArray.__init__
248260
assertion_msg = "Should be numpy array of type i8"
249261
assert isinstance(values, np.ndarray) and values.dtype == "i8", assertion_msg
250-
return cls(values, freq=freq, dtype=dtype)
262+
return cls(values, dtype=dtype)
251263

252264
@classmethod
253265
def _from_sequence(
254266
cls,
255-
scalars: Sequence[Period | None] | AnyArrayLike,
267+
scalars,
256268
*,
257269
dtype: Dtype | None = None,
258270
copy: bool = False,
259271
) -> Self:
272+
if dtype is not None:
273+
dtype = pandas_dtype(dtype)
260274
if dtype and isinstance(dtype, PeriodDtype):
261275
freq = dtype.freq
262276
else:
@@ -266,16 +280,14 @@ def _from_sequence(
266280
validate_dtype_freq(scalars.dtype, freq)
267281
if copy:
268282
scalars = scalars.copy()
269-
# error: Incompatible return value type
270-
# (got "Union[Sequence[Optional[Period]], Union[Union[ExtensionArray,
271-
# ndarray[Any, Any]], Index, Series]]", expected "PeriodArray")
272-
return scalars # type: ignore[return-value]
283+
return scalars
273284

274285
periods = np.asarray(scalars, dtype=object)
275286

276287
freq = freq or libperiod.extract_freq(periods)
277288
ordinals = libperiod.extract_ordinals(periods, freq)
278-
return cls(ordinals, freq=freq)
289+
dtype = PeriodDtype(freq)
290+
return cls(ordinals, dtype=dtype)
279291

280292
@classmethod
281293
def _from_sequence_of_strings(
@@ -299,7 +311,8 @@ def _from_datetime64(cls, data, freq, tz=None) -> Self:
299311
PeriodArray[freq]
300312
"""
301313
data, freq = dt64arr_to_periodarr(data, freq, tz)
302-
return cls(data, freq=freq)
314+
dtype = PeriodDtype(freq)
315+
return cls(data, dtype=dtype)
303316

304317
@classmethod
305318
def _generate_range(cls, start, end, periods, freq, fields):
@@ -610,7 +623,8 @@ def asfreq(self, freq=None, how: str = "E") -> Self:
610623
if self._hasna:
611624
new_data[self._isnan] = iNaT
612625

613-
return type(self)(new_data, freq=freq)
626+
dtype = PeriodDtype(freq)
627+
return type(self)(new_data, dtype=dtype)
614628

615629
# ------------------------------------------------------------------
616630
# Rendering Methods
@@ -697,7 +711,7 @@ def _addsub_int_array_or_scalar(
697711
if op is operator.sub:
698712
other = -other
699713
res_values = algos.checked_add_with_arr(self.asi8, other, arr_mask=self._isnan)
700-
return type(self)(res_values, freq=self.freq)
714+
return type(self)(res_values, dtype=self.dtype)
701715

702716
def _add_offset(self, other: BaseOffset):
703717
assert not isinstance(other, Tick)
@@ -768,7 +782,7 @@ def _add_timedelta_arraylike(
768782
self.asi8, delta.view("i8"), arr_mask=self._isnan, b_mask=b_mask
769783
)
770784
np.putmask(res_values, self._isnan | b_mask, iNaT)
771-
return type(self)(res_values, freq=self.freq)
785+
return type(self)(res_values, dtype=self.dtype)
772786

773787
def _check_timedeltalike_freq_compat(self, other):
774788
"""
@@ -904,7 +918,12 @@ def period_array(
904918
if is_datetime64_dtype(data_dtype):
905919
return PeriodArray._from_datetime64(data, freq)
906920
if isinstance(data_dtype, PeriodDtype):
907-
return PeriodArray(data, freq=freq)
921+
out = PeriodArray(data)
922+
if freq is not None:
923+
if freq == data_dtype.freq:
924+
return out
925+
return out.asfreq(freq)
926+
return out
908927

909928
# other iterable of some kind
910929
if not isinstance(data, (np.ndarray, list, tuple, ABCSeries)):

pandas/core/dtypes/dtypes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1013,14 +1013,14 @@ def __from_arrow__(
10131013
results = []
10141014
for arr in chunks:
10151015
data, mask = pyarrow_array_to_numpy_and_mask(arr, dtype=np.dtype(np.int64))
1016-
parr = PeriodArray(data.copy(), freq=self.freq, copy=False)
1016+
parr = PeriodArray(data.copy(), dtype=self, copy=False)
10171017
# error: Invalid index type "ndarray[Any, dtype[bool_]]" for "PeriodArray";
10181018
# expected type "Union[int, Sequence[int], Sequence[bool], slice]"
10191019
parr[~mask] = NaT # type: ignore[index]
10201020
results.append(parr)
10211021

10221022
if not results:
1023-
return PeriodArray(np.array([], dtype="int64"), freq=self.freq, copy=False)
1023+
return PeriodArray(np.array([], dtype="int64"), dtype=self, copy=False)
10241024
return PeriodArray._concat_same_type(results)
10251025

10261026

pandas/core/indexes/period.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
)
2626

2727
from pandas.core.dtypes.common import is_integer
28+
from pandas.core.dtypes.dtypes import PeriodDtype
2829
from pandas.core.dtypes.generic import ABCSeries
2930
from pandas.core.dtypes.missing import is_valid_na_for_dtype
3031

@@ -52,7 +53,6 @@
5253
npt,
5354
)
5455

55-
from pandas.core.dtypes.dtypes import PeriodDtype
5656

5757
_index_doc_kwargs = dict(ibase._index_doc_kwargs)
5858
_index_doc_kwargs.update({"target_klass": "PeriodIndex or list of Periods"})
@@ -68,7 +68,8 @@ def _new_PeriodIndex(cls, **d):
6868
values = d.pop("data")
6969
if values.dtype == "int64":
7070
freq = d.pop("freq", None)
71-
values = PeriodArray(values, freq=freq)
71+
dtype = PeriodDtype(freq)
72+
values = PeriodArray(values, dtype=dtype)
7273
return cls._simple_new(values, **d)
7374
else:
7475
return cls(values, **d)
@@ -246,7 +247,8 @@ def __new__(
246247
# empty when really using the range-based constructor.
247248
freq = freq2
248249

249-
data = PeriodArray(data, freq=freq)
250+
dtype = PeriodDtype(freq)
251+
data = PeriodArray(data, dtype=dtype)
250252
else:
251253
freq = validate_dtype_freq(dtype, freq)
252254

@@ -261,7 +263,8 @@ def __new__(
261263
if data is None and ordinal is not None:
262264
# we strangely ignore `ordinal` if data is passed.
263265
ordinal = np.asarray(ordinal, dtype=np.int64)
264-
data = PeriodArray(ordinal, freq=freq)
266+
dtype = PeriodDtype(freq)
267+
data = PeriodArray(ordinal, dtype=dtype)
265268
else:
266269
# don't pass copy here, since we copy later.
267270
data = period_array(data=data, freq=freq)
@@ -535,5 +538,6 @@ def period_range(
535538
freq = "D"
536539

537540
data, freq = PeriodArray._generate_range(start, end, periods, freq, fields={})
538-
data = PeriodArray(data, freq=freq)
541+
dtype = PeriodDtype(freq)
542+
data = PeriodArray(data, dtype=dtype)
539543
return PeriodIndex(data, name=name)

pandas/io/pytables.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
CategoricalDtype,
6969
DatetimeTZDtype,
7070
ExtensionDtype,
71+
PeriodDtype,
7172
)
7273
from pandas.core.dtypes.missing import array_equivalent
7374

@@ -2792,7 +2793,8 @@ def f(values, freq=None, tz=None):
27922793
elif index_class == PeriodIndex:
27932794

27942795
def f(values, freq=None, tz=None):
2795-
parr = PeriodArray._simple_new(values, freq=freq)
2796+
dtype = PeriodDtype(freq)
2797+
parr = PeriodArray._simple_new(values, dtype=dtype)
27962798
return PeriodIndex._simple_new(parr, name=None)
27972799

27982800
factory = f

pandas/tests/arrays/period/test_arrow_compat.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_arrow_array(data, freq):
6262
def test_arrow_array_missing():
6363
from pandas.core.arrays.arrow.extension_types import ArrowPeriodType
6464

65-
arr = PeriodArray([1, 2, 3], freq="D")
65+
arr = PeriodArray([1, 2, 3], dtype="period[D]")
6666
arr[1] = pd.NaT
6767

6868
result = pa.array(arr)
@@ -75,7 +75,7 @@ def test_arrow_array_missing():
7575
def test_arrow_table_roundtrip():
7676
from pandas.core.arrays.arrow.extension_types import ArrowPeriodType
7777

78-
arr = PeriodArray([1, 2, 3], freq="D")
78+
arr = PeriodArray([1, 2, 3], dtype="period[D]")
7979
arr[1] = pd.NaT
8080
df = pd.DataFrame({"a": arr})
8181

@@ -96,7 +96,7 @@ def test_arrow_load_from_zero_chunks():
9696

9797
from pandas.core.arrays.arrow.extension_types import ArrowPeriodType
9898

99-
arr = PeriodArray([], freq="D")
99+
arr = PeriodArray([], dtype="period[D]")
100100
df = pd.DataFrame({"a": arr})
101101

102102
table = pa.table(df)
@@ -110,7 +110,7 @@ def test_arrow_load_from_zero_chunks():
110110

111111

112112
def test_arrow_table_roundtrip_without_metadata():
113-
arr = PeriodArray([1, 2, 3], freq="H")
113+
arr = PeriodArray([1, 2, 3], dtype="period[H]")
114114
arr[1] = pd.NaT
115115
df = pd.DataFrame({"a": arr})
116116

pandas/tests/arrays/period/test_constructors.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,17 @@ def test_period_array_raises(data, freq, msg):
7575
def test_period_array_non_period_series_raies():
7676
ser = pd.Series([1, 2, 3])
7777
with pytest.raises(TypeError, match="dtype"):
78-
PeriodArray(ser, freq="D")
78+
PeriodArray(ser, dtype="period[D]")
7979

8080

8181
def test_period_array_freq_mismatch():
8282
arr = period_array(["2000", "2001"], freq="D")
8383
with pytest.raises(IncompatibleFrequency, match="freq"):
84-
PeriodArray(arr, freq="M")
84+
PeriodArray(arr, dtype="period[M]")
8585

86+
dtype = pd.PeriodDtype(pd.tseries.offsets.MonthEnd())
8687
with pytest.raises(IncompatibleFrequency, match="freq"):
87-
PeriodArray(arr, freq=pd.tseries.offsets.MonthEnd())
88+
PeriodArray(arr, dtype=dtype)
8889

8990

9091
def test_from_sequence_disallows_i8():
@@ -121,3 +122,14 @@ def test_from_td64nat_sequence_raises():
121122
pd.Series(arr, dtype=dtype)
122123
with pytest.raises(ValueError, match=msg):
123124
pd.DataFrame(arr, dtype=dtype)
125+
126+
127+
def test_freq_deprecated():
128+
# GH#52462
129+
data = np.arange(5).astype(np.int64)
130+
msg = "The 'freq' keyword in the PeriodArray constructor is deprecated"
131+
with tm.assert_produces_warning(FutureWarning, match=msg):
132+
res = PeriodArray(data, freq="M")
133+
134+
expected = PeriodArray(data, dtype="period[M]")
135+
tm.assert_equal(res, expected)

0 commit comments

Comments
 (0)