Это альтернативный вариант использования pandas melt :
#flip table into long format
(df.melt(['Id','Name'])
#sort by Id so that result follows immediately after Exam
.sort_values('Id')
#create new column on rows that have result in the variable column
.assign(Result=lambda x: x.loc[x['variable']=="Result",'value'])
.bfill()
#get rid of rows that contain 'result' in variable column
.query('variable != "Result"')
.drop(['variable'],axis=1)
.rename(columns={'value':'Exam'})
)
Id Name Exam Result
0 1 Bob Maths 10
4 1 Bob Physics 9
1 2 Mar ML 8
5 2 Mar Chemistry 10
В качестве альтернативы, просто для удовольствия:
df = df.set_index(['Id','Name'])
#get boolean of duplicated columns
dupes = df.columns.duplicated()
#concatenate first columns and their duplicates
pd.concat([df.loc[:,~dupes],
df.loc[:,dupes]
]).sort_index()