Skip to content
Merged
70 changes: 55 additions & 15 deletions xarray/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -3463,7 +3463,7 @@ def drop(
) -> "Dataset":
...

def drop(self, labels, dim=None, *, errors="raise"): # noqa: F811
def drop(self, labels=None, dim=None, *, errors="raise", **labels_kwargs):
"""Drop variables or index labels from this dataset.

Parameters
Expand All @@ -3479,34 +3479,74 @@ def drop(self, labels, dim=None, *, errors="raise"): # noqa: F811
any of the variable or index labels passed are not
in the dataset. If 'ignore', any given labels that are in the
dataset are dropped and no error is raised.
**labels_kwargs : {dim: label, ...}, optional
The keyword arguments form of ``dim`` and ``labels``.

Returns
-------
dropped : Dataset

Examples
--------
>>> data = np.random.randn(2, 3)
>>> labels = ['a', 'b', 'c']
>>> ds = xr.Dataset({'A': (['x', 'y'], data), 'y': labels})
>>> ds.drop(y=['a', 'c'])
<xarray.Dataset>
Dimensions: (x: 2, y: 1)
Coordinates:
* y (y) <U1 'b'
Dimensions without coordinates: x
Data variables:
A (x, y) float64 -0.3454 0.1734
>>> ds.drop(y='b')
<xarray.Dataset>
Dimensions: (x: 2, y: 2)
Coordinates:
* y (y) <U1 'a' 'c'
Dimensions without coordinates: x
Data variables:
A (x, y) float64 -0.3944 -1.418 1.423 -1.041
"""
if errors not in ["raise", "ignore"]:
raise ValueError('errors must be either "raise" or "ignore"')

if dim is None:
if labels_kwargs or utils.is_dict_like(labels):
labels_kwargs = utils.either_dict_or_kwargs(labels, labels_kwargs, "drop")
if dim is not None:
raise ValueError("cannot specify dim amd dict-like " "arguments.")
ds = self
for dim, labels in labels_kwargs.items():
ds = ds._drop_labels(labels, dim, errors=errors)
return ds
elif dim is None:
if isinstance(labels, str) or not isinstance(labels, Iterable):
labels = {labels}
else:
labels = set(labels)

return self._drop_vars(labels, errors=errors)
else:
# Don't cast to set, as it would harm performance when labels
# is a large numpy array
if utils.is_scalar(labels):
labels = [labels]
labels = np.asarray(labels)

try:
index = self.indexes[dim]
except KeyError:
raise ValueError("dimension %r does not have coordinate labels" % dim)
new_index = index.drop(labels, errors=errors)
return self.loc[{dim: new_index}]
if utils.is_list_like(labels):
warnings.warn(
"dropping dimensions using list-like labels is deprecated; "
"use dict-like arguments.",
DeprecationWarning,
stacklevel=2,
)
return self._drop_labels(labels, dim, errors=errors)

def _drop_labels(self, labels=None, dim=None, errors="raise"):
# Don't cast to set, as it would harm performance when labels
# is a large numpy array
if utils.is_scalar(labels):
labels = [labels]
labels = np.asarray(labels)
try:
index = self.indexes[dim]
except KeyError:
raise ValueError("dimension %r does not have coordinate labels" % dim)
new_index = index.drop(labels, errors=errors)
return self.loc[{dim: new_index}]

def _drop_vars(self, names: set, errors: str = "raise") -> "Dataset":
if errors == "raise":
Expand Down
4 changes: 4 additions & 0 deletions xarray/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ def is_full_slice(value: Any) -> bool:
return isinstance(value, slice) and value == slice(None)


def is_list_like(value: Any) -> bool:
return isinstance(value, list) or isinstance(value, tuple)


def either_dict_or_kwargs(
pos_kwargs: Optional[Mapping[Hashable, T]],
kw_kwargs: Mapping[str, T],
Expand Down
25 changes: 25 additions & 0 deletions xarray/tests/test_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2161,6 +2161,31 @@ def test_drop_index_labels(self):
with raises_regex(ValueError, "does not have coordinate labels"):
data.drop(1, "y")

def test_drop_labels_by_keyword(self):
# Tests for #2910: Support for a additional `drop()` API.
data = Dataset({"A": (["x", "y"], np.random.randn(2, 3)), "x": ["a", "b"]})
# Basic functionality.
assert len(data.coords["x"]) == 2

with pytest.warns(DeprecationWarning):
ds1 = data.drop(["a"], dim="x")

ds2 = data.drop(x="a")
ds3 = data.drop(x=["a"])
ds4 = data.drop(x=["a", "b"])
assert len(ds1.coords["x"]) == 1
assert len(ds2.coords["x"]) == 1
assert len(ds3.coords["x"]) == 1
assert len(ds4.coords["x"]) == 0

# Error handling if user tries both approaches.
with pytest.raises(ValueError):
data.drop(labels=["a"], x="a")
with pytest.raises(ValueError):
data.drop(dim="x", x="a")
with pytest.raises(ValueError):
data.drop(labels=["a"], dim="x", x="a")

def test_drop_dims(self):
data = xr.Dataset(
{
Expand Down