gtfreader 0.1.1__tar.gz → 0.2.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.
- gtfreader-0.2.0/PKG-INFO +35 -0
- gtfreader-0.2.0/README.md +19 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/gtfreader/__init__.py +0 -4
- gtfreader-0.2.0/gtfreader/_parser.pyx +197 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/gtfreader/readers.py +15 -111
- gtfreader-0.2.0/gtfreader.egg-info/PKG-INFO +35 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/pyproject.toml +5 -3
- {gtfreader-0.1.1 → gtfreader-0.2.0}/tests/test_parser.py +8 -2
- {gtfreader-0.1.1 → gtfreader-0.2.0}/tests/test_readers.py +23 -54
- gtfreader-0.1.1/PKG-INFO +0 -66
- gtfreader-0.1.1/README.md +0 -52
- gtfreader-0.1.1/gtfreader/_parser.pyx +0 -352
- gtfreader-0.1.1/gtfreader.egg-info/PKG-INFO +0 -66
- {gtfreader-0.1.1 → gtfreader-0.2.0}/MANIFEST.in +0 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/gtfreader.egg-info/SOURCES.txt +0 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/gtfreader.egg-info/dependency_links.txt +0 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/gtfreader.egg-info/requires.txt +0 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/gtfreader.egg-info/top_level.txt +0 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/setup.cfg +0 -0
- {gtfreader-0.1.1 → gtfreader-0.2.0}/setup.py +0 -0
gtfreader-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gtfreader
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Fast Cython-backed parsing for GTF attribute columns.
|
|
5
|
+
Author: Endre Bakken Stovner
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Cython
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: pandas>=2.0
|
|
16
|
+
|
|
17
|
+
# gtfreader
|
|
18
|
+
|
|
19
|
+
Fast GTF reading into pandas DataFrames.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python -m pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from gtfreader import read_gtf
|
|
31
|
+
|
|
32
|
+
df = read_gtf("annotation.gtf")
|
|
33
|
+
print(df.columns)
|
|
34
|
+
print(df.head())
|
|
35
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# gtfreader
|
|
2
|
+
|
|
3
|
+
Fast GTF reading into pandas DataFrames.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python -m pip install -e .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Example
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from gtfreader import read_gtf
|
|
15
|
+
|
|
16
|
+
df = read_gtf("annotation.gtf")
|
|
17
|
+
print(df.columns)
|
|
18
|
+
print(df.head())
|
|
19
|
+
```
|
|
@@ -14,8 +14,6 @@ from .readers import (
|
|
|
14
14
|
read_gtf_full,
|
|
15
15
|
read_gtf_full_python,
|
|
16
16
|
read_gtf_python,
|
|
17
|
-
read_gtf_restricted,
|
|
18
|
-
read_gtf_restricted_python,
|
|
19
17
|
to_rows,
|
|
20
18
|
to_rows_keep_duplicates,
|
|
21
19
|
)
|
|
@@ -28,8 +26,6 @@ __all__ = [
|
|
|
28
26
|
"read_gtf_full",
|
|
29
27
|
"read_gtf_full_python",
|
|
30
28
|
"read_gtf_python",
|
|
31
|
-
"read_gtf_restricted",
|
|
32
|
-
"read_gtf_restricted_python",
|
|
33
29
|
"to_rows",
|
|
34
30
|
"to_rows_keep_duplicates",
|
|
35
31
|
]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# cython: language_level=3
|
|
2
|
+
# cython: boundscheck=False
|
|
3
|
+
# cython: wraparound=False
|
|
4
|
+
# cython: initializedcheck=False
|
|
5
|
+
# cython: infer_types=True
|
|
6
|
+
# cython: nonecheck=False
|
|
7
|
+
|
|
8
|
+
cdef inline int _key_code(str s, Py_ssize_t a, Py_ssize_t b):
|
|
9
|
+
cdef Py_ssize_t n
|
|
10
|
+
n = b - a
|
|
11
|
+
|
|
12
|
+
if n == 7:
|
|
13
|
+
if (
|
|
14
|
+
s[a] == 'g' and s[a + 1] == 'e' and s[a + 2] == 'n' and
|
|
15
|
+
s[a + 3] == 'e' and s[a + 4] == '_' and s[a + 5] == 'i' and
|
|
16
|
+
s[a + 6] == 'd'
|
|
17
|
+
):
|
|
18
|
+
return 1
|
|
19
|
+
if (
|
|
20
|
+
s[a] == 'e' and s[a + 1] == 'x' and s[a + 2] == 'o' and
|
|
21
|
+
s[a + 3] == 'n' and s[a + 4] == '_' and s[a + 5] == 'i' and
|
|
22
|
+
s[a + 6] == 'd'
|
|
23
|
+
):
|
|
24
|
+
return 6
|
|
25
|
+
|
|
26
|
+
elif n == 9:
|
|
27
|
+
if (
|
|
28
|
+
s[a] == 'g' and s[a + 1] == 'e' and s[a + 2] == 'n' and
|
|
29
|
+
s[a + 3] == 'e' and s[a + 4] == '_' and s[a + 5] == 'n' and
|
|
30
|
+
s[a + 6] == 'a' and s[a + 7] == 'm' and s[a + 8] == 'e'
|
|
31
|
+
):
|
|
32
|
+
return 3
|
|
33
|
+
|
|
34
|
+
elif n == 11:
|
|
35
|
+
if (
|
|
36
|
+
s[a] == 'e' and s[a + 1] == 'x' and s[a + 2] == 'o' and
|
|
37
|
+
s[a + 3] == 'n' and s[a + 4] == '_' and s[a + 5] == 'n' and
|
|
38
|
+
s[a + 6] == 'u' and s[a + 7] == 'm' and s[a + 8] == 'b' and
|
|
39
|
+
s[a + 9] == 'e' and s[a + 10] == 'r'
|
|
40
|
+
):
|
|
41
|
+
return 5
|
|
42
|
+
|
|
43
|
+
elif n == 13:
|
|
44
|
+
if (
|
|
45
|
+
s[a] == 't' and s[a + 1] == 'r' and s[a + 2] == 'a' and
|
|
46
|
+
s[a + 3] == 'n' and s[a + 4] == 's' and s[a + 5] == 'c' and
|
|
47
|
+
s[a + 6] == 'r' and s[a + 7] == 'i' and s[a + 8] == 'p' and
|
|
48
|
+
s[a + 9] == 't' and s[a + 10] == '_' and s[a + 11] == 'i' and
|
|
49
|
+
s[a + 12] == 'd'
|
|
50
|
+
):
|
|
51
|
+
return 2
|
|
52
|
+
|
|
53
|
+
elif n == 15:
|
|
54
|
+
if (
|
|
55
|
+
s[a] == 't' and s[a + 1] == 'r' and s[a + 2] == 'a' and
|
|
56
|
+
s[a + 3] == 'n' and s[a + 4] == 's' and s[a + 5] == 'c' and
|
|
57
|
+
s[a + 6] == 'r' and s[a + 7] == 'i' and s[a + 8] == 'p' and
|
|
58
|
+
s[a + 9] == 't' and s[a + 10] == '_' and s[a + 11] == 'n' and
|
|
59
|
+
s[a + 12] == 'a' and s[a + 13] == 'm' and s[a + 14] == 'e'
|
|
60
|
+
):
|
|
61
|
+
return 4
|
|
62
|
+
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
cdef inline str _cached_str(dict cache, str value):
|
|
67
|
+
cdef object obj
|
|
68
|
+
obj = cache.get(value)
|
|
69
|
+
if obj is None:
|
|
70
|
+
cache[value] = value
|
|
71
|
+
return value
|
|
72
|
+
return <str> obj
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def parse_chunk_columns(lines):
|
|
76
|
+
cdef Py_ssize_t n, row_i
|
|
77
|
+
cdef Py_ssize_t pos, m, key_start, key_end, val_start, val_end
|
|
78
|
+
cdef dict columns
|
|
79
|
+
cdef dict gene_name_cache
|
|
80
|
+
|
|
81
|
+
cdef str s, key, value
|
|
82
|
+
cdef list col
|
|
83
|
+
cdef int code
|
|
84
|
+
|
|
85
|
+
cdef object obj_col
|
|
86
|
+
|
|
87
|
+
cdef list gene_id_col
|
|
88
|
+
cdef list transcript_id_col
|
|
89
|
+
cdef list gene_name_col
|
|
90
|
+
cdef list transcript_name_col
|
|
91
|
+
cdef list exon_number_col
|
|
92
|
+
cdef list exon_id_col
|
|
93
|
+
|
|
94
|
+
n = len(lines)
|
|
95
|
+
columns = {}
|
|
96
|
+
gene_name_cache = {}
|
|
97
|
+
|
|
98
|
+
gene_id_col = None
|
|
99
|
+
transcript_id_col = None
|
|
100
|
+
gene_name_col = None
|
|
101
|
+
transcript_name_col = None
|
|
102
|
+
exon_number_col = None
|
|
103
|
+
exon_id_col = None
|
|
104
|
+
|
|
105
|
+
for row_i in range(n):
|
|
106
|
+
s = <str> lines[row_i]
|
|
107
|
+
m = len(s)
|
|
108
|
+
pos = 0
|
|
109
|
+
|
|
110
|
+
while pos < m:
|
|
111
|
+
while pos < m and (s[pos] == ' ' or s[pos] == '\t' or s[pos] == ';'):
|
|
112
|
+
pos += 1
|
|
113
|
+
if pos >= m:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
key_start = pos
|
|
117
|
+
|
|
118
|
+
while pos < m and s[pos] != ' ' and s[pos] != '\t':
|
|
119
|
+
pos += 1
|
|
120
|
+
key_end = pos
|
|
121
|
+
|
|
122
|
+
while pos < m and (s[pos] == ' ' or s[pos] == '\t'):
|
|
123
|
+
pos += 1
|
|
124
|
+
if pos >= m:
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
if s[pos] == '"':
|
|
128
|
+
val_start = pos + 1
|
|
129
|
+
pos += 1
|
|
130
|
+
|
|
131
|
+
while pos < m and s[pos] != '"':
|
|
132
|
+
pos += 1
|
|
133
|
+
if pos >= m:
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
val_end = pos
|
|
137
|
+
else:
|
|
138
|
+
val_start = pos
|
|
139
|
+
while pos < m and s[pos] != ';':
|
|
140
|
+
pos += 1
|
|
141
|
+
val_end = pos
|
|
142
|
+
while val_end > val_start and (s[val_end - 1] == ' ' or s[val_end - 1] == '\t'):
|
|
143
|
+
val_end -= 1
|
|
144
|
+
|
|
145
|
+
value = s[val_start:val_end]
|
|
146
|
+
|
|
147
|
+
code = _key_code(s, key_start, key_end)
|
|
148
|
+
|
|
149
|
+
if code == 1:
|
|
150
|
+
if gene_id_col is None:
|
|
151
|
+
gene_id_col = [None] * n
|
|
152
|
+
columns["gene_id"] = gene_id_col
|
|
153
|
+
gene_id_col[row_i] = value
|
|
154
|
+
|
|
155
|
+
elif code == 2:
|
|
156
|
+
if transcript_id_col is None:
|
|
157
|
+
transcript_id_col = [None] * n
|
|
158
|
+
columns["transcript_id"] = transcript_id_col
|
|
159
|
+
transcript_id_col[row_i] = value
|
|
160
|
+
|
|
161
|
+
elif code == 3:
|
|
162
|
+
if gene_name_col is None:
|
|
163
|
+
gene_name_col = [None] * n
|
|
164
|
+
columns["gene_name"] = gene_name_col
|
|
165
|
+
gene_name_col[row_i] = _cached_str(gene_name_cache, value)
|
|
166
|
+
|
|
167
|
+
elif code == 4:
|
|
168
|
+
if transcript_name_col is None:
|
|
169
|
+
transcript_name_col = [None] * n
|
|
170
|
+
columns["transcript_name"] = transcript_name_col
|
|
171
|
+
transcript_name_col[row_i] = value
|
|
172
|
+
|
|
173
|
+
elif code == 5:
|
|
174
|
+
if exon_number_col is None:
|
|
175
|
+
exon_number_col = [None] * n
|
|
176
|
+
columns["exon_number"] = exon_number_col
|
|
177
|
+
exon_number_col[row_i] = value
|
|
178
|
+
|
|
179
|
+
elif code == 6:
|
|
180
|
+
if exon_id_col is None:
|
|
181
|
+
exon_id_col = [None] * n
|
|
182
|
+
columns["exon_id"] = exon_id_col
|
|
183
|
+
exon_id_col[row_i] = value
|
|
184
|
+
|
|
185
|
+
else:
|
|
186
|
+
key = s[key_start:key_end]
|
|
187
|
+
obj_col = columns.get(key)
|
|
188
|
+
if obj_col is None:
|
|
189
|
+
col = [None] * n
|
|
190
|
+
columns[key] = col
|
|
191
|
+
else:
|
|
192
|
+
col = <list> obj_col
|
|
193
|
+
col[row_i] = value
|
|
194
|
+
|
|
195
|
+
pos += 1
|
|
196
|
+
|
|
197
|
+
return columns
|
|
@@ -23,7 +23,6 @@ GTF_DTYPES = {
|
|
|
23
23
|
"Frame": "category",
|
|
24
24
|
}
|
|
25
25
|
GTF_NAMES = ["Chromosome", "Source", "Feature", "Start", "End", "Score", "Strand", "Frame", "Attribute"]
|
|
26
|
-
RESTRICTED_ATTRIBUTE_COLUMNS = ["gene_id", "transcript_id", "exon_number", "exon_id"]
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
def find_first_data_line_index(file_path: str | Path) -> int:
|
|
@@ -162,6 +161,7 @@ def _open_gtf_reader(
|
|
|
162
161
|
header=None,
|
|
163
162
|
names=GTF_NAMES,
|
|
164
163
|
dtype=GTF_DTYPES,
|
|
164
|
+
comment="#",
|
|
165
165
|
chunksize=chunksize,
|
|
166
166
|
skiprows=skiprows,
|
|
167
167
|
nrows=nrows,
|
|
@@ -204,22 +204,6 @@ def _parse_attributes_python(
|
|
|
204
204
|
return to_rows(attribute_column, ignore_bad=ignore_bad)
|
|
205
205
|
|
|
206
206
|
|
|
207
|
-
def _parse_restricted_attributes_compiled(attribute_column: pd.Series) -> pd.DataFrame:
|
|
208
|
-
if _parse_chunk_columns_compiled is None:
|
|
209
|
-
return _parse_restricted_attributes_python(attribute_column)
|
|
210
|
-
|
|
211
|
-
attribute_column = _normalize_attribute_series(attribute_column)
|
|
212
|
-
columns = _parse_chunk_columns_compiled(attribute_column.to_numpy(copy=False))
|
|
213
|
-
return pd.DataFrame(
|
|
214
|
-
{name: columns[name] for name in RESTRICTED_ATTRIBUTE_COLUMNS},
|
|
215
|
-
index=attribute_column.index,
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def _parse_restricted_attributes_python(attribute_column: pd.Series) -> pd.DataFrame:
|
|
220
|
-
return to_rows(_normalize_attribute_series(attribute_column)).reindex(columns=RESTRICTED_ATTRIBUTE_COLUMNS)
|
|
221
|
-
|
|
222
|
-
|
|
223
207
|
def _read_gtf_full(
|
|
224
208
|
path: Path,
|
|
225
209
|
*,
|
|
@@ -242,55 +226,24 @@ def _read_gtf_full(
|
|
|
242
226
|
return _finalize_gtf_frame(pd.concat(dfs, sort=False))
|
|
243
227
|
|
|
244
228
|
|
|
245
|
-
def _read_gtf_restricted(
|
|
246
|
-
path: Path,
|
|
247
|
-
*,
|
|
248
|
-
skiprows: int,
|
|
249
|
-
nrows: int | None,
|
|
250
|
-
chunksize: int,
|
|
251
|
-
parse_attributes,
|
|
252
|
-
) -> pd.DataFrame:
|
|
253
|
-
dfs: list[pd.DataFrame] = []
|
|
254
|
-
with _open_gtf_reader(path, chunksize=chunksize, skiprows=skiprows, nrows=nrows) as df_iter:
|
|
255
|
-
for df in df_iter:
|
|
256
|
-
subset = parse_attributes(df["Attribute"])
|
|
257
|
-
dfs.append(
|
|
258
|
-
pd.concat(
|
|
259
|
-
[df[["Chromosome", "Source", "Feature", "Start", "End", "Score", "Strand", "Frame"]], subset],
|
|
260
|
-
axis=1,
|
|
261
|
-
sort=False,
|
|
262
|
-
)
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
if not dfs:
|
|
266
|
-
return pd.DataFrame(columns=GTF_NAMES[:-1] + RESTRICTED_ATTRIBUTE_COLUMNS)
|
|
267
|
-
|
|
268
|
-
return _finalize_gtf_frame(pd.concat(dfs, sort=False))
|
|
269
|
-
|
|
270
|
-
|
|
271
229
|
def read_gtf(
|
|
272
230
|
f: str | Path,
|
|
273
231
|
/,
|
|
274
232
|
*,
|
|
275
233
|
nrows: int | None = None,
|
|
276
|
-
full: bool = True,
|
|
277
234
|
duplicate_attr: bool = False,
|
|
278
235
|
ignore_bad: bool = False,
|
|
279
236
|
) -> pd.DataFrame:
|
|
280
237
|
"""Read a GTF file using the compiled parser path when available."""
|
|
281
238
|
path = Path(f)
|
|
282
239
|
skiprows = find_first_data_line_index(path)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
ignore_bad=ignore_bad,
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
return read_gtf_restricted(path, skiprows=skiprows, nrows=nrows)
|
|
240
|
+
return read_gtf_full(
|
|
241
|
+
path,
|
|
242
|
+
nrows=nrows,
|
|
243
|
+
skiprows=skiprows,
|
|
244
|
+
duplicate_attr=duplicate_attr,
|
|
245
|
+
ignore_bad=ignore_bad,
|
|
246
|
+
)
|
|
294
247
|
|
|
295
248
|
|
|
296
249
|
def read_gtf_python(
|
|
@@ -298,24 +251,19 @@ def read_gtf_python(
|
|
|
298
251
|
/,
|
|
299
252
|
*,
|
|
300
253
|
nrows: int | None = None,
|
|
301
|
-
full: bool = True,
|
|
302
254
|
duplicate_attr: bool = False,
|
|
303
255
|
ignore_bad: bool = False,
|
|
304
256
|
) -> pd.DataFrame:
|
|
305
257
|
"""Read a GTF file using the pure Python attribute parser."""
|
|
306
258
|
path = Path(f)
|
|
307
259
|
skiprows = find_first_data_line_index(path)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
ignore_bad=ignore_bad,
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
return read_gtf_restricted_python(path, skiprows=skiprows, nrows=nrows)
|
|
260
|
+
return read_gtf_full_python(
|
|
261
|
+
path,
|
|
262
|
+
nrows=nrows,
|
|
263
|
+
skiprows=skiprows,
|
|
264
|
+
duplicate_attr=duplicate_attr,
|
|
265
|
+
ignore_bad=ignore_bad,
|
|
266
|
+
)
|
|
319
267
|
|
|
320
268
|
|
|
321
269
|
def read_gtf_full(
|
|
@@ -368,48 +316,6 @@ def read_gtf_full_python(
|
|
|
368
316
|
)
|
|
369
317
|
|
|
370
318
|
|
|
371
|
-
def read_gtf_restricted(
|
|
372
|
-
f: str | Path,
|
|
373
|
-
/,
|
|
374
|
-
skiprows: int = 0,
|
|
375
|
-
nrows: int | None = None,
|
|
376
|
-
chunksize: int = int(1e5),
|
|
377
|
-
*,
|
|
378
|
-
chunk_size: int | None = None,
|
|
379
|
-
) -> pd.DataFrame:
|
|
380
|
-
"""Read core GTF columns plus a small compiled-parser attribute subset."""
|
|
381
|
-
path = Path(f)
|
|
382
|
-
chunksize = _resolve_chunksize(chunksize, chunk_size)
|
|
383
|
-
return _read_gtf_restricted(
|
|
384
|
-
path,
|
|
385
|
-
skiprows=skiprows,
|
|
386
|
-
nrows=nrows,
|
|
387
|
-
chunksize=chunksize,
|
|
388
|
-
parse_attributes=_parse_restricted_attributes_compiled,
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
def read_gtf_restricted_python(
|
|
393
|
-
f: str | Path,
|
|
394
|
-
/,
|
|
395
|
-
skiprows: int = 0,
|
|
396
|
-
nrows: int | None = None,
|
|
397
|
-
chunksize: int = int(1e5),
|
|
398
|
-
*,
|
|
399
|
-
chunk_size: int | None = None,
|
|
400
|
-
) -> pd.DataFrame:
|
|
401
|
-
"""Read core GTF columns plus a small pure-Python attribute subset."""
|
|
402
|
-
path = Path(f)
|
|
403
|
-
chunksize = _resolve_chunksize(chunksize, chunk_size)
|
|
404
|
-
return _read_gtf_restricted(
|
|
405
|
-
path,
|
|
406
|
-
skiprows=skiprows,
|
|
407
|
-
nrows=nrows,
|
|
408
|
-
chunksize=chunksize,
|
|
409
|
-
parse_attributes=_parse_restricted_attributes_python,
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
|
|
413
319
|
__all__ = [
|
|
414
320
|
"find_first_data_line_index",
|
|
415
321
|
"parse_kv_fields",
|
|
@@ -417,8 +323,6 @@ __all__ = [
|
|
|
417
323
|
"read_gtf_full",
|
|
418
324
|
"read_gtf_full_python",
|
|
419
325
|
"read_gtf_python",
|
|
420
|
-
"read_gtf_restricted",
|
|
421
|
-
"read_gtf_restricted_python",
|
|
422
326
|
"to_rows",
|
|
423
327
|
"to_rows_keep_duplicates",
|
|
424
328
|
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gtfreader
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Fast Cython-backed parsing for GTF attribute columns.
|
|
5
|
+
Author: Endre Bakken Stovner
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Cython
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: pandas>=2.0
|
|
16
|
+
|
|
17
|
+
# gtfreader
|
|
18
|
+
|
|
19
|
+
Fast GTF reading into pandas DataFrames.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python -m pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from gtfreader import read_gtf
|
|
31
|
+
|
|
32
|
+
df = read_gtf("annotation.gtf")
|
|
33
|
+
print(df.columns)
|
|
34
|
+
print(df.head())
|
|
35
|
+
```
|
|
@@ -8,10 +8,10 @@ build-backend = "setuptools.build_meta"
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "gtfreader"
|
|
11
|
-
version = "0.
|
|
11
|
+
version = "0.2.0"
|
|
12
12
|
description = "Fast Cython-backed parsing for GTF attribute columns."
|
|
13
13
|
readme = "README.md"
|
|
14
|
-
requires-python = ">=3.
|
|
14
|
+
requires-python = ">=3.12"
|
|
15
15
|
authors = [
|
|
16
16
|
{ name = "Endre Bakken Stovner" },
|
|
17
17
|
]
|
|
@@ -22,6 +22,8 @@ classifiers = [
|
|
|
22
22
|
"Programming Language :: Python :: 3",
|
|
23
23
|
"Programming Language :: Cython",
|
|
24
24
|
"Programming Language :: Python :: 3 :: Only",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
25
27
|
"Operating System :: OS Independent",
|
|
26
28
|
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
27
29
|
]
|
|
@@ -39,7 +41,7 @@ include = ["gtfreader*"]
|
|
|
39
41
|
testpaths = ["tests"]
|
|
40
42
|
|
|
41
43
|
[tool.cibuildwheel]
|
|
42
|
-
build = "
|
|
44
|
+
build = "cp312-* cp313-*"
|
|
43
45
|
skip = "pp* *-musllinux_* *-manylinux_i686"
|
|
44
46
|
test-command = "pytest -q {project}/tests"
|
|
45
47
|
test-requires = ["pytest"]
|
|
@@ -25,8 +25,14 @@ def test_known_and_dynamic_columns_are_parsed():
|
|
|
25
25
|
assert columns["exon_number"] == ["1", None]
|
|
26
26
|
assert columns["custom_key"] == ["custom", None]
|
|
27
27
|
assert columns["gene_name"][0] is columns["gene_name"][1]
|
|
28
|
-
assert columns["gene_type"]
|
|
29
|
-
assert columns["level"]
|
|
28
|
+
assert columns["gene_type"] == ["protein_coding", "protein_coding"]
|
|
29
|
+
assert columns["level"] == ["2", "2"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_compiled_parser_omits_unseen_fast_path_columns():
|
|
33
|
+
columns = parse_chunk_columns(['gene_id "GENE1"; transcript_id "TX1";'])
|
|
34
|
+
|
|
35
|
+
assert set(columns) == {"gene_id", "transcript_id"}
|
|
30
36
|
|
|
31
37
|
|
|
32
38
|
def test_compiled_parser_supports_semicolons_in_quotes_and_unquoted_values():
|
|
@@ -11,8 +11,6 @@ from gtfreader import (
|
|
|
11
11
|
read_gtf_full,
|
|
12
12
|
read_gtf_full_python,
|
|
13
13
|
read_gtf_python,
|
|
14
|
-
read_gtf_restricted,
|
|
15
|
-
read_gtf_restricted_python,
|
|
16
14
|
)
|
|
17
15
|
|
|
18
16
|
|
|
@@ -120,6 +118,23 @@ def test_full_readers_support_mixed_attribute_formats(tmp_path: Path, reader):
|
|
|
120
118
|
assert result.iloc[1]["exon_id"] == "EX2"
|
|
121
119
|
|
|
122
120
|
|
|
121
|
+
@pytest.mark.parametrize("reader", [read_gtf, read_gtf_python], ids=["default", "python"])
|
|
122
|
+
def test_readers_ignore_comment_lines_inside_body(tmp_path: Path, reader):
|
|
123
|
+
path = _write_temp_gtf(
|
|
124
|
+
tmp_path,
|
|
125
|
+
"# header\n"
|
|
126
|
+
'chr1\thavana\tgene\t11869\t14409\t.\t+\t.\tgene_id "G1";\n'
|
|
127
|
+
"# another comment\n"
|
|
128
|
+
'chr1\thavana\ttranscript\t11869\t14409\t.\t+\t.\tgene_id "G1"; transcript_id "T1";\n',
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
result = _normalize_frame_for_compare(reader(path))
|
|
132
|
+
|
|
133
|
+
assert list(result["Feature"]) == ["gene", "transcript"]
|
|
134
|
+
assert list(result["gene_id"]) == ["G1", "G1"]
|
|
135
|
+
assert list(result["transcript_id"]) == [None, "T1"]
|
|
136
|
+
|
|
137
|
+
|
|
123
138
|
def test_read_gtf_duplicate_attr_keeps_all_values(tmp_path: Path):
|
|
124
139
|
path = _write_temp_gtf(
|
|
125
140
|
tmp_path,
|
|
@@ -153,15 +168,14 @@ def test_read_gtf_full_multiple_chunks_match(tmp_path: Path):
|
|
|
153
168
|
assert_frame_equal(chunked, unchunked, check_dtype=False)
|
|
154
169
|
|
|
155
170
|
|
|
156
|
-
def
|
|
171
|
+
def test_read_gtf_omits_unseen_fast_path_columns(tmp_path: Path):
|
|
157
172
|
path = _write_temp_gtf(
|
|
158
173
|
tmp_path,
|
|
159
174
|
"# header\n"
|
|
160
|
-
'chr1\thavana\
|
|
175
|
+
'chr1\thavana\tgene\t12010\t12057\t.\t+\t.\tgene_id "ENSG1"; gene_name "DDX11L1";\n',
|
|
161
176
|
)
|
|
162
177
|
|
|
163
|
-
|
|
164
|
-
result = read_gtf_restricted(path, skiprows=skiprows)
|
|
178
|
+
result = read_gtf(path)
|
|
165
179
|
|
|
166
180
|
assert list(result.columns) == [
|
|
167
181
|
"Chromosome",
|
|
@@ -173,33 +187,10 @@ def test_read_gtf_restricted_returns_core_columns(tmp_path: Path):
|
|
|
173
187
|
"Strand",
|
|
174
188
|
"Frame",
|
|
175
189
|
"gene_id",
|
|
176
|
-
"
|
|
177
|
-
"exon_number",
|
|
178
|
-
"exon_id",
|
|
190
|
+
"gene_name",
|
|
179
191
|
]
|
|
180
192
|
assert result.iloc[0]["Start"] == 12009
|
|
181
|
-
assert result.iloc[0]["
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
@pytest.mark.parametrize(
|
|
185
|
-
"reader",
|
|
186
|
-
[read_gtf_restricted, read_gtf_restricted_python],
|
|
187
|
-
ids=["default", "python"],
|
|
188
|
-
)
|
|
189
|
-
def test_restricted_readers_support_unquoted_values(tmp_path: Path, reader):
|
|
190
|
-
path = _write_temp_gtf(
|
|
191
|
-
tmp_path,
|
|
192
|
-
"# header\n"
|
|
193
|
-
'chr1\tensembl_havana\texon\t3069203\t3069296\t.\t+\t.\tgene_id "G1"; transcript_id "T1"; exon_number 4; exon_id "EX4";\n',
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
skiprows = find_first_data_line_index(path)
|
|
197
|
-
result = _normalize_frame_for_compare(reader(path, skiprows=skiprows))
|
|
198
|
-
|
|
199
|
-
assert result.iloc[0]["gene_id"] == "G1"
|
|
200
|
-
assert result.iloc[0]["transcript_id"] == "T1"
|
|
201
|
-
assert result.iloc[0]["exon_number"] == "4"
|
|
202
|
-
assert result.iloc[0]["exon_id"] == "EX4"
|
|
193
|
+
assert result.iloc[0]["gene_name"] == "DDX11L1"
|
|
203
194
|
|
|
204
195
|
|
|
205
196
|
def test_read_gtf_python_matches_compiled_reader(tmp_path: Path):
|
|
@@ -217,12 +208,7 @@ def test_read_gtf_python_matches_compiled_reader(tmp_path: Path):
|
|
|
217
208
|
compiled = _normalize_frame_for_compare(compiled)
|
|
218
209
|
python = _normalize_frame_for_compare(python)
|
|
219
210
|
|
|
220
|
-
|
|
221
|
-
assert list(python["gene_name"]) == ["DDX11L1", None]
|
|
222
|
-
assert list(python["transcript_id"]) == [None, "ENST1"]
|
|
223
|
-
assert list(python["transcript_name"]) == [None, "DDX11L1-201"]
|
|
224
|
-
assert list(python["tag"]) == [None, "basic"]
|
|
225
|
-
assert list(python["Start"]) == list(compiled["Start"])
|
|
211
|
+
assert_frame_equal(compiled, python, check_dtype=False)
|
|
226
212
|
|
|
227
213
|
|
|
228
214
|
def test_read_gtf_python_duplicate_attr_keeps_all_values(tmp_path: Path):
|
|
@@ -237,20 +223,3 @@ def test_read_gtf_python_duplicate_attr_keeps_all_values(tmp_path: Path):
|
|
|
237
223
|
|
|
238
224
|
assert compiled.iloc[0]["tag"] == "CCDS,basic"
|
|
239
225
|
assert python.iloc[0]["tag"] == "CCDS,basic"
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def test_read_gtf_restricted_python_matches_compiled(tmp_path: Path):
|
|
243
|
-
path = _write_temp_gtf(
|
|
244
|
-
tmp_path,
|
|
245
|
-
"# header\n"
|
|
246
|
-
'chr1\thavana\texon\t12010\t12057\t.\t+\t.\tgene_id "ENSG1"; transcript_id "ENST1"; exon_number "1"; exon_id "ENSE1";\n',
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
skiprows = find_first_data_line_index(path)
|
|
250
|
-
compiled = read_gtf_restricted(path, skiprows=skiprows)
|
|
251
|
-
python = read_gtf_restricted_python(path, skiprows=skiprows)
|
|
252
|
-
|
|
253
|
-
compiled = _normalize_frame_for_compare(compiled)
|
|
254
|
-
python = _normalize_frame_for_compare(python)
|
|
255
|
-
|
|
256
|
-
assert_frame_equal(compiled, python, check_dtype=False)
|
gtfreader-0.1.1/PKG-INFO
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: gtfreader
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: Fast Cython-backed parsing for GTF attribute columns.
|
|
5
|
-
Author: Endre Bakken Stovner
|
|
6
|
-
Classifier: Programming Language :: Python :: 3
|
|
7
|
-
Classifier: Programming Language :: Cython
|
|
8
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
11
|
-
Requires-Python: >=3.9
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
Requires-Dist: pandas>=2.0
|
|
14
|
-
|
|
15
|
-
# gtfreader
|
|
16
|
-
|
|
17
|
-
`gtfreader` is a small package for parsing and reading GTF files into pandas dataframes.
|
|
18
|
-
|
|
19
|
-
## Install
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
python -m pip install -e .
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Usage
|
|
26
|
-
|
|
27
|
-
```python
|
|
28
|
-
from gtfreader import read_gtf, read_gtf_python
|
|
29
|
-
|
|
30
|
-
df = read_gtf("annotation.gtf")
|
|
31
|
-
df_python = read_gtf_python("annotation.gtf")
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
`read_gtf(...)` uses the compiled parser path when available. `read_gtf_python(...)` uses the high-level pure Python parser path used by the current `pyrunges` reader style.
|
|
35
|
-
|
|
36
|
-
If you want to use the compiled low-level parser directly, pass it raw attribute strings from column 9 of the GTF before they have been expanded:
|
|
37
|
-
|
|
38
|
-
```python
|
|
39
|
-
import pandas as pd
|
|
40
|
-
|
|
41
|
-
from gtfreader import find_first_data_line_index, parse_chunk_columns
|
|
42
|
-
|
|
43
|
-
skiprows = find_first_data_line_index("annotation.gtf")
|
|
44
|
-
attribute_lines = pd.read_csv(
|
|
45
|
-
"annotation.gtf",
|
|
46
|
-
sep="\t",
|
|
47
|
-
header=None,
|
|
48
|
-
usecols=[8],
|
|
49
|
-
names=["Attribute"],
|
|
50
|
-
skiprows=skiprows,
|
|
51
|
-
)["Attribute"].tolist()
|
|
52
|
-
|
|
53
|
-
compiled_columns = parse_chunk_columns(attribute_lines)
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Build
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
python -m build
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Test
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
python -m pytest -q
|
|
66
|
-
```
|
gtfreader-0.1.1/README.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# gtfreader
|
|
2
|
-
|
|
3
|
-
`gtfreader` is a small package for parsing and reading GTF files into pandas dataframes.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
python -m pip install -e .
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
```python
|
|
14
|
-
from gtfreader import read_gtf, read_gtf_python
|
|
15
|
-
|
|
16
|
-
df = read_gtf("annotation.gtf")
|
|
17
|
-
df_python = read_gtf_python("annotation.gtf")
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
`read_gtf(...)` uses the compiled parser path when available. `read_gtf_python(...)` uses the high-level pure Python parser path used by the current `pyrunges` reader style.
|
|
21
|
-
|
|
22
|
-
If you want to use the compiled low-level parser directly, pass it raw attribute strings from column 9 of the GTF before they have been expanded:
|
|
23
|
-
|
|
24
|
-
```python
|
|
25
|
-
import pandas as pd
|
|
26
|
-
|
|
27
|
-
from gtfreader import find_first_data_line_index, parse_chunk_columns
|
|
28
|
-
|
|
29
|
-
skiprows = find_first_data_line_index("annotation.gtf")
|
|
30
|
-
attribute_lines = pd.read_csv(
|
|
31
|
-
"annotation.gtf",
|
|
32
|
-
sep="\t",
|
|
33
|
-
header=None,
|
|
34
|
-
usecols=[8],
|
|
35
|
-
names=["Attribute"],
|
|
36
|
-
skiprows=skiprows,
|
|
37
|
-
)["Attribute"].tolist()
|
|
38
|
-
|
|
39
|
-
compiled_columns = parse_chunk_columns(attribute_lines)
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Build
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
python -m build
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Test
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
python -m pytest -q
|
|
52
|
-
```
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
# cython: language_level=3
|
|
2
|
-
# cython: boundscheck=False
|
|
3
|
-
# cython: wraparound=False
|
|
4
|
-
# cython: initializedcheck=False
|
|
5
|
-
# cython: infer_types=True
|
|
6
|
-
# cython: nonecheck=False
|
|
7
|
-
|
|
8
|
-
cdef inline int _key_code(str s, Py_ssize_t a, Py_ssize_t b):
|
|
9
|
-
cdef Py_ssize_t n
|
|
10
|
-
n = b - a
|
|
11
|
-
|
|
12
|
-
if n == 3:
|
|
13
|
-
if s[a] == 't' and s[a + 1] == 'a' and s[a + 2] == 'g':
|
|
14
|
-
return 9
|
|
15
|
-
if s[a] == 'o' and s[a + 1] == 'n' and s[a + 2] == 't':
|
|
16
|
-
return 17
|
|
17
|
-
|
|
18
|
-
elif n == 5:
|
|
19
|
-
if (
|
|
20
|
-
s[a] == 'l' and s[a + 1] == 'e' and s[a + 2] == 'v' and
|
|
21
|
-
s[a + 3] == 'e' and s[a + 4] == 'l'
|
|
22
|
-
):
|
|
23
|
-
return 16
|
|
24
|
-
|
|
25
|
-
elif n == 6:
|
|
26
|
-
if (
|
|
27
|
-
s[a] == 'c' and s[a + 1] == 'c' and s[a + 2] == 'd' and
|
|
28
|
-
s[a + 3] == 's' and s[a + 4] == 'i' and s[a + 5] == 'd'
|
|
29
|
-
):
|
|
30
|
-
return 14
|
|
31
|
-
|
|
32
|
-
elif n == 7:
|
|
33
|
-
if (
|
|
34
|
-
s[a] == 'g' and s[a + 1] == 'e' and s[a + 2] == 'n' and
|
|
35
|
-
s[a + 3] == 'e' and s[a + 4] == '_' and s[a + 5] == 'i' and
|
|
36
|
-
s[a + 6] == 'd'
|
|
37
|
-
):
|
|
38
|
-
return 1
|
|
39
|
-
if (
|
|
40
|
-
s[a] == 'e' and s[a + 1] == 'x' and s[a + 2] == 'o' and
|
|
41
|
-
s[a + 3] == 'n' and s[a + 4] == '_' and s[a + 5] == 'i' and
|
|
42
|
-
s[a + 6] == 'd'
|
|
43
|
-
):
|
|
44
|
-
return 8
|
|
45
|
-
if (
|
|
46
|
-
s[a] == 'h' and s[a + 1] == 'g' and s[a + 2] == 'n' and
|
|
47
|
-
s[a + 3] == 'c' and s[a + 4] == '_' and s[a + 5] == 'i' and
|
|
48
|
-
s[a + 6] == 'd'
|
|
49
|
-
):
|
|
50
|
-
return 13
|
|
51
|
-
|
|
52
|
-
elif n == 9:
|
|
53
|
-
if (
|
|
54
|
-
s[a] == 'g' and s[a + 1] == 'e' and s[a + 2] == 'n' and
|
|
55
|
-
s[a + 3] == 'e' and s[a + 4] == '_'
|
|
56
|
-
):
|
|
57
|
-
if (
|
|
58
|
-
s[a + 5] == 'n' and s[a + 6] == 'a' and
|
|
59
|
-
s[a + 7] == 'm' and s[a + 8] == 'e'
|
|
60
|
-
):
|
|
61
|
-
return 3
|
|
62
|
-
if (
|
|
63
|
-
s[a + 5] == 't' and s[a + 6] == 'y' and
|
|
64
|
-
s[a + 7] == 'p' and s[a + 8] == 'e'
|
|
65
|
-
):
|
|
66
|
-
return 4
|
|
67
|
-
|
|
68
|
-
elif n == 10:
|
|
69
|
-
if (
|
|
70
|
-
s[a] == 'a' and s[a + 1] == 'r' and s[a + 2] == 't' and
|
|
71
|
-
s[a + 3] == 'i' and s[a + 4] == 'f' and s[a + 5] == '_' and
|
|
72
|
-
s[a + 6] == 'd' and s[a + 7] == 'u' and s[a + 8] == 'p' and
|
|
73
|
-
s[a + 9] == 'l'
|
|
74
|
-
):
|
|
75
|
-
return 15
|
|
76
|
-
|
|
77
|
-
elif n == 11:
|
|
78
|
-
if (
|
|
79
|
-
s[a] == 'e' and s[a + 1] == 'x' and s[a + 2] == 'o' and
|
|
80
|
-
s[a + 3] == 'n' and s[a + 4] == '_' and s[a + 5] == 'n' and
|
|
81
|
-
s[a + 6] == 'u' and s[a + 7] == 'm' and s[a + 8] == 'b' and
|
|
82
|
-
s[a + 9] == 'e' and s[a + 10] == 'r'
|
|
83
|
-
):
|
|
84
|
-
return 7
|
|
85
|
-
if (
|
|
86
|
-
s[a] == 'h' and s[a + 1] == 'a' and s[a + 2] == 'v' and
|
|
87
|
-
s[a + 3] == 'a' and s[a + 4] == 'n' and s[a + 5] == 'a' and
|
|
88
|
-
s[a + 6] == '_' and s[a + 7] == 'g' and s[a + 8] == 'e' and
|
|
89
|
-
s[a + 9] == 'n' and s[a + 10] == 'e'
|
|
90
|
-
):
|
|
91
|
-
return 11
|
|
92
|
-
|
|
93
|
-
elif n == 13:
|
|
94
|
-
if (
|
|
95
|
-
s[a] == 't' and s[a + 1] == 'r' and s[a + 2] == 'a' and
|
|
96
|
-
s[a + 3] == 'n' and s[a + 4] == 's' and s[a + 5] == 'c' and
|
|
97
|
-
s[a + 6] == 'r' and s[a + 7] == 'i' and s[a + 8] == 'p' and
|
|
98
|
-
s[a + 9] == 't' and s[a + 10] == '_' and s[a + 11] == 'i' and
|
|
99
|
-
s[a + 12] == 'd'
|
|
100
|
-
):
|
|
101
|
-
return 2
|
|
102
|
-
|
|
103
|
-
elif n == 15:
|
|
104
|
-
if (
|
|
105
|
-
s[a] == 't' and s[a + 1] == 'r' and s[a + 2] == 'a' and
|
|
106
|
-
s[a + 3] == 'n' and s[a + 4] == 's' and s[a + 5] == 'c' and
|
|
107
|
-
s[a + 6] == 'r' and s[a + 7] == 'i' and s[a + 8] == 'p' and
|
|
108
|
-
s[a + 9] == 't' and s[a + 10] == '_'
|
|
109
|
-
):
|
|
110
|
-
if (
|
|
111
|
-
s[a + 11] == 'n' and s[a + 12] == 'a' and
|
|
112
|
-
s[a + 13] == 'm' and s[a + 14] == 'e'
|
|
113
|
-
):
|
|
114
|
-
return 5
|
|
115
|
-
if (
|
|
116
|
-
s[a + 11] == 't' and s[a + 12] == 'y' and
|
|
117
|
-
s[a + 13] == 'p' and s[a + 14] == 'e'
|
|
118
|
-
):
|
|
119
|
-
return 6
|
|
120
|
-
|
|
121
|
-
elif n == 17:
|
|
122
|
-
if (
|
|
123
|
-
s[a] == 'h' and s[a + 1] == 'a' and s[a + 2] == 'v' and
|
|
124
|
-
s[a + 3] == 'a' and s[a + 4] == 'n' and s[a + 5] == 'a' and
|
|
125
|
-
s[a + 6] == '_' and s[a + 7] == 't' and s[a + 8] == 'r' and
|
|
126
|
-
s[a + 9] == 'a' and s[a + 10] == 'n' and s[a + 11] == 's' and
|
|
127
|
-
s[a + 12] == 'c' and s[a + 13] == 'r' and s[a + 14] == 'i' and
|
|
128
|
-
s[a + 15] == 'p' and s[a + 16] == 't'
|
|
129
|
-
):
|
|
130
|
-
return 10
|
|
131
|
-
|
|
132
|
-
elif n == 24:
|
|
133
|
-
if (
|
|
134
|
-
s[a] == 't' and s[a + 1] == 'r' and s[a + 2] == 'a' and
|
|
135
|
-
s[a + 3] == 'n' and s[a + 4] == 's' and s[a + 5] == 'c' and
|
|
136
|
-
s[a + 6] == 'r' and s[a + 7] == 'i' and s[a + 8] == 'p' and
|
|
137
|
-
s[a + 9] == 't' and s[a + 10] == '_' and s[a + 11] == 's' and
|
|
138
|
-
s[a + 12] == 'u' and s[a + 13] == 'p' and s[a + 14] == 'p' and
|
|
139
|
-
s[a + 15] == 'o' and s[a + 16] == 'r' and s[a + 17] == 't' and
|
|
140
|
-
s[a + 18] == '_' and s[a + 19] == 'l' and s[a + 20] == 'e' and
|
|
141
|
-
s[a + 21] == 'v' and s[a + 22] == 'e' and s[a + 23] == 'l'
|
|
142
|
-
):
|
|
143
|
-
return 12
|
|
144
|
-
|
|
145
|
-
return 0
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
cdef inline str _cached_str(dict cache, str value):
|
|
149
|
-
cdef object obj
|
|
150
|
-
obj = cache.get(value)
|
|
151
|
-
if obj is None:
|
|
152
|
-
cache[value] = value
|
|
153
|
-
return value
|
|
154
|
-
return <str> obj
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def parse_chunk_columns(lines):
|
|
158
|
-
cdef Py_ssize_t n, row_i
|
|
159
|
-
cdef Py_ssize_t pos, m, key_start, key_end, val_start, val_end
|
|
160
|
-
cdef dict columns
|
|
161
|
-
|
|
162
|
-
cdef dict gene_name_cache
|
|
163
|
-
cdef dict gene_type_cache
|
|
164
|
-
cdef dict transcript_type_cache
|
|
165
|
-
cdef dict tag_cache
|
|
166
|
-
cdef dict level_cache
|
|
167
|
-
cdef dict ont_cache
|
|
168
|
-
cdef dict transcript_support_level_cache
|
|
169
|
-
cdef dict artif_dupl_cache
|
|
170
|
-
|
|
171
|
-
cdef str s, key, value
|
|
172
|
-
cdef list col
|
|
173
|
-
cdef int code
|
|
174
|
-
|
|
175
|
-
cdef object obj_col
|
|
176
|
-
|
|
177
|
-
cdef list gene_id_col
|
|
178
|
-
cdef list transcript_id_col
|
|
179
|
-
cdef list gene_name_col
|
|
180
|
-
cdef list gene_type_col
|
|
181
|
-
cdef list transcript_name_col
|
|
182
|
-
cdef list transcript_type_col
|
|
183
|
-
cdef list exon_number_col
|
|
184
|
-
cdef list exon_id_col
|
|
185
|
-
cdef list tag_col
|
|
186
|
-
cdef list havana_transcript_col
|
|
187
|
-
cdef list havana_gene_col
|
|
188
|
-
cdef list transcript_support_level_col
|
|
189
|
-
cdef list hgnc_id_col
|
|
190
|
-
cdef list ccdsid_col
|
|
191
|
-
cdef list artif_dupl_col
|
|
192
|
-
cdef list level_col
|
|
193
|
-
cdef list ont_col
|
|
194
|
-
|
|
195
|
-
n = len(lines)
|
|
196
|
-
columns = {}
|
|
197
|
-
|
|
198
|
-
gene_name_cache = {}
|
|
199
|
-
gene_type_cache = {}
|
|
200
|
-
transcript_type_cache = {}
|
|
201
|
-
tag_cache = {}
|
|
202
|
-
level_cache = {}
|
|
203
|
-
ont_cache = {}
|
|
204
|
-
transcript_support_level_cache = {}
|
|
205
|
-
artif_dupl_cache = {}
|
|
206
|
-
|
|
207
|
-
gene_id_col = [None] * n
|
|
208
|
-
transcript_id_col = [None] * n
|
|
209
|
-
gene_name_col = [None] * n
|
|
210
|
-
gene_type_col = [None] * n
|
|
211
|
-
transcript_name_col = [None] * n
|
|
212
|
-
transcript_type_col = [None] * n
|
|
213
|
-
exon_number_col = [None] * n
|
|
214
|
-
exon_id_col = [None] * n
|
|
215
|
-
tag_col = [None] * n
|
|
216
|
-
havana_transcript_col = [None] * n
|
|
217
|
-
havana_gene_col = [None] * n
|
|
218
|
-
transcript_support_level_col = [None] * n
|
|
219
|
-
hgnc_id_col = [None] * n
|
|
220
|
-
ccdsid_col = [None] * n
|
|
221
|
-
artif_dupl_col = [None] * n
|
|
222
|
-
level_col = [None] * n
|
|
223
|
-
ont_col = [None] * n
|
|
224
|
-
|
|
225
|
-
columns["gene_id"] = gene_id_col
|
|
226
|
-
columns["transcript_id"] = transcript_id_col
|
|
227
|
-
columns["gene_name"] = gene_name_col
|
|
228
|
-
columns["gene_type"] = gene_type_col
|
|
229
|
-
columns["transcript_name"] = transcript_name_col
|
|
230
|
-
columns["transcript_type"] = transcript_type_col
|
|
231
|
-
columns["exon_number"] = exon_number_col
|
|
232
|
-
columns["exon_id"] = exon_id_col
|
|
233
|
-
columns["tag"] = tag_col
|
|
234
|
-
columns["havana_transcript"] = havana_transcript_col
|
|
235
|
-
columns["havana_gene"] = havana_gene_col
|
|
236
|
-
columns["transcript_support_level"] = transcript_support_level_col
|
|
237
|
-
columns["hgnc_id"] = hgnc_id_col
|
|
238
|
-
columns["ccdsid"] = ccdsid_col
|
|
239
|
-
columns["artif_dupl"] = artif_dupl_col
|
|
240
|
-
columns["level"] = level_col
|
|
241
|
-
columns["ont"] = ont_col
|
|
242
|
-
|
|
243
|
-
for row_i in range(n):
|
|
244
|
-
s = <str> lines[row_i]
|
|
245
|
-
m = len(s)
|
|
246
|
-
pos = 0
|
|
247
|
-
|
|
248
|
-
while pos < m:
|
|
249
|
-
while pos < m and (s[pos] == ' ' or s[pos] == '\t' or s[pos] == ';'):
|
|
250
|
-
pos += 1
|
|
251
|
-
if pos >= m:
|
|
252
|
-
break
|
|
253
|
-
|
|
254
|
-
key_start = pos
|
|
255
|
-
|
|
256
|
-
while pos < m and s[pos] != ' ' and s[pos] != '\t':
|
|
257
|
-
pos += 1
|
|
258
|
-
key_end = pos
|
|
259
|
-
|
|
260
|
-
while pos < m and (s[pos] == ' ' or s[pos] == '\t'):
|
|
261
|
-
pos += 1
|
|
262
|
-
if pos >= m:
|
|
263
|
-
break
|
|
264
|
-
|
|
265
|
-
if s[pos] == '"':
|
|
266
|
-
val_start = pos + 1
|
|
267
|
-
pos += 1
|
|
268
|
-
|
|
269
|
-
while pos < m and s[pos] != '"':
|
|
270
|
-
pos += 1
|
|
271
|
-
if pos >= m:
|
|
272
|
-
break
|
|
273
|
-
|
|
274
|
-
val_end = pos
|
|
275
|
-
else:
|
|
276
|
-
val_start = pos
|
|
277
|
-
while pos < m and s[pos] != ';':
|
|
278
|
-
pos += 1
|
|
279
|
-
val_end = pos
|
|
280
|
-
while val_end > val_start and (s[val_end - 1] == ' ' or s[val_end - 1] == '\t'):
|
|
281
|
-
val_end -= 1
|
|
282
|
-
|
|
283
|
-
value = s[val_start:val_end]
|
|
284
|
-
|
|
285
|
-
code = _key_code(s, key_start, key_end)
|
|
286
|
-
|
|
287
|
-
if code == 1:
|
|
288
|
-
gene_id_col[row_i] = value
|
|
289
|
-
|
|
290
|
-
elif code == 2:
|
|
291
|
-
transcript_id_col[row_i] = value
|
|
292
|
-
|
|
293
|
-
elif code == 3:
|
|
294
|
-
gene_name_col[row_i] = _cached_str(gene_name_cache, value)
|
|
295
|
-
|
|
296
|
-
elif code == 4:
|
|
297
|
-
gene_type_col[row_i] = _cached_str(gene_type_cache, value)
|
|
298
|
-
|
|
299
|
-
elif code == 5:
|
|
300
|
-
transcript_name_col[row_i] = value
|
|
301
|
-
|
|
302
|
-
elif code == 6:
|
|
303
|
-
transcript_type_col[row_i] = _cached_str(transcript_type_cache, value)
|
|
304
|
-
|
|
305
|
-
elif code == 7:
|
|
306
|
-
exon_number_col[row_i] = value
|
|
307
|
-
|
|
308
|
-
elif code == 8:
|
|
309
|
-
exon_id_col[row_i] = value
|
|
310
|
-
|
|
311
|
-
elif code == 9:
|
|
312
|
-
tag_col[row_i] = _cached_str(tag_cache, value)
|
|
313
|
-
|
|
314
|
-
elif code == 10:
|
|
315
|
-
havana_transcript_col[row_i] = value
|
|
316
|
-
|
|
317
|
-
elif code == 11:
|
|
318
|
-
havana_gene_col[row_i] = value
|
|
319
|
-
|
|
320
|
-
elif code == 12:
|
|
321
|
-
transcript_support_level_col[row_i] = _cached_str(
|
|
322
|
-
transcript_support_level_cache, value
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
elif code == 13:
|
|
326
|
-
hgnc_id_col[row_i] = value
|
|
327
|
-
|
|
328
|
-
elif code == 14:
|
|
329
|
-
ccdsid_col[row_i] = value
|
|
330
|
-
|
|
331
|
-
elif code == 15:
|
|
332
|
-
artif_dupl_col[row_i] = _cached_str(artif_dupl_cache, value)
|
|
333
|
-
|
|
334
|
-
elif code == 16:
|
|
335
|
-
level_col[row_i] = _cached_str(level_cache, value)
|
|
336
|
-
|
|
337
|
-
elif code == 17:
|
|
338
|
-
ont_col[row_i] = _cached_str(ont_cache, value)
|
|
339
|
-
|
|
340
|
-
else:
|
|
341
|
-
key = s[key_start:key_end]
|
|
342
|
-
obj_col = columns.get(key)
|
|
343
|
-
if obj_col is None:
|
|
344
|
-
col = [None] * n
|
|
345
|
-
columns[key] = col
|
|
346
|
-
else:
|
|
347
|
-
col = <list> obj_col
|
|
348
|
-
col[row_i] = value
|
|
349
|
-
|
|
350
|
-
pos += 1
|
|
351
|
-
|
|
352
|
-
return columns
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: gtfreader
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: Fast Cython-backed parsing for GTF attribute columns.
|
|
5
|
-
Author: Endre Bakken Stovner
|
|
6
|
-
Classifier: Programming Language :: Python :: 3
|
|
7
|
-
Classifier: Programming Language :: Cython
|
|
8
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
11
|
-
Requires-Python: >=3.9
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
Requires-Dist: pandas>=2.0
|
|
14
|
-
|
|
15
|
-
# gtfreader
|
|
16
|
-
|
|
17
|
-
`gtfreader` is a small package for parsing and reading GTF files into pandas dataframes.
|
|
18
|
-
|
|
19
|
-
## Install
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
python -m pip install -e .
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Usage
|
|
26
|
-
|
|
27
|
-
```python
|
|
28
|
-
from gtfreader import read_gtf, read_gtf_python
|
|
29
|
-
|
|
30
|
-
df = read_gtf("annotation.gtf")
|
|
31
|
-
df_python = read_gtf_python("annotation.gtf")
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
`read_gtf(...)` uses the compiled parser path when available. `read_gtf_python(...)` uses the high-level pure Python parser path used by the current `pyrunges` reader style.
|
|
35
|
-
|
|
36
|
-
If you want to use the compiled low-level parser directly, pass it raw attribute strings from column 9 of the GTF before they have been expanded:
|
|
37
|
-
|
|
38
|
-
```python
|
|
39
|
-
import pandas as pd
|
|
40
|
-
|
|
41
|
-
from gtfreader import find_first_data_line_index, parse_chunk_columns
|
|
42
|
-
|
|
43
|
-
skiprows = find_first_data_line_index("annotation.gtf")
|
|
44
|
-
attribute_lines = pd.read_csv(
|
|
45
|
-
"annotation.gtf",
|
|
46
|
-
sep="\t",
|
|
47
|
-
header=None,
|
|
48
|
-
usecols=[8],
|
|
49
|
-
names=["Attribute"],
|
|
50
|
-
skiprows=skiprows,
|
|
51
|
-
)["Attribute"].tolist()
|
|
52
|
-
|
|
53
|
-
compiled_columns = parse_chunk_columns(attribute_lines)
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Build
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
python -m build
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Test
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
python -m pytest -q
|
|
66
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|