Skip to content

Commit

Permalink
Merge branch 'main' into pr/9881
Browse files Browse the repository at this point in the history
  • Loading branch information
Illviljan committed Dec 14, 2024
2 parents 586f638 + 755581c commit 972e9fb
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 126 deletions.
7 changes: 6 additions & 1 deletion doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@ Breaking changes

Deprecations
~~~~~~~~~~~~

- Finalize deprecation of ``closed`` parameters of :py:func:`cftime_range` and
:py:func:`date_range` (:pull:`9882`).
By `Kai Mühlbauer <https://github.com/kmuehlbauer>`_.

Bug fixes
~~~~~~~~~
- Fix type annotations for ``get_axis_num``. (:issue:`9822`, :pull:`9827`).
By `Bruce Merry <https://github.com/bmerry>`_.
- Fix unintended load on datasets when calling :py:meth:`DataArray.plot.scatter` (:pull:`9818`).
By `Jimmy Westling <https://github.com/illviljan>`_.
- Fix interpolation when non-numeric coordinate variables are present (:issue:`8099`, :issue:`9839`).
By `Deepak Cherian <https://github.com/dcherian>`_.


Documentation
~~~~~~~~~~~~~
Expand Down
4 changes: 1 addition & 3 deletions xarray/backends/zarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1135,9 +1135,7 @@ def set_variables(self, variables, check_encoding_set, writer, unlimited_dims=No
else:
encoded_attrs[DIMENSION_KEY] = dims

encoding["exists_ok" if _zarr_v3() else "overwrite"] = (
True if self._mode == "w" else False
)
encoding["overwrite"] = True if self._mode == "w" else False

zarr_array = self._create_new_array(
name=name,
Expand Down
73 changes: 5 additions & 68 deletions xarray/coding/cftime_offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,13 @@
)
from xarray.core.common import _contains_datetime_like_objects, is_np_datetime_like
from xarray.core.pdcompat import (
NoDefault,
count_not_none,
nanosecond_precision_timestamp,
no_default,
)
from xarray.core.utils import attempt_import, emit_user_level_warning

if TYPE_CHECKING:
from xarray.core.types import InclusiveOptions, Self, SideOptions, TypeAlias
from xarray.core.types import InclusiveOptions, Self, TypeAlias


DayOption: TypeAlias = Literal["start", "end"]
Expand Down Expand Up @@ -943,51 +941,14 @@ def _generate_range(start, end, periods, offset):
current = next_date


def _translate_closed_to_inclusive(closed):
"""Follows code added in pandas #43504."""
emit_user_level_warning(
"Following pandas, the `closed` parameter is deprecated in "
"favor of the `inclusive` parameter, and will be removed in "
"a future version of xarray.",
FutureWarning,
)
if closed is None:
inclusive = "both"
elif closed in ("left", "right"):
inclusive = closed
else:
raise ValueError(
f"Argument `closed` must be either 'left', 'right', or None. "
f"Got {closed!r}."
)
return inclusive


def _infer_inclusive(
closed: NoDefault | SideOptions, inclusive: InclusiveOptions | None
) -> InclusiveOptions:
"""Follows code added in pandas #43504."""
if closed is not no_default and inclusive is not None:
raise ValueError(
"Following pandas, deprecated argument `closed` cannot be "
"passed if argument `inclusive` is not None."
)
if closed is not no_default:
return _translate_closed_to_inclusive(closed)
if inclusive is None:
return "both"
return inclusive


def cftime_range(
start=None,
end=None,
periods=None,
freq=None,
normalize=False,
name=None,
closed: NoDefault | SideOptions = no_default,
inclusive: None | InclusiveOptions = None,
inclusive: InclusiveOptions = "both",
calendar="standard",
) -> CFTimeIndex:
"""Return a fixed frequency CFTimeIndex.
Expand All @@ -1006,16 +967,7 @@ def cftime_range(
Normalize start/end dates to midnight before generating date range.
name : str, default: None
Name of the resulting index
closed : {None, "left", "right"}, default: "NO_DEFAULT"
Make the interval closed with respect to the given frequency to the
"left", "right", or both sides (None).
.. deprecated:: 2023.02.0
Following pandas, the ``closed`` parameter is deprecated in favor
of the ``inclusive`` parameter, and will be removed in a future
version of xarray.
inclusive : {None, "both", "neither", "left", "right"}, default None
inclusive : {"both", "neither", "left", "right"}, default "both"
Include boundaries; whether to set each bound as closed or open.
.. versionadded:: 2023.02.0
Expand Down Expand Up @@ -1193,8 +1145,6 @@ def cftime_range(
offset = to_offset(freq)
dates = np.array(list(_generate_range(start, end, periods, offset)))

inclusive = _infer_inclusive(closed, inclusive)

if inclusive == "neither":
left_closed = False
right_closed = False
Expand Down Expand Up @@ -1229,8 +1179,7 @@ def date_range(
tz=None,
normalize=False,
name=None,
closed: NoDefault | SideOptions = no_default,
inclusive: None | InclusiveOptions = None,
inclusive: InclusiveOptions = "both",
calendar="standard",
use_cftime=None,
):
Expand All @@ -1257,20 +1206,10 @@ def date_range(
Normalize start/end dates to midnight before generating date range.
name : str, default: None
Name of the resulting index
closed : {None, "left", "right"}, default: "NO_DEFAULT"
Make the interval closed with respect to the given frequency to the
"left", "right", or both sides (None).
.. deprecated:: 2023.02.0
Following pandas, the `closed` parameter is deprecated in favor
of the `inclusive` parameter, and will be removed in a future
version of xarray.
inclusive : {None, "both", "neither", "left", "right"}, default: None
inclusive : {"both", "neither", "left", "right"}, default: "both"
Include boundaries; whether to set each bound as closed or open.
.. versionadded:: 2023.02.0
calendar : str, default: "standard"
Calendar type for the datetimes.
use_cftime : boolean, optional
Expand All @@ -1294,8 +1233,6 @@ def date_range(
if tz is not None:
use_cftime = False

inclusive = _infer_inclusive(closed, inclusive)

if _is_standard_calendar(calendar) and use_cftime is not True:
try:
return pd.date_range(
Expand Down
11 changes: 6 additions & 5 deletions xarray/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -4167,7 +4167,7 @@ def _validate_interp_indexer(x, new_x):
}

variables: dict[Hashable, Variable] = {}
reindex: bool = False
reindex_vars: list[Hashable] = []
for name, var in obj._variables.items():
if name in indexers:
continue
Expand All @@ -4188,19 +4188,20 @@ def _validate_interp_indexer(x, new_x):
# booleans and objects and retains the dtype but inside
# this loop there might be some duplicate code that slows it
# down, therefore collect these signals and run it later:
reindex = True
reindex_vars.append(name)
elif all(d not in indexers for d in var.dims):
# For anything else we can only keep variables if they
# are not dependent on any coords that are being
# interpolated along:
variables[name] = var

if reindex:
reindex_indexers = {
if reindex_vars and (
reindex_indexers := {
k: v for k, (_, v) in validated_indexers.items() if v.dims == (k,)
}
):
reindexed = alignment.reindex(
obj,
obj[reindex_vars],
indexers=reindex_indexers,
method=method_non_numeric,
exclude_vars=variables.keys(),
Expand Down
48 changes: 0 additions & 48 deletions xarray/tests/test_cftime_offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1057,15 +1057,6 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg
False,
[(1, 1, 2), (1, 1, 3)],
),
(
"0001-01-01",
"0001-01-04",
None,
"D",
None,
False,
[(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)],
),
(
"0001-01-01",
"0001-01-04",
Expand Down Expand Up @@ -1294,13 +1285,6 @@ def test_invalid_cftime_range_inputs(
cftime_range(start, end, periods, freq, inclusive=inclusive) # type: ignore[arg-type]


def test_invalid_cftime_arg() -> None:
with pytest.warns(
FutureWarning, match="Following pandas, the `closed` parameter is deprecated"
):
cftime_range("2000", "2001", None, "YE", closed="left")


_CALENDAR_SPECIFIC_MONTH_END_TESTS = [
("noleap", [(2, 28), (4, 30), (6, 30), (8, 31), (10, 31), (12, 31)]),
("all_leap", [(2, 29), (4, 30), (6, 30), (8, 31), (10, 31), (12, 31)]),
Expand Down Expand Up @@ -1534,15 +1518,6 @@ def as_timedelta_not_implemented_error():
tick.as_timedelta()


@pytest.mark.parametrize("function", [cftime_range, date_range])
def test_cftime_or_date_range_closed_and_inclusive_error(function: Callable) -> None:
if function == cftime_range and not has_cftime:
pytest.skip("requires cftime")

with pytest.raises(ValueError, match="Following pandas, deprecated"):
function("2000", periods=3, closed=None, inclusive="right")


@pytest.mark.parametrize("function", [cftime_range, date_range])
def test_cftime_or_date_range_invalid_inclusive_value(function: Callable) -> None:
if function == cftime_range and not has_cftime:
Expand All @@ -1552,29 +1527,6 @@ def test_cftime_or_date_range_invalid_inclusive_value(function: Callable) -> Non
function("2000", periods=3, inclusive="foo")


@pytest.mark.parametrize(
"function",
[
pytest.param(cftime_range, id="cftime", marks=requires_cftime),
pytest.param(date_range, id="date"),
],
)
@pytest.mark.parametrize(
("closed", "inclusive"), [(None, "both"), ("left", "left"), ("right", "right")]
)
def test_cftime_or_date_range_closed(
function: Callable,
closed: Literal["left", "right", None],
inclusive: Literal["left", "right", "both"],
) -> None:
with pytest.warns(FutureWarning, match="Following pandas"):
result_closed = function("2000-01-01", "2000-01-04", freq="D", closed=closed)
result_inclusive = function(
"2000-01-01", "2000-01-04", freq="D", inclusive=inclusive
)
np.testing.assert_equal(result_closed.values, result_inclusive.values)


@pytest.mark.parametrize("function", [cftime_range, date_range])
def test_cftime_or_date_range_inclusive_None(function) -> None:
if function == cftime_range and not has_cftime:
Expand Down
40 changes: 39 additions & 1 deletion xarray/tests/test_interp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,44 @@ def test_interp1d_complex_out_of_bounds() -> None:
assert_identical(actual, expected)


@requires_scipy
def test_interp_non_numeric_1d() -> None:
ds = xr.Dataset(
{
"numeric": ("time", 1 + np.arange(0, 4, 1)),
"non_numeric": ("time", np.array(["a", "b", "c", "d"])),
},
coords={"time": (np.arange(0, 4, 1))},
)
actual = ds.interp(time=np.linspace(0, 3, 7))

expected = xr.Dataset(
{
"numeric": ("time", 1 + np.linspace(0, 3, 7)),
"non_numeric": ("time", np.array(["a", "b", "b", "c", "c", "d", "d"])),
},
coords={"time": np.linspace(0, 3, 7)},
)
xr.testing.assert_identical(actual, expected)


@requires_scipy
def test_interp_non_numeric_nd() -> None:
# regression test for GH8099, GH9839
ds = xr.Dataset({"x": ("a", np.arange(4))}, coords={"a": (np.arange(4) - 1.5)})
t = xr.DataArray(
np.random.randn(6).reshape((2, 3)) * 0.5,
dims=["r", "s"],
coords={"r": np.arange(2) - 0.5, "s": np.arange(3) - 1},
)
ds["m"] = ds.x > 1

actual = ds.interp(a=t, method="linear")
# with numeric only
expected = ds[["x"]].interp(a=t, method="linear")
assert_identical(actual[["x"]], expected)


@requires_dask
@requires_scipy
def test_interp_vectorized_dask():
Expand Down Expand Up @@ -1128,4 +1166,4 @@ def test_interp_vectorized_shared_dims(chunk):
dims=("t", "u"),
coords={"u": [45, 55], "t": [10, 12], "x": dx, "y": dy},
)
assert_identical(actual, expected)
assert_identical(actual, expected)

0 comments on commit 972e9fb

Please sign in to comment.