Skip to content

Commit b1580db

Browse files
committed
Fix pandas deprecation warnings
1 parent f87f0b2 commit b1580db

12 files changed

Lines changed: 141 additions & 42 deletions

xarray/coding/calendar_ops.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def _interpolate_day_of_year(times, target_calendar):
249249
source_calendar = times.dt.calendar
250250
return np.round(
251251
_days_in_year(times.dt.year, target_calendar)
252-
* times.dt.dayofyear
252+
* times.dt.day_of_year
253253
/ _days_in_year(times.dt.year, source_calendar)
254254
).astype(int)
255255

@@ -272,7 +272,7 @@ def _random_day_of_year(time, target_calendar, use_cftime):
272272
new_doy = np.insert(new_doy, rm_idx - np.arange(5), -1)
273273
if _days_in_year(year, source_calendar) == 366:
274274
new_doy = np.insert(new_doy, 60, -1)
275-
return new_doy[time.dt.dayofyear - 1]
275+
return new_doy[time.dt.day_of_year - 1]
276276

277277

278278
def _convert_to_new_calendar_with_new_day_of_year(

xarray/coding/cftime_offsets.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,10 +1623,18 @@ def date_range_like(source, calendar, use_cftime=None):
16231623
end = convert_time_or_go_back(source_end, date_type)
16241624

16251625
# For the cases where the source ends on the end of the month, we expect the same in the new calendar.
1626-
if source_end.day == source_end.daysinmonth and isinstance(
1626+
if isinstance(source_end, pd.Timestamp):
1627+
source_end_days_in_month = source_end.days_in_month
1628+
else:
1629+
source_end_days_in_month = source_end.daysinmonth
1630+
if isinstance(end, pd.Timestamp):
1631+
end_days_in_month = end.days_in_month
1632+
else:
1633+
end_days_in_month = end.daysinmonth
1634+
if source_end.day == source_end_days_in_month and isinstance(
16271635
freq_as_offset, YearEnd | QuarterEnd | MonthEnd | Day
16281636
):
1629-
end = end.replace(day=end.daysinmonth)
1637+
end = end.replace(day=end_days_in_month)
16301638

16311639
return date_range(
16321640
start=start.isoformat(),

xarray/coding/cftimeindex.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,12 @@ def get_date_field(datetimes, field):
127127
return np.array([getattr(date, field) for date in datetimes], dtype=np.int64)
128128

129129

130-
def _field_accessor(name, docstring=None, min_cftime_version="0.0"):
130+
def _field_accessor(
131+
name: str,
132+
docstring: str | None = None,
133+
min_cftime_version: str = "0.0",
134+
deprecation_pair: tuple[str, str] | None = None,
135+
):
131136
"""Adapted from pandas.tseries.index._field_accessor"""
132137

133138
def f(self, min_cftime_version=min_cftime_version):
@@ -136,6 +141,14 @@ def f(self, min_cftime_version=min_cftime_version):
136141
else:
137142
cftime = attempt_import("cftime")
138143

144+
if deprecation_pair is not None:
145+
original, replacement = deprecation_pair
146+
emit_user_level_warning(
147+
f"CFTimeIndex.{original} is deprecated and will be removed in "
148+
f"a future version. Use CFTimeIndex.{replacement} instead",
149+
FutureWarning,
150+
)
151+
139152
if Version(cftime.__version__) >= Version(min_cftime_version):
140153
return get_date_field(self._data, name)
141154
else:
@@ -249,9 +262,23 @@ class CFTimeIndex(pd.Index):
249262
second = _field_accessor("second", "The seconds of the datetime")
250263
microsecond = _field_accessor("microsecond", "The microseconds of the datetime")
251264
dayofyear = _field_accessor(
265+
"dayofyr",
266+
"The ordinal day of year of the datetime",
267+
"1.0.2.1",
268+
("dayofyear", "day_of_year"),
269+
)
270+
dayofweek = _field_accessor(
271+
"dayofwk",
272+
"The day of week of the datetime",
273+
"1.0.2.1",
274+
("dayofweek", "day_of_week"),
275+
)
276+
day_of_year = _field_accessor(
252277
"dayofyr", "The ordinal day of year of the datetime", "1.0.2.1"
253278
)
254-
dayofweek = _field_accessor("dayofwk", "The day of week of the datetime", "1.0.2.1")
279+
day_of_week = _field_accessor(
280+
"dayofwk", "The day of week of the datetime", "1.0.2.1"
281+
)
255282
days_in_month = _field_accessor(
256283
"daysinmonth", "The number of days in the month of the datetime", "1.1.0.0"
257284
)

xarray/core/accessor_dt.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
is_np_timedelta_like,
1717
)
1818
from xarray.core.types import T_DataArray
19+
from xarray.core.utils import emit_user_level_warning
1920
from xarray.core.variable import IndexVariable, Variable
2021
from xarray.namedarray.utils import is_duck_dask_array
2122

@@ -465,17 +466,45 @@ def weekofyear(self) -> DataArray:
465466

466467
week = weekofyear
467468

469+
@property
470+
def day_of_week(self) -> T_DataArray:
471+
"""The day of the week with Monday=0, Sunday=6"""
472+
return self._date_field("day_of_week", np.int64)
473+
468474
@property
469475
def dayofweek(self) -> T_DataArray:
470476
"""The day of the week with Monday=0, Sunday=6"""
471-
return self._date_field("dayofweek", np.int64)
477+
emit_user_level_warning(
478+
"dt.dayofweek is deprecated and will be removed in a future "
479+
"version. Use dt.day_of_week instead.",
480+
FutureWarning,
481+
)
482+
return self._date_field("day_of_week", np.int64)
472483

473-
weekday = dayofweek
484+
@property
485+
def weekday(self) -> T_DataArray:
486+
"""The day of the week with Monday=0, Sunday=6"""
487+
emit_user_level_warning(
488+
"dt.weekday is deprecated and will be removed in a "
489+
"future version. Use dt.day_of_week instead.",
490+
FutureWarning,
491+
)
492+
return self._date_field("day_of_week", np.int64)
493+
494+
@property
495+
def day_of_year(self) -> T_DataArray:
496+
"""The ordinal day of the year"""
497+
return self._date_field("day_of_year", np.int64)
474498

475499
@property
476500
def dayofyear(self) -> T_DataArray:
477501
"""The ordinal day of the year"""
478-
return self._date_field("dayofyear", np.int64)
502+
emit_user_level_warning(
503+
"dt.dayofyear is deprecated and will be removed in a future "
504+
"version. Use dt.day_of_year instead.",
505+
FutureWarning,
506+
)
507+
return self._date_field("day_of_year", np.int64)
479508

480509
@property
481510
def quarter(self) -> T_DataArray:
@@ -487,7 +516,15 @@ def days_in_month(self) -> T_DataArray:
487516
"""The number of days in the month"""
488517
return self._date_field("days_in_month", np.int64)
489518

490-
daysinmonth = days_in_month
519+
@property
520+
def daysinmonth(self) -> T_DataArray:
521+
"""The number of days in the month"""
522+
emit_user_level_warning(
523+
"dt.daysinmonth is deprecated and will be removed in a future "
524+
"version. Use dt.days_in_month instead.",
525+
FutureWarning,
526+
)
527+
return self._date_field("days_in_month", np.int64)
491528

492529
@property
493530
def season(self) -> T_DataArray:

xarray/tests/test_accessor_dt.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,11 @@ def setup(self):
5656
"nanosecond",
5757
"week",
5858
"weekofyear",
59-
"dayofweek",
60-
"weekday",
61-
"dayofyear",
59+
"day_of_week",
60+
"day_of_year",
6261
"quarter",
6362
"date",
6463
"time",
65-
"daysinmonth",
6664
"days_in_month",
6765
"is_month_start",
6866
"is_month_end",
@@ -104,6 +102,21 @@ def test_field_access(self, field) -> None:
104102
assert expected.dtype == actual.dtype
105103
assert_identical(expected, actual)
106104

105+
@pytest.mark.parametrize(
106+
("field", "replacement"),
107+
[
108+
("daysinmonth", "days_in_month"),
109+
("dayofweek", "day_of_week"),
110+
("weekday", "day_of_week"),
111+
("dayofyear", "day_of_year"),
112+
],
113+
)
114+
def test_deprecated_field_access(self, field, replacement) -> None:
115+
expected = getattr(self.data.time.dt, replacement)
116+
with pytest.warns(FutureWarning, match=f"{field}.*{replacement}"):
117+
actual = getattr(self.data.time.dt, field)
118+
assert_identical(expected, actual)
119+
107120
def test_total_seconds(self) -> None:
108121
# Subtract a value in the middle of the range to ensure that some values
109122
# are negative
@@ -177,9 +190,8 @@ def test_not_datetime_type(self) -> None:
177190
"nanosecond",
178191
"week",
179192
"weekofyear",
180-
"dayofweek",
181-
"weekday",
182-
"dayofyear",
193+
"day_of_week",
194+
"day_of_year",
183195
"quarter",
184196
"date",
185197
"time",
@@ -441,7 +453,7 @@ def times_3d(times):
441453

442454
@requires_cftime
443455
@pytest.mark.parametrize(
444-
"field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"]
456+
"field", ["year", "month", "day", "hour", "day_of_year", "day_of_week"]
445457
)
446458
def test_field_access(data, field) -> None:
447459
result = getattr(data.time.dt, field)
@@ -533,7 +545,7 @@ def test_cftime_strftime_access(data) -> None:
533545
@requires_cftime
534546
@requires_dask
535547
@pytest.mark.parametrize(
536-
"field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"]
548+
"field", ["year", "month", "day", "hour", "day_of_year", "day_of_week"]
537549
)
538550
def test_dask_field_access_1d(data, field) -> None:
539551
import dask.array as da
@@ -553,7 +565,7 @@ def test_dask_field_access_1d(data, field) -> None:
553565
@requires_cftime
554566
@requires_dask
555567
@pytest.mark.parametrize(
556-
"field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"]
568+
"field", ["year", "month", "day", "hour", "day_of_year", "day_of_week"]
557569
)
558570
def test_dask_field_access(times_3d, data, field) -> None:
559571
import dask.array as da

xarray/tests/test_calendar_ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def test_convert_calendar_360_days_random():
159159
# Ensure that added days are evenly distributed in the 5 fifths of each year
160160
conv = convert_calendar(da_360, "noleap", align_on="random", missing=np.nan)
161161
conv = conv.where(conv.isnull(), drop=True)
162-
nandoys = conv.time.dt.dayofyear[:366]
162+
nandoys = conv.time.dt.day_of_year[:366]
163163
assert all(nandoys < np.array([74, 147, 220, 293, 366]))
164164
assert all(nandoys > np.array([0, 73, 146, 219, 292]))
165165

xarray/tests/test_cftime_offsets.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,19 +1380,19 @@ def test_calendar_year_length(
13801380

13811381
@pytest.mark.parametrize("freq", ["YE", "ME", "D"])
13821382
def test_dayofweek_after_cftime(freq: str) -> None:
1383-
result = date_range("2000-02-01", periods=3, freq=freq, use_cftime=True).dayofweek
1383+
result = date_range("2000-02-01", periods=3, freq=freq, use_cftime=True).day_of_week
13841384
# TODO: remove once requiring pandas 2.2+
13851385
freq = _new_to_legacy_freq(freq)
1386-
expected = pd.date_range("2000-02-01", periods=3, freq=freq).dayofweek
1386+
expected = pd.date_range("2000-02-01", periods=3, freq=freq).day_of_week
13871387
np.testing.assert_array_equal(result, expected)
13881388

13891389

13901390
@pytest.mark.parametrize("freq", ["YE", "ME", "D"])
13911391
def test_dayofyear_after_cftime(freq: str) -> None:
1392-
result = date_range("2000-02-01", periods=3, freq=freq, use_cftime=True).dayofyear
1392+
result = date_range("2000-02-01", periods=3, freq=freq, use_cftime=True).day_of_year
13931393
# TODO: remove once requiring pandas 2.2+
13941394
freq = _new_to_legacy_freq(freq)
1395-
expected = pd.date_range("2000-02-01", periods=3, freq=freq).dayofyear
1395+
expected = pd.date_range("2000-02-01", periods=3, freq=freq).day_of_year
13961396
np.testing.assert_array_equal(result, expected)
13971397

13981398

xarray/tests/test_cftimeindex.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,8 @@ def test_cftimeindex_field_accessors(index, field, expected):
315315
"minute",
316316
"second",
317317
"microsecond",
318-
"dayofyear",
319-
"dayofweek",
318+
"day_of_year",
319+
"day_of_week",
320320
"days_in_month",
321321
],
322322
)
@@ -329,16 +329,30 @@ def test_empty_cftimeindex_field_accessors(field):
329329

330330

331331
@requires_cftime
332-
def test_cftimeindex_dayofyear_accessor(index):
333-
result = index.dayofyear
332+
@pytest.mark.parametrize(
333+
("field", "replacement"), [("day_of_year", None), ("dayofyear", "day_of_year")]
334+
)
335+
def test_cftimeindex_dayofyear_accessor(index, field, replacement):
336+
if replacement is not None:
337+
with pytest.warns(FutureWarning, match=f"{field}.*{replacement}"):
338+
result = getattr(index, field)
339+
else:
340+
result = getattr(index, field)
334341
expected = np.array([date.dayofyr for date in index], dtype=np.int64)
335342
assert_array_equal(result, expected)
336343
assert result.dtype == expected.dtype
337344

338345

339346
@requires_cftime
340-
def test_cftimeindex_dayofweek_accessor(index):
341-
result = index.dayofweek
347+
@pytest.mark.parametrize(
348+
("field", "replacement"), [("day_of_week", None), ("dayofweek", "day_of_week")]
349+
)
350+
def test_cftimeindex_dayofweek_accessor(index, field, replacement):
351+
if replacement is not None:
352+
with pytest.warns(FutureWarning, match=f"{field}.*{replacement}"):
353+
result = getattr(index, field)
354+
else:
355+
result = getattr(index, field)
342356
expected = np.array([date.dayofwk for date in index], dtype=np.int64)
343357
assert_array_equal(result, expected)
344358
assert result.dtype == expected.dtype

xarray/tests/test_dataarray.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,8 +1481,8 @@ def test_virtual_time_components(self) -> None:
14811481
dates = pd.date_range("2000-01-01", periods=10)
14821482
da = DataArray(np.arange(1, 11), [("time", dates)])
14831483

1484-
assert_array_equal(da["time.dayofyear"], da.values)
1485-
assert_array_equal(da.coords["time.dayofyear"], da.values)
1484+
assert_array_equal(da["time.day_of_year"], da.values)
1485+
assert_array_equal(da.coords["time.day_of_year"], da.values)
14861486

14871487
def test_coords(self) -> None:
14881488
# use int64 to ensure repr() consistency on windows

xarray/tests/test_dataset.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,7 +1729,7 @@ def test_sel(self) -> None:
17291729
times = pd.date_range("2000-01-01", periods=3)
17301730
assert_equal(data.isel(time=slice(3)), data.sel(time=times))
17311731
assert_equal(
1732-
data.isel(time=slice(3)), data.sel(time=(data["time.dayofyear"] <= 3))
1732+
data.isel(time=slice(3)), data.sel(time=(data["time.day_of_year"] <= 3))
17331733
)
17341734

17351735
td = pd.to_timedelta(np.arange(3), unit="days")
@@ -4572,11 +4572,11 @@ def test_virtual_variables_time(self) -> None:
45724572
assert_array_equal(data["time.month"].values, index.month)
45734573
assert_array_equal(data["time.season"].values, "DJF")
45744574
# test virtual variable math
4575-
assert_array_equal(data["time.dayofyear"] + 1, 2 + np.arange(20))
4576-
assert_array_equal(np.sin(data["time.dayofyear"]), np.sin(1 + np.arange(20)))
4575+
assert_array_equal(data["time.day_of_year"] + 1, 2 + np.arange(20))
4576+
assert_array_equal(np.sin(data["time.day_of_year"]), np.sin(1 + np.arange(20)))
45774577
# ensure they become coordinates
4578-
expected = Dataset({}, {"dayofyear": data["time.dayofyear"]})
4579-
actual = data[["time.dayofyear"]]
4578+
expected = Dataset({}, {"day_of_year": data["time.day_of_year"]})
4579+
actual = data[["time.day_of_year"]]
45804580
assert_equal(expected, actual)
45814581
# non-coordinate variables
45824582
ds = Dataset({"t": ("x", pd.date_range("2000-01-01", periods=3))})
@@ -4599,9 +4599,10 @@ def test_time_season(self) -> None:
45994599
def test_slice_virtual_variable(self) -> None:
46004600
data = create_test_data()
46014601
assert_equal(
4602-
data["time.dayofyear"][:10].variable, Variable(["time"], 1 + np.arange(10))
4602+
data["time.day_of_year"][:10].variable,
4603+
Variable(["time"], 1 + np.arange(10)),
46034604
)
4604-
assert_equal(data["time.dayofyear"][0].variable, Variable([], 1))
4605+
assert_equal(data["time.day_of_year"][0].variable, Variable([], 1))
46054606

46064607
def test_setitem(self) -> None:
46074608
# assign a variable

0 commit comments

Comments
 (0)