statgen 0.3.2__tar.gz → 0.3.3__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.
- {statgen-0.3.2 → statgen-0.3.3}/PKG-INFO +1 -1
- {statgen-0.3.2 → statgen-0.3.3}/pyproject.toml +1 -1
- {statgen-0.3.2 → statgen-0.3.3}/statgen/__init__.py +1 -1
- statgen-0.3.3/statgen/_annotation_painting.py +146 -0
- statgen-0.3.3/statgen/annotations.py +842 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/reference.py +92 -48
- {statgen-0.3.2 → statgen-0.3.3}/statgen.egg-info/PKG-INFO +1 -1
- {statgen-0.3.2 → statgen-0.3.3}/statgen.egg-info/SOURCES.txt +1 -0
- statgen-0.3.2/statgen/annotations.py +0 -450
- {statgen-0.3.2 → statgen-0.3.3}/README.md +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/setup.cfg +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/_bfile_utils.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/_ld_npz.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/_ld_reference.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/_ld_schema.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/_ld_writer.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/_utils.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/_variant_match.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/_verbosity.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/genotype.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/ld.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen/sumstats.py +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen.egg-info/dependency_links.txt +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen.egg-info/requires.txt +0 -0
- {statgen-0.3.2 → statgen-0.3.3}/statgen.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from scipy import sparse
|
|
6
|
+
|
|
7
|
+
IGNORED_CHR = {"Y", "MT"}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def merge_intervals(starts: np.ndarray, ends: np.ndarray) -> np.ndarray:
|
|
11
|
+
if starts.size == 0:
|
|
12
|
+
return np.empty((0, 2), dtype=np.int64)
|
|
13
|
+
|
|
14
|
+
order = np.lexsort((ends, starts))
|
|
15
|
+
s = starts[order]
|
|
16
|
+
e = ends[order]
|
|
17
|
+
|
|
18
|
+
running_max_end = np.maximum.accumulate(e)
|
|
19
|
+
new_group = np.empty(len(s), dtype=bool)
|
|
20
|
+
new_group[0] = True
|
|
21
|
+
new_group[1:] = s[1:] > running_max_end[:-1]
|
|
22
|
+
|
|
23
|
+
group_starts_idx = np.where(new_group)[0]
|
|
24
|
+
merged_starts = s[group_starts_idx]
|
|
25
|
+
merged_ends = np.maximum.reduceat(e, group_starts_idx)
|
|
26
|
+
|
|
27
|
+
return np.column_stack([merged_starts.astype(np.int64), merged_ends.astype(np.int64)])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def binary_intervals_by_chr_from_arrays(
|
|
31
|
+
chr_values: np.ndarray,
|
|
32
|
+
starts: np.ndarray,
|
|
33
|
+
ends: np.ndarray,
|
|
34
|
+
) -> dict[str, np.ndarray]:
|
|
35
|
+
intervals_by_chr: dict[str, np.ndarray] = {}
|
|
36
|
+
grouped = pd.DataFrame({"chr": chr_values, "start": starts, "end": ends}).groupby(
|
|
37
|
+
"chr", sort=False
|
|
38
|
+
)
|
|
39
|
+
for chr_label, grp in grouped:
|
|
40
|
+
if chr_label in IGNORED_CHR:
|
|
41
|
+
continue
|
|
42
|
+
intervals_by_chr[str(chr_label)] = merge_intervals(
|
|
43
|
+
grp["start"].to_numpy(dtype=np.int64),
|
|
44
|
+
grp["end"].to_numpy(dtype=np.int64),
|
|
45
|
+
)
|
|
46
|
+
return intervals_by_chr
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_numeric_non_overlapping(
|
|
50
|
+
chr_values: np.ndarray,
|
|
51
|
+
starts: np.ndarray,
|
|
52
|
+
ends: np.ndarray,
|
|
53
|
+
path: Path,
|
|
54
|
+
row_base0: int = 0,
|
|
55
|
+
) -> None:
|
|
56
|
+
source = pd.DataFrame(
|
|
57
|
+
{
|
|
58
|
+
"chr": chr_values,
|
|
59
|
+
"start": starts,
|
|
60
|
+
"end": ends,
|
|
61
|
+
"row0": np.arange(starts.size, dtype=np.int64),
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
for chr_label, grp in source.groupby("chr", sort=False):
|
|
65
|
+
if chr_label in IGNORED_CHR or grp.shape[0] <= 1:
|
|
66
|
+
continue
|
|
67
|
+
order = np.lexsort((grp["end"].to_numpy(), grp["start"].to_numpy()))
|
|
68
|
+
s = grp["start"].to_numpy(dtype=np.int64)[order]
|
|
69
|
+
e = grp["end"].to_numpy(dtype=np.int64)[order]
|
|
70
|
+
rows = grp["row0"].to_numpy(dtype=np.int64)[order]
|
|
71
|
+
prev_max = np.maximum.accumulate(e)[:-1]
|
|
72
|
+
overlap = s[1:] < prev_max
|
|
73
|
+
if overlap.any():
|
|
74
|
+
j = int(np.flatnonzero(overlap)[0]) + 1
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"{path}: row {row_base0 + int(rows[j]) + 1}: "
|
|
77
|
+
f"numeric annotation intervals overlap on chromosome {chr_label}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def paint_mask(bp: np.ndarray, intervals: np.ndarray) -> np.ndarray:
|
|
82
|
+
n = bp.size
|
|
83
|
+
if intervals.size == 0:
|
|
84
|
+
return np.zeros(n, dtype=np.float64)
|
|
85
|
+
|
|
86
|
+
pos0 = np.asarray(bp, dtype=np.int64) - 1
|
|
87
|
+
starts = intervals[:, 0]
|
|
88
|
+
ends = intervals[:, 1]
|
|
89
|
+
|
|
90
|
+
idx = np.searchsorted(starts, pos0, side="right") - 1
|
|
91
|
+
valid = idx >= 0
|
|
92
|
+
out = np.zeros(n, dtype=np.float64)
|
|
93
|
+
if valid.any():
|
|
94
|
+
valid_idx = idx[valid]
|
|
95
|
+
inside = pos0[valid] < ends[valid_idx]
|
|
96
|
+
out[np.flatnonzero(valid)[inside]] = 1.0
|
|
97
|
+
return out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def paint_numeric_sparse(
|
|
101
|
+
bp: np.ndarray,
|
|
102
|
+
intervals: np.ndarray,
|
|
103
|
+
values: np.ndarray,
|
|
104
|
+
) -> sparse.csr_matrix:
|
|
105
|
+
n = bp.size
|
|
106
|
+
k = values.shape[1]
|
|
107
|
+
if intervals.size == 0:
|
|
108
|
+
return sparse.csr_matrix((n, k), dtype=np.float64)
|
|
109
|
+
|
|
110
|
+
pos0 = np.asarray(bp, dtype=np.int64) - 1
|
|
111
|
+
starts = intervals[:, 0]
|
|
112
|
+
ends = intervals[:, 1]
|
|
113
|
+
idx = np.searchsorted(starts, pos0, side="right") - 1
|
|
114
|
+
valid = idx >= 0
|
|
115
|
+
if not valid.any():
|
|
116
|
+
return sparse.csr_matrix((n, k), dtype=np.float64)
|
|
117
|
+
|
|
118
|
+
valid_idx = idx[valid]
|
|
119
|
+
inside = pos0[valid] < ends[valid_idx]
|
|
120
|
+
if not inside.any():
|
|
121
|
+
return sparse.csr_matrix((n, k), dtype=np.float64)
|
|
122
|
+
|
|
123
|
+
hit_rows = np.flatnonzero(valid)[inside]
|
|
124
|
+
hit_values = values[valid_idx[inside], :]
|
|
125
|
+
row_idx, col_idx = np.nonzero(hit_values)
|
|
126
|
+
if row_idx.size == 0:
|
|
127
|
+
return sparse.csr_matrix((n, k), dtype=np.float64)
|
|
128
|
+
|
|
129
|
+
return sparse.csr_matrix(
|
|
130
|
+
(hit_values[row_idx, col_idx], (hit_rows[row_idx], col_idx)),
|
|
131
|
+
shape=(n, k),
|
|
132
|
+
dtype=np.float64,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def paint_binary_column(intervals_by_chr: dict[str, np.ndarray], reference) -> sparse.csr_matrix:
|
|
137
|
+
n = int(reference.num_snp)
|
|
138
|
+
mask = np.zeros(n, dtype=np.float64)
|
|
139
|
+
for ref_shard, off in zip(reference.shards, reference.shard_offsets):
|
|
140
|
+
start = int(off["start0"])
|
|
141
|
+
stop = int(off["stop0"])
|
|
142
|
+
intervals = intervals_by_chr.get(ref_shard.label)
|
|
143
|
+
if intervals is None:
|
|
144
|
+
continue
|
|
145
|
+
mask[start:stop] = paint_mask(ref_shard.bp, intervals)
|
|
146
|
+
return sparse.csr_matrix(mask.reshape(-1, 1))
|