Question: I have noticed an unexpected result when resetting the level values in a pandas.MultiIndex. The minimal working example I have found to reproduce the problem is as follows: import numpy as np import pandas as pd numbers = np.arange(11).astype(str) columns = pd.MultiIndex.from_product([['A'],numbers]) df = pd.DataFrame(index=[0], columns=columns, dtype=float) print(df.columns)  returns MultiIndex([('A', '0'), ('A', '1'), ('A', '2'), ('A', '3'), ('A', '4'), ('A', '5'), ('A', '6'), ('A', '7'), ('A', '8'), ('A', '9'), ('A', '10')], )  Notice how all values on the second level are strings of integers. I have tried to replace them with the respective integers by using the set_levels method: numbers = df.columns.get_level_values(1).astype(int) df.columns = df.columns.set_levels(numbers, level=1) print(df.columns)  To my surprise, the result looks as follows: MultiIndex([('A', 0), ('A', 1), ('A', 3), ('A', 4), ('A', 5), ('A', 6), ('A', 7), ('A', 8), ('A', 9), ('A', 10), ('A', 2)], )  The values on the second level now are in a different order. What am I missing here? How can I actually replace the integer strings with the respective integers?

Answer: It's a bit confusing but it's not a surprise and this is the expected behavior. I slightly modified your example: numbers = np.arange(11).astype(str) columns = pd.MultiIndex.from_product([['A'],numbers]) df = pd.DataFrame(columns.codes, index=['Lvl0', 'Lvl1'], columns=columns) print(df) # Output: A 0 1 2 3 4 5 6 7 8 9 10 Lvl0 0 0 0 0 0 0 0 0 0 0 0 # internal codes for level 0 Lvl1 0 1 3 4 5 6 7 8 9 10 2 # internal codes for level 1  When you create your MultiIndex (here, with numbers as string), Pandas creates internal codes (for indexing, sorting, etc) associated to the labels but in the lexicographical order in your case: # labels: '0' < '1' < '10' < '2' < '3' < '4' < '5' < '6' < '7' < '8' < '9' # codes: 0 < 1 < 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 >>> df.columns.codes FrozenList([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 2]]) >>> df.columns.is_monotonic_increasing False # because it's not lexicographically ordered >>> df.sort_index(axis=1) # lexicographical sort A 0 1 10 2 3 4 5 6 7 8 9 Lvl0 0 0 0 0 0 0 0 0 0 0 0 Lvl1 0 1 2 3 4 5 6 7 8 9 10  As human, you use labels to access your columns but Pandas uses codes behind the scene to optimize indexing and maintain order. In the second part of your code, you convert the labels from string to int and set level. However, you don't sort the index so the previous order (given by codes) are still maintained: # labels: 0 < 1 < 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 # codes: 0 < 1 < 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 >> df.columns.codes FrozenList([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 2]]) >>> df.columns.is_monotonic_increasing False >>> df.sort_index(axis=1) # numbering sort A 0 1 2 3 4 5 6 7 8 9 10 Lvl0 0 0 0 0 0 0 0 0 0 0 0 Lvl1 0 1 2 3 4 5 6 7 8 9 10  As you can see, you lost correct values. You have to sort your columns before: >>> df # with '0', '1', '2', etc A 0 1 2 3 4 5 6 7 8 9 10 Lvl0 0 0 0 0 0 0 0 0 0 0 0 Lvl1 0 1 3 4 5 6 7 8 9 10 2 >>> df = df.sort_index(axis=1) A 0 1 10 2 3 4 5 6 7 8 9 Lvl0 0 0 0 0 0 0 0 0 0 0 0 Lvl1 0 1 2 3 4 5 6 7 8 9 10 # set_levels(...) >>> df # with 0, 1, 2, etc A 0 1 10 2 3 4 5 6 7 8 9 Lvl0 0 0 0 0 0 0 0 0 0 0 0 Lvl1 0 1 2 3 4 5 6 7 8 9 10 >>> df = df.sort_index(axis=1) A 0 1 2 3 4 5 6 7 8 9 10 Lvl0 0 0 0 0 0 0 0 0 0 0 0 Lvl1 0 1 3 4 5 6 7 8 9 10 2 # now values and columns are well aligned.