pyaging 0.0.7__tar.gz → 0.0.8__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.

Potentially problematic release.


This version of pyaging might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyaging
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: A Python-based compendium of GPU-optimized aging clocks.
5
5
  License: BSD
6
6
  Author: Lucas Paulo de Lima Camillo
@@ -66,9 +66,10 @@ def predict_age(
66
66
  silence_logger("predict_age")
67
67
  logger.first_info("Starting predict_age function")
68
68
 
69
- # Ensure clock_names is a list
69
+ # Ensure clock_names is a list with lowercase names
70
70
  if isinstance(clock_names, str):
71
71
  clock_names = [clock_names]
72
+ clock_names = [clock_name.lower() for clock_name in clock_names]
72
73
 
73
74
  # Set device for PyTorch operations
74
75
  device = set_torch_device(logger)
@@ -77,7 +78,6 @@ def predict_age(
77
78
  logger.info(f"🕒 Processing clock: {clock_name}", indent_level=1)
78
79
 
79
80
  # Load and prepare the clock
80
- clock_name = clock_name.lower()
81
81
  (
82
82
  features,
83
83
  reference_feature_values,
@@ -108,16 +108,6 @@ def predict_age(
108
108
  indent_level=2,
109
109
  )
110
110
 
111
- # Filter features and then extract data matrix
112
- x_numpy = filter_features_and_extract_data(
113
- adata, preprocessing, features, logger, indent_level=2
114
- )
115
-
116
- # Convert numpy array to tensor
117
- x_tensor = convert_numpy_array_to_tensor(
118
- x_numpy, device, logger, indent_level=2
119
- )
120
-
121
111
  # Initialize and configure the model
122
112
  clock_model = initialize_model(
123
113
  clock_name, features, weight_dict, device, logger, indent_level=2
@@ -125,10 +115,10 @@ def predict_age(
125
115
 
126
116
  # Perform age prediction using the model
127
117
  predicted_ages_tensor = predict_ages_with_model(
128
- clock_model, x_tensor, logger, indent_level=2
118
+ clock_model, adata, features, device, logger, indent_level=2
129
119
  )
130
120
 
131
- # Convert adata tensor to numpy array
121
+ # Convert torch tensor to numpy array
132
122
  predicted_ages = convert_tensor_to_numpy_array(
133
123
  predicted_ages_tensor, logger, indent_level=2
134
124
  )
@@ -145,15 +135,10 @@ def predict_age(
145
135
  # Add predicted ages to adata
146
136
  add_pred_ages_adata(adata, predicted_ages, clock_name, logger, indent_level=2)
147
137
 
148
- # Load all clocks metadata
149
- all_clock_metadata = load_clock_metadata(dir, logger, indent_level=2)
150
-
151
138
  # Add clock metadata to adata object
152
- add_clock_metadata_adata(
153
- adata, clock_name, all_clock_metadata, logger, indent_level=2
154
- )
139
+ add_clock_metadata_adata(adata, clock_name, dir, logger, indent_level=2)
155
140
 
156
- # Reduce feature size to original features
141
+ # Return adata to original size and number of features
157
142
  adata = filter_missing_features(adata, logger, indent_level=2)
158
143
 
159
144
  # Flush memory
@@ -5,6 +5,7 @@ from torch.utils.data import TensorDataset, DataLoader
5
5
  import numpy as np
6
6
  import pandas as pd
7
7
  import anndata
8
+ from anndata.experimental.pytorch import AnnLoader
8
9
  import ntpath
9
10
  from urllib.request import urlretrieve
10
11
 
@@ -342,6 +343,7 @@ def initialize_model(
342
343
  "bitage",
343
344
  "zhangmortality",
344
345
  "zhangen",
346
+ "zhangblup",
345
347
  "leecpc",
346
348
  "leerpc",
347
349
  "leerefinedrpc",
@@ -349,6 +351,7 @@ def initialize_model(
349
351
  "thompsonmultitissue",
350
352
  "petkovichblood",
351
353
  "stubbsmultitissue",
354
+ "linblood",
352
355
  ]:
353
356
  model = LinearModel(len(features))
354
357
  elif clock_name in [
@@ -385,6 +388,7 @@ def initialize_model(
385
388
  raise ValueError(f"Clock '{clock_name}' is not supported.")
386
389
 
387
390
  model.load_state_dict(weight_dict)
391
+ model.to(torch.float64)
388
392
  model = model.to(device)
389
393
  model.eval()
390
394
  return model
@@ -456,14 +460,6 @@ def preprocess_data(
456
460
  )
457
461
  return adata
458
462
 
459
- # Skip if it has already found the preprocessing layer
460
- if f"X_{preprocessing}" in adata.layers:
461
- logger.info(
462
- f"Layer with {preprocessing} preprocessing is already in adata",
463
- indent_level=3,
464
- )
465
- return adata
466
-
467
463
  # Move to adata.X for preprocessing
468
464
  adata.X = (
469
465
  adata.layers["X_imputed"].copy()
@@ -502,7 +498,6 @@ def preprocess_data(
502
498
  else:
503
499
  logger.error(f"Preprocessing function {preprocessing} not found.")
504
500
  raise ValueError()
505
- adata.layers[f"X_{preprocessing}"] = adata.X.copy()
506
501
 
507
502
  return adata
508
503
 
@@ -601,7 +596,12 @@ def postprocess_data(
601
596
 
602
597
  @progress("Predict ages with model")
603
598
  def predict_ages_with_model(
604
- model: torch.nn.Module, data: torch.Tensor, logger, indent_level: int = 2
599
+ model: torch.nn.Module,
600
+ adata: torch.Tensor,
601
+ features: List[str],
602
+ device: str,
603
+ logger,
604
+ indent_level: int = 2,
605
605
  ) -> torch.Tensor:
606
606
  """
607
607
  Predict biological ages using a trained model and input data.
@@ -609,16 +609,23 @@ def predict_ages_with_model(
609
609
  This function takes a machine learning model and input data, and returns predictions made by the model.
610
610
  It's primarily used for estimating biological ages based on various biological markers. The function
611
611
  assumes that the model is already trained and the data is preprocessed according to the model's requirements.
612
- If the size of the data is large (> 1000 samples), a dataloader is used because of possible memory constraints.
612
+ A dataloader is used because of possible memory constraints.
613
613
 
614
614
  Parameters
615
615
  ----------
616
616
  model : torch.nn.Module
617
617
  A pre-trained machine learning model that can make predictions.
618
618
 
619
- data : array-like
620
- The input data on which age predictions need to be made. This should be in the format expected
621
- by the `model` (e.g., a numpy array, pandas DataFrame, or PyTorch tensor).
619
+ adata : anndata.AnnData
620
+ The AnnData object containing the dataset. Its `.X` attribute is expected to be a matrix where rows
621
+ correspond to samples and columns correspond to features.
622
+
623
+ features : list of str
624
+ A list of feature names to be included in the output array. Only these features from the AnnData object will
625
+ be extracted for age prediction.
626
+
627
+ device : str
628
+ Device to move AnnData to during inference. Eithe 'cpu' or 'cuda'.
622
629
 
623
630
  logger : Logger
624
631
  A logger object for logging the progress or any relevant information during the prediction process.
@@ -642,41 +649,26 @@ def predict_ages_with_model(
642
649
  Examples
643
650
  --------
644
651
  >>> model = load_pretrained_model()
645
- >>> predictions = predict_ages_with_model(model, data, logger)
652
+ >>> predictions = predict_ages_with_model(model, adata, features, 'cpu', logger)
646
653
  >>> print(predictions[:5])
647
654
  [34.5, 29.3, 47.8, 50.1, 42.6]
648
655
 
649
656
  """
650
- # Check if the size of data is greater than 1000
651
- if data.size(0) > 1024:
652
- logger.info(
653
- f"Number of samples ({data.size(0)}) is bigger than 1024. Predicting age with DataLoader",
654
- indent_level=indent_level + 1,
655
- )
656
-
657
- # Create a TensorDataset
658
- dataset = TensorDataset(data)
659
-
660
- # Create a DataLoader
661
- batch_size = 1024
662
- dataloader = DataLoader(dataset, batch_size=batch_size)
663
-
664
- # Use the DataLoader for batched prediction
665
- predictions = []
666
- with torch.no_grad():
667
- for batch in main_tqdm(
668
- dataloader, indent_level=indent_level + 1, logger=logger
669
- ):
670
- batch_data = batch[0]
671
- batch_pred = model(batch_data)
672
- predictions.append(batch_pred)
673
-
674
- # Concatenate all batch predictions
675
- predictions = torch.cat(predictions)
676
- else:
677
- # If data size is not greater than 1000, predict directly
678
- with torch.no_grad():
679
- predictions = model(data)
657
+ # Create an AnnLoader
658
+ use_cuda = device == "cuda"
659
+ dataloader = AnnLoader(adata, batch_size=1024, use_cuda=use_cuda)
660
+
661
+ # Use the AnnLoader for batched prediction
662
+ predictions = []
663
+ with torch.no_grad():
664
+ for batch in main_tqdm(
665
+ dataloader, indent_level=indent_level + 1, logger=logger
666
+ ):
667
+ batch_pred = model(batch[:, features].X)
668
+ predictions.append(batch_pred)
669
+
670
+ # Concatenate all batch predictions
671
+ predictions = torch.cat(predictions)
680
672
  return predictions
681
673
 
682
674
 
@@ -726,133 +718,6 @@ def convert_tensor_to_numpy_array(
726
718
  return tensor.cpu().detach().numpy().flatten()
727
719
 
728
720
 
729
- @progress("Convert numpy array to tensor")
730
- def convert_numpy_array_to_tensor(
731
- array: np.ndarray, device: str, logger, indent_level: int = 2
732
- ) -> torch.Tensor:
733
- """
734
- Convert a NumPy array to a tensor.
735
-
736
- This utility function takes a NumPy array and converts it into PyTorch tensor. It is useful in scenarios
737
- where NumPy arrays need to be processed or analyzed using PyTorch-based tools or functions. The function also
738
- moves the tensor to the device being used.
739
-
740
- Parameters
741
- ----------
742
- tensor : torch.Tensor
743
- The PyTorch tensor to be converted. This can be a tensor of any shape.
744
-
745
- device : str
746
- Device to move tensor to after. Eithe 'cpu' or 'cuda'.
747
-
748
- logger : Logger
749
- A logger object for logging the progress or any relevant information during the conversion process.
750
-
751
- indent_level : int, optional
752
- The indentation level for logging messages, by default 2.
753
-
754
- Returns
755
- -------
756
- numpy_array : ndarray
757
- A 1D NumPy array equivalent to the input tensor.
758
-
759
- Notes
760
- -----
761
- This function detaches the tensor from the computation graph, so it should be used with caution if the tensor
762
- is part of a PyTorch computational graph, such as when dealing with gradients in training neural networks.
763
-
764
- The flattening to a 1D array may lose the original shape information of the tensor, which should be considered
765
- if the shape is important for further processing.
766
-
767
- Examples
768
- --------
769
- >>> numpy_array = np.array([[1, 2], [3, 4]])
770
- >>> tensor = convert_numpy_array_to_tensor(numpy_array, logger)
771
- >>> tensor
772
- torch.tensor([1, 2, 3, 4])
773
-
774
- """
775
- tensor = torch.tensor(array, dtype=torch.float32)
776
- tensor = tensor.to(device)
777
- return tensor
778
-
779
-
780
- @progress("Filter features and extract data matrix")
781
- def filter_features_and_extract_data(
782
- adata: anndata.AnnData,
783
- preprocessing: str,
784
- features: List[str],
785
- logger,
786
- indent_level: int = 2,
787
- ) -> np.ndarray:
788
- """
789
- Filter features from an AnnData object
790
-
791
- This function processes an AnnData object, which is commonly used in bioinformatics for storing large
792
- gene expression datasets. It extracts the data matrix corresponding to a specified set of features (genes or
793
- other genomic elements) and converts this subset into a numpy array.
794
-
795
- Parameters
796
- ----------
797
- adata : anndata.AnnData
798
- The AnnData object containing the dataset. Its `.X` attribute is expected to be a matrix where rows
799
- correspond to samples and columns correspond to features.
800
-
801
- preprocessing : str
802
- The name of the preprocessing method used (if any). Used to search adata.layers for data matrix.
803
-
804
- features : list of str
805
- A list of feature names to be included in the output array. Only these features from the AnnData object will
806
- be extracted and converted.
807
-
808
- logger : Logger
809
- A logger object for logging progress or relevant information during the conversion process.
810
-
811
- indent_level : int, optional
812
- The indentation level for logging messages, by default 2.
813
-
814
- Returns
815
- -------
816
- numpy_array : ndarray
817
- A Numpy array containing the filtered data from the AnnData object, with dtype set to float32.
818
-
819
- Notes
820
- -----
821
- The AnnData object is a central data structure in many bioinformatics pipelines. It efficiently handles
822
- large datasets typical in genomics and transcriptomics. This function facilitates the integration of
823
- AnnData-based workflows with PyTorch models and functions.
824
-
825
- The dtype of the returned tensor is set to float32, which is a common choice for numerical operations in
826
- PyTorch, especially in the context of machine learning models.
827
-
828
- Examples
829
- --------
830
- >>> adata = anndata.AnnData(np.random.rand(5, 10))
831
- >>> features = ['gene1', 'gene2', 'gene3']
832
- >>> numpy_array = filter_features_and_extract_data(adata, None, features, logger)
833
- >>> numpy_array.shape
834
- array([5, 3])
835
-
836
- """
837
- if preprocessing is not None:
838
- layer_name = f"X_{preprocessing}"
839
- elif "X_imputed" in adata.layers:
840
- layer_name = "X_imputed"
841
- else:
842
- layer_name = "X_original"
843
- # x_numpy = np.array(adata[:, features].layers[layer_name])
844
- layer_data = adata[:, features].layers[layer_name]
845
-
846
- if not isinstance(layer_data, np.ndarray):
847
- layer_data = (
848
- layer_data.toarray()
849
- if hasattr(layer_data, "toarray")
850
- else np.array(layer_data, dtype=np.float32)
851
- )
852
-
853
- return layer_data
854
-
855
-
856
721
  @progress("Add predicted ages to adata")
857
722
  def add_pred_ages_adata(
858
723
  adata: anndata.AnnData,
@@ -959,7 +824,19 @@ def filter_missing_features(
959
824
  >>> adata = filter_missing_features(adata, logger)
960
825
 
961
826
  """
962
- adata = adata[:, adata.var["percent_na"] < 1].copy()
827
+ n_missing_features = sum(adata.var["percent_na"] == 1)
828
+ if n_missing_features > 0:
829
+ logger.info(
830
+ f"Removing {n_missing_features} added features",
831
+ indent_level=indent_level + 1,
832
+ )
833
+ adata = adata[:, adata.var["percent_na"] < 1].copy()
834
+ else:
835
+ logger.info(
836
+ "No missing features, so adata size did not change",
837
+ indent_level=indent_level + 1,
838
+ )
839
+
963
840
  return adata
964
841
 
965
842
 
@@ -967,7 +844,7 @@ def filter_missing_features(
967
844
  def add_clock_metadata_adata(
968
845
  adata: anndata.AnnData,
969
846
  clock_name: str,
970
- all_clock_metadata: dict,
847
+ dir: str,
971
848
  logger,
972
849
  indent_level: int = 2,
973
850
  ) -> None:
@@ -990,9 +867,8 @@ def add_clock_metadata_adata(
990
867
  The name of the aging clock. The metadata associated with this clock will be added to
991
868
  the AnnData object.
992
869
 
993
- all_clock_metadata : dict
994
- A dictionary containing metadata for all available clocks. The function will extract metadata for
995
- the specified clock from this dictionary.
870
+ dir: str
871
+ The directory to deposit the downloaded file. Defaults to "pyaging_data".
996
872
 
997
873
  logger : Logger
998
874
  A logger object for logging the progress or relevant information during the operation.
@@ -1011,18 +887,21 @@ def add_clock_metadata_adata(
1011
887
  here ensures that all relevant information about the aging clocks used in the analysis is
1012
888
  preserved alongside the dataset.
1013
889
 
1014
- It is important to ensure that the `clock_name` exists within the `all_clock_metadata` dictionary to
1015
- avoid key errors.
1016
-
1017
890
  Examples
1018
891
  --------
1019
892
  >>> adata = anndata.AnnData(np.random.rand(5, 10))
1020
- >>> all_clock_metadata = {'horvath2013': {'description': 'Horvath’s 2013 epigenetic clock', 'reference': 'Horvath, S. (2013)'}}
1021
- >>> add_clock_metadata_adata(adata, 'horvath2013', all_clock_metadata, logger)
893
+ >>> add_clock_metadata_adata(adata, 'horvath2013', 'pyaging_data', logger)
1022
894
  >>> adata.uns['horvath2013_metadata']
1023
895
  {'description': 'Horvath’s 2013 epigenetic clock', 'reference': 'Horvath, S. (2013)'}
1024
896
 
1025
897
  """
898
+
899
+ # Load all clocks metadata
900
+ url = f"https://pyaging.s3.amazonaws.com/clocks/metadata/all_clock_metadata.pt"
901
+ download(url, dir, logger, indent_level=indent_level)
902
+ all_clock_metadata = torch.load(f"{dir}/all_clock_metadata.pt")
903
+
904
+ # Add clock metadata to adata.uns
1026
905
  adata.uns[f"{clock_name}_metadata"] = all_clock_metadata[clock_name]
1027
906
 
1028
907
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyaging"
3
- version = "0.0.7"
3
+ version = "0.0.8"
4
4
  description = "A Python-based compendium of GPU-optimized aging clocks."
5
5
  authors = ["Lucas Paulo de Lima Camillo <lucas_camillo@alumni.brown.edu>"]
6
6
  license = "BSD"
File without changes
File without changes
File without changes
File without changes
File without changes