преобразовать столбцы в нижний регистр и разделить на '_' ... настройка expand = True преобразует его в MultiIndex:
df.columns = df.columns.str.lower().str.split('_',expand=True)
df.columns = df.columns.set_names(['stage','status'])
print(df)
product design production
NaN start complete start complete
0 1 2020-01-05 2020-01-22 2020-02-07 NaT
1 2 2020-01-17 2020-03-04 2020-03-15 2020-04-28
Следующая фаза комбинация стек , значений сортировки , droplevel , сброса индекса и переиндексации :
res = (df
.stack([0,1])
.sort_values()
.droplevel(0)
.reset_index(name='Date')
.reindex(['Date','stage','status'],axis=1)
)
res
DATE STAGE STATUS
0 2020-01-05 design start
1 2020-01-17 design start
2 2020-01-22 design complete
3 2020-02-07 production start
4 2020-03-04 design complete
5 2020-03-15 production start
6 2020-04-28 production complete
если вас интересуют только группировки и агрегация, тогда вы можете пропустить длинный путь и просто взлететь после стека:
df.stack([0,1]).groupby(['stage','status']).count()
stage status
design complete 2
start 2
production complete 1
start 2
Name: Date, dtype: int64