'python: how to melt dataframe retaining specific order / custom sorting

I have a dataframe df

Cat  B_1 A_2 C_3
A      1   2   3
B      4   5   6
C      7   8   9

which I want to convert into a dataframe so that the rows in column Label are following the order of df columns for each category.

Desired output

    Cat Label Value
    A     B_1    1
    A     A_2    2
    A     C_3    3
    B     B_1    4
    B     A_2    5
    B     C_3    6
    C     B_1    7
    C     A_2    8
    C     C_3    9  

when I try the

pd.melt(df, id_vars=["Cat"], var_name="Label",value_name="Value")

I lose the desired order in column Label, the results are sorted like below,

Cat Label Value
A     B_1    1
B     B_1    4
C     B_1    7
A     A_2    2
...

Can the desired order of rows be forced in the melt function? if not, how can be this custom sorting achieved?


UPDATE

I renamed the labels as they don't follow the alphabetical order, so that simple sorting doesn't work



Solution 1:[1]

IIUC, you can use your exact same code and add .sort_values('Cat'), or more simply:

df.melt('Cat',var_name='Label',value_name='Value').sort_values('Cat')

  Cat Label  Value
0   A   L_1      1
3   A   L_2      2
6   A   L_3      3
1   B   L_1      4
4   B   L_2      5
7   B   L_3      6
2   C   L_1      7
5   C   L_2      8
8   C   L_3      9

If you want to order it in a custom manner (In the example below, B precedes A which precedes C), then you can set Cat to be an ordered categorical:

melted = df.melt('Cat',var_name='Label',value_name='Value')

melted['Cat'] = pd.Categorical(melted['Cat'], categories=['B','A','C'], ordered=True)

melted.sort_values('Cat')

  Cat Label  Value
1   B   L_1      4
4   B   L_2      5
7   B   L_3      6
0   A   L_1      1
3   A   L_2      2
6   A   L_3      3
2   C   L_1      7
5   C   L_2      8
8   C   L_3      9

An alternative is to use stack, but then you have to deal with annoying renaming of columns:

df.set_index('Cat').stack().reset_index().rename(columns={'level_1':'Label', 0:'Value'})

  Cat Label  Value
0   A   L_1      1
1   A   L_2      2
2   A   L_3      3
3   B   L_1      4
4   B   L_2      5
5   B   L_3      6
6   C   L_1      7
7   C   L_2      8
8   C   L_3      9

Solution 2:[2]

One option where you do not need to convert to categorical dtype, and maintain the order is with pivot_longer from pyjanitor

# pip install pyjanitor
import pandas as pd
import janitor

df.pivot_longer('Cat', sort_by_appearance=True)

  Cat variable  value
0   A      B_1      1
1   A      A_2      2
2   A      C_3      3
3   B      B_1      4
4   B      A_2      5
5   B      C_3      6
6   C      B_1      7
7   C      A_2      8
8   C      C_3      9

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 sammywemmy