CoolFace
Apppublic

onconpc/onconpc-visualization

sourceHugging Faceupdated 7mo agoView on Hugging Face
2likes
utils_training.py308 linesDownload Raw Back to codes
1import os2import random3from typing import List, Mapping, Optional, Tuple, Union4 5import numpy as np6import pandas as pd7from sklearn.metrics import classification_report8from xgboost import XGBClassifier9 10"""11Author: Intae Moon12The following functions are used for training and evaluating XGBoost-based OncoNPC model.13"""14 15def get_new_indices(curr_indices: List[Union[str, float]],16					mapping_dict: Mapping[float, Union[float,str]],17					non_profile_prefix: Optional[str]='GENIE') -> List[str]:18	"""19	Get new indices based on the mapping dictionary.20	Args:21		curr_indices: list of indices to be mapped22		mapping_dict: dictionary of mapping23		non_profile_prefix: prefix for non-profile data24	Returns:25		new_indices: list of new indices26	"""27	new_indices = []28	for idx in curr_indices:29		if non_profile_prefix in str(idx):30			new_indices.append(idx)31		else:32			new_indices.append(str(mapping_dict[float(idx)]))33	return new_indices34 35def standardize_feat_names(curr_feat_names: List[str]) -> List[str]:36	"""37	Standardize feature names.38	Args:39		curr_feat_names: list of feature names40	Returns:41		new_feat_names: list of standardized feature names42	"""43	new_feat_names = []44	for feat in curr_feat_names:45		if '_mut' in feat:46			new_feat_names.append(feat.replace('_mut', '_MUT'))47		elif 'AGE' in feat or 'Age' in feat:48			new_feat_names.append('Age')49		elif 'GENDER' in feat or 'Sex' in feat:50			new_feat_names.append('Sex')51		elif 'SBS' in feat:52			new_feat_names.append(feat)53		else:   54			new_feat_names.append(feat + '_CNA')55	return new_feat_names56 57def filter_by_threshold(df: pd.DataFrame,58						threshold_per_sample: int,59						threshold_per_feature: int) -> Tuple[List[str], set]:60	"""61	Filter out features and samples based on threshold.62	"""63	binary_df = df != 064	feature_sum = binary_df.sum()65	sample_sum = binary_df.sum(axis=1)66	features_to_exclude = feature_sum.index[feature_sum < threshold_per_feature]67	samples_to_exclude = sample_sum.index[sample_sum < threshold_per_sample]68	return features_to_exclude, set(samples_to_exclude)69 70def categorize_samples_by_center(samples_to_exclude, labels_ckp):71	"""72	Categorize samples by cancer ceneter.73	"""74	profile_excluded = []75	msk_excluded = []76	vicc_excluded = []77	for sample_id in samples_to_exclude:78		sample_str = str(sample_id)79		if 'MSK' in sample_str:80			msk_excluded.append(sample_id)81		elif 'VICC' in sample_str:82			vicc_excluded.append(sample_id)83		else:84			profile_excluded.append(sample_id)85	return labels_ckp.loc[profile_excluded], labels_ckp.loc[vicc_excluded], labels_ckp.loc[msk_excluded]86 87def filter_out_low_freq_feats_and_samples(data_ckp: pd.DataFrame,88										  labels_ckp: pd.DataFrame,89										  data_cup: pd.DataFrame,90										  feature_group_to_features_dict: Mapping[str, List[str]],91										  threshold_per_sample: int=3,92										  threshold_per_feature: int=50) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:93	"""94	Filter out low frequency features and samples.95 96	Args:97		data_ckp: CKP data98		labels_ckp: CKP labels99		data_cup: CUP-CKP data100		feature_group_to_features_dict: dictionary of feature groups to features101		threshold_per_sample: threshold per sample102		threshold_per_feature: threshold per feature103	Returns:104		data_ckp_filtered: filtered CKP data105		labels_ckp_filtered: filtered CKP labels106		data_cup_fitlered: filtered CUP-CKP data107	"""108	mutation_features_to_exclude, mutation_samples_to_exclude = filter_by_threshold(109		data_ckp[feature_group_to_features_dict['mutation']], threshold_per_sample, threshold_per_feature110	)111	112	cna_features_to_exclude, cna_samples_to_exclude = filter_by_threshold(113		data_ckp[feature_group_to_features_dict['cna']], threshold_per_sample, threshold_per_feature114	)115	116	samples_to_exclude = mutation_samples_to_exclude & cna_samples_to_exclude117	118	profile_labels, vicc_labels, msk_labels = categorize_samples_by_center(samples_to_exclude, labels_ckp)119	120	total_excluded_samples = len(profile_labels) + len(vicc_labels) + len(msk_labels)121	print('\n')122	print('Filtering out low frequency features and samples...\n')123	print(f'Total excluded number of patients : {total_excluded_samples}\n')124	print('Profile:\n', pd.value_counts(profile_labels.cancer_type.values, sort=True))125	print('VICC:\n', pd.value_counts(vicc_labels.cancer_type.values, sort=True))126	print('MSK:\n', pd.value_counts(msk_labels.cancer_type.values, sort=True))127	128	features_to_drop = set(mutation_features_to_exclude) | set(cna_features_to_exclude)129	130	print('Dropping features...\n', features_to_drop)131	data_ckp_filtered = data_ckp.drop(columns=features_to_drop)132	data_cup_fitlered = data_cup.drop(columns=features_to_drop)133	134	print('Dropping samples...\n')135	data_ckp_filtered = data_ckp.drop(index=samples_to_exclude)136	labels_ckp_filtered = labels_ckp.drop(index=samples_to_exclude)137	138	print('Samples labels and features matching :')139	print('CKP labels:', all(labels_ckp.index == data_ckp.index))140	print('CUP-CKP features:', all(data_ckp.columns == data_cup.columns))141	return data_ckp_filtered, labels_ckp_filtered, data_cup_fitlered142 143def get_cancer_to_num_val_samples(labels: pd.DataFrame,144								  cancer_types: List[str],145								  k_fold: int=10) -> Mapping[str, int]:146	"""147	Get number of validation samples per cancer type.148	Args:149		labels: labels dataframe with cancer types150		cancer_types: list of cancer types to consider151		k_fold: number of folds152	Returns:153		cancer_to_num_val_samples_dict: dictionary of cancer type to number of validation samples154	"""155	test_frac = 1/k_fold156	cancer_to_num_val_samples_dict = {}157	for cancer in cancer_types:158		labels_cancer = labels.loc[labels['cancer_type'] == cancer]159		cancer_to_num_val_samples_dict[cancer] = int(np.floor(len(labels_cancer) * test_frac))160	return cancer_to_num_val_samples_dict161 162def get_sample_indices_and_labels_based_on_cut_off(pred_probs: np.ndarray,163												   p_max_cut_off: float) -> Tuple[List[int], List[int]]:164	"""165	Get sample indices and labels based on p max cut-off.166	Args:167		pred_probs: predicted probabilities168		p_max_cut_off: cut-off169	Returns:170		indices: list of indices171		max_prob_labels: list of labels172	"""173	indices, max_prob_labels = [], []174	for num_idx, max_prob_idx in enumerate(np.argmax(pred_probs, axis=1)):175		if pred_probs[num_idx][max_prob_idx] > p_max_cut_off:176			indices.append(num_idx)177			max_prob_labels.append(max_prob_idx)178	return indices, max_prob_labels179 180def fit_and_evaluate_model(X_train: np.ndarray,181						   y_train: np.ndarray,182						   X_test: Optional[np.ndarray],183						   y_test: Optional[np.ndarray],184						   params_xgb: Mapping[str, Union[str, int, float]],185						   cancer_types: List[str]) -> Tuple[XGBClassifier, np.ndarray, Mapping[str, Union[str, int, float]]]:186	"""187	Fit and evaluate XGBoost model.188	189	Args:190		X_train: training data191		y_train: training labels192		X_test: test data; optional193		y_test: test labels; optional194		params_xgb: XGBoost parameters195		cancer_types: list of cancer types196	Returns:197		xg_clf: XGBoost model198		pred_probs_on_test: predicted probabilities on test data199		performance_report: performance report200	"""201	xg_clf = XGBClassifier(202		tree_method='hist', 203		n_estimators=int(params_xgb['n_estimators']),204		max_depth=int(params_xgb['max_depth']),205		scale_pos_weight=int(params_xgb['scale_pos_weight']),206		learning_rate=float(params_xgb['learning_rate']),207		verbosity=0208	)209	xg_clf.fit(X_train, y_train, verbose=False)210	if X_test is not None and y_test is not None:211		pred_probs_on_test = xg_clf.predict_proba(X_test)212		performance_report = classification_report(y_test, xg_clf.predict(X_test), target_names=cancer_types, output_dict=True)213		return xg_clf, pred_probs_on_test, performance_report214	else:215		return xg_clf216 217def perform_k_fold(data: pd.DataFrame,218				   labels: pd.DataFrame,219				   cancer_types: List[str],220				   params_xgb: Mapping[str, Union[str, int, float]],221				   k_fold: int=10,222				   save_model_name: Optional[str]=None,223				   p_max_cut_offs: Optional[List[float]]=[0.0, 0.5, 0.7, 0.9]224				   ) -> Tuple[Mapping[int, pd.DataFrame], pd.DataFrame]:	225	"""226	Performs k-fold cross validation.227 228	Args:229		data: training and validation data230		labels: training and validation labels231		cancer_types: list of cancer types232		params_xgb: XGBoost parameters233		k_fold: number of folds234		save_model_name: name of the model to save235		p_max_cut_offs: list of max prediction probability cut-offs236	Returns:237		k_fold_to_performance_report_dict: dictionary of k-fold to performance report238		pred_probs_on_val_total_df: dataframe of predicted probabilities on validation data239	"""240	print('\n')241	print('Chosen parameters for XGBoost:\n', params_xgb)242	print('\n')243	cancer_to_num_val_samples_dict = get_cancer_to_num_val_samples(labels, cancer_types, k_fold)244	k_fold_to_performance_report_dict = {}245	total_val_sampled_so_far = []246	for k in range(k_fold):247		print('\n')248		print(f'k = {k}')249		if k == k_fold - 1:250			# At the last fold, use the remaining samples as validation data.251			val_labels_sampled = labels.loc[list(set(labels.index) - set(total_val_sampled_so_far))]252		else:253			val_labels_sampled = pd.DataFrame()254			# Update the validation labels to sample from.255			val_labels_to_sample_from = labels.loc[list(set(labels.index) - set(total_val_sampled_so_far))]256			for cancer in cancer_types:257				# Sample validation data from each cancer type.258				# this ensures that the validation data is balanced wrt cancer type.259				# Set random seed260				np.random.seed(k)261				random.seed(k)262				val_labels_sampled_curr = val_labels_to_sample_from.loc[val_labels_to_sample_from['cancer_type'] == cancer].sample(263					n=cancer_to_num_val_samples_dict[cancer], replace=False)264				val_labels_sampled = pd.concat([val_labels_sampled, val_labels_sampled_curr])265			# Update the total validation samples sampled so far.266			total_val_sampled_so_far.extend(list(val_labels_sampled.index))267 268		y_val = val_labels_sampled['cancer_label']269		X_val = data.loc[val_labels_sampled.index]270		X_train = data.loc[list(set(data.index) - set(X_val.index))]271		y_train = labels.loc[X_train.index]['cancer_label']272		# Standardize Age based on train data273		age_mean = X_train['Age'].mean()274		age_std = X_train['Age'].std()275		X_train['Age'] = (X_train['Age'] - age_mean) / age_std276		X_val['Age'] = (X_val['Age'] - age_mean) / age_std277		xg_clf, pred_probs_on_val, performance_report = fit_and_evaluate_model(X_train.values,278																		 y_train.values,279																		 X_val.values,280																		 y_val.values,281																		 params_xgb,282																		 cancer_types)283		# Evaluate the model performance based on different maximum prediction probability cut-offs.284		p_max_cut_off_to_performance_report_dict = {}285		for p_max_cut_off in p_max_cut_offs:286			indices, max_prob_labels = get_sample_indices_and_labels_based_on_cut_off(pred_probs_on_val, p_max_cut_off)287			unique_labels = list(set(y_val[indices]))288			report_cut_off = classification_report(y_val[indices], max_prob_labels,289										  target_names=cancer_types, labels=unique_labels, output_dict=True)290			print(pd.DataFrame(report_cut_off))291			p_max_cut_off_to_performance_report_dict[p_max_cut_off] = pd.DataFrame(report_cut_off)292		k_fold_to_performance_report_dict[k] = p_max_cut_off_to_performance_report_dict293		if save_model_name is not None:294			if not os.path.exists('../models'):295				os.makedirs('../models/models')296			xg_clf.save_model(f'../models/xgboost_{save_model_name}_{k}.json')297 298		# Get prediction probabilities on val data.299		pred_probs_on_val_df = pd.DataFrame(pred_probs_on_val, columns=cancer_types, index=X_val.index)300		pred_probs_on_val_df['predicted_cancer'] = [cancer_types[max_idx] for max_idx in pred_probs_on_val.argmax(axis=1)]301		pred_probs_on_val_df['prediction_prob'] = [pred_probs_on_val[num_idx][max_idx]302											 for num_idx, max_idx in enumerate(pred_probs_on_val.argmax(axis=1))]303		if k == 0:304			pred_probs_on_val_total_df = pred_probs_on_val_df305		else:306			pred_probs_on_val_total_df = pd.concat([pred_probs_on_val_total_df, pred_probs_on_val_df])307	return k_fold_to_performance_report_dict, pred_probs_on_val_total_df308