txoptimus 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. txoptimus/__init__.py +8 -0
  2. txoptimus/core/TxData.py +160 -0
  3. txoptimus/core/TxEval.py +42 -0
  4. txoptimus/core/TxGNN.py +764 -0
  5. txoptimus/core/__init__.py +3 -0
  6. txoptimus/core/data_splits/HumanDO.obo +144082 -0
  7. txoptimus/core/data_splits/__init__.py +0 -0
  8. txoptimus/core/data_splits/datasplit.py +204 -0
  9. txoptimus/core/data_splits/do_obo_parser.py +631 -0
  10. txoptimus/core/data_splits/kg_grouped_diseases.csv +22206 -0
  11. txoptimus/core/data_splits/kg_grouped_diseases_bert_map.csv +6393 -0
  12. txoptimus/core/data_splits/mondo_references.csv +117273 -0
  13. txoptimus/core/graphmask/__init__.py +0 -0
  14. txoptimus/core/graphmask/abstract_torch_module.py +21 -0
  15. txoptimus/core/graphmask/hard_concrete.py +48 -0
  16. txoptimus/core/graphmask/lagrangian_optimization.py +59 -0
  17. txoptimus/core/graphmask/moving_average.py +40 -0
  18. txoptimus/core/graphmask/multiple_inputs_layernorm_linear.py +68 -0
  19. txoptimus/core/graphmask/sigmoid_penalty.py +53 -0
  20. txoptimus/core/graphmask/squeezer.py +6 -0
  21. txoptimus/core/model.py +642 -0
  22. txoptimus/core/utils.py +1772 -0
  23. txoptimus/core/version.py +22 -0
  24. txoptimus/data_manager.py +166 -0
  25. txoptimus/disease_matcher.py +177 -0
  26. txoptimus/engines/__init__.py +2 -0
  27. txoptimus/engines/optimus.py +293 -0
  28. txoptimus/engines/prime.py +294 -0
  29. txoptimus/txoptimus.py +263 -0
  30. txoptimus-0.1.0.dist-info/METADATA +156 -0
  31. txoptimus-0.1.0.dist-info/RECORD +34 -0
  32. txoptimus-0.1.0.dist-info/WHEEL +5 -0
  33. txoptimus-0.1.0.dist-info/entry_points.txt +2 -0
  34. txoptimus-0.1.0.dist-info/top_level.txt +1 -0
txoptimus/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ TxOptimus — Zero-shot drug repurposing via TxGNN on PrimeKG and OptimusKG.
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+ __author__ = "arsalanriaz"
7
+
8
+ from .core import TxData, TxGNN, TxEval
@@ -0,0 +1,160 @@
1
+ import os
2
+ import math
3
+ import copy
4
+ import pickle
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ from tqdm.auto import tqdm
9
+ import dgl
10
+
11
+ from .utils import preprocess_kg, create_split, process_disease_area_split, create_dgl_graph, evaluate_graph_construct, convert2str, data_download_wrapper, reverse_rel_generation
12
+
13
+ import warnings
14
+ warnings.filterwarnings("ignore")
15
+
16
+
17
+ class TxData:
18
+
19
+ def __init__(self, data_folder_path):
20
+ if not os.path.exists(data_folder_path):
21
+ os.mkdir(data_folder_path)
22
+
23
+ self.data_folder = data_folder_path # the data folder, contains the kg.csv
24
+ data_download_wrapper('https://dataverse.harvard.edu/api/access/datafile/7144484', os.path.join(self.data_folder, 'kg.csv'))
25
+ data_download_wrapper('https://dataverse.harvard.edu/api/access/datafile/7144482', os.path.join(self.data_folder, 'node.csv'))
26
+ data_download_wrapper('https://dataverse.harvard.edu/api/access/datafile/7144483', os.path.join(self.data_folder, 'edges.csv'))
27
+
28
+
29
+ def prepare_split(self, split = 'complex_disease',
30
+ disease_eval_idx = None,
31
+ seed = 42,
32
+ no_kg = False,
33
+ test_size = 0.05,
34
+ mask_ratio = 0.1,
35
+ one_hop = False):
36
+
37
+ if split not in ['random', 'complex_disease', 'complex_disease_cv', 'disease_eval', 'cell_proliferation', 'mental_health', 'cardiovascular', 'anemia', 'adrenal_gland','autoimmune', 'metabolic_disorder', 'diabetes', 'neurodigenerative', 'full_graph', 'downstream_pred', 'few_edeges_to_kg', 'few_edeges_to_indications']:
38
+ raise ValueError("Please select one of the following supported splits: 'random', 'complex_disease', 'disease_eval', 'cell_proliferation', 'mental_health', 'cardiovascular', 'anemia', 'adrenal_gland'")
39
+
40
+ if disease_eval_idx is not None:
41
+ split = 'disease_eval'
42
+ print('disease eval index is not none, use the individual disease split...')
43
+ self.split = split
44
+
45
+ if split in ['cell_proliferation', 'mental_health', 'cardiovascular', 'anemia', 'adrenal_gland', 'autoimmune', 'metabolic_disorder', 'diabetes', 'neurodigenerative']:
46
+
47
+ if test_size != 0.05:
48
+ folder_name = split + '_kg' + '_frac' + str(test_size)
49
+ elif one_hop:
50
+ folder_name = split + '_kg' + '_one_hop_ratio' + str(mask_ratio)
51
+ else:
52
+ folder_name = split + '_kg'
53
+
54
+ if not os.path.exists(os.path.join(self.data_folder, folder_name)):
55
+ os.mkdir(os.path.join(self.data_folder, folder_name))
56
+ kg_path = os.path.join(self.data_folder, folder_name, 'kg_directed.csv')
57
+ else:
58
+ kg_path = os.path.join(self.data_folder, 'kg_directed.csv')
59
+
60
+ if os.path.exists(kg_path):
61
+ print('Found saved processed KG... Loading...')
62
+ df = pd.read_csv(kg_path)
63
+ else:
64
+ if os.path.exists(os.path.join(self.data_folder, 'kg.csv')):
65
+ print('First time usage... Mapping TxData raw KG to directed csv... it takes several minutes...')
66
+ preprocess_kg(self.data_folder, split, test_size,one_hop, mask_ratio)
67
+ df = pd.read_csv(kg_path)
68
+ else:
69
+ raise ValueError("KG file path does not exist...")
70
+
71
+ if split == 'disease_eval':
72
+ split_data_path = os.path.join(self.data_folder, self.split + '_' + str(disease_eval_idx))
73
+ elif split == 'downstream_pred':
74
+ split_data_path = os.path.join(self.data_folder, self.split + '_downstream_pred')
75
+ disease_eval_idx = [11394., 6353., 12696., 14183., 12895., 9128., 12623., 15129.,
76
+ 12897., 12860., 7611., 13113., 4029., 14906., 13438., 13177.,
77
+ 13335., 12896., 12879., 12909., 4815., 12766., 12653.]
78
+ elif no_kg:
79
+ split_data_path = os.path.join(self.data_folder, self.split + '_no_kg_' + str(seed))
80
+ elif test_size != 0.05:
81
+ split_data_path = os.path.join(self.data_folder, self.split + '_' + str(seed)) + '_frac' + str(test_size)
82
+ elif one_hop:
83
+ split_data_path = os.path.join(self.data_folder, self.split + '_' + str(seed)) + '_one_hop_ratio' + str(mask_ratio)
84
+ else:
85
+ split_data_path = os.path.join(self.data_folder, self.split + '_' + str(seed))
86
+
87
+ if no_kg:
88
+ sub_kg = ['off-label use', 'indication', 'contraindication']
89
+ df = df[df.relation.isin(sub_kg)].reset_index(drop = True)
90
+
91
+ if not os.path.exists(os.path.join(split_data_path, 'train.csv')):
92
+ if not os.path.exists(split_data_path):
93
+ os.mkdir(split_data_path)
94
+ print('Creating splits... it takes several minutes...')
95
+ df_train, df_valid, df_test = create_split(df, split, disease_eval_idx, split_data_path, seed)
96
+ else:
97
+ print('Splits detected... Loading splits....')
98
+ df_train = pd.read_csv(os.path.join(split_data_path, 'train.csv'))
99
+ df_valid = pd.read_csv(os.path.join(split_data_path, 'valid.csv'))
100
+ df_test = pd.read_csv(os.path.join(split_data_path, 'test.csv'))
101
+
102
+ # Safeguard: if loaded splits are missing rev_ edges, generate them
103
+ if not any(df_train.relation.str.startswith('rev_')):
104
+ print('Safeguard: generating missing rev_ relations for loaded splits...')
105
+ unique_rel = df[['x_type', 'relation', 'y_type']].drop_duplicates()
106
+ df_train = reverse_rel_generation(df, df_train, unique_rel)
107
+ df_valid = reverse_rel_generation(df, df_valid, unique_rel)
108
+ df_test = reverse_rel_generation(df, df_test, unique_rel)
109
+
110
+ if split not in ['random', 'complex_disease', 'complex_disease_cv', 'disease_eval', 'full_graph', 'downstream_pred', 'few_edeges_to_indications', 'few_edeges_to_kg']:
111
+ # in disease area split
112
+ df_test = process_disease_area_split(self.data_folder, df, df_test, split)
113
+
114
+ print('Creating DGL graph....')
115
+ # create dgl graph
116
+ g = create_dgl_graph(df_train, df)
117
+
118
+ self.G = g
119
+ self.df, self.df_train, self.df_valid, self.df_test = df, df_train, df_valid, df_test
120
+ self.disease_eval_idx = disease_eval_idx
121
+ self.no_kg = no_kg
122
+ self.seed = seed
123
+ print('Done!')
124
+
125
+
126
+ def retrieve_id_mapping(self):
127
+ df = self.df
128
+ df['x_id'] = df['x_id'].apply(lambda x: convert2str(x))
129
+ df['y_id'] = df['y_id'].apply(lambda x: convert2str(x))
130
+
131
+ idx2id_drug = dict(df[df.x_type == 'drug'][['x_idx', 'x_id']].drop_duplicates().values)
132
+ idx2id_drug.update(dict(df[df.y_type == 'drug'][['y_idx', 'y_id']].drop_duplicates().values))
133
+
134
+ idx2id_disease = dict(df[df.x_type == 'disease'][['x_idx', 'x_id']].drop_duplicates().values)
135
+ idx2id_disease.update(dict(df[df.y_type == 'disease'][['y_idx', 'y_id']].drop_duplicates().values))
136
+
137
+ # Only read columns needed for name lookups, and filter to drug/disease rows
138
+ # This avoids loading all 21.8M rows into memory (~6GB)
139
+ df_ = pd.read_csv(os.path.join(self.data_folder, 'kg.csv'),
140
+ usecols=['x_id', 'x_type', 'x_name', 'y_id', 'y_type', 'y_name'])
141
+ df_dd = df_[df_.x_type.isin(['drug', 'disease']) | df_.y_type.isin(['drug', 'disease'])].copy()
142
+
143
+ df_dd['x_id'] = df_dd['x_id'].apply(lambda x: convert2str(x))
144
+ df_dd['y_id'] = df_dd['y_id'].apply(lambda x: convert2str(x))
145
+
146
+ id2name_disease = dict(zip(df_dd.loc[df_dd.x_type == 'disease', 'x_id'],
147
+ df_dd.loc[df_dd.x_type == 'disease', 'x_name']))
148
+ id2name_disease.update(dict(zip(df_dd.loc[df_dd.y_type == 'disease', 'y_id'],
149
+ df_dd.loc[df_dd.y_type == 'disease', 'y_name'])))
150
+
151
+ id2name_drug = dict(zip(df_dd.loc[df_dd.x_type == 'drug', 'x_id'],
152
+ df_dd.loc[df_dd.x_type == 'drug', 'x_name']))
153
+ id2name_drug.update(dict(zip(df_dd.loc[df_dd.y_type == 'drug', 'y_id'],
154
+ df_dd.loc[df_dd.y_type == 'drug', 'y_name'])))
155
+
156
+ return {'id2name_drug': id2name_drug,
157
+ 'id2name_disease': id2name_disease,
158
+ 'idx2id_disease': idx2id_disease,
159
+ 'idx2id_drug': idx2id_drug
160
+ }
@@ -0,0 +1,42 @@
1
+ from .utils import *
2
+
3
+ class TxEval:
4
+
5
+ def __init__(self, model):
6
+ self.df, self.df_train, self.df_valid, self.df_test, self.data_folder, self.G, self.best_model, self.weight_bias_track, self.wandb = model.df, model.df_train, model.df_valid, model.df_test, model.data_folder, model.G, model.best_model, model.weight_bias_track, model.wandb
7
+ self.device = model.device
8
+ self.disease_rel_types = ['rev_contraindication', 'rev_indication', 'rev_off-label use']
9
+ self.split = model.split
10
+
11
+ def eval_disease_centric(self, disease_idxs, relation = None, save_result = False, show_plot = False, verbose = False, save_name = None, return_raw = False, simulate_random = True):
12
+ if self.split == 'full_graph':
13
+ # set only_prediction to True during full graph training
14
+ only_prediction = True
15
+ else:
16
+ only_prediction = False
17
+
18
+ if disease_idxs == 'test_set':
19
+ disease_idxs = None
20
+
21
+ self.out = disease_centric_evaluation(self.df, self.df_train, self.df_valid, self.df_test, self.data_folder, self.G, self.best_model,self.device, disease_idxs, relation, self.weight_bias_track, self.wandb, show_plot, verbose, return_raw, simulate_random, only_prediction)
22
+
23
+ if save_result:
24
+ import pickle, os
25
+ if save_name is None:
26
+ save_name = os.path.join(self.data_folder, 'disease_centric_eval.pkl')
27
+ with open(save_name, 'wb') as f:
28
+ pickle.dump(self.out, f)
29
+ return self.out
30
+
31
+ def retrieve_disease_idxs_test_set(self, relation):
32
+ relation = 'rev_' + relation
33
+ df_train_valid = pd.concat([self.df_train, self.df_valid])
34
+ df_dd = self.df_test[self.df_test.relation.isin(self.disease_rel_types)]
35
+ df_dd_train = df_train_valid[df_train_valid.relation.isin(self.disease_rel_types)]
36
+
37
+ df_rel_dd = df_dd[df_dd.relation == relation]
38
+ return df_rel_dd.x_idx.unique()
39
+
40
+
41
+ def retrieve_all_disease_idxs(self):
42
+ return np.unique(self.df[self.df.x_type == 'disease'].x_idx.unique().tolist() + self.df[self.df.y_type == 'disease'].y_idx.unique().tolist())