tppis 0.1.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.
- tppis-0.1.0/.gitignore +43 -0
- tppis-0.1.0/CHANGELOG.md +15 -0
- tppis-0.1.0/CITATION.cff +25 -0
- tppis-0.1.0/LICENSE +21 -0
- tppis-0.1.0/PKG-INFO +154 -0
- tppis-0.1.0/README.md +88 -0
- tppis-0.1.0/pyproject.toml +127 -0
- tppis-0.1.0/tests/__init__.py +0 -0
- tppis-0.1.0/tests/conftest.py +39 -0
- tppis-0.1.0/tests/reference.py +103 -0
- tppis-0.1.0/tests/test_criteria.py +59 -0
- tppis-0.1.0/tests/test_datasets.py +52 -0
- tppis-0.1.0/tests/test_equivalences.py +49 -0
- tppis-0.1.0/tests/test_kernel.py +120 -0
- tppis-0.1.0/tests/test_metrics.py +30 -0
- tppis-0.1.0/tests/test_screeners.py +77 -0
- tppis-0.1.0/tests/test_sklearn_api.py +63 -0
- tppis-0.1.0/tests/test_spectral.py +64 -0
- tppis-0.1.0/tests/test_tuning.py +69 -0
- tppis-0.1.0/tests/test_validation_and_edges.py +150 -0
- tppis-0.1.0/tppis/__init__.py +29 -0
- tppis-0.1.0/tppis/_validation.py +150 -0
- tppis-0.1.0/tppis/criteria.py +220 -0
- tppis-0.1.0/tppis/datasets/__init__.py +26 -0
- tppis-0.1.0/tppis/datasets/real.py +97 -0
- tppis-0.1.0/tppis/datasets/simulate.py +188 -0
- tppis-0.1.0/tppis/exceptions.py +56 -0
- tppis-0.1.0/tppis/factors.py +37 -0
- tppis-0.1.0/tppis/kernel.py +159 -0
- tppis-0.1.0/tppis/metrics.py +127 -0
- tppis-0.1.0/tppis/py.typed +0 -0
- tppis-0.1.0/tppis/screeners.py +504 -0
- tppis-0.1.0/tppis/spectral.py +183 -0
- tppis-0.1.0/tppis/tuning.py +309 -0
tppis-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg-info/
|
|
7
|
+
.eggs/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
.env
|
|
13
|
+
|
|
14
|
+
# Tests and typing
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
.hypothesis/
|
|
21
|
+
|
|
22
|
+
# Editors
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
*.swp
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Local data caches (real datasets are fetched, not shipped)
|
|
32
|
+
.tppis_data/
|
|
33
|
+
|
|
34
|
+
# Working notes and paper copies, not part of the library
|
|
35
|
+
discussions/
|
|
36
|
+
research_paper_pdf/
|
|
37
|
+
yet_to_do.md
|
|
38
|
+
test_results/
|
|
39
|
+
tools/
|
|
40
|
+
|
|
41
|
+
# Simulation outputs
|
|
42
|
+
reproduction/results/*
|
|
43
|
+
!reproduction/results/.gitkeep
|
tppis-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-09-26
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- SIS, FPSIS, FPSIS-BIC, PPIS and TPPIS estimators implementing Tanaka and Matsui (2023).
|
|
12
|
+
- Shared spectral kernel, BIC-type criterion, and joint grid search over `d`, `alpha` and `k`.
|
|
13
|
+
- Simulation generators for the paper's Examples 1 to 4, plus screening metrics.
|
|
14
|
+
- scikit-learn compatible `fit` / `transform` / `get_support` API.
|
|
15
|
+
|
tppis-0.1.0/CITATION.cff
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
title: tppis
|
|
3
|
+
message: If you use this software, please cite both the method paper and this package.
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: M
|
|
7
|
+
given-names: Mohammed Aswath
|
|
8
|
+
email: mohammed.aswath07@gmail.com
|
|
9
|
+
repository-code: https://github.com/Mohammed-Aswath/tppis
|
|
10
|
+
license: MIT
|
|
11
|
+
version: 0.1.0
|
|
12
|
+
abstract: >
|
|
13
|
+
Open-source Python implementation of Truncated Preconditioned Profiled
|
|
14
|
+
Independence Screening (TPPIS) as specified by Tanaka and Matsui (2023).
|
|
15
|
+
references:
|
|
16
|
+
- type: article
|
|
17
|
+
title: Variable screening using factor analysis for high-dimensional data with multicollinearity
|
|
18
|
+
authors:
|
|
19
|
+
- family-names: Tanaka
|
|
20
|
+
given-names: Shuntaro
|
|
21
|
+
- family-names: Matsui
|
|
22
|
+
given-names: Hidetoshi
|
|
23
|
+
year: 2023
|
|
24
|
+
doi: 10.48550/arXiv.2306.05702
|
|
25
|
+
url: https://arxiv.org/abs/2306.05702
|
tppis-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohammed Aswath M
|
|
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.
|
tppis-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tppis
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Truncated Preconditioned Profiled Independence Screening for high-dimensional data with multicollinearity.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Mohammed-Aswath/tppis
|
|
6
|
+
Project-URL: Documentation, https://github.com/Mohammed-Aswath/tppis
|
|
7
|
+
Project-URL: Source, https://github.com/Mohammed-Aswath/tppis
|
|
8
|
+
Project-URL: Issues, https://github.com/Mohammed-Aswath/tppis/issues
|
|
9
|
+
Author-email: Mohammed Aswath M <mohammed.aswath07@gmail.com>
|
|
10
|
+
Maintainer-email: Mohammed Aswath M <mohammed.aswath07@gmail.com>
|
|
11
|
+
License: MIT License
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2026 Mohammed Aswath M
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Keywords: SIS,TPPIS,factor-analysis,feature-selection,high-dimensional,multicollinearity,variable-screening
|
|
34
|
+
Classifier: Development Status :: 3 - Alpha
|
|
35
|
+
Classifier: Intended Audience :: Science/Research
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
43
|
+
Classifier: Typing :: Typed
|
|
44
|
+
Requires-Python: >=3.10
|
|
45
|
+
Requires-Dist: numpy>=1.24
|
|
46
|
+
Requires-Dist: scikit-learn>=1.6
|
|
47
|
+
Requires-Dist: scipy>=1.10
|
|
48
|
+
Provides-Extra: datasets
|
|
49
|
+
Requires-Dist: pandas>=2.0; extra == 'datasets'
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: hypothesis>=6.82; extra == 'dev'
|
|
52
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
53
|
+
Requires-Dist: pandas>=2.0; extra == 'dev'
|
|
54
|
+
Requires-Dist: pre-commit>=3.6; extra == 'dev'
|
|
55
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
56
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
57
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
58
|
+
Provides-Extra: docs
|
|
59
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
60
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
61
|
+
Provides-Extra: repro
|
|
62
|
+
Requires-Dist: joblib>=1.3; extra == 'repro'
|
|
63
|
+
Requires-Dist: matplotlib>=3.7; extra == 'repro'
|
|
64
|
+
Requires-Dist: pandas>=2.0; extra == 'repro'
|
|
65
|
+
Description-Content-Type: text/markdown
|
|
66
|
+
|
|
67
|
+
# tppis
|
|
68
|
+
|
|
69
|
+
Open-source Python implementation of **Truncated Preconditioned Profiled Independence Screening** (TPPIS) from Tanaka and Matsui (2023), plus the baselines SIS, FPSIS (and FPSIS-BIC), and PPIS.
|
|
70
|
+
|
|
71
|
+
The method screens variables in high-dimensional regression when predictors are strongly multicollinear. A factor-analysis transform removes the common factors; TPPIS then *truncates* the tail of that transform so the unique-factor signal is not washed out.
|
|
72
|
+
|
|
73
|
+
**Maintainer:** [Mohammed Aswath M](https://github.com/Mohammed-Aswath) · [mohammed.aswath07@gmail.com](mailto:mohammed.aswath07@gmail.com) · issues: [github.com/Mohammed-Aswath/tppis](https://github.com/Mohammed-Aswath/tppis/issues)
|
|
74
|
+
|
|
75
|
+
## Install
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install tppis
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The package requires Python 3.10+, NumPy, SciPy, and **scikit-learn 1.6+**.
|
|
82
|
+
|
|
83
|
+
From a clone of this repository:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python -m venv .venv
|
|
87
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
88
|
+
pip install -e ".[dev]"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Requires Python 3.10+, NumPy, SciPy, and scikit-learn 1.6+.
|
|
92
|
+
|
|
93
|
+
## Quickstart
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from tppis import TPPIS, make_example1
|
|
97
|
+
|
|
98
|
+
data = make_example1(n=100, p=80, phi=0.7, seed=0)
|
|
99
|
+
est = TPPIS().fit(data.X, data.y)
|
|
100
|
+
|
|
101
|
+
est.get_support(indices=True) # selected columns, ranking order
|
|
102
|
+
est.d_, est.alpha_, est.k_ # BIC-chosen parameters
|
|
103
|
+
est.bic_
|
|
104
|
+
est.grid_ # every (d, alpha, k, bic) evaluated
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Fixed parameters, no search:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
TPPIS(d=20, alpha=0.4, k=10).fit(data.X, data.y)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Inside a scikit-learn pipeline:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from sklearn.linear_model import LinearRegression
|
|
117
|
+
from sklearn.pipeline import Pipeline
|
|
118
|
+
from tppis import TPPIS
|
|
119
|
+
|
|
120
|
+
pipe = Pipeline([
|
|
121
|
+
("select", TPPIS(d=8, alpha=0.6, k=10)),
|
|
122
|
+
("lm", LinearRegression()),
|
|
123
|
+
])
|
|
124
|
+
pipe.fit(data.X, data.y)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The functional entry point `screen(X, y, method="tppis")` returns the same fitted estimator.
|
|
128
|
+
|
|
129
|
+
## When to use which method
|
|
130
|
+
|
|
131
|
+
| Method | Transform | How `d` is chosen |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| SIS | none (`X.T @ y`) | — |
|
|
134
|
+
| FPSIS | project out leading `d` factors | singular-value ratio (3) |
|
|
135
|
+
| FPSIS-BIC | same | BIC grid |
|
|
136
|
+
| PPIS | Puffer whitening of the tail | singular-value ratio (3) |
|
|
137
|
+
| TPPIS | PPIS with the tail truncated at `[n α]` | joint BIC over `d`, `α`, `k` |
|
|
138
|
+
|
|
139
|
+
TPPIS at `alpha=1` is exactly PPIS. That identity is tested.
|
|
140
|
+
|
|
141
|
+
## Citation
|
|
142
|
+
|
|
143
|
+
If you use the method, cite the paper. If you use this software, cite the package as well.
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Tanaka, S. and Matsui, H. (2023). Variable screening using factor analysis
|
|
147
|
+
for high-dimensional data with multicollinearity. arXiv:2306.05702.
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
See [`CITATION.cff`](CITATION.cff).
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT. See [`LICENSE`](LICENSE).
|
tppis-0.1.0/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# tppis
|
|
2
|
+
|
|
3
|
+
Open-source Python implementation of **Truncated Preconditioned Profiled Independence Screening** (TPPIS) from Tanaka and Matsui (2023), plus the baselines SIS, FPSIS (and FPSIS-BIC), and PPIS.
|
|
4
|
+
|
|
5
|
+
The method screens variables in high-dimensional regression when predictors are strongly multicollinear. A factor-analysis transform removes the common factors; TPPIS then *truncates* the tail of that transform so the unique-factor signal is not washed out.
|
|
6
|
+
|
|
7
|
+
**Maintainer:** [Mohammed Aswath M](https://github.com/Mohammed-Aswath) · [mohammed.aswath07@gmail.com](mailto:mohammed.aswath07@gmail.com) · issues: [github.com/Mohammed-Aswath/tppis](https://github.com/Mohammed-Aswath/tppis/issues)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install tppis
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The package requires Python 3.10+, NumPy, SciPy, and **scikit-learn 1.6+**.
|
|
16
|
+
|
|
17
|
+
From a clone of this repository:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python -m venv .venv
|
|
21
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
22
|
+
pip install -e ".[dev]"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Requires Python 3.10+, NumPy, SciPy, and scikit-learn 1.6+.
|
|
26
|
+
|
|
27
|
+
## Quickstart
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from tppis import TPPIS, make_example1
|
|
31
|
+
|
|
32
|
+
data = make_example1(n=100, p=80, phi=0.7, seed=0)
|
|
33
|
+
est = TPPIS().fit(data.X, data.y)
|
|
34
|
+
|
|
35
|
+
est.get_support(indices=True) # selected columns, ranking order
|
|
36
|
+
est.d_, est.alpha_, est.k_ # BIC-chosen parameters
|
|
37
|
+
est.bic_
|
|
38
|
+
est.grid_ # every (d, alpha, k, bic) evaluated
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Fixed parameters, no search:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
TPPIS(d=20, alpha=0.4, k=10).fit(data.X, data.y)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Inside a scikit-learn pipeline:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from sklearn.linear_model import LinearRegression
|
|
51
|
+
from sklearn.pipeline import Pipeline
|
|
52
|
+
from tppis import TPPIS
|
|
53
|
+
|
|
54
|
+
pipe = Pipeline([
|
|
55
|
+
("select", TPPIS(d=8, alpha=0.6, k=10)),
|
|
56
|
+
("lm", LinearRegression()),
|
|
57
|
+
])
|
|
58
|
+
pipe.fit(data.X, data.y)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The functional entry point `screen(X, y, method="tppis")` returns the same fitted estimator.
|
|
62
|
+
|
|
63
|
+
## When to use which method
|
|
64
|
+
|
|
65
|
+
| Method | Transform | How `d` is chosen |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| SIS | none (`X.T @ y`) | — |
|
|
68
|
+
| FPSIS | project out leading `d` factors | singular-value ratio (3) |
|
|
69
|
+
| FPSIS-BIC | same | BIC grid |
|
|
70
|
+
| PPIS | Puffer whitening of the tail | singular-value ratio (3) |
|
|
71
|
+
| TPPIS | PPIS with the tail truncated at `[n α]` | joint BIC over `d`, `α`, `k` |
|
|
72
|
+
|
|
73
|
+
TPPIS at `alpha=1` is exactly PPIS. That identity is tested.
|
|
74
|
+
|
|
75
|
+
## Citation
|
|
76
|
+
|
|
77
|
+
If you use the method, cite the paper. If you use this software, cite the package as well.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Tanaka, S. and Matsui, H. (2023). Variable screening using factor analysis
|
|
81
|
+
for high-dimensional data with multicollinearity. arXiv:2306.05702.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
See [`CITATION.cff`](CITATION.cff).
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tppis"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Truncated Preconditioned Profiled Independence Screening for high-dimensional data with multicollinearity."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Mohammed Aswath M", email = "mohammed.aswath07@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
maintainers = [
|
|
16
|
+
{ name = "Mohammed Aswath M", email = "mohammed.aswath07@gmail.com" },
|
|
17
|
+
]
|
|
18
|
+
keywords = [
|
|
19
|
+
"feature-selection",
|
|
20
|
+
"variable-screening",
|
|
21
|
+
"high-dimensional",
|
|
22
|
+
"multicollinearity",
|
|
23
|
+
"factor-analysis",
|
|
24
|
+
"SIS",
|
|
25
|
+
"TPPIS",
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 3 - Alpha",
|
|
29
|
+
"Intended Audience :: Science/Research",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
37
|
+
"Typing :: Typed",
|
|
38
|
+
]
|
|
39
|
+
dependencies = [
|
|
40
|
+
"numpy>=1.24",
|
|
41
|
+
"scipy>=1.10",
|
|
42
|
+
"scikit-learn>=1.6",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
datasets = [
|
|
47
|
+
"pandas>=2.0",
|
|
48
|
+
]
|
|
49
|
+
repro = [
|
|
50
|
+
"pandas>=2.0",
|
|
51
|
+
"matplotlib>=3.7",
|
|
52
|
+
"joblib>=1.3",
|
|
53
|
+
]
|
|
54
|
+
dev = [
|
|
55
|
+
"pytest>=7.4",
|
|
56
|
+
"pytest-cov>=4.1",
|
|
57
|
+
"hypothesis>=6.82",
|
|
58
|
+
"ruff>=0.5",
|
|
59
|
+
"mypy>=1.8",
|
|
60
|
+
"pre-commit>=3.6",
|
|
61
|
+
"pandas>=2.0",
|
|
62
|
+
]
|
|
63
|
+
docs = [
|
|
64
|
+
"mkdocs-material>=9.5",
|
|
65
|
+
"mkdocstrings[python]>=0.24",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[project.urls]
|
|
69
|
+
Homepage = "https://github.com/Mohammed-Aswath/tppis"
|
|
70
|
+
Documentation = "https://github.com/Mohammed-Aswath/tppis"
|
|
71
|
+
Source = "https://github.com/Mohammed-Aswath/tppis"
|
|
72
|
+
Issues = "https://github.com/Mohammed-Aswath/tppis/issues"
|
|
73
|
+
|
|
74
|
+
[tool.hatch.build.targets.sdist]
|
|
75
|
+
include = [
|
|
76
|
+
"/tppis",
|
|
77
|
+
"/tests",
|
|
78
|
+
"/README.md",
|
|
79
|
+
"/LICENSE",
|
|
80
|
+
"/CHANGELOG.md",
|
|
81
|
+
"/CITATION.cff",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
[tool.hatch.build.targets.wheel]
|
|
85
|
+
packages = ["tppis"]
|
|
86
|
+
|
|
87
|
+
[tool.ruff]
|
|
88
|
+
line-length = 88
|
|
89
|
+
target-version = "py310"
|
|
90
|
+
src = ["tppis", "tests"]
|
|
91
|
+
|
|
92
|
+
[tool.ruff.lint]
|
|
93
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
94
|
+
|
|
95
|
+
[tool.ruff.format]
|
|
96
|
+
quote-style = "double"
|
|
97
|
+
|
|
98
|
+
[tool.mypy]
|
|
99
|
+
python_version = "3.12"
|
|
100
|
+
strict = true
|
|
101
|
+
packages = ["tppis"]
|
|
102
|
+
warn_unused_ignores = true
|
|
103
|
+
|
|
104
|
+
[[tool.mypy.overrides]]
|
|
105
|
+
module = ["sklearn.*", "scipy.*"]
|
|
106
|
+
ignore_missing_imports = true
|
|
107
|
+
|
|
108
|
+
[tool.pytest.ini_options]
|
|
109
|
+
testpaths = ["tests"]
|
|
110
|
+
addopts = "-q --strict-markers"
|
|
111
|
+
filterwarnings = [
|
|
112
|
+
"ignore::UserWarning",
|
|
113
|
+
"ignore::FutureWarning",
|
|
114
|
+
"ignore::DeprecationWarning",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
[tool.coverage.run]
|
|
118
|
+
source = ["tppis"]
|
|
119
|
+
branch = true
|
|
120
|
+
|
|
121
|
+
[tool.coverage.report]
|
|
122
|
+
fail_under = 90
|
|
123
|
+
show_missing = true
|
|
124
|
+
exclude_lines = [
|
|
125
|
+
"pragma: no cover",
|
|
126
|
+
"if TYPE_CHECKING:",
|
|
127
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Shared fixtures.
|
|
2
|
+
|
|
3
|
+
Every random draw here is seeded explicitly, so that a failure is reproducible from the
|
|
4
|
+
test name alone.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def multicollinear_design(n_samples, n_features, n_factors=3, seed=0):
|
|
14
|
+
"""A design with a few strong common factors above a noise floor."""
|
|
15
|
+
rng = np.random.default_rng(seed)
|
|
16
|
+
Z = rng.standard_normal((n_samples, n_factors))
|
|
17
|
+
B = rng.standard_normal((n_factors, n_features))
|
|
18
|
+
return Z @ B + rng.standard_normal((n_samples, n_features))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def small_problem():
|
|
23
|
+
"""A wide, strongly multicollinear problem with a known active set."""
|
|
24
|
+
rng = np.random.default_rng(11)
|
|
25
|
+
X = multicollinear_design(40, 120, seed=11)
|
|
26
|
+
X = (X - X.mean(axis=0)) / X.std(axis=0)
|
|
27
|
+
y = 5.0 * X[:, 0] - 4.0 * X[:, 1] + 3.0 * X[:, 2] + 0.1 * rng.standard_normal(40)
|
|
28
|
+
return X, y - y.mean()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture(params=[(20, 60), (30, 30), (40, 15)], ids=["wide", "square", "tall"])
|
|
32
|
+
def shaped_problem(request):
|
|
33
|
+
"""The same problem across the three shape regimes n < p, n == p and n > p."""
|
|
34
|
+
n_samples, n_features = request.param
|
|
35
|
+
rng = np.random.default_rng(hash(request.param) % 2**32)
|
|
36
|
+
X = multicollinear_design(n_samples, n_features, n_factors=2, seed=3)
|
|
37
|
+
X = (X - X.mean(axis=0)) / X.std(axis=0)
|
|
38
|
+
y = 4.0 * X[:, 0] - 3.0 * X[:, 1] + 0.2 * rng.standard_normal(n_samples)
|
|
39
|
+
return X, y - y.mean()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Literal transcription of equations (4), (6), (7), (8) and (10).
|
|
2
|
+
|
|
3
|
+
These routines form the explicit n-by-n projection matrices printed in the
|
|
4
|
+
paper. They are the yardstick for the fast path, not a public API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from numpy.typing import NDArray
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def svd_parts(
|
|
14
|
+
X: NDArray[np.float64],
|
|
15
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
|
16
|
+
U, mu, Vt = np.linalg.svd(X, full_matrices=False)
|
|
17
|
+
return U, mu, Vt.T
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def q_f(U1: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
21
|
+
"""Equation (4): ``Q_F = I - U1 (U1.T U1)^{-1} U1.T``."""
|
|
22
|
+
n = U1.shape[0]
|
|
23
|
+
if U1.shape[1] == 0:
|
|
24
|
+
return np.eye(n)
|
|
25
|
+
gram = U1.T @ U1
|
|
26
|
+
return np.eye(n) - U1 @ np.linalg.inv(gram) @ U1.T
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def q_p(
|
|
30
|
+
U1: NDArray[np.float64], U2: NDArray[np.float64], mu2: NDArray[np.float64]
|
|
31
|
+
) -> NDArray[np.float64]:
|
|
32
|
+
"""Equation (6) as printed, without an inverse on ``U1.T U1`` (A-3)."""
|
|
33
|
+
n = U1.shape[0]
|
|
34
|
+
if U2.shape[1] == 0:
|
|
35
|
+
return np.zeros((n, n))
|
|
36
|
+
tail = U2 @ np.diag(1.0 / mu2) @ U2.T
|
|
37
|
+
if U1.shape[1] == 0:
|
|
38
|
+
return tail
|
|
39
|
+
return tail @ (np.eye(n) - U1 @ (U1.T @ U1) @ U1.T)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def q_t(
|
|
43
|
+
U1: NDArray[np.float64], U2a: NDArray[np.float64], mu2a: NDArray[np.float64]
|
|
44
|
+
) -> NDArray[np.float64]:
|
|
45
|
+
"""Equation (7) as printed."""
|
|
46
|
+
return q_p(U1, U2a, mu2a)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def omega_from_q(
|
|
50
|
+
X: NDArray[np.float64],
|
|
51
|
+
y: NDArray[np.float64],
|
|
52
|
+
Q: NDArray[np.float64],
|
|
53
|
+
) -> NDArray[np.float64]:
|
|
54
|
+
"""``omega = (Q X).T @ (Q y)``."""
|
|
55
|
+
X_hat = Q @ X
|
|
56
|
+
y_hat = Q @ y
|
|
57
|
+
return X_hat.T @ y_hat
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def sis_omega(X: NDArray[np.float64], y: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
61
|
+
return X.T @ y
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def fpsis_omega(
|
|
65
|
+
X: NDArray[np.float64],
|
|
66
|
+
y: NDArray[np.float64],
|
|
67
|
+
d: int,
|
|
68
|
+
) -> NDArray[np.float64]:
|
|
69
|
+
U, _mu, _V = svd_parts(X)
|
|
70
|
+
return omega_from_q(X, y, q_f(U[:, :d]))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def ppis_omega(
|
|
74
|
+
X: NDArray[np.float64],
|
|
75
|
+
y: NDArray[np.float64],
|
|
76
|
+
d: int,
|
|
77
|
+
) -> NDArray[np.float64]:
|
|
78
|
+
U, mu, _V = svd_parts(X)
|
|
79
|
+
return omega_from_q(X, y, q_p(U[:, :d], U[:, d:], mu[d:]))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def tppis_omega(
|
|
83
|
+
X: NDArray[np.float64],
|
|
84
|
+
y: NDArray[np.float64],
|
|
85
|
+
d: int,
|
|
86
|
+
m: int,
|
|
87
|
+
) -> NDArray[np.float64]:
|
|
88
|
+
U, mu, _V = svd_parts(X)
|
|
89
|
+
return omega_from_q(X, y, q_t(U[:, :d], U[:, d:m], mu[d:m]))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def refit_beta(
|
|
93
|
+
X_hat_m: NDArray[np.float64],
|
|
94
|
+
y_hat: NDArray[np.float64],
|
|
95
|
+
) -> NDArray[np.float64]:
|
|
96
|
+
"""Equation (8): ``beta = (X_hat_M.T X_hat_M)^{-1} X_hat_M.T y_hat``."""
|
|
97
|
+
gram = X_hat_m.T @ X_hat_m
|
|
98
|
+
return np.linalg.solve(gram, X_hat_m.T @ y_hat)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def bic_paper(rss: float, k: int, n: int, p: int) -> float:
|
|
102
|
+
"""Equation (10): natural log, unnormalized RSS (A-4)."""
|
|
103
|
+
return float(np.log(rss) + (np.log(p) / n) * k * np.log(n))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Refit (8), BIC (10), and the analytic check of specification Section 5.1."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from tppis.criteria import bic_value, incremental_refit
|
|
7
|
+
from tppis.datasets import make_example1
|
|
8
|
+
|
|
9
|
+
from tests.reference import bic_paper, refit_beta
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_bic_matches_reference_transcription() -> None:
|
|
13
|
+
got = bic_value(100.0, 4, 100, 1000, kind="paper")
|
|
14
|
+
assert got == bic_paper(100.0, 4, 100, 1000)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_section_51_analytic_check() -> None:
|
|
18
|
+
# RSS ~ n * sigma^2 = 100 at the true model; penalty ~ 1.272; BIC ~ 5.877.
|
|
19
|
+
value = bic_value(100.0, 4, 100, 1000, kind="paper")
|
|
20
|
+
assert 5.85 < value < 5.90
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_incremental_matches_direct_solve() -> None:
|
|
24
|
+
rng = np.random.default_rng(0)
|
|
25
|
+
n, K, s = 30, 6, 8
|
|
26
|
+
F = rng.standard_normal((K, s))
|
|
27
|
+
X_sel = rng.standard_normal((n, K))
|
|
28
|
+
y = rng.standard_normal(n)
|
|
29
|
+
rhs = rng.standard_normal(K)
|
|
30
|
+
sweep = incremental_refit(F, rhs, X_sel, y, p=20, kind="paper")
|
|
31
|
+
for k in range(1, K + 1):
|
|
32
|
+
gram = F[:k] @ F[:k].T
|
|
33
|
+
beta_k = np.linalg.solve(gram, rhs[:k])
|
|
34
|
+
resid = y - X_sel[:, :k] @ beta_k
|
|
35
|
+
rss = float(resid @ resid)
|
|
36
|
+
np.testing.assert_allclose(sweep.rss[k - 1], rss, rtol=1e-10, atol=1e-10)
|
|
37
|
+
if k == sweep.best_k:
|
|
38
|
+
np.testing.assert_allclose(sweep.best_beta, beta_k, rtol=1e-10, atol=1e-10)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_direct_refit_matches_equation_8() -> None:
|
|
42
|
+
rng = np.random.default_rng(1)
|
|
43
|
+
n, k = 25, 4
|
|
44
|
+
X_hat = rng.standard_normal((n, k))
|
|
45
|
+
y_hat = rng.standard_normal(n)
|
|
46
|
+
beta = refit_beta(X_hat, y_hat)
|
|
47
|
+
gram = X_hat.T @ X_hat
|
|
48
|
+
np.testing.assert_allclose(gram @ beta, X_hat.T @ y_hat, rtol=1e-10, atol=1e-10)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_example1_true_model_bic_is_near_588() -> None:
|
|
52
|
+
data = make_example1(n=100, p=80, phi=0.5, seed=0)
|
|
53
|
+
# Residual of the true four-variable model on this one draw.
|
|
54
|
+
X4 = data.X[:, :4]
|
|
55
|
+
beta, *_ = np.linalg.lstsq(X4, data.y, rcond=None)
|
|
56
|
+
rss = float(np.sum((data.y - X4 @ beta) ** 2))
|
|
57
|
+
value = bic_value(rss, 4, 100, 80, kind="paper")
|
|
58
|
+
# One draw, not the expectation: just check the criterion is O(1) not O(10).
|
|
59
|
+
assert 3.0 < value < 9.0
|