allelign 0.1.0__py3-none-any.whl
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.
- allelign/__init__.py +69 -0
- allelign/__main__.py +5 -0
- allelign/_common.py +95 -0
- allelign/_numba.py +22 -0
- allelign/align.py +1252 -0
- allelign/alleles.py +90 -0
- allelign/build.py +57 -0
- allelign/catalog.py +88 -0
- allelign/cli.py +323 -0
- allelign/download.py +525 -0
- allelign/genotypes.py +241 -0
- allelign/ldcheck.py +771 -0
- allelign/qc.py +638 -0
- allelign/status.py +130 -0
- allelign/sumstats.py +1207 -0
- allelign/variants.py +155 -0
- allelign/warning_markers.py +15 -0
- allelign/weights.py +614 -0
- allelign-0.1.0.dist-info/METADATA +175 -0
- allelign-0.1.0.dist-info/RECORD +24 -0
- allelign-0.1.0.dist-info/WHEEL +5 -0
- allelign-0.1.0.dist-info/entry_points.txt +2 -0
- allelign-0.1.0.dist-info/licenses/LICENSE +21 -0
- allelign-0.1.0.dist-info/top_level.txt +1 -0
allelign/variants.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Reference variant tables: the variants (and counted alleles) to align to.
|
|
2
|
+
|
|
3
|
+
A :class:`Variants` table is what summary statistics or weights are aligned to:
|
|
4
|
+
an LD reference, a genotype file, or another GWAS. ``a1`` is the counted
|
|
5
|
+
allele -- the allele a dosage counts and an aligned effect refers to. The
|
|
6
|
+
attribute names match ldpred3's ``VariantTable``, so either table works with
|
|
7
|
+
either package's harmoniser.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
import os
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
from ._common import open_text
|
|
19
|
+
|
|
20
|
+
__all__ = ["Variants", "read_bim", "read_pvar", "read_variants"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Variants:
|
|
25
|
+
"""Per-variant reference metadata as parallel arrays.
|
|
26
|
+
|
|
27
|
+
Mutable by default. :meth:`freeze` marks the matching columns read-only so
|
|
28
|
+
the harmoniser can cache its indices across calls (useful when many files
|
|
29
|
+
are aligned to one reference).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
chrom: np.ndarray
|
|
33
|
+
id: np.ndarray
|
|
34
|
+
pos: np.ndarray
|
|
35
|
+
a1: np.ndarray
|
|
36
|
+
a2: np.ndarray
|
|
37
|
+
alternate_id: np.ndarray = None
|
|
38
|
+
cm: np.ndarray = None # genetic position, when the source has one
|
|
39
|
+
|
|
40
|
+
def __len__(self):
|
|
41
|
+
return len(self.id)
|
|
42
|
+
|
|
43
|
+
def freeze(self):
|
|
44
|
+
"""Mark matching columns read-only, without copying; returns self."""
|
|
45
|
+
was_writable = False
|
|
46
|
+
for name in ("chrom", "id", "pos", "a1", "a2", "alternate_id"):
|
|
47
|
+
column = getattr(self, name)
|
|
48
|
+
if column is not None:
|
|
49
|
+
column = np.asarray(column)
|
|
50
|
+
was_writable |= column.flags.writeable
|
|
51
|
+
column.flags.writeable = False
|
|
52
|
+
setattr(self, name, column)
|
|
53
|
+
if was_writable:
|
|
54
|
+
from .align import _CACHE_ATTR, _INDEX_CACHE_LOCK
|
|
55
|
+
with _INDEX_CACHE_LOCK:
|
|
56
|
+
self.__dict__.pop(_CACHE_ATTR, None)
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def subset(self, idx):
|
|
60
|
+
"""A new (mutable) table keeping the rows in ``idx``."""
|
|
61
|
+
idx = np.asarray(idx)
|
|
62
|
+
return Variants(
|
|
63
|
+
chrom=self.chrom[idx], id=self.id[idx], pos=self.pos[idx],
|
|
64
|
+
a1=self.a1[idx], a2=self.a2[idx],
|
|
65
|
+
alternate_id=(None if self.alternate_id is None
|
|
66
|
+
else self.alternate_id[idx]),
|
|
67
|
+
cm=None if self.cm is None else self.cm[idx])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def read_bim(path):
|
|
71
|
+
"""Read a PLINK 1 ``.bim`` file; ``a1`` is column 5, PLINK's counted allele."""
|
|
72
|
+
chrom, ids, cm, pos, a1, a2 = [], [], [], [], [], []
|
|
73
|
+
pool = {}
|
|
74
|
+
with open_text(path) as fh:
|
|
75
|
+
for line_no, line in enumerate(fh, start=1):
|
|
76
|
+
f = line.split()
|
|
77
|
+
if not f:
|
|
78
|
+
continue
|
|
79
|
+
if len(f) < 6:
|
|
80
|
+
raise ValueError(f"{path}: line {line_no} has {len(f)} fields, "
|
|
81
|
+
"expected 6")
|
|
82
|
+
chrom.append(pool.setdefault(f[0], f[0]))
|
|
83
|
+
ids.append(f[1])
|
|
84
|
+
cm.append(float(f[2]))
|
|
85
|
+
pos.append(int(float(f[3])))
|
|
86
|
+
x, y = f[4].upper(), f[5].upper()
|
|
87
|
+
a1.append(pool.setdefault(x, x))
|
|
88
|
+
a2.append(pool.setdefault(y, y))
|
|
89
|
+
return Variants(chrom=np.array(chrom, dtype=object),
|
|
90
|
+
id=np.array(ids, dtype=object),
|
|
91
|
+
pos=np.array(pos, dtype=np.int64),
|
|
92
|
+
a1=np.array(a1, dtype=object),
|
|
93
|
+
a2=np.array(a2, dtype=object),
|
|
94
|
+
cm=np.array(cm, dtype=float))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def read_pvar(path):
|
|
98
|
+
"""Read a PLINK 2 ``.pvar`` (or VCF-like) variant file.
|
|
99
|
+
|
|
100
|
+
``a1`` is ``ALT``, the allele PLINK 2 dosages count by default, and ``a2``
|
|
101
|
+
is ``REF``. Multi-allelic records (comma in ``ALT``) are skipped with a
|
|
102
|
+
warning: a single dosage cannot represent them.
|
|
103
|
+
"""
|
|
104
|
+
chrom, ids, pos, a1, a2 = [], [], [], [], []
|
|
105
|
+
pool = {}
|
|
106
|
+
n_multi = 0
|
|
107
|
+
col = None
|
|
108
|
+
with open_text(path) as fh:
|
|
109
|
+
for line in fh:
|
|
110
|
+
if line.startswith("##"):
|
|
111
|
+
continue
|
|
112
|
+
if line.startswith("#"):
|
|
113
|
+
header = [h.strip().upper() for h in line[1:].split()]
|
|
114
|
+
col = {name: i for i, name in enumerate(header)}
|
|
115
|
+
continue
|
|
116
|
+
f = line.split()
|
|
117
|
+
if not f:
|
|
118
|
+
continue
|
|
119
|
+
if col is None:
|
|
120
|
+
# A headerless .pvar follows the .bim column order.
|
|
121
|
+
col = {name: i for i, name in enumerate(
|
|
122
|
+
["CHROM", "ID", "CM", "POS", "ALT", "REF"])}
|
|
123
|
+
try:
|
|
124
|
+
c, vid = f[col["CHROM"]], f[col["ID"]]
|
|
125
|
+
p, ref, alt = f[col["POS"]], f[col["REF"]], f[col["ALT"]]
|
|
126
|
+
except KeyError as exc:
|
|
127
|
+
raise ValueError(f"{path}: header lacks column {exc}") from None
|
|
128
|
+
if "," in alt:
|
|
129
|
+
n_multi += 1
|
|
130
|
+
continue
|
|
131
|
+
chrom.append(pool.setdefault(c, c))
|
|
132
|
+
ids.append(vid)
|
|
133
|
+
pos.append(int(float(p)))
|
|
134
|
+
alt, ref = alt.upper(), ref.upper()
|
|
135
|
+
a1.append(pool.setdefault(alt, alt))
|
|
136
|
+
a2.append(pool.setdefault(ref, ref))
|
|
137
|
+
if n_multi:
|
|
138
|
+
warnings.warn(f"{path}: skipped {n_multi:,} multi-allelic records",
|
|
139
|
+
RuntimeWarning, stacklevel=2)
|
|
140
|
+
return Variants(chrom=np.array(chrom, dtype=object),
|
|
141
|
+
id=np.array(ids, dtype=object),
|
|
142
|
+
pos=np.array(pos, dtype=np.int64),
|
|
143
|
+
a1=np.array(a1, dtype=object),
|
|
144
|
+
a2=np.array(a2, dtype=object))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def read_variants(path):
|
|
148
|
+
"""Read a ``.bim`` or ``.pvar`` (optionally gzipped) by its extension."""
|
|
149
|
+
name = os.fspath(path)
|
|
150
|
+
stem = name[:-3] if name.endswith(".gz") else name
|
|
151
|
+
if stem.endswith(".bim"):
|
|
152
|
+
return read_bim(name)
|
|
153
|
+
if stem.endswith((".pvar", ".vcf")):
|
|
154
|
+
return read_pvar(name)
|
|
155
|
+
raise ValueError(f"{name}: expected a .bim or .pvar variant file")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Stable fragments of the warnings and log reasons allelign emits.
|
|
2
|
+
|
|
3
|
+
Consumers that classify messages (SMARTpred's verdict, for one) import these
|
|
4
|
+
constants instead of copying the text, so a rewording cannot silently stop a
|
|
5
|
+
classifier from firing. The values match ldpred3's markers of the same name.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
AF_ANTI_CORRELATED = "anti-correlated"
|
|
9
|
+
AF_SAFETY_LIMIT = "safety limit"
|
|
10
|
+
AF_CONCORDANCE_GUARD = "concordance guard"
|
|
11
|
+
LD_SCREEN_HIGH_DROP = "LD-consistency screen dropped"
|
|
12
|
+
LD_SCREEN_UNTESTED = "never entered a window"
|
|
13
|
+
|
|
14
|
+
ATTENTION_MARKERS = (AF_ANTI_CORRELATED, AF_SAFETY_LIMIT, AF_CONCORDANCE_GUARD,
|
|
15
|
+
LD_SCREEN_HIGH_DROP, LD_SCREEN_UNTESTED)
|