flexynesis 0.2.3__tar.gz → 0.2.5__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 (32) hide show
  1. {flexynesis-0.2.3 → flexynesis-0.2.5}/PKG-INFO +2 -2
  2. {flexynesis-0.2.3 → flexynesis-0.2.5}/README.md +1 -1
  3. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/__main__.py +80 -45
  4. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/data.py +29 -18
  5. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/utils.py +116 -44
  6. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis.egg-info/PKG-INFO +2 -2
  7. {flexynesis-0.2.3 → flexynesis-0.2.5}/pyproject.toml +1 -1
  8. {flexynesis-0.2.3 → flexynesis-0.2.5}/LICENCE.md +0 -0
  9. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/__init__.py +0 -0
  10. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/cli.py +0 -0
  11. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/config.py +0 -0
  12. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/feature_selection.py +0 -0
  13. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/main.py +0 -0
  14. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/__init__.py +0 -0
  15. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/crossmodal_pred.py +0 -0
  16. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/direct_pred.py +0 -0
  17. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/gnn_early.py +0 -0
  18. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/on_ice/direct_pred_cnn.py +0 -0
  19. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/on_ice/direct_pred_gcnn.py +0 -0
  20. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/on_ice/modules_on_ice.py +0 -0
  21. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/supervised_vae.py +0 -0
  22. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/models/triplet_encoder.py +0 -0
  23. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis/modules.py +0 -0
  24. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis.egg-info/SOURCES.txt +0 -0
  25. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis.egg-info/dependency_links.txt +0 -0
  26. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis.egg-info/entry_points.txt +0 -0
  27. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis.egg-info/requires.txt +0 -0
  28. {flexynesis-0.2.3 → flexynesis-0.2.5}/flexynesis.egg-info/top_level.txt +0 -0
  29. {flexynesis-0.2.3 → flexynesis-0.2.5}/setup.cfg +0 -0
  30. {flexynesis-0.2.3 → flexynesis-0.2.5}/tests/__init__.py +0 -0
  31. {flexynesis-0.2.3 → flexynesis-0.2.5}/tests/unit/__init__.py +0 -0
  32. {flexynesis-0.2.3 → flexynesis-0.2.5}/tests/unit/test_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flexynesis
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: A deep-learning based multi-omics bulk sequencing data integration suite with a focus on (pre-)clinical endpoint prediction.
5
5
  Author-email: Bora Uyar <bora.uyar@mdc-berlin.de>, Taras Savchyn <Taras.Savchyn@mdc-berlin.de>, Ricardo Wurmus <Ricardo.Wurmus@mdc-berlin.de>, Ahmet Sarigun <Ahmet.Sariguen@mdc-berlin.de>
6
6
  Project-URL: homepage, https://github.com/BIMSBbioinfo/flexynesis
@@ -113,7 +113,7 @@ flexynesis --data_path dataset1 \
113
113
  --prefix erlotinib_direct \
114
114
  --early_stop_patience 3 \
115
115
  --use_loss_weighting False \
116
- --evaluate_baseline_performance False
116
+ --evaluate_baseline_performance
117
117
  ```
118
118
 
119
119
  ## Accelerating with GPUs
@@ -77,7 +77,7 @@ flexynesis --data_path dataset1 \
77
77
  --prefix erlotinib_direct \
78
78
  --early_stop_patience 3 \
79
79
  --use_loss_weighting False \
80
- --evaluate_baseline_performance False
80
+ --evaluate_baseline_performance
81
81
  ```
82
82
 
83
83
  ## Accelerating with GPUs
@@ -1,7 +1,7 @@
1
1
  from lightning import seed_everything
2
2
  import lightning as pl
3
3
  from typing import NamedTuple
4
- import os, yaml, torch, time, random, warnings, argparse
4
+ import os, yaml, torch, time, random, warnings, argparse, sys
5
5
  os.environ["OMP_NUM_THREADS"] = "1"
6
6
  import pandas as pd
7
7
  import flexynesis
@@ -18,7 +18,7 @@ def main():
18
18
 
19
19
  Args:
20
20
  --data_path (str): Path to the folder with train/test data files. (Required)
21
- --model_class (str): The kind of model class to instantiate. Choices are ["DirectPred", "GNN", "supervised_vae", "MultiTripletNetwork", "CrossModalPred"]. (Required)
21
+ --model_class (str): The kind of model class to instantiate. Choices are ["DirectPred", "GNN", "supervised_vae", "MultiTripletNetwork", "CrossModalPred", "RandomForest", "SVM", "RandomSurvivalForest"]. (Required)
22
22
  --gnn_conv_type (str): If model_class is set to GNN, choose which graph convolution type to use. Choices are ["GC", "GCN", "SAGE"].
23
23
  --target_variables (str): Which variables in 'clin.csv' to use for predictions, comma-separated if multiple. Optional if survival variables are not set to None.
24
24
  --batch_variables (str): Which variables in 'clin.csv' to use for data integration/batch correction, comma-separated if multiple. Optional.
@@ -44,7 +44,7 @@ def main():
44
44
  --hpo_patience (int): How many hyperparameter optimisation iterations to wait for when no improvements are observed. Default is 10; set to 0 to disable early stopping.
45
45
  --use_cv (bool): If set, a 5-fold cross-validation training will be done. Otherwise, a single training on 80 percent of the dataset is done.
46
46
  --use_loss_weighting (str): Whether to apply loss-balancing using uncertainty weights method. Choices are ['True', 'False']. Default is 'True'.
47
- --evaluate_baseline_performance (str): Whether to run Random Forest + SVMs to see the performance of off-the-shelf tools on the same dataset. Choices are ['True', 'False']. Default is 'True'.
47
+ --evaluate_baseline_performance (bool): Enables modeling also with Random Forest + SVMs to see the performance of off-the-shelf tools on the same dataset.
48
48
  --threads (int): How many threads to use when using CPU. Default is 4.
49
49
  --num_workers (int): How many workers to use for model training. Default is 2
50
50
  --use_gpu (bool): If set, the system will attempt to use CUDA/GPU if available.
@@ -57,7 +57,7 @@ def main():
57
57
 
58
58
  parser.add_argument("--data_path", help="(Required) Path to the folder with train/test data files", type=str, required = True)
59
59
  parser.add_argument("--model_class", help="(Required) The kind of model class to instantiate", type=str,
60
- choices=["DirectPred", "supervised_vae", "MultiTripletNetwork", "CrossModalPred", "GNN"], required = True)
60
+ choices=["DirectPred", "supervised_vae", "MultiTripletNetwork", "CrossModalPred", "GNN", "RandomForest", "SVM", "RandomSurvivalForest"], required = True)
61
61
  parser.add_argument("--gnn_conv_type", help="If model_class is set to GNN, choose which graph convolution type to use", type=str,
62
62
  choices=["GC", "GCN", "SAGE"])
63
63
  parser.add_argument("--target_variables",
@@ -98,7 +98,8 @@ def main():
98
98
  parser.add_argument("--use_cv", action="store_true",
99
99
  help="(Optional) If set, the a 5-fold cross-validation training will be done. Otherwise, a single trainig on 80 percent of the dataset is done.")
100
100
  parser.add_argument("--use_loss_weighting", help="whether to apply loss-balancing using uncertainty weights method", type=str, choices=['True', 'False'], default = 'True')
101
- parser.add_argument("--evaluate_baseline_performance", help="whether to run Random Forest + SVMs to see the performance of off-the-shelf tools on the same dataset", type=str, choices=['True', 'False'], default = 'True')
101
+ parser.add_argument("--evaluate_baseline_performance", action="store_true",
102
+ help="whether to run Random Forest + SVMs to see the performance of off-the-shelf tools on the same dataset")
102
103
  parser.add_argument("--threads", help="(Optional) How many threads to use when using CPU (default is 4)", type=int, default = 4)
103
104
  parser.add_argument("--num_workers", help="(Optional) How many workers to use for model training (default is 2)", type=int, default = 2)
104
105
  parser.add_argument("--use_gpu", action="store_true",
@@ -140,12 +141,10 @@ def main():
140
141
  "`srun --gpus=1 --pty flexynesis <rest of your_command>` !!!\n\n"]))
141
142
  time.sleep(3) #wait a bit to capture user's attention to the warning
142
143
  device_type = 'cpu'
143
- torch.set_num_threads(args.threads)
144
144
  else:
145
145
  device_type = 'gpu'
146
146
  else:
147
147
  device_type = 'cpu'
148
- torch.set_num_threads(args.threads)
149
148
 
150
149
  # 5. check GNN arguments
151
150
  if args.model_class == 'GNN':
@@ -186,20 +185,24 @@ def main():
186
185
  if not os.path.exists(args.outdir):
187
186
  raise FileNotFoundError(f"Path to --outdir doesn't exist at:", {args.outdir})
188
187
 
189
- class AvailableModels(NamedTuple):
190
- # type AvailableModel = ModelClass: Type, ModelConfig: str
191
- DirectPred: tuple[DirectPred, str] = DirectPred, "DirectPred"
192
- supervised_vae: tuple[supervised_vae, str] = supervised_vae, "supervised_vae"
193
- MultiTripletNetwork: tuple[MultiTripletNetwork, str] = MultiTripletNetwork, "MultiTripletNetwork"
194
- CrossModalPred: tuple[CrossModalPred, str] = CrossModalPred, "CrossModalPred"
195
- GNN: tuple[GNN, str] = GNN, "GNN"
196
-
197
- available_models = AvailableModels()
198
- model_class = getattr(available_models, args.model_class, None)
199
- if model_class is None:
200
- raise ValueError(f"Invalid model_class: {args.model_class}")
201
- else:
202
- model_class, config_name = model_class
188
+ available_models = {
189
+ "DirectPred": (DirectPred, "DirectPred"),
190
+ "supervised_vae": (supervised_vae, "supervised_vae"),
191
+ "MultiTripletNetwork": (MultiTripletNetwork, "MultiTripletNetwork"),
192
+ "CrossModalPred": (CrossModalPred, "CrossModalPred"),
193
+ "GNN": (GNN, "GNN"),
194
+ "RandomForest": ("RandomForest", None),
195
+ "SVM": ("SVM", None),
196
+ "RandomSurvivalForest": ("RandomSurvivalForest", None)
197
+ }
198
+
199
+ model_info = available_models.get(args.model_class)
200
+
201
+ if model_info is None:
202
+ raise ValueError(f"Unsupported model class {args.model_class}")
203
+
204
+ # Unpack the tuple into model class and config name
205
+ model_class, config_name = model_info
203
206
 
204
207
  # import assays and labels
205
208
  inputDir = args.data_path
@@ -221,6 +224,34 @@ def main():
221
224
  downsample = args.subsample)
222
225
  train_dataset, test_dataset = data_importer.import_data()
223
226
 
227
+ if args.model_class in ["RandomForest", "SVM"]:
228
+ if args.target_variables:
229
+ var = args.target_variables.strip().split(',')[0]
230
+ print(f"Training {args.model_class} on variable: {var}")
231
+ metrics = flexynesis.evaluate_baseline_performance(train_dataset, test_dataset, variable_name=var,
232
+ methods=[args.model_class], n_folds=5, n_jobs=args.threads)
233
+ metrics.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'stats.csv'])), header=True, index=False)
234
+ print(f"{args.model_class} evaluation complete. Results saved.")
235
+ # we skip everything related to deep learning models here
236
+ sys.exit(0)
237
+ else:
238
+ raise ValueError(f"At least one target variable is required to run RandomForest/SVM models. Set --target_variables argument")
239
+
240
+ if args.model_class == "RandomSurvivalForest":
241
+ if args.surv_event_var and args.surv_time_var:
242
+ print(f"Training {args.model_class} on survival variables: {args.surv_event_var} and {args.surv_time_var}")
243
+ metrics = flexynesis.evaluate_baseline_survival_performance(train_dataset, test_dataset,
244
+ args.surv_time_var,
245
+ args.surv_event_var,
246
+ n_folds = 5,
247
+ n_jobs = int(args.threads))
248
+ metrics.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'stats.csv'])), header=True, index=False)
249
+ print(f"{args.model_class} evaluation complete. Results saved.")
250
+ # we skip everything related to deep learning models here
251
+ sys.exit(0)
252
+ else:
253
+ raise ValueError(f"Missing survival variables. Set --surv_event_var --surv_time_var arguments")
254
+
224
255
  if args.model_class == 'GNN':
225
256
  # overlay datasets with network info
226
257
  # this is a temporary solution
@@ -282,20 +313,16 @@ def main():
282
313
  # update the test dataset to exclude finetuning samples
283
314
  test_dataset = holdout_dataset
284
315
 
316
+ # get sample embeddings and save
317
+ print("[INFO] Extracting sample embeddings")
318
+ embeddings_train = model.transform(train_dataset)
319
+ embeddings_test = model.transform(test_dataset)
320
+
321
+ embeddings_train.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'embeddings_train.csv'])), header=True)
322
+ embeddings_test.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'embeddings_test.csv'])), header=True)
323
+
285
324
  # evaluate predictions; (if any supervised learning happened)
286
325
  if any([args.target_variables, args.surv_event_var, args.batch_variables]):
287
- print("[INFO] Computing model evaluation metrics")
288
- metrics_df = flexynesis.evaluate_wrapper(model.predict(test_dataset), test_dataset,
289
- surv_event_var=model.surv_event_var,
290
- surv_time_var=model.surv_time_var)
291
- metrics_df.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'stats.csv'])), header=True, index=False)
292
-
293
- # print known/predicted labels
294
- predicted_labels = pd.concat([flexynesis.get_predicted_labels(model.predict(train_dataset), train_dataset, 'train'),
295
- flexynesis.get_predicted_labels(model.predict(test_dataset), test_dataset, 'test')],
296
- ignore_index=True)
297
- predicted_labels.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'predicted_labels.csv'])), header=True, index=False)
298
-
299
326
  if not args.disable_marker_finding: # unless marker discovery is disabled
300
327
  # compute feature importance values
301
328
  print("[INFO] Computing variable importance scores")
@@ -305,14 +332,19 @@ def main():
305
332
  ignore_index = True)
306
333
  df_imp.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'feature_importance.csv'])), header=True, index=False)
307
334
 
308
- # get sample embeddings and save
309
- print("[INFO] Extracting sample embeddings")
310
- embeddings_train = model.transform(train_dataset)
311
- embeddings_test = model.transform(test_dataset)
312
-
313
- embeddings_train.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'embeddings_train.csv'])), header=True)
314
- embeddings_test.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'embeddings_test.csv'])), header=True)
315
-
335
+ # print known/predicted labels
336
+ predicted_labels = pd.concat([flexynesis.get_predicted_labels(model.predict(train_dataset), train_dataset, 'train'),
337
+ flexynesis.get_predicted_labels(model.predict(test_dataset), test_dataset, 'test')],
338
+ ignore_index=True)
339
+ predicted_labels.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'predicted_labels.csv'])), header=True, index=False)
340
+
341
+ print("[INFO] Computing model evaluation metrics")
342
+ metrics_df = flexynesis.evaluate_wrapper(args.model_class, model.predict(test_dataset), test_dataset,
343
+ surv_event_var=model.surv_event_var,
344
+ surv_time_var=model.surv_time_var)
345
+ metrics_df.to_csv(os.path.join(args.outdir, '.'.join([args.prefix, 'stats.csv'])), header=True, index=False)
346
+
347
+
316
348
  # also filter embeddings to remove batch-associated dims and only keep target-variable associated dims
317
349
  if args.batch_variables is not None:
318
350
  print("[INFO] Printing filtered embeddings")
@@ -339,7 +371,7 @@ def main():
339
371
 
340
372
 
341
373
  # evaluate off-the-shelf methods on the main target variable
342
- if args.evaluate_baseline_performance == 'True':
374
+ if args.evaluate_baseline_performance:
343
375
  print("[INFO] Computing off-the-shelf method performance on first target variable:",model.target_variables[0])
344
376
  var = model.target_variables[0]
345
377
  metrics = pd.DataFrame()
@@ -351,9 +383,10 @@ def main():
351
383
 
352
384
  if var != model.surv_event_var:
353
385
  metrics = flexynesis.evaluate_baseline_performance(train, test,
354
- variable_name = var,
355
- n_folds=5,
356
- n_jobs = int(args.threads))
386
+ variable_name = var,
387
+ methods = ['RandomForest', 'SVM'],
388
+ n_folds = 5,
389
+ n_jobs = int(args.threads))
357
390
  if model.surv_event_var and model.surv_time_var:
358
391
  print("[INFO] Computing off-the-shelf method performance on survival variable:",model.surv_time_var)
359
392
  metrics_baseline_survival = flexynesis.evaluate_baseline_survival_performance(train, test,
@@ -369,5 +402,7 @@ def main():
369
402
  # save the trained model in file
370
403
  torch.save(model, os.path.join(args.outdir, '.'.join([args.prefix, 'final_model.pth'])))
371
404
 
405
+
372
406
  if __name__ == "__main__":
373
407
  main()
408
+ print("[INFO] Finished the analysis!")
@@ -627,13 +627,14 @@ class TripletMultiOmicDataset(Dataset):
627
627
  for label in labels_set}
628
628
  return labels_set, label_to_indices
629
629
 
630
+
630
631
  class MultiOmicDatasetNW(Dataset):
631
632
  def __init__(self, multiomic_dataset, interaction_df):
632
633
  self.multiomic_dataset = multiomic_dataset
633
634
  self.interaction_df = interaction_df
634
635
 
635
- # Precompute common features and edge index
636
- self.common_features = self.find_common_features()
636
+ # Compute union of features in the data matrices that also appear in the network
637
+ self.common_features = self.find_union_features()
637
638
  self.gene_to_index = {gene: idx for idx, gene in enumerate(self.common_features)}
638
639
  self.edge_index = self.create_edge_index()
639
640
  self.samples = self.multiomic_dataset.samples
@@ -647,38 +648,48 @@ class MultiOmicDatasetNW(Dataset):
647
648
  # Store labels for all samples
648
649
  self.labels = {target_name: labels for target_name, labels in self.multiomic_dataset.ann.items()}
649
650
 
650
- def find_common_features(self):
651
- common_features = set.intersection(*(set(features) for features in self.multiomic_dataset.features.values()))
651
+ def find_union_features(self):
652
+ # Find the union of all features in the multiomic dataset
653
+ all_omic_features = set().union(*(set(features) for features in self.multiomic_dataset.features.values()))
654
+ # Find the union of proteins involved in interactions
652
655
  interaction_genes = set(self.interaction_df['protein1']).union(set(self.interaction_df['protein2']))
653
- return list(common_features.intersection(interaction_genes))
656
+ # Return the intersection of omic features and interaction genes
657
+ return list(all_omic_features.intersection(interaction_genes))
654
658
 
655
659
  def create_edge_index(self):
660
+ # Create edges only if both proteins are within the available features
656
661
  filtered_df = self.interaction_df[
657
662
  (self.interaction_df['protein1'].isin(self.common_features)) &
658
663
  (self.interaction_df['protein2'].isin(self.common_features))
659
664
  ]
660
665
  edge_list = [(self.gene_to_index[row['protein1']], self.gene_to_index[row['protein2']]) for index, row in filtered_df.iterrows()]
661
666
  return torch.tensor(edge_list, dtype=torch.long).t()
662
-
667
+
663
668
  def precompute_node_features(self):
664
- # Find indices of common features in each data matrix
665
- feature_indices = {data_type: [self.multiomic_dataset.features[data_type].get_loc(gene)
666
- for gene in self.common_features]
667
- for data_type in self.multiomic_dataset.dat}
668
- # Create a tensor to store all features [num_samples, num_nodes, num_data_types]
669
669
  num_samples = len(self.samples)
670
670
  num_nodes = len(self.common_features)
671
671
  num_data_types = len(self.multiomic_dataset.dat)
672
- all_features = torch.empty((num_samples, num_nodes, num_data_types), dtype=torch.float)
672
+ all_features = torch.full((num_samples, num_nodes, num_data_types), float('nan'), dtype=torch.float)
673
673
 
674
- # Extract features for each data type and place them in the tensor
675
674
  for i, data_type in enumerate(self.multiomic_dataset.dat):
676
- # Get the data matrix
677
675
  data_matrix = self.multiomic_dataset.dat[data_type]
678
- # Use advanced indexing to extract features for all samples at once
679
- indices = feature_indices[data_type]
680
- if indices: # Ensure there are common features in this data type
681
- all_features[:, :, i] = data_matrix[:, indices]
676
+ feature_indices = {
677
+ gene: self.multiomic_dataset.features[data_type].get_loc(gene)
678
+ for gene in self.common_features if gene in self.multiomic_dataset.features[data_type]
679
+ }
680
+ valid_indices = torch.tensor(list(feature_indices.values()))
681
+ feature_positions = torch.tensor([self.gene_to_index[gene] for gene in feature_indices.keys()])
682
+
683
+ # Fill in the available data
684
+ all_features[:, feature_positions, i] = data_matrix[:, valid_indices]
685
+
686
+ # Precompute medians for all data types, ignoring NaN values
687
+ medians = torch.nanmedian(all_features, dim=1, keepdim=True).values # Use .values to get the actual median tensor
688
+
689
+ # Replace all NaN values in all_features with their corresponding median values
690
+ isnan = torch.isnan(all_features)
691
+ all_features[isnan] = medians.expand_as(all_features)[isnan]
692
+
682
693
  return all_features
683
694
 
684
695
  def subset(self, indices):
@@ -199,6 +199,23 @@ def evaluate_survival(outputs, durations, events):
199
199
  return {'cindex': c_index}
200
200
 
201
201
  def evaluate_classifier(y_true, y_pred, print_report = False):
202
+ """
203
+ Evaluate the performance of a classifier using multiple metrics and optionally print a detailed classification report.
204
+
205
+ This function computes balanced accuracy, F1 score (macro), and Cohen's Kappa score for the given true and predicted labels.
206
+ If `print_report` is set to True, it prints a detailed classification report.
207
+
208
+ Args:
209
+ y_true (array-like): True labels of the data, must be 1D list or array of labels.
210
+ y_pred (array-like): Predicted labels as returned by a classifier, must match the dimensions of y_true.
211
+ print_report (bool, optional): If True, prints a detailed classification report. Defaults to False.
212
+
213
+ Returns:
214
+ dict: A dictionary containing:
215
+ - 'balanced_acc': The balanced accuracy of the predictions.
216
+ - 'f1_score': The macro-average F1 score of the predictions.
217
+ - 'kappa': Cohen's Kappa score indicating the level of agreement between the true and predicted labels.
218
+ """
202
219
  # Balanced accuracy
203
220
  balanced_acc = balanced_accuracy_score(y_true, y_pred)
204
221
  # F1 score (macro)
@@ -213,12 +230,48 @@ def evaluate_classifier(y_true, y_pred, print_report = False):
213
230
  return {"balanced_acc": balanced_acc, "f1_score": f1, "kappa": kappa}
214
231
 
215
232
  def evaluate_regressor(y_true, y_pred):
233
+ """
234
+ Evaluate the performance of a regression model using mean squared error, R-squared, and Pearson correlation coefficient.
235
+
236
+ This function computes the mean squared error (MSE) between true and predicted values as a measure of prediction accuracy.
237
+ It also performs a linear regression analysis between the true and predicted values to obtain the R-squared value, which
238
+ explains the variance ratio, and the Pearson correlation coefficient, providing insight into the linear relationship strength.
239
+
240
+ Args:
241
+ y_true (array-like): True values of the dependent variable, must be a 1D list or array.
242
+ y_pred (array-like): Predicted values as returned by a regressor, must match the dimensions of y_true.
243
+
244
+ Returns:
245
+ dict: A dictionary containing:
246
+ - 'mse': The mean squared error between the true and predicted values.
247
+ - 'r2': The R-squared value indicating the proportion of variance in the dependent variable predictable from the independent variable.
248
+ - 'pearson_corr': The Pearson correlation coefficient indicating the linear relationship strength between the true and predicted values.
249
+ """
216
250
  mse = mean_squared_error(y_true, y_pred)
217
251
  slope, intercept, r_value, p_value, std_err = linregress(y_true,y_pred)
218
252
  r2 = r_value**2
219
253
  return {"mse": mse, "r2": r2, "pearson_corr": r_value}
220
254
 
221
- def evaluate_wrapper(y_pred_dict, dataset, surv_event_var = None, surv_time_var = None):
255
+ def evaluate_wrapper(method, y_pred_dict, dataset, surv_event_var = None, surv_time_var = None):
256
+ """
257
+ Evaluates predictions for different variables within a dataset using appropriate metrics based on the variable type.
258
+ Supports evaluation for numerical, categorical, and survival data.
259
+
260
+ This function loops through each variable in the predictions dictionary, determines the type of the variable,
261
+ and evaluates the predictions using the appropriate method: regression, classification, or survival analysis.
262
+ It compiles the metrics into a list of dictionaries, which is then converted into a pandas DataFrame.
263
+
264
+ Args:
265
+ method (str): Identifier for the prediction method or model used.
266
+ y_pred_dict (dict): A dictionary where keys are variable names and values are arrays of predicted values.
267
+ dataset (Dataset): A dataset object containing actual values and metadata such as variable types.
268
+ surv_event_var (str, optional): The name of the survival event variable. Required if survival analysis is performed.
269
+ surv_time_var (str, optional): The name of the survival time variable. Required if survival analysis is performed.
270
+
271
+ Returns:
272
+ pd.DataFrame: A DataFrame where each row contains the method, variable name, variable type, metric name, and metric value.
273
+
274
+ """
222
275
  metrics_list = []
223
276
  for var in y_pred_dict.keys():
224
277
  if dataset.variable_types[var] == 'numerical':
@@ -235,6 +288,7 @@ def evaluate_wrapper(y_pred_dict, dataset, surv_event_var = None, surv_time_var
235
288
 
236
289
  for metric, value in metrics.items():
237
290
  metrics_list.append({
291
+ 'method': method,
238
292
  'var': var,
239
293
  'variable_type': dataset.variable_types[var],
240
294
  'metric': metric,
@@ -263,8 +317,26 @@ def get_predicted_labels(y_pred_dict, dataset, split):
263
317
  dfs.append(df)
264
318
  return pd.concat(dfs, ignore_index=True)
265
319
 
266
- # evaluate performance of off-the-shelf methods such as Random Forests and SVMs on regression/classification tasks
267
- def evaluate_baseline_performance(train_dataset, test_dataset, variable_name, n_folds=5, n_jobs = 4):
320
+ def evaluate_baseline_performance(train_dataset, test_dataset, variable_name, methods, n_folds=5, n_jobs=4):
321
+ """
322
+ Evaluates the performance of RandomForest and/or Support Vector Machine models on a given variable from the provided datasets using cross-validation.
323
+
324
+ This function preprocesses the training and testing data, performs grid search with cross-validation to find the best
325
+ hyperparameters for the specified methods, and then evaluates the performance of these models on the testing set.
326
+ It supports evaluation for both categorical and numerical variables using appropriate machine learning models.
327
+
328
+ Args:
329
+ train_dataset (Dataset): A MultiOmicDataset object containing training data and metadata such as variable types.
330
+ test_dataset (Dataset): A MultiOmicDataset object containing testing data.
331
+ variable_name (str): The name of the target variable for prediction.
332
+ methods (list of str): List of machine learning methods to evaluate, e.g., ['RandomForest', 'SVM'].
333
+ n_folds (int, optional): Number of folds to use in K-fold cross-validation. Defaults to 5.
334
+ n_jobs (int, optional): Number of jobs to run in parallel during grid search. Defaults to 4.
335
+
336
+ Returns:
337
+ pd.DataFrame: A DataFrame containing the method, variable name, variable type, metric name, and metric value for each tested method.
338
+
339
+ """
268
340
  def prepare_data(data_object):
269
341
  # Concatenate Data Matrices
270
342
  X = np.concatenate([tensor for tensor in data_object.dat.values()], axis=1)
@@ -281,52 +353,32 @@ def evaluate_baseline_performance(train_dataset, test_dataset, variable_name, n_
281
353
  # Determine variable type
282
354
  variable_type = train_dataset.variable_types[variable_name]
283
355
 
284
- # Initialize models and parameter grids
285
- if variable_type == 'categorical':
286
- model_params = {
287
- 'RandomForestClassifier': {
288
- 'model': RandomForestClassifier(random_state=42),
289
- 'params': {
290
- 'n_estimators': [100, 200, 300],
291
- 'max_depth': [10, 20, None]
292
- }
293
- },
294
- 'SVC': {
295
- 'model': SVC(),
296
- 'params': {
297
- 'C': [0.1, 1, 10],
298
- 'kernel': ['rbf', 'poly']
299
- }
300
- }
301
- }
302
- elif variable_type == 'numerical':
303
- model_params = {
304
- 'RandomForestRegressor': {
305
- 'model': RandomForestRegressor(random_state=42),
306
- 'params': {
307
- 'n_estimators': [100, 200, 300],
308
- 'max_depth': [10, 20, None]
309
- }
310
- },
311
- 'SVR': {
312
- 'model': SVR(),
313
- 'params': {
314
- 'C': [0.1, 1, 10],
315
- 'kernel': ['rbf', 'poly']
316
- }
317
- }
318
- }
319
-
320
356
  # Cross-Validation and Training
321
357
  kf = KFold(n_splits=n_folds, shuffle=True, random_state=42)
322
358
  X_train, y_train = prepare_data(train_dataset)
323
- print("Train:",X_train.shape)
359
+ print("Train:", X_train.shape)
324
360
  X_test, y_test = prepare_data(test_dataset)
325
- print("Test:",X_test.shape)
361
+ print("Test:", X_test.shape)
326
362
 
327
363
  metrics_list = []
328
- for model_name, mp in model_params.items():
329
- grid_search = GridSearchCV(mp['model'], mp['params'], cv=kf, n_jobs=n_jobs)
364
+
365
+ for method in methods:
366
+ if variable_type == 'categorical':
367
+ if method == 'RandomForest':
368
+ model = RandomForestClassifier(random_state=42)
369
+ params = {'n_estimators': [100, 200, 300], 'max_depth': [10, 20, None]}
370
+ elif method == 'SVM':
371
+ model = SVC(random_state=42)
372
+ params = {'C': [0.1, 1, 10], 'kernel': ['rbf', 'poly']}
373
+ elif variable_type == 'numerical':
374
+ if method == 'RandomForest':
375
+ model = RandomForestRegressor(random_state=42)
376
+ params = {'n_estimators': [100, 200, 300], 'max_depth': [10, 20, None]}
377
+ elif method == 'SVM':
378
+ model = SVR()
379
+ params = {'C': [0.1, 1, 10], 'kernel': ['rbf', 'poly']}
380
+
381
+ grid_search = GridSearchCV(model, params, cv=kf, n_jobs=n_jobs)
330
382
  grid_search.fit(X_train, y_train)
331
383
  best_model = grid_search.best_estimator_
332
384
 
@@ -341,7 +393,7 @@ def evaluate_baseline_performance(train_dataset, test_dataset, variable_name, n_
341
393
 
342
394
  for metric, value in metrics.items():
343
395
  metrics_list.append({
344
- 'method': model_name,
396
+ 'method': method + ('Classifier' if variable_type == 'categorical' else 'Regressor'),
345
397
  'var': variable_name,
346
398
  'variable_type': variable_type,
347
399
  'metric': metric,
@@ -352,6 +404,26 @@ def evaluate_baseline_performance(train_dataset, test_dataset, variable_name, n_
352
404
  return pd.DataFrame(metrics_list)
353
405
 
354
406
  def evaluate_baseline_survival_performance(train_dataset, test_dataset, duration_col, event_col, n_folds=5, n_jobs=4):
407
+ """
408
+ Evaluates the baseline performance of a Random Survival Forest model on survival data using the Concordance Index.
409
+
410
+ The function preprocesses both training and testing datasets to prepare appropriate survival data (comprising durations
411
+ and event occurrences), performs cross-validation to assess model robustness, and then calculates the Concordance Index on
412
+ the test data. It uses a Random Survival Forest (RSF) as the predictive model.
413
+
414
+ Args:
415
+ train_dataset (Dataset): The training dataset (a MultiOmicDataset object) containing features and survival data.
416
+ test_dataset (Dataset): The testing dataset (a MultiOmicDataset object) containing features and survival data.
417
+ duration_col (str): Column name in the dataset for survival time.
418
+ event_col (str): Column name in the dataset for the event occurrence (1 if event occurred, 0 otherwise).
419
+ n_folds (int, optional): Number of folds for K-fold cross-validation. Defaults to 5.
420
+ n_jobs (int, optional): Number of parallel jobs to run for Random Survival Forest training. Defaults to 4.
421
+
422
+ Returns:
423
+ pd.DataFrame: A DataFrame containing the performance metrics of the RSF model, specifically the Concordance Index,
424
+ listed along with the method name and variable details.
425
+
426
+ """
355
427
  print(f"[INFO] Evaluating baseline survival prediction performance")
356
428
  def prepare_data(data_object, duration_col, event_col):
357
429
  # Concatenate Data Matrices
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flexynesis
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: A deep-learning based multi-omics bulk sequencing data integration suite with a focus on (pre-)clinical endpoint prediction.
5
5
  Author-email: Bora Uyar <bora.uyar@mdc-berlin.de>, Taras Savchyn <Taras.Savchyn@mdc-berlin.de>, Ricardo Wurmus <Ricardo.Wurmus@mdc-berlin.de>, Ahmet Sarigun <Ahmet.Sariguen@mdc-berlin.de>
6
6
  Project-URL: homepage, https://github.com/BIMSBbioinfo/flexynesis
@@ -113,7 +113,7 @@ flexynesis --data_path dataset1 \
113
113
  --prefix erlotinib_direct \
114
114
  --early_stop_patience 3 \
115
115
  --use_loss_weighting False \
116
- --evaluate_baseline_performance False
116
+ --evaluate_baseline_performance
117
117
  ```
118
118
 
119
119
  ## Accelerating with GPUs
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flexynesis"
7
- version = "0.2.3"
7
+ version = "0.2.5"
8
8
  authors = [
9
9
  {name = "Bora Uyar", email = "bora.uyar@mdc-berlin.de"},
10
10
  {name = "Taras Savchyn", email = "Taras.Savchyn@mdc-berlin.de"},
File without changes
File without changes
File without changes
File without changes