diff --git a/pandas/_libs/missing.pyx b/pandas/_libs/missing.pyx index 4d17a6f883c1c..5c1ca79eb685c 100644 --- a/pandas/_libs/missing.pyx +++ b/pandas/_libs/missing.pyx @@ -287,6 +287,27 @@ cdef inline bint is_null_period(v): return checknull_with_nat(v) +def is_matching_na(left, right) -> bool: + """ + Check if two NA-like scalars represent the same type of NA. + """ + if left is None: + return right is None + if left is C_NA: + return right is C_NA + if left is NaT: + return right is NaT + if util.is_nan(left): + if not util.is_nan(right): + return False + if util.is_float_object(left): + return util.is_float_object(right) + if util.is_complex_object(left): + return util.is_complex_object(right) + raise NotImplementedError(left, right) + raise NotImplementedError(left, right) + + # ----------------------------------------------------------------------------- # Implementation of NA singleton diff --git a/pandas/_testing.py b/pandas/_testing.py index 631d550c60534..bb71e1f23f945 100644 --- a/pandas/_testing.py +++ b/pandas/_testing.py @@ -21,6 +21,7 @@ set_locale, ) +from pandas._libs.lib import no_default import pandas._libs.testing as _testing from pandas._typing import FilePathOrBuffer, FrameOrSeries from pandas.compat import _get_lzma_file, _import_lzma @@ -1575,6 +1576,30 @@ def getCols(k): return string.ascii_uppercase[:k] +def make_any_name(): + """ + Return a random hashable object. + """ + return np.random.choice( + [ + None, + "a", + "foobar" * 100, + 1, + 2.0, + np.float64(3), + np.nan, + pd.NaT, + pd.NA, + pd.Timestamp.now(), + pd.Timestamp.now("UTC"), + pd.Timedelta(minutes=45678), + pd.Timedelta(minutes=45678).to_timedelta64(), + pd.Timedelta(minutes=45678).to_pytimedelta(), + ] + ) + + # make index def makeStringIndex(k=10, name=None): return Index(rands_array(nchars=10, size=k), name=name) @@ -1623,17 +1648,23 @@ def makeFloatIndex(k=10, name=None): return Index(values * (10 ** np.random.randint(0, 9)), name=name) -def makeDateIndex(k=10, freq="B", name=None, **kwargs): +def makeDateIndex(k=10, freq="B", name=no_default, **kwargs): + if name is no_default: + name = make_any_name() dt = datetime(2000, 1, 1) dr = bdate_range(dt, periods=k, freq=freq, name=name) return DatetimeIndex(dr, name=name, **kwargs) -def makeTimedeltaIndex(k=10, freq="D", name=None, **kwargs): +def makeTimedeltaIndex(k=10, freq="D", name=no_default, **kwargs): + if name is no_default: + name = make_any_name() return pd.timedelta_range(start="1 day", periods=k, freq=freq, name=name, **kwargs) -def makePeriodIndex(k=10, name=None, **kwargs): +def makePeriodIndex(k=10, name=no_default, **kwargs): + if name is no_default: + name = make_any_name() dt = datetime(2000, 1, 1) dr = pd.period_range(start=dt, periods=k, freq="B", name=name, **kwargs) return dr diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index c158bdfbac441..0db99a76e8f22 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6,7 +6,12 @@ import numpy as np -from pandas._libs import algos as libalgos, index as libindex, lib +from pandas._libs import ( + algos as libalgos, + index as libindex, + lib, + missing as libmissing, +) import pandas._libs.join as libjoin from pandas._libs.lib import is_datetime_array from pandas._libs.tslibs import OutOfBoundsDatetime, Timestamp @@ -4333,16 +4338,29 @@ def identical(self, other) -> bool: If two Index objects have equal elements and same type True, otherwise False. """ - return ( - self.equals(other) - and all( - ( - getattr(self, c, None) == getattr(other, c, None) - for c in self._comparables - ) - ) - and type(self) == type(other) - ) + if type(self) != type(other): + return False + if not self.equals(other): + return False + for attr in self._comparables: + left = getattr(self, attr) + right = getattr(other, attr) + if is_scalar(left) and isna(left): + # We have to avoid pd.NA raising TypeError when checking equality + if not libmissing.is_matching_na(left, right): + return False + continue + elif is_scalar(right) and isna(right): + return False + else: + try: + if not left == right: + return False + except TypeError: + # Timestamp tzawareness compat, Period freq compat + return False + + return True def asof(self, label): """ diff --git a/pandas/tests/indexes/common.py b/pandas/tests/indexes/common.py index f3ebe8313d0c6..f9ef0243eba39 100644 --- a/pandas/tests/indexes/common.py +++ b/pandas/tests/indexes/common.py @@ -300,7 +300,7 @@ def test_ensure_copied_data(self, indices): return index_type = type(indices) - result = index_type(indices.values, copy=True, **init_kwargs) + result = index_type(indices.values, copy=True, name=indices.name, **init_kwargs) tm.assert_index_equal(indices, result) tm.assert_numpy_array_equal( indices._ndarray_values, result._ndarray_values, check_same="copy" diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index d40a2257771a2..c4000c40fcac1 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -1082,7 +1082,7 @@ def test_map_tseries_indices_return_index(self, attr): index = getattr(tm, attr)(10) expected = Index([1] * 10) result = index.map(lambda x: 1) - tm.assert_index_equal(expected, result) + tm.assert_index_equal(expected, result, check_names=False) def test_map_tseries_indices_accsr_return_index(self): date_index = tm.makeDateIndex(24, freq="h", name="hourly") @@ -1126,7 +1126,7 @@ def test_map_dictlike(self, indices, mapper): expected = Index(np.arange(len(indices), 0, -1)) result = indices.map(mapper(expected, indices)) - tm.assert_index_equal(result, expected) + tm.assert_index_equal(result, expected, check_names=False) @pytest.mark.parametrize( "mapper", diff --git a/pandas/tests/indexes/test_common.py b/pandas/tests/indexes/test_common.py index 7e30233353553..3f6c7eff6e3d2 100644 --- a/pandas/tests/indexes/test_common.py +++ b/pandas/tests/indexes/test_common.py @@ -36,7 +36,7 @@ def test_droplevel(self, indices): for level in "wrong", ["wrong"]: with pytest.raises( KeyError, - match=r"'Requested level \(wrong\) does not match index name \(None\)'", + match=r"'Requested level \(wrong\) does not match index name \(.*\)'", ): indices.droplevel(level) diff --git a/pandas/tests/plotting/test_datetimelike.py b/pandas/tests/plotting/test_datetimelike.py index 84d298cd7c6fe..dbf666afffaed 100644 --- a/pandas/tests/plotting/test_datetimelike.py +++ b/pandas/tests/plotting/test_datetimelike.py @@ -339,7 +339,9 @@ def test_dataframe(self): _, ax = self.plt.subplots() bts.plot(ax=ax) idx = ax.get_lines()[0].get_xdata() - tm.assert_index_equal(bts.index.to_period(), PeriodIndex(idx)) + tm.assert_index_equal( + bts.index.to_period(), PeriodIndex(idx, name=bts.index.name) + ) @pytest.mark.slow def test_axis_limits(self): @@ -692,8 +694,8 @@ def test_mixed_freq_regular_first(self): ax2 = s2.plot(style="g", ax=ax) lines = ax2.get_lines() - idx1 = PeriodIndex(lines[0].get_xdata()) - idx2 = PeriodIndex(lines[1].get_xdata()) + idx1 = PeriodIndex(lines[0].get_xdata(), name=s1.index.name) + idx2 = PeriodIndex(lines[1].get_xdata(), name=s2.index.name) tm.assert_index_equal(idx1, s1.index.to_period("B")) tm.assert_index_equal(idx2, s2.index.to_period("B")) diff --git a/pandas/tests/series/methods/test_shift.py b/pandas/tests/series/methods/test_shift.py index 8256e2f33b936..2f02ded4c6a51 100644 --- a/pandas/tests/series/methods/test_shift.py +++ b/pandas/tests/series/methods/test_shift.py @@ -209,7 +209,9 @@ def test_tshift(self, datetime_series): tm.assert_series_equal(shifted, shifted2) inferred_ts = Series( - datetime_series.values, Index(np.asarray(datetime_series.index)), name="ts" + datetime_series.values, + Index(np.asarray(datetime_series.index), name=datetime_series.index.name), + name="ts", ) shifted = inferred_ts.tshift(1) unshifted = shifted.tshift(-1) diff --git a/pandas/tests/series/methods/test_to_dict.py b/pandas/tests/series/methods/test_to_dict.py index 2fbf3e8d39cf3..260aa0b1411b4 100644 --- a/pandas/tests/series/methods/test_to_dict.py +++ b/pandas/tests/series/methods/test_to_dict.py @@ -13,8 +13,10 @@ class TestSeriesToDict: def test_to_dict(self, mapping, datetime_series): # GH#16122 tm.assert_series_equal( - Series(datetime_series.to_dict(mapping), name="ts"), datetime_series + Series(datetime_series.to_dict(mapping), name="ts"), + datetime_series, + check_names=False, ) from_method = Series(datetime_series.to_dict(collections.Counter)) from_constructor = Series(collections.Counter(datetime_series.items())) - tm.assert_series_equal(from_method, from_constructor) + tm.assert_series_equal(from_method, from_constructor, check_names=False) diff --git a/pandas/tests/series/test_apply.py b/pandas/tests/series/test_apply.py index a4c55a80a9f0f..cf9103833d421 100644 --- a/pandas/tests/series/test_apply.py +++ b/pandas/tests/series/test_apply.py @@ -520,7 +520,8 @@ def test_map_empty(self, index): result = s.map({}) expected = pd.Series(np.nan, index=s.index) - tm.assert_series_equal(result, expected) + tm.assert_series_equal(result, expected, check_names=False) + # TODO: can we check names her? def test_map_compat(self): # related GH 8024