statgen 0.3.2__tar.gz → 0.3.4__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 (25) hide show
  1. {statgen-0.3.2 → statgen-0.3.4}/PKG-INFO +1 -1
  2. {statgen-0.3.2 → statgen-0.3.4}/pyproject.toml +1 -1
  3. {statgen-0.3.2 → statgen-0.3.4}/statgen/__init__.py +1 -1
  4. statgen-0.3.4/statgen/_annotation_painting.py +146 -0
  5. statgen-0.3.4/statgen/annotations.py +931 -0
  6. {statgen-0.3.2 → statgen-0.3.4}/statgen/reference.py +92 -48
  7. {statgen-0.3.2 → statgen-0.3.4}/statgen.egg-info/PKG-INFO +1 -1
  8. {statgen-0.3.2 → statgen-0.3.4}/statgen.egg-info/SOURCES.txt +1 -0
  9. statgen-0.3.2/statgen/annotations.py +0 -450
  10. {statgen-0.3.2 → statgen-0.3.4}/README.md +0 -0
  11. {statgen-0.3.2 → statgen-0.3.4}/setup.cfg +0 -0
  12. {statgen-0.3.2 → statgen-0.3.4}/statgen/_bfile_utils.py +0 -0
  13. {statgen-0.3.2 → statgen-0.3.4}/statgen/_ld_npz.py +0 -0
  14. {statgen-0.3.2 → statgen-0.3.4}/statgen/_ld_reference.py +0 -0
  15. {statgen-0.3.2 → statgen-0.3.4}/statgen/_ld_schema.py +0 -0
  16. {statgen-0.3.2 → statgen-0.3.4}/statgen/_ld_writer.py +0 -0
  17. {statgen-0.3.2 → statgen-0.3.4}/statgen/_utils.py +0 -0
  18. {statgen-0.3.2 → statgen-0.3.4}/statgen/_variant_match.py +0 -0
  19. {statgen-0.3.2 → statgen-0.3.4}/statgen/_verbosity.py +0 -0
  20. {statgen-0.3.2 → statgen-0.3.4}/statgen/genotype.py +0 -0
  21. {statgen-0.3.2 → statgen-0.3.4}/statgen/ld.py +0 -0
  22. {statgen-0.3.2 → statgen-0.3.4}/statgen/sumstats.py +0 -0
  23. {statgen-0.3.2 → statgen-0.3.4}/statgen.egg-info/dependency_links.txt +0 -0
  24. {statgen-0.3.2 → statgen-0.3.4}/statgen.egg-info/requires.txt +0 -0
  25. {statgen-0.3.2 → statgen-0.3.4}/statgen.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statgen
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Statistical genetics data objects and loaders for Python and MATLAB/Octave.
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "statgen"
7
- version = "0.3.2"
7
+ version = "0.3.4"
8
8
  description = "Statistical genetics data objects and loaders for Python and MATLAB/Octave."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,5 +1,5 @@
1
1
  from ._verbosity import get_verbosity, set_verbosity
2
2
 
3
- __version__ = "0.3.2"
3
+ __version__ = "0.3.4"
4
4
 
5
5
  __all__ = ["__version__", "get_verbosity", "set_verbosity"]
@@ -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))