test_constructors.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. from datetime import (
  2. date,
  3. datetime,
  4. )
  5. import itertools
  6. import numpy as np
  7. import pytest
  8. from pandas.compat import pa_version_under7p0
  9. from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
  10. import pandas as pd
  11. from pandas import (
  12. Index,
  13. MultiIndex,
  14. Series,
  15. Timestamp,
  16. date_range,
  17. )
  18. import pandas._testing as tm
  19. def test_constructor_single_level():
  20. result = MultiIndex(
  21. levels=[["foo", "bar", "baz", "qux"]], codes=[[0, 1, 2, 3]], names=["first"]
  22. )
  23. assert isinstance(result, MultiIndex)
  24. expected = Index(["foo", "bar", "baz", "qux"], name="first")
  25. tm.assert_index_equal(result.levels[0], expected)
  26. assert result.names == ["first"]
  27. def test_constructor_no_levels():
  28. msg = "non-zero number of levels/codes"
  29. with pytest.raises(ValueError, match=msg):
  30. MultiIndex(levels=[], codes=[])
  31. msg = "Must pass both levels and codes"
  32. with pytest.raises(TypeError, match=msg):
  33. MultiIndex(levels=[])
  34. with pytest.raises(TypeError, match=msg):
  35. MultiIndex(codes=[])
  36. def test_constructor_nonhashable_names():
  37. # GH 20527
  38. levels = [[1, 2], ["one", "two"]]
  39. codes = [[0, 0, 1, 1], [0, 1, 0, 1]]
  40. names = (["foo"], ["bar"])
  41. msg = r"MultiIndex\.name must be a hashable type"
  42. with pytest.raises(TypeError, match=msg):
  43. MultiIndex(levels=levels, codes=codes, names=names)
  44. # With .rename()
  45. mi = MultiIndex(
  46. levels=[[1, 2], ["one", "two"]],
  47. codes=[[0, 0, 1, 1], [0, 1, 0, 1]],
  48. names=("foo", "bar"),
  49. )
  50. renamed = [["foor"], ["barr"]]
  51. with pytest.raises(TypeError, match=msg):
  52. mi.rename(names=renamed)
  53. # With .set_names()
  54. with pytest.raises(TypeError, match=msg):
  55. mi.set_names(names=renamed)
  56. def test_constructor_mismatched_codes_levels(idx):
  57. codes = [np.array([1]), np.array([2]), np.array([3])]
  58. levels = ["a"]
  59. msg = "Length of levels and codes must be the same"
  60. with pytest.raises(ValueError, match=msg):
  61. MultiIndex(levels=levels, codes=codes)
  62. length_error = (
  63. r"On level 0, code max \(3\) >= length of level \(1\)\. "
  64. "NOTE: this index is in an inconsistent state"
  65. )
  66. label_error = r"Unequal code lengths: \[4, 2\]"
  67. code_value_error = r"On level 0, code value \(-2\) < -1"
  68. # important to check that it's looking at the right thing.
  69. with pytest.raises(ValueError, match=length_error):
  70. MultiIndex(levels=[["a"], ["b"]], codes=[[0, 1, 2, 3], [0, 3, 4, 1]])
  71. with pytest.raises(ValueError, match=label_error):
  72. MultiIndex(levels=[["a"], ["b"]], codes=[[0, 0, 0, 0], [0, 0]])
  73. # external API
  74. with pytest.raises(ValueError, match=length_error):
  75. idx.copy().set_levels([["a"], ["b"]])
  76. with pytest.raises(ValueError, match=label_error):
  77. idx.copy().set_codes([[0, 0, 0, 0], [0, 0]])
  78. # test set_codes with verify_integrity=False
  79. # the setting should not raise any value error
  80. idx.copy().set_codes(codes=[[0, 0, 0, 0], [0, 0]], verify_integrity=False)
  81. # code value smaller than -1
  82. with pytest.raises(ValueError, match=code_value_error):
  83. MultiIndex(levels=[["a"], ["b"]], codes=[[0, -2], [0, 0]])
  84. def test_na_levels():
  85. # GH26408
  86. # test if codes are re-assigned value -1 for levels
  87. # with missing values (NaN, NaT, None)
  88. result = MultiIndex(
  89. levels=[[np.nan, None, pd.NaT, 128, 2]], codes=[[0, -1, 1, 2, 3, 4]]
  90. )
  91. expected = MultiIndex(
  92. levels=[[np.nan, None, pd.NaT, 128, 2]], codes=[[-1, -1, -1, -1, 3, 4]]
  93. )
  94. tm.assert_index_equal(result, expected)
  95. result = MultiIndex(
  96. levels=[[np.nan, "s", pd.NaT, 128, None]], codes=[[0, -1, 1, 2, 3, 4]]
  97. )
  98. expected = MultiIndex(
  99. levels=[[np.nan, "s", pd.NaT, 128, None]], codes=[[-1, -1, 1, -1, 3, -1]]
  100. )
  101. tm.assert_index_equal(result, expected)
  102. # verify set_levels and set_codes
  103. result = MultiIndex(
  104. levels=[[1, 2, 3, 4, 5]], codes=[[0, -1, 1, 2, 3, 4]]
  105. ).set_levels([[np.nan, "s", pd.NaT, 128, None]])
  106. tm.assert_index_equal(result, expected)
  107. result = MultiIndex(
  108. levels=[[np.nan, "s", pd.NaT, 128, None]], codes=[[1, 2, 2, 2, 2, 2]]
  109. ).set_codes([[0, -1, 1, 2, 3, 4]])
  110. tm.assert_index_equal(result, expected)
  111. def test_copy_in_constructor():
  112. levels = np.array(["a", "b", "c"])
  113. codes = np.array([1, 1, 2, 0, 0, 1, 1])
  114. val = codes[0]
  115. mi = MultiIndex(levels=[levels, levels], codes=[codes, codes], copy=True)
  116. assert mi.codes[0][0] == val
  117. codes[0] = 15
  118. assert mi.codes[0][0] == val
  119. val = levels[0]
  120. levels[0] = "PANDA"
  121. assert mi.levels[0][0] == val
  122. # ----------------------------------------------------------------------------
  123. # from_arrays
  124. # ----------------------------------------------------------------------------
  125. def test_from_arrays(idx):
  126. arrays = [
  127. np.asarray(lev).take(level_codes)
  128. for lev, level_codes in zip(idx.levels, idx.codes)
  129. ]
  130. # list of arrays as input
  131. result = MultiIndex.from_arrays(arrays, names=idx.names)
  132. tm.assert_index_equal(result, idx)
  133. # infer correctly
  134. result = MultiIndex.from_arrays([[pd.NaT, Timestamp("20130101")], ["a", "b"]])
  135. assert result.levels[0].equals(Index([Timestamp("20130101")]))
  136. assert result.levels[1].equals(Index(["a", "b"]))
  137. def test_from_arrays_iterator(idx):
  138. # GH 18434
  139. arrays = [
  140. np.asarray(lev).take(level_codes)
  141. for lev, level_codes in zip(idx.levels, idx.codes)
  142. ]
  143. # iterator as input
  144. result = MultiIndex.from_arrays(iter(arrays), names=idx.names)
  145. tm.assert_index_equal(result, idx)
  146. # invalid iterator input
  147. msg = "Input must be a list / sequence of array-likes."
  148. with pytest.raises(TypeError, match=msg):
  149. MultiIndex.from_arrays(0)
  150. def test_from_arrays_tuples(idx):
  151. arrays = tuple(
  152. tuple(np.asarray(lev).take(level_codes))
  153. for lev, level_codes in zip(idx.levels, idx.codes)
  154. )
  155. # tuple of tuples as input
  156. result = MultiIndex.from_arrays(arrays, names=idx.names)
  157. tm.assert_index_equal(result, idx)
  158. @pytest.mark.parametrize(
  159. ("idx1", "idx2"),
  160. [
  161. (
  162. pd.period_range("2011-01-01", freq="D", periods=3),
  163. pd.period_range("2015-01-01", freq="H", periods=3),
  164. ),
  165. (
  166. date_range("2015-01-01 10:00", freq="D", periods=3, tz="US/Eastern"),
  167. date_range("2015-01-01 10:00", freq="H", periods=3, tz="Asia/Tokyo"),
  168. ),
  169. (
  170. pd.timedelta_range("1 days", freq="D", periods=3),
  171. pd.timedelta_range("2 hours", freq="H", periods=3),
  172. ),
  173. ],
  174. )
  175. def test_from_arrays_index_series_period_datetimetz_and_timedelta(idx1, idx2):
  176. result = MultiIndex.from_arrays([idx1, idx2])
  177. tm.assert_index_equal(result.get_level_values(0), idx1)
  178. tm.assert_index_equal(result.get_level_values(1), idx2)
  179. result2 = MultiIndex.from_arrays([Series(idx1), Series(idx2)])
  180. tm.assert_index_equal(result2.get_level_values(0), idx1)
  181. tm.assert_index_equal(result2.get_level_values(1), idx2)
  182. tm.assert_index_equal(result, result2)
  183. def test_from_arrays_index_datetimelike_mixed():
  184. idx1 = date_range("2015-01-01 10:00", freq="D", periods=3, tz="US/Eastern")
  185. idx2 = date_range("2015-01-01 10:00", freq="H", periods=3)
  186. idx3 = pd.timedelta_range("1 days", freq="D", periods=3)
  187. idx4 = pd.period_range("2011-01-01", freq="D", periods=3)
  188. result = MultiIndex.from_arrays([idx1, idx2, idx3, idx4])
  189. tm.assert_index_equal(result.get_level_values(0), idx1)
  190. tm.assert_index_equal(result.get_level_values(1), idx2)
  191. tm.assert_index_equal(result.get_level_values(2), idx3)
  192. tm.assert_index_equal(result.get_level_values(3), idx4)
  193. result2 = MultiIndex.from_arrays(
  194. [Series(idx1), Series(idx2), Series(idx3), Series(idx4)]
  195. )
  196. tm.assert_index_equal(result2.get_level_values(0), idx1)
  197. tm.assert_index_equal(result2.get_level_values(1), idx2)
  198. tm.assert_index_equal(result2.get_level_values(2), idx3)
  199. tm.assert_index_equal(result2.get_level_values(3), idx4)
  200. tm.assert_index_equal(result, result2)
  201. def test_from_arrays_index_series_categorical():
  202. # GH13743
  203. idx1 = pd.CategoricalIndex(list("abcaab"), categories=list("bac"), ordered=False)
  204. idx2 = pd.CategoricalIndex(list("abcaab"), categories=list("bac"), ordered=True)
  205. result = MultiIndex.from_arrays([idx1, idx2])
  206. tm.assert_index_equal(result.get_level_values(0), idx1)
  207. tm.assert_index_equal(result.get_level_values(1), idx2)
  208. result2 = MultiIndex.from_arrays([Series(idx1), Series(idx2)])
  209. tm.assert_index_equal(result2.get_level_values(0), idx1)
  210. tm.assert_index_equal(result2.get_level_values(1), idx2)
  211. result3 = MultiIndex.from_arrays([idx1.values, idx2.values])
  212. tm.assert_index_equal(result3.get_level_values(0), idx1)
  213. tm.assert_index_equal(result3.get_level_values(1), idx2)
  214. def test_from_arrays_empty():
  215. # 0 levels
  216. msg = "Must pass non-zero number of levels/codes"
  217. with pytest.raises(ValueError, match=msg):
  218. MultiIndex.from_arrays(arrays=[])
  219. # 1 level
  220. result = MultiIndex.from_arrays(arrays=[[]], names=["A"])
  221. assert isinstance(result, MultiIndex)
  222. expected = Index([], name="A")
  223. tm.assert_index_equal(result.levels[0], expected)
  224. assert result.names == ["A"]
  225. # N levels
  226. for N in [2, 3]:
  227. arrays = [[]] * N
  228. names = list("ABC")[:N]
  229. result = MultiIndex.from_arrays(arrays=arrays, names=names)
  230. expected = MultiIndex(levels=[[]] * N, codes=[[]] * N, names=names)
  231. tm.assert_index_equal(result, expected)
  232. @pytest.mark.parametrize(
  233. "invalid_sequence_of_arrays",
  234. [
  235. 1,
  236. [1],
  237. [1, 2],
  238. [[1], 2],
  239. [1, [2]],
  240. "a",
  241. ["a"],
  242. ["a", "b"],
  243. [["a"], "b"],
  244. (1,),
  245. (1, 2),
  246. ([1], 2),
  247. (1, [2]),
  248. "a",
  249. ("a",),
  250. ("a", "b"),
  251. (["a"], "b"),
  252. [(1,), 2],
  253. [1, (2,)],
  254. [("a",), "b"],
  255. ((1,), 2),
  256. (1, (2,)),
  257. (("a",), "b"),
  258. ],
  259. )
  260. def test_from_arrays_invalid_input(invalid_sequence_of_arrays):
  261. msg = "Input must be a list / sequence of array-likes"
  262. with pytest.raises(TypeError, match=msg):
  263. MultiIndex.from_arrays(arrays=invalid_sequence_of_arrays)
  264. @pytest.mark.parametrize(
  265. "idx1, idx2", [([1, 2, 3], ["a", "b"]), ([], ["a", "b"]), ([1, 2, 3], [])]
  266. )
  267. def test_from_arrays_different_lengths(idx1, idx2):
  268. # see gh-13599
  269. msg = "^all arrays must be same length$"
  270. with pytest.raises(ValueError, match=msg):
  271. MultiIndex.from_arrays([idx1, idx2])
  272. def test_from_arrays_respects_none_names():
  273. # GH27292
  274. a = Series([1, 2, 3], name="foo")
  275. b = Series(["a", "b", "c"], name="bar")
  276. result = MultiIndex.from_arrays([a, b], names=None)
  277. expected = MultiIndex(
  278. levels=[[1, 2, 3], ["a", "b", "c"]], codes=[[0, 1, 2], [0, 1, 2]], names=None
  279. )
  280. tm.assert_index_equal(result, expected)
  281. # ----------------------------------------------------------------------------
  282. # from_tuples
  283. # ----------------------------------------------------------------------------
  284. def test_from_tuples():
  285. msg = "Cannot infer number of levels from empty list"
  286. with pytest.raises(TypeError, match=msg):
  287. MultiIndex.from_tuples([])
  288. expected = MultiIndex(
  289. levels=[[1, 3], [2, 4]], codes=[[0, 1], [0, 1]], names=["a", "b"]
  290. )
  291. # input tuples
  292. result = MultiIndex.from_tuples(((1, 2), (3, 4)), names=["a", "b"])
  293. tm.assert_index_equal(result, expected)
  294. def test_from_tuples_iterator():
  295. # GH 18434
  296. # input iterator for tuples
  297. expected = MultiIndex(
  298. levels=[[1, 3], [2, 4]], codes=[[0, 1], [0, 1]], names=["a", "b"]
  299. )
  300. result = MultiIndex.from_tuples(zip([1, 3], [2, 4]), names=["a", "b"])
  301. tm.assert_index_equal(result, expected)
  302. # input non-iterables
  303. msg = "Input must be a list / sequence of tuple-likes."
  304. with pytest.raises(TypeError, match=msg):
  305. MultiIndex.from_tuples(0)
  306. def test_from_tuples_empty():
  307. # GH 16777
  308. result = MultiIndex.from_tuples([], names=["a", "b"])
  309. expected = MultiIndex.from_arrays(arrays=[[], []], names=["a", "b"])
  310. tm.assert_index_equal(result, expected)
  311. def test_from_tuples_index_values(idx):
  312. result = MultiIndex.from_tuples(idx)
  313. assert (result.values == idx.values).all()
  314. def test_tuples_with_name_string():
  315. # GH 15110 and GH 14848
  316. li = [(0, 0, 1), (0, 1, 0), (1, 0, 0)]
  317. msg = "Names should be list-like for a MultiIndex"
  318. with pytest.raises(ValueError, match=msg):
  319. Index(li, name="abc")
  320. with pytest.raises(ValueError, match=msg):
  321. Index(li, name="a")
  322. def test_from_tuples_with_tuple_label():
  323. # GH 15457
  324. expected = pd.DataFrame(
  325. [[2, 1, 2], [4, (1, 2), 3]], columns=["a", "b", "c"]
  326. ).set_index(["a", "b"])
  327. idx = MultiIndex.from_tuples([(2, 1), (4, (1, 2))], names=("a", "b"))
  328. result = pd.DataFrame([2, 3], columns=["c"], index=idx)
  329. tm.assert_frame_equal(expected, result)
  330. # ----------------------------------------------------------------------------
  331. # from_product
  332. # ----------------------------------------------------------------------------
  333. def test_from_product_empty_zero_levels():
  334. # 0 levels
  335. msg = "Must pass non-zero number of levels/codes"
  336. with pytest.raises(ValueError, match=msg):
  337. MultiIndex.from_product([])
  338. def test_from_product_empty_one_level():
  339. result = MultiIndex.from_product([[]], names=["A"])
  340. expected = Index([], name="A")
  341. tm.assert_index_equal(result.levels[0], expected)
  342. assert result.names == ["A"]
  343. @pytest.mark.parametrize(
  344. "first, second", [([], []), (["foo", "bar", "baz"], []), ([], ["a", "b", "c"])]
  345. )
  346. def test_from_product_empty_two_levels(first, second):
  347. names = ["A", "B"]
  348. result = MultiIndex.from_product([first, second], names=names)
  349. expected = MultiIndex(levels=[first, second], codes=[[], []], names=names)
  350. tm.assert_index_equal(result, expected)
  351. @pytest.mark.parametrize("N", list(range(4)))
  352. def test_from_product_empty_three_levels(N):
  353. # GH12258
  354. names = ["A", "B", "C"]
  355. lvl2 = list(range(N))
  356. result = MultiIndex.from_product([[], lvl2, []], names=names)
  357. expected = MultiIndex(levels=[[], lvl2, []], codes=[[], [], []], names=names)
  358. tm.assert_index_equal(result, expected)
  359. @pytest.mark.parametrize(
  360. "invalid_input", [1, [1], [1, 2], [[1], 2], "a", ["a"], ["a", "b"], [["a"], "b"]]
  361. )
  362. def test_from_product_invalid_input(invalid_input):
  363. msg = r"Input must be a list / sequence of iterables|Input must be list-like"
  364. with pytest.raises(TypeError, match=msg):
  365. MultiIndex.from_product(iterables=invalid_input)
  366. def test_from_product_datetimeindex():
  367. dt_index = date_range("2000-01-01", periods=2)
  368. mi = MultiIndex.from_product([[1, 2], dt_index])
  369. etalon = construct_1d_object_array_from_listlike(
  370. [
  371. (1, Timestamp("2000-01-01")),
  372. (1, Timestamp("2000-01-02")),
  373. (2, Timestamp("2000-01-01")),
  374. (2, Timestamp("2000-01-02")),
  375. ]
  376. )
  377. tm.assert_numpy_array_equal(mi.values, etalon)
  378. def test_from_product_rangeindex():
  379. # RangeIndex is preserved by factorize, so preserved in levels
  380. rng = Index(range(5))
  381. other = ["a", "b"]
  382. mi = MultiIndex.from_product([rng, other])
  383. tm.assert_index_equal(mi._levels[0], rng, exact=True)
  384. @pytest.mark.parametrize("ordered", [False, True])
  385. @pytest.mark.parametrize("f", [lambda x: x, lambda x: Series(x), lambda x: x.values])
  386. def test_from_product_index_series_categorical(ordered, f):
  387. # GH13743
  388. first = ["foo", "bar"]
  389. idx = pd.CategoricalIndex(list("abcaab"), categories=list("bac"), ordered=ordered)
  390. expected = pd.CategoricalIndex(
  391. list("abcaab") + list("abcaab"), categories=list("bac"), ordered=ordered
  392. )
  393. result = MultiIndex.from_product([first, f(idx)])
  394. tm.assert_index_equal(result.get_level_values(1), expected)
  395. def test_from_product():
  396. first = ["foo", "bar", "buz"]
  397. second = ["a", "b", "c"]
  398. names = ["first", "second"]
  399. result = MultiIndex.from_product([first, second], names=names)
  400. tuples = [
  401. ("foo", "a"),
  402. ("foo", "b"),
  403. ("foo", "c"),
  404. ("bar", "a"),
  405. ("bar", "b"),
  406. ("bar", "c"),
  407. ("buz", "a"),
  408. ("buz", "b"),
  409. ("buz", "c"),
  410. ]
  411. expected = MultiIndex.from_tuples(tuples, names=names)
  412. tm.assert_index_equal(result, expected)
  413. def test_from_product_iterator():
  414. # GH 18434
  415. first = ["foo", "bar", "buz"]
  416. second = ["a", "b", "c"]
  417. names = ["first", "second"]
  418. tuples = [
  419. ("foo", "a"),
  420. ("foo", "b"),
  421. ("foo", "c"),
  422. ("bar", "a"),
  423. ("bar", "b"),
  424. ("bar", "c"),
  425. ("buz", "a"),
  426. ("buz", "b"),
  427. ("buz", "c"),
  428. ]
  429. expected = MultiIndex.from_tuples(tuples, names=names)
  430. # iterator as input
  431. result = MultiIndex.from_product(iter([first, second]), names=names)
  432. tm.assert_index_equal(result, expected)
  433. # Invalid non-iterable input
  434. msg = "Input must be a list / sequence of iterables."
  435. with pytest.raises(TypeError, match=msg):
  436. MultiIndex.from_product(0)
  437. @pytest.mark.parametrize(
  438. "a, b, expected_names",
  439. [
  440. (
  441. Series([1, 2, 3], name="foo"),
  442. Series(["a", "b"], name="bar"),
  443. ["foo", "bar"],
  444. ),
  445. (Series([1, 2, 3], name="foo"), ["a", "b"], ["foo", None]),
  446. ([1, 2, 3], ["a", "b"], None),
  447. ],
  448. )
  449. def test_from_product_infer_names(a, b, expected_names):
  450. # GH27292
  451. result = MultiIndex.from_product([a, b])
  452. expected = MultiIndex(
  453. levels=[[1, 2, 3], ["a", "b"]],
  454. codes=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]],
  455. names=expected_names,
  456. )
  457. tm.assert_index_equal(result, expected)
  458. def test_from_product_respects_none_names():
  459. # GH27292
  460. a = Series([1, 2, 3], name="foo")
  461. b = Series(["a", "b"], name="bar")
  462. result = MultiIndex.from_product([a, b], names=None)
  463. expected = MultiIndex(
  464. levels=[[1, 2, 3], ["a", "b"]],
  465. codes=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]],
  466. names=None,
  467. )
  468. tm.assert_index_equal(result, expected)
  469. def test_from_product_readonly():
  470. # GH#15286 passing read-only array to from_product
  471. a = np.array(range(3))
  472. b = ["a", "b"]
  473. expected = MultiIndex.from_product([a, b])
  474. a.setflags(write=False)
  475. result = MultiIndex.from_product([a, b])
  476. tm.assert_index_equal(result, expected)
  477. def test_create_index_existing_name(idx):
  478. # GH11193, when an existing index is passed, and a new name is not
  479. # specified, the new index should inherit the previous object name
  480. index = idx
  481. index.names = ["foo", "bar"]
  482. result = Index(index)
  483. expected = Index(
  484. Index(
  485. [
  486. ("foo", "one"),
  487. ("foo", "two"),
  488. ("bar", "one"),
  489. ("baz", "two"),
  490. ("qux", "one"),
  491. ("qux", "two"),
  492. ],
  493. dtype="object",
  494. )
  495. )
  496. tm.assert_index_equal(result, expected)
  497. result = Index(index, name="A")
  498. expected = Index(
  499. Index(
  500. [
  501. ("foo", "one"),
  502. ("foo", "two"),
  503. ("bar", "one"),
  504. ("baz", "two"),
  505. ("qux", "one"),
  506. ("qux", "two"),
  507. ],
  508. dtype="object",
  509. ),
  510. name="A",
  511. )
  512. tm.assert_index_equal(result, expected)
  513. # ----------------------------------------------------------------------------
  514. # from_frame
  515. # ----------------------------------------------------------------------------
  516. def test_from_frame():
  517. # GH 22420
  518. df = pd.DataFrame(
  519. [["a", "a"], ["a", "b"], ["b", "a"], ["b", "b"]], columns=["L1", "L2"]
  520. )
  521. expected = MultiIndex.from_tuples(
  522. [("a", "a"), ("a", "b"), ("b", "a"), ("b", "b")], names=["L1", "L2"]
  523. )
  524. result = MultiIndex.from_frame(df)
  525. tm.assert_index_equal(expected, result)
  526. @pytest.mark.skipif(pa_version_under7p0, reason="minimum pyarrow not installed")
  527. def test_from_frame_missing_values_multiIndex():
  528. # GH 39984
  529. import pyarrow as pa
  530. df = pd.DataFrame(
  531. {
  532. "a": Series([1, 2, None], dtype="Int64"),
  533. "b": pd.Float64Dtype().__from_arrow__(pa.array([0.2, np.nan, None])),
  534. }
  535. )
  536. multi_indexed = MultiIndex.from_frame(df)
  537. expected = MultiIndex.from_arrays(
  538. [
  539. Series([1, 2, None]).astype("Int64"),
  540. pd.Float64Dtype().__from_arrow__(pa.array([0.2, np.nan, None])),
  541. ],
  542. names=["a", "b"],
  543. )
  544. tm.assert_index_equal(multi_indexed, expected)
  545. @pytest.mark.parametrize(
  546. "non_frame",
  547. [
  548. Series([1, 2, 3, 4]),
  549. [1, 2, 3, 4],
  550. [[1, 2], [3, 4], [5, 6]],
  551. Index([1, 2, 3, 4]),
  552. np.array([[1, 2], [3, 4], [5, 6]]),
  553. 27,
  554. ],
  555. )
  556. def test_from_frame_error(non_frame):
  557. # GH 22420
  558. with pytest.raises(TypeError, match="Input must be a DataFrame"):
  559. MultiIndex.from_frame(non_frame)
  560. def test_from_frame_dtype_fidelity():
  561. # GH 22420
  562. df = pd.DataFrame(
  563. {
  564. "dates": date_range("19910905", periods=6, tz="US/Eastern"),
  565. "a": [1, 1, 1, 2, 2, 2],
  566. "b": pd.Categorical(["a", "a", "b", "b", "c", "c"], ordered=True),
  567. "c": ["x", "x", "y", "z", "x", "y"],
  568. }
  569. )
  570. original_dtypes = df.dtypes.to_dict()
  571. expected_mi = MultiIndex.from_arrays(
  572. [
  573. date_range("19910905", periods=6, tz="US/Eastern"),
  574. [1, 1, 1, 2, 2, 2],
  575. pd.Categorical(["a", "a", "b", "b", "c", "c"], ordered=True),
  576. ["x", "x", "y", "z", "x", "y"],
  577. ],
  578. names=["dates", "a", "b", "c"],
  579. )
  580. mi = MultiIndex.from_frame(df)
  581. mi_dtypes = {name: mi.levels[i].dtype for i, name in enumerate(mi.names)}
  582. tm.assert_index_equal(expected_mi, mi)
  583. assert original_dtypes == mi_dtypes
  584. @pytest.mark.parametrize(
  585. "names_in,names_out", [(None, [("L1", "x"), ("L2", "y")]), (["x", "y"], ["x", "y"])]
  586. )
  587. def test_from_frame_valid_names(names_in, names_out):
  588. # GH 22420
  589. df = pd.DataFrame(
  590. [["a", "a"], ["a", "b"], ["b", "a"], ["b", "b"]],
  591. columns=MultiIndex.from_tuples([("L1", "x"), ("L2", "y")]),
  592. )
  593. mi = MultiIndex.from_frame(df, names=names_in)
  594. assert mi.names == names_out
  595. @pytest.mark.parametrize(
  596. "names,expected_error_msg",
  597. [
  598. ("bad_input", "Names should be list-like for a MultiIndex"),
  599. (["a", "b", "c"], "Length of names must match number of levels in MultiIndex"),
  600. ],
  601. )
  602. def test_from_frame_invalid_names(names, expected_error_msg):
  603. # GH 22420
  604. df = pd.DataFrame(
  605. [["a", "a"], ["a", "b"], ["b", "a"], ["b", "b"]],
  606. columns=MultiIndex.from_tuples([("L1", "x"), ("L2", "y")]),
  607. )
  608. with pytest.raises(ValueError, match=expected_error_msg):
  609. MultiIndex.from_frame(df, names=names)
  610. def test_index_equal_empty_iterable():
  611. # #16844
  612. a = MultiIndex(levels=[[], []], codes=[[], []], names=["a", "b"])
  613. b = MultiIndex.from_arrays(arrays=[[], []], names=["a", "b"])
  614. tm.assert_index_equal(a, b)
  615. def test_raise_invalid_sortorder():
  616. # Test that the MultiIndex constructor raise when a incorrect sortorder is given
  617. # GH#28518
  618. levels = [[0, 1], [0, 1, 2]]
  619. # Correct sortorder
  620. MultiIndex(
  621. levels=levels, codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]], sortorder=2
  622. )
  623. with pytest.raises(ValueError, match=r".* sortorder 2 with lexsort_depth 1.*"):
  624. MultiIndex(
  625. levels=levels, codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 2, 1]], sortorder=2
  626. )
  627. with pytest.raises(ValueError, match=r".* sortorder 1 with lexsort_depth 0.*"):
  628. MultiIndex(
  629. levels=levels, codes=[[0, 0, 1, 0, 1, 1], [0, 1, 0, 2, 2, 1]], sortorder=1
  630. )
  631. def test_datetimeindex():
  632. idx1 = pd.DatetimeIndex(
  633. ["2013-04-01 9:00", "2013-04-02 9:00", "2013-04-03 9:00"] * 2, tz="Asia/Tokyo"
  634. )
  635. idx2 = date_range("2010/01/01", periods=6, freq="M", tz="US/Eastern")
  636. idx = MultiIndex.from_arrays([idx1, idx2])
  637. expected1 = pd.DatetimeIndex(
  638. ["2013-04-01 9:00", "2013-04-02 9:00", "2013-04-03 9:00"], tz="Asia/Tokyo"
  639. )
  640. tm.assert_index_equal(idx.levels[0], expected1)
  641. tm.assert_index_equal(idx.levels[1], idx2)
  642. # from datetime combos
  643. # GH 7888
  644. date1 = np.datetime64("today")
  645. date2 = datetime.today()
  646. date3 = Timestamp.today()
  647. for d1, d2 in itertools.product([date1, date2, date3], [date1, date2, date3]):
  648. index = MultiIndex.from_product([[d1], [d2]])
  649. assert isinstance(index.levels[0], pd.DatetimeIndex)
  650. assert isinstance(index.levels[1], pd.DatetimeIndex)
  651. # but NOT date objects, matching Index behavior
  652. date4 = date.today()
  653. index = MultiIndex.from_product([[date4], [date2]])
  654. assert not isinstance(index.levels[0], pd.DatetimeIndex)
  655. assert isinstance(index.levels[1], pd.DatetimeIndex)
  656. def test_constructor_with_tz():
  657. index = pd.DatetimeIndex(
  658. ["2013/01/01 09:00", "2013/01/02 09:00"], name="dt1", tz="US/Pacific"
  659. )
  660. columns = pd.DatetimeIndex(
  661. ["2014/01/01 09:00", "2014/01/02 09:00"], name="dt2", tz="Asia/Tokyo"
  662. )
  663. result = MultiIndex.from_arrays([index, columns])
  664. assert result.names == ["dt1", "dt2"]
  665. tm.assert_index_equal(result.levels[0], index)
  666. tm.assert_index_equal(result.levels[1], columns)
  667. result = MultiIndex.from_arrays([Series(index), Series(columns)])
  668. assert result.names == ["dt1", "dt2"]
  669. tm.assert_index_equal(result.levels[0], index)
  670. tm.assert_index_equal(result.levels[1], columns)
  671. def test_multiindex_inference_consistency():
  672. # check that inference behavior matches the base class
  673. v = date.today()
  674. arr = [v, v]
  675. idx = Index(arr)
  676. assert idx.dtype == object
  677. mi = MultiIndex.from_arrays([arr])
  678. lev = mi.levels[0]
  679. assert lev.dtype == object
  680. mi = MultiIndex.from_product([arr])
  681. lev = mi.levels[0]
  682. assert lev.dtype == object
  683. mi = MultiIndex.from_tuples([(x,) for x in arr])
  684. lev = mi.levels[0]
  685. assert lev.dtype == object
  686. def test_dtype_representation():
  687. # GH#46900
  688. pmidx = MultiIndex.from_arrays([[1], ["a"]], names=[("a", "b"), ("c", "d")])
  689. result = pmidx.dtypes
  690. expected = Series(
  691. ["int64", "object"], index=MultiIndex.from_tuples([("a", "b"), ("c", "d")])
  692. )
  693. tm.assert_series_equal(result, expected)