Skip to content

Commit 73ea3c1

Browse files
authored
DEPR: deprecate units 'w', 'd', 'MS', 'US', 'NS' for Timedelta in favor of 'W', 'D', 'ms', 'us', 'ns' (#59051)
* deprecate lower/uppercase units for Timedelta * correct examples in timedeltas.rst, add a note to v.3.0.0 * fix tests * add tests, fix tests, corrected docs examples * correct docs * fix an example in v0.13.0
1 parent 214ac73 commit 73ea3c1

31 files changed

+192
-94
lines changed

doc/source/user_guide/timedeltas.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ You can construct a ``Timedelta`` scalar through various arguments, including `I
3535
pd.Timedelta(days=1, seconds=1)
3636
3737
# integers with a unit
38-
pd.Timedelta(1, unit="d")
38+
pd.Timedelta(1, unit="D")
3939
4040
# from a datetime.timedelta/np.timedelta64
4141
pd.Timedelta(datetime.timedelta(days=1, seconds=1))
@@ -94,7 +94,7 @@ is numeric:
9494
.. ipython:: python
9595
9696
pd.to_timedelta(np.arange(5), unit="s")
97-
pd.to_timedelta(np.arange(5), unit="d")
97+
pd.to_timedelta(np.arange(5), unit="D")
9898
9999
.. warning::
100100
If a string or array of strings is passed as an input then the ``unit`` keyword

doc/source/whatsnew/v0.13.0.rst

+18-6
Original file line numberDiff line numberDiff line change
@@ -523,13 +523,25 @@ Enhancements
523523
Using the new top-level ``to_timedelta``, you can convert a scalar or array from the standard
524524
timedelta format (produced by ``to_csv``) into a timedelta type (``np.timedelta64`` in ``nanoseconds``).
525525

526-
.. ipython:: python
526+
.. code-block:: ipython
527+
528+
In [53]: pd.to_timedelta('1 days 06:05:01.00003')
529+
Out[53]: Timedelta('1 days 06:05:01.000030')
530+
531+
In [54]: pd.to_timedelta('15.5us')
532+
Out[54]: Timedelta('0 days 00:00:00.000015500')
533+
534+
In [55]: pd.to_timedelta(['1 days 06:05:01.00003', '15.5us', 'nan'])
535+
Out[55]: TimedeltaIndex(['1 days 06:05:01.000030', '0 days 00:00:00.000015500', NaT], dtype='timedelta64[ns]', freq=None)
536+
537+
In [56]: pd.to_timedelta(np.arange(5), unit='s')
538+
Out[56]:
539+
TimedeltaIndex(['0 days 00:00:00', '0 days 00:00:01', '0 days 00:00:02',
540+
'0 days 00:00:03', '0 days 00:00:04'],
541+
dtype='timedelta64[ns]', freq=None)
527542
528-
pd.to_timedelta('1 days 06:05:01.00003')
529-
pd.to_timedelta('15.5us')
530-
pd.to_timedelta(['1 days 06:05:01.00003', '15.5us', 'nan'])
531-
pd.to_timedelta(np.arange(5), unit='s')
532-
pd.to_timedelta(np.arange(5), unit='d')
543+
In [57]: pd.to_timedelta(np.arange(5), unit='d')
544+
Out[57]: TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'], dtype='timedelta64[ns]', freq=None)
533545
534546
A Series of dtype ``timedelta64[ns]`` can now be divided by another
535547
``timedelta64[ns]`` object, or astyped to yield a ``float64`` dtyped Series. This

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ Other Deprecations
273273
- Deprecated allowing non-keyword arguments in :meth:`Series.to_string` except ``buf``. (:issue:`57280`)
274274
- Deprecated behavior of :meth:`Series.dt.to_pytimedelta`, in a future version this will return a :class:`Series` containing python ``datetime.timedelta`` objects instead of an ``ndarray`` of timedelta; this matches the behavior of other :meth:`Series.dt` properties. (:issue:`57463`)
275275
- Deprecated parameter ``method`` in :meth:`DataFrame.reindex_like` / :meth:`Series.reindex_like` (:issue:`58667`)
276+
- Deprecated strings ``w``, ``d``, ``MIN``, ``MS``, ``US`` and ``NS`` denoting units in :class:`Timedelta` in favour of ``W``, ``D``, ``min``, ``ms``, ``us`` and ``ns`` (:issue:`59051`)
276277
- Deprecated using ``epoch`` date format in :meth:`DataFrame.to_json` and :meth:`Series.to_json`, use ``iso`` instead. (:issue:`57063`)
277278

278279
.. ---------------------------------------------------------------------------

pandas/_libs/tslibs/dtypes.pxd

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ cdef dict c_OFFSET_TO_PERIOD_FREQSTR
1515
cdef dict c_PERIOD_TO_OFFSET_FREQSTR
1616
cdef dict c_OFFSET_RENAMED_FREQSTR
1717
cdef dict c_DEPR_ABBREVS
18+
cdef dict c_DEPR_UNITS
1819
cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR
1920
cdef dict attrname_to_abbrevs
2021
cdef dict npy_unit_to_attrname

pandas/_libs/tslibs/dtypes.pyx

+11
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,17 @@ cdef dict c_DEPR_ABBREVS = {
346346
"S": "s",
347347
}
348348

349+
cdef dict c_DEPR_UNITS = {
350+
"w": "W",
351+
"d": "D",
352+
"H": "h",
353+
"MIN": "min",
354+
"S": "s",
355+
"MS": "ms",
356+
"US": "us",
357+
"NS": "ns",
358+
}
359+
349360
cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR = {
350361
"w": "W",
351362
"MIN": "min",

pandas/_libs/tslibs/timedeltas.pyx

+9-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ from pandas._libs.tslibs.conversion cimport (
4343
precision_from_unit,
4444
)
4545
from pandas._libs.tslibs.dtypes cimport (
46-
c_DEPR_ABBREVS,
46+
c_DEPR_UNITS,
4747
get_supported_reso,
4848
is_supported_unit,
4949
npy_unit_to_abbrev,
@@ -719,15 +719,15 @@ cpdef inline str parse_timedelta_unit(str unit):
719719
return "ns"
720720
elif unit == "M":
721721
return unit
722-
elif unit in c_DEPR_ABBREVS:
722+
elif unit in c_DEPR_UNITS:
723723
warnings.warn(
724724
f"\'{unit}\' is deprecated and will be removed in a "
725-
f"future version. Please use \'{c_DEPR_ABBREVS.get(unit)}\' "
725+
f"future version. Please use \'{c_DEPR_UNITS.get(unit)}\' "
726726
f"instead of \'{unit}\'.",
727727
FutureWarning,
728728
stacklevel=find_stack_level(),
729729
)
730-
unit = c_DEPR_ABBREVS[unit]
730+
unit = c_DEPR_UNITS[unit]
731731
try:
732732
return timedelta_abbrevs[unit.lower()]
733733
except KeyError:
@@ -1823,6 +1823,11 @@ class Timedelta(_Timedelta):
18231823
Values `H`, `T`, `S`, `L`, `U`, and `N` are deprecated in favour
18241824
of the values `h`, `min`, `s`, `ms`, `us`, and `ns`.
18251825
1826+
.. deprecated:: 3.0.0
1827+
1828+
Allowing the values `w`, `d`, `MIN`, `MS`, `US` and `NS` to denote units
1829+
are deprecated in favour of the values `W`, `D`, `min`, `ms`, `us` and `ns`.
1830+
18261831
**kwargs
18271832
Available kwargs: {days, seconds, microseconds,
18281833
milliseconds, minutes, hours, weeks}.

pandas/core/arrays/timedeltas.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ def total_seconds(self) -> npt.NDArray[np.float64]:
746746
--------
747747
**Series**
748748
749-
>>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="d"))
749+
>>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="D"))
750750
>>> s
751751
0 0 days
752752
1 1 days
@@ -765,7 +765,7 @@ def total_seconds(self) -> npt.NDArray[np.float64]:
765765
766766
**TimedeltaIndex**
767767
768-
>>> idx = pd.to_timedelta(np.arange(5), unit="d")
768+
>>> idx = pd.to_timedelta(np.arange(5), unit="D")
769769
>>> idx
770770
TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
771771
dtype='timedelta64[ns]', freq=None)
@@ -809,7 +809,7 @@ def to_pytimedelta(self) -> npt.NDArray[np.object_]:
809809
--------
810810
For Series:
811811
812-
>>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='d'))
812+
>>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='D'))
813813
>>> ser
814814
0 1 days
815815
1 2 days

pandas/core/indexes/accessors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ def to_pytimedelta(self) -> np.ndarray:
459459
460460
Examples
461461
--------
462-
>>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="d"))
462+
>>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="D"))
463463
>>> s
464464
0 0 days
465465
1 1 days

pandas/core/tools/timedeltas.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def to_timedelta(
170170
TimedeltaIndex(['0 days 00:00:00', '0 days 00:00:01', '0 days 00:00:02',
171171
'0 days 00:00:03', '0 days 00:00:04'],
172172
dtype='timedelta64[ns]', freq=None)
173-
>>> pd.to_timedelta(np.arange(5), unit="d")
173+
>>> pd.to_timedelta(np.arange(5), unit="D")
174174
TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
175175
dtype='timedelta64[ns]', freq=None)
176176
"""

pandas/tests/frame/indexing/test_mask.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,15 @@ def test_mask_stringdtype(frame_or_series):
122122

123123
def test_mask_where_dtype_timedelta():
124124
# https://github.com/pandas-dev/pandas/issues/39548
125-
df = DataFrame([Timedelta(i, unit="d") for i in range(5)])
125+
df = DataFrame([Timedelta(i, unit="D") for i in range(5)])
126126

127127
expected = DataFrame(np.full(5, np.nan, dtype="timedelta64[ns]"))
128128
tm.assert_frame_equal(df.mask(df.notna()), expected)
129129

130130
expected = DataFrame(
131131
[np.nan, np.nan, np.nan, Timedelta("3 day"), Timedelta("4 day")]
132132
)
133-
tm.assert_frame_equal(df.where(df > Timedelta(2, unit="d")), expected)
133+
tm.assert_frame_equal(df.where(df > Timedelta(2, unit="D")), expected)
134134

135135

136136
def test_mask_return_dtype():

pandas/tests/frame/methods/test_astype.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def test_astype_str(self):
149149
# see GH#9757
150150
a = Series(date_range("2010-01-04", periods=5))
151151
b = Series(date_range("3/6/2012 00:00", periods=5, tz="US/Eastern"))
152-
c = Series([Timedelta(x, unit="d") for x in range(5)])
152+
c = Series([Timedelta(x, unit="D") for x in range(5)])
153153
d = Series(range(5))
154154
e = Series([0.0, 0.2, 0.4, 0.6, 0.8])
155155

pandas/tests/frame/methods/test_reset_index.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -600,8 +600,8 @@ def test_reset_index_with_drop(
600600
{"a": [pd.NaT, Timestamp("2020-01-01")], "b": [1, 2], "x": [11, 12]},
601601
),
602602
(
603-
[(pd.NaT, 1), (pd.Timedelta(123, "d"), 2)],
604-
{"a": [pd.NaT, pd.Timedelta(123, "d")], "b": [1, 2], "x": [11, 12]},
603+
[(pd.NaT, 1), (pd.Timedelta(123, "D"), 2)],
604+
{"a": [pd.NaT, pd.Timedelta(123, "D")], "b": [1, 2], "x": [11, 12]},
605605
),
606606
],
607607
)

pandas/tests/groupby/test_groupby.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ def test_len_nan_group():
148148

149149
def test_groupby_timedelta_median():
150150
# issue 57926
151-
expected = Series(data=Timedelta("1d"), index=["foo"])
152-
df = DataFrame({"label": ["foo", "foo"], "timedelta": [pd.NaT, Timedelta("1d")]})
151+
expected = Series(data=Timedelta("1D"), index=["foo"])
152+
df = DataFrame({"label": ["foo", "foo"], "timedelta": [pd.NaT, Timedelta("1D")]})
153153
gb = df.groupby("label")["timedelta"]
154154
actual = gb.median()
155155
tm.assert_series_equal(actual, expected, check_names=False)

pandas/tests/groupby/test_reductions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,7 @@ def test_groupby_sum_timedelta_with_nat():
982982
df = DataFrame(
983983
{
984984
"a": [1, 1, 2, 2],
985-
"b": [pd.Timedelta("1d"), pd.Timedelta("2d"), pd.Timedelta("3d"), pd.NaT],
985+
"b": [pd.Timedelta("1D"), pd.Timedelta("2D"), pd.Timedelta("3D"), pd.NaT],
986986
}
987987
)
988988
td3 = pd.Timedelta(days=3)

pandas/tests/indexes/timedeltas/methods/test_shift.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_tdi_shift_minutes(self):
3737

3838
def test_tdi_shift_int(self):
3939
# GH#8083
40-
tdi = pd.to_timedelta(range(5), unit="d")
40+
tdi = pd.to_timedelta(range(5), unit="D")
4141
trange = tdi._with_freq("infer") + pd.offsets.Hour(1)
4242
result = trange.shift(1)
4343
expected = TimedeltaIndex(
@@ -54,7 +54,7 @@ def test_tdi_shift_int(self):
5454

5555
def test_tdi_shift_nonstandard_freq(self):
5656
# GH#8083
57-
tdi = pd.to_timedelta(range(5), unit="d")
57+
tdi = pd.to_timedelta(range(5), unit="D")
5858
trange = tdi._with_freq("infer") + pd.offsets.Hour(1)
5959
result = trange.shift(3, freq="2D 1s")
6060
expected = TimedeltaIndex(

pandas/tests/indexes/timedeltas/test_constructors.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def test_constructor_coverage(self):
168168
# NumPy string array
169169
strings = np.array(["1 days", "2 days", "3 days"])
170170
result = TimedeltaIndex(strings)
171-
expected = to_timedelta([1, 2, 3], unit="d")
171+
expected = to_timedelta([1, 2, 3], unit="D")
172172
tm.assert_index_equal(result, expected)
173173

174174
from_ints = TimedeltaIndex(expected.asi8)
@@ -239,3 +239,28 @@ def test_from_categorical(self):
239239
ci = pd.CategoricalIndex(tdi)
240240
result = TimedeltaIndex(ci)
241241
tm.assert_index_equal(result, tdi)
242+
243+
@pytest.mark.parametrize(
244+
"unit,unit_depr",
245+
[
246+
("W", "w"),
247+
("D", "d"),
248+
("min", "MIN"),
249+
("s", "S"),
250+
("h", "H"),
251+
("ms", "MS"),
252+
("us", "US"),
253+
],
254+
)
255+
def test_unit_deprecated(self, unit, unit_depr):
256+
# GH#52536, GH#59051
257+
msg = f"'{unit_depr}' is deprecated and will be removed in a future version."
258+
259+
expected = TimedeltaIndex([f"1{unit}", f"2{unit}"])
260+
with tm.assert_produces_warning(FutureWarning, match=msg):
261+
result = TimedeltaIndex([f"1{unit_depr}", f"2{unit_depr}"])
262+
tm.assert_index_equal(result, expected)
263+
264+
with tm.assert_produces_warning(FutureWarning, match=msg):
265+
tdi = to_timedelta([1, 2], unit=unit_depr)
266+
tm.assert_index_equal(tdi, expected)

pandas/tests/indexes/timedeltas/test_delete.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_delete_slice(self):
4444

4545
# reset freq to None
4646
expected_3_5 = TimedeltaIndex(
47-
["1 d", "2 d", "3 d", "7 d", "8 d", "9 d", "10d"], freq=None, name="idx"
47+
["1 D", "2 D", "3 D", "7 D", "8 D", "9 D", "10D"], freq=None, name="idx"
4848
)
4949

5050
cases = {

pandas/tests/indexes/timedeltas/test_indexing.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020

2121
class TestGetItem:
2222
def test_getitem_slice_keeps_name(self):
23-
# GH#4226
24-
tdi = timedelta_range("1d", "5d", freq="h", name="timebucket")
23+
# GH#4226, GH#59051
24+
msg = "'d' is deprecated and will be removed in a future version."
25+
with tm.assert_produces_warning(FutureWarning, match=msg):
26+
tdi = timedelta_range("1d", "5d", freq="h", name="timebucket")
2527
assert tdi[1:].name == tdi.name
2628

2729
def test_getitem(self):
@@ -230,7 +232,7 @@ def test_take_invalid_kwargs(self):
230232

231233
def test_take_equiv_getitem(self):
232234
tds = ["1day 02:00:00", "1 day 04:00:00", "1 day 10:00:00"]
233-
idx = timedelta_range(start="1d", end="2d", freq="h", name="idx")
235+
idx = timedelta_range(start="1D", end="2D", freq="h", name="idx")
234236
expected = TimedeltaIndex(tds, freq=None, name="idx")
235237

236238
taken1 = idx.take([2, 4, 10])
@@ -337,8 +339,10 @@ def test_contains_nonunique(self):
337339

338340
def test_contains(self):
339341
# Checking for any NaT-like objects
340-
# GH#13603
341-
td = to_timedelta(range(5), unit="d") + offsets.Hour(1)
342+
# GH#13603, GH#59051
343+
msg = "'d' is deprecated and will be removed in a future version."
344+
with tm.assert_produces_warning(FutureWarning, match=msg):
345+
td = to_timedelta(range(5), unit="d") + offsets.Hour(1)
342346
for v in [NaT, None, float("nan"), np.nan]:
343347
assert v not in td
344348

pandas/tests/indexes/timedeltas/test_setops.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ def test_union_sort_false(self):
4242
tm.assert_index_equal(result, expected)
4343

4444
def test_union_coverage(self):
45-
idx = TimedeltaIndex(["3d", "1d", "2d"])
45+
# GH#59051
46+
msg = "'d' is deprecated and will be removed in a future version."
47+
with tm.assert_produces_warning(FutureWarning, match=msg):
48+
idx = TimedeltaIndex(["3d", "1d", "2d"])
4649
ordered = TimedeltaIndex(idx.sort_values(), freq="infer")
4750
result = ordered.union(idx)
4851
tm.assert_index_equal(result, ordered)
@@ -70,7 +73,7 @@ def test_union_bug_1745(self):
7073
tm.assert_index_equal(result, exp)
7174

7275
def test_union_bug_4564(self):
73-
left = timedelta_range("1 day", "30d")
76+
left = timedelta_range("1 day", "30D")
7477
right = left + pd.offsets.Minute(15)
7578

7679
result = left.union(right)

pandas/tests/indexing/test_categorical.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -511,13 +511,13 @@ def test_loc_and_at_with_categorical_index(self):
511511
# pandas scalars
512512
[Interval(1, 4), Interval(4, 6), Interval(6, 9)],
513513
[Timestamp(2019, 1, 1), Timestamp(2019, 2, 1), Timestamp(2019, 3, 1)],
514-
[Timedelta(1, "d"), Timedelta(2, "d"), Timedelta(3, "D")],
514+
[Timedelta(1, "D"), Timedelta(2, "D"), Timedelta(3, "D")],
515515
# pandas Integer arrays
516516
*(pd.array([1, 2, 3], dtype=dtype) for dtype in tm.ALL_INT_EA_DTYPES),
517517
# other pandas arrays
518518
pd.IntervalIndex.from_breaks([1, 4, 6, 9]).array,
519519
pd.date_range("2019-01-01", periods=3).array,
520-
pd.timedelta_range(start="1d", periods=3).array,
520+
pd.timedelta_range(start="1D", periods=3).array,
521521
],
522522
)
523523
def test_loc_getitem_with_non_string_categories(self, idx_values, ordered):

pandas/tests/io/sas/test_sas7bdat.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ def data_test_ix(request, dirpath):
3030
fname = os.path.join(dirpath, f"test_sas7bdat_{i}.csv")
3131
df = pd.read_csv(fname)
3232
epoch = datetime(1960, 1, 1)
33-
t1 = pd.to_timedelta(df["Column4"], unit="d")
33+
t1 = pd.to_timedelta(df["Column4"], unit="D")
3434
df["Column4"] = (epoch + t1).astype("M8[s]")
35-
t2 = pd.to_timedelta(df["Column12"], unit="d")
35+
t2 = pd.to_timedelta(df["Column12"], unit="D")
3636
df["Column12"] = (epoch + t2).astype("M8[s]")
3737
for k in range(df.shape[1]):
3838
col = df.iloc[:, k]

pandas/tests/reshape/merge/test_merge.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1367,8 +1367,8 @@ def test_merge_two_empty_df_no_division_error(self):
13671367
),
13681368
),
13691369
(
1370-
TimedeltaIndex(["1d", "2d", "3d"]),
1371-
TimedeltaIndex(["1d", "2d", "3d", pd.NaT, pd.NaT, pd.NaT]),
1370+
TimedeltaIndex(["1D", "2D", "3D"]),
1371+
TimedeltaIndex(["1D", "2D", "3D", pd.NaT, pd.NaT, pd.NaT]),
13721372
),
13731373
],
13741374
)

0 commit comments

Comments
 (0)