Project: s41467-024-52580-3 | Function: build_feature_df (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Feature engineering algorithm constructing 39-dimensional feature vectors to encode missense mutation impacts on phase separation by integrating physicochemical properties, pi-pi contact frequencies, disorder scores, and domain boundary distances

**KEY TERMS:** {
  "aa_property_feat": "physicochemical properties including hydrophobicity, polarity, mass",
  "pipi_feat": "pi-pi contact frequency predictions from PScore",
  "iupred_feat": "intrinsic disorder scores for mutation site context",
  "pfam_dist_feat": "distance to nearest protein domain boundary",
  "MT_AA": "mutant amino acid identity",
  "WT_AA": "wild-type amino acid identity",
  "MuPosition": "position of missense mutation in protein sequence"
}

**CONTEXT CODE:**                 self.return_dict['mt_poscharge'] = 1
            if aa in sp2_aa:
                self.return_dict['mt_sp2aa'] = 1

    def pipi_feat(self):
        pipi_contact_lists = []
        for p in self.MuP_wt:
            p = min(max(self.pscore_dict.keys()),p)
            p = max(2,p)
            pipi_contact = self.pscore_dict[p-1][1:9]
            pipi_contact_lists.append(pipi_contact)
        if len(pipi_contact_lists) > 1:
            pipi_contact = list(np.average(np.array(pipi_contact_lists),axis=0))
        for idx,name in enumerate(['AVG_BB_SRZ','SRBB_FbyG','AVG_BB_LRZ','LRBB_FbyG',\
            'AVG_SC_SRZ','SRSC_FbyG','AVG_SC_LRZ','LRSC_FbyG']):
            self.return_dict[name] = pipi_contact[idx]

    def idr_feat(self):
        if self.in_pfam == 'out':
            self.return_dict['in_idr'] = 1
            self.return_dict['dist2idr'] = 0
        else:
            self.return_dict['in_idr'] = 0
            self.return_dict['dist2idr'] = self.idr_dist
        if self.pfam_list.count(0) == 0:
            self.return_dict['has_idr'] = 0
        else:
            self.return_dict['has_idr'] = 1

    def build_mut_aa_wt_features(self):
        for f in props.keys():
            f_list = props[f]
            mu_aa_feature_avg = get_prop_average(self.MuPAA, f_list)
            wt_aa_feature_avg = get_prop_average(self.MuPAA_wt, f_list)
            self.return_dict[f+'_wt_AA'] = wt_aa_feature_avg
            self.return_dict[f+'_diff_with_self'] = (mu_aa_feature_avg - wt_aa_feature_avg)*len(self.MuPAA)
            if len(self.idr_wt_seq) == 0: 
                self.return_dict[f+'_wt_idr'] = 0
                self.return_dict[f+'_diff_with_idr'] = 0
            else:
                idr_seq_aa_feature_avg = get_prop_average(self.idr_wt_seq, f_list)
                self.return_dict[f+'_wt_idr'] = idr_seq_aa_feature_avg
                self.return_dict[f+'_diff_with_idr'] = (mu_aa_feature_avg - idr_seq_aa_feature_avg)*len(self.MuPAA)

    def build_idr_features(self):
        iupred_wt = self.input_dict['wt_idrpred']
        self.return_dict['iupred_wt_avg'] = sum(iupred_wt)/len(self.seq_wt)
        self.return_dict['idr_wt_AA'] = np.average([iupred_wt[pos-1] for pos in self.MuP_wt])


def build_feature_df(input_dataframe,seq=None,
                     iupred_scores=None):
    """Feature engineering algorithm constructing 39-dimensional feature vectors to encode missense mutation impacts on phase separation by integrating physicochemical properties, pi-pi contact frequencies, disorder scores, and domain boundary distances"""
    raise NotImplementedError("Function implementation has been masked")

if __name__=='__main__':
    ### process training and evaluation data
    file_names = ['data_merged.tsv','data_merged_weighted_sampling.tsv']
    files_to_save = [
        'ml_features_train_eval.tsv','ml_features_train_eval_weighted_sampling.tsv'
        ] # data for evaluation
    for input_df_name,output_df_name in zip(file_names,files_to_save):
        pd_ = pd.read_csv(
            f'data/dataset/{input_df_name}',
            sep='\t')
        ml_df = pd.DataFrame()
        seq_pfam_info,seq_pfam_mapped,seq_pfam_dist = {},{},{} ### acceleration using dict
        iupred_results,pscore_results = {},{} ### acceleration using dict
        for idx,row in tqdm(pd_.iterrows()):
            if row['wt_seq'] in pfam_dict.keys():
                pfam_df = pfam_dict[row['wt_seq']]
                seq_id = pfam_df['seq id'].values[0]
            elif row['Seq'] in pfam_dict.keys():
                pfam_df = pfam_dict[row['Seq']]
                seq_id = pfam_df['seq id'].values[0]
            else:
                seq_id,pfam_df = '',pd.DataFrame() ### no pfam domain
            seq = row['Seq']
            seq_pfam_info[seq] = map_pfam(seq_id, seq, pfam_df)
            mapped,min_dist = 'out',0
            for pos in str(row['MuPosition']).split(' '): ### computing distance to domain boundary
                if pos_pfam_distance(seq_id, int(pos),pfam_df)[0] != 'out':
                    mapped = 'domain'
                min_dist += pos_pfam_distance(seq_id, int(pos),pfam_df)[1]
            if mapped!= 'domain':
                seq_pfam_dist[seq],seq_pfam_mapped[seq] = 0,'out'
            else:
                seq_pfam_dist[seq],seq_pfam_mapped[seq] = min_dist/len(str(row['MuPosition']).split(' ')),mapped

            mt_aas,wt_aas = row['MT_AA'].split(' '),row['WT_AA'].split(' ')
            positions = [int(x) for x in row['MuPosition'].split(' ')]
            mt_seq = row['Seq']
            wt_seq = points_mutation(mt_seq,positions,mt_aas,wt_aas) # possitive sample only have mt_seq
            row['wt_seq'] = wt_seq
            if wt_seq not in iupred_results.keys():
                wt_idrpred = iupred(wt_seq,'glob')[0]
                iupred_results[wt_seq] = wt_idrpred
            else:
                wt_idrpred = iupred_results[wt_seq]
            row['wt_idrpred'] = wt_idrpred
            if wt_seq not in pscore_results.keys():
                wt_pipi_dict = position_scores(wt_seq)
                pscore_results[wt_seq] = wt_pipi_dict
            else:
                wt_pipi_dict = pscore_results[wt_seq]
            row['pi_dict'] = wt_pipi_dict
            row['pfam_info'] = seq_pfam_info[mt_seq]
            row['in_pfam'] = seq_pfam_mapped[mt_seq]
            row['dist2IDR'] = seq_pfam_dist[mt_seq]
            bf = buildFeatures()
            bf.call(row)
            return_dict = bf.return_dict
            return_dict['label'] = row['label']
            return_dict['Gene_Name'] = str(row['Gene_Name'])
            ml_df = ml_df.append(return_dict,ignore_index=True)
        ml_df.to_csv(f'data/{output_df_name}',sep='\t')



Complete the function implementation replacing "raise NotImplementedError".