test_common.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. """
  2. Collection of tests asserting things that should be true for
  3. any index subclass. Makes use of the `indices` fixture defined
  4. in pandas/tests/indexes/conftest.py.
  5. """
  6. import re
  7. import numpy as np
  8. import pytest
  9. from pandas._libs.tslibs import iNaT
  10. from pandas.core.dtypes.common import needs_i8_conversion
  11. import pandas as pd
  12. from pandas import CategoricalIndex, MultiIndex, RangeIndex
  13. import pandas._testing as tm
  14. class TestCommon:
  15. def test_droplevel(self, indices):
  16. # GH 21115
  17. if isinstance(indices, MultiIndex):
  18. # Tested separately in test_multi.py
  19. return
  20. assert indices.droplevel([]).equals(indices)
  21. for level in indices.name, [indices.name]:
  22. if isinstance(indices.name, tuple) and level is indices.name:
  23. # GH 21121 : droplevel with tuple name
  24. continue
  25. with pytest.raises(ValueError):
  26. indices.droplevel(level)
  27. for level in "wrong", ["wrong"]:
  28. with pytest.raises(
  29. KeyError,
  30. match=r"'Requested level \(wrong\) does not match index name \(None\)'",
  31. ):
  32. indices.droplevel(level)
  33. def test_constructor_non_hashable_name(self, indices):
  34. # GH 20527
  35. if isinstance(indices, MultiIndex):
  36. pytest.skip("multiindex handled in test_multi.py")
  37. message = "Index.name must be a hashable type"
  38. renamed = [["1"]]
  39. # With .rename()
  40. with pytest.raises(TypeError, match=message):
  41. indices.rename(name=renamed)
  42. # With .set_names()
  43. with pytest.raises(TypeError, match=message):
  44. indices.set_names(names=renamed)
  45. def test_constructor_unwraps_index(self, indices):
  46. if isinstance(indices, pd.MultiIndex):
  47. raise pytest.skip("MultiIndex has no ._data")
  48. a = indices
  49. b = type(a)(a)
  50. tm.assert_equal(a._data, b._data)
  51. @pytest.mark.parametrize("itm", [101, "no_int"])
  52. # FutureWarning from non-tuple sequence of nd indexing
  53. @pytest.mark.filterwarnings("ignore::FutureWarning")
  54. def test_getitem_error(self, indices, itm):
  55. with pytest.raises(IndexError):
  56. indices[itm]
  57. @pytest.mark.parametrize(
  58. "fname, sname, expected_name",
  59. [
  60. ("A", "A", "A"),
  61. ("A", "B", None),
  62. ("A", None, None),
  63. (None, "B", None),
  64. (None, None, None),
  65. ],
  66. )
  67. def test_corner_union(self, indices, fname, sname, expected_name):
  68. # GH 9943 9862
  69. # Test unions with various name combinations
  70. # Do not test MultiIndex or repeats
  71. if isinstance(indices, MultiIndex) or not indices.is_unique:
  72. pytest.skip("Not for MultiIndex or repeated indices")
  73. # Test copy.union(copy)
  74. first = indices.copy().set_names(fname)
  75. second = indices.copy().set_names(sname)
  76. union = first.union(second)
  77. expected = indices.copy().set_names(expected_name)
  78. tm.assert_index_equal(union, expected)
  79. # Test copy.union(empty)
  80. first = indices.copy().set_names(fname)
  81. second = indices.drop(indices).set_names(sname)
  82. union = first.union(second)
  83. expected = indices.copy().set_names(expected_name)
  84. tm.assert_index_equal(union, expected)
  85. # Test empty.union(copy)
  86. first = indices.drop(indices).set_names(fname)
  87. second = indices.copy().set_names(sname)
  88. union = first.union(second)
  89. expected = indices.copy().set_names(expected_name)
  90. tm.assert_index_equal(union, expected)
  91. # Test empty.union(empty)
  92. first = indices.drop(indices).set_names(fname)
  93. second = indices.drop(indices).set_names(sname)
  94. union = first.union(second)
  95. expected = indices.drop(indices).set_names(expected_name)
  96. tm.assert_index_equal(union, expected)
  97. def test_to_flat_index(self, indices):
  98. # 22866
  99. if isinstance(indices, MultiIndex):
  100. pytest.skip("Separate expectation for MultiIndex")
  101. result = indices.to_flat_index()
  102. tm.assert_index_equal(result, indices)
  103. def test_wrong_number_names(self, indices):
  104. with pytest.raises(ValueError, match="^Length"):
  105. indices.names = ["apple", "banana", "carrot"]
  106. def test_set_name_methods(self, indices):
  107. new_name = "This is the new name for this index"
  108. # don't tests a MultiIndex here (as its tested separated)
  109. if isinstance(indices, MultiIndex):
  110. pytest.skip("Skip check for MultiIndex")
  111. original_name = indices.name
  112. new_ind = indices.set_names([new_name])
  113. assert new_ind.name == new_name
  114. assert indices.name == original_name
  115. res = indices.rename(new_name, inplace=True)
  116. # should return None
  117. assert res is None
  118. assert indices.name == new_name
  119. assert indices.names == [new_name]
  120. # FIXME: dont leave commented-out
  121. # with pytest.raises(TypeError, match="list-like"):
  122. # # should still fail even if it would be the right length
  123. # ind.set_names("a")
  124. with pytest.raises(ValueError, match="Level must be None"):
  125. indices.set_names("a", level=0)
  126. # rename in place just leaves tuples and other containers alone
  127. name = ("A", "B")
  128. indices.rename(name, inplace=True)
  129. assert indices.name == name
  130. assert indices.names == [name]
  131. def test_hash_error(self, indices):
  132. index = indices
  133. with pytest.raises(
  134. TypeError, match=f"unhashable type: '{type(index).__name__}'"
  135. ):
  136. hash(indices)
  137. def test_copy_and_deepcopy(self, indices):
  138. from copy import copy, deepcopy
  139. if isinstance(indices, MultiIndex):
  140. pytest.skip("Skip check for MultiIndex")
  141. for func in (copy, deepcopy):
  142. idx_copy = func(indices)
  143. assert idx_copy is not indices
  144. assert idx_copy.equals(indices)
  145. new_copy = indices.copy(deep=True, name="banana")
  146. assert new_copy.name == "banana"
  147. def test_unique(self, indices):
  148. # don't test a MultiIndex here (as its tested separated)
  149. # don't test a CategoricalIndex because categories change (GH 18291)
  150. if isinstance(indices, (MultiIndex, CategoricalIndex)):
  151. pytest.skip("Skip check for MultiIndex/CategoricalIndex")
  152. # GH 17896
  153. expected = indices.drop_duplicates()
  154. for level in 0, indices.name, None:
  155. result = indices.unique(level=level)
  156. tm.assert_index_equal(result, expected)
  157. msg = "Too many levels: Index has only 1 level, not 4"
  158. with pytest.raises(IndexError, match=msg):
  159. indices.unique(level=3)
  160. msg = (
  161. fr"Requested level \(wrong\) does not match index name "
  162. fr"\({re.escape(indices.name.__repr__())}\)"
  163. )
  164. with pytest.raises(KeyError, match=msg):
  165. indices.unique(level="wrong")
  166. def test_get_unique_index(self, indices):
  167. # MultiIndex tested separately
  168. if not len(indices) or isinstance(indices, MultiIndex):
  169. pytest.skip("Skip check for empty Index and MultiIndex")
  170. idx = indices[[0] * 5]
  171. idx_unique = indices[[0]]
  172. # We test against `idx_unique`, so first we make sure it's unique
  173. # and doesn't contain nans.
  174. assert idx_unique.is_unique is True
  175. try:
  176. assert idx_unique.hasnans is False
  177. except NotImplementedError:
  178. pass
  179. for dropna in [False, True]:
  180. result = idx._get_unique_index(dropna=dropna)
  181. tm.assert_index_equal(result, idx_unique)
  182. # nans:
  183. if not indices._can_hold_na:
  184. pytest.skip("Skip na-check if index cannot hold na")
  185. if needs_i8_conversion(indices):
  186. vals = indices.asi8[[0] * 5]
  187. vals[0] = iNaT
  188. else:
  189. vals = indices.values[[0] * 5]
  190. vals[0] = np.nan
  191. vals_unique = vals[:2]
  192. idx_nan = indices._shallow_copy(vals)
  193. idx_unique_nan = indices._shallow_copy(vals_unique)
  194. assert idx_unique_nan.is_unique is True
  195. assert idx_nan.dtype == indices.dtype
  196. assert idx_unique_nan.dtype == indices.dtype
  197. for dropna, expected in zip([False, True], [idx_unique_nan, idx_unique]):
  198. for i in [idx_nan, idx_unique_nan]:
  199. result = i._get_unique_index(dropna=dropna)
  200. tm.assert_index_equal(result, expected)
  201. def test_sort(self, indices):
  202. msg = "cannot sort an Index object in-place, use sort_values instead"
  203. with pytest.raises(TypeError, match=msg):
  204. indices.sort()
  205. def test_mutability(self, indices):
  206. if not len(indices):
  207. pytest.skip("Skip check for empty Index")
  208. msg = "Index does not support mutable operations"
  209. with pytest.raises(TypeError, match=msg):
  210. indices[0] = indices[0]
  211. def test_view(self, indices):
  212. assert indices.view().name == indices.name
  213. def test_compat(self, indices):
  214. assert indices.tolist() == list(indices)
  215. def test_searchsorted_monotonic(self, indices):
  216. # GH17271
  217. # not implemented for tuple searches in MultiIndex
  218. # or Intervals searches in IntervalIndex
  219. if isinstance(indices, (MultiIndex, pd.IntervalIndex)):
  220. pytest.skip("Skip check for MultiIndex/IntervalIndex")
  221. # nothing to test if the index is empty
  222. if indices.empty:
  223. pytest.skip("Skip check for empty Index")
  224. value = indices[0]
  225. # determine the expected results (handle dupes for 'right')
  226. expected_left, expected_right = 0, (indices == value).argmin()
  227. if expected_right == 0:
  228. # all values are the same, expected_right should be length
  229. expected_right = len(indices)
  230. # test _searchsorted_monotonic in all cases
  231. # test searchsorted only for increasing
  232. if indices.is_monotonic_increasing:
  233. ssm_left = indices._searchsorted_monotonic(value, side="left")
  234. assert expected_left == ssm_left
  235. ssm_right = indices._searchsorted_monotonic(value, side="right")
  236. assert expected_right == ssm_right
  237. ss_left = indices.searchsorted(value, side="left")
  238. assert expected_left == ss_left
  239. ss_right = indices.searchsorted(value, side="right")
  240. assert expected_right == ss_right
  241. elif indices.is_monotonic_decreasing:
  242. ssm_left = indices._searchsorted_monotonic(value, side="left")
  243. assert expected_left == ssm_left
  244. ssm_right = indices._searchsorted_monotonic(value, side="right")
  245. assert expected_right == ssm_right
  246. else:
  247. # non-monotonic should raise.
  248. with pytest.raises(ValueError):
  249. indices._searchsorted_monotonic(value, side="left")
  250. def test_pickle(self, indices):
  251. original_name, indices.name = indices.name, "foo"
  252. unpickled = tm.round_trip_pickle(indices)
  253. assert indices.equals(unpickled)
  254. indices.name = original_name
  255. @pytest.mark.parametrize("keep", ["first", "last", False])
  256. def test_duplicated(self, indices, keep):
  257. if not len(indices) or isinstance(indices, (MultiIndex, RangeIndex)):
  258. # MultiIndex tested separately in:
  259. # tests/indexes/multi/test_unique_and_duplicates
  260. pytest.skip("Skip check for empty Index, MultiIndex, RangeIndex")
  261. holder = type(indices)
  262. idx = holder(indices)
  263. if idx.has_duplicates:
  264. # We are testing the duplicated-method here, so we need to know
  265. # exactly which indices are duplicate and how (for the result).
  266. # This is not possible if "idx" has duplicates already, which we
  267. # therefore remove. This is seemingly circular, as drop_duplicates
  268. # invokes duplicated, but in the end, it all works out because we
  269. # cross-check with Series.duplicated, which is tested separately.
  270. idx = idx.drop_duplicates()
  271. n, k = len(idx), 10
  272. duplicated_selection = np.random.choice(n, k * n)
  273. expected = pd.Series(duplicated_selection).duplicated(keep=keep).values
  274. idx = holder(idx.values[duplicated_selection])
  275. result = idx.duplicated(keep=keep)
  276. tm.assert_numpy_array_equal(result, expected)
  277. def test_has_duplicates(self, indices):
  278. holder = type(indices)
  279. if not len(indices) or isinstance(indices, (MultiIndex, RangeIndex)):
  280. # MultiIndex tested separately in:
  281. # tests/indexes/multi/test_unique_and_duplicates.
  282. # RangeIndex is unique by definition.
  283. pytest.skip("Skip check for empty Index, MultiIndex, and RangeIndex")
  284. idx = holder([indices[0]] * 5)
  285. assert idx.is_unique is False
  286. assert idx.has_duplicates is True