This is the solution which I came up with.
Creating a unique key for each bounding box
df['key']=df['xmin'].astype(str)+'_'+df['ymin'].astype(str)+'_'+df['xmax'].astype(str)+'_'+df['ymax'].astype(str)
Making an outer join of all the rows based on filename. (for calculating IoU)
###copy df
df_1=df.copy()
###Renaming df columns with _1 suffix
df_cols=df.columns.tolist()
df_cols.remove('filename')
new_cols=[col+'_1' for col in df_cols]
new_col_dict=dict(zip(df_cols,new_cols))
df_1.rename(columns=new_col_dict,inplace=True)
### Outer joining both dataframes
newdf=pd.merge(df,df_1,'outer',on='filename')
Outer join sample:

Function to find IoU of each row
def IOU(df):
'''funtion to calulcate IOU within rows of dataframe'''
# determining the minimum and maximum -coordinates of the intersection rectangle
xmin_inter = max(df.xmin, df.xmin_1)
ymin_inter = max(df.ymin, df.ymin_1)
xmax_inter = min(df.xmax, df.xmax_1)
ymax_inter = min(df.ymax, df.ymax_1)
# calculate area of intersection rectangle
inter_area = max(0, xmax_inter - xmin_inter + 1) * max(0, ymax_inter - ymin_inter + 1)
# calculate area of actual and predicted boxes
actual_area = (df.xmax - df.xmin + 1) * (df.ymax - df.ymin + 1)
pred_area = (df.xmax_1 - df.xmin_1 + 1) * (df.ymax_1 - df.ymin_1+ 1)
# computing intersection over union
iou = inter_area / float(actual_area + pred_area - inter_area)
# return the intersection over union value
return iou
Calculating IoU for each row and filtering rows with ioU<0.4
newdf['iou']= newdf.apply(IOU, axis = 1)
### filtering all iou<0.4
newdf=newdf[newdf['iou']>=0.4]
Getting the best match for each key
once we have the IoU match dataframe, parse through each unique key bounding box,
get the value with max confidence for each unique key with iou>0.4
best_df=pd.DataFrame()
for i, v in df.iterrows():
key=v['key']
iou_match=newdf[newdf['key']==key]
iou_match.sort_values('confidence_1',ascending=False,inplace=True)
iou_match=iou_match.reset_index()
best_match=iou_match.loc[0,['filename','class_1','xmin_1','ymin_1','xmax_1','ymax_1','confidence_1']]
best_df=best_df.append(best_match,ignore_index=True)
best_df now looks like:
class_1 confidence_1 filename xmax_1 xmin_1 ymax_1 ymin_1
0 cube 0.99 dummyfile.jpg 197.0 88.0 198.0 87.0
1 cube 0.99 dummyfile.jpg 197.0 88.0 198.0 87.0
2 cube 0.99 dummyfile.jpg 197.0 88.0 198.0 87.0
3 cube 0.89 dummyfile.jpg 603.0 492.0 295.0 187.0
4 cube 0.89 dummyfile.jpg 603.0 492.0 295.0 187.0
Removing duplicates to get unique best matches
best_df.drop_duplicates(inplace=True)
Final result:
class_1 confidence_1 filename xmax_1 xmin_1 ymax_1 ymin_1
0 cube 0.99 dummyfile.jpg 197.0 88.0 198.0 87.0
3 cube 0.89 dummyfile.jpg 603.0 492.0 295.0 187.0