ldpart 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.
- ldpart-0.1.0/LICENSE +21 -0
- ldpart-0.1.0/PKG-INFO +151 -0
- ldpart-0.1.0/README.md +124 -0
- ldpart-0.1.0/pyproject.toml +48 -0
- ldpart-0.1.0/setup.cfg +4 -0
- ldpart-0.1.0/src/ldpart/__init__.py +36 -0
- ldpart-0.1.0/src/ldpart/cli.py +115 -0
- ldpart-0.1.0/src/ldpart/helpers.py +57 -0
- ldpart-0.1.0/src/ldpart/partitioner.py +509 -0
- ldpart-0.1.0/src/ldpart.egg-info/PKG-INFO +151 -0
- ldpart-0.1.0/src/ldpart.egg-info/SOURCES.txt +19 -0
- ldpart-0.1.0/src/ldpart.egg-info/dependency_links.txt +1 -0
- ldpart-0.1.0/src/ldpart.egg-info/entry_points.txt +2 -0
- ldpart-0.1.0/src/ldpart.egg-info/requires.txt +5 -0
- ldpart-0.1.0/src/ldpart.egg-info/top_level.txt +1 -0
- ldpart-0.1.0/tests/test_certification.py +83 -0
- ldpart-0.1.0/tests/test_cli.py +68 -0
- ldpart-0.1.0/tests/test_equivalence.py +43 -0
- ldpart-0.1.0/tests/test_labels.py +61 -0
- ldpart-0.1.0/tests/test_maximality.py +68 -0
- ldpart-0.1.0/tests/test_nan.py +102 -0
ldpart-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gennady Khvorykh
|
|
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.
|
ldpart-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ldpart
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Certified multicollinearity partitioning of LD matrices into blocks of correlated SNPs
|
|
5
|
+
Author-email: Gennady Khvorykh <info@inzilico.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gkhvorykh/ldpart
|
|
8
|
+
Project-URL: Documentation, https://github.com/gkhvorykh/ldpart#readme
|
|
9
|
+
Keywords: linkage disequilibrium,LD blocks,multicollinearity,variance inflation factor,population genetics,genetics
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numpy>=1.20
|
|
23
|
+
Requires-Dist: h5py>=3.0
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Linkage Disequilibrium Partition (ldpart)
|
|
29
|
+
|
|
30
|
+
Certified multicollinearity partitioning of linkage disequilibrium (LD) matrices: group SNPs
|
|
31
|
+
into contiguous blocks of correlated SNPs with greedy criteria built
|
|
32
|
+
on classical multicollinearity diagnostics.
|
|
33
|
+
|
|
34
|
+
Every saved block is **certified** — its submatrix of the LD matrix
|
|
35
|
+
provably satisfies the criterion — and the monotone criteria
|
|
36
|
+
(determinant, condition number, maximal VIF) additionally certify that
|
|
37
|
+
every block is **maximal**: no neighbouring SNP could join it without
|
|
38
|
+
violating the criterion. NaN values never extend a block.
|
|
39
|
+
|
|
40
|
+
## Criteria
|
|
41
|
+
|
|
42
|
+
| function | method | criterion | certified | maximal | cost per candidate |
|
|
43
|
+
|---|---|---|---|---|---|
|
|
44
|
+
| `partition_by_determinant` | `fast` | det(block) > θ (Schur updates) | yes | yes (det is monotone under growth) | O(k²) |
|
|
45
|
+
| `partition_by_determinant_slogdet` | `slogdet` | det(block) > θ (LAPACK reference) | yes | yes | O(k³) |
|
|
46
|
+
| `partition_by_condition` | `condition` | κ(block) = λmax/\|λmin\| ≤ θ | yes | yes (Cauchy interlacing) | O(k³) |
|
|
47
|
+
| `partition_by_vif` | `vif` | max VIF = max diag(R⁻¹) ≤ θ | yes | yes (nested regressions) | O(k³) |
|
|
48
|
+
| `partition_by_reciprocal_eigenvalues` | `reciprocal` | Σ 1/λq ≤ θ·k | yes | no (the ratio can recover) | O(k³) |
|
|
49
|
+
| `partition_by_pairwise` | `pairwise` | max r2(candidate, member) ≥ θ | yes (pairwise sense) | — marginal screen | O(k) |
|
|
50
|
+
|
|
51
|
+
Classical operating points: det θ = 0.001, κ = 1000 (squared Belsley
|
|
52
|
+
CN ≈ 31.6), VIF 5/10, Σ1/λ ≈ 5·k (Chatterjee & Price), pairwise
|
|
53
|
+
r² = 0.8.
|
|
54
|
+
|
|
55
|
+
The determinant, condition, VIF and reciprocal criteria certify that
|
|
56
|
+
a block is **not too jointly redundant** (joint criteria); the
|
|
57
|
+
pairwise criterion groups SNPs that **are** in LD (marginal screen).
|
|
58
|
+
Their cuts land in different places — the joint criteria cut inside
|
|
59
|
+
strong LD where redundancy accumulates, the pairwise criterion cuts
|
|
60
|
+
where LD has decayed.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install ldpart
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Python API
|
|
69
|
+
|
|
70
|
+
The input is a square symmetric LD matrix (r or r2, 1 on the
|
|
71
|
+
diagonal, SNPs ordered as the rows and columns) — a numpy array or an
|
|
72
|
+
open h5py dataset (the matrix is streamed row by row, never copied).
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import h5py
|
|
76
|
+
from ldpart import partition_by_determinant, labels_to_blocks
|
|
77
|
+
|
|
78
|
+
with h5py.File("ld.h5", "r") as f:
|
|
79
|
+
matrix = f["r2"]
|
|
80
|
+
labels, nblocks = partition_by_determinant(matrix, min_det=0.001)
|
|
81
|
+
|
|
82
|
+
blocks = labels_to_blocks(labels) # [(block, [snp indexes]), ...]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Every function returns `(labels, nblocks)`, where labels is a per-SNP
|
|
86
|
+
list of `[index, block]` with blocks numbered from 1.
|
|
87
|
+
|
|
88
|
+
## Command line
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
ldpart -i ld.h5 -o labels.txt # det 0.001
|
|
92
|
+
ldpart -i ld.h5 -o labels.txt -m condition -c 1000
|
|
93
|
+
ldpart -i ld.h5 -o labels.txt -m vif -v 10
|
|
94
|
+
ldpart -i ld.h5 -o labels.txt -m reciprocal -r 5
|
|
95
|
+
ldpart -i ld.h5 -o labels.txt -m pairwise -p 0.8
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The output has two columns, the zero-based SNP index and the block
|
|
99
|
+
label.
|
|
100
|
+
|
|
101
|
+
## Semantics
|
|
102
|
+
|
|
103
|
+
- **Greedy growth:** the block starting at SNP i1 is extended by the
|
|
104
|
+
candidate SNP j only while the criterion holds over the window
|
|
105
|
+
matrix[i1:j+1, i1:j+1] *including the candidate*; the first failing
|
|
106
|
+
candidate seeds the next block, so blocks are contiguous and every
|
|
107
|
+
block satisfies its criterion at its final size and at every prefix.
|
|
108
|
+
- **Certified maximality:** the determinant can only decrease when a
|
|
109
|
+
SNP is added (Schur complement: each step multiplies it by
|
|
110
|
+
1 − R² ≤ 1), the condition number cannot recover (Cauchy
|
|
111
|
+
interlacing), the maximal VIF cannot recover (nested regressions) —
|
|
112
|
+
so the greedy cut yields the largest admissible block. The sum of
|
|
113
|
+
reciprocal eigenvalues per window size can recover, so its blocks
|
|
114
|
+
are certified but not maximal.
|
|
115
|
+
- **NaN semantics:** a NaN anywhere in the candidate window (or the
|
|
116
|
+
candidate's row segment for the pairwise screen) fails the
|
|
117
|
+
extension — NaN never silently extends a block. A SNP with a NaN
|
|
118
|
+
diagonal stays a singleton seed.
|
|
119
|
+
|
|
120
|
+
## Testing
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install -e ".[test]"
|
|
124
|
+
pytest tests
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The suite holds property tests on random correlation matrices:
|
|
128
|
+
certification of every block by independent LAPACK recomputation,
|
|
129
|
+
maximality of the monotone criteria against brute force, the NaN
|
|
130
|
+
semantics, fast/slogdet agreement, numpy/h5py equivalence, and the
|
|
131
|
+
command line interface end to end.
|
|
132
|
+
|
|
133
|
+
## References
|
|
134
|
+
|
|
135
|
+
- Rogers & Huff (2008), *Heredity* — the r² estimator used in the
|
|
136
|
+
companion study.
|
|
137
|
+
- Belsley, Kuh & Welsch (1980) — regression diagnostics, condition
|
|
138
|
+
indices.
|
|
139
|
+
- Chatterjee & Price (1977); Dillon & Goldstein (1984) — the
|
|
140
|
+
sum-of-reciprocal-eigenvalues rule.
|
|
141
|
+
- Khvorykh, G., Khrunin, A., 2025. Determinant-based grouping of SNPs and its application for detecting disease-associated genomic loci. NAR Genomics and Bioinformatics 7. https://doi.org/10.1093/nargab/lqaf024
|
|
142
|
+
- The method is developed and validated in the LD matrix partition study (in preparation)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT © Gennady Khvorykh
|
|
148
|
+
|
|
149
|
+
## Author
|
|
150
|
+
|
|
151
|
+
Gennady Khvorykh, info [at] inzilico.com
|
ldpart-0.1.0/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Linkage Disequilibrium Partition (ldpart)
|
|
2
|
+
|
|
3
|
+
Certified multicollinearity partitioning of linkage disequilibrium (LD) matrices: group SNPs
|
|
4
|
+
into contiguous blocks of correlated SNPs with greedy criteria built
|
|
5
|
+
on classical multicollinearity diagnostics.
|
|
6
|
+
|
|
7
|
+
Every saved block is **certified** — its submatrix of the LD matrix
|
|
8
|
+
provably satisfies the criterion — and the monotone criteria
|
|
9
|
+
(determinant, condition number, maximal VIF) additionally certify that
|
|
10
|
+
every block is **maximal**: no neighbouring SNP could join it without
|
|
11
|
+
violating the criterion. NaN values never extend a block.
|
|
12
|
+
|
|
13
|
+
## Criteria
|
|
14
|
+
|
|
15
|
+
| function | method | criterion | certified | maximal | cost per candidate |
|
|
16
|
+
|---|---|---|---|---|---|
|
|
17
|
+
| `partition_by_determinant` | `fast` | det(block) > θ (Schur updates) | yes | yes (det is monotone under growth) | O(k²) |
|
|
18
|
+
| `partition_by_determinant_slogdet` | `slogdet` | det(block) > θ (LAPACK reference) | yes | yes | O(k³) |
|
|
19
|
+
| `partition_by_condition` | `condition` | κ(block) = λmax/\|λmin\| ≤ θ | yes | yes (Cauchy interlacing) | O(k³) |
|
|
20
|
+
| `partition_by_vif` | `vif` | max VIF = max diag(R⁻¹) ≤ θ | yes | yes (nested regressions) | O(k³) |
|
|
21
|
+
| `partition_by_reciprocal_eigenvalues` | `reciprocal` | Σ 1/λq ≤ θ·k | yes | no (the ratio can recover) | O(k³) |
|
|
22
|
+
| `partition_by_pairwise` | `pairwise` | max r2(candidate, member) ≥ θ | yes (pairwise sense) | — marginal screen | O(k) |
|
|
23
|
+
|
|
24
|
+
Classical operating points: det θ = 0.001, κ = 1000 (squared Belsley
|
|
25
|
+
CN ≈ 31.6), VIF 5/10, Σ1/λ ≈ 5·k (Chatterjee & Price), pairwise
|
|
26
|
+
r² = 0.8.
|
|
27
|
+
|
|
28
|
+
The determinant, condition, VIF and reciprocal criteria certify that
|
|
29
|
+
a block is **not too jointly redundant** (joint criteria); the
|
|
30
|
+
pairwise criterion groups SNPs that **are** in LD (marginal screen).
|
|
31
|
+
Their cuts land in different places — the joint criteria cut inside
|
|
32
|
+
strong LD where redundancy accumulates, the pairwise criterion cuts
|
|
33
|
+
where LD has decayed.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install ldpart
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Python API
|
|
42
|
+
|
|
43
|
+
The input is a square symmetric LD matrix (r or r2, 1 on the
|
|
44
|
+
diagonal, SNPs ordered as the rows and columns) — a numpy array or an
|
|
45
|
+
open h5py dataset (the matrix is streamed row by row, never copied).
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import h5py
|
|
49
|
+
from ldpart import partition_by_determinant, labels_to_blocks
|
|
50
|
+
|
|
51
|
+
with h5py.File("ld.h5", "r") as f:
|
|
52
|
+
matrix = f["r2"]
|
|
53
|
+
labels, nblocks = partition_by_determinant(matrix, min_det=0.001)
|
|
54
|
+
|
|
55
|
+
blocks = labels_to_blocks(labels) # [(block, [snp indexes]), ...]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Every function returns `(labels, nblocks)`, where labels is a per-SNP
|
|
59
|
+
list of `[index, block]` with blocks numbered from 1.
|
|
60
|
+
|
|
61
|
+
## Command line
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
ldpart -i ld.h5 -o labels.txt # det 0.001
|
|
65
|
+
ldpart -i ld.h5 -o labels.txt -m condition -c 1000
|
|
66
|
+
ldpart -i ld.h5 -o labels.txt -m vif -v 10
|
|
67
|
+
ldpart -i ld.h5 -o labels.txt -m reciprocal -r 5
|
|
68
|
+
ldpart -i ld.h5 -o labels.txt -m pairwise -p 0.8
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The output has two columns, the zero-based SNP index and the block
|
|
72
|
+
label.
|
|
73
|
+
|
|
74
|
+
## Semantics
|
|
75
|
+
|
|
76
|
+
- **Greedy growth:** the block starting at SNP i1 is extended by the
|
|
77
|
+
candidate SNP j only while the criterion holds over the window
|
|
78
|
+
matrix[i1:j+1, i1:j+1] *including the candidate*; the first failing
|
|
79
|
+
candidate seeds the next block, so blocks are contiguous and every
|
|
80
|
+
block satisfies its criterion at its final size and at every prefix.
|
|
81
|
+
- **Certified maximality:** the determinant can only decrease when a
|
|
82
|
+
SNP is added (Schur complement: each step multiplies it by
|
|
83
|
+
1 − R² ≤ 1), the condition number cannot recover (Cauchy
|
|
84
|
+
interlacing), the maximal VIF cannot recover (nested regressions) —
|
|
85
|
+
so the greedy cut yields the largest admissible block. The sum of
|
|
86
|
+
reciprocal eigenvalues per window size can recover, so its blocks
|
|
87
|
+
are certified but not maximal.
|
|
88
|
+
- **NaN semantics:** a NaN anywhere in the candidate window (or the
|
|
89
|
+
candidate's row segment for the pairwise screen) fails the
|
|
90
|
+
extension — NaN never silently extends a block. A SNP with a NaN
|
|
91
|
+
diagonal stays a singleton seed.
|
|
92
|
+
|
|
93
|
+
## Testing
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install -e ".[test]"
|
|
97
|
+
pytest tests
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The suite holds property tests on random correlation matrices:
|
|
101
|
+
certification of every block by independent LAPACK recomputation,
|
|
102
|
+
maximality of the monotone criteria against brute force, the NaN
|
|
103
|
+
semantics, fast/slogdet agreement, numpy/h5py equivalence, and the
|
|
104
|
+
command line interface end to end.
|
|
105
|
+
|
|
106
|
+
## References
|
|
107
|
+
|
|
108
|
+
- Rogers & Huff (2008), *Heredity* — the r² estimator used in the
|
|
109
|
+
companion study.
|
|
110
|
+
- Belsley, Kuh & Welsch (1980) — regression diagnostics, condition
|
|
111
|
+
indices.
|
|
112
|
+
- Chatterjee & Price (1977); Dillon & Goldstein (1984) — the
|
|
113
|
+
sum-of-reciprocal-eigenvalues rule.
|
|
114
|
+
- Khvorykh, G., Khrunin, A., 2025. Determinant-based grouping of SNPs and its application for detecting disease-associated genomic loci. NAR Genomics and Bioinformatics 7. https://doi.org/10.1093/nargab/lqaf024
|
|
115
|
+
- The method is developed and validated in the LD matrix partition study (in preparation)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT © Gennady Khvorykh
|
|
121
|
+
|
|
122
|
+
## Author
|
|
123
|
+
|
|
124
|
+
Gennady Khvorykh, info [at] inzilico.com
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ldpart"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Certified multicollinearity partitioning of LD matrices into blocks of correlated SNPs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Gennady Khvorykh", email = "info@inzilico.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"linkage disequilibrium",
|
|
15
|
+
"LD blocks",
|
|
16
|
+
"multicollinearity",
|
|
17
|
+
"variance inflation factor",
|
|
18
|
+
"population genetics",
|
|
19
|
+
"genetics",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 4 - Beta",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"numpy>=1.20",
|
|
34
|
+
"h5py>=3.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/gkhvorykh/ldpart"
|
|
39
|
+
Documentation = "https://github.com/gkhvorykh/ldpart#readme"
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
ldpart = "ldpart.cli:main"
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
test = ["pytest>=7"]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["src"]
|
ldpart-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ldpart: certified multicollinearity partitioning of LD matrices.
|
|
3
|
+
|
|
4
|
+
Group SNPs into contiguous blocks of correlated SNPs from a square LD
|
|
5
|
+
matrix (r or r2 with 1 on the diagonal, SNPs ordered as the rows and
|
|
6
|
+
columns), with six greedy criteria built on classical
|
|
7
|
+
multicollinearity diagnostics: the determinant (fast Schur updates
|
|
8
|
+
and the LAPACK slogdet reference), the condition number, the maximal
|
|
9
|
+
variance inflation factor, the sum of reciprocal eigenvalues, and the
|
|
10
|
+
marginal pairwise r2 screen. Every saved block is certified: it
|
|
11
|
+
satisfies its criterion, and the monotone criteria additionally
|
|
12
|
+
certify maximality. See partitioner for the semantics.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .partitioner import (
|
|
16
|
+
partition_by_determinant,
|
|
17
|
+
partition_by_determinant_slogdet,
|
|
18
|
+
partition_by_condition,
|
|
19
|
+
partition_by_vif,
|
|
20
|
+
partition_by_reciprocal_eigenvalues,
|
|
21
|
+
partition_by_pairwise,
|
|
22
|
+
labels_to_blocks,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"partition_by_determinant",
|
|
29
|
+
"partition_by_determinant_slogdet",
|
|
30
|
+
"partition_by_condition",
|
|
31
|
+
"partition_by_vif",
|
|
32
|
+
"partition_by_reciprocal_eigenvalues",
|
|
33
|
+
"partition_by_pairwise",
|
|
34
|
+
"labels_to_blocks",
|
|
35
|
+
"__version__",
|
|
36
|
+
]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command line interface of ldpart: partition a square LD matrix into
|
|
3
|
+
blocks of correlated SNPs by a multicollinearity criterion. The input
|
|
4
|
+
file holds the matrix (1 on the diagonal, SNPs ordered as the rows and
|
|
5
|
+
columns) in HDF5; the output holds two columns, the zero-based SNP
|
|
6
|
+
index and the block label of every SNP, the format consumed by the
|
|
7
|
+
reproducibility layer of the ld-matrix-partition-01 project.
|
|
8
|
+
|
|
9
|
+
Installed as the console script `ldpart`:
|
|
10
|
+
|
|
11
|
+
ldpart -i LD.h5 -o labels.txt -m fast -d 0.001
|
|
12
|
+
ldpart -i LD.h5 -o labels.txt -m condition -c 1000
|
|
13
|
+
ldpart -i LD.h5 -o labels.txt -m vif -v 10
|
|
14
|
+
ldpart -i LD.h5 -o labels.txt -m reciprocal -r 5
|
|
15
|
+
ldpart -i LD.h5 -o labels.txt -m pairwise -p 0.8
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import h5py
|
|
23
|
+
|
|
24
|
+
from . import __version__
|
|
25
|
+
from .helpers import (attach_matrix, check_input_files,
|
|
26
|
+
check_output_dir, show_time_elapsed)
|
|
27
|
+
from .partitioner import (
|
|
28
|
+
partition_by_determinant,
|
|
29
|
+
partition_by_determinant_slogdet,
|
|
30
|
+
partition_by_condition,
|
|
31
|
+
partition_by_vif,
|
|
32
|
+
partition_by_reciprocal_eigenvalues,
|
|
33
|
+
partition_by_pairwise,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Method -> (partition function, criterion argument, printed label)
|
|
37
|
+
METHODS = {
|
|
38
|
+
"fast": (partition_by_determinant, "min_det", "Minimal determinant"),
|
|
39
|
+
"slogdet": (partition_by_determinant_slogdet, "min_det",
|
|
40
|
+
"Minimal determinant"),
|
|
41
|
+
"condition": (partition_by_condition, "max_cond",
|
|
42
|
+
"Maximal condition number"),
|
|
43
|
+
"vif": (partition_by_vif, "max_vif", "Maximal VIF"),
|
|
44
|
+
"reciprocal": (partition_by_reciprocal_eigenvalues, "max_ratio",
|
|
45
|
+
"Maximal sum of reciprocal eigenvalues per block "
|
|
46
|
+
"size"),
|
|
47
|
+
"pairwise": (partition_by_pairwise, "min_r2",
|
|
48
|
+
"Minimal pairwise r2"),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def parser_args(argv=None) -> argparse.Namespace:
|
|
53
|
+
parser = argparse.ArgumentParser(
|
|
54
|
+
prog="ldpart",
|
|
55
|
+
description="Partition a square LD matrix into blocks of "
|
|
56
|
+
"correlated SNPs by a multicollinearity criterion")
|
|
57
|
+
parser.add_argument("-i", "--input", required=True,
|
|
58
|
+
help="/path/to/input.h5 with the matrix in "
|
|
59
|
+
"HDF5 format")
|
|
60
|
+
parser.add_argument("-o", "--output", required=True,
|
|
61
|
+
help="/path/to/output.txt for the block labels")
|
|
62
|
+
parser.add_argument("-d", "--min_det", type=float, default=0.001,
|
|
63
|
+
help="minimal determinant of a block submatrix, "
|
|
64
|
+
"methods fast and slogdet (default 0.001)")
|
|
65
|
+
parser.add_argument("-c", "--max_cond", type=float, default=1000.0,
|
|
66
|
+
help="maximal condition number of a block "
|
|
67
|
+
"submatrix, method condition "
|
|
68
|
+
"(default 1000)")
|
|
69
|
+
parser.add_argument("-v", "--max_vif", type=float, default=10.0,
|
|
70
|
+
help="maximal variance inflation factor of a "
|
|
71
|
+
"block submatrix, method vif (default 10)")
|
|
72
|
+
parser.add_argument("-r", "--max_ratio", type=float, default=5.0,
|
|
73
|
+
help="maximal sum of reciprocal eigenvalues "
|
|
74
|
+
"per block size, method reciprocal "
|
|
75
|
+
"(default 5)")
|
|
76
|
+
parser.add_argument("-p", "--min_r2", type=float, default=0.8,
|
|
77
|
+
help="minimal pairwise r2 between the "
|
|
78
|
+
"candidate and a block member, method "
|
|
79
|
+
"pairwise (default 0.8)")
|
|
80
|
+
parser.add_argument("-m", "--method", choices=tuple(METHODS),
|
|
81
|
+
default="fast",
|
|
82
|
+
help="partition function: fast, slogdet, "
|
|
83
|
+
"condition, vif, reciprocal or pairwise "
|
|
84
|
+
"(default fast)")
|
|
85
|
+
parser.add_argument("--version", action="version",
|
|
86
|
+
version=f"ldpart {__version__}")
|
|
87
|
+
return parser.parse_args(argv)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main(argv=None) -> None:
|
|
91
|
+
args = parser_args(argv)
|
|
92
|
+
ts = time.time()
|
|
93
|
+
|
|
94
|
+
check_input_files([args.input])
|
|
95
|
+
check_output_dir(args.output)
|
|
96
|
+
|
|
97
|
+
print("Input:", args.input)
|
|
98
|
+
print("Output:", args.output)
|
|
99
|
+
print("Method:", args.method)
|
|
100
|
+
|
|
101
|
+
fun, arg_name, label = METHODS[args.method]
|
|
102
|
+
params = {arg_name: getattr(args, arg_name)}
|
|
103
|
+
print(label + ":", params[arg_name])
|
|
104
|
+
|
|
105
|
+
with h5py.File(args.input, "r") as f:
|
|
106
|
+
matrix = attach_matrix(f)
|
|
107
|
+
labels, nblocks = fun(matrix, **params)
|
|
108
|
+
|
|
109
|
+
np.savetxt(args.output, labels, delimiter=" ", fmt="%d")
|
|
110
|
+
print("Number of blocks:", nblocks)
|
|
111
|
+
show_time_elapsed(ts)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Assisting functions of the ldpart command line interface: the LD
|
|
3
|
+
matrix is read straight from its HDF5 dataset (no copy into memory),
|
|
4
|
+
and the small file and folder checks live here. Trimmed from the
|
|
5
|
+
helpers of the ld-matrix-partition-01 project, pandas free.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from typing import Union
|
|
12
|
+
|
|
13
|
+
import h5py
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def attach_matrix(file_obj: h5py.File) -> Union[h5py.Dataset,
|
|
17
|
+
h5py.Group]:
|
|
18
|
+
"""
|
|
19
|
+
The square matrix of the first object of an open HDF5 file: a
|
|
20
|
+
plain dataset, or a group of chunked pandas arrays
|
|
21
|
+
(block0_values).
|
|
22
|
+
"""
|
|
23
|
+
key = list(file_obj.keys())[0]
|
|
24
|
+
obj = file_obj[key]
|
|
25
|
+
if isinstance(obj, h5py.Dataset):
|
|
26
|
+
ds_obj = obj
|
|
27
|
+
elif isinstance(obj, h5py.Group):
|
|
28
|
+
ds_obj = obj["block0_values"]
|
|
29
|
+
else:
|
|
30
|
+
print("Unknown type of dataset")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
n, m = ds_obj.shape[0], ds_obj.shape[1]
|
|
34
|
+
if n != m:
|
|
35
|
+
print("Dataset is not a square matrix")
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
print(f"Dataset shape: {n} x {m}")
|
|
38
|
+
return ds_obj
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def check_output_dir(output: str) -> None:
|
|
42
|
+
# Create the folder holding an output path when missing
|
|
43
|
+
dir_name = os.path.dirname(output)
|
|
44
|
+
if dir_name:
|
|
45
|
+
os.makedirs(dir_name, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def check_input_files(files: list) -> None:
|
|
49
|
+
for file in files:
|
|
50
|
+
if file and not os.path.isfile(file):
|
|
51
|
+
print(f"{file} doesn't exist")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def show_time_elapsed(ts: float) -> None:
|
|
56
|
+
dur = time.strftime("%H:%M:%S", time.gmtime(time.time() - ts))
|
|
57
|
+
print("Time spent:", dur)
|