bed-reader 0.2.37__cp312-none-win_amd64.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.
- bed_reader/__init__.py +30 -0
- bed_reader/_open_bed.py +1290 -0
- bed_reader/_sample_data.py +99 -0
- bed_reader/_to_bed.py +235 -0
- bed_reader/bed_reader.cp312-win_amd64.pyd +0 -0
- bed_reader/tests/__init__.py +1 -0
- bed_reader/tests/benchmark/benchmark.py +129 -0
- bed_reader/tests/data/500x400_o640_array.memmap +0 -0
- bed_reader/tests/data/badfile.bed +1 -0
- bed_reader/tests/data/empty.bed +0 -0
- bed_reader/tests/data/plink_sim_10s_100v_10pmiss.bed +0 -0
- bed_reader/tests/data/plink_sim_10s_100v_10pmiss.bim +100 -0
- bed_reader/tests/data/plink_sim_10s_100v_10pmiss.fam +10 -0
- bed_reader/tests/data/small.bad_bim +4 -0
- bed_reader/tests/data/small.bed +1 -0
- bed_reader/tests/data/small.bim +4 -0
- bed_reader/tests/data/small.bim_bad_positions.bim +4 -0
- bed_reader/tests/data/small.deb +1 -0
- bed_reader/tests/data/small.fam +3 -0
- bed_reader/tests/data/small.fam_bad +4 -0
- bed_reader/tests/data/small.maf +3 -0
- bed_reader/tests/data/small.mib +4 -0
- bed_reader/tests/data/small2_array.memmap +0 -0
- bed_reader/tests/data/small_array.memmap +0 -0
- bed_reader/tests/data/small_no_bim.bed +1 -0
- bed_reader/tests/data/small_no_bim.fam +3 -0
- bed_reader/tests/data/small_no_fam.bed +1 -0
- bed_reader/tests/data/small_no_fam.bim +4 -0
- bed_reader/tests/data/small_too_short.bed +1 -0
- bed_reader/tests/data/small_too_short.bim +4 -0
- bed_reader/tests/data/small_too_short.fam +3 -0
- bed_reader/tests/data/smallmode0.bed +0 -0
- bed_reader/tests/data/smallmode0.bim +3 -0
- bed_reader/tests/data/smallmode0.fam +4 -0
- bed_reader/tests/data/smallmodebad.bed +1 -0
- bed_reader/tests/data/some_missing.bed +0 -0
- bed_reader/tests/data/some_missing.bim +100 -0
- bed_reader/tests/data/some_missing.fam +100 -0
- bed_reader/tests/data/some_missing.properties.npz +0 -0
- bed_reader/tests/data/some_missing.val.npy +0 -0
- bed_reader/tests/data/toydata.5chrom.bed +0 -0
- bed_reader/tests/data/toydata.5chrom.bim +10000 -0
- bed_reader/tests/data/toydata.5chrom.fam +500 -0
- bed_reader/tests/registry.txt +36 -0
- bed_reader/tests/test_file_dot.py +368 -0
- bed_reader/tests/test_open_bed.py +806 -0
- bed_reader-0.2.37.dist-info/METADATA +112 -0
- bed_reader-0.2.37.dist-info/RECORD +51 -0
- bed_reader-0.2.37.dist-info/WHEEL +4 -0
- bed_reader-0.2.37.dist-info/license_files/AUTHORS.txt +2 -0
- bed_reader-0.2.37.dist-info/license_files/LICENSE.md +202 -0
bed_reader/_open_bed.py
ADDED
|
@@ -0,0 +1,1290 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import multiprocessing
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from itertools import repeat, takewhile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, List, Mapping, Optional, Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from .bed_reader import read_f32, read_f64, read_i8 # type: ignore
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# https://stackoverflow.com/questions/845058/how-to-get-line-count-of-a-large-file-cheaply-in-python
|
|
16
|
+
def _rawincount(filepath):
|
|
17
|
+
with open(filepath, "rb") as f:
|
|
18
|
+
bufgen = takewhile(lambda x: x, (f.raw.read(1024 * 1024) for _ in repeat(None)))
|
|
19
|
+
return sum(buf.count(b"\n") for buf in bufgen)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class _MetaMeta:
|
|
24
|
+
suffix: str
|
|
25
|
+
column: int
|
|
26
|
+
dtype: type
|
|
27
|
+
missing_value: object
|
|
28
|
+
fill_sequence: object
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _all_same(key, length, missing, dtype):
|
|
32
|
+
if np.issubdtype(dtype, np.str_):
|
|
33
|
+
dtype = f"<U{len(missing)}"
|
|
34
|
+
return np.full(length, missing, dtype=dtype)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _sequence(key, length, missing, dtype):
|
|
38
|
+
if np.issubdtype(dtype, np.str_):
|
|
39
|
+
longest = len(f"{key}{length}")
|
|
40
|
+
dtype = f"<U{longest}"
|
|
41
|
+
return np.fromiter(
|
|
42
|
+
(f"{key}{i + 1}" for i in range(length)), dtype=dtype, count=length
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_delimiters = {"fam": r"\s+", "bim": "\t"}
|
|
47
|
+
_count_name = {"fam": "iid_count", "bim": "sid_count"}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_meta_meta = {
|
|
51
|
+
# https://stackoverflow.com/questions/41921255/staticmethod-object-is-not-callable
|
|
52
|
+
"fid": _MetaMeta("fam", 0, np.str_, "0", _all_same),
|
|
53
|
+
"iid": _MetaMeta("fam", 1, np.str_, None, _sequence),
|
|
54
|
+
"father": _MetaMeta("fam", 2, np.str_, "0", _all_same),
|
|
55
|
+
"mother": _MetaMeta("fam", 3, np.str_, "0", _all_same),
|
|
56
|
+
"sex": _MetaMeta("fam", 4, np.int32, 0, _all_same),
|
|
57
|
+
"pheno": _MetaMeta("fam", 5, np.str_, "0", _all_same),
|
|
58
|
+
"chromosome": _MetaMeta("bim", 0, np.str_, "0", _all_same),
|
|
59
|
+
"sid": _MetaMeta("bim", 1, np.str_, None, _sequence),
|
|
60
|
+
"cm_position": _MetaMeta("bim", 2, np.float32, 0, _all_same),
|
|
61
|
+
"bp_position": _MetaMeta("bim", 3, np.int32, 0, _all_same),
|
|
62
|
+
"allele_1": _MetaMeta("bim", 4, np.str_, "A1", _all_same),
|
|
63
|
+
"allele_2": _MetaMeta("bim", 5, np.str_, "A2", _all_same),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_num_threads(num_threads=None):
|
|
68
|
+
if num_threads is not None:
|
|
69
|
+
return num_threads
|
|
70
|
+
if "PST_NUM_THREADS" in os.environ:
|
|
71
|
+
return int(os.environ["PST_NUM_THREADS"])
|
|
72
|
+
if "NUM_THREADS" in os.environ:
|
|
73
|
+
return int(os.environ["NUM_THREADS"])
|
|
74
|
+
if "MKL_NUM_THREADS" in os.environ:
|
|
75
|
+
return int(os.environ["MKL_NUM_THREADS"])
|
|
76
|
+
return multiprocessing.cpu_count()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class open_bed:
|
|
80
|
+
"""
|
|
81
|
+
Open a PLINK .bed file for reading.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
filepath: pathlib.Path or str
|
|
86
|
+
File path to the .bed file.
|
|
87
|
+
iid_count: None or int, optional
|
|
88
|
+
Number of individuals (samples) in the .bed file.
|
|
89
|
+
The default (``iid_count=None``) finds the number
|
|
90
|
+
automatically by quickly scanning the .fam file.
|
|
91
|
+
sid_count: None or int, optional
|
|
92
|
+
Number of SNPs (variants) in the .bed file.
|
|
93
|
+
The default (``sid_count=None``) finds the number
|
|
94
|
+
automatically by quickly scanning the .bim file.
|
|
95
|
+
properties: dict, optional
|
|
96
|
+
A dictionary of any replacement properties. The default is an empty dictionary.
|
|
97
|
+
The keys of the dictionary are the names of the properties to replace.
|
|
98
|
+
The possible keys are:
|
|
99
|
+
|
|
100
|
+
"fid" (family id), "iid" (individual or sample id), "father" (father id),
|
|
101
|
+
"mother" (mother id), "sex", "pheno" (phenotype), "chromosome", "sid"
|
|
102
|
+
(SNP or variant id), "cm_position" (centimorgan position), "bp_position"
|
|
103
|
+
(base-pair position), "allele_1", "allele_2".
|
|
104
|
+
|
|
105
|
+
The values are replacement lists or arrays. A value can also be `None`,
|
|
106
|
+
meaning do not read or offer this property. See examples, below.
|
|
107
|
+
|
|
108
|
+
The list or array will be converted to a :class:`numpy.ndarray`
|
|
109
|
+
of the appropriate dtype, if necessary. Any :class:`numpy.nan` values
|
|
110
|
+
will converted to the appropriate missing value. The PLINK `.fam specification
|
|
111
|
+
<https://www.cog-genomics.org/plink2/formats#fam>`_
|
|
112
|
+
and `.bim specification <https://www.cog-genomics.org/plink2/formats#bim>`_
|
|
113
|
+
lists the dtypes and missing values for each property.
|
|
114
|
+
|
|
115
|
+
count_A1: bool, optional
|
|
116
|
+
True (default) to count the number of A1 alleles (the PLINK standard).
|
|
117
|
+
False to count the number of A2 alleles.
|
|
118
|
+
num_threads: None or int, optional
|
|
119
|
+
The number of threads with which to read data. Defaults to all available
|
|
120
|
+
processors.
|
|
121
|
+
Can also be set with these environment variables (listed in priority order):
|
|
122
|
+
'PST_NUM_THREADS', 'NUM_THREADS', 'MKL_NUM_THREADS'.
|
|
123
|
+
skip_format_check: bool, optional
|
|
124
|
+
False (default) to immediately check for expected starting bytes in
|
|
125
|
+
the .bed file. True to delay the check until (and if) data is read.
|
|
126
|
+
fam_filepath: pathlib.Path or str, optional
|
|
127
|
+
Path to the file containing information about each individual (sample).
|
|
128
|
+
Defaults to replacing the .bed file’s suffix with .fam.
|
|
129
|
+
bim_filepath: pathlib.Path or str, optional
|
|
130
|
+
Path to the file containing information about each SNP (variant).
|
|
131
|
+
Defaults to replacing the .bed file’s suffix with .bim.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
open_bed
|
|
136
|
+
an open_bed object
|
|
137
|
+
|
|
138
|
+
Examples
|
|
139
|
+
--------
|
|
140
|
+
|
|
141
|
+
List individual (sample) :attr:`iid` and SNP (variant) :attr:`sid`, then :meth:`read`
|
|
142
|
+
the whole file.
|
|
143
|
+
|
|
144
|
+
.. doctest::
|
|
145
|
+
|
|
146
|
+
>>> from bed_reader import open_bed, sample_file
|
|
147
|
+
>>>
|
|
148
|
+
>>> file_name = sample_file("small.bed")
|
|
149
|
+
>>> bed = open_bed(file_name)
|
|
150
|
+
>>> print(bed.iid)
|
|
151
|
+
['iid1' 'iid2' 'iid3']
|
|
152
|
+
>>> print(bed.sid)
|
|
153
|
+
['sid1' 'sid2' 'sid3' 'sid4']
|
|
154
|
+
>>> print(bed.read())
|
|
155
|
+
[[ 1. 0. nan 0.]
|
|
156
|
+
[ 2. 0. nan 2.]
|
|
157
|
+
[ 0. 1. 2. 0.]]
|
|
158
|
+
>>> del bed # optional: delete bed object
|
|
159
|
+
|
|
160
|
+
Open the file and read data for one SNP (variant)
|
|
161
|
+
at index position 2.
|
|
162
|
+
|
|
163
|
+
.. doctest::
|
|
164
|
+
|
|
165
|
+
>>> import numpy as np
|
|
166
|
+
>>> with open_bed(file_name) as bed:
|
|
167
|
+
... print(bed.read(np.s_[:,2]))
|
|
168
|
+
[[nan]
|
|
169
|
+
[nan]
|
|
170
|
+
[ 2.]]
|
|
171
|
+
|
|
172
|
+
Replace :attr:`iid`.
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
>>> bed = open_bed(file_name, properties={"iid":["sample1","sample2","sample3"]})
|
|
176
|
+
>>> print(bed.iid) # replaced
|
|
177
|
+
['sample1' 'sample2' 'sample3']
|
|
178
|
+
>>> print(bed.sid) # same as before
|
|
179
|
+
['sid1' 'sid2' 'sid3' 'sid4']
|
|
180
|
+
|
|
181
|
+
Give the number of individuals (samples) and SNPs (variants) so that the .fam and
|
|
182
|
+
.bim files need never be opened.
|
|
183
|
+
|
|
184
|
+
>>> with open_bed(file_name, iid_count=3, sid_count=4) as bed:
|
|
185
|
+
... print(bed.read())
|
|
186
|
+
[[ 1. 0. nan 0.]
|
|
187
|
+
[ 2. 0. nan 2.]
|
|
188
|
+
[ 0. 1. 2. 0.]]
|
|
189
|
+
|
|
190
|
+
Mark some properties as "don’t read or offer".
|
|
191
|
+
|
|
192
|
+
>>> bed = open_bed(file_name, properties={
|
|
193
|
+
... "father" : None, "mother" : None, "sex" : None, "pheno" : None,
|
|
194
|
+
... "allele_1" : None, "allele_2":None })
|
|
195
|
+
>>> print(bed.iid) # read from file
|
|
196
|
+
['iid1' 'iid2' 'iid3']
|
|
197
|
+
>>> print(bed.allele_2) # not read and not offered
|
|
198
|
+
None
|
|
199
|
+
|
|
200
|
+
See the :meth:`read` for details of reading batches via slicing and fancy indexing.
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def __init__(
|
|
205
|
+
self,
|
|
206
|
+
filepath: Union[str, Path],
|
|
207
|
+
iid_count: Optional[int] = None,
|
|
208
|
+
sid_count: Optional[int] = None,
|
|
209
|
+
properties: Mapping[str, List[Any]] = {},
|
|
210
|
+
count_A1: bool = True,
|
|
211
|
+
num_threads: Optional[int] = None,
|
|
212
|
+
skip_format_check: bool = False,
|
|
213
|
+
fam_filepath: Union[str, Path] = None,
|
|
214
|
+
bim_filepath: Union[str, Path] = None,
|
|
215
|
+
):
|
|
216
|
+
self.filepath = Path(filepath)
|
|
217
|
+
self.count_A1 = count_A1
|
|
218
|
+
self._num_threads = num_threads
|
|
219
|
+
self.skip_format_check = skip_format_check
|
|
220
|
+
self._fam_filepath = (
|
|
221
|
+
Path(fam_filepath)
|
|
222
|
+
if fam_filepath is not None
|
|
223
|
+
else self.filepath.parent / (self.filepath.stem + ".fam")
|
|
224
|
+
)
|
|
225
|
+
self._bim_filepath = (
|
|
226
|
+
Path(bim_filepath)
|
|
227
|
+
if bim_filepath is not None
|
|
228
|
+
else self.filepath.parent / (self.filepath.stem + ".bim")
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
self.properties_dict, self._counts = open_bed._fix_up_properties(
|
|
232
|
+
properties, iid_count, sid_count, use_fill_sequence=False
|
|
233
|
+
)
|
|
234
|
+
self._iid_range = None
|
|
235
|
+
self._sid_range = None
|
|
236
|
+
|
|
237
|
+
if not self.skip_format_check:
|
|
238
|
+
with open(self.filepath, "rb") as filepointer:
|
|
239
|
+
self._check_file(filepointer)
|
|
240
|
+
|
|
241
|
+
def read(
|
|
242
|
+
self,
|
|
243
|
+
index: Optional[Any] = None,
|
|
244
|
+
dtype: Optional[Union[type, str]] = "float32",
|
|
245
|
+
order: Optional[str] = "F",
|
|
246
|
+
force_python_only: Optional[bool] = False,
|
|
247
|
+
num_threads=None,
|
|
248
|
+
) -> np.ndarray:
|
|
249
|
+
"""
|
|
250
|
+
Read genotype information.
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
index:
|
|
255
|
+
An optional expression specifying the individuals (samples) and SNPs
|
|
256
|
+
(variants) to read. (See examples, below).
|
|
257
|
+
Defaults to ``None``, meaning read all.
|
|
258
|
+
|
|
259
|
+
(If index is a tuple, the first component indexes the individuals and the
|
|
260
|
+
second indexes
|
|
261
|
+
the SNPs. If it is not a tuple and not None, it indexes SNPs.)
|
|
262
|
+
|
|
263
|
+
dtype: {'float32' (default), 'float64', 'int8'}, optional
|
|
264
|
+
The desired data-type for the returned array.
|
|
265
|
+
order : {'F','C'}, optional
|
|
266
|
+
The desired memory layout for the returned array.
|
|
267
|
+
Defaults to ``F`` (Fortran order, which is SNP-major).
|
|
268
|
+
force_python_only: bool, optional
|
|
269
|
+
If False (default), uses the faster Rust code; otherwise it uses the slower
|
|
270
|
+
pure Python code.
|
|
271
|
+
|
|
272
|
+
num_threads: None or int, optional
|
|
273
|
+
The number of threads with which to read data. Defaults to all available
|
|
274
|
+
processors.
|
|
275
|
+
Can also be set with :class:`open_bed` or these
|
|
276
|
+
environment variables (listed in priority order):
|
|
277
|
+
'PST_NUM_THREADS', 'NUM_THREADS', 'MKL_NUM_THREADS'.
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
numpy.ndarray
|
|
283
|
+
2-D array containing values of 0, 1, 2, or missing
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
Rows represent individuals (samples). Columns represent SNPs (variants).
|
|
287
|
+
|
|
288
|
+
For ``dtype`` 'float32' and 'float64', NaN indicates missing values.
|
|
289
|
+
For 'int8', -127 indicates missing values.
|
|
290
|
+
|
|
291
|
+
Examples
|
|
292
|
+
--------
|
|
293
|
+
|
|
294
|
+
To read all data in a .bed file, set ``index`` to ``None``. This is the default.
|
|
295
|
+
|
|
296
|
+
.. doctest::
|
|
297
|
+
|
|
298
|
+
>>> from bed_reader import open_bed, sample_file
|
|
299
|
+
>>>
|
|
300
|
+
>>> file_name = sample_file("small.bed")
|
|
301
|
+
>>> with open_bed(file_name) as bed:
|
|
302
|
+
... print(bed.read())
|
|
303
|
+
[[ 1. 0. nan 0.]
|
|
304
|
+
[ 2. 0. nan 2.]
|
|
305
|
+
[ 0. 1. 2. 0.]]
|
|
306
|
+
|
|
307
|
+
To read selected individuals (samples) and/or SNPs (variants), set each part of
|
|
308
|
+
a :class:`numpy.s_` to an `int`, a list of `int`, a slice expression, or
|
|
309
|
+
a list of `bool`.
|
|
310
|
+
Negative integers count from the end of the list.
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
.. doctest::
|
|
314
|
+
|
|
315
|
+
>>> import numpy as np
|
|
316
|
+
>>> bed = open_bed(file_name)
|
|
317
|
+
>>> print(bed.read(np.s_[:,2])) # read the SNPs indexed by 2.
|
|
318
|
+
[[nan]
|
|
319
|
+
[nan]
|
|
320
|
+
[ 2.]]
|
|
321
|
+
>>> print(bed.read(np.s_[:,[2,3,0]])) # read the SNPs indexed by 2, 3, and 0
|
|
322
|
+
[[nan 0. 1.]
|
|
323
|
+
[nan 2. 2.]
|
|
324
|
+
[ 2. 0. 0.]]
|
|
325
|
+
>>> # read SNPs from 1 (inclusive) to 4 (exclusive)
|
|
326
|
+
>>> print(bed.read(np.s_[:,1:4]))
|
|
327
|
+
[[ 0. nan 0.]
|
|
328
|
+
[ 0. nan 2.]
|
|
329
|
+
[ 1. 2. 0.]]
|
|
330
|
+
>>> print(np.unique(bed.chromosome)) # print unique chrom values
|
|
331
|
+
['1' '5' 'Y']
|
|
332
|
+
>>> print(bed.read(np.s_[:,bed.chromosome=='5'])) # read all SNPs in chrom 5
|
|
333
|
+
[[nan]
|
|
334
|
+
[nan]
|
|
335
|
+
[ 2.]]
|
|
336
|
+
>>> print(bed.read(np.s_[0,:])) # Read 1st individual (across all SNPs)
|
|
337
|
+
[[ 1. 0. nan 0.]]
|
|
338
|
+
>>> print(bed.read(np.s_[::2,:])) # Read every 2nd individual
|
|
339
|
+
[[ 1. 0. nan 0.]
|
|
340
|
+
[ 0. 1. 2. 0.]]
|
|
341
|
+
>>> #read last and 2nd-to-last individuals and the last SNPs
|
|
342
|
+
>>> print(bed.read(np.s_[[-1,-2],-1]))
|
|
343
|
+
[[0.]
|
|
344
|
+
[2.]]
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
You can give a dtype for the output.
|
|
348
|
+
|
|
349
|
+
.. doctest::
|
|
350
|
+
|
|
351
|
+
>>> print(bed.read(dtype='int8'))
|
|
352
|
+
[[ 1 0 -127 0]
|
|
353
|
+
[ 2 0 -127 2]
|
|
354
|
+
[ 0 1 2 0]]
|
|
355
|
+
>>> del bed # optional: delete bed object
|
|
356
|
+
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
iid_index_or_slice_etc, sid_index_or_slice_etc = self._split_index(index)
|
|
360
|
+
|
|
361
|
+
dtype = np.dtype(dtype)
|
|
362
|
+
if order not in {"F", "C"}:
|
|
363
|
+
raise ValueError(f"order '{order}' not known, only 'F', 'C'")
|
|
364
|
+
|
|
365
|
+
# Later happy with _iid_range and _sid_range or could it be done with
|
|
366
|
+
# allocation them?
|
|
367
|
+
if self._iid_range is None:
|
|
368
|
+
self._iid_range = np.arange(self.iid_count, dtype="intp")
|
|
369
|
+
if self._sid_range is None:
|
|
370
|
+
self._sid_range = np.arange(self.sid_count, dtype="intp")
|
|
371
|
+
|
|
372
|
+
iid_index = np.ascontiguousarray(
|
|
373
|
+
self._iid_range[iid_index_or_slice_etc],
|
|
374
|
+
dtype="intp",
|
|
375
|
+
)
|
|
376
|
+
sid_index = np.ascontiguousarray(
|
|
377
|
+
self._sid_range[sid_index_or_slice_etc], dtype="intp"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if not force_python_only:
|
|
381
|
+
num_threads = get_num_threads(
|
|
382
|
+
self._num_threads if num_threads is None else num_threads
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
val = np.zeros((len(iid_index), len(sid_index)), order=order, dtype=dtype)
|
|
386
|
+
|
|
387
|
+
if self.iid_count > 0 and self.sid_count > 0:
|
|
388
|
+
if dtype == np.int8:
|
|
389
|
+
reader = read_i8
|
|
390
|
+
elif dtype == np.float64:
|
|
391
|
+
reader = read_f64
|
|
392
|
+
elif dtype == np.float32:
|
|
393
|
+
reader = read_f32
|
|
394
|
+
else:
|
|
395
|
+
raise ValueError(
|
|
396
|
+
f"dtype '{val.dtype}' not known, only "
|
|
397
|
+
+ "'int8', 'float32', and 'float64' are allowed."
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
reader(
|
|
401
|
+
str(self.filepath),
|
|
402
|
+
iid_count=self.iid_count,
|
|
403
|
+
sid_count=self.sid_count,
|
|
404
|
+
is_a1_counted=self.count_A1,
|
|
405
|
+
iid_index=iid_index,
|
|
406
|
+
sid_index=sid_index,
|
|
407
|
+
val=val,
|
|
408
|
+
num_threads=num_threads,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
else:
|
|
412
|
+
if not self.count_A1:
|
|
413
|
+
byteZero = 0
|
|
414
|
+
byteThree = 2
|
|
415
|
+
else:
|
|
416
|
+
byteZero = 2
|
|
417
|
+
byteThree = 0
|
|
418
|
+
if dtype == np.int8:
|
|
419
|
+
missing = -127
|
|
420
|
+
else:
|
|
421
|
+
missing = np.nan
|
|
422
|
+
# An earlier version of this code had a way to read consecutive SNPs of code
|
|
423
|
+
# in one read. May want
|
|
424
|
+
# to add that ability back to the code.
|
|
425
|
+
# Also, note that reading with python will often result in
|
|
426
|
+
# non-contiguous memory
|
|
427
|
+
# logging.warn("using pure python plink parser (might be much slower!!)")
|
|
428
|
+
val = np.zeros(
|
|
429
|
+
((int(np.ceil(0.25 * self.iid_count)) * 4), len(sid_index)),
|
|
430
|
+
order=order,
|
|
431
|
+
dtype=dtype,
|
|
432
|
+
) # allocate it a little big
|
|
433
|
+
|
|
434
|
+
nbyte = int(np.ceil(0.25 * self.iid_count))
|
|
435
|
+
with open(self.filepath, "rb") as filepointer:
|
|
436
|
+
for SNPsIndex, bimIndex in enumerate(sid_index):
|
|
437
|
+
startbit = int(np.ceil(0.25 * self.iid_count) * bimIndex + 3)
|
|
438
|
+
filepointer.seek(startbit)
|
|
439
|
+
bytes = np.array(bytearray(filepointer.read(nbyte))).reshape(
|
|
440
|
+
(int(np.ceil(0.25 * self.iid_count)), 1), order="F"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
val[3::4, SNPsIndex : SNPsIndex + 1] = byteZero
|
|
444
|
+
val[3::4, SNPsIndex : SNPsIndex + 1][bytes >= 64] = missing
|
|
445
|
+
val[3::4, SNPsIndex : SNPsIndex + 1][bytes >= 128] = 1
|
|
446
|
+
val[3::4, SNPsIndex : SNPsIndex + 1][bytes >= 192] = byteThree
|
|
447
|
+
bytes = np.mod(bytes, 64)
|
|
448
|
+
val[2::4, SNPsIndex : SNPsIndex + 1] = byteZero
|
|
449
|
+
val[2::4, SNPsIndex : SNPsIndex + 1][bytes >= 16] = missing
|
|
450
|
+
val[2::4, SNPsIndex : SNPsIndex + 1][bytes >= 32] = 1
|
|
451
|
+
val[2::4, SNPsIndex : SNPsIndex + 1][bytes >= 48] = byteThree
|
|
452
|
+
bytes = np.mod(bytes, 16)
|
|
453
|
+
val[1::4, SNPsIndex : SNPsIndex + 1] = byteZero
|
|
454
|
+
val[1::4, SNPsIndex : SNPsIndex + 1][bytes >= 4] = missing
|
|
455
|
+
val[1::4, SNPsIndex : SNPsIndex + 1][bytes >= 8] = 1
|
|
456
|
+
val[1::4, SNPsIndex : SNPsIndex + 1][bytes >= 12] = byteThree
|
|
457
|
+
bytes = np.mod(bytes, 4)
|
|
458
|
+
val[0::4, SNPsIndex : SNPsIndex + 1] = byteZero
|
|
459
|
+
val[0::4, SNPsIndex : SNPsIndex + 1][bytes >= 1] = missing
|
|
460
|
+
val[0::4, SNPsIndex : SNPsIndex + 1][bytes >= 2] = 1
|
|
461
|
+
val[0::4, SNPsIndex : SNPsIndex + 1][bytes >= 3] = byteThree
|
|
462
|
+
val = val[iid_index, :] # reorder or trim any extra allocation
|
|
463
|
+
assert val.dtype == np.dtype(dtype) # real assert
|
|
464
|
+
if not open_bed._array_properties_are_ok(val, order):
|
|
465
|
+
val = val.copy(order=order)
|
|
466
|
+
|
|
467
|
+
return val
|
|
468
|
+
|
|
469
|
+
def __str__(self) -> str:
|
|
470
|
+
return f"{self.__class__.__name__}('{self.filepath}',...)"
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def fid(self) -> np.ndarray:
|
|
474
|
+
"""
|
|
475
|
+
Family id of each individual (sample).
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
numpy.ndarray
|
|
480
|
+
array of str
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
'0' represents a missing value.
|
|
484
|
+
|
|
485
|
+
If needed, will cause a one-time read of the .fam file.
|
|
486
|
+
|
|
487
|
+
Example
|
|
488
|
+
-------
|
|
489
|
+
|
|
490
|
+
.. doctest::
|
|
491
|
+
|
|
492
|
+
>>> from bed_reader import open_bed, sample_file
|
|
493
|
+
>>>
|
|
494
|
+
>>> file_name = sample_file("small.bed")
|
|
495
|
+
>>> with open_bed(file_name) as bed:
|
|
496
|
+
... print(bed.fid)
|
|
497
|
+
['fid1' 'fid1' 'fid2']
|
|
498
|
+
|
|
499
|
+
"""
|
|
500
|
+
|
|
501
|
+
return self.property_item("fid")
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def iid(self) -> np.ndarray:
|
|
505
|
+
"""
|
|
506
|
+
Individual id of each individual (sample).
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
numpy.ndarray
|
|
511
|
+
array of str
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
If needed, will cause a one-time read of the .fam file.
|
|
515
|
+
|
|
516
|
+
Example
|
|
517
|
+
-------
|
|
518
|
+
|
|
519
|
+
.. doctest::
|
|
520
|
+
|
|
521
|
+
>>> from bed_reader import open_bed, sample_file
|
|
522
|
+
>>>
|
|
523
|
+
>>> file_name = sample_file("small.bed")
|
|
524
|
+
>>> with open_bed(file_name) as bed:
|
|
525
|
+
... print(bed.iid)
|
|
526
|
+
['iid1' 'iid2' 'iid3']
|
|
527
|
+
|
|
528
|
+
"""
|
|
529
|
+
return self.property_item("iid")
|
|
530
|
+
|
|
531
|
+
@property
|
|
532
|
+
def father(self) -> np.ndarray:
|
|
533
|
+
"""
|
|
534
|
+
Father id of each individual (sample).
|
|
535
|
+
|
|
536
|
+
Returns
|
|
537
|
+
-------
|
|
538
|
+
numpy.ndarray
|
|
539
|
+
array of str
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
'0' represents a missing value.
|
|
543
|
+
|
|
544
|
+
If needed, will cause a one-time read of the .fam file.
|
|
545
|
+
|
|
546
|
+
Example
|
|
547
|
+
-------
|
|
548
|
+
|
|
549
|
+
.. doctest::
|
|
550
|
+
|
|
551
|
+
>>> from bed_reader import open_bed, sample_file
|
|
552
|
+
>>>
|
|
553
|
+
>>> file_name = sample_file("small.bed")
|
|
554
|
+
>>> with open_bed(file_name) as bed:
|
|
555
|
+
... print(bed.father)
|
|
556
|
+
['iid23' 'iid23' 'iid22']
|
|
557
|
+
|
|
558
|
+
"""
|
|
559
|
+
return self.property_item("father")
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def mother(self) -> np.ndarray:
|
|
563
|
+
"""
|
|
564
|
+
Mother id of each individual (sample).
|
|
565
|
+
|
|
566
|
+
Returns
|
|
567
|
+
-------
|
|
568
|
+
numpy.ndarray
|
|
569
|
+
array of str
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
'0' represents a missing value.
|
|
573
|
+
|
|
574
|
+
If needed, will cause a one-time read of the .fam file.
|
|
575
|
+
|
|
576
|
+
Example
|
|
577
|
+
-------
|
|
578
|
+
|
|
579
|
+
.. doctest::
|
|
580
|
+
|
|
581
|
+
>>> from bed_reader import open_bed, sample_file
|
|
582
|
+
>>>
|
|
583
|
+
>>> file_name = sample_file("small.bed")
|
|
584
|
+
>>> with open_bed(file_name) as bed:
|
|
585
|
+
... print(bed.mother)
|
|
586
|
+
['iid34' 'iid34' 'iid33']
|
|
587
|
+
|
|
588
|
+
"""
|
|
589
|
+
return self.property_item("mother")
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def sex(self) -> np.ndarray:
|
|
593
|
+
"""
|
|
594
|
+
Sex of each individual (sample).
|
|
595
|
+
|
|
596
|
+
Returns
|
|
597
|
+
-------
|
|
598
|
+
numpy.ndarray
|
|
599
|
+
array of 0, 1, or 2
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
0 is unknown, 1 is male, 2 is female
|
|
603
|
+
|
|
604
|
+
If needed, will cause a one-time read of the .fam file.
|
|
605
|
+
|
|
606
|
+
Example
|
|
607
|
+
-------
|
|
608
|
+
.. doctest::
|
|
609
|
+
|
|
610
|
+
>>> from bed_reader import open_bed, sample_file
|
|
611
|
+
>>>
|
|
612
|
+
>>> file_name = sample_file("small.bed")
|
|
613
|
+
>>> with open_bed(file_name) as bed:
|
|
614
|
+
... print(bed.sex)
|
|
615
|
+
[1 2 0]
|
|
616
|
+
|
|
617
|
+
"""
|
|
618
|
+
return self.property_item("sex")
|
|
619
|
+
|
|
620
|
+
@property
|
|
621
|
+
def pheno(self) -> np.ndarray:
|
|
622
|
+
"""
|
|
623
|
+
A phenotype for each individual (sample)
|
|
624
|
+
(seldom used).
|
|
625
|
+
|
|
626
|
+
Returns
|
|
627
|
+
-------
|
|
628
|
+
numpy.ndarray
|
|
629
|
+
array of str
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
'0' may represent a missing value.
|
|
633
|
+
|
|
634
|
+
If needed, will cause a one-time read of the .fam file.
|
|
635
|
+
|
|
636
|
+
Example
|
|
637
|
+
-------
|
|
638
|
+
|
|
639
|
+
.. doctest::
|
|
640
|
+
|
|
641
|
+
>>> from bed_reader import open_bed, sample_file
|
|
642
|
+
>>>
|
|
643
|
+
>>> file_name = sample_file("small.bed")
|
|
644
|
+
>>> with open_bed(file_name) as bed:
|
|
645
|
+
... print(bed.pheno)
|
|
646
|
+
['red' 'red' 'blue']
|
|
647
|
+
|
|
648
|
+
"""
|
|
649
|
+
return self.property_item("pheno")
|
|
650
|
+
|
|
651
|
+
@property
|
|
652
|
+
def properties(self) -> Mapping[str, np.array]:
|
|
653
|
+
"""
|
|
654
|
+
All the properties returned as a dictionary.
|
|
655
|
+
|
|
656
|
+
Returns
|
|
657
|
+
-------
|
|
658
|
+
dict
|
|
659
|
+
all the properties
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
The keys of the dictionary are the names of the properties, namely:
|
|
663
|
+
|
|
664
|
+
"fid" (family id), "iid" (individual or sample id), "father" (father id),
|
|
665
|
+
"mother" (mother id), "sex", "pheno" (phenotype), "chromosome", "sid"
|
|
666
|
+
(SNP or variant id), "cm_position" (centimorgan position), "bp_position"
|
|
667
|
+
(base-pair position), "allele_1", "allele_2".
|
|
668
|
+
|
|
669
|
+
The values are :class:`numpy.ndarray`.
|
|
670
|
+
|
|
671
|
+
If needed, will cause a one-time read of the .fam and .bim file.
|
|
672
|
+
|
|
673
|
+
Example
|
|
674
|
+
-------
|
|
675
|
+
|
|
676
|
+
.. doctest::
|
|
677
|
+
|
|
678
|
+
>>> from bed_reader import open_bed, sample_file
|
|
679
|
+
>>>
|
|
680
|
+
>>> file_name = sample_file("small.bed")
|
|
681
|
+
>>> with open_bed(file_name) as bed:
|
|
682
|
+
... print(len(bed.properties)) #length of dict
|
|
683
|
+
12
|
|
684
|
+
|
|
685
|
+
"""
|
|
686
|
+
for key in _meta_meta:
|
|
687
|
+
self.property_item(key)
|
|
688
|
+
return self.properties_dict
|
|
689
|
+
|
|
690
|
+
def property_item(self, name: str) -> np.ndarray:
|
|
691
|
+
"""
|
|
692
|
+
Retrieve one property by name.
|
|
693
|
+
|
|
694
|
+
Returns
|
|
695
|
+
-------
|
|
696
|
+
numpy.ndarray
|
|
697
|
+
a property value
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
The name is one of these:
|
|
701
|
+
|
|
702
|
+
"fid" (family id), "iid" (individual or sample id), "father" (father id),
|
|
703
|
+
"mother" (mother id), "sex", "pheno" (phenotype), "chromosome", "sid"
|
|
704
|
+
(SNP or variant id), "cm_position" (centimorgan position), "bp_position"
|
|
705
|
+
(base-pair position), "allele_1", "allele_2".
|
|
706
|
+
|
|
707
|
+
If needed, will cause a one-time read of the .fam or .bim file.
|
|
708
|
+
|
|
709
|
+
Example
|
|
710
|
+
-------
|
|
711
|
+
|
|
712
|
+
.. doctest::
|
|
713
|
+
|
|
714
|
+
>>> from bed_reader import open_bed, sample_file
|
|
715
|
+
>>>
|
|
716
|
+
>>> file_name = sample_file("small.bed")
|
|
717
|
+
>>> with open_bed(file_name) as bed:
|
|
718
|
+
... print(bed.property_item('chromosome'))
|
|
719
|
+
['1' '1' '5' 'Y']
|
|
720
|
+
|
|
721
|
+
"""
|
|
722
|
+
if name not in self.properties_dict:
|
|
723
|
+
mm = _meta_meta[name]
|
|
724
|
+
self._read_fam_or_bim(suffix=mm.suffix)
|
|
725
|
+
return self.properties_dict[name]
|
|
726
|
+
|
|
727
|
+
@property
|
|
728
|
+
def chromosome(self) -> np.ndarray:
|
|
729
|
+
"""
|
|
730
|
+
Chromosome of each SNP (variant)
|
|
731
|
+
|
|
732
|
+
Returns
|
|
733
|
+
-------
|
|
734
|
+
numpy.ndarray
|
|
735
|
+
array of str
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
'0' represents a missing value.
|
|
739
|
+
|
|
740
|
+
If needed, will cause a one-time read of the .bim file.
|
|
741
|
+
|
|
742
|
+
Example
|
|
743
|
+
-------
|
|
744
|
+
|
|
745
|
+
.. doctest::
|
|
746
|
+
|
|
747
|
+
>>> from bed_reader import open_bed, sample_file
|
|
748
|
+
>>>
|
|
749
|
+
>>> file_name = sample_file("small.bed")
|
|
750
|
+
>>> with open_bed(file_name) as bed:
|
|
751
|
+
... print(bed.chromosome)
|
|
752
|
+
['1' '1' '5' 'Y']
|
|
753
|
+
|
|
754
|
+
"""
|
|
755
|
+
return self.property_item("chromosome")
|
|
756
|
+
|
|
757
|
+
@property
|
|
758
|
+
def sid(self) -> np.ndarray:
|
|
759
|
+
"""
|
|
760
|
+
SNP id of each SNP (variant).
|
|
761
|
+
|
|
762
|
+
Returns
|
|
763
|
+
-------
|
|
764
|
+
numpy.ndarray
|
|
765
|
+
array of str
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
If needed, will cause a one-time read of the .bim file.
|
|
769
|
+
|
|
770
|
+
Example
|
|
771
|
+
-------
|
|
772
|
+
|
|
773
|
+
.. doctest::
|
|
774
|
+
|
|
775
|
+
>>> from bed_reader import open_bed, sample_file
|
|
776
|
+
>>>
|
|
777
|
+
>>> file_name = sample_file("small.bed")
|
|
778
|
+
>>> with open_bed(file_name) as bed:
|
|
779
|
+
... print(bed.sid)
|
|
780
|
+
['sid1' 'sid2' 'sid3' 'sid4']
|
|
781
|
+
|
|
782
|
+
"""
|
|
783
|
+
return self.property_item("sid")
|
|
784
|
+
|
|
785
|
+
@property
|
|
786
|
+
def cm_position(self) -> np.ndarray:
|
|
787
|
+
"""
|
|
788
|
+
Centimorgan position of each SNP (variant).
|
|
789
|
+
|
|
790
|
+
Returns
|
|
791
|
+
-------
|
|
792
|
+
numpy.ndarray
|
|
793
|
+
array of float
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
0.0 represents a missing value.
|
|
797
|
+
|
|
798
|
+
If needed, will cause a one-time read of the .bim file.
|
|
799
|
+
|
|
800
|
+
Example
|
|
801
|
+
-------
|
|
802
|
+
|
|
803
|
+
.. doctest::
|
|
804
|
+
|
|
805
|
+
>>> from bed_reader import open_bed, sample_file
|
|
806
|
+
>>>
|
|
807
|
+
>>> file_name = sample_file("small.bed")
|
|
808
|
+
>>> with open_bed(file_name) as bed:
|
|
809
|
+
... print(bed.cm_position)
|
|
810
|
+
[ 100.4 2000.5 4000.7 7000.9]
|
|
811
|
+
|
|
812
|
+
"""
|
|
813
|
+
return self.property_item("cm_position")
|
|
814
|
+
|
|
815
|
+
@property
|
|
816
|
+
def bp_position(self) -> np.ndarray:
|
|
817
|
+
"""
|
|
818
|
+
Base-pair position of each SNP (variant).
|
|
819
|
+
|
|
820
|
+
Returns
|
|
821
|
+
-------
|
|
822
|
+
numpy.ndarray
|
|
823
|
+
array of int
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
0 represents a missing value.
|
|
827
|
+
|
|
828
|
+
If needed, will cause a one-time read of the .bim file.
|
|
829
|
+
|
|
830
|
+
Example
|
|
831
|
+
-------
|
|
832
|
+
|
|
833
|
+
.. doctest::
|
|
834
|
+
|
|
835
|
+
>>> from bed_reader import open_bed, sample_file
|
|
836
|
+
>>>
|
|
837
|
+
>>> file_name = sample_file("small.bed")
|
|
838
|
+
>>> with open_bed(file_name) as bed:
|
|
839
|
+
... print(bed.bp_position)
|
|
840
|
+
[ 1 100 1000 1004]
|
|
841
|
+
|
|
842
|
+
"""
|
|
843
|
+
return self.property_item("bp_position")
|
|
844
|
+
|
|
845
|
+
@property
|
|
846
|
+
def allele_1(self) -> np.ndarray:
|
|
847
|
+
"""
|
|
848
|
+
First allele of each SNP (variant).
|
|
849
|
+
|
|
850
|
+
Returns
|
|
851
|
+
-------
|
|
852
|
+
numpy.ndarray
|
|
853
|
+
array of str
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
If needed, will cause a one-time read of the .bim file.
|
|
857
|
+
|
|
858
|
+
Example
|
|
859
|
+
-------
|
|
860
|
+
|
|
861
|
+
.. doctest::
|
|
862
|
+
|
|
863
|
+
>>> from bed_reader import open_bed, sample_file
|
|
864
|
+
>>>
|
|
865
|
+
>>> file_name = sample_file("small.bed")
|
|
866
|
+
>>> with open_bed(file_name) as bed:
|
|
867
|
+
... print(bed.allele_1)
|
|
868
|
+
['A' 'T' 'A' 'T']
|
|
869
|
+
|
|
870
|
+
"""
|
|
871
|
+
return self.property_item("allele_1")
|
|
872
|
+
|
|
873
|
+
@property
|
|
874
|
+
def allele_2(self) -> np.ndarray:
|
|
875
|
+
"""
|
|
876
|
+
Second allele of each SNP (variant),
|
|
877
|
+
|
|
878
|
+
Returns
|
|
879
|
+
-------
|
|
880
|
+
numpy.ndarray
|
|
881
|
+
array of str
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
If needed, will cause a one-time read of the .bim file.
|
|
885
|
+
|
|
886
|
+
Example
|
|
887
|
+
-------
|
|
888
|
+
|
|
889
|
+
.. doctest::
|
|
890
|
+
|
|
891
|
+
>>> from bed_reader import open_bed, sample_file
|
|
892
|
+
>>>
|
|
893
|
+
>>> file_name = sample_file("small.bed")
|
|
894
|
+
>>> with open_bed(file_name) as bed:
|
|
895
|
+
... print(bed.allele_2)
|
|
896
|
+
['A' 'C' 'C' 'G']
|
|
897
|
+
|
|
898
|
+
"""
|
|
899
|
+
return self.property_item("allele_2")
|
|
900
|
+
|
|
901
|
+
@property
|
|
902
|
+
def iid_count(self) -> np.ndarray:
|
|
903
|
+
"""
|
|
904
|
+
Number of individuals (samples).
|
|
905
|
+
|
|
906
|
+
Returns
|
|
907
|
+
-------
|
|
908
|
+
int
|
|
909
|
+
number of individuals
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
If needed, will cause a fast line-count of the .fam file.
|
|
913
|
+
|
|
914
|
+
Example
|
|
915
|
+
-------
|
|
916
|
+
|
|
917
|
+
.. doctest::
|
|
918
|
+
|
|
919
|
+
>>> from bed_reader import open_bed, sample_file
|
|
920
|
+
>>>
|
|
921
|
+
>>> file_name = sample_file("small.bed")
|
|
922
|
+
>>> with open_bed(file_name) as bed:
|
|
923
|
+
... print(bed.iid_count)
|
|
924
|
+
3
|
|
925
|
+
|
|
926
|
+
"""
|
|
927
|
+
return self._count("fam")
|
|
928
|
+
|
|
929
|
+
@property
|
|
930
|
+
def sid_count(self) -> np.ndarray:
|
|
931
|
+
"""
|
|
932
|
+
Number of SNPs (variants).
|
|
933
|
+
|
|
934
|
+
Returns
|
|
935
|
+
-------
|
|
936
|
+
int
|
|
937
|
+
number of SNPs
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
If needed, will cause a fast line-count of the .bim file.
|
|
941
|
+
|
|
942
|
+
Example
|
|
943
|
+
-------
|
|
944
|
+
|
|
945
|
+
.. doctest::
|
|
946
|
+
|
|
947
|
+
>>> from bed_reader import open_bed, sample_file
|
|
948
|
+
>>>
|
|
949
|
+
>>> file_name = sample_file("small.bed")
|
|
950
|
+
>>> with open_bed(file_name) as bed:
|
|
951
|
+
... print(bed.sid_count)
|
|
952
|
+
4
|
|
953
|
+
|
|
954
|
+
"""
|
|
955
|
+
return self._count("bim")
|
|
956
|
+
|
|
957
|
+
def _property_filepath(self, suffix):
|
|
958
|
+
if suffix == "fam":
|
|
959
|
+
return self._fam_filepath
|
|
960
|
+
else:
|
|
961
|
+
assert suffix == "bim" # real assert
|
|
962
|
+
return self._bim_filepath
|
|
963
|
+
|
|
964
|
+
def _count(self, suffix):
|
|
965
|
+
count = self._counts[suffix]
|
|
966
|
+
if count is None:
|
|
967
|
+
count = _rawincount(self._property_filepath(suffix))
|
|
968
|
+
self._counts[suffix] = count
|
|
969
|
+
return count
|
|
970
|
+
|
|
971
|
+
@staticmethod
|
|
972
|
+
def _check_file(filepointer):
|
|
973
|
+
mode = filepointer.read(2)
|
|
974
|
+
if mode != b"l\x1b":
|
|
975
|
+
raise ValueError("Not a valid .bed file")
|
|
976
|
+
mode = filepointer.read(1) # \x01 = SNP major \x00 = individual major
|
|
977
|
+
if mode != b"\x01":
|
|
978
|
+
raise ValueError("only SNP-major is implemented")
|
|
979
|
+
|
|
980
|
+
def __del__(self):
|
|
981
|
+
self.__exit__()
|
|
982
|
+
|
|
983
|
+
def __enter__(self):
|
|
984
|
+
return self
|
|
985
|
+
|
|
986
|
+
def __exit__(self, *_):
|
|
987
|
+
pass
|
|
988
|
+
|
|
989
|
+
@staticmethod
|
|
990
|
+
def _array_properties_are_ok(val, order):
|
|
991
|
+
if order == "F":
|
|
992
|
+
return val.flags["F_CONTIGUOUS"]
|
|
993
|
+
else:
|
|
994
|
+
assert order == "C" # real assert
|
|
995
|
+
return val.flags["C_CONTIGUOUS"]
|
|
996
|
+
|
|
997
|
+
@property
|
|
998
|
+
def shape(self):
|
|
999
|
+
"""
|
|
1000
|
+
Number of individuals (samples) and SNPs (variants).
|
|
1001
|
+
|
|
1002
|
+
Returns
|
|
1003
|
+
-------
|
|
1004
|
+
(int, int)
|
|
1005
|
+
number of individuals, number of SNPs
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
If needed, will cause a fast line-count of the .fam and .bim files.
|
|
1009
|
+
|
|
1010
|
+
Example
|
|
1011
|
+
-------
|
|
1012
|
+
|
|
1013
|
+
.. doctest::
|
|
1014
|
+
|
|
1015
|
+
>>> from bed_reader import open_bed, sample_file
|
|
1016
|
+
>>>
|
|
1017
|
+
>>> file_name = sample_file("small.bed")
|
|
1018
|
+
>>> with open_bed(file_name) as bed:
|
|
1019
|
+
... print(bed.shape)
|
|
1020
|
+
(3, 4)
|
|
1021
|
+
|
|
1022
|
+
"""
|
|
1023
|
+
return (len(self.iid), len(self.sid))
|
|
1024
|
+
|
|
1025
|
+
@staticmethod
|
|
1026
|
+
def _split_index(index):
|
|
1027
|
+
if not isinstance(index, tuple):
|
|
1028
|
+
index = (None, index)
|
|
1029
|
+
iid_index = open_bed._fix_up_index(index[0])
|
|
1030
|
+
sid_index = open_bed._fix_up_index(index[1])
|
|
1031
|
+
return iid_index, sid_index
|
|
1032
|
+
|
|
1033
|
+
@staticmethod
|
|
1034
|
+
def _fix_up_index(index):
|
|
1035
|
+
if index is None: # make a shortcut for None
|
|
1036
|
+
return slice(None)
|
|
1037
|
+
try: # If index is an int, return it in an array
|
|
1038
|
+
index = index.__index__() # (see
|
|
1039
|
+
# https://stackoverflow.com/questions/3501382/checking-whether-a-variable-is-an-integer-or-not)
|
|
1040
|
+
return [index]
|
|
1041
|
+
except Exception:
|
|
1042
|
+
pass
|
|
1043
|
+
return index
|
|
1044
|
+
|
|
1045
|
+
@staticmethod
|
|
1046
|
+
def _write_fam_or_bim(base_filepath, properties, suffix, property_filepath):
|
|
1047
|
+
assert suffix in {"fam", "bim"}, "real assert"
|
|
1048
|
+
|
|
1049
|
+
filepath = (
|
|
1050
|
+
Path(property_filepath)
|
|
1051
|
+
if property_filepath is not None
|
|
1052
|
+
else base_filepath.parent / (base_filepath.stem + "." + suffix)
|
|
1053
|
+
)
|
|
1054
|
+
|
|
1055
|
+
fam_bim_list = []
|
|
1056
|
+
for key, mm in _meta_meta.items():
|
|
1057
|
+
if mm.suffix == suffix:
|
|
1058
|
+
assert len(fam_bim_list) == mm.column, "real assert"
|
|
1059
|
+
fam_bim_list.append(properties[key])
|
|
1060
|
+
|
|
1061
|
+
sep = " " if suffix == "fam" else "\t"
|
|
1062
|
+
|
|
1063
|
+
with open(filepath, "w") as filepointer:
|
|
1064
|
+
for index in range(len(fam_bim_list[0])):
|
|
1065
|
+
filepointer.write(
|
|
1066
|
+
sep.join(str(seq[index]) for seq in fam_bim_list) + "\n"
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
@staticmethod
|
|
1070
|
+
def _fix_up_properties_array(input, dtype, missing_value, key):
|
|
1071
|
+
if input is None:
|
|
1072
|
+
return None
|
|
1073
|
+
if len(input) == 0:
|
|
1074
|
+
return np.zeros([0], dtype=dtype)
|
|
1075
|
+
|
|
1076
|
+
if not isinstance(input, np.ndarray):
|
|
1077
|
+
return open_bed._fix_up_properties_array(
|
|
1078
|
+
np.array(input), dtype, missing_value, key
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
if len(input.shape) != 1:
|
|
1082
|
+
raise ValueError(f"{key} should be one dimensional")
|
|
1083
|
+
|
|
1084
|
+
if not np.issubdtype(input.dtype, dtype):
|
|
1085
|
+
output = np.array(input, dtype=dtype)
|
|
1086
|
+
else:
|
|
1087
|
+
output = input
|
|
1088
|
+
|
|
1089
|
+
# Change NaN in input to correct missing value
|
|
1090
|
+
if np.issubdtype(input.dtype, np.floating):
|
|
1091
|
+
output[input != input] = missing_value
|
|
1092
|
+
|
|
1093
|
+
return output
|
|
1094
|
+
|
|
1095
|
+
@staticmethod
|
|
1096
|
+
def _fix_up_properties(properties, iid_count, sid_count, use_fill_sequence):
|
|
1097
|
+
for key in properties:
|
|
1098
|
+
if key not in _meta_meta:
|
|
1099
|
+
raise KeyError(f"properties key '{key}' not known")
|
|
1100
|
+
|
|
1101
|
+
count_dict = {"fam": iid_count, "bim": sid_count}
|
|
1102
|
+
properties_dict = {}
|
|
1103
|
+
for key, mm in _meta_meta.items():
|
|
1104
|
+
count = count_dict[mm.suffix]
|
|
1105
|
+
|
|
1106
|
+
if key not in properties or (use_fill_sequence and properties[key] is None):
|
|
1107
|
+
if use_fill_sequence:
|
|
1108
|
+
output = mm.fill_sequence(key, count, mm.missing_value, mm.dtype)
|
|
1109
|
+
else:
|
|
1110
|
+
continue # Test coverage reaches this, but doesn't report it.
|
|
1111
|
+
else:
|
|
1112
|
+
output = open_bed._fix_up_properties_array(
|
|
1113
|
+
properties[key], mm.dtype, mm.missing_value, key
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
if output is not None:
|
|
1117
|
+
if count is None:
|
|
1118
|
+
count_dict[mm.suffix] = len(output)
|
|
1119
|
+
else:
|
|
1120
|
+
if count != len(output):
|
|
1121
|
+
raise ValueError(
|
|
1122
|
+
f"The length of override {key}, {len(output)}, should not "
|
|
1123
|
+
+ "be different from the current "
|
|
1124
|
+
+ f"{_count_name[mm.suffix]}, {count}"
|
|
1125
|
+
)
|
|
1126
|
+
properties_dict[key] = output
|
|
1127
|
+
return properties_dict, count_dict
|
|
1128
|
+
|
|
1129
|
+
def _read_fam_or_bim(self, suffix):
|
|
1130
|
+
property_filepath = self._property_filepath(suffix)
|
|
1131
|
+
|
|
1132
|
+
logging.info("Loading {0} file {1}".format(suffix, property_filepath))
|
|
1133
|
+
|
|
1134
|
+
count = self._counts[suffix]
|
|
1135
|
+
|
|
1136
|
+
delimiter = _delimiters[suffix]
|
|
1137
|
+
if delimiter in {r"\s+"}:
|
|
1138
|
+
delimiter = None
|
|
1139
|
+
delim_whitespace = True
|
|
1140
|
+
else:
|
|
1141
|
+
delim_whitespace = False
|
|
1142
|
+
|
|
1143
|
+
usecolsdict = {}
|
|
1144
|
+
dtype_dict = {}
|
|
1145
|
+
for key, mm in _meta_meta.items():
|
|
1146
|
+
if mm.suffix is suffix and key not in self.properties_dict:
|
|
1147
|
+
usecolsdict[key] = mm.column
|
|
1148
|
+
dtype_dict[mm.column] = mm.dtype
|
|
1149
|
+
assert list(usecolsdict.values()) == sorted(usecolsdict.values()) # real assert
|
|
1150
|
+
assert len(usecolsdict) > 0 # real assert
|
|
1151
|
+
|
|
1152
|
+
if os.path.getsize(property_filepath) == 0:
|
|
1153
|
+
fields = []
|
|
1154
|
+
else:
|
|
1155
|
+
fields = pd.read_csv(
|
|
1156
|
+
property_filepath,
|
|
1157
|
+
delimiter=delimiter,
|
|
1158
|
+
delim_whitespace=delim_whitespace,
|
|
1159
|
+
header=None,
|
|
1160
|
+
index_col=False,
|
|
1161
|
+
comment=None,
|
|
1162
|
+
dtype=dtype_dict,
|
|
1163
|
+
usecols=usecolsdict.values(),
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1166
|
+
if count is None:
|
|
1167
|
+
self._counts[suffix] = len(fields)
|
|
1168
|
+
else:
|
|
1169
|
+
if count != len(fields):
|
|
1170
|
+
raise ValueError(
|
|
1171
|
+
f"The number of lines in the *.{suffix} file, {len(fields)}, "
|
|
1172
|
+
+ "should not be different from the current "
|
|
1173
|
+
+ "f{_count_name[suffix]}, {count}"
|
|
1174
|
+
)
|
|
1175
|
+
for key in usecolsdict.keys():
|
|
1176
|
+
mm = _meta_meta[key]
|
|
1177
|
+
if len(fields) == 0:
|
|
1178
|
+
output = np.array([], dtype=mm.dtype)
|
|
1179
|
+
else:
|
|
1180
|
+
output = fields[mm.column].values
|
|
1181
|
+
if not np.issubdtype(output.dtype, mm.dtype):
|
|
1182
|
+
output = np.array(output, dtype=mm.dtype)
|
|
1183
|
+
self.properties_dict[key] = output
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
if __name__ == "__main__":
|
|
1187
|
+
logging.basicConfig(level=logging.INFO)
|
|
1188
|
+
|
|
1189
|
+
# if True:
|
|
1190
|
+
# from bed_reader import sample_file
|
|
1191
|
+
|
|
1192
|
+
# file_name = sample_file("small.bed")
|
|
1193
|
+
# with open_bed(file_name) as bed:
|
|
1194
|
+
# print(bed.iid)
|
|
1195
|
+
# print(bed.sid)
|
|
1196
|
+
# print(bed.read())
|
|
1197
|
+
|
|
1198
|
+
# if False:
|
|
1199
|
+
# import numpy as np
|
|
1200
|
+
# from bed_reader._open_bed import open_bed
|
|
1201
|
+
|
|
1202
|
+
# # Can get file from
|
|
1203
|
+
# https://www.dropbox.com/sh/xluk9opjiaobteg/AABgEggLk0ZoO0KQq0I4CaTJa?dl=0
|
|
1204
|
+
# bigfile = r"M:\deldir\genbgen\2\merged_487400x220000.1.bed"
|
|
1205
|
+
# # bigfile = '/mnt/m/deldir/genbgen/2/merged_487400x220000.1.bed'
|
|
1206
|
+
# with open_bed(bigfile, num_threads=20) as bed:
|
|
1207
|
+
# sid_batch = 22 * 1000
|
|
1208
|
+
# for sid_start in range(0, 10 * sid_batch, sid_batch):
|
|
1209
|
+
# slicer = np.s_[:10000, sid_start : sid_start + sid_batch]
|
|
1210
|
+
# print(slicer)
|
|
1211
|
+
# val = bed.read(slicer)
|
|
1212
|
+
# print(val.shape)
|
|
1213
|
+
|
|
1214
|
+
# if False:
|
|
1215
|
+
# file = r"D:\OneDrive\programs\sgkit-plink\bed_reader\tests\data
|
|
1216
|
+
# /plink_sim_10s_100v_10pmiss.bed"
|
|
1217
|
+
# with open_bed(file) as bed:
|
|
1218
|
+
# print(bed.iid)
|
|
1219
|
+
# print(bed.shape)
|
|
1220
|
+
# val = bed.read()
|
|
1221
|
+
# print(val)
|
|
1222
|
+
|
|
1223
|
+
# if False:
|
|
1224
|
+
|
|
1225
|
+
# # bed_file = example_file('doc/ipynb/all.*','*.bed')
|
|
1226
|
+
# bed_file = r"F:\backup\carlk4d\data\carlk\cachebio\genetics\onemil\
|
|
1227
|
+
# id1000000.sid_1000000.seed0.byiid\iid990000to1000000.bed"
|
|
1228
|
+
# bed = Bed(bed_file, count_A1=False)
|
|
1229
|
+
# snpdata1 = bed[:, :1000].read()
|
|
1230
|
+
# snpdata2 = bed[:, :1000].read(dtype="int8", _require_float32_64=False)
|
|
1231
|
+
# print(snpdata2)
|
|
1232
|
+
# snpdata3 = bed[:, :1000].read(
|
|
1233
|
+
# dtype="int8", order="C", _require_float32_64=False
|
|
1234
|
+
# )
|
|
1235
|
+
# print(snpdata3)
|
|
1236
|
+
# snpdata3.val = snpdata3.val.astype("float32")
|
|
1237
|
+
# snpdata3.val.dtype
|
|
1238
|
+
|
|
1239
|
+
# if False:
|
|
1240
|
+
# from bed_reader import Bed, SnpGen
|
|
1241
|
+
|
|
1242
|
+
# iid_count = 487409
|
|
1243
|
+
# sid_count = 5000
|
|
1244
|
+
# sid_count_max = 5765294
|
|
1245
|
+
# sid_batch_size = 50
|
|
1246
|
+
|
|
1247
|
+
# sid_batch_count = -(sid_count // -sid_batch_size)
|
|
1248
|
+
# sid_batch_count_max = -(sid_count_max // -sid_batch_size)
|
|
1249
|
+
# snpgen = SnpGen(seed=234, iid_count=iid_count, sid_count=sid_count_max)
|
|
1250
|
+
|
|
1251
|
+
# for batch_index in range(sid_batch_count):
|
|
1252
|
+
# sid_index_start = batch_index * sid_batch_size
|
|
1253
|
+
# sid_index_end = (batch_index + 1) * sid_batch_size # what about rounding
|
|
1254
|
+
# filename = r"d:\deldir\rand\fakeukC{0}x{1}-{2}.bed".format(
|
|
1255
|
+
# iid_count, sid_index_start, sid_index_end
|
|
1256
|
+
# )
|
|
1257
|
+
# if not os.path.exists(filename):
|
|
1258
|
+
# Bed.write(
|
|
1259
|
+
# filename + ".temp", snpgen[:, sid_index_start:sid_index_end].read()
|
|
1260
|
+
# )
|
|
1261
|
+
# os.rename(filename + ".temp", filename)
|
|
1262
|
+
|
|
1263
|
+
# if False:
|
|
1264
|
+
# from bed_reader import Pheno, Bed
|
|
1265
|
+
|
|
1266
|
+
# filename = r"m:\deldir\New folder (4)\all_chr.maf0.001.N300.bed"
|
|
1267
|
+
# iid_count = 300
|
|
1268
|
+
# iid = [["0", "iid_{0}".format(iid_index)] for iid_index in range(iid_count)]
|
|
1269
|
+
# bed = Bed(filename, iid=iid, count_A1=False)
|
|
1270
|
+
# print(bed.iid_count)
|
|
1271
|
+
|
|
1272
|
+
# if False:
|
|
1273
|
+
# from pysnptools.util import example_file
|
|
1274
|
+
|
|
1275
|
+
# pheno_fn = example_file("pysnptools/examples/toydata.phe")
|
|
1276
|
+
|
|
1277
|
+
# if False:
|
|
1278
|
+
# from bed_reader import Pheno, Bed
|
|
1279
|
+
|
|
1280
|
+
# print(os.getcwd())
|
|
1281
|
+
# # Read data from Pheno format
|
|
1282
|
+
# snpdata = Pheno("../examples/toydata.phe").read()
|
|
1283
|
+
# # pstutil.create_directory_if_necessary("tempdir/toydata.5chrom.bed")
|
|
1284
|
+
# Bed.write(
|
|
1285
|
+
# "tempdir/toydata.5chrom.bed", snpdata, count_A1=False
|
|
1286
|
+
# ) # Write data in Bed format
|
|
1287
|
+
|
|
1288
|
+
import pytest
|
|
1289
|
+
|
|
1290
|
+
pytest.main(["--doctest-modules", __file__])
|