Repository: pydata/xarray

Issue:
xr.Dataset.drop
Currently, `drop` throws an error if one of the labels doesn't exist. It would be nice to have a parameter in the drop method for optionally ignoring errors like in the pandas.DataFrame.
From the pandas [documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.drop.html):

> errors : {‘ignore’, ‘raise’}, default ‘raise’
>     If ‘ignore’, suppress error and only existing labels are dropped.


Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 913c5e1c..ee242ebf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,6 +21,7 @@ matrix:
   - env: CONDA_ENV=py36-zarr-dev
   - env: CONDA_ENV=docs
   - env: CONDA_ENV=lint
+  - env: CONDA_ENV=typing
   - env: CONDA_ENV=py36-hypothesis
 
   allow_failures:
@@ -30,6 +31,7 @@ matrix:
     - EXTRA_FLAGS="--run-flaky --run-network-tests"
   - env: CONDA_ENV=py36-pandas-dev
   - env: CONDA_ENV=py36-zarr-dev
+  - env: CONDA_ENV=typing
 
 before_install:
   - wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
@@ -40,9 +42,10 @@ before_install:
   - conda info -a
 
 install:
-  - if [[ "$CONDA_ENV" == "docs" ]]; then
+  - |
+    if [[ "$CONDA_ENV" == "docs" ]]; then
       conda env create -n test_env --file doc/environment.yml;
-    elif [[ "$CONDA_ENV" == "lint" ]]; then
+    elif [[ "$CONDA_ENV" == "lint" ]] || [[ "$CONDA_ENV" == "typing" ]] ; then
       conda env create -n test_env --file ci/requirements-py37.yml;
     else
       conda env create -n test_env --file ci/requirements-$CONDA_ENV.yml;
@@ -56,11 +59,14 @@ script:
   - which python
   - python --version
   - python -OO -c "import xarray"
-  - if [[ "$CONDA_ENV" == "docs" ]]; then
+  - |
+    if [[ "$CONDA_ENV" == "docs" ]]; then
       cd doc;
       sphinx-build -n -j auto -b html -d _build/doctrees . _build/html;
     elif [[ "$CONDA_ENV" == "lint" ]]; then
       flake8 ;
+    elif [[ "$CONDA_ENV" == "typing" ]]; then
+      mypy . ;
     elif [[ "$CONDA_ENV" == "py36-hypothesis" ]]; then
       pytest properties ;
     else
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index e62c7e87..ca50856a 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -59,6 +59,11 @@ Enhancements
   formatted datetimes. By `Alan Brammer <https://github.com/abrammer>`_.
 - Add ``.str`` accessor to DataArrays for string related manipulations.
   By `0x0L <https://github.com/0x0L>`_.
+- Add ``errors`` keyword argument to :py:meth:`Dataset.drop` and :py:meth:`Dataset.drop_dims`
+  that allows ignoring errors if a passed label or dimension is not in the dataset
+  (:issue:`2994`).
+  By `Andrew Ross <https://github.com/andrew-c-ross>`_.
+
 
 Bug fixes
 ~~~~~~~~~
diff --git a/setup.cfg b/setup.cfg
index 28cf17b9..cdfe2ec3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -34,6 +34,8 @@ known_first_party=xarray
 multi_line_output=4
 
 # Most of the numerical computing stack doesn't have type annotations yet.
+[mypy-affine.*]
+ignore_missing_imports = True
 [mypy-bottleneck.*]
 ignore_missing_imports = True
 [mypy-cdms2.*]
@@ -85,6 +87,12 @@ ignore_missing_imports = True
 [mypy-zarr.*]
 ignore_missing_imports = True
 
+# setuptools is not typed
+[mypy-setup]
+ignore_errors = True
+# versioneer code
+[mypy-versioneer.*]
+ignore_errors = True
 # written by versioneer
 [mypy-xarray._version]
 ignore_errors = True
diff --git a/xarray/core/coordinates.py b/xarray/core/coordinates.py
index ea3eaa0f..6a5795cc 100644
--- a/xarray/core/coordinates.py
+++ b/xarray/core/coordinates.py
@@ -193,7 +193,7 @@ class DatasetCoordinates(AbstractCoordinates):
 
         self._data._variables = variables
         self._data._coord_names.update(new_coord_names)
-        self._data._dims = dict(dims)
+        self._data._dims = dims
         self._data._indexes = None
 
     def __delitem__(self, key):
diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py
index 094b8615..4c3dcc27 100644
--- a/xarray/core/dataarray.py
+++ b/xarray/core/dataarray.py
@@ -2,6 +2,7 @@ import functools
 import sys
 import warnings
 from collections import OrderedDict
+from typing import Any
 
 import numpy as np
 import pandas as pd
@@ -67,7 +68,7 @@ def _infer_coords_and_dims(shape, coords, dims):
         for dim, coord in zip(dims, coords):
             var = as_variable(coord, name=dim)
             var.dims = (dim,)
-            new_coords[dim] = var
+            new_coords[dim] = var.to_index_variable()
 
     sizes = dict(zip(dims, shape))
     for k, v in new_coords.items():
@@ -1442,7 +1443,7 @@ class DataArray(AbstractArray, DataWithCoords):
 
         variable = self.variable.transpose(*dims)
         if transpose_coords:
-            coords = {}
+            coords = OrderedDict()  # type: OrderedDict[Any, Variable]
             for name, coord in self.coords.items():
                 coord_dims = tuple(dim for dim in dims if dim in coord.dims)
                 coords[name] = coord.variable.transpose(*coord_dims)
@@ -1461,7 +1462,7 @@ class DataArray(AbstractArray, DataWithCoords):
     def T(self) -> 'DataArray':
         return self.transpose()
 
-    def drop(self, labels, dim=None):
+    def drop(self, labels, dim=None, *, errors='raise'):
         """Drop coordinates or index labels from this DataArray.
 
         Parameters
@@ -1471,14 +1472,18 @@ class DataArray(AbstractArray, DataWithCoords):
         dim : str, optional
             Dimension along which to drop index labels. By default (if
             ``dim is None``), drops coordinates rather than index labels.
-
+        errors: {'raise', 'ignore'}, optional
+            If 'raise' (default), raises a ValueError error if
+            any of the coordinates or index labels passed are not
+            in the array. If 'ignore', any given labels that are in the
+            array are dropped and no error is raised.
         Returns
         -------
         dropped : DataArray
         """
         if utils.is_scalar(labels):
             labels = [labels]
-        ds = self._to_temp_dataset().drop(labels, dim)
+        ds = self._to_temp_dataset().drop(labels, dim, errors=errors)
         return self._from_temp_dataset(ds)
 
     def dropna(self, dim, how='any', thresh=None):
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index ced1dba0..13a6a6ee 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -100,7 +100,7 @@ def calculate_dimensions(variables):
     Returns dictionary mapping from dimension names to sizes. Raises ValueError
     if any of the dimension sizes conflict.
     """
-    dims = OrderedDict()
+    dims = {}
     last_used = {}
     scalar_vars = set(k for k, v in variables.items() if not v.dims)
     for k, var in variables.items():
@@ -692,7 +692,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
 
     @classmethod
     def _from_vars_and_coord_names(cls, variables, coord_names, attrs=None):
-        dims = dict(calculate_dimensions(variables))
+        dims = calculate_dimensions(variables)
         return cls._construct_direct(variables, coord_names, dims, attrs)
 
     # TODO(shoyer): renable type checking on this signature when pytype has a
@@ -753,18 +753,20 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
         coord_names: set = None,
         attrs: 'Optional[OrderedDict]' = __default,
         indexes: 'Optional[OrderedDict[Any, pd.Index]]' = __default,
+        encoding: Optional[dict] = __default,
         inplace: bool = False,
     ) -> T:
         """Replace variables with recalculated dimensions."""
-        dims = dict(calculate_dimensions(variables))
+        dims = calculate_dimensions(variables)
         return self._replace(
-            variables, coord_names, dims, attrs, indexes, inplace=inplace)
+            variables, coord_names, dims, attrs, indexes, encoding,
+            inplace=inplace)
 
     def _replace_vars_and_dims(  # type: ignore
         self: T,
         variables: 'OrderedDict[Any, Variable]' = None,
         coord_names: set = None,
-        dims: 'OrderedDict[Any, int]' = None,
+        dims: Dict[Any, int] = None,
         attrs: 'Optional[OrderedDict]' = __default,
         inplace: bool = False,
     ) -> T:
@@ -1080,6 +1082,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
         """
         del self._variables[key]
         self._coord_names.discard(key)
+        self._dims = calculate_dimensions(self._variables)
 
     # mutable objects should not be hashable
     # https://github.com/python/mypy/issues/4266
@@ -2469,7 +2472,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
             else:
                 # If dims includes a label of a non-dimension coordinate,
                 # it will be promoted to a 1D coordinate with a single value.
-                variables[k] = v.set_dims(k)
+                variables[k] = v.set_dims(k).to_index_variable()
 
         new_dims = self._dims.copy()
         new_dims.update(dim)
@@ -2823,7 +2826,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
             raise ValueError('One or more of the specified variables '
                              'cannot be found in this dataset')
 
-    def drop(self, labels, dim=None):
+    def drop(self, labels, dim=None, *, errors='raise'):
         """Drop variables or index labels from this dataset.
 
         Parameters
@@ -2833,33 +2836,41 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
         dim : None or str, optional
             Dimension along which to drop index labels. By default (if
             ``dim is None``), drops variables rather than index labels.
+        errors: {'raise', 'ignore'}, optional
+            If 'raise' (default), raises a ValueError error if
+            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.
 
         Returns
         -------
         dropped : Dataset
         """
+        if errors not in ['raise', 'ignore']:
+            raise ValueError('errors must be either "raise" or "ignore"')
         if utils.is_scalar(labels):
             labels = [labels]
         if dim is None:
-            return self._drop_vars(labels)
+            return self._drop_vars(labels, errors=errors)
         else:
             try:
                 index = self.indexes[dim]
             except KeyError:
                 raise ValueError(
                     'dimension %r does not have coordinate labels' % dim)
-            new_index = index.drop(labels)
+            new_index = index.drop(labels, errors=errors)
             return self.loc[{dim: new_index}]
 
-    def _drop_vars(self, names):
-        self._assert_all_in_dataset(names)
+    def _drop_vars(self, names, errors='raise'):
+        if errors == 'raise':
+            self._assert_all_in_dataset(names)
         drop = set(names)
         variables = OrderedDict((k, v) for k, v in self._variables.items()
                                 if k not in drop)
         coord_names = set(k for k in self._coord_names if k in variables)
         return self._replace_vars_and_dims(variables, coord_names)
 
-    def drop_dims(self, drop_dims):
+    def drop_dims(self, drop_dims, *, errors='raise'):
         """Drop dimensions and associated variables from this dataset.
 
         Parameters
@@ -2872,14 +2883,23 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
         obj : Dataset
             The dataset without the given dimensions (or any variables
             containing those dimensions)
+        errors: {'raise', 'ignore'}, optional
+            If 'raise' (default), raises a ValueError error if
+            any of the dimensions passed are not
+            in the dataset. If 'ignore', any given dimensions that are in the
+            dataset are dropped and no error is raised.
         """
+        if errors not in ['raise', 'ignore']:
+            raise ValueError('errors must be either "raise" or "ignore"')
+
         if utils.is_scalar(drop_dims):
             drop_dims = [drop_dims]
 
-        missing_dimensions = [d for d in drop_dims if d not in self.dims]
-        if missing_dimensions:
-            raise ValueError('Dataset does not contain the dimensions: %s'
-                             % missing_dimensions)
+        if errors == 'raise':
+            missing_dimensions = [d for d in drop_dims if d not in self.dims]
+            if missing_dimensions:
+                raise ValueError('Dataset does not contain the dimensions: %s'
+                                 % missing_dimensions)
 
         drop_vars = set(k for k, v in self._variables.items()
                         for d in v.dims if d in drop_dims)
@@ -3556,12 +3576,15 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords):
     def _unary_op(f, keep_attrs=False):
         @functools.wraps(f)
         def func(self, *args, **kwargs):
-            ds = self.coords.to_dataset()
-            for k in self.data_vars:
-                ds._variables[k] = f(self._variables[k], *args, **kwargs)
-            if keep_attrs:
-                ds._attrs = self._attrs
-            return ds
+            variables = OrderedDict()
+            for k, v in self._variables.items():
+                if k in self._coord_names:
+                    variables[k] = v
+                else:
+                    variables[k] = f(v, *args, **kwargs)
+            attrs = self._attrs if keep_attrs else None
+            return self._replace_with_new_dims(
+                variables, attrs=attrs, encoding=None)
 
         return func
 
diff --git a/xarray/core/merge.py b/xarray/core/merge.py
index c2c6aee7..94a5d4af 100644
--- a/xarray/core/merge.py
+++ b/xarray/core/merge.py
@@ -473,7 +473,7 @@ def merge_core(objs,
                          'coordinates or not in the merged result: %s'
                          % ambiguous_coords)
 
-    return variables, coord_names, dict(dims)
+    return variables, coord_names, dims
 
 
 def merge(objects, compat='no_conflicts', join='outer', fill_value=dtypes.NA):


Test patch:
diff --git a/xarray/testing.py b/xarray/testing.py
index ed015181..42c91b1e 100644
--- a/xarray/testing.py
+++ b/xarray/testing.py
@@ -1,10 +1,15 @@
 """Testing functions exposed to the user API"""
 from collections import OrderedDict
+from typing import Hashable, Union
 
 import numpy as np
 import pandas as pd
 
-from xarray.core import duck_array_ops, formatting
+from xarray.core import duck_array_ops
+from xarray.core import formatting
+from xarray.core.dataarray import DataArray
+from xarray.core.dataset import Dataset
+from xarray.core.variable import IndexVariable, Variable
 from xarray.core.indexes import default_indexes
 
 
@@ -48,12 +53,11 @@ def assert_equal(a, b):
     assert_identical, assert_allclose, Dataset.equals, DataArray.equals,
     numpy.testing.assert_array_equal
     """
-    import xarray as xr
     __tracebackhide__ = True  # noqa: F841
     assert type(a) == type(b)  # noqa
-    if isinstance(a, (xr.Variable, xr.DataArray)):
+    if isinstance(a, (Variable, DataArray)):
         assert a.equals(b), formatting.diff_array_repr(a, b, 'equals')
-    elif isinstance(a, xr.Dataset):
+    elif isinstance(a, Dataset):
         assert a.equals(b), formatting.diff_dataset_repr(a, b, 'equals')
     else:
         raise TypeError('{} not supported by assertion comparison'
@@ -77,15 +81,14 @@ def assert_identical(a, b):
     --------
     assert_equal, assert_allclose, Dataset.equals, DataArray.equals
     """
-    import xarray as xr
     __tracebackhide__ = True  # noqa: F841
     assert type(a) == type(b)  # noqa
-    if isinstance(a, xr.Variable):
+    if isinstance(a, Variable):
         assert a.identical(b), formatting.diff_array_repr(a, b, 'identical')
-    elif isinstance(a, xr.DataArray):
+    elif isinstance(a, DataArray):
         assert a.name == b.name
         assert a.identical(b), formatting.diff_array_repr(a, b, 'identical')
-    elif isinstance(a, (xr.Dataset, xr.Variable)):
+    elif isinstance(a, (Dataset, Variable)):
         assert a.identical(b), formatting.diff_dataset_repr(a, b, 'identical')
     else:
         raise TypeError('{} not supported by assertion comparison'
@@ -117,15 +120,14 @@ def assert_allclose(a, b, rtol=1e-05, atol=1e-08, decode_bytes=True):
     --------
     assert_identical, assert_equal, numpy.testing.assert_allclose
     """
-    import xarray as xr
     __tracebackhide__ = True  # noqa: F841
     assert type(a) == type(b)  # noqa
     kwargs = dict(rtol=rtol, atol=atol, decode_bytes=decode_bytes)
-    if isinstance(a, xr.Variable):
+    if isinstance(a, Variable):
         assert a.dims == b.dims
         allclose = _data_allclose_or_equiv(a.values, b.values, **kwargs)
         assert allclose, '{}\n{}'.format(a.values, b.values)
-    elif isinstance(a, xr.DataArray):
+    elif isinstance(a, DataArray):
         assert_allclose(a.variable, b.variable, **kwargs)
         assert set(a.coords) == set(b.coords)
         for v in a.coords.variables:
@@ -135,7 +137,7 @@ def assert_allclose(a, b, rtol=1e-05, atol=1e-08, decode_bytes=True):
                                                b.coords[v].values, **kwargs)
             assert allclose, '{}\n{}'.format(a.coords[v].values,
                                              b.coords[v].values)
-    elif isinstance(a, xr.Dataset):
+    elif isinstance(a, Dataset):
         assert set(a.data_vars) == set(b.data_vars)
         assert set(a.coords) == set(b.coords)
         for k in list(a.variables) + list(a.coords):
@@ -147,14 +149,12 @@ def assert_allclose(a, b, rtol=1e-05, atol=1e-08, decode_bytes=True):
 
 
 def _assert_indexes_invariants_checks(indexes, possible_coord_variables, dims):
-    import xarray as xr
-
     assert isinstance(indexes, OrderedDict), indexes
     assert all(isinstance(v, pd.Index) for v in indexes.values()), \
         {k: type(v) for k, v in indexes.items()}
 
     index_vars = {k for k, v in possible_coord_variables.items()
-                  if isinstance(v, xr.IndexVariable)}
+                  if isinstance(v, IndexVariable)}
     assert indexes.keys() <= index_vars, (set(indexes), index_vars)
 
     # Note: when we support non-default indexes, these checks should be opt-in
@@ -166,17 +166,97 @@ def _assert_indexes_invariants_checks(indexes, possible_coord_variables, dims):
         (indexes, defaults)
 
 
-def _assert_indexes_invariants(a):
-    """Separate helper function for checking indexes invariants only."""
-    import xarray as xr
-
-    if isinstance(a, xr.DataArray):
-        if a._indexes is not None:
-            _assert_indexes_invariants_checks(a._indexes, a._coords, a.dims)
-    elif isinstance(a, xr.Dataset):
-        if a._indexes is not None:
-            _assert_indexes_invariants_checks(
-                a._indexes, a._variables, a._dims)
-    elif isinstance(a, xr.Variable):
-        # no indexes
-        pass
+def _assert_variable_invariants(var: Variable, name: Hashable = None):
+    if name is None:
+        name_or_empty = ()  # type: tuple
+    else:
+        name_or_empty = (name,)
+    assert isinstance(var._dims, tuple), name_or_empty + (var._dims,)
+    assert len(var._dims) == len(var._data.shape), \
+        name_or_empty + (var._dims, var._data.shape)
+    assert isinstance(var._encoding, (type(None), dict)), \
+        name_or_empty + (var._encoding,)
+    assert isinstance(var._attrs, (type(None), OrderedDict)), \
+        name_or_empty + (var._attrs,)
+
+
+def _assert_dataarray_invariants(da: DataArray):
+    assert isinstance(da._variable, Variable), da._variable
+    _assert_variable_invariants(da._variable)
+
+    assert isinstance(da._coords, OrderedDict), da._coords
+    assert all(
+        isinstance(v, Variable) for v in da._coords.values()), da._coords
+    assert all(set(v.dims) <= set(da.dims) for v in da._coords.values()), \
+        (da.dims, {k: v.dims for k, v in da._coords.items()})
+    assert all(isinstance(v, IndexVariable)
+               for (k, v) in da._coords.items()
+               if v.dims == (k,)), \
+        {k: type(v) for k, v in da._coords.items()}
+    for k, v in da._coords.items():
+        _assert_variable_invariants(v, k)
+
+    if da._indexes is not None:
+        _assert_indexes_invariants_checks(da._indexes, da._coords, da.dims)
+
+    assert da._initialized is True
+
+
+def _assert_dataset_invariants(ds: Dataset):
+    assert isinstance(ds._variables, OrderedDict), type(ds._variables)
+    assert all(
+        isinstance(v, Variable) for v in ds._variables.values()), \
+        ds._variables
+    for k, v in ds._variables.items():
+        _assert_variable_invariants(v, k)
+
+    assert isinstance(ds._coord_names, set), ds._coord_names
+    assert ds._coord_names <= ds._variables.keys(), \
+        (ds._coord_names, set(ds._variables))
+
+    assert type(ds._dims) is dict, ds._dims
+    assert all(isinstance(v, int) for v in ds._dims.values()), ds._dims
+    var_dims = set()  # type: set
+    for v in ds._variables.values():
+        var_dims.update(v.dims)
+    assert ds._dims.keys() == var_dims, (set(ds._dims), var_dims)
+    assert all(ds._dims[k] == v.sizes[k]
+               for v in ds._variables.values()
+               for k in v.sizes), \
+        (ds._dims, {k: v.sizes for k, v in ds._variables.items()})
+    assert all(isinstance(v, IndexVariable)
+               for (k, v) in ds._variables.items()
+               if v.dims == (k,)), \
+        {k: type(v) for k, v in ds._variables.items() if v.dims == (k,)}
+    assert all(v.dims == (k,)
+               for (k, v) in ds._variables.items()
+               if k in ds._dims), \
+        {k: v.dims for k, v in ds._variables.items() if k in ds._dims}
+
+    if ds._indexes is not None:
+        _assert_indexes_invariants_checks(ds._indexes, ds._variables, ds._dims)
+
+    assert isinstance(ds._encoding, (type(None), dict))
+    assert isinstance(ds._attrs, (type(None), OrderedDict))
+    assert ds._initialized is True
+
+
+def _assert_internal_invariants(
+    xarray_obj: Union[DataArray, Dataset, Variable],
+):
+    """Validate that an xarray object satisfies its own internal invariants.
+
+    This exists for the benefit of xarray's own test suite, but may be useful
+    in external projects if they (ill-advisedly) create objects using xarray's
+    private APIs.
+    """
+    if isinstance(xarray_obj, Variable):
+        _assert_variable_invariants(xarray_obj)
+    elif isinstance(xarray_obj, DataArray):
+        _assert_dataarray_invariants(xarray_obj)
+    elif isinstance(xarray_obj, Dataset):
+        _assert_dataset_invariants(xarray_obj)
+    else:
+        raise TypeError(
+            '{} is not a supported type for xarray invariant checks'
+            .format(type(xarray_obj)))
diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py
index d3fe5e16..dc8b26e4 100644
--- a/xarray/tests/__init__.py
+++ b/xarray/tests/__init__.py
@@ -168,21 +168,20 @@ def source_ndarray(array):
 
 # Internal versions of xarray's test functions that validate additional
 # invariants
-# TODO: add more invariant checks.
 
 def assert_equal(a, b):
     xarray.testing.assert_equal(a, b)
-    xarray.testing._assert_indexes_invariants(a)
-    xarray.testing._assert_indexes_invariants(b)
+    xarray.testing._assert_internal_invariants(a)
+    xarray.testing._assert_internal_invariants(b)
 
 
 def assert_identical(a, b):
     xarray.testing.assert_identical(a, b)
-    xarray.testing._assert_indexes_invariants(a)
-    xarray.testing._assert_indexes_invariants(b)
+    xarray.testing._assert_internal_invariants(a)
+    xarray.testing._assert_internal_invariants(b)
 
 
 def assert_allclose(a, b, **kwargs):
     xarray.testing.assert_allclose(a, b, **kwargs)
-    xarray.testing._assert_indexes_invariants(a)
-    xarray.testing._assert_indexes_invariants(b)
+    xarray.testing._assert_internal_invariants(a)
+    xarray.testing._assert_internal_invariants(b)
diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py
index fd9076e7..a8825055 100644
--- a/xarray/tests/test_dataarray.py
+++ b/xarray/tests/test_dataarray.py
@@ -1859,19 +1859,34 @@ class TestDataArray:
         with pytest.raises(ValueError):
             arr.drop('not found')
 
+        actual = expected.drop('not found', errors='ignore')
+        assert_identical(actual, expected)
+
         with raises_regex(ValueError, 'cannot be found'):
             arr.drop(None)
 
+        actual = expected.drop(None, errors='ignore')
+        assert_identical(actual, expected)
+
         renamed = arr.rename('foo')
         with raises_regex(ValueError, 'cannot be found'):
             renamed.drop('foo')
 
+        actual = renamed.drop('foo', errors='ignore')
+        assert_identical(actual, renamed)
+
     def test_drop_index_labels(self):
         arr = DataArray(np.random.randn(2, 3), coords={'y': [0, 1, 2]},
                         dims=['x', 'y'])
         actual = arr.drop([0, 1], dim='y')
         expected = arr[:, 2:]
-        assert_identical(expected, actual)
+        assert_identical(actual, expected)
+
+        with raises_regex((KeyError, ValueError), 'not .* in axis'):
+            actual = arr.drop([0, 1, 3], dim='y')
+
+        actual = arr.drop([0, 1, 3], dim='y', errors='ignore')
+        assert_identical(actual, expected)
 
     def test_dropna(self):
         x = np.random.randn(4, 4)
diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py
index 812e2893..8cd129e3 100644
--- a/xarray/tests/test_dataset.py
+++ b/xarray/tests/test_dataset.py
@@ -1889,6 +1889,15 @@ class TestDataset:
         with raises_regex(ValueError, 'cannot be found'):
             data.drop('not_found_here')
 
+        actual = data.drop('not_found_here', errors='ignore')
+        assert_identical(data, actual)
+
+        actual = data.drop(['not_found_here'], errors='ignore')
+        assert_identical(data, actual)
+
+        actual = data.drop(['time', 'not_found_here'], errors='ignore')
+        assert_identical(expected, actual)
+
     def test_drop_index_labels(self):
         data = Dataset({'A': (['x', 'y'], np.random.randn(2, 3)),
                         'x': ['a', 'b']})
@@ -1907,6 +1916,16 @@ class TestDataset:
             # not contained in axis
             data.drop(['c'], dim='x')
 
+        actual = data.drop(['c'], dim='x', errors='ignore')
+        assert_identical(data, actual)
+
+        with pytest.raises(ValueError):
+            data.drop(['c'], dim='x', errors='wrong_value')
+
+        actual = data.drop(['a', 'b', 'c'], 'x', errors='ignore')
+        expected = data.isel(x=slice(0, 0))
+        assert_identical(expected, actual)
+
         with raises_regex(
                 ValueError, 'does not have coordinate labels'):
             data.drop(1, 'y')
@@ -1931,6 +1950,22 @@ class TestDataset:
         with pytest.raises((ValueError, KeyError)):
             data.drop_dims('z')  # not a dimension
 
+        with pytest.raises((ValueError, KeyError)):
+            data.drop_dims(None)
+
+        actual = data.drop_dims('z', errors='ignore')
+        assert_identical(data, actual)
+
+        actual = data.drop_dims(None, errors='ignore')
+        assert_identical(data, actual)
+
+        with pytest.raises(ValueError):
+            actual = data.drop_dims('z', errors='wrong_value')
+
+        actual = data.drop_dims(['x', 'y', 'z'], errors='ignore')
+        expected = data.drop(['A', 'B', 'x'])
+        assert_identical(expected, actual)
+
     def test_copy(self):
         data = create_test_data()
         data.attrs['Test'] = [1, 2, 3]
@@ -2752,6 +2787,11 @@ class TestDataset:
         assert set(data.variables) == all_items - set(['var1', 'numbers'])
         assert 'numbers' not in data.coords
 
+        expected = Dataset()
+        actual = Dataset({'y': ('x', [1, 2])})
+        del actual['y']
+        assert_identical(expected, actual)
+
     def test_squeeze(self):
         data = Dataset({'foo': (['x', 'y', 'z'], [[[1], [2]]])})
         for args in [[], [['x']], [['x', 'z']]]: