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.
- grnimmuneclock-1.0.0/LICENSE +21 -0
- grnimmuneclock-1.0.0/MANIFEST.in +5 -0
- grnimmuneclock-1.0.0/PKG-INFO +133 -0
- grnimmuneclock-1.0.0/README.md +93 -0
- grnimmuneclock-1.0.0/grnimmuneclock/__init__.py +63 -0
- grnimmuneclock-1.0.0/grnimmuneclock/__version__.py +5 -0
- grnimmuneclock-1.0.0/grnimmuneclock/core.py +349 -0
- grnimmuneclock-1.0.0/grnimmuneclock/data/consensus_grn_CD4T.csv +12085 -0
- grnimmuneclock-1.0.0/grnimmuneclock/data/consensus_grn_CD8T.csv +45553 -0
- grnimmuneclock-1.0.0/grnimmuneclock/data/example_data.h5ad +0 -0
- grnimmuneclock-1.0.0/grnimmuneclock/helpers.py +439 -0
- grnimmuneclock-1.0.0/grnimmuneclock/models/CD4T/features.txt +1395 -0
- grnimmuneclock-1.0.0/grnimmuneclock/models/CD4T/metadata.json +12 -0
- grnimmuneclock-1.0.0/grnimmuneclock/models/CD4T/model.pkl +0 -0
- grnimmuneclock-1.0.0/grnimmuneclock/models/CD8T/features.txt +2790 -0
- grnimmuneclock-1.0.0/grnimmuneclock/models/CD8T/metadata.json +12 -0
- grnimmuneclock-1.0.0/grnimmuneclock/models/CD8T/model.pkl +0 -0
- grnimmuneclock-1.0.0/grnimmuneclock/plotting.py +480 -0
- grnimmuneclock-1.0.0/grnimmuneclock/training.py +403 -0
- grnimmuneclock-1.0.0/grnimmuneclock.egg-info/PKG-INFO +133 -0
- grnimmuneclock-1.0.0/grnimmuneclock.egg-info/SOURCES.txt +24 -0
- grnimmuneclock-1.0.0/grnimmuneclock.egg-info/dependency_links.txt +1 -0
- grnimmuneclock-1.0.0/grnimmuneclock.egg-info/requires.txt +18 -0
- grnimmuneclock-1.0.0/grnimmuneclock.egg-info/top_level.txt +1 -0
- grnimmuneclock-1.0.0/pyproject.toml +72 -0
- 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,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,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)
|