grnimmuneclock 1.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 (26) hide show
  1. grnimmuneclock-1.0.0/LICENSE +21 -0
  2. grnimmuneclock-1.0.0/MANIFEST.in +5 -0
  3. grnimmuneclock-1.0.0/PKG-INFO +133 -0
  4. grnimmuneclock-1.0.0/README.md +93 -0
  5. grnimmuneclock-1.0.0/grnimmuneclock/__init__.py +63 -0
  6. grnimmuneclock-1.0.0/grnimmuneclock/__version__.py +5 -0
  7. grnimmuneclock-1.0.0/grnimmuneclock/core.py +349 -0
  8. grnimmuneclock-1.0.0/grnimmuneclock/data/consensus_grn_CD4T.csv +12085 -0
  9. grnimmuneclock-1.0.0/grnimmuneclock/data/consensus_grn_CD8T.csv +45553 -0
  10. grnimmuneclock-1.0.0/grnimmuneclock/data/example_data.h5ad +0 -0
  11. grnimmuneclock-1.0.0/grnimmuneclock/helpers.py +439 -0
  12. grnimmuneclock-1.0.0/grnimmuneclock/models/CD4T/features.txt +1395 -0
  13. grnimmuneclock-1.0.0/grnimmuneclock/models/CD4T/metadata.json +12 -0
  14. grnimmuneclock-1.0.0/grnimmuneclock/models/CD4T/model.pkl +0 -0
  15. grnimmuneclock-1.0.0/grnimmuneclock/models/CD8T/features.txt +2790 -0
  16. grnimmuneclock-1.0.0/grnimmuneclock/models/CD8T/metadata.json +12 -0
  17. grnimmuneclock-1.0.0/grnimmuneclock/models/CD8T/model.pkl +0 -0
  18. grnimmuneclock-1.0.0/grnimmuneclock/plotting.py +480 -0
  19. grnimmuneclock-1.0.0/grnimmuneclock/training.py +403 -0
  20. grnimmuneclock-1.0.0/grnimmuneclock.egg-info/PKG-INFO +133 -0
  21. grnimmuneclock-1.0.0/grnimmuneclock.egg-info/SOURCES.txt +24 -0
  22. grnimmuneclock-1.0.0/grnimmuneclock.egg-info/dependency_links.txt +1 -0
  23. grnimmuneclock-1.0.0/grnimmuneclock.egg-info/requires.txt +18 -0
  24. grnimmuneclock-1.0.0/grnimmuneclock.egg-info/top_level.txt +1 -0
  25. grnimmuneclock-1.0.0/pyproject.toml +72 -0
  26. grnimmuneclock-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jalil Nourisa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include LICENSE
2
+ include README.md
3
+ include pyproject.toml
4
+ recursive-include grnimmuneclock/models *.pkl *.txt *.json
5
+ recursive-include grnimmuneclock/data *.h5ad *.csv
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.1
2
+ Name: grnimmuneclock
3
+ Version: 1.0.0
4
+ Summary: Cell-type specific aging clocks for immune cells based on gene regulatory networks
5
+ Author-email: Jalil Nourisa <jalil.nourisa@example.com>
6
+ Project-URL: Homepage, https://github.com/janursa/GRNimmuneClock
7
+ Project-URL: Documentation, https://github.com/janursa/GRNimmuneClock/blob/main/README.md
8
+ Project-URL: Repository, https://github.com/janursa/GRNimmuneClock
9
+ Project-URL: Bug Tracker, https://github.com/janursa/GRNimmuneClock/issues
10
+ Keywords: aging,immune cells,gene regulatory networks,aging clock,bioinformatics
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: anndata>=0.8.0
24
+ Requires-Dist: numpy>=1.20.0
25
+ Requires-Dist: pandas>=1.3.0
26
+ Requires-Dist: scikit-learn>=1.0.0
27
+ Requires-Dist: scipy>=1.7.0
28
+ Requires-Dist: joblib>=1.0.0
29
+ Requires-Dist: matplotlib>=3.4.0
30
+ Requires-Dist: seaborn>=0.11.0
31
+ Requires-Dist: statsmodels>=0.13.0
32
+ Requires-Dist: optuna>=3.0.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
36
+ Requires-Dist: black>=22.0.0; extra == "dev"
37
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
38
+ Requires-Dist: jupyter>=1.0.0; extra == "dev"
39
+ Requires-Dist: ipykernel>=6.0.0; extra == "dev"
40
+
41
+ # GRNimmuneClock
42
+
43
+ **Cell-Type Specific Aging Clocks for Immune Cells**
44
+
45
+ GRNimmuneClock provides pre-trained aging clocks for immune cell types, built using gene regulatory network (GRN) analysis. Predict biological age from gene expression data with cell-type specific models trained on multiple cohorts.
46
+
47
+ ## Features
48
+
49
+ - 🔬 **Cell-Type Specific**: Separate models for CD4T and CD8T cells
50
+ - 📊 **High Performance**: Trained on multiple cohorts with Spearman corr > 0.8.
51
+ - 🧬 **GRN-Based**: Uses gene regulatory network-informed features
52
+ - 🔗 **Network Analysis**: Access GRNs for TF-target exploration
53
+ - 🎨 **Visualization Tools**: Built-in plotting functions for analysis
54
+ - 🚀 **Easy to Use**: Simple Python API
55
+ - 🔧 **Training Pipeline**: Tools to train custom aging clocks
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install grnimmuneclock
61
+ ```
62
+
63
+ Or install from source:
64
+
65
+ ```bash
66
+ git clone https://github.com/janursa/GRNimmuneClock.git
67
+ cd GRNimmuneClock
68
+ pip install -e .
69
+ ```
70
+
71
+ ## Quick Start
72
+
73
+ ```python
74
+ from grnimmuneclock import AgingClock, load_example_data
75
+ import grnimmuneclock.plotting as gplot
76
+
77
+ # Load pre-trained clock for CD4T cells
78
+ clock = AgingClock(cell_type='CD4T')
79
+
80
+ # Load example data
81
+ adata = load_example_data()
82
+
83
+ # Predict biological age
84
+ adata_predicted = clock.predict(adata)
85
+ print(adata_predicted.obs['predicted_age'])
86
+
87
+ # Visualize predictions
88
+ gplot.plot_predicted_vs_actual(adata_predicted, hue='sex')
89
+ ```
90
+ See the tutorial.ipynb for more.
91
+
92
+ ## Supported Cell Types
93
+
94
+ - `CD4T`: CD4+ T cells
95
+ - `CD8T`: CD8+ T cells
96
+
97
+
98
+ ## Model Information
99
+
100
+ All models are:
101
+ - **Algorithm**: Ridge regression with StandardScaler
102
+ - **Features**: Gene expression values (target genes from GRN analysis)
103
+ - **Training**: Multiple cohorts (European, Korean, Japanese, Chinese)
104
+ - **Age Range**: 20-80 years
105
+ - **Species**: Human
106
+ - **Tissue**: Peripheral blood
107
+
108
+
109
+ ## Citation
110
+
111
+ If you use GRNimmuneClock in your research, please cite:
112
+
113
+ ```bibtex
114
+ @article{nourisa2025grnimmuneclock,
115
+ title={TBD},
116
+ author={Nourisa, Jalil and others},
117
+ journal={TBD},
118
+ year={2025}
119
+ }
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT License - see [LICENSE](LICENSE) file for details.
125
+
126
+ ## Contributing
127
+
128
+ Contributions are welcome! Please feel free to submit a Pull Request.
129
+
130
+ ## Support
131
+
132
+ For questions and issues, please open an issue on [GitHub](https://github.com/janursa/GRNimmuneClock/issues).
133
+
@@ -0,0 +1,93 @@
1
+ # GRNimmuneClock
2
+
3
+ **Cell-Type Specific Aging Clocks for Immune Cells**
4
+
5
+ GRNimmuneClock provides pre-trained aging clocks for immune cell types, built using gene regulatory network (GRN) analysis. Predict biological age from gene expression data with cell-type specific models trained on multiple cohorts.
6
+
7
+ ## Features
8
+
9
+ - 🔬 **Cell-Type Specific**: Separate models for CD4T and CD8T cells
10
+ - 📊 **High Performance**: Trained on multiple cohorts with Spearman corr > 0.8.
11
+ - 🧬 **GRN-Based**: Uses gene regulatory network-informed features
12
+ - 🔗 **Network Analysis**: Access GRNs for TF-target exploration
13
+ - 🎨 **Visualization Tools**: Built-in plotting functions for analysis
14
+ - 🚀 **Easy to Use**: Simple Python API
15
+ - 🔧 **Training Pipeline**: Tools to train custom aging clocks
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install grnimmuneclock
21
+ ```
22
+
23
+ Or install from source:
24
+
25
+ ```bash
26
+ git clone https://github.com/janursa/GRNimmuneClock.git
27
+ cd GRNimmuneClock
28
+ pip install -e .
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ from grnimmuneclock import AgingClock, load_example_data
35
+ import grnimmuneclock.plotting as gplot
36
+
37
+ # Load pre-trained clock for CD4T cells
38
+ clock = AgingClock(cell_type='CD4T')
39
+
40
+ # Load example data
41
+ adata = load_example_data()
42
+
43
+ # Predict biological age
44
+ adata_predicted = clock.predict(adata)
45
+ print(adata_predicted.obs['predicted_age'])
46
+
47
+ # Visualize predictions
48
+ gplot.plot_predicted_vs_actual(adata_predicted, hue='sex')
49
+ ```
50
+ See the tutorial.ipynb for more.
51
+
52
+ ## Supported Cell Types
53
+
54
+ - `CD4T`: CD4+ T cells
55
+ - `CD8T`: CD8+ T cells
56
+
57
+
58
+ ## Model Information
59
+
60
+ All models are:
61
+ - **Algorithm**: Ridge regression with StandardScaler
62
+ - **Features**: Gene expression values (target genes from GRN analysis)
63
+ - **Training**: Multiple cohorts (European, Korean, Japanese, Chinese)
64
+ - **Age Range**: 20-80 years
65
+ - **Species**: Human
66
+ - **Tissue**: Peripheral blood
67
+
68
+
69
+ ## Citation
70
+
71
+ If you use GRNimmuneClock in your research, please cite:
72
+
73
+ ```bibtex
74
+ @article{nourisa2025grnimmuneclock,
75
+ title={TBD},
76
+ author={Nourisa, Jalil and others},
77
+ journal={TBD},
78
+ year={2025}
79
+ }
80
+ ```
81
+
82
+ ## License
83
+
84
+ MIT License - see [LICENSE](LICENSE) file for details.
85
+
86
+ ## Contributing
87
+
88
+ Contributions are welcome! Please feel free to submit a Pull Request.
89
+
90
+ ## Support
91
+
92
+ For questions and issues, please open an issue on [GitHub](https://github.com/janursa/GRNimmuneClock/issues).
93
+
@@ -0,0 +1,63 @@
1
+ """
2
+ GRNimmuneClock: Cell-Type Specific Immune Aging Clocks
3
+
4
+ This package provides pre-trained aging clocks for immune cell types,
5
+ built using gene regulatory network (GRN) analysis.
6
+
7
+ Example
8
+ -------
9
+ >>> from grnimmuneclock import AgingClock, load_example_data
10
+ >>>
11
+ >>> # Load pre-trained clock
12
+ >>> clock = AgingClock(cell_type='CD4T')
13
+ >>>
14
+ >>> # Load example data
15
+ >>> adata = load_example_data()
16
+ >>>
17
+ >>> # Predict ages
18
+ >>> adata_predicted = clock.predict(adata)
19
+ >>> print(adata_predicted.obs['predicted_age'])
20
+ """
21
+
22
+ from .core import AgingClock, load_example_data
23
+ from .__version__ import __version__, __author__, __license__
24
+ from . import plotting
25
+ from .training import (
26
+ train_aging_clock,
27
+ build_model,
28
+ merge_training_data,
29
+ evaluate_cv_performance,
30
+ tune_ridge_params
31
+ )
32
+ from .helpers import (
33
+ predict_age,
34
+ format_data,
35
+ retrieve_function,
36
+ save_function,
37
+ merge_adata,
38
+ evaluate_groupwise_median,
39
+ load_consensus_grn,
40
+ prepare_user_data
41
+ )
42
+
43
+ __all__ = [
44
+ 'AgingClock',
45
+ 'load_example_data',
46
+ 'plotting',
47
+ 'train_aging_clock',
48
+ 'build_model',
49
+ 'merge_training_data',
50
+ 'evaluate_cv_performance',
51
+ 'tune_ridge_params',
52
+ 'predict_age',
53
+ 'format_data',
54
+ 'retrieve_function',
55
+ 'save_function',
56
+ 'merge_adata',
57
+ 'evaluate_groupwise_median',
58
+ 'load_consensus_grn',
59
+ 'prepare_user_data',
60
+ '__version__',
61
+ '__author__',
62
+ '__license__'
63
+ ]
@@ -0,0 +1,5 @@
1
+ """Version information for GRNimmuneClock."""
2
+
3
+ __version__ = "1.0.0"
4
+ __author__ = "Jalil Nourisa"
5
+ __license__ = "MIT"
@@ -0,0 +1,349 @@
1
+ """
2
+ Core module for GRNimmuneClock - Cell-type specific immune aging clocks.
3
+ """
4
+
5
+ import os
6
+ import warnings
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Optional, Union, List
10
+ import numpy as np
11
+ import pandas as pd
12
+ import joblib
13
+ from scipy import sparse
14
+ from scipy.sparse import issparse
15
+ import anndata as ad
16
+ from anndata import AnnData
17
+
18
+
19
+ class AgingClock:
20
+ """
21
+ Cell-type specific aging clock for immune cells.
22
+
23
+ This class provides an interface to load pre-trained aging clock models
24
+ and predict biological age from gene expression data.
25
+
26
+ Parameters
27
+ ----------
28
+ cell_type : str
29
+ Cell type for the aging clock. Options: 'CD4T', 'CD8T', 'MONO', 'B', 'NK'
30
+ version : str, optional
31
+ Version of the model to load (default: 'all_data')
32
+ model_dir : str or Path, optional
33
+ Custom directory containing model files. If None, uses bundled models.
34
+
35
+ Attributes
36
+ ----------
37
+ cell_type : str
38
+ The cell type this clock is trained for
39
+ model : sklearn.Pipeline
40
+ The trained model (StandardScaler + Ridge regression)
41
+ feature_names : np.ndarray
42
+ Names of genes used as features
43
+ metadata : dict
44
+ Model metadata including training info and performance metrics
45
+
46
+ Examples
47
+ --------
48
+ >>> from grnimmuneclock import AgingClock
49
+ >>> clock = AgingClock(cell_type='CD4T')
50
+ >>> adata_predicted = clock.predict(adata)
51
+ >>> print(adata_predicted.obs['predicted_age'])
52
+ """
53
+
54
+ SUPPORTED_CELL_TYPES = ['CD4T', 'CD8T', 'MONO', 'B', 'NK']
55
+
56
+ def __init__(
57
+ self,
58
+ cell_type: str,
59
+ version: str = 'all_data',
60
+ model_dir: Optional[Union[str, Path]] = None
61
+ ):
62
+ if cell_type not in self.SUPPORTED_CELL_TYPES:
63
+ raise ValueError(
64
+ f"Unsupported cell type: {cell_type}. "
65
+ f"Choose from {self.SUPPORTED_CELL_TYPES}"
66
+ )
67
+
68
+ self.cell_type = cell_type
69
+ self.version = version
70
+ self.feature_type = 'gene_expression'
71
+ self.data_type = 'bulk'
72
+ self.reg_type = 'ridge'
73
+
74
+ # Determine model directory
75
+ if model_dir is None:
76
+ # Use bundled models
77
+ package_dir = Path(__file__).parent
78
+ model_dir = package_dir / 'models' / cell_type
79
+ else:
80
+ model_dir = Path(model_dir)
81
+
82
+ if not model_dir.exists():
83
+ raise FileNotFoundError(f"Model directory not found: {model_dir}")
84
+
85
+ self.model_dir = model_dir
86
+
87
+ # Load model and metadata
88
+ self._load_model()
89
+ self._load_metadata()
90
+
91
+ def _load_model(self):
92
+ """Load the trained model and feature names."""
93
+ model_path = self.model_dir / "model.pkl"
94
+ features_path = self.model_dir / "features.txt"
95
+
96
+ if not model_path.exists():
97
+ raise FileNotFoundError(f"Model file not found: {model_path}")
98
+ if not features_path.exists():
99
+ raise FileNotFoundError(f"Features file not found: {features_path}")
100
+
101
+ # Load model
102
+ self.model = joblib.load(model_path)
103
+
104
+ # Load feature names
105
+ self.feature_names = np.loadtxt(features_path, dtype=str)
106
+
107
+ def _load_metadata(self):
108
+ """Load model metadata if available."""
109
+ metadata_path = self.model_dir / 'metadata.json'
110
+
111
+ if metadata_path.exists():
112
+ with open(metadata_path, 'r') as f:
113
+ self.metadata = json.load(f)
114
+ else:
115
+ # Create basic metadata
116
+ self.metadata = {
117
+ 'cell_type': self.cell_type,
118
+ 'version': self.version,
119
+ 'feature_type': self.feature_type,
120
+ 'data_type': self.data_type,
121
+ 'reg_type': self.reg_type,
122
+ 'n_features': len(self.feature_names)
123
+ }
124
+
125
+ def _align_feature_space(self, adata: AnnData) -> AnnData:
126
+ """
127
+ Align input data features to match the model's feature space.
128
+
129
+ Missing features will be filled with zeros.
130
+
131
+ Parameters
132
+ ----------
133
+ adata : AnnData
134
+ Input data with gene expression
135
+
136
+ Returns
137
+ -------
138
+ AnnData
139
+ Data with aligned features
140
+ """
141
+ var_names = np.array(adata.var.index.tolist())
142
+ var_index = {gene: i for i, gene in enumerate(var_names)}
143
+
144
+ # Collect indices or mark as -1 for missing
145
+ idxs = np.array([var_index.get(gene, -1) for gene in self.feature_names])
146
+
147
+ # Create a matrix with correct shape
148
+ rows = adata.obs.shape[0]
149
+ cols = len(self.feature_names)
150
+ X_aligned = sparse.lil_matrix((rows, cols))
151
+
152
+ # Fill in available gene columns
153
+ present = idxs != -1
154
+ if present.sum() > 0:
155
+ X_aligned[:, present] = adata[:].X[:, idxs[present]]
156
+
157
+ # Convert to CSR for efficiency
158
+ X_aligned = X_aligned.tocsr()
159
+
160
+ # Create new AnnData object
161
+ new_adata = AnnData(
162
+ X=X_aligned,
163
+ obs=adata.obs.copy(),
164
+ var={"gene_symbols": self.feature_names},
165
+ )
166
+ new_adata.var_names = self.feature_names
167
+
168
+ # Warn about missing features
169
+ n_missing = (~present).sum()
170
+ if n_missing > 0:
171
+ coverage = present.sum() / len(self.feature_names)
172
+ warnings.warn(
173
+ f"{n_missing} features ({(1-coverage)*100:.1f}%) missing from input data. "
174
+ f"They will be set to zero."
175
+ )
176
+
177
+ return new_adata
178
+
179
+ def _validate_input(self, adata: AnnData):
180
+ """Validate input data format."""
181
+ if not isinstance(adata, AnnData):
182
+ raise TypeError("Input must be an AnnData object")
183
+
184
+ if adata.X is None:
185
+ raise ValueError("Input AnnData has no expression matrix (X)")
186
+
187
+ if adata.n_obs == 0:
188
+ raise ValueError("Input AnnData has no observations (cells/samples)")
189
+
190
+ if adata.n_vars == 0:
191
+ raise ValueError("Input AnnData has no variables (genes)")
192
+
193
+ def predict(self, adata: AnnData, return_adata: bool = True) -> Union[AnnData, np.ndarray]:
194
+ """
195
+ Predict biological age from gene expression data.
196
+
197
+ Parameters
198
+ ----------
199
+ adata : AnnData
200
+ Input data with gene expression in .X
201
+ Rows are samples/cells, columns are genes
202
+ return_adata : bool, optional
203
+ If True, return AnnData with 'predicted_age' in .obs
204
+ If False, return numpy array of predictions (default: True)
205
+
206
+ Returns
207
+ -------
208
+ AnnData or np.ndarray
209
+ If return_adata=True: Input AnnData with 'predicted_age' column added to .obs
210
+ If return_adata=False: Array of predicted ages
211
+
212
+ Examples
213
+ --------
214
+ >>> clock = AgingClock(cell_type='CD4T')
215
+ >>> adata_with_predictions = clock.predict(adata)
216
+ >>> ages = clock.predict(adata, return_adata=False)
217
+ """
218
+ # Validate input
219
+ self._validate_input(adata)
220
+
221
+ # Align features to model's feature space
222
+ adata_aligned = self._align_feature_space(adata)
223
+
224
+ # Get expression matrix
225
+ X = adata_aligned.X.copy()
226
+ if issparse(X):
227
+ X = X.toarray()
228
+
229
+ # Predict
230
+ predicted_age = self.model.predict(X)
231
+
232
+ if return_adata:
233
+ # Add predictions to original adata
234
+ adata.obs['predicted_age'] = predicted_age.copy()
235
+
236
+ # Calculate age acceleration if actual age is available
237
+ if 'age' in adata.obs.columns:
238
+ adata.obs['age_acceleration'] = adata.obs['predicted_age'] - adata.obs['age']
239
+
240
+ return adata
241
+ else:
242
+ return predicted_age
243
+
244
+ def predict_batch(
245
+ self,
246
+ adata_list: List[AnnData],
247
+ return_dataframe: bool = True
248
+ ) -> Union[pd.DataFrame, List[np.ndarray]]:
249
+ """
250
+ Predict ages for multiple AnnData objects.
251
+
252
+ Parameters
253
+ ----------
254
+ adata_list : list of AnnData
255
+ List of AnnData objects to predict on
256
+ return_dataframe : bool, optional
257
+ If True, return consolidated DataFrame (default: True)
258
+ If False, return list of prediction arrays
259
+
260
+ Returns
261
+ -------
262
+ pd.DataFrame or list of np.ndarray
263
+ Predictions for all inputs
264
+ """
265
+ predictions = []
266
+
267
+ for i, adata in enumerate(adata_list):
268
+ pred = self.predict(adata, return_adata=False)
269
+ predictions.append(pred)
270
+
271
+ if return_dataframe:
272
+ # Combine into DataFrame
273
+ df_list = []
274
+ for i, (adata, pred) in enumerate(zip(adata_list, predictions)):
275
+ df = pd.DataFrame({
276
+ 'predicted_age': pred,
277
+ 'batch': i
278
+ })
279
+ # Add other obs columns if available
280
+ for col in adata.obs.columns:
281
+ df[col] = adata.obs[col].values
282
+ df_list.append(df)
283
+
284
+ return pd.concat(df_list, ignore_index=True)
285
+ else:
286
+ return predictions
287
+
288
+ def __repr__(self):
289
+ return (
290
+ f"AgingClock(cell_type='{self.cell_type}', "
291
+ f"version='{self.version}', "
292
+ f"n_features={len(self.feature_names)})"
293
+ )
294
+
295
+ def get_feature_importance(self, top_n: Optional[int] = None) -> pd.DataFrame:
296
+ """
297
+ Get feature importances (model coefficients).
298
+
299
+ Parameters
300
+ ----------
301
+ top_n : int, optional
302
+ Return only top N features by absolute importance
303
+ If None, return all features
304
+
305
+ Returns
306
+ -------
307
+ pd.DataFrame
308
+ DataFrame with columns: 'feature', 'coefficient'
309
+ Sorted by absolute coefficient value (descending)
310
+ """
311
+ # Get coefficients from Ridge model
312
+ coefs = self.model.named_steps['ridge'].coef_
313
+
314
+ # Create DataFrame
315
+ df = pd.DataFrame({
316
+ 'feature': self.feature_names,
317
+ 'coefficient': coefs
318
+ })
319
+
320
+ # Sort by absolute value
321
+ df['abs_coefficient'] = np.abs(df['coefficient'])
322
+ df = df.sort_values('abs_coefficient', ascending=False)
323
+ df = df.drop('abs_coefficient', axis=1)
324
+
325
+ if top_n is not None:
326
+ df = df.head(top_n)
327
+
328
+ return df.reset_index(drop=True)
329
+
330
+
331
+ def load_example_data() -> AnnData:
332
+ """
333
+ Load example data for testing the aging clock.
334
+
335
+ Returns
336
+ -------
337
+ AnnData
338
+ Small example dataset from a single donor
339
+ """
340
+ package_dir = Path(__file__).parent
341
+ data_path = package_dir / 'data' / 'example_data.h5ad'
342
+
343
+ if not data_path.exists():
344
+ raise FileNotFoundError(
345
+ f"Example data not found at {data_path}. "
346
+ "Please ensure the package is properly installed."
347
+ )
348
+
349
+ return ad.read_h5ad(data_path)