MASSA-Algorithm 1.0.0__py3-none-any.whl → 2.0.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.
- MASSA_Algorithm/MASSA.py +60 -27
- MASSA_Algorithm/MASSAargs.py +19 -4
- MASSA_Algorithm/MASSAcluster.py +177 -15
- MASSA_Algorithm/MASSAextraction.py +36 -5
- MASSA_Algorithm/MASSAlogos.py +5 -1
- MASSA_Algorithm/MASSAmoloutput.py +23 -4
- MASSA_Algorithm/MASSAod.py +7 -6
- MASSA_Algorithm/MASSAopen_files.py +1 -2
- MASSA_Algorithm/MASSApreparation.py +1 -4
- MASSA_Algorithm/MASSAsplit.py +2 -0
- MASSA_Algorithm/pyMASSA.py +353 -0
- {massa_algorithm-1.0.0.dist-info → MASSA_Algorithm-2.0.0.dist-info}/METADATA +167 -166
- MASSA_Algorithm-2.0.0.dist-info/RECORD +19 -0
- {massa_algorithm-1.0.0.dist-info → MASSA_Algorithm-2.0.0.dist-info}/WHEEL +1 -1
- massa_algorithm-1.0.0.dist-info/RECORD +0 -18
- {massa_algorithm-1.0.0.dist-info/licenses → MASSA_Algorithm-2.0.0.dist-info}/LICENSE +0 -0
- {massa_algorithm-1.0.0.dist-info → MASSA_Algorithm-2.0.0.dist-info}/entry_points.txt +0 -0
- {massa_algorithm-1.0.0.dist-info → MASSA_Algorithm-2.0.0.dist-info}/top_level.txt +0 -0
MASSA_Algorithm/MASSA.py
CHANGED
|
@@ -8,6 +8,7 @@ from MASSA_Algorithm import MASSApreparation
|
|
|
8
8
|
from MASSA_Algorithm import MASSAcluster
|
|
9
9
|
from MASSA_Algorithm import MASSAsplit
|
|
10
10
|
from MASSA_Algorithm import MASSAmoloutput
|
|
11
|
+
import time
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def returns_zero(total, test, validation):
|
|
@@ -51,7 +52,7 @@ def main():
|
|
|
51
52
|
# Initializing from the command line.
|
|
52
53
|
# Print the program logo:
|
|
53
54
|
MASSAlogos.initial_print()
|
|
54
|
-
|
|
55
|
+
start_time = time.time()
|
|
55
56
|
# It captures command line arguments.
|
|
56
57
|
(file_input,
|
|
57
58
|
file_output,
|
|
@@ -67,18 +68,32 @@ def main():
|
|
|
67
68
|
linkage_method,
|
|
68
69
|
flag_dendrogram,
|
|
69
70
|
splitting_strategy,
|
|
70
|
-
drop_errors
|
|
71
|
+
drop_errors,
|
|
72
|
+
large_library) = MASSAargs.capture_args()
|
|
71
73
|
print('Initializing, wait...\n')
|
|
72
74
|
|
|
73
75
|
# Create log.txt file in directory:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
log_path = directoryfile_output+'/log.txt'
|
|
77
|
+
write_log = open(log_path, 'w')
|
|
76
78
|
|
|
77
79
|
# Initial file management:
|
|
78
|
-
# It creates the output directories.
|
|
79
|
-
MASSAod.output_directory(directoryfile_output)
|
|
80
80
|
# Read molecules.
|
|
81
|
-
mols = MASSAopen_files.read_molecules(file_input,
|
|
81
|
+
mols = MASSAopen_files.read_molecules(file_input, write_log, drop_errors)
|
|
82
|
+
|
|
83
|
+
# If auto detection is enable to select the initial cluster algorithm.
|
|
84
|
+
if large_library == 'auto':
|
|
85
|
+
if int(len(mols)) > 9999:
|
|
86
|
+
large_library = True
|
|
87
|
+
else:
|
|
88
|
+
large_library = False
|
|
89
|
+
|
|
90
|
+
if large_library == True:
|
|
91
|
+
# Deactivates the HCA dendrogram.
|
|
92
|
+
flag_dendrogram = False
|
|
93
|
+
|
|
94
|
+
# It creates the output directories.
|
|
95
|
+
MASSAod.output_directory(directoryfile_output, flag_dendrogram)
|
|
96
|
+
|
|
82
97
|
# Extracting the property names from the ".sdf" input file.
|
|
83
98
|
sdf_property_names = MASSAopen_files.get_sdf_property_names(mols)
|
|
84
99
|
# Structure 3D management - It adds hydrogens keeping 3D coordenates.
|
|
@@ -87,6 +102,7 @@ def main():
|
|
|
87
102
|
# Extraction properties from ".sdf":
|
|
88
103
|
# It extracts the names of the molecules and creates a name:molecule dictionary and a dataframe.
|
|
89
104
|
names, dataframe = MASSAextraction.name_extraction(mols_h)
|
|
105
|
+
|
|
90
106
|
# It defines a list of what biological activities are being extracted.
|
|
91
107
|
biological_activity = MASSAextraction.the_biological_handler(
|
|
92
108
|
sdf_property_names, number_bioact, bioactivities_as_args)
|
|
@@ -112,16 +128,28 @@ def main():
|
|
|
112
128
|
# PCA for the structural domain.
|
|
113
129
|
fp_pca = MASSApreparation.pca_maker(fp_matrix, npcs, svd_parameter)
|
|
114
130
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
if large_library == True:
|
|
132
|
+
# First clustering (MiniBatch-KMeans):
|
|
133
|
+
# It performs KMeans clustering for the biological domain.
|
|
134
|
+
bio_hca = MASSAcluster.kmeans_clusters(
|
|
135
|
+
bio_pca, names, 'bio', directoryfile_output, extension_type)
|
|
136
|
+
# It performs KMeans clustering for the physicochemical domain.
|
|
137
|
+
phch_hca = MASSAcluster.kmeans_clusters(
|
|
138
|
+
phch_pca, names, 'PhCh', directoryfile_output, extension_type)
|
|
139
|
+
# It performs KMeans clustering for the structural domain.
|
|
140
|
+
fp_hca = MASSAcluster.kmeans_clusters(
|
|
141
|
+
fp_pca, names, 'FP', directoryfile_output, extension_type)
|
|
142
|
+
else:
|
|
143
|
+
# First clustering (HCA):
|
|
144
|
+
# It performs HCA clustering without generating the dendrogram for the biological domain.
|
|
145
|
+
leaves_cluster_bio, bio_hca, linkage_bio, cutoff_bio = MASSAcluster.hca_clusters(
|
|
146
|
+
bio_pca, names, 'bio', directoryfile_output, extension_type, linkage_method)
|
|
147
|
+
# It performs HCA clustering without generating the dendrogram for the physicochemical domain.
|
|
148
|
+
leaves_cluster_phch, phch_hca, linkage_phch, cutoff_phch = MASSAcluster.hca_clusters(
|
|
149
|
+
phch_pca, names, 'PhCh', directoryfile_output, extension_type, linkage_method)
|
|
150
|
+
# It performs HCA clustering without generating the dendrogram for the structural domain.
|
|
151
|
+
leaves_cluster_fp, fp_hca, linkage_fp, cutoff_fp = MASSAcluster.hca_clusters(
|
|
152
|
+
fp_pca, names, 'FP', directoryfile_output, extension_type, linkage_method)
|
|
125
153
|
|
|
126
154
|
# It adds the biological cluster identification to the spreadsheet.
|
|
127
155
|
dataframe = MASSApreparation.organize_df_clusterization(
|
|
@@ -137,7 +165,8 @@ def main():
|
|
|
137
165
|
# It creates a matrix with cluster identifications for each of the three domains for Kmodes.
|
|
138
166
|
matrix_for_kmodes = MASSApreparation.organize_for_kmodes(dataframe)
|
|
139
167
|
# It performs Kmodes clustering for the general domain.
|
|
140
|
-
all_hca = MASSAcluster.kmodes_clusters(matrix_for_kmodes, names
|
|
168
|
+
all_hca = MASSAcluster.kmodes_clusters(matrix_for_kmodes, names,
|
|
169
|
+
directoryfile_output, extension_type)
|
|
141
170
|
# It adds the general cluster identification to the spreadsheet.
|
|
142
171
|
dataframe = MASSApreparation.organize_df_clusterization(
|
|
143
172
|
dataframe, all_hca, 'all')
|
|
@@ -239,27 +268,30 @@ def main():
|
|
|
239
268
|
MASSAsplit.log_of_distributions(
|
|
240
269
|
bio_distr, bio_total, bio_training,
|
|
241
270
|
bio_test, bio_val, splitting_strategy,
|
|
242
|
-
|
|
271
|
+
write_log
|
|
243
272
|
)
|
|
244
273
|
phch_distr = 'Physicochemical Distribution'
|
|
245
274
|
MASSAsplit.log_of_distributions(
|
|
246
275
|
phch_distr, phch_total, phch_training,
|
|
247
276
|
phch_test, phch_val, splitting_strategy,
|
|
248
|
-
|
|
277
|
+
write_log
|
|
249
278
|
)
|
|
250
279
|
fp_distr = 'Structural (FP) Distribution'
|
|
251
280
|
MASSAsplit.log_of_distributions(
|
|
252
281
|
fp_distr, fp_total, fp_training,
|
|
253
282
|
fp_test, fp_val, splitting_strategy,
|
|
254
|
-
|
|
283
|
+
write_log
|
|
255
284
|
)
|
|
256
285
|
all_distr = 'General Distribution'
|
|
257
286
|
MASSAsplit.log_of_distributions(
|
|
258
287
|
all_distr, all_total, all_training,
|
|
259
288
|
all_test, all_val, splitting_strategy,
|
|
260
|
-
|
|
289
|
+
write_log
|
|
261
290
|
)
|
|
262
|
-
|
|
291
|
+
|
|
292
|
+
# Output management:
|
|
293
|
+
# It adds, for each molecule, the values of the calculated properties, the identifications of each cluster and which set the molecule belongs to.
|
|
294
|
+
MASSAmoloutput.output_file_mols(dataframe, file_output)
|
|
263
295
|
|
|
264
296
|
# Plot HCAs:
|
|
265
297
|
if flag_dendrogram == True:
|
|
@@ -280,7 +312,8 @@ def main():
|
|
|
280
312
|
extension_type, dendrogram_xfont_size, test_or_val_molecules
|
|
281
313
|
) # fp_Plot: Plot the HCA dendrogram
|
|
282
314
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
315
|
+
end_time = time.time()
|
|
316
|
+
elapsed_time = end_time - start_time
|
|
317
|
+
write_log.write(f'Completed! Elapsed time = {elapsed_time} s.')
|
|
318
|
+
write_log.close()
|
|
319
|
+
print(f'Completed! Elapsed time = {elapsed_time} s.')
|
MASSA_Algorithm/MASSAargs.py
CHANGED
|
@@ -26,7 +26,7 @@ def capture_args():
|
|
|
26
26
|
'[-n <number_of_principal_components>] [-v <svd_parameter>] [-l <linkage_method] '
|
|
27
27
|
'[-t <extension_for_images>] [-d <dendrogram_font_size_on_Xaxis>] '
|
|
28
28
|
'[-x <frequency_bar_plot_font_size_on_Xaxis>] [-f <toggle_dendrogram>] '
|
|
29
|
-
'[-e <drop_molecules_with_errors]'
|
|
29
|
+
'[-e <drop_molecules_with_errors] [-a <large_dataset>]'
|
|
30
30
|
)
|
|
31
31
|
)
|
|
32
32
|
required = parser.add_argument_group('required arguments')
|
|
@@ -120,9 +120,17 @@ def capture_args():
|
|
|
120
120
|
)
|
|
121
121
|
)
|
|
122
122
|
optional.add_argument('-e', '--drop_errors', type=str, choices=['true', 'false'],
|
|
123
|
-
metavar='DROP_MOLECULES_WITH_ERRORS', default='
|
|
123
|
+
metavar='DROP_MOLECULES_WITH_ERRORS', default='true', help=(
|
|
124
124
|
'Ignore chemistry errors, saving only molecules without any errors. '
|
|
125
|
-
'
|
|
125
|
+
'False will treat molecule errors as fatal and fail the execution. '
|
|
126
|
+
'Default = \'true\'.'
|
|
127
|
+
))
|
|
128
|
+
optional.add_argument('-a', '--large_dataset', type=str, choices=['auto', 'true', 'false'],
|
|
129
|
+
metavar='LARGE_DATASET', default='auto', help=(
|
|
130
|
+
'Switch the MASSA algorithm from HCA to KMeans to handle large datasets. '
|
|
131
|
+
'Options = auto (switches to KMeans if ≥ 10,000 molecules), '
|
|
132
|
+
'false = use HCA (for smaller datasets), '
|
|
133
|
+
'true = use KMeans (for large datasets).'
|
|
126
134
|
))
|
|
127
135
|
args = parser.parse_args()
|
|
128
136
|
|
|
@@ -153,6 +161,12 @@ def capture_args():
|
|
|
153
161
|
flag_strategy = str(args.splitting_strategy).lower()
|
|
154
162
|
# It captures whether or not to ignore chemistry errors.
|
|
155
163
|
drop = str(args.drop_errors).lower()
|
|
164
|
+
# It captures whether or not is handling with a large dataset.
|
|
165
|
+
large_dataset = str(args.large_dataset).lower()
|
|
166
|
+
if large_dataset == 'true':
|
|
167
|
+
large_dataset = True
|
|
168
|
+
elif large_dataset == 'false':
|
|
169
|
+
large_dataset = False
|
|
156
170
|
|
|
157
171
|
if args.the_biological_activities != None:
|
|
158
172
|
# It creates a list with the names of the biological activities to be searched for.
|
|
@@ -210,5 +224,6 @@ def capture_args():
|
|
|
210
224
|
linkage_method,
|
|
211
225
|
flag_dendrogram,
|
|
212
226
|
flag_strategy,
|
|
213
|
-
drop
|
|
227
|
+
drop,
|
|
228
|
+
large_dataset
|
|
214
229
|
)
|
MASSA_Algorithm/MASSAcluster.py
CHANGED
|
@@ -3,6 +3,9 @@ from matplotlib import pyplot as plt
|
|
|
3
3
|
from matplotlib import colors as colrs
|
|
4
4
|
import scipy.cluster.hierarchy as sch
|
|
5
5
|
from kmodes import kmodes
|
|
6
|
+
from sklearn.cluster import MiniBatchKMeans as KMeans
|
|
7
|
+
from multiprocessing import cpu_count as mpi_count
|
|
8
|
+
from scipy.spatial.distance import cdist
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
def numerical_number_of_clusters(distances_list):
|
|
@@ -26,12 +29,15 @@ def numerical_number_of_clusters(distances_list):
|
|
|
26
29
|
return distances.index(max(distances)) + 1
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
def categorical_number_of_clusters(file):
|
|
32
|
+
def categorical_number_of_clusters(file, directory_fileoutput, extension_type):
|
|
30
33
|
# Elbow method using the clustering cost:
|
|
31
34
|
list_clus_costs = []
|
|
32
|
-
|
|
35
|
+
maxcluster = 31
|
|
36
|
+
ran_curve = [x for x in range(2, maxcluster, 2)]
|
|
37
|
+
for n in range(2, maxcluster): # Calculate the cost for each n cluster.
|
|
33
38
|
km = kmodes.KModes(n_clusters=n, verbose=0,
|
|
34
|
-
n_init=20, init='Cao',
|
|
39
|
+
n_init=20, init='Cao',
|
|
40
|
+
n_jobs=-1, max_iter=300)
|
|
35
41
|
km.fit(X=file)
|
|
36
42
|
# Costs = Clustering cost, defined as the sum distance of all points
|
|
37
43
|
# to their respective cluster centroids.
|
|
@@ -39,10 +45,10 @@ def categorical_number_of_clusters(file):
|
|
|
39
45
|
# Define the x-axis coordinates of the first and last points on the line.
|
|
40
46
|
x1, y1 = 2, list_clus_costs[0]
|
|
41
47
|
# Define the y-axis coordinates of the first and last points on the line.
|
|
42
|
-
x2, y2 = 20, list_clus_costs[
|
|
48
|
+
x2, y2 = 20, list_clus_costs[18]
|
|
43
49
|
|
|
44
50
|
distances = []
|
|
45
|
-
for i in range(
|
|
51
|
+
for i in range(19):
|
|
46
52
|
# Coordinate on the x-axis of the point to calculate the distance to the line.
|
|
47
53
|
x0 = i+2
|
|
48
54
|
# Coordinate on the y-axis of the point to calculate the distance to the line.
|
|
@@ -51,7 +57,52 @@ def categorical_number_of_clusters(file):
|
|
|
51
57
|
denominator = ((y2 - y1)**2 + (x2 - x1)**2)**(1/2)
|
|
52
58
|
# Distance between a point and a line.
|
|
53
59
|
distances.append(numerator/denominator)
|
|
54
|
-
|
|
60
|
+
number_of_clusters = (distances.index(max(distances)) + 2)
|
|
61
|
+
ident = "all"
|
|
62
|
+
plot_distortion(
|
|
63
|
+
list_clus_costs, directory_fileoutput,
|
|
64
|
+
ident, extension_type, number_of_clusters,
|
|
65
|
+
ran_curve)
|
|
66
|
+
return number_of_clusters
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def kmeans_number_of_clusters(file, ident, directory_fileoutput, extension_type):
|
|
70
|
+
# Elbow method using the clustering cost:
|
|
71
|
+
list_clus_costs = []
|
|
72
|
+
maxcluster = 31
|
|
73
|
+
ran_curve = [x for x in range(2, maxcluster, 2)]
|
|
74
|
+
for n in range(2, maxcluster): # Calculate the cost for each n cluster.
|
|
75
|
+
km = KMeans(n_clusters=n, verbose=0,
|
|
76
|
+
init='k-means++', max_iter=300,
|
|
77
|
+
batch_size=256*int(mpi_count()),
|
|
78
|
+
compute_labels=True, random_state=2025)
|
|
79
|
+
km.fit(X=file)
|
|
80
|
+
# Costs = Clustering cost, defined as the sum distance of all points
|
|
81
|
+
# to their respective cluster centroids.
|
|
82
|
+
list_clus_costs.append(
|
|
83
|
+
np.sum(np.min(cdist(file, km.cluster_centers_, 'euclidean'), axis=1)**2) / file.shape[0])
|
|
84
|
+
# Define the x-axis coordinates of the first and last points on the line.
|
|
85
|
+
x1, y1 = 2, list_clus_costs[0]
|
|
86
|
+
# Define the y-axis coordinates of the first and last points on the line.
|
|
87
|
+
x2, y2 = 20, list_clus_costs[18]
|
|
88
|
+
|
|
89
|
+
distances = []
|
|
90
|
+
for i in range(19):
|
|
91
|
+
# Coordinate on the x-axis of the point to calculate the distance to the line.
|
|
92
|
+
x0 = i+2
|
|
93
|
+
# Coordinate on the y-axis of the point to calculate the distance to the line.
|
|
94
|
+
y0 = list_clus_costs[i]
|
|
95
|
+
numerator = abs((y2-y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1)
|
|
96
|
+
denominator = ((y2 - y1)**2 + (x2 - x1)**2)**(1/2)
|
|
97
|
+
# Distance between a point and a line.
|
|
98
|
+
distances.append(numerator/denominator)
|
|
99
|
+
|
|
100
|
+
number_of_clusters = (distances.index(max(distances)) + 2)
|
|
101
|
+
plot_distortion(
|
|
102
|
+
list_clus_costs, directory_fileoutput,
|
|
103
|
+
ident, extension_type, number_of_clusters,
|
|
104
|
+
ran_curve)
|
|
105
|
+
return number_of_clusters
|
|
55
106
|
|
|
56
107
|
|
|
57
108
|
def optimal_threshold(file, ident, directory_fileoutput, extension_type, linkage):
|
|
@@ -69,7 +120,7 @@ def optimal_threshold(file, ident, directory_fileoutput, extension_type, linkage
|
|
|
69
120
|
if ident == 'bio':
|
|
70
121
|
# Define the name, directory and extension of the Euclidean distance graph.
|
|
71
122
|
name_euc_dist_file = directory_fileoutput + \
|
|
72
|
-
'
|
|
123
|
+
'Images/Distance_Images/Dist_HCA_biological.'+extension_type
|
|
73
124
|
title_distances = 'Plot of Euclidean Distances from Biological HCA'
|
|
74
125
|
print('Number of clusters:')
|
|
75
126
|
# Print the number of clusters for the biological domain.
|
|
@@ -80,7 +131,7 @@ def optimal_threshold(file, ident, directory_fileoutput, extension_type, linkage
|
|
|
80
131
|
elif ident == 'PhCh':
|
|
81
132
|
# Define the name, directory and extension of the Euclidean distance graph.
|
|
82
133
|
name_euc_dist_file = directory_fileoutput + \
|
|
83
|
-
'
|
|
134
|
+
'Images/Distance_Images/Dist_HCA_physicochemical.'+extension_type
|
|
84
135
|
title_distances = 'Plot of Euclidean Distances from Physicochemical HCA'
|
|
85
136
|
# Print the number of clusters for the physicochemical domain.
|
|
86
137
|
print('Physicochemical:', number_of_clusters)
|
|
@@ -90,7 +141,7 @@ def optimal_threshold(file, ident, directory_fileoutput, extension_type, linkage
|
|
|
90
141
|
else:
|
|
91
142
|
# Define the name, directory and extension of the Euclidean distance graph.
|
|
92
143
|
name_euc_dist_file = directory_fileoutput + \
|
|
93
|
-
'
|
|
144
|
+
'Images/Distance_Images/Dist_HCA_structural.'+extension_type
|
|
94
145
|
title_distances = 'Plot of Euclidean Distances from Structural HCA'
|
|
95
146
|
# Print the number of clusters for the structural domain.
|
|
96
147
|
print('Structural:', number_of_clusters)
|
|
@@ -279,24 +330,24 @@ def hca_plot(
|
|
|
279
330
|
if ident == 'bio':
|
|
280
331
|
plt.title('HCA of biological activity')
|
|
281
332
|
plt.savefig(directory_fileoutput +
|
|
282
|
-
'
|
|
333
|
+
'Images/Dendrogram_HCA_Images/HCA_biological.'+extension_type)
|
|
283
334
|
plt.close()
|
|
284
335
|
elif ident == 'PhCh':
|
|
285
336
|
plt.title('HCA of physicochemical properties')
|
|
286
337
|
plt.savefig(directory_fileoutput +
|
|
287
|
-
'
|
|
338
|
+
'Images/Dendrogram_HCA_Images/HCA_physicochemical.'+extension_type)
|
|
288
339
|
plt.close()
|
|
289
340
|
else:
|
|
290
341
|
plt.title('HCA of AtomPairs fingerprint')
|
|
291
342
|
plt.savefig(directory_fileoutput +
|
|
292
|
-
'
|
|
343
|
+
'Images/Dendrogram_HCA_Images/HCA_structural.'+extension_type)
|
|
293
344
|
plt.close()
|
|
294
345
|
|
|
295
346
|
|
|
296
|
-
def kmodes_clusters(file, names):
|
|
347
|
+
def kmodes_clusters(file, names, directory_fileoutput, extension_type):
|
|
297
348
|
# Calculate the number of clusters for the Kmodes:
|
|
298
|
-
n_clusters = categorical_number_of_clusters(
|
|
299
|
-
|
|
349
|
+
n_clusters = categorical_number_of_clusters(
|
|
350
|
+
file, directory_fileoutput, extension_type)
|
|
300
351
|
|
|
301
352
|
# Run Kmodes with the calculated number of clusters:
|
|
302
353
|
# Parameter setting.
|
|
@@ -311,3 +362,114 @@ def kmodes_clusters(file, names):
|
|
|
311
362
|
for i, a in zip(names, clusters_list):
|
|
312
363
|
HCA_General_dict[i] = int(a+1)
|
|
313
364
|
return HCA_General_dict
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def plot_distortion(
|
|
368
|
+
list_clus_costs, directory_fileoutput,
|
|
369
|
+
ident, extension_type, number_of_clusters,
|
|
370
|
+
ran_curve):
|
|
371
|
+
plt.plot(
|
|
372
|
+
list(range(2, len(list_clus_costs)+2)),
|
|
373
|
+
list_clus_costs, '-o',
|
|
374
|
+
color='black',
|
|
375
|
+
markersize=4, linewidth=1,
|
|
376
|
+
markeredgewidth=1
|
|
377
|
+
) # Initial setting of graph values and parameters.
|
|
378
|
+
if ident == 'bio':
|
|
379
|
+
# Define the name, directory and extension of the Distortion graph.
|
|
380
|
+
name_euc_dist_file = directory_fileoutput + \
|
|
381
|
+
'Images/Distance_Images/Distortion_biological.'+extension_type
|
|
382
|
+
title_distances = 'Plot of Distortion from Biological Space'
|
|
383
|
+
print('Number of clusters:')
|
|
384
|
+
# Print the number of clusters for the biological domain.
|
|
385
|
+
print('Biological:', number_of_clusters)
|
|
386
|
+
plt.ylabel('KMeans Distortion') # Set the y-axis label.
|
|
387
|
+
elif ident == 'PhCh':
|
|
388
|
+
# Define the name, directory and extension of the Distortion graph.
|
|
389
|
+
name_euc_dist_file = directory_fileoutput + \
|
|
390
|
+
'Images/Distance_Images/Distortion_physicochemical.'+extension_type
|
|
391
|
+
title_distances = 'Plot of Distortion from Physicochemical Space'
|
|
392
|
+
# Print the number of clusters for the physicochemical domain.
|
|
393
|
+
print('Physicochemical:', number_of_clusters)
|
|
394
|
+
plt.ylabel('KMeans Distortion') # Set the y-axis label.
|
|
395
|
+
elif ident == 'FP':
|
|
396
|
+
# Define the name, directory and extension of the Distortion graph.
|
|
397
|
+
name_euc_dist_file = directory_fileoutput + \
|
|
398
|
+
'Images/Distance_Images/Distortion_structural.'+extension_type
|
|
399
|
+
title_distances = 'Plot of Distortion from Structural Space'
|
|
400
|
+
# Print the number of clusters for the structural domain.
|
|
401
|
+
print('Structural:', number_of_clusters)
|
|
402
|
+
plt.ylabel('KMeans Distortion') # Set the y-axis label.
|
|
403
|
+
else:
|
|
404
|
+
# Define the name, directory and extension of the Distortion graph.
|
|
405
|
+
name_euc_dist_file = directory_fileoutput + \
|
|
406
|
+
'Images/Distance_Images/Cost_general.'+extension_type
|
|
407
|
+
title_distances = 'Cost Plot from General (KModes)'
|
|
408
|
+
# Print the number of clusters for the structural domain.
|
|
409
|
+
print('General:', number_of_clusters)
|
|
410
|
+
plt.ylabel('KModes Cost') # Set the y-axis label.
|
|
411
|
+
plt.title(title_distances) # Define the graph title.
|
|
412
|
+
plt.xticks(ran_curve) # Define the x-axis ticks.
|
|
413
|
+
plt.savefig(name_euc_dist_file) # Save figure.
|
|
414
|
+
plt.close() # Close plot.
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def kmeans_number_of_clusters(file, ident, directory_fileoutput, extension_type):
|
|
418
|
+
# Elbow method using the clustering cost:
|
|
419
|
+
list_clus_costs = []
|
|
420
|
+
maxcluster = 31
|
|
421
|
+
ran_curve = [x for x in range(2, maxcluster, 2)]
|
|
422
|
+
for n in range(2, maxcluster): # Calculate the cost for each n cluster.
|
|
423
|
+
km = KMeans(n_clusters=n, verbose=0,
|
|
424
|
+
init='k-means++', max_iter=300,
|
|
425
|
+
batch_size=256*int(mpi_count()),
|
|
426
|
+
compute_labels=True, random_state=2025)
|
|
427
|
+
km.fit(X=file)
|
|
428
|
+
# Costs = Clustering cost, defined as the sum distance of all points
|
|
429
|
+
# to their respective cluster centroids.
|
|
430
|
+
list_clus_costs.append(
|
|
431
|
+
np.sum(np.min(cdist(file, km.cluster_centers_, 'euclidean'), axis=1)**2) / file.shape[0])
|
|
432
|
+
# Define the x-axis coordinates of the first and last points on the line.
|
|
433
|
+
x1, y1 = 2, list_clus_costs[0]
|
|
434
|
+
# Define the y-axis coordinates of the first and last points on the line.
|
|
435
|
+
x2, y2 = 20, list_clus_costs[18]
|
|
436
|
+
|
|
437
|
+
distances = []
|
|
438
|
+
for i in range(19):
|
|
439
|
+
# Coordinate on the x-axis of the point to calculate the distance to the line.
|
|
440
|
+
x0 = i+2
|
|
441
|
+
# Coordinate on the y-axis of the point to calculate the distance to the line.
|
|
442
|
+
y0 = list_clus_costs[i]
|
|
443
|
+
numerator = abs((y2-y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1)
|
|
444
|
+
denominator = ((y2 - y1)**2 + (x2 - x1)**2)**(1/2)
|
|
445
|
+
# Distance between a point and a line.
|
|
446
|
+
distances.append(numerator/denominator)
|
|
447
|
+
|
|
448
|
+
number_of_clusters = (distances.index(max(distances)) + 2)
|
|
449
|
+
plot_distortion(
|
|
450
|
+
list_clus_costs, directory_fileoutput,
|
|
451
|
+
ident, extension_type, number_of_clusters,
|
|
452
|
+
ran_curve)
|
|
453
|
+
return number_of_clusters
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def kmeans_clusters(file, names, ident, directory_fileoutput, extension_type):
|
|
457
|
+
# Calculate the number of clusters for the Kmodes:
|
|
458
|
+
n_clusters = kmeans_number_of_clusters(
|
|
459
|
+
file, ident, directory_fileoutput, extension_type)
|
|
460
|
+
|
|
461
|
+
# Run Kmodes with the calculated number of clusters:
|
|
462
|
+
# Parameter setting.
|
|
463
|
+
km = KMeans(n_clusters=n_clusters, verbose=0,
|
|
464
|
+
init='k-means++', max_iter=300,
|
|
465
|
+
batch_size=256*int(mpi_count()),
|
|
466
|
+
compute_labels=True, random_state=2025)
|
|
467
|
+
# Run Kmodes for the dataset and return an array with cluster labels for each molecule.
|
|
468
|
+
clusters = km.fit_predict(file)
|
|
469
|
+
clusters_list = list(clusters) # Transform np.array into a list.
|
|
470
|
+
|
|
471
|
+
# Create a dictionary with the pair "molecule name": "General cluster ID":
|
|
472
|
+
kmeans_cluster_dict = {}
|
|
473
|
+
for i, a in zip(names, clusters_list):
|
|
474
|
+
kmeans_cluster_dict[i] = int(a+1)
|
|
475
|
+
return kmeans_cluster_dict
|
|
@@ -5,11 +5,11 @@ from rdkit.Chem import AllChem
|
|
|
5
5
|
def name_extraction(file):
|
|
6
6
|
names = []
|
|
7
7
|
dict_names = {}
|
|
8
|
-
for
|
|
8
|
+
for idx, f in enumerate(file):
|
|
9
9
|
try:
|
|
10
|
-
names.append(
|
|
10
|
+
names.append(f.GetProp('_Name')) # Get name of molecules to a list
|
|
11
11
|
# Get name of molecules to a dict
|
|
12
|
-
dict_names[
|
|
12
|
+
dict_names[idx] = [f.GetProp('_Name'), f]
|
|
13
13
|
except:
|
|
14
14
|
names.append(None)
|
|
15
15
|
if None in names: # If any molecule is unnamed it returns an error.
|
|
@@ -17,8 +17,39 @@ def name_extraction(file):
|
|
|
17
17
|
print('Closing...')
|
|
18
18
|
exit()
|
|
19
19
|
else:
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
# Dealing with duplicates:
|
|
21
|
+
duplicates = {}
|
|
22
|
+
for idx, value in dict_names.items():
|
|
23
|
+
name = value[0]
|
|
24
|
+
mol = value[1]
|
|
25
|
+
if name in duplicates.keys():
|
|
26
|
+
duplicates[name].append([idx, mol])
|
|
27
|
+
else:
|
|
28
|
+
duplicates[name] = [[idx, mol]]
|
|
29
|
+
|
|
30
|
+
final_names = []
|
|
31
|
+
final_names_dict = {}
|
|
32
|
+
for k, v in duplicates.items():
|
|
33
|
+
name = k
|
|
34
|
+
replicates = v
|
|
35
|
+
if len(replicates) > 1:
|
|
36
|
+
for r in replicates:
|
|
37
|
+
index = r[0]
|
|
38
|
+
molecule = r[1]
|
|
39
|
+
final_key = f"{name}_idx-_{index}"
|
|
40
|
+
molecule.SetProp("_Name", final_key)
|
|
41
|
+
final_names_dict[final_key] = molecule
|
|
42
|
+
final_names.append(final_key)
|
|
43
|
+
else:
|
|
44
|
+
for r in replicates:
|
|
45
|
+
index = r[0]
|
|
46
|
+
molecule = r[1]
|
|
47
|
+
final_key = name
|
|
48
|
+
molecule.SetProp("_Name", final_key)
|
|
49
|
+
final_names_dict[final_key] = molecule
|
|
50
|
+
final_names.append(final_key)
|
|
51
|
+
dataframe = pd.DataFrame({'molecules': pd.Series(final_names_dict)})
|
|
52
|
+
return final_names, dataframe
|
|
22
53
|
|
|
23
54
|
|
|
24
55
|
def the_biological_handler(sdf_property_names, numberBioAct, BioActAsArgs):
|
MASSA_Algorithm/MASSAlogos.py
CHANGED
|
@@ -20,7 +20,7 @@ def initial_print():
|
|
|
20
20
|
██░░░▓▓▓▓░░░░░░░▓▓▓▓░░░░░░░▓▓▓▓░░░░█
|
|
21
21
|
██░░░░▓▓▓▓▓▓░░░░░▓▓▓▓▓▓░░░░░▓▓▓▓▓▓░░░░█ MASSA Algorithm:
|
|
22
22
|
████░░░░░▓▓▓▓░░░░░░░▓▓▓▓░░░░░░░▓▓▓▓░░░░░█████ Molecular data set sampling — Training-Test Separation
|
|
23
|
-
█▓▓▓███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███▓▓▓█ Version: v.
|
|
23
|
+
█▓▓▓███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███▓▓▓█ Version: v. 2.0.0
|
|
24
24
|
█▒▓▓▒▒▒████░░░░░░░░░░░░░░░░░░░░░░░░░░░░████▒▒▓▓▒█
|
|
25
25
|
██▓▒▒▒▓▓▓▓██████████████████████████████▓▓▓▒▒▒▒▓█ Developed by: Gabriel Corrêa Veríssimo
|
|
26
26
|
██▒▒▒▓▓▒▒▒▒▓▓▒▒▒▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▓▓▒▒▒▓▓▒▒██ LICENSE: AGPL-3.0
|
|
@@ -28,3 +28,7 @@ def initial_print():
|
|
|
28
28
|
███▓▓▓▓▒▒▒▒▒▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒▓▓████
|
|
29
29
|
███████████████████████████████████
|
|
30
30
|
''')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def python_logo():
|
|
34
|
+
print("🍕 MASSA Algorithm v. 2.0.0: Molecular data set sampling")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from rdkit.Chem import AllChem
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
def output_mols(dataframe
|
|
4
|
+
def output_mols(dataframe):
|
|
5
5
|
"""
|
|
6
6
|
For each molecule, adds the values of the calculated properties,
|
|
7
7
|
the identifications of each cluster and which set the molecule belongs to.
|
|
@@ -9,7 +9,6 @@ def output_mols(dataframe, file_output):
|
|
|
9
9
|
|
|
10
10
|
Args:
|
|
11
11
|
dataframe (pd.DataFrame): The dataframe of molecules.
|
|
12
|
-
file_output (str): The path to the output file.
|
|
13
12
|
"""
|
|
14
13
|
# Creation of dictionaries with the name of the molecule:
|
|
15
14
|
dict_molecules = dict(
|
|
@@ -41,6 +40,8 @@ def output_mols(dataframe, file_output):
|
|
|
41
40
|
|
|
42
41
|
# Add the properties to molecules from the name:molecule dictionary:
|
|
43
42
|
for i in dict_molecules.keys():
|
|
43
|
+
name = dict_molecules[i].GetProp('_Name')
|
|
44
|
+
dict_molecules[i].SetProp("_Name", name)
|
|
44
45
|
dict_molecules[i].SetProp('Set', str(dict_set[i]))
|
|
45
46
|
dict_molecules[i].SetProp('HBA count', str(dic_hba[i]))
|
|
46
47
|
dict_molecules[i].SetProp('HBD count', str(dic_hbd[i]))
|
|
@@ -62,9 +63,27 @@ def output_mols(dataframe, file_output):
|
|
|
62
63
|
|
|
63
64
|
# Create a dict_values with just the molecules
|
|
64
65
|
# (the name of the molecule is already part of molecule object):
|
|
65
|
-
|
|
66
|
+
out_mols = list(dict_molecules.values())
|
|
67
|
+
return out_mols
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def output_file_mols(dataframe, file_output):
|
|
71
|
+
"""
|
|
72
|
+
For each molecule, adds the values of the calculated properties,
|
|
73
|
+
the identifications of each cluster and which set the molecule belongs to.
|
|
74
|
+
Also, it creates the final output .sdf file.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
dataframe (pd.DataFrame): The dataframe of molecules.
|
|
78
|
+
file_output (str): The path to the output file.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Recovers a list with just the molecules
|
|
82
|
+
# (the name of the molecule is already part of molecule object):
|
|
83
|
+
out_mols = output_mols(dataframe)
|
|
66
84
|
|
|
67
85
|
# Create output ".sdf" file:
|
|
68
86
|
with AllChem.SDWriter(file_output) as w:
|
|
69
|
-
for m in
|
|
87
|
+
for m in out_mols:
|
|
70
88
|
w.write(m)
|
|
89
|
+
return out_mols
|
MASSA_Algorithm/MASSAod.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
def output_directory(directory_file_output):
|
|
4
|
+
def output_directory(directory_file_output, flag_dendrogram):
|
|
5
5
|
"""
|
|
6
6
|
Creates the output directories.
|
|
7
7
|
"""
|
|
@@ -10,13 +10,14 @@ def output_directory(directory_file_output):
|
|
|
10
10
|
except FileExistsError:
|
|
11
11
|
pass
|
|
12
12
|
try:
|
|
13
|
-
os.makedirs(directory_file_output+'Images/
|
|
14
|
-
except FileExistsError:
|
|
15
|
-
pass
|
|
16
|
-
try:
|
|
17
|
-
os.makedirs(directory_file_output+'Images/Dendrogram_HCA_Images')
|
|
13
|
+
os.makedirs(directory_file_output+'Images/Distance_Images')
|
|
18
14
|
except FileExistsError:
|
|
19
15
|
pass
|
|
16
|
+
if flag_dendrogram == True:
|
|
17
|
+
try:
|
|
18
|
+
os.makedirs(directory_file_output+'Images/Dendrogram_HCA_Images')
|
|
19
|
+
except FileExistsError:
|
|
20
|
+
pass
|
|
20
21
|
try:
|
|
21
22
|
os.makedirs(directory_file_output+'Images/Distribution')
|
|
22
23
|
except FileExistsError:
|
|
@@ -94,7 +94,6 @@ def read_df_smiles(df, write_log, drop_errors):
|
|
|
94
94
|
smi_name_column = find_smiles_column(columns)
|
|
95
95
|
name_name_column = find_name_column(columns)
|
|
96
96
|
|
|
97
|
-
# name_name_column = "index"
|
|
98
97
|
columns.remove(smi_name_column)
|
|
99
98
|
columns.remove(name_name_column)
|
|
100
99
|
|
|
@@ -290,9 +289,9 @@ def read_molecules(file, write_log, drop_errors):
|
|
|
290
289
|
def hydrogen_add(file):
|
|
291
290
|
molsH = []
|
|
292
291
|
for i in file:
|
|
292
|
+
# Add hydrogens keeping 3D coordenates.
|
|
293
293
|
i = AllChem.AddHs(i, addCoords=True)
|
|
294
294
|
molsH.append(i)
|
|
295
|
-
# Add hydrogens keeping 3D coordenates.
|
|
296
295
|
return molsH
|
|
297
296
|
|
|
298
297
|
|