Skip to content

Commit 77f3af8

Browse files
author
cloudboat
committed
DOC: Add whatsnew entry for MultiIndex.insert_level
1 parent 094958d commit 77f3af8

File tree

4 files changed

+105
-83
lines changed

4 files changed

+105
-83
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ Other enhancements
214214
- :meth:`pandas.concat` will raise a ``ValueError`` when ``ignore_index=True`` and ``keys`` is not ``None`` (:issue:`59274`)
215215
- :py:class:`frozenset` elements in pandas objects are now natively printed (:issue:`60690`)
216216
- Add ``"delete_rows"`` option to ``if_exists`` argument in :meth:`DataFrame.to_sql` deleting all records of the table before inserting data (:issue:`37210`).
217+
- Added :meth:`MultiIndex.insert_level` to insert new levels at specified positions in a MultiIndex (:issue:`62558`)
217218
- Added half-year offset classes :class:`HalfYearBegin`, :class:`HalfYearEnd`, :class:`BHalfYearBegin` and :class:`BHalfYearEnd` (:issue:`60928`)
218219
- Added support for ``axis=1`` with ``dict`` or :class:`Series` arguments into :meth:`DataFrame.fillna` (:issue:`4514`)
219220
- Added support to read and write from and to Apache Iceberg tables with the new :func:`read_iceberg` and :meth:`DataFrame.to_iceberg` functions (:issue:`61383`)
@@ -228,7 +229,7 @@ Other enhancements
228229
- Support reading Stata 102-format (Stata 1) dta files (:issue:`58978`)
229230
- Support reading Stata 110-format (Stata 7) dta files (:issue:`47176`)
230231
- Switched wheel upload to **PyPI Trusted Publishing** (OIDC) for release-tag pushes in ``wheels.yml``. (:issue:`61718`)
231-
-
232+
232233

233234
.. ---------------------------------------------------------------------------
234235
.. _whatsnew_300.notable_bug_fixes:

pandas/core/indexes/multi.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2775,7 +2775,6 @@ def insert_level(self, position: int, value, name=None) -> MultiIndex:
27752775

27762776
new_names.insert(position, name)
27772777

2778-
27792778
return MultiIndex.from_tuples(new_tuples, names=new_names)
27802779

27812780
def _reorder_ilevels(self, order) -> MultiIndex:

pandas/tests/frame/test_query_eval.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -160,21 +160,13 @@ def test_query_empty_string(self):
160160
df.query("")
161161

162162
def test_query_duplicate_column_name(self, engine, parser):
163-
df = DataFrame(
164-
{
165-
"A": range(3),
166-
"B": range(3),
167-
"C": range(3)
168-
}
169-
).rename(columns={"B": "A"})
163+
df = DataFrame({"A": range(3), "B": range(3), "C": range(3)}).rename(
164+
columns={"B": "A"}
165+
)
170166

171167
res = df.query("C == 1", engine=engine, parser=parser)
172168

173-
expect = DataFrame(
174-
[[1, 1, 1]],
175-
columns=["A", "A", "C"],
176-
index=[1]
177-
)
169+
expect = DataFrame([[1, 1, 1]], columns=["A", "A", "C"], index=[1])
178170

179171
tm.assert_frame_equal(res, expect)
180172

@@ -1140,9 +1132,7 @@ def test_query_with_nested_special_character(self, parser, engine):
11401132
[">=", operator.ge],
11411133
],
11421134
)
1143-
def test_query_lex_compare_strings(
1144-
self, parser, engine, op, func
1145-
):
1135+
def test_query_lex_compare_strings(self, parser, engine, op, func):
11461136
a = Series(np.random.default_rng(2).choice(list("abcde"), 20))
11471137
b = Series(np.arange(a.size))
11481138
df = DataFrame({"X": a, "Y": b})

pandas/tests/indexes/multi/test_insert_level.py

Lines changed: 98 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,87 +5,119 @@
55

66

77
class TestMultiIndexInsertLevel:
8-
def setup_method(self):
9-
self.simple_idx = pd.MultiIndex.from_tuples(
8+
@pytest.mark.parametrize(
9+
"position, value, name, expected_tuples, expected_names",
10+
[
11+
(
12+
0,
13+
"new_value",
14+
None,
15+
[("new_value", "A", 1), ("new_value", "B", 2), ("new_value", "C", 3)],
16+
[None, "level1", "level2"],
17+
),
18+
(
19+
1,
20+
"middle",
21+
None,
22+
[("A", "middle", 1), ("B", "middle", 2), ("C", "middle", 3)],
23+
["level1", None, "level2"],
24+
),
25+
(
26+
0,
27+
"new_val",
28+
"new_level",
29+
[("new_val", "A", 1), ("new_val", "B", 2), ("new_val", "C", 3)],
30+
["new_level", "level1", "level2"],
31+
),
32+
(
33+
1,
34+
"middle",
35+
"custom_name",
36+
[("A", "middle", 1), ("B", "middle", 2), ("C", "middle", 3)],
37+
["level1", "custom_name", "level2"],
38+
),
39+
],
40+
)
41+
def test_insert_level_basic(
42+
self, position, value, name, expected_tuples, expected_names
43+
):
44+
simple_idx = pd.MultiIndex.from_tuples(
1045
[("A", 1), ("B", 2), ("C", 3)], names=["level1", "level2"]
1146
)
12-
self.empty_idx = pd.MultiIndex.from_tuples([], names=["level1", "level2"])
1347

14-
def test_insert_level_basic(self):
15-
result = self.simple_idx.insert_level(0, "new_value")
16-
expected = pd.MultiIndex.from_tuples(
17-
[("new_value", "A", 1), ("new_value", "B", 2), ("new_value", "C", 3)],
18-
names=[None, "level1", "level2"],
19-
)
48+
result = simple_idx.insert_level(position, value, name=name)
49+
expected = pd.MultiIndex.from_tuples(expected_tuples, names=expected_names)
2050
tm.assert_index_equal(result, expected)
2151

22-
result = self.simple_idx.insert_level(1, "middle")
23-
expected = pd.MultiIndex.from_tuples(
24-
[("A", "middle", 1), ("B", "middle", 2), ("C", "middle", 3)],
25-
names=["level1", None, "level2"],
52+
@pytest.mark.parametrize(
53+
"position, value",
54+
[
55+
(0, "start"),
56+
(2, "end"),
57+
],
58+
)
59+
def test_insert_level_edge_positions(self, position, value):
60+
simple_idx = pd.MultiIndex.from_tuples(
61+
[("A", 1), ("B", 2), ("C", 3)], names=["level1", "level2"]
2662
)
27-
tm.assert_index_equal(result, expected)
2863

29-
def test_insert_level_with_different_values(self):
30-
new_values = ["X", "Y", "Z"]
31-
result = self.simple_idx.insert_level(1, new_values)
32-
expected = pd.MultiIndex.from_tuples(
33-
[("A", "X", 1), ("B", "Y", 2), ("C", "Z", 3)],
34-
names=["level1", None, "level2"],
64+
result = simple_idx.insert_level(position, value)
65+
assert result.nlevels == 3
66+
67+
@pytest.mark.parametrize(
68+
"position, value, expected_error",
69+
[
70+
(5, "invalid", "position must be between"),
71+
(-1, "invalid", "position must be between"),
72+
(1, ["too", "few"], "Length of values must match"),
73+
],
74+
)
75+
def test_insert_level_error_cases(self, position, value, expected_error):
76+
simple_idx = pd.MultiIndex.from_tuples(
77+
[("A", 1), ("B", 2), ("C", 3)], names=["level1", "level2"]
3578
)
36-
tm.assert_index_equal(result, expected)
37-
38-
def test_insert_level_with_name(self):
39-
result = self.simple_idx.insert_level(0, "new_val", name="new_level")
40-
assert result.names[0] == "new_level"
41-
42-
def test_insert_level_edge_positions(self):
43-
result_start = self.simple_idx.insert_level(0, "start")
44-
assert result_start.nlevels == 3
45-
46-
result_end = self.simple_idx.insert_level(2, "end")
47-
assert result_end.nlevels == 3
48-
49-
def test_insert_level_error_cases(self):
50-
with pytest.raises(ValueError, match="position must be between"):
51-
self.simple_idx.insert_level(5, "invalid")
5279

53-
with pytest.raises(ValueError, match="position must be between"):
54-
self.simple_idx.insert_level(-1, "invalid")
80+
with pytest.raises(ValueError, match=expected_error):
81+
simple_idx.insert_level(position, value)
5582

56-
with pytest.raises(ValueError, match="Length of values must match"):
57-
self.simple_idx.insert_level(1, ["too", "few"])
58-
59-
def test_insert_level_with_different_data_types(self):
60-
result_int = self.simple_idx.insert_level(1, 100)
61-
62-
result_float = self.simple_idx.insert_level(1, 1.5)
63-
64-
result_none = self.simple_idx.insert_level(1, None)
83+
@pytest.mark.parametrize(
84+
"value",
85+
[100, 1.5, None],
86+
)
87+
def test_insert_level_with_different_data_types(self, value):
88+
simple_idx = pd.MultiIndex.from_tuples(
89+
[("A", 1), ("B", 2), ("C", 3)], names=["level1", "level2"]
90+
)
6591

66-
assert result_int.nlevels == 3
67-
assert result_float.nlevels == 3
68-
assert result_none.nlevels == 3
92+
result = simple_idx.insert_level(1, value)
93+
assert result.nlevels == 3
6994

7095
def test_insert_level_preserves_original(self):
71-
original = self.simple_idx.copy()
72-
result = self.simple_idx.insert_level(1, "temp")
96+
simple_idx = pd.MultiIndex.from_tuples(
97+
[("A", 1), ("B", 2), ("C", 3)], names=["level1", "level2"]
98+
)
7399

74-
tm.assert_index_equal(original, self.simple_idx)
100+
original = simple_idx.copy()
101+
simple_idx.insert_level(1, "temp")
75102

76-
assert result.nlevels == original.nlevels + 1
103+
tm.assert_index_equal(original, simple_idx)
77104

78-
def test_debug_names():
79-
idx = pd.MultiIndex.from_tuples(
80-
[("A", 1), ("B", 2), ("C", 3)], names=["level1", "level2"]
81-
)
82-
print("Original names:", idx.names)
105+
def test_insert_level_empty_index(self):
106+
empty_idx = pd.MultiIndex.from_tuples([], names=["level1", "level2"])
83107

84-
result = idx.insert_level(0, "new_value")
85-
print("Result names:", result.names)
108+
result = empty_idx.insert_level(0, [])
109+
assert result.nlevels == 3
110+
assert len(result) == 0
86111

87-
expected = pd.MultiIndex.from_tuples(
88-
[("new_value", "A", 1), ("new_value", "B", 2), ("new_value", "C", 3)],
89-
names=[None, "level1", "level2"],
90-
)
91-
print("Expected names:", expected.names)
112+
def test_insert_level_with_different_values(self):
113+
simple_idx = pd.MultiIndex.from_tuples(
114+
[("A", 1), ("B", 2), ("C", 3)], names=["level1", "level2"]
115+
)
116+
117+
new_values = ["X", "Y", "Z"]
118+
result = simple_idx.insert_level(1, new_values)
119+
expected = pd.MultiIndex.from_tuples(
120+
[("A", "X", 1), ("B", "Y", 2), ("C", "Z", 3)],
121+
names=["level1", None, "level2"],
122+
)
123+
tm.assert_index_equal(result, expected)

0 commit comments

Comments
 (0)