statgen 0.2.6__tar.gz → 0.3.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.
- {statgen-0.2.6 → statgen-0.3.0}/PKG-INFO +1 -1
- {statgen-0.2.6 → statgen-0.3.0}/pyproject.toml +1 -1
- {statgen-0.2.6 → statgen-0.3.0}/statgen/__init__.py +1 -1
- {statgen-0.2.6 → statgen-0.3.0}/statgen/_bfile_utils.py +29 -15
- {statgen-0.2.6 → statgen-0.3.0}/statgen/annotations.py +12 -12
- {statgen-0.2.6 → statgen-0.3.0}/statgen/genotype.py +8 -4
- {statgen-0.2.6 → statgen-0.3.0}/statgen/reference.py +3 -3
- {statgen-0.2.6 → statgen-0.3.0}/statgen/sumstats.py +19 -19
- {statgen-0.2.6 → statgen-0.3.0}/statgen.egg-info/PKG-INFO +1 -1
- {statgen-0.2.6 → statgen-0.3.0}/README.md +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/setup.cfg +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen/_ld_npz.py +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen/_ld_reference.py +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen/_ld_schema.py +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen/_ld_writer.py +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen/_utils.py +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen/_variant_match.py +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen/_verbosity.py +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen/ld.py +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen.egg-info/SOURCES.txt +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen.egg-info/dependency_links.txt +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen.egg-info/requires.txt +0 -0
- {statgen-0.2.6 → statgen-0.3.0}/statgen.egg-info/top_level.txt +0 -0
|
@@ -17,6 +17,7 @@ _FAM_NAMES = ["fid", "iid", "father_id", "mother_id", "sex", "pheno"]
|
|
|
17
17
|
_PLOIDY_NAMES = ["ploidy_male", "ploidy_female"]
|
|
18
18
|
_DNA_ALLELE_RE = re.compile(r"^[ACGT]+$")
|
|
19
19
|
_BED_LOOKUP_INT8 = None
|
|
20
|
+
_BED_MAX_READ_BYTES = 64 * 1024 * 1024
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _read_csv_strict(path: Path, *, sep, names, description: str | None = None) -> pd.DataFrame:
|
|
@@ -256,20 +257,33 @@ def _bed_lookup_int8() -> np.ndarray:
|
|
|
256
257
|
def _read_bed_rows_int8(path: Path, source_rows: np.ndarray, source_num_sample: int) -> np.ndarray:
|
|
257
258
|
path = Path(path)
|
|
258
259
|
source_rows = np.asarray(source_rows, dtype=np.int64).reshape(-1)
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
n_samples = int(source_num_sample)
|
|
261
|
+
bytes_per_snp = (n_samples + 3) // 4
|
|
262
|
+
if source_rows.size == 0:
|
|
263
|
+
return np.empty((n_samples, 0), dtype=np.int8)
|
|
264
|
+
unique_rows, inverse = np.unique(source_rows, return_inverse=True)
|
|
265
|
+
unique_decoded = np.empty((n_samples, unique_rows.size), dtype=np.int8)
|
|
266
|
+
rows_per_read = unique_rows.size if bytes_per_snp == 0 else max(1, _BED_MAX_READ_BYTES // bytes_per_snp)
|
|
261
267
|
lookup = _bed_lookup_int8()
|
|
262
268
|
with open(path, "rb") as f:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
269
|
+
group_starts = np.r_[0, np.flatnonzero(np.diff(unique_rows) != 1) + 1]
|
|
270
|
+
group_stops = np.r_[group_starts[1:], unique_rows.size]
|
|
271
|
+
# PERF: one seek/read per contiguous BED row block; requested order and
|
|
272
|
+
# repeats are restored after block decoding. Blocks are capped to
|
|
273
|
+
# bound transient packed/decode allocations for large requests.
|
|
274
|
+
for start, stop in zip(group_starts, group_stops, strict=True):
|
|
275
|
+
for chunk_start in range(start, stop, rows_per_read):
|
|
276
|
+
chunk_stop = min(chunk_start + rows_per_read, stop)
|
|
277
|
+
block_rows = unique_rows[chunk_start:chunk_stop]
|
|
278
|
+
expected_bytes = int(bytes_per_snp * block_rows.size)
|
|
279
|
+
f.seek(3 + int(block_rows[0]) * bytes_per_snp)
|
|
280
|
+
packed = f.read(expected_bytes)
|
|
281
|
+
if len(packed) != expected_bytes:
|
|
282
|
+
raise ValueError(
|
|
283
|
+
f"{path}: short BED read for source SNP rows {int(block_rows[0])}-{int(block_rows[-1])}: "
|
|
284
|
+
f"expected {expected_bytes} bytes, got {len(packed)}"
|
|
285
|
+
)
|
|
286
|
+
byte_values = np.frombuffer(packed, dtype=np.uint8)
|
|
287
|
+
block_decoded = lookup[byte_values].reshape(block_rows.size, bytes_per_snp * 4)
|
|
288
|
+
unique_decoded[:, chunk_start:chunk_stop] = block_decoded[:, :n_samples].T
|
|
289
|
+
return unique_decoded[:, inverse]
|
|
@@ -12,13 +12,13 @@ _CACHE_SCHEMA = "annotations_cache/0.1"
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def _as_sparse_binary(mat) -> sparse.csr_matrix:
|
|
15
|
+
def _as_sparse_binary(mat, name: str = "annomat") -> sparse.csr_matrix:
|
|
16
16
|
if sparse.issparse(mat):
|
|
17
17
|
csr = mat.tocsr(copy=True)
|
|
18
18
|
else:
|
|
19
19
|
arr = np.asarray(mat)
|
|
20
20
|
if arr.ndim != 2:
|
|
21
|
-
raise ValueError("
|
|
21
|
+
raise ValueError(f"{name} must be a 2D matrix")
|
|
22
22
|
csr = sparse.csr_matrix(arr)
|
|
23
23
|
|
|
24
24
|
# Guard against CSR matrices carrying explicit stored zeros in .data.
|
|
@@ -30,7 +30,7 @@ def _as_sparse_binary(mat) -> sparse.csr_matrix:
|
|
|
30
30
|
bad = data != 1
|
|
31
31
|
if bad.any():
|
|
32
32
|
idx = int(np.flatnonzero(bad)[0])
|
|
33
|
-
raise ValueError(f"
|
|
33
|
+
raise ValueError(f"{name} contains non-binary value: {data[idx]!r}")
|
|
34
34
|
|
|
35
35
|
csr = csr.astype(np.uint8)
|
|
36
36
|
csr.data[:] = 1
|
|
@@ -336,17 +336,17 @@ class AnnotationPanel:
|
|
|
336
336
|
|
|
337
337
|
return AnnotationPanel(out_shards, lhs_names + rhs_names)
|
|
338
338
|
|
|
339
|
-
def save_cache(self, path
|
|
340
|
-
save_annotations_cache(self, path
|
|
339
|
+
def save_cache(self, path) -> None:
|
|
340
|
+
save_annotations_cache(self, path)
|
|
341
341
|
|
|
342
342
|
|
|
343
|
-
def create_annotations(reference,
|
|
344
|
-
names = _coerce_annonames(
|
|
343
|
+
def create_annotations(reference, annotation_matrix, annotation_names) -> AnnotationPanel:
|
|
344
|
+
names = _coerce_annonames(annotation_names)
|
|
345
345
|
n = int(reference.num_snp)
|
|
346
|
-
mat = _as_sparse_binary(
|
|
346
|
+
mat = _as_sparse_binary(annotation_matrix, "annotation_matrix")
|
|
347
347
|
if mat.shape != (n, len(names)):
|
|
348
348
|
raise ValueError(
|
|
349
|
-
f"
|
|
349
|
+
f"annotation_matrix shape mismatch: expected ({n}, {len(names)}), got {mat.shape}"
|
|
350
350
|
)
|
|
351
351
|
|
|
352
352
|
out_shards = []
|
|
@@ -369,7 +369,7 @@ def create_annotation(reference, annovec, annoname) -> AnnotationPanel:
|
|
|
369
369
|
raise ValueError("annoname must be non-empty")
|
|
370
370
|
vec = _coerce_annovec(annovec, int(reference.num_snp))
|
|
371
371
|
mat = sparse.csr_matrix(vec.reshape(-1, 1))
|
|
372
|
-
return create_annotations(reference, mat, [name])
|
|
372
|
+
return create_annotations(reference, annotation_matrix=mat, annotation_names=[name])
|
|
373
373
|
|
|
374
374
|
|
|
375
375
|
def load_annotations(bed_paths, reference) -> AnnotationPanel:
|
|
@@ -384,10 +384,10 @@ def load_annotations(bed_paths, reference) -> AnnotationPanel:
|
|
|
384
384
|
raise FileNotFoundError(f"BED file not found: {p}")
|
|
385
385
|
|
|
386
386
|
annomat, annonames = _paint_annotations(paths, reference)
|
|
387
|
-
return create_annotations(reference, annomat, annonames)
|
|
387
|
+
return create_annotations(reference, annotation_matrix=annomat, annotation_names=annonames)
|
|
388
388
|
|
|
389
389
|
|
|
390
|
-
def save_annotations_cache(panel: AnnotationPanel, path
|
|
390
|
+
def save_annotations_cache(panel: AnnotationPanel, path) -> None:
|
|
391
391
|
meta = {
|
|
392
392
|
"schema": _CACHE_SCHEMA,
|
|
393
393
|
"shard_labels": [s.label for s in panel.shards],
|
|
@@ -519,8 +519,8 @@ class GenotypePanel:
|
|
|
519
519
|
same_ploidy = ploidy_male.reshape(1, -1)
|
|
520
520
|
geno[np.ix_(is_unknown, cols)] *= same_ploidy / 2.0
|
|
521
521
|
|
|
522
|
-
def save_cache(self, path
|
|
523
|
-
save_genotype_cache(self, path
|
|
522
|
+
def save_cache(self, path) -> None:
|
|
523
|
+
save_genotype_cache(self, path)
|
|
524
524
|
|
|
525
525
|
|
|
526
526
|
def _fam_equal(lhs: pd.DataFrame, rhs: pd.DataFrame) -> bool:
|
|
@@ -642,7 +642,11 @@ def load_genotype(bfile_prefix, reference) -> GenotypePanel:
|
|
|
642
642
|
else:
|
|
643
643
|
source_layout = "non_sharded"
|
|
644
644
|
shared = _load_source_record(prefix, None)
|
|
645
|
-
if
|
|
645
|
+
if (
|
|
646
|
+
"X" in ref_labels
|
|
647
|
+
and (shared.bim["chr"] == "X").any()
|
|
648
|
+
and Path(prefix + ".ploidy").is_file() is False
|
|
649
|
+
):
|
|
646
650
|
warnings.warn(
|
|
647
651
|
f"{prefix}: chrX genotype source has no .ploidy sidecar; "
|
|
648
652
|
"defaulting chrX rows to male/female ploidy (1, 2)",
|
|
@@ -692,7 +696,7 @@ def load_genotype(bfile_prefix, reference) -> GenotypePanel:
|
|
|
692
696
|
)
|
|
693
697
|
|
|
694
698
|
|
|
695
|
-
def save_genotype_cache(panel: GenotypePanel, path
|
|
699
|
+
def save_genotype_cache(panel: GenotypePanel, path) -> None:
|
|
696
700
|
meta = {
|
|
697
701
|
"schema": _CACHE_SCHEMA,
|
|
698
702
|
"n_shards": len(panel.shards),
|
|
@@ -341,8 +341,8 @@ class ReferencePanel:
|
|
|
341
341
|
ok = False
|
|
342
342
|
return ok
|
|
343
343
|
|
|
344
|
-
def save_cache(self, path
|
|
345
|
-
save_reference_cache(self, path
|
|
344
|
+
def save_cache(self, path) -> None:
|
|
345
|
+
save_reference_cache(self, path)
|
|
346
346
|
|
|
347
347
|
|
|
348
348
|
def load_reference(path, shards=None) -> ReferencePanel:
|
|
@@ -403,7 +403,7 @@ def load_reference(path, shards=None) -> ReferencePanel:
|
|
|
403
403
|
return ReferencePanel(out_shards)
|
|
404
404
|
|
|
405
405
|
|
|
406
|
-
def save_reference_cache(panel: ReferencePanel, path
|
|
406
|
+
def save_reference_cache(panel: ReferencePanel, path) -> None:
|
|
407
407
|
# Metadata (schema, labels, checksums) as a compact JSON blob stored in the npz.
|
|
408
408
|
# SNP-axis numeric vectors (bp) and string arrays are stored as native
|
|
409
409
|
# binary numpy arrays — not JSON — per the performance contract.
|
|
@@ -252,8 +252,8 @@ class Sumstats:
|
|
|
252
252
|
by_label = {s.label: s for s in self._shards}
|
|
253
253
|
return Sumstats([by_label[label] for label in selected])
|
|
254
254
|
|
|
255
|
-
def save_cache(self, path
|
|
256
|
-
save_sumstats_cache(self, path
|
|
255
|
+
def save_cache(self, path) -> None:
|
|
256
|
+
save_sumstats_cache(self, path)
|
|
257
257
|
|
|
258
258
|
|
|
259
259
|
def _derive_logp(p_vals: np.ndarray) -> np.ndarray:
|
|
@@ -393,26 +393,26 @@ def load_sumstats(path, reference) -> Sumstats:
|
|
|
393
393
|
|
|
394
394
|
def create_sumstats(
|
|
395
395
|
reference,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
396
|
+
p,
|
|
397
|
+
z=None,
|
|
398
|
+
n=None,
|
|
399
|
+
beta=None,
|
|
400
|
+
se=None,
|
|
401
|
+
eaf=None,
|
|
402
|
+
info=None,
|
|
403
403
|
) -> Sumstats:
|
|
404
|
-
|
|
405
|
-
aligned_p = _coerce_aligned_vec("
|
|
406
|
-
_validate_pvec(aligned_p, "
|
|
404
|
+
num_snp_value = int(reference.num_snp)
|
|
405
|
+
aligned_p = _coerce_aligned_vec("p", p, n=num_snp_value, allow_inf=True)
|
|
406
|
+
_validate_pvec(aligned_p, "p")
|
|
407
407
|
aligned_logp = _derive_logp(aligned_p)
|
|
408
408
|
|
|
409
409
|
aligned_optional = {
|
|
410
|
-
"z": None if
|
|
411
|
-
"n": None if
|
|
412
|
-
"beta": None if
|
|
413
|
-
"se": None if
|
|
414
|
-
"eaf": None if
|
|
415
|
-
"info": None if
|
|
410
|
+
"z": None if z is None else _coerce_aligned_vec("z", z, n=num_snp_value, allow_inf=False),
|
|
411
|
+
"n": None if n is None else _coerce_aligned_vec("n", n, n=num_snp_value, allow_inf=False),
|
|
412
|
+
"beta": None if beta is None else _coerce_aligned_vec("beta", beta, n=num_snp_value, allow_inf=False),
|
|
413
|
+
"se": None if se is None else _coerce_aligned_vec("se", se, n=num_snp_value, allow_inf=False),
|
|
414
|
+
"eaf": None if eaf is None else _coerce_aligned_vec("eaf", eaf, n=num_snp_value, allow_inf=False),
|
|
415
|
+
"info": None if info is None else _coerce_aligned_vec("info", info, n=num_snp_value, allow_inf=False),
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
return _build_sumstats_from_aligned(
|
|
@@ -422,7 +422,7 @@ def create_sumstats(
|
|
|
422
422
|
)
|
|
423
423
|
|
|
424
424
|
|
|
425
|
-
def save_sumstats_cache(sumstats: Sumstats, path
|
|
425
|
+
def save_sumstats_cache(sumstats: Sumstats, path) -> None:
|
|
426
426
|
meta = {
|
|
427
427
|
"schema": _CACHE_SCHEMA,
|
|
428
428
|
"shard_labels": [s.label for s in sumstats.shards],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|