MASSA-Algorithm 1.0.0__tar.gz → 2.0.0__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.
Files changed (25) hide show
  1. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSA.py +60 -27
  2. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAargs.py +19 -4
  3. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAcluster.py +177 -15
  4. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAextraction.py +36 -5
  5. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAlogos.py +5 -1
  6. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAmoloutput.py +23 -4
  7. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAod.py +7 -6
  8. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAopen_files.py +1 -2
  9. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSApreparation.py +1 -4
  10. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAsplit.py +2 -0
  11. massa_algorithm-2.0.0/MASSA_Algorithm/pyMASSA.py +353 -0
  12. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm.egg-info/PKG-INFO +167 -166
  13. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm.egg-info/SOURCES.txt +1 -0
  14. massa_algorithm-1.0.0/README.md → massa_algorithm-2.0.0/PKG-INFO +46 -5
  15. massa_algorithm-1.0.0/PKG-INFO → massa_algorithm-2.0.0/README.md +139 -166
  16. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/setup.cfg +4 -4
  17. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/setup.py +2 -2
  18. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/LICENSE +0 -0
  19. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MANIFEST.in +0 -0
  20. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/MASSAdescriptors.py +0 -0
  21. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm/__init__.py +0 -0
  22. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm.egg-info/dependency_links.txt +0 -0
  23. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm.egg-info/entry_points.txt +0 -0
  24. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm.egg-info/requires.txt +0 -0
  25. {massa_algorithm-1.0.0 → massa_algorithm-2.0.0}/MASSA_Algorithm.egg-info/top_level.txt +0 -0
@@ -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) = MASSAargs.capture_args()
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
- arqlog = directoryfile_output+'/log.txt'
75
- writelog = open(arqlog, 'w')
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, writelog, drop_errors)
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
- # First clustering (HCA):
116
- # It performs HCA clustering without generating the dendrogram for the biological domain.
117
- leaves_cluster_bio, bio_hca, linkage_bio, cutoff_bio = MASSAcluster.hca_clusters(
118
- bio_pca, names, 'bio', directoryfile_output, extension_type, linkage_method)
119
- # It performs HCA clustering without generating the dendrogram for the physicochemical domain.
120
- leaves_cluster_phch, phch_hca, linkage_phch, cutoff_phch = MASSAcluster.hca_clusters(
121
- phch_pca, names, 'PhCh', directoryfile_output, extension_type, linkage_method)
122
- # It performs HCA clustering without generating the dendrogram for the structural domain.
123
- leaves_cluster_fp, fp_hca, linkage_fp, cutoff_fp = MASSAcluster.hca_clusters(
124
- fp_pca, names, 'FP', directoryfile_output, extension_type, linkage_method)
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
- writelog
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
- writelog
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
- writelog
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
- writelog
289
+ write_log
261
290
  )
262
- writelog.close()
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
- # Output management:
284
- # It adds, for each molecule, the values of the calculated properties, the identifications of each cluster and which set the molecule belongs to.
285
- MASSAmoloutput.output_mols(dataframe, file_output)
286
- print('Completed')
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.')
@@ -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='false', help=(
123
+ metavar='DROP_MOLECULES_WITH_ERRORS', default='true', help=(
124
124
  'Ignore chemistry errors, saving only molecules without any errors. '
125
- 'Default = \'false\'.'
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
- for n in range(2, 21): # Calculate the cost for each n cluster.
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', max_iter=300)
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[len(list_clus_costs)-1]
48
+ x2, y2 = 20, list_clus_costs[18]
43
49
 
44
50
  distances = []
45
- for i in range(len(list_clus_costs)):
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
- return (distances.index(max(distances)) + 2)
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
- '/Images/Distance_HCA_Images/Dist_HCA_biological.'+extension_type
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
- '/Images/Distance_HCA_Images/Dist_HCA_physicochemical.'+extension_type
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
- '/Images/Distance_HCA_Images/Dist_HCA_structural.'+extension_type
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
- '/Images/Dendrogram_HCA_Images/HCA_biological.'+extension_type)
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
- '/Images/Dendrogram_HCA_Images/HCA_physicochemical.'+extension_type)
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
- '/Images/Dendrogram_HCA_Images/HCA_structural.'+extension_type)
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(file)
299
- print('General:', n_clusters) # Print the number of clusters.
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 i in file:
8
+ for idx, f in enumerate(file):
9
9
  try:
10
- names.append(i.GetProp('_Name')) # Get name of molecules to a list
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[i.GetProp('_Name')] = i
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
- dataframe = pd.DataFrame({'molecules': pd.Series(dict_names)})
21
- return names, dataframe
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. 1.0.0
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, file_output):
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
- output_mols = dict_molecules.values()
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 output_mols:
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/Distance_HCA_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