pyaging 0.5.1__tar.gz → 0.5.2__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 (28) hide show
  1. {pyaging-0.5.1 → pyaging-0.5.2}/PKG-INFO +1 -1
  2. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/__init__.py +1 -1
  3. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/models/__init__.py +4 -8
  4. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/models/_base_models.py +4 -0
  5. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/models/_models.py +70 -114
  6. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/predict/_pred_utils.py +6 -2
  7. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/utils/_feature_ranges.py +1 -1
  8. {pyaging-0.5.1 → pyaging-0.5.2}/.gitignore +0 -0
  9. {pyaging-0.5.1 → pyaging-0.5.2}/LICENSE +0 -0
  10. {pyaging-0.5.1 → pyaging-0.5.2}/README.md +0 -0
  11. {pyaging-0.5.1 → pyaging-0.5.2}/pyproject.toml +0 -0
  12. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/data/__init__.py +0 -0
  13. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/data/_data.py +0 -0
  14. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/data/feature_ranges.json +0 -0
  15. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/logger/__init__.py +0 -0
  16. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/logger/_live.py +0 -0
  17. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/logger/_logger.py +0 -0
  18. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/predict/__init__.py +0 -0
  19. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/predict/_pred.py +0 -0
  20. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/predict/_transforms.py +0 -0
  21. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/preprocess/__init__.py +0 -0
  22. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/preprocess/_preprocess.py +0 -0
  23. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/preprocess/_preprocess_utils.py +0 -0
  24. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/preprocess/_tage.py +0 -0
  25. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/py.typed +0 -0
  26. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/utils/__init__.py +0 -0
  27. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/utils/_hf.py +0 -0
  28. {pyaging-0.5.1 → pyaging-0.5.2}/src/pyaging/utils/_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyaging
3
- Version: 0.5.1
3
+ Version: 0.5.2
4
4
  Summary: A Python-based compendium of GPU-optimized aging clocks.
5
5
  Project-URL: homepage, https://github.com/lucascamillomd/pyaging
6
6
  Project-URL: repository, https://github.com/lucascamillomd/pyaging
@@ -4,4 +4,4 @@ from . import data, logger, models, utils
4
4
  from . import predict as pred
5
5
  from . import preprocess as pp
6
6
 
7
- __version__ = "0.5.1"
7
+ __version__ = "0.5.2"
@@ -34,10 +34,8 @@ from ._models import (
34
34
  DepressionBarbu,
35
35
  DNAmFILi,
36
36
  DNAmFitAge,
37
- DNAmFitAgeGaitF,
38
- DNAmFitAgeGaitM,
39
- DNAmFitAgeGripF,
40
- DNAmFitAgeGripM,
37
+ DNAmFitAgeGait,
38
+ DNAmFitAgeGrip,
41
39
  DNAmFitAgeVO2Max,
42
40
  DNAmIC,
43
41
  DNAmPhenoAge,
@@ -201,10 +199,8 @@ __all__ = [
201
199
  "CpGPTPCGrimAge3",
202
200
  "DNAmFILi",
203
201
  "DNAmFitAge",
204
- "DNAmFitAgeGaitF",
205
- "DNAmFitAgeGaitM",
206
- "DNAmFitAgeGripF",
207
- "DNAmFitAgeGripM",
202
+ "DNAmFitAgeGait",
203
+ "DNAmFitAgeGrip",
208
204
  "DNAmFitAgeVO2Max",
209
205
  "DNAmIC",
210
206
  "DNAmPhenoAge",
@@ -41,6 +41,10 @@ class pyagingModel(nn.Module, ABC):
41
41
 
42
42
  self.features = None
43
43
  self.feature_units = None
44
+ # Optional internal range profile when the model consumes values that
45
+ # differ from the public assay modality (for example, cohort-centered
46
+ # transcriptomic values derived from raw RNA-seq counts).
47
+ self.feature_range_data_type = None
44
48
  self.base_model_features = self.features
45
49
 
46
50
  self.base_model = None
@@ -2116,66 +2116,50 @@ class DNAmFitAge(pyagingModel):
2116
2116
  def __init__(self):
2117
2117
  super().__init__()
2118
2118
 
2119
- self.GaitF = None
2120
- self.GripF = None
2121
- self.GaitM = None
2122
- self.GripM = None
2119
+ self.Gait = None
2120
+ self.Grip = None
2123
2121
  self.VO2Max = None
2122
+ self.GrimAge = None
2124
2123
 
2125
- self.features_GaitF = None
2126
- self.features_GripF = None
2127
- self.features_GaitM = None
2128
- self.features_GripM = None
2124
+ self.features_Gait = None
2125
+ self.features_Grip = None
2129
2126
  self.features_VO2Max = None
2127
+ self.features_GrimAge = None
2128
+ self.female_index = None
2129
+ self.age_index = None
2130
2130
 
2131
- def forward(self, x):
2132
- Female = x[:, -3] # .unsqueeze(1)
2133
- GrimAge = x[:, -1].unsqueeze(1)
2134
-
2135
- is_female = Female == 1
2136
- is_male = Female == 0
2137
-
2138
- x_f = x[is_female]
2139
- x_m = x[is_male]
2131
+ def _component_input(self, x, indices, component):
2132
+ values = x[:, indices]
2133
+ return _fill_dnamfitage_references(values, component.reference_values)
2140
2134
 
2141
- GaitF = self.GaitF(x_f[:, self.features_GaitF])
2142
- GripF = self.GripF(x_f[:, self.features_GripF])
2143
- VO2MaxF = self.VO2Max(x_f[:, self.features_VO2Max])
2144
- GrimAgeF = GrimAge[is_female, :]
2145
-
2146
- GaitM = self.GaitM(x_m[:, self.features_GaitM])
2147
- GripM = self.GripM(x_m[:, self.features_GripM])
2148
- VO2MaxM = self.VO2Max(x_m[:, self.features_VO2Max])
2149
- GrimAgeM = GrimAge[is_male, :]
2135
+ def forward(self, x):
2136
+ female = x[:, self.female_index].unsqueeze(1)
2137
+ gait = self.Gait(self._component_input(x, self.features_Gait, self.Gait))
2138
+ grip = self.Grip(self._component_input(x, self.features_Grip, self.Grip))
2139
+ vo2max = self.VO2Max(self._component_input(x, self.features_VO2Max, self.VO2Max))
2140
+ grimage = self.GrimAge(self._component_input(x, self.features_GrimAge, self.GrimAge))
2150
2141
 
2151
- x_f = torch.concat(
2142
+ female_components = torch.concat(
2152
2143
  [
2153
- (VO2MaxF - 46.825091) / (-0.13620215),
2154
- (GripF - 39.857718) / (-0.22074456),
2155
- (GaitF - 2.508547) / (-0.01245682),
2156
- (GrimAgeF - 7.978487) / (0.80928530),
2144
+ (vo2max - 46.825091) / -0.13620215,
2145
+ (grip - 39.857718) / -0.22074456,
2146
+ (gait - 2.508547) / -0.01245682,
2147
+ (grimage - 7.978487) / 0.80928530,
2157
2148
  ],
2158
2149
  dim=1,
2159
2150
  )
2160
-
2161
- x_m = torch.concat(
2151
+ male_components = torch.concat(
2162
2152
  [
2163
- (VO2MaxM - 49.836389) / (-0.141862925),
2164
- (GripM - 57.514016) / (-0.253179827),
2165
- (GaitM - 2.349080) / (-0.009380061),
2166
- (GrimAgeM - 9.549733) / (0.835120557),
2153
+ (vo2max - 49.836389) / -0.141862925,
2154
+ (grip - 57.514016) / -0.253179827,
2155
+ (gait - 2.349080) / -0.009380061,
2156
+ (grimage - 9.549733) / 0.835120557,
2167
2157
  ],
2168
2158
  dim=1,
2169
2159
  )
2170
-
2171
- y_f = self.base_model_f(x_f)
2172
- y_m = self.base_model_m(x_m)
2173
-
2174
- y = torch.zeros((x.size(0), 1), dtype=x.dtype, device=x.device)
2175
- y[is_female] = y_f
2176
- y[is_male] = y_m
2177
-
2178
- return y
2160
+ female_prediction = self.base_model_f(female_components)
2161
+ male_prediction = self.base_model_m(male_components)
2162
+ return male_prediction + female * (female_prediction - male_prediction)
2179
2163
 
2180
2164
  def preprocess(self, x):
2181
2165
  if self.reference_values is None:
@@ -2190,6 +2174,45 @@ class DNAmFitAge(pyagingModel):
2190
2174
  return x
2191
2175
 
2192
2176
 
2177
+ def _fill_dnamfitage_references(x, reference_values):
2178
+ reference = torch.as_tensor(reference_values, device=x.device, dtype=x.dtype)
2179
+ return torch.where(torch.isnan(x), reference, x)
2180
+
2181
+
2182
+ class _DNAmFitAgeSexGated(pyagingModel):
2183
+ def __init__(self):
2184
+ super().__init__()
2185
+ self.female_model = None
2186
+ self.male_model = None
2187
+ self.female_feature_indices = None
2188
+ self.male_feature_indices = None
2189
+ self.female_reference_values = None
2190
+ self.male_reference_values = None
2191
+ self.female_index = None
2192
+
2193
+ def forward(self, x):
2194
+ female = x[:, self.female_index].unsqueeze(1)
2195
+ female_x = _fill_dnamfitage_references(x[:, self.female_feature_indices], self.female_reference_values)
2196
+ male_x = _fill_dnamfitage_references(x[:, self.male_feature_indices], self.male_reference_values)
2197
+ female_prediction = self.female_model(female_x)
2198
+ male_prediction = self.male_model(male_x)
2199
+ return male_prediction + female * (female_prediction - male_prediction)
2200
+
2201
+ def preprocess(self, x):
2202
+ return x
2203
+
2204
+ def postprocess(self, x):
2205
+ return x
2206
+
2207
+
2208
+ class DNAmFitAgeGait(_DNAmFitAgeSexGated):
2209
+ pass
2210
+
2211
+
2212
+ class DNAmFitAgeGrip(_DNAmFitAgeSexGated):
2213
+ pass
2214
+
2215
+
2193
2216
  class StocH(pyagingModel):
2194
2217
  def __init__(self):
2195
2218
  super().__init__()
@@ -2680,74 +2703,6 @@ class GrimAge2LogCRP(pyagingModel):
2680
2703
  return x
2681
2704
 
2682
2705
 
2683
- class DNAmFitAgeGaitF(pyagingModel):
2684
- def __init__(self):
2685
- super().__init__()
2686
-
2687
- def preprocess(self, x):
2688
- if self.reference_values is None:
2689
- return x
2690
- if isinstance(self.reference_values, torch.Tensor):
2691
- reference = self.reference_values.to(device=x.device, dtype=x.dtype)
2692
- else:
2693
- reference = torch.tensor(self.reference_values, device=x.device, dtype=x.dtype)
2694
- return torch.where(torch.isnan(x), reference, x)
2695
-
2696
- def postprocess(self, x):
2697
- return x
2698
-
2699
-
2700
- class DNAmFitAgeGaitM(pyagingModel):
2701
- def __init__(self):
2702
- super().__init__()
2703
-
2704
- def preprocess(self, x):
2705
- if self.reference_values is None:
2706
- return x
2707
- if isinstance(self.reference_values, torch.Tensor):
2708
- reference = self.reference_values.to(device=x.device, dtype=x.dtype)
2709
- else:
2710
- reference = torch.tensor(self.reference_values, device=x.device, dtype=x.dtype)
2711
- return torch.where(torch.isnan(x), reference, x)
2712
-
2713
- def postprocess(self, x):
2714
- return x
2715
-
2716
-
2717
- class DNAmFitAgeGripF(pyagingModel):
2718
- def __init__(self):
2719
- super().__init__()
2720
-
2721
- def preprocess(self, x):
2722
- if self.reference_values is None:
2723
- return x
2724
- if isinstance(self.reference_values, torch.Tensor):
2725
- reference = self.reference_values.to(device=x.device, dtype=x.dtype)
2726
- else:
2727
- reference = torch.tensor(self.reference_values, device=x.device, dtype=x.dtype)
2728
- return torch.where(torch.isnan(x), reference, x)
2729
-
2730
- def postprocess(self, x):
2731
- return x
2732
-
2733
-
2734
- class DNAmFitAgeGripM(pyagingModel):
2735
- def __init__(self):
2736
- super().__init__()
2737
-
2738
- def preprocess(self, x):
2739
- if self.reference_values is None:
2740
- return x
2741
- if isinstance(self.reference_values, torch.Tensor):
2742
- reference = self.reference_values.to(device=x.device, dtype=x.dtype)
2743
- else:
2744
- reference = torch.tensor(self.reference_values, device=x.device, dtype=x.dtype)
2745
- return torch.where(torch.isnan(x), reference, x)
2746
-
2747
- def postprocess(self, x):
2748
- return x
2749
-
2750
-
2751
2706
  class DNAmFitAgeVO2Max(pyagingModel):
2752
2707
  def __init__(self):
2753
2708
  super().__init__()
@@ -3278,6 +3233,7 @@ class TAge(pyagingModel):
3278
3233
  # Cohort-relative clock: ``predict_age`` runs the tAge cohort
3279
3234
  # preprocessing on the raw counts before aligning these features.
3280
3235
  self.cohort_transform = "tage"
3236
+ self.feature_range_data_type = "transcriptomics (relative)"
3281
3237
 
3282
3238
  def preprocess(self, x):
3283
3239
  """Substitute the fitted imputer statistics for genes absent from the sample.
@@ -122,8 +122,12 @@ def _load_clock_impl(clock_name: str, device: str, dir: str, logger, indent_leve
122
122
  try:
123
123
  weights_path = download_clock_weights(clock_name, dir, logger, indent_level=indent_level)
124
124
  except PyAgingResourceNotFoundError as exc:
125
+ from pyaging import __version__
126
+
125
127
  message = (
126
- f"Clock {clock_name} is not available on pyaging. "
128
+ f"Clock {clock_name} is not available on pyaging {__version__}. "
129
+ "This clock may require a newer pyaging release; check PyPI and run "
130
+ "`pip install --upgrade pyaging` if a newer version is available. "
127
131
  "Please refer to the clock names in the clock glossary table "
128
132
  "in the package documentation page: pyaging.readthedocs.io"
129
133
  )
@@ -452,7 +456,7 @@ def check_feature_ranges(
452
456
  try:
453
457
  units, low, high = resolve_feature_bounds(
454
458
  model.features,
455
- model.metadata.get("data_type"),
459
+ getattr(model, "feature_range_data_type", None) or model.metadata.get("data_type"),
456
460
  getattr(model, "feature_units", None),
457
461
  )
458
462
  except Exception as exc:
@@ -159,7 +159,7 @@ def get_feature_ranges(clock_name: str):
159
159
  raise ValueError(f"clock {clock_name!r} has no feature list, so its feature ranges cannot be resolved")
160
160
  records = resolve_feature_ranges(
161
161
  model.features,
162
- model.metadata.get("data_type"),
162
+ getattr(model, "feature_range_data_type", None) or model.metadata.get("data_type"),
163
163
  getattr(model, "feature_units", None),
164
164
  )
165
165
  return pd.DataFrame.from_records(records)
File without changes
File without changes
File without changes
File without changes
File without changes