Если вы можете организовать ваши столбцы как мультииндекс с первым уровнем 'Payment Type X'
... есть относительно простое решение (в конце этой публикации вы найдете код, который переводит ваш фрейм данных в эту форму).
При использовании мультииндексов в столбцах, как описано выше, следующий код производит вывод:
result= None
for col_group in set(df.columns.get_level_values(0)):
df_group= df[col_group].assign(variable=col_group).set_index('variable', append=True)
if result is None:
result= df_group
else:
result= pd.concat([result, df_group], axis='index')
result.sort_index(inplace=True)
После того, как переменная результата выполнения содержит кадр данных, который выглядит следующим образом:
owned Credit Used Limit
cust_id variable
Person_A Payment Type X 1 300 700
Payment Type Y 3 700 800
Payment Type Z 4 400 900
Person_B Payment Type X 2 400 600
Payment Type Y 1 100 150
Payment Type Z 3 400 500
Person_C Payment Type X 2 500 600
Payment Type Y 4 700 800
Payment Type Z 4 100 500
следующий код создает тестовые данные и реорганизует столбцы, как указано выше:
import pandas as pd
import io
raw=\
""" cust_id Payment Type X owned Payment Type Y owned Payment Type Z owned Credit Used_X Limit_X Credit Used_Y Limit_Y Credit Used_Z Limit_Z
0 Person_A 1 3 4 300 700 700 800 400 900
1 Person_B 2 1 3 400 600 100 150 400 500
2 Person_C 2 4 4 500 600 700 800 100 500"""
df= pd.read_csv(io.StringIO(raw), sep=' +', engine='python')
df.set_index(['cust_id'], inplace=True)
new_cols= list()
for col in df.columns:
if 'X' in col:
lv1= 'Payment Type X'
elif 'Y' in col:
lv1= 'Payment Type Y'
elif 'Z' in col:
lv1= 'Payment Type Z'
else:
lv1= col
if col[-2:-1] == '_':
lv2= col[:-2]
elif col.endswith(' owned'):
lv2= 'owned'
else:
lv2= col
new_cols.append((lv1, lv2))
df.columns= pd.MultiIndex.from_tuples(new_cols)
Более радикальный подход всего за один шаг выглядит следующим образом:
flat= df_orig.melt(id_vars=['cust_id'], var_name='column')
flat['variable']= ''
flat.loc[flat['column'].str.match('.*[_ ]X.*'), 'variable']= 'Payment Type X'
flat.loc[flat['column'].str.match('.*[_ ]Y.*'), 'variable']= 'Payment Type Y'
flat.loc[flat['column'].str.match('.*[_ ]Z.*'), 'variable']= 'Payment Type Z'
flat['column']= flat['column'].str.replace('[_ ][XYZ]', '').str.replace('Payment Type owned', 'Owned')
flat.set_index(['cust_id', 'variable', 'column'], inplace=True)
result= flat.unstack().droplevel(0, axis='columns')
Это более радикально, потому что онополностью разлагает исходный фрейм данных, чтобы восстановить его. Вероятно, он менее эффективен, чем первый подход.