Files
digital-trial/digital_patient_extract_metrics.py
Olamide Isreal 9e75f44f1a Digital Trials pipeline configured for WES
Source-only snapshot of the cluster branch for WES execution. Large
reference files (HPA/MANE/ensemble FASTA, model weights, ~597 MB) are
omitted: they are baked into the container images at build time and
mounted from the dreamdock-data PVC at runtime, and exceed the Gitea
request size limit.

Pipeline entry point is main.nf, which orchestrates the biotransformer,
conplex and tissue modules as a single workflow. Ligand inputs are read
from the eureka workspace; protein_zarr and chembl_db come from the
dreamdock-data PVC.
2026-07-27 21:59:52 +01:00

239 lines
12 KiB
Python

import pandas as pd
import numpy as np
import argparse
#get data framw with ENST ids and tissue specific expression
human_protein_atlas = '/home/omic/HPA_normal_ihc_data.tsv'
mane = '/home/omic/MANE_all_transcipts.csv'
#extrect enst with hight expression per tissue and match to enst ids
HPA_normal_ihc_data = pd.read_csv(human_protein_atlas, sep = '\t')
MANE_all_transcipts = pd.read_csv(mane)
#change name to merge
HPA_normal_ihc_data = HPA_normal_ihc_data.rename({'Gene':'Ensembl_Gene'}, axis=1)
#merge dfs
HPA_normal_ihc_data = MANE_all_transcipts.merge(HPA_normal_ihc_data, left_on='Ensembl_Gene', right_on='Ensembl_Gene')
#drop not needed colums
HPA_normal_ihc_data = HPA_normal_ihc_data.drop(['Unnamed: 0', 'seq', 'Ensembl_Gene'], axis=1)
#get only hight expressed proteins
HPA_normal_hight = HPA_normal_ihc_data[HPA_normal_ihc_data['Level'] == 'High']
def get_n_metabolites(file):
"""
Get number of metabolites for a drug
input metabolite file from GET_FINAL_METABOLITES
"""
with open(file, 'r') as f:
first_line = f.readline().strip()
if first_line == 'NO_METABOLITES' or first_line == '':
return 0
metabol = pd.read_csv(file, sep = '\t', header=None)
return metabol.shape[0]
def get_conplex_metric(file_drug, file_all_interactions, target):
"""
Get merrics of interest in dataframe and a list of ENTSs with durg/metabolite interactions
with high expression based on Human Protein Atlas
input files from CONPLEX (first for drug interaction only, second for all interaction above threshold)
and traget ids from pipeline input csv file
"""
#drug target interaction
drug_interactions = pd.read_csv(file_drug, sep = '\t')
#drug target protein ents
ensts = target
scors = []
for enst in ensts:
scor = drug_interactions[drug_interactions['Transcript'] == enst]['Score']
scors.append(scor)
# mean_drug_scor = avg(scors)
mean_drug_scor = 0
max_drug_scor = 0
if len(scors) > 0:
mean_drug_scor = pd.concat(scors).mean() # 0 if empty
max_drug_scor = pd.concat(scors).max() # 0 if empty
#drug/metabolite target interaction — only 'transcipt' and 'conplex_score' are
# referenced below (the 'smile' and 'drug/metabolite' columns are the bulk of the
# 5+ GB file). Skipping them cuts memory ~10x and prevents OOM in 5 GB pods.
drug_interactions = pd.read_csv(file_all_interactions, sep = '\t', usecols=['transcipt', 'conplex_score'])
n_significant_int = len(drug_interactions)
# Handle empty DataFrame case
if n_significant_int == 0:
df = pd.DataFrame([max_drug_scor, mean_drug_scor, np.nan, np.nan, 0, 0, 0, 0, np.nan]).T.rename(
{0:'max_drug_scor',1:'mean_drug_scor',2:'max_target_scor',3:'mean_target_scor',
4:'n_significant_int',5:'n_unique_proteins_above_target_interaction',
6:'n_interactions_above_target_interaction',7:'n_enst_with_hight_expression',
8:'mean_scor_with_hight_expression'}, axis = 1)
return df, [], drug_interactions
#drug target protein ents
ensts = target
scors = []
for enst in ensts:
scor = drug_interactions[drug_interactions['transcipt'] == enst]['conplex_score']
scors.append(scor)
mean_target_scor = 0
max_target_scor = 0
if len(scors) > 0:
mean_target_scor = pd.concat(scors).mean() # 0 if empty
max_target_scor = pd.concat(scors).max() # 0 if empty
if np.isnan(max_target_scor):
pass
else:
drug_interactions = drug_interactions[drug_interactions['conplex_score'] >= max_target_scor].sort_values('conplex_score',ascending=False)
n_unique_proteins_above_target_interaction = pd.unique(drug_interactions['transcipt']).shape[0]
n_interactions_above_target_interaction = drug_interactions.shape[0]
uniq_enst = pd.unique(drug_interactions['transcipt'])
#get only ensts with hight expression
uniq_enst_hight = list(set(pd.unique(HPA_normal_hight['transcipt'])).intersection(uniq_enst))
enst_with_hight_expression = len(uniq_enst_hight)
#sort based on a conplex score
drug_interactions = drug_interactions[[i in uniq_enst_hight for i in drug_interactions['transcipt']]]
if len(drug_interactions) > 0:
mean_scor_with_hight_expression = drug_interactions['conplex_score'].mean()
hight_expression_enst_per_drug = pd.unique(drug_interactions['transcipt'])
else:
mean_scor_with_hight_expression = np.nan
hight_expression_enst_per_drug = []
df = pd.DataFrame([max_drug_scor, mean_drug_scor, max_target_scor, mean_target_scor, n_significant_int,n_unique_proteins_above_target_interaction,n_interactions_above_target_interaction,enst_with_hight_expression, mean_scor_with_hight_expression]).T.rename({0:'max_drug_scor',1:'mean_drug_scor',2:'max_target_scor',3:'mean_target_scor',4:'n_significant_int',5:'n_unique_proteins_above_target_interaction',6:'n_interactions_above_target_interaction',7:'n_enst_with_hight_expression',8:'mean_scor_with_hight_expression'}, axis = 1)
return df, hight_expression_enst_per_drug, drug_interactions
#look at tissue interactions specificly
def get_number_of_interacting_prot_per_tissue(names, hight_expression_enst_per_drug, drug_interactions, n_of_interactions = None):
'''
Tissue specific interactions, only for high expressed proteins
input:
names; drug id, list
hight_expression_enst_per_drug; ensts with interaction of interest, make sure if follows names order and enst orderd by importance based on highest conplex score, lisf of list
drug_interactions; significant interactions filtered
n_of_interactions; n proteins to analyse ,default None use all proteins, use when there is no drug target interaction, interger
output:
number and min_max of tissue highly exprest proteins in interaction with a drug/metabolites, pandas df
'''
n_interactions = []
min_max_df = []
list_of_tissue_ids = [['Heart muscle'],\
['Rectum', 'Gallbladder', 'Esophagus', 'Stomach', 'Small intestine', 'Duodenum', 'Colon', 'Salivary gland'],\
['Breast', 'Lactating breast', 'Cervix', 'Fallopian tube', 'Ovary', 'Placenta', 'Endometrium', 'Vagina'],\
['Testis', 'Prostate', 'Epididymis', 'Seminal vesicle'],\
['Liver'],\
['Kidney', 'Urinary bladder'],\
['Cerebral cortex', 'Cerebellum', 'Caudate', 'Hippocampus', 'Hypothalamus', 'Dorsal raphe', 'Choroid plexus', 'Pituitary gland', 'Substantia nigra'],\
['Adrenal gland', 'Pancreas', 'Parathyroid gland', 'Thyroid gland'],\
['Appendix', 'Lymph node', 'Spleen', 'Thymus', 'Tonsil'],\
['Bone marrow'],\
['Bronchus', 'Lung', 'Nasopharynx', 'Oral mucosa'],\
['Eye', 'Retina'],\
['Skeletal muscle', 'Cartilage'],\
['Adipose tissue', 'Hair', 'Skin', 'Smooth muscle', 'Soft tissue', 'Sole of foot']]
list_of_tissue_names = ['heart', 'gasrto_intestin', 'female_tissues', 'male_tissues', 'liver', 'kidney', 'brain', 'endocrine', 'immune',\
'hematopoietic', 'respiratory', 'sensory', 'musculoskeletal', 'others']
for t_id, t_name in zip(list_of_tissue_ids, list_of_tissue_names):
HPA_normal_hight_tis = HPA_normal_hight[[i in t_id for i in HPA_normal_hight['Tissue']]]
tis = [HPA_normal_hight_tis[[i in j[:n_of_interactions] for i in HPA_normal_hight_tis['transcipt']]] for j in hight_expression_enst_per_drug]
tis = [i.drop_duplicates('transcipt') for i in tis]
# Calculate highly expressed proteins once to use in both branches
num_highly_expressed = len(HPA_normal_hight_tis.drop_duplicates('transcipt'))
# Handle empty drug_interactions case
if len(drug_interactions) == 0 or len(tis[0]) == 0:
min_max_df.append(pd.DataFrame([names, [np.nan], [np.nan], [np.nan], [num_highly_expressed], [0.0], [np.nan]])\
.T.rename({0:'Drug_ID',
1:f'max_{t_name}_interaction',
2:f'min_{t_name}_interaction',
3:f'mean_{t_name}_interaction',
4:f'n_high_protein_in_{t_name}',
5:f'share_affected_{t_name}',
6:f'weighted_score_{t_name}'}, axis = 1))
else:
#get max and min conplex sores for tissue specific ents !!! only for one drug ate a time not for a list (n_interactions is for a list)
inter = drug_interactions[drug_interactions['transcipt'].isin(tis[0]['transcipt'])].sort_values('conplex_score', ascending=False)
# Calculate new metrics based on `inter`
num_affected = inter.shape[0]
share_affected = num_affected / num_highly_expressed if num_highly_expressed > 0 else 0.0
mean_score = inter['conplex_score'].mean()
weighted_score = share_affected * mean_score
min_max_df.append(pd.DataFrame([names, [inter['conplex_score'].max()], [inter['conplex_score'].min()], [mean_score], [num_highly_expressed], [share_affected], [weighted_score]])\
.T.rename({0:'Drug_ID',
1:f'max_{t_name}_interaction',
2:f'min_{t_name}_interaction',
3:f'mean_{t_name}_interaction',
4:f'n_high_protein_in_{t_name}',
5:f'share_affected_{t_name}',
6:f'weighted_score_{t_name}'}, axis = 1))
#get number of interactions for tissue specific ents
n_interactions.append(pd.DataFrame([names, [len(i) for i in tis]]).T.rename({0:'Drug_ID',1:f'n_enst_{t_name}_above_target_interaction'}, axis = 1))
#merge interaction data
df_n_interactions = n_interactions[0]
for d in n_interactions[1:]:
df_n_interactions = df_n_interactions.merge(d, on='Drug_ID')
#merge min max data
df_min_max_df = min_max_df[0]
for d in min_max_df[1:]:
df_min_max_df = df_min_max_df.merge(d, on='Drug_ID')
n_interactions = df_n_interactions.merge(df_min_max_df, left_on='Drug_ID', right_on='Drug_ID')
return n_interactions
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Calculate biological metrics.")
parser.add_argument("--metabolites", required=True, help="Path to the metabolites file.")
parser.add_argument("--conplex_all_interactions", required=True, help="Path to the conplex_significant_interaction file.")
parser.add_argument("--conplex_drug", required=True, help="Path to the conplex_significant_interaction_drug_only file.")
parser.add_argument("--target", required=True, help="PATH to file csv with drug targets in ENST formet separated by space.")
args = parser.parse_args()
drug_name = args.metabolites.split('/')[-1].split('_out.txt')[0]
n_metabolites = get_n_metabolites(args.metabolites)
#get target
target = pd.read_csv(args.target, header=None)[2:]
if len(target) == 2:
target = list(target.iloc[-1])
target = target[0].split(' ')
else:
target = []
df_1, target_ents, drug_interactions = get_conplex_metric(args.conplex_drug, args.conplex_all_interactions, target)
#if no target interaction use only 200 unique proteins with highest interactions per tissue
if df_1['max_target_scor'].isna()[0]:
target_interaction = 0
df_2 = get_number_of_interacting_prot_per_tissue([drug_name], [target_ents], drug_interactions, n_of_interactions = 200)
else:
target_interaction = 1
df_2 = get_number_of_interacting_prot_per_tissue([drug_name], [target_ents], drug_interactions)
df = pd.concat([df_2, df_1], axis=1)
df['target_interaction_found'] = target_interaction
df['n_metabolites'] = n_metabolites
df.to_csv(f'{drug_name}_biological_properties.tsv', sep = '\t', index=False)