MASSA-Algorithm 1.0.0__tar.gz → 2.0.1__tar.gz
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-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSA.py +63 -27
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAargs.py +19 -4
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAcluster.py +177 -15
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAextraction.py +36 -5
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAlogos.py +5 -1
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAmoloutput.py +23 -4
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAod.py +7 -6
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAopen_files.py +15 -16
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSApreparation.py +1 -4
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAsplit.py +2 -0
- massa_algorithm-2.0.1/MASSA_Algorithm/pyMASSA.py +353 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1/MASSA_Algorithm.egg-info}/PKG-INFO +19 -4
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm.egg-info/SOURCES.txt +1 -0
- {massa_algorithm-1.0.0/MASSA_Algorithm.egg-info → massa_algorithm-2.0.1}/PKG-INFO +19 -4
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/README.md +17 -2
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/setup.py +2 -2
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/LICENSE +0 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MANIFEST.in +0 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/MASSAdescriptors.py +0 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm/__init__.py +0 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm.egg-info/dependency_links.txt +0 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm.egg-info/entry_points.txt +0 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm.egg-info/requires.txt +0 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/MASSA_Algorithm.egg-info/top_level.txt +0 -0
- {massa_algorithm-1.0.0 → massa_algorithm-2.0.1}/setup.cfg +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import time
|
|
1
2
|
from MASSA_Algorithm import MASSAlogos
|
|
2
3
|
from MASSA_Algorithm import MASSAargs
|
|
3
4
|
from MASSA_Algorithm import MASSAod
|
|
@@ -8,6 +9,8 @@ from MASSA_Algorithm import MASSApreparation
|
|
|
8
9
|
from MASSA_Algorithm import MASSAcluster
|
|
9
10
|
from MASSA_Algorithm import MASSAsplit
|
|
10
11
|
from MASSA_Algorithm import MASSAmoloutput
|
|
12
|
+
from joblib import parallel_backend
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
def returns_zero(total, test, validation):
|
|
@@ -48,10 +51,11 @@ def main():
|
|
|
48
51
|
Main subroutine, allows the program to run directly from
|
|
49
52
|
the command line when installed via pip.
|
|
50
53
|
"""
|
|
54
|
+
parallel_backend('multiprocessing')
|
|
51
55
|
# Initializing from the command line.
|
|
52
56
|
# Print the program logo:
|
|
53
57
|
MASSAlogos.initial_print()
|
|
54
|
-
|
|
58
|
+
start_time = time.time()
|
|
55
59
|
# It captures command line arguments.
|
|
56
60
|
(file_input,
|
|
57
61
|
file_output,
|
|
@@ -67,18 +71,32 @@ def main():
|
|
|
67
71
|
linkage_method,
|
|
68
72
|
flag_dendrogram,
|
|
69
73
|
splitting_strategy,
|
|
70
|
-
drop_errors
|
|
74
|
+
drop_errors,
|
|
75
|
+
large_library) = MASSAargs.capture_args()
|
|
71
76
|
print('Initializing, wait...\n')
|
|
72
77
|
|
|
73
78
|
# Create log.txt file in directory:
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
log_path = directoryfile_output+'/log.txt'
|
|
80
|
+
write_log = open(log_path, 'w')
|
|
76
81
|
|
|
77
82
|
# Initial file management:
|
|
78
|
-
# It creates the output directories.
|
|
79
|
-
MASSAod.output_directory(directoryfile_output)
|
|
80
83
|
# Read molecules.
|
|
81
|
-
mols = MASSAopen_files.read_molecules(file_input,
|
|
84
|
+
mols = MASSAopen_files.read_molecules(file_input, write_log, drop_errors)
|
|
85
|
+
|
|
86
|
+
# If auto detection is enable to select the initial cluster algorithm.
|
|
87
|
+
if large_library == 'auto':
|
|
88
|
+
if int(len(mols)) > 9999:
|
|
89
|
+
large_library = True
|
|
90
|
+
else:
|
|
91
|
+
large_library = False
|
|
92
|
+
|
|
93
|
+
if large_library == True:
|
|
94
|
+
# Deactivates the HCA dendrogram.
|
|
95
|
+
flag_dendrogram = False
|
|
96
|
+
|
|
97
|
+
# It creates the output directories.
|
|
98
|
+
MASSAod.output_directory(directoryfile_output, flag_dendrogram)
|
|
99
|
+
|
|
82
100
|
# Extracting the property names from the ".sdf" input file.
|
|
83
101
|
sdf_property_names = MASSAopen_files.get_sdf_property_names(mols)
|
|
84
102
|
# Structure 3D management - It adds hydrogens keeping 3D coordenates.
|
|
@@ -87,6 +105,7 @@ def main():
|
|
|
87
105
|
# Extraction properties from ".sdf":
|
|
88
106
|
# It extracts the names of the molecules and creates a name:molecule dictionary and a dataframe.
|
|
89
107
|
names, dataframe = MASSAextraction.name_extraction(mols_h)
|
|
108
|
+
|
|
90
109
|
# It defines a list of what biological activities are being extracted.
|
|
91
110
|
biological_activity = MASSAextraction.the_biological_handler(
|
|
92
111
|
sdf_property_names, number_bioact, bioactivities_as_args)
|
|
@@ -112,16 +131,28 @@ def main():
|
|
|
112
131
|
# PCA for the structural domain.
|
|
113
132
|
fp_pca = MASSApreparation.pca_maker(fp_matrix, npcs, svd_parameter)
|
|
114
133
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
if large_library == True:
|
|
135
|
+
# First clustering (MiniBatch-KMeans):
|
|
136
|
+
# It performs KMeans clustering for the biological domain.
|
|
137
|
+
bio_hca = MASSAcluster.kmeans_clusters(
|
|
138
|
+
bio_pca, names, 'bio', directoryfile_output, extension_type)
|
|
139
|
+
# It performs KMeans clustering for the physicochemical domain.
|
|
140
|
+
phch_hca = MASSAcluster.kmeans_clusters(
|
|
141
|
+
phch_pca, names, 'PhCh', directoryfile_output, extension_type)
|
|
142
|
+
# It performs KMeans clustering for the structural domain.
|
|
143
|
+
fp_hca = MASSAcluster.kmeans_clusters(
|
|
144
|
+
fp_pca, names, 'FP', directoryfile_output, extension_type)
|
|
145
|
+
else:
|
|
146
|
+
# First clustering (HCA):
|
|
147
|
+
# It performs HCA clustering without generating the dendrogram for the biological domain.
|
|
148
|
+
leaves_cluster_bio, bio_hca, linkage_bio, cutoff_bio = MASSAcluster.hca_clusters(
|
|
149
|
+
bio_pca, names, 'bio', directoryfile_output, extension_type, linkage_method)
|
|
150
|
+
# It performs HCA clustering without generating the dendrogram for the physicochemical domain.
|
|
151
|
+
leaves_cluster_phch, phch_hca, linkage_phch, cutoff_phch = MASSAcluster.hca_clusters(
|
|
152
|
+
phch_pca, names, 'PhCh', directoryfile_output, extension_type, linkage_method)
|
|
153
|
+
# It performs HCA clustering without generating the dendrogram for the structural domain.
|
|
154
|
+
leaves_cluster_fp, fp_hca, linkage_fp, cutoff_fp = MASSAcluster.hca_clusters(
|
|
155
|
+
fp_pca, names, 'FP', directoryfile_output, extension_type, linkage_method)
|
|
125
156
|
|
|
126
157
|
# It adds the biological cluster identification to the spreadsheet.
|
|
127
158
|
dataframe = MASSApreparation.organize_df_clusterization(
|
|
@@ -137,7 +168,8 @@ def main():
|
|
|
137
168
|
# It creates a matrix with cluster identifications for each of the three domains for Kmodes.
|
|
138
169
|
matrix_for_kmodes = MASSApreparation.organize_for_kmodes(dataframe)
|
|
139
170
|
# It performs Kmodes clustering for the general domain.
|
|
140
|
-
all_hca = MASSAcluster.kmodes_clusters(matrix_for_kmodes, names
|
|
171
|
+
all_hca = MASSAcluster.kmodes_clusters(matrix_for_kmodes, names,
|
|
172
|
+
directoryfile_output, extension_type)
|
|
141
173
|
# It adds the general cluster identification to the spreadsheet.
|
|
142
174
|
dataframe = MASSApreparation.organize_df_clusterization(
|
|
143
175
|
dataframe, all_hca, 'all')
|
|
@@ -239,27 +271,30 @@ def main():
|
|
|
239
271
|
MASSAsplit.log_of_distributions(
|
|
240
272
|
bio_distr, bio_total, bio_training,
|
|
241
273
|
bio_test, bio_val, splitting_strategy,
|
|
242
|
-
|
|
274
|
+
write_log
|
|
243
275
|
)
|
|
244
276
|
phch_distr = 'Physicochemical Distribution'
|
|
245
277
|
MASSAsplit.log_of_distributions(
|
|
246
278
|
phch_distr, phch_total, phch_training,
|
|
247
279
|
phch_test, phch_val, splitting_strategy,
|
|
248
|
-
|
|
280
|
+
write_log
|
|
249
281
|
)
|
|
250
282
|
fp_distr = 'Structural (FP) Distribution'
|
|
251
283
|
MASSAsplit.log_of_distributions(
|
|
252
284
|
fp_distr, fp_total, fp_training,
|
|
253
285
|
fp_test, fp_val, splitting_strategy,
|
|
254
|
-
|
|
286
|
+
write_log
|
|
255
287
|
)
|
|
256
288
|
all_distr = 'General Distribution'
|
|
257
289
|
MASSAsplit.log_of_distributions(
|
|
258
290
|
all_distr, all_total, all_training,
|
|
259
291
|
all_test, all_val, splitting_strategy,
|
|
260
|
-
|
|
292
|
+
write_log
|
|
261
293
|
)
|
|
262
|
-
|
|
294
|
+
|
|
295
|
+
# Output management:
|
|
296
|
+
# It adds, for each molecule, the values of the calculated properties, the identifications of each cluster and which set the molecule belongs to.
|
|
297
|
+
MASSAmoloutput.output_file_mols(dataframe, file_output)
|
|
263
298
|
|
|
264
299
|
# Plot HCAs:
|
|
265
300
|
if flag_dendrogram == True:
|
|
@@ -280,7 +315,8 @@ def main():
|
|
|
280
315
|
extension_type, dendrogram_xfont_size, test_or_val_molecules
|
|
281
316
|
) # fp_Plot: Plot the HCA dendrogram
|
|
282
317
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
318
|
+
end_time = time.time()
|
|
319
|
+
elapsed_time = end_time - start_time
|
|
320
|
+
write_log.write(f'Completed! Elapsed time = {elapsed_time} s.')
|
|
321
|
+
write_log.close()
|
|
322
|
+
print(f'Completed! Elapsed time = {elapsed_time} s.')
|
|
@@ -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
|
)
|
|
@@ -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):
|
|
@@ -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.1
|
|
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.1: 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
|
|
@@ -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:
|