Repository: pydata/xarray

Issue:
check index invariants in test_dataset, test_dataarray
There are a large number of tests in `test_dataarray.py` and `test_dataset.py` that use the `assert actual.equals(expected)` pattern. 

We should switch these to be `assert_equal(actual, expected)` so that we run the invariant checks:

https://github.com/pydata/xarray/blob/acf7d4157ca44f05c85a92d1b914b68738988773/xarray/tests/__init__.py#L150-L154

Seems like an easy use of regexes for someone that knows them (i.e. not me ;))

Discussion:
dcherian: Thanks @max-sixty changing to `assert_*` also means we run `assert_internal_invariants` which will help with the indexing refactor. I made a couple of changes to see if that fixes the tests (hope thats OK)

Patch:
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 6682bbdf..2cbf55f5 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -75,7 +75,7 @@ jobs:
   - task: UsePythonVersion@0
   - bash: python -m pip install black
     displayName: Install black
-  - bash: black --check .
+  - bash: black --diff --check .
     displayName: black formatting check
 
 - job: Doctests
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index 151af2de..3bfe5fe4 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -22,9 +22,15 @@ v0.16.3 (unreleased)
 
 Breaking changes
 ~~~~~~~~~~~~~~~~
+- As a result of :pull:`4684` the default units encoding for
+  datetime-like values (``np.datetime64[ns]`` or ``cftime.datetime``) will now
+  always be set such that ``int64`` values can be used.  In the past, no units
+  finer than "seconds" were chosen, which would sometimes mean that ``float64``
+  values were required, which would lead to inaccurate I/O round-trips.
 - remove deprecated ``autoclose`` kwargs from :py:func:`open_dataset` (:pull: `4725`).
   By `Aureliana Barghini <https://github.com/aurghs>`_
 
+
 New Features
 ~~~~~~~~~~~~
 
@@ -34,6 +40,12 @@ Bug fixes
 
 - :py:meth:`DataArray.resample` and :py:meth:`Dataset.resample` do not trigger computations anymore if :py:meth:`Dataset.weighted` or :py:meth:`DataArray.weighted` are applied (:issue:`4625`, :pull:`4668`). By `Julius Busecke <https://github.com/jbusecke>`_.
 - :py:func:`merge` with ``combine_attrs='override'`` makes a copy of the attrs (:issue:`4627`).
+- By default, when possible, xarray will now always use values of type ``int64`` when encoding
+  and decoding ``numpy.datetime64[ns]`` datetimes.  This ensures that maximum
+  precision and accuracy are maintained in the round-tripping process
+  (:issue:`4045`, :pull:`4684`). It also enables encoding and decoding standard calendar
+  dates with time units of nanoseconds (:pull:`4400`). By `Spencer Clark
+  <https://github.com/spencerkclark>`_ and `Mark Harfouche <http://github.com/hmaarrfk>`_.
 - :py:meth:`DataArray.astype`, :py:meth:`Dataset.astype` and :py:meth:`Variable.astype` support
   the ``order`` and ``subok`` parameters again. This fixes a regression introduced in version 0.16.1
   (:issue:`4644`, :pull:`4683`).
@@ -63,6 +75,11 @@ Internal Changes
   - Run the tests in parallel using pytest-xdist (:pull:`4694`).
 
   By `Justus Magin <https://github.com/keewis>`_ and `Mathias Hauser <https://github.com/mathause>`_.
+  
+- Replace all usages of ``assert x.identical(y)`` with ``assert_identical(x,  y)`` 
+  for clearer error messages.
+  (:pull:`4752`);
+  By `Maximilian Roos <https://github.com/max-sixty>`_.
 
 .. _whats-new.0.16.2:
 
diff --git a/xarray/coding/times.py b/xarray/coding/times.py
index 59f8b897..3d877a16 100644
--- a/xarray/coding/times.py
+++ b/xarray/coding/times.py
@@ -26,6 +26,7 @@ from .variables import (
 _STANDARD_CALENDARS = {"standard", "gregorian", "proleptic_gregorian"}
 
 _NS_PER_TIME_DELTA = {
+    "ns": 1,
     "us": int(1e3),
     "ms": int(1e6),
     "s": int(1e9),
@@ -35,7 +36,15 @@ _NS_PER_TIME_DELTA = {
 }
 
 TIME_UNITS = frozenset(
-    ["days", "hours", "minutes", "seconds", "milliseconds", "microseconds"]
+    [
+        "days",
+        "hours",
+        "minutes",
+        "seconds",
+        "milliseconds",
+        "microseconds",
+        "nanoseconds",
+    ]
 )
 
 
@@ -44,6 +53,7 @@ def _netcdf_to_numpy_timeunit(units):
     if not units.endswith("s"):
         units = "%ss" % units
     return {
+        "nanoseconds": "ns",
         "microseconds": "us",
         "milliseconds": "ms",
         "seconds": "s",
@@ -151,21 +161,22 @@ def _decode_datetime_with_pandas(flat_num_dates, units, calendar):
         # strings, in which case we fall back to using cftime
         raise OutOfBoundsDatetime
 
-    # fixes: https://github.com/pydata/pandas/issues/14068
-    # these lines check if the the lowest or the highest value in dates
-    # cause an OutOfBoundsDatetime (Overflow) error
-    with warnings.catch_warnings():
-        warnings.filterwarnings("ignore", "invalid value encountered", RuntimeWarning)
-        pd.to_timedelta(flat_num_dates.min(), delta) + ref_date
-        pd.to_timedelta(flat_num_dates.max(), delta) + ref_date
-
-    # Cast input dates to integers of nanoseconds because `pd.to_datetime`
-    # works much faster when dealing with integers
-    # make _NS_PER_TIME_DELTA an array to ensure type upcasting
-    flat_num_dates_ns_int = (
-        flat_num_dates.astype(np.float64) * _NS_PER_TIME_DELTA[delta]
-    ).astype(np.int64)
+    # To avoid integer overflow when converting to nanosecond units for integer
+    # dtypes smaller than np.int64 cast all integer-dtype arrays to np.int64
+    # (GH 2002).
+    if flat_num_dates.dtype.kind == "i":
+        flat_num_dates = flat_num_dates.astype(np.int64)
 
+    # Cast input ordinals to integers of nanoseconds because pd.to_timedelta
+    # works much faster when dealing with integers (GH 1399).
+    flat_num_dates_ns_int = (flat_num_dates * _NS_PER_TIME_DELTA[delta]).astype(
+        np.int64
+    )
+
+    # Use pd.to_timedelta to safely cast integer values to timedeltas,
+    # and add those to a Timestamp to safely produce a DatetimeIndex.  This
+    # ensures that we do not encounter integer overflow at any point in the
+    # process without raising OutOfBoundsDatetime.
     return (pd.to_timedelta(flat_num_dates_ns_int, "ns") + ref_date).values
 
 
@@ -252,11 +263,24 @@ def decode_cf_timedelta(num_timedeltas, units):
 
 
 def _infer_time_units_from_diff(unique_timedeltas):
-    for time_unit in ["days", "hours", "minutes", "seconds"]:
+    # Note that the modulus operator was only implemented for np.timedelta64
+    # arrays as of NumPy version 1.16.0.  Once our minimum version of NumPy
+    # supported is greater than or equal to this we will no longer need to cast
+    # unique_timedeltas to a TimedeltaIndex.  In the meantime, however, the
+    # modulus operator works for TimedeltaIndex objects.
+    unique_deltas_as_index = pd.TimedeltaIndex(unique_timedeltas)
+    for time_unit in [
+        "days",
+        "hours",
+        "minutes",
+        "seconds",
+        "milliseconds",
+        "microseconds",
+        "nanoseconds",
+    ]:
         delta_ns = _NS_PER_TIME_DELTA[_netcdf_to_numpy_timeunit(time_unit)]
         unit_delta = np.timedelta64(delta_ns, "ns")
-        diffs = unique_timedeltas / unit_delta
-        if np.all(diffs == diffs.astype(int)):
+        if np.all(unique_deltas_as_index % unit_delta == np.timedelta64(0, "ns")):
             return time_unit
     return "seconds"
 
@@ -416,7 +440,15 @@ def encode_cf_datetime(dates, units=None, calendar=None):
         # Wrap the dates in a DatetimeIndex to do the subtraction to ensure
         # an OverflowError is raised if the ref_date is too far away from
         # dates to be encoded (GH 2272).
-        num = (pd.DatetimeIndex(dates.ravel()) - ref_date) / time_delta
+        dates_as_index = pd.DatetimeIndex(dates.ravel())
+        time_deltas = dates_as_index - ref_date
+
+        # Use floor division if time_delta evenly divides all differences
+        # to preserve integer dtype if possible (GH 4045).
+        if np.all(time_deltas % time_delta == np.timedelta64(0, "ns")):
+            num = time_deltas // time_delta
+        else:
+            num = time_deltas / time_delta
         num = num.values.reshape(dates.shape)
 
     except (OutOfBoundsDatetime, OverflowError):


Test patch:
diff --git a/xarray/tests/test_coding_times.py b/xarray/tests/test_coding_times.py
index d35cad01..dfd558f7 100644
--- a/xarray/tests/test_coding_times.py
+++ b/xarray/tests/test_coding_times.py
@@ -6,7 +6,7 @@ import pandas as pd
 import pytest
 from pandas.errors import OutOfBoundsDatetime
 
-from xarray import DataArray, Dataset, Variable, coding, decode_cf
+from xarray import DataArray, Dataset, Variable, coding, conventions, decode_cf
 from xarray.coding.times import (
     cftime_to_nptime,
     decode_cf_datetime,
@@ -479,27 +479,36 @@ def test_decoded_cf_datetime_array_2d():
     assert_array_equal(np.asarray(result), expected)
 
 
+FREQUENCIES_TO_ENCODING_UNITS = {
+    "N": "nanoseconds",
+    "U": "microseconds",
+    "L": "milliseconds",
+    "S": "seconds",
+    "T": "minutes",
+    "H": "hours",
+    "D": "days",
+}
+
+
+@pytest.mark.parametrize(("freq", "units"), FREQUENCIES_TO_ENCODING_UNITS.items())
+def test_infer_datetime_units(freq, units):
+    dates = pd.date_range("2000", periods=2, freq=freq)
+    expected = f"{units} since 2000-01-01 00:00:00"
+    assert expected == coding.times.infer_datetime_units(dates)
+
+
 @pytest.mark.parametrize(
     ["dates", "expected"],
     [
-        (pd.date_range("1900-01-01", periods=5), "days since 1900-01-01 00:00:00"),
-        (
-            pd.date_range("1900-01-01 12:00:00", freq="H", periods=2),
-            "hours since 1900-01-01 12:00:00",
-        ),
         (
             pd.to_datetime(["1900-01-01", "1900-01-02", "NaT"]),
             "days since 1900-01-01 00:00:00",
         ),
-        (
-            pd.to_datetime(["1900-01-01", "1900-01-02T00:00:00.005"]),
-            "seconds since 1900-01-01 00:00:00",
-        ),
         (pd.to_datetime(["NaT", "1900-01-01"]), "days since 1900-01-01 00:00:00"),
         (pd.to_datetime(["NaT"]), "days since 1970-01-01 00:00:00"),
     ],
 )
-def test_infer_datetime_units(dates, expected):
+def test_infer_datetime_units_with_NaT(dates, expected):
     assert expected == coding.times.infer_datetime_units(dates)
 
 
@@ -535,6 +544,7 @@ def test_infer_cftime_datetime_units(calendar, date_args, expected):
         ("1h", "hours", np.int64(1)),
         ("1ms", "milliseconds", np.int64(1)),
         ("1us", "microseconds", np.int64(1)),
+        ("1ns", "nanoseconds", np.int64(1)),
         (["NaT", "0s", "1s"], None, [np.nan, 0, 1]),
         (["30m", "60m"], "hours", [0.5, 1.0]),
         ("NaT", "days", np.nan),
@@ -958,3 +968,30 @@ def test_decode_ambiguous_time_warns(calendar):
         assert not record
 
     np.testing.assert_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("encoding_units", FREQUENCIES_TO_ENCODING_UNITS.values())
+@pytest.mark.parametrize("freq", FREQUENCIES_TO_ENCODING_UNITS.keys())
+def test_encode_cf_datetime_defaults_to_correct_dtype(encoding_units, freq):
+    times = pd.date_range("2000", periods=3, freq=freq)
+    units = f"{encoding_units} since 2000-01-01"
+    encoded, _, _ = coding.times.encode_cf_datetime(times, units)
+
+    numpy_timeunit = coding.times._netcdf_to_numpy_timeunit(encoding_units)
+    encoding_units_as_timedelta = np.timedelta64(1, numpy_timeunit)
+    if pd.to_timedelta(1, freq) >= encoding_units_as_timedelta:
+        assert encoded.dtype == np.int64
+    else:
+        assert encoded.dtype == np.float64
+
+
+@pytest.mark.parametrize("freq", FREQUENCIES_TO_ENCODING_UNITS.keys())
+def test_encode_decode_roundtrip(freq):
+    # See GH 4045. Prior to GH 4684 this test would fail for frequencies of
+    # "S", "L", "U", and "N".
+    initial_time = pd.date_range("1678-01-01", periods=1)
+    times = initial_time.append(pd.date_range("1968", periods=2, freq=freq))
+    variable = Variable(["time"], times)
+    encoded = conventions.encode_cf_variable(variable)
+    decoded = conventions.decode_cf_variable("time", encoded)
+    assert_equal(variable, decoded)
diff --git a/xarray/tests/test_combine.py b/xarray/tests/test_combine.py
index 86c96151..109b78f0 100644
--- a/xarray/tests/test_combine.py
+++ b/xarray/tests/test_combine.py
@@ -482,7 +482,7 @@ class TestNestedCombine:
 
         expected = data[["var1", "var2"]]
         actual = combine_nested(objs, concat_dim=[None, "dim2"])
-        assert expected.identical(actual)
+        assert_identical(expected, actual)
 
     def test_auto_combine_2d(self):
         ds = create_test_data
diff --git a/xarray/tests/test_computation.py b/xarray/tests/test_computation.py
index 1922977f..a1c8e75b 100644
--- a/xarray/tests/test_computation.py
+++ b/xarray/tests/test_computation.py
@@ -28,9 +28,11 @@ dask = pytest.importorskip("dask")
 
 
 def assert_identical(a, b):
+    """ A version of this function which accepts numpy arrays """
+    from xarray.testing import assert_identical as assert_identical_
+
     if hasattr(a, "identical"):
-        msg = f"not identical:\n{a!r}\n{b!r}"
-        assert a.identical(b), msg
+        assert_identical_(a, b)
     else:
         assert_array_equal(a, b)
 
@@ -1306,7 +1308,7 @@ def test_dot(use_dask):
     # for only a single array is passed without dims argument, just return
     # as is
     actual = xr.dot(da_a)
-    assert da_a.identical(actual)
+    assert_identical(da_a, actual)
 
     # test for variable
     actual = xr.dot(da_a.variable, da_b.variable)
diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py
index 1ddb97e5..204ff706 100644
--- a/xarray/tests/test_dataarray.py
+++ b/xarray/tests/test_dataarray.py
@@ -1494,8 +1494,8 @@ class TestDataArray:
         new1 = arr1.broadcast_like(arr2)
         new2 = arr2.broadcast_like(arr1)
 
-        assert orig1.identical(new1)
-        assert orig2.identical(new2)
+        assert_identical(orig1, new1)
+        assert_identical(orig2, new2)
 
         orig3 = DataArray(np.random.randn(5), [("x", range(5))])
         orig4 = DataArray(np.random.randn(6), [("y", range(6))])
diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py
index 099fb5c0..b007aa26 100644
--- a/xarray/tests/test_dataset.py
+++ b/xarray/tests/test_dataset.py
@@ -2127,7 +2127,8 @@ class TestDataset:
     def test_align_non_unique(self):
         x = Dataset({"foo": ("x", [3, 4, 5]), "x": [0, 0, 1]})
         x1, x2 = align(x, x)
-        assert x1.identical(x) and x2.identical(x)
+        assert_identical(x1, x)
+        assert_identical(x2, x)
 
         y = Dataset({"bar": ("x", [6, 7]), "x": [0, 1]})
         with raises_regex(ValueError, "cannot reindex or align"):
diff --git a/xarray/tests/test_extensions.py b/xarray/tests/test_extensions.py
index 5af0f6d8..fa91e5c8 100644
--- a/xarray/tests/test_extensions.py
+++ b/xarray/tests/test_extensions.py
@@ -4,7 +4,7 @@ import pytest
 
 import xarray as xr
 
-from . import raises_regex
+from . import assert_identical, raises_regex
 
 
 @xr.register_dataset_accessor("example_accessor")
@@ -61,20 +61,20 @@ class TestAccessor:
     def test_pickle_dataset(self):
         ds = xr.Dataset()
         ds_restored = pickle.loads(pickle.dumps(ds))
-        assert ds.identical(ds_restored)
+        assert_identical(ds, ds_restored)
 
         # state save on the accessor is restored
         assert ds.example_accessor is ds.example_accessor
         ds.example_accessor.value = "foo"
         ds_restored = pickle.loads(pickle.dumps(ds))
-        assert ds.identical(ds_restored)
+        assert_identical(ds, ds_restored)
         assert ds_restored.example_accessor.value == "foo"
 
     def test_pickle_dataarray(self):
         array = xr.Dataset()
         assert array.example_accessor is array.example_accessor
         array_restored = pickle.loads(pickle.dumps(array))
-        assert array.identical(array_restored)
+        assert_identical(array, array_restored)
 
     def test_broken_accessor(self):
         # regression test for GH933
diff --git a/xarray/tests/test_merge.py b/xarray/tests/test_merge.py
index afb65a35..34b138e1 100644
--- a/xarray/tests/test_merge.py
+++ b/xarray/tests/test_merge.py
@@ -4,7 +4,7 @@ import pytest
 import xarray as xr
 from xarray.core import dtypes, merge
 from xarray.core.merge import MergeError
-from xarray.testing import assert_identical
+from xarray.testing import assert_equal, assert_identical
 
 from . import raises_regex
 from .test_dataset import create_test_data
@@ -33,17 +33,17 @@ class TestMergeFunction:
         data = create_test_data()
         actual = xr.merge([data.var1, data.var2])
         expected = data[["var1", "var2"]]
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
 
     def test_merge_datasets(self):
         data = create_test_data()
 
         actual = xr.merge([data[["var1"]], data[["var2"]]])
         expected = data[["var1", "var2"]]
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
 
         actual = xr.merge([data, data])
-        assert actual.identical(data)
+        assert_identical(actual, data)
 
     def test_merge_dataarray_unnamed(self):
         data = xr.DataArray([1, 2], dims="x")
@@ -61,7 +61,7 @@ class TestMergeFunction:
         actual = xr.merge([data.var1, data.var2])
         expected = data[["var1", "var2"]]
         expected.attrs = expected_attrs
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
 
     @pytest.mark.parametrize(
         "combine_attrs, var1_attrs, var2_attrs, expected_attrs, expect_exception",
@@ -107,7 +107,7 @@ class TestMergeFunction:
             actual = xr.merge([data.var1, data.var2], combine_attrs=combine_attrs)
             expected = data[["var1", "var2"]]
             expected.attrs = expected_attrs
-            assert actual.identical(expected)
+            assert_identical(actual, expected)
 
     def test_merge_attrs_override_copy(self):
         ds1 = xr.Dataset(attrs={"x": 0})
@@ -119,12 +119,12 @@ class TestMergeFunction:
     def test_merge_dicts_simple(self):
         actual = xr.merge([{"foo": 0}, {"bar": "one"}, {"baz": 3.5}])
         expected = xr.Dataset({"foo": 0, "bar": "one", "baz": 3.5})
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
 
     def test_merge_dicts_dims(self):
         actual = xr.merge([{"y": ("x", [13])}, {"x": [12]}])
         expected = xr.Dataset({"x": [12], "y": ("x", [13])})
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
 
     def test_merge_error(self):
         ds = xr.Dataset({"x": 0})
@@ -174,7 +174,7 @@ class TestMergeFunction:
 
         expected = data[["var1", "var2"]]
         actual = xr.merge([data1.var1, data2.var2], compat="no_conflicts")
-        assert expected.identical(actual)
+        assert_identical(expected, actual)
 
         data1["var1"][:, :5] = np.nan
         data2["var1"][:, 5:] = np.nan
@@ -183,22 +183,22 @@ class TestMergeFunction:
         del data2["var3"]
 
         actual = xr.merge([data1, data2], compat="no_conflicts")
-        assert data.equals(actual)
+        assert_equal(data, actual)
 
     def test_merge_no_conflicts_preserve_attrs(self):
         data = xr.Dataset({"x": ([], 0, {"foo": "bar"})})
         actual = xr.merge([data, data])
-        assert data.identical(actual)
+        assert_identical(data, actual)
 
     def test_merge_no_conflicts_broadcast(self):
         datasets = [xr.Dataset({"x": ("y", [0])}), xr.Dataset({"x": np.nan})]
         actual = xr.merge(datasets)
         expected = xr.Dataset({"x": ("y", [0])})
-        assert expected.identical(actual)
+        assert_identical(expected, actual)
 
         datasets = [xr.Dataset({"x": ("y", [np.nan])}), xr.Dataset({"x": 0})]
         actual = xr.merge(datasets)
-        assert expected.identical(actual)
+        assert_identical(expected, actual)
 
 
 class TestMergeMethod:
@@ -208,17 +208,17 @@ class TestMergeMethod:
         ds2 = data[["var3"]]
         expected = data[["var1", "var3"]]
         actual = ds1.merge(ds2)
-        assert expected.identical(actual)
+        assert_identical(expected, actual)
 
         actual = ds2.merge(ds1)
-        assert expected.identical(actual)
+        assert_identical(expected, actual)
 
         actual = data.merge(data)
-        assert data.identical(actual)
+        assert_identical(data, actual)
         actual = data.reset_coords(drop=True).merge(data)
-        assert data.identical(actual)
+        assert_identical(data, actual)
         actual = data.merge(data.reset_coords(drop=True))
-        assert data.identical(actual)
+        assert_identical(data, actual)
 
         with pytest.raises(ValueError):
             ds1.merge(ds2.rename({"var3": "var1"}))
@@ -231,19 +231,19 @@ class TestMergeMethod:
         ds1 = xr.Dataset({"x": 0})
         ds2 = xr.Dataset({"x": ("y", [0, 0])})
         actual = ds1.merge(ds2)
-        assert ds2.identical(actual)
+        assert_identical(ds2, actual)
 
         actual = ds2.merge(ds1)
-        assert ds2.identical(actual)
+        assert_identical(ds2, actual)
 
         actual = ds1.copy()
         actual.update(ds2)
-        assert ds2.identical(actual)
+        assert_identical(ds2, actual)
 
         ds1 = xr.Dataset({"x": np.nan})
         ds2 = xr.Dataset({"x": ("y", [np.nan, np.nan])})
         actual = ds1.merge(ds2)
-        assert ds2.identical(actual)
+        assert_identical(ds2, actual)
 
     def test_merge_compat(self):
         ds1 = xr.Dataset({"x": 0})
diff --git a/xarray/tests/test_sparse.py b/xarray/tests/test_sparse.py
index a746a676..49b6a586 100644
--- a/xarray/tests/test_sparse.py
+++ b/xarray/tests/test_sparse.py
@@ -651,7 +651,7 @@ class TestSparseDataArrayAndDataset:
         assert_equal(expected, stacked)
 
         roundtripped = stacked.unstack()
-        assert arr.identical(roundtripped)
+        assert_identical(arr, roundtripped)
 
     @pytest.mark.filterwarnings("ignore::PendingDeprecationWarning")
     def test_ufuncs(self):
diff --git a/xarray/tests/test_variable.py b/xarray/tests/test_variable.py
index ecd53aa9..91eeb044 100644
--- a/xarray/tests/test_variable.py
+++ b/xarray/tests/test_variable.py
@@ -1392,7 +1392,7 @@ class TestVariable(VariableSubclassobjects):
         ]:
             variable = Variable([], value)
             actual = variable.transpose()
-            assert actual.identical(variable)
+            assert_identical(actual, variable)
 
     def test_squeeze(self):
         v = Variable(["x", "y"], [[1]])
@@ -1445,7 +1445,7 @@ class TestVariable(VariableSubclassobjects):
         for i in range(3):
             exp_values[i] = ("a", 1)
         expected = Variable(["x"], exp_values)
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
 
     def test_stack(self):
         v = Variable(["x", "y"], [[0, 1], [2, 3]], {"foo": "bar"})
@@ -2075,12 +2075,12 @@ class TestIndexVariable(VariableSubclassobjects):
         coords = [IndexVariable("t", periods[:5]), IndexVariable("t", periods[5:])]
         expected = IndexVariable("t", periods)
         actual = IndexVariable.concat(coords, dim="t")
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
         assert isinstance(actual.to_index(), pd.PeriodIndex)
 
         positions = [list(range(5)), list(range(5, 10))]
         actual = IndexVariable.concat(coords, dim="t", positions=positions)
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
         assert isinstance(actual.to_index(), pd.PeriodIndex)
 
     def test_concat_multiindex(self):
@@ -2088,7 +2088,7 @@ class TestIndexVariable(VariableSubclassobjects):
         coords = [IndexVariable("x", idx[:2]), IndexVariable("x", idx[2:])]
         expected = IndexVariable("x", idx)
         actual = IndexVariable.concat(coords, dim="x")
-        assert actual.identical(expected)
+        assert_identical(actual, expected)
         assert isinstance(actual.to_index(), pd.MultiIndex)
 
     def test_coordinate_alias(self):