phylotypy 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.
- phylotypy-0.2.0/LICENSE +21 -0
- phylotypy-0.2.0/PKG-INFO +144 -0
- phylotypy-0.2.0/README.md +123 -0
- phylotypy-0.2.0/pyproject.toml +39 -0
- phylotypy-0.2.0/setup.cfg +4 -0
- phylotypy-0.2.0/setup.py +30 -0
- phylotypy-0.2.0/src/phylotypy/__init__.py +4 -0
- phylotypy-0.2.0/src/phylotypy/bootstrap.py +105 -0
- phylotypy-0.2.0/src/phylotypy/classifier.py +217 -0
- phylotypy-0.2.0/src/phylotypy/classify_bootstraps/__init__.py +1 -0
- phylotypy-0.2.0/src/phylotypy/classify_bootstraps/classify_bootstraps.c +30328 -0
- phylotypy-0.2.0/src/phylotypy/classify_bootstraps/classify_bootstraps.pyx +77 -0
- phylotypy-0.2.0/src/phylotypy/cond_prob_c/__init__.py +0 -0
- phylotypy-0.2.0/src/phylotypy/cond_prob_c/cond_prob_cython.c +13010 -0
- phylotypy-0.2.0/src/phylotypy/cond_prob_c/cond_prob_cython.pyx +40 -0
- phylotypy-0.2.0/src/phylotypy/conditional_prob.py +220 -0
- phylotypy-0.2.0/src/phylotypy/get_kmer_db.py +145 -0
- phylotypy-0.2.0/src/phylotypy/kmers.py +443 -0
- phylotypy-0.2.0/src/phylotypy/results.py +34 -0
- phylotypy-0.2.0/src/phylotypy/training_data.py +114 -0
- phylotypy-0.2.0/src/phylotypy/utilities/__init__.py +1 -0
- phylotypy-0.2.0/src/phylotypy/utilities/read_fasta.py +106 -0
- phylotypy-0.2.0/src/phylotypy/utilities/utilities.py +187 -0
- phylotypy-0.2.0/src/phylotypy.egg-info/PKG-INFO +144 -0
- phylotypy-0.2.0/src/phylotypy.egg-info/SOURCES.txt +34 -0
- phylotypy-0.2.0/src/phylotypy.egg-info/dependency_links.txt +1 -0
- phylotypy-0.2.0/src/phylotypy.egg-info/requires.txt +11 -0
- phylotypy-0.2.0/src/phylotypy.egg-info/top_level.txt +1 -0
- phylotypy-0.2.0/tests/test_bootstrap.py +23 -0
- phylotypy-0.2.0/tests/test_classify.py +60 -0
- phylotypy-0.2.0/tests/test_classify_boostrap_cython.py +38 -0
- phylotypy-0.2.0/tests/test_conditional_prob.py +53 -0
- phylotypy-0.2.0/tests/test_kmers.py +357 -0
- phylotypy-0.2.0/tests/test_read_fasta.py +40 -0
- phylotypy-0.2.0/tests/test_training_data.py +28 -0
- phylotypy-0.2.0/tests/test_utilities.py +30 -0
phylotypy-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Chad Saltikov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
phylotypy-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phylotypy
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Naive Bayes Classifier with Rust-accelerated taxonomy functions
|
|
5
|
+
Author-email: Chad Saltikov <saltikov@ucsc.edu>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: setuptools
|
|
11
|
+
Requires-Dist: numpy~=2.1.3
|
|
12
|
+
Requires-Dist: numba~=0.61.0
|
|
13
|
+
Requires-Dist: pandas~=2.2.3
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: pandarallel
|
|
16
|
+
Requires-Dist: jax
|
|
17
|
+
Requires-Dist: cython
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest; extra == "test"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# phylotypy
|
|
23
|
+
Naive Bayesian Classifier for 16S rRNA gene sequence data
|
|
24
|
+
|
|
25
|
+
Porting Riffomonas's CodeClub R package, phylotypr to python: https://github.com/riffomonas/phylotypr
|
|
26
|
+
|
|
27
|
+
It's been a great challenge learning how to interpret the R code into Python with minimal use of extra libraries.
|
|
28
|
+
|
|
29
|
+
It's best to clone the repository. Run vigentte.py to see if everything works.
|
|
30
|
+
|
|
31
|
+
Training the model with the full reference database from RDP takes about 30 seconds on my MacBook Pro.
|
|
32
|
+
|
|
33
|
+
You can modify the vignette at the end to classify your own sequences. I've done this using DADA2's output files.
|
|
34
|
+
|
|
35
|
+
There's also [read_fasta.py](src/phylotypy/utilities/read_fasta.py) that lets you take a fasta file of DNA seqences and process them into a dataframe for runing this classifier.
|
|
36
|
+
|
|
37
|
+
I made a separate vignette, [vignette.py](vignette.py) on how to do this and classify 16S sequence data from QIIME, DADA2, or text files.
|
|
38
|
+
|
|
39
|
+
Thanks Riffomonas for the inspiration. Check out the videos on his Youtube channel https://youtube.com/playlist?list=PLmNrK_nkqBpIZlWa3yGEc2-wX7An2kpCL&si=LmHDV02K5_wb6C0j
|
|
40
|
+
|
|
41
|
+
## How to install
|
|
42
|
+
```
|
|
43
|
+
pip install git+https://github.com/csaltikov/phylotypy.git
|
|
44
|
+
```
|
|
45
|
+
or if using uv (recommended) (<a href="https://docs.astral.sh/uv/getting-started/installation/">how to install uv</a>)
|
|
46
|
+
```
|
|
47
|
+
uv pip install git+https://github.com/csaltikov/phylotypy.git
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## How to get started:
|
|
51
|
+
First download the training data, RDP's trainset19072023, either from https://mothur.org/wiki/rdp_reference_files/
|
|
52
|
+
You can use the one in the [data](data/) directory called [rdp_16S_v19.dada2.fasta](data/rdp_16S_v19.dada2.fasta)
|
|
53
|
+
|
|
54
|
+
I processed the latest rdp reference data into a format that will work here and for DADA2.
|
|
55
|
+
|
|
56
|
+
The taxonomy string looks like this semicolon separated:
|
|
57
|
+
```
|
|
58
|
+
Bacteria;Phylum;Class;Order;Family;Genus
|
|
59
|
+
|
|
60
|
+
for example:
|
|
61
|
+
>Bacteria;Pseudomonadota;Gammaproteobacteria;Enterobacterales;Enterobacteriaceae;Citrobacter
|
|
62
|
+
TAGAGTTTGATCCATGGCTCAGATTGAACGCTGGCGGCAGGCCTAACAC.....
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
1. Load the training data and sequences to be classified
|
|
66
|
+
```
|
|
67
|
+
from pathlib import Path
|
|
68
|
+
from phylotypy import classifier, results, read_fasta
|
|
69
|
+
|
|
70
|
+
rdp = read_fasta.read_taxa_fasta("data/rdp_16S_v19.dada2.fasta")
|
|
71
|
+
moving_pics = read_fasta.read_taxa_fasta("data/dna_moving_pictures.fasta")
|
|
72
|
+
```
|
|
73
|
+
2. Create the classifier. We'll call it database
|
|
74
|
+
```
|
|
75
|
+
database = classifier.make_classifier(rdp)
|
|
76
|
+
```
|
|
77
|
+
3. Classify the sequences
|
|
78
|
+
```
|
|
79
|
+
classified = classifier.classify_sequences(moving_pics, database)
|
|
80
|
+
```
|
|
81
|
+
4. Format the output
|
|
82
|
+
```
|
|
83
|
+
classified = results.summarize_predictions(classified)
|
|
84
|
+
print(classified.columns)
|
|
85
|
+
```
|
|
86
|
+
Output:
|
|
87
|
+
```
|
|
88
|
+
>>> Index(['id', 'sequence', 'classification', 'Kingdom', 'Phylum', 'Class',
|
|
89
|
+
'Order', 'Family', 'Genus', 'observed', 'lineage'],
|
|
90
|
+
dtype='object')
|
|
91
|
+
```
|
|
92
|
+
```
|
|
93
|
+
print(classified["classification"].head())
|
|
94
|
+
```
|
|
95
|
+
Output:
|
|
96
|
+
```
|
|
97
|
+
0 Bacteria(100);Bacteroidota(100);Bacteroidia(10...
|
|
98
|
+
1 Bacteria(100);Pseudomonadota(100);Betaproteoba...
|
|
99
|
+
2 Bacteria(100);Bacillota(100);Bacilli(100);Lact...
|
|
100
|
+
3 Bacteria(100);Bacteroidota(100);Bacteroidia(10...
|
|
101
|
+
4 Bacteria(100);Bacteroidota(100);Bacteroidia(10...
|
|
102
|
+
Name: classification, dtype: object
|
|
103
|
+
```
|
|
104
|
+
Format the results using results.summarize_predictions() function.
|
|
105
|
+
The output is a pandas dataframe and can be saved to csv.
|
|
106
|
+
```
|
|
107
|
+
from phylotypy import results
|
|
108
|
+
classified = results.summarize_predictions(classified)
|
|
109
|
+
print(classified.head())
|
|
110
|
+
|
|
111
|
+
classified.to_csv("classified_results.csv")
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
## Example classification output:
|
|
115
|
+
The taxonomic levels "Domain", "Phylum", "Class", "Order", "Family", "Genus" are separated by ";". The numbers in the () represent the confidence in the classificaiton. The default confidence is 80%.
|
|
116
|
+
```
|
|
117
|
+
>>> Bacteria(100);Pseudomonadota(99);Alphaproteobacteria(99);Rhodospirillales(99);Acetobacteraceae(99);Roseomonas(83)
|
|
118
|
+
|
|
119
|
+
>>> Bacteria(99);Bacteroidota(97);Bacteroidia(93);Bacteroidales(93);Bacteroidales_unclassified(93);Bacteroidales_unclassified(93)
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
## Complete code block:
|
|
123
|
+
```
|
|
124
|
+
from pathlib import Path
|
|
125
|
+
from phylotypy import classifier, results, read_fasta
|
|
126
|
+
|
|
127
|
+
rdp = read_fasta.read_taxa_fasta("data/rdp_16S_v19.dada2.fasta")
|
|
128
|
+
moving_pics = read_fasta.read_taxa_fasta("data/dna_moving_pictures.fasta")
|
|
129
|
+
|
|
130
|
+
database = classifier.make_classifier(rdp)
|
|
131
|
+
|
|
132
|
+
classified = classifier.classify_sequences(moving_pics, database)
|
|
133
|
+
classified = results.summarize_predictions(classified)
|
|
134
|
+
print(classified.head())
|
|
135
|
+
```
|
|
136
|
+
### Requirements
|
|
137
|
+
setuptools
|
|
138
|
+
numpy
|
|
139
|
+
numba
|
|
140
|
+
pandas
|
|
141
|
+
requests
|
|
142
|
+
pandarallel
|
|
143
|
+
jax
|
|
144
|
+
cython
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# phylotypy
|
|
2
|
+
Naive Bayesian Classifier for 16S rRNA gene sequence data
|
|
3
|
+
|
|
4
|
+
Porting Riffomonas's CodeClub R package, phylotypr to python: https://github.com/riffomonas/phylotypr
|
|
5
|
+
|
|
6
|
+
It's been a great challenge learning how to interpret the R code into Python with minimal use of extra libraries.
|
|
7
|
+
|
|
8
|
+
It's best to clone the repository. Run vigentte.py to see if everything works.
|
|
9
|
+
|
|
10
|
+
Training the model with the full reference database from RDP takes about 30 seconds on my MacBook Pro.
|
|
11
|
+
|
|
12
|
+
You can modify the vignette at the end to classify your own sequences. I've done this using DADA2's output files.
|
|
13
|
+
|
|
14
|
+
There's also [read_fasta.py](src/phylotypy/utilities/read_fasta.py) that lets you take a fasta file of DNA seqences and process them into a dataframe for runing this classifier.
|
|
15
|
+
|
|
16
|
+
I made a separate vignette, [vignette.py](vignette.py) on how to do this and classify 16S sequence data from QIIME, DADA2, or text files.
|
|
17
|
+
|
|
18
|
+
Thanks Riffomonas for the inspiration. Check out the videos on his Youtube channel https://youtube.com/playlist?list=PLmNrK_nkqBpIZlWa3yGEc2-wX7An2kpCL&si=LmHDV02K5_wb6C0j
|
|
19
|
+
|
|
20
|
+
## How to install
|
|
21
|
+
```
|
|
22
|
+
pip install git+https://github.com/csaltikov/phylotypy.git
|
|
23
|
+
```
|
|
24
|
+
or if using uv (recommended) (<a href="https://docs.astral.sh/uv/getting-started/installation/">how to install uv</a>)
|
|
25
|
+
```
|
|
26
|
+
uv pip install git+https://github.com/csaltikov/phylotypy.git
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## How to get started:
|
|
30
|
+
First download the training data, RDP's trainset19072023, either from https://mothur.org/wiki/rdp_reference_files/
|
|
31
|
+
You can use the one in the [data](data/) directory called [rdp_16S_v19.dada2.fasta](data/rdp_16S_v19.dada2.fasta)
|
|
32
|
+
|
|
33
|
+
I processed the latest rdp reference data into a format that will work here and for DADA2.
|
|
34
|
+
|
|
35
|
+
The taxonomy string looks like this semicolon separated:
|
|
36
|
+
```
|
|
37
|
+
Bacteria;Phylum;Class;Order;Family;Genus
|
|
38
|
+
|
|
39
|
+
for example:
|
|
40
|
+
>Bacteria;Pseudomonadota;Gammaproteobacteria;Enterobacterales;Enterobacteriaceae;Citrobacter
|
|
41
|
+
TAGAGTTTGATCCATGGCTCAGATTGAACGCTGGCGGCAGGCCTAACAC.....
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
1. Load the training data and sequences to be classified
|
|
45
|
+
```
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
from phylotypy import classifier, results, read_fasta
|
|
48
|
+
|
|
49
|
+
rdp = read_fasta.read_taxa_fasta("data/rdp_16S_v19.dada2.fasta")
|
|
50
|
+
moving_pics = read_fasta.read_taxa_fasta("data/dna_moving_pictures.fasta")
|
|
51
|
+
```
|
|
52
|
+
2. Create the classifier. We'll call it database
|
|
53
|
+
```
|
|
54
|
+
database = classifier.make_classifier(rdp)
|
|
55
|
+
```
|
|
56
|
+
3. Classify the sequences
|
|
57
|
+
```
|
|
58
|
+
classified = classifier.classify_sequences(moving_pics, database)
|
|
59
|
+
```
|
|
60
|
+
4. Format the output
|
|
61
|
+
```
|
|
62
|
+
classified = results.summarize_predictions(classified)
|
|
63
|
+
print(classified.columns)
|
|
64
|
+
```
|
|
65
|
+
Output:
|
|
66
|
+
```
|
|
67
|
+
>>> Index(['id', 'sequence', 'classification', 'Kingdom', 'Phylum', 'Class',
|
|
68
|
+
'Order', 'Family', 'Genus', 'observed', 'lineage'],
|
|
69
|
+
dtype='object')
|
|
70
|
+
```
|
|
71
|
+
```
|
|
72
|
+
print(classified["classification"].head())
|
|
73
|
+
```
|
|
74
|
+
Output:
|
|
75
|
+
```
|
|
76
|
+
0 Bacteria(100);Bacteroidota(100);Bacteroidia(10...
|
|
77
|
+
1 Bacteria(100);Pseudomonadota(100);Betaproteoba...
|
|
78
|
+
2 Bacteria(100);Bacillota(100);Bacilli(100);Lact...
|
|
79
|
+
3 Bacteria(100);Bacteroidota(100);Bacteroidia(10...
|
|
80
|
+
4 Bacteria(100);Bacteroidota(100);Bacteroidia(10...
|
|
81
|
+
Name: classification, dtype: object
|
|
82
|
+
```
|
|
83
|
+
Format the results using results.summarize_predictions() function.
|
|
84
|
+
The output is a pandas dataframe and can be saved to csv.
|
|
85
|
+
```
|
|
86
|
+
from phylotypy import results
|
|
87
|
+
classified = results.summarize_predictions(classified)
|
|
88
|
+
print(classified.head())
|
|
89
|
+
|
|
90
|
+
classified.to_csv("classified_results.csv")
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
## Example classification output:
|
|
94
|
+
The taxonomic levels "Domain", "Phylum", "Class", "Order", "Family", "Genus" are separated by ";". The numbers in the () represent the confidence in the classificaiton. The default confidence is 80%.
|
|
95
|
+
```
|
|
96
|
+
>>> Bacteria(100);Pseudomonadota(99);Alphaproteobacteria(99);Rhodospirillales(99);Acetobacteraceae(99);Roseomonas(83)
|
|
97
|
+
|
|
98
|
+
>>> Bacteria(99);Bacteroidota(97);Bacteroidia(93);Bacteroidales(93);Bacteroidales_unclassified(93);Bacteroidales_unclassified(93)
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
## Complete code block:
|
|
102
|
+
```
|
|
103
|
+
from pathlib import Path
|
|
104
|
+
from phylotypy import classifier, results, read_fasta
|
|
105
|
+
|
|
106
|
+
rdp = read_fasta.read_taxa_fasta("data/rdp_16S_v19.dada2.fasta")
|
|
107
|
+
moving_pics = read_fasta.read_taxa_fasta("data/dna_moving_pictures.fasta")
|
|
108
|
+
|
|
109
|
+
database = classifier.make_classifier(rdp)
|
|
110
|
+
|
|
111
|
+
classified = classifier.classify_sequences(moving_pics, database)
|
|
112
|
+
classified = results.summarize_predictions(classified)
|
|
113
|
+
print(classified.head())
|
|
114
|
+
```
|
|
115
|
+
### Requirements
|
|
116
|
+
setuptools
|
|
117
|
+
numpy
|
|
118
|
+
numba
|
|
119
|
+
pandas
|
|
120
|
+
requests
|
|
121
|
+
pandarallel
|
|
122
|
+
jax
|
|
123
|
+
cython
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel", "Cython>=0.29.21", "numpy"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.cython]
|
|
6
|
+
language_level = "3"
|
|
7
|
+
|
|
8
|
+
[tool.setuptools]
|
|
9
|
+
include-package-data = true
|
|
10
|
+
|
|
11
|
+
[tool.setuptools.package-data]
|
|
12
|
+
"*" = ["*.pyx", "*.pxd"]
|
|
13
|
+
|
|
14
|
+
[project]
|
|
15
|
+
name = "phylotypy"
|
|
16
|
+
version = "0.2.0"
|
|
17
|
+
description = "Naive Bayes Classifier with Rust-accelerated taxonomy functions"
|
|
18
|
+
authors = [
|
|
19
|
+
{name = "Chad Saltikov", email = "saltikov@ucsc.edu"}
|
|
20
|
+
]
|
|
21
|
+
license = {text = "MIT"}
|
|
22
|
+
readme = "README.md"
|
|
23
|
+
requires-python = ">=3.11"
|
|
24
|
+
dependencies = [
|
|
25
|
+
"setuptools",
|
|
26
|
+
"numpy~=2.1.3",
|
|
27
|
+
"numba~=0.61.0",
|
|
28
|
+
"pandas~=2.2.3",
|
|
29
|
+
"requests",
|
|
30
|
+
"pandarallel",
|
|
31
|
+
"jax",
|
|
32
|
+
"cython"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
test = [
|
|
37
|
+
"pytest",
|
|
38
|
+
]
|
|
39
|
+
|
phylotypy-0.2.0/setup.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from setuptools import setup, Extension, find_packages
|
|
2
|
+
from Cython.Build import cythonize
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
extensions = [
|
|
7
|
+
Extension(
|
|
8
|
+
"phylotypy.classify_bootstraps.classify_bootstraps",
|
|
9
|
+
["src/phylotypy/classify_bootstraps/classify_bootstraps.pyx"],
|
|
10
|
+
include_dirs=[np.get_include()],
|
|
11
|
+
),
|
|
12
|
+
# New cond_prob_cython Cython extension
|
|
13
|
+
Extension(
|
|
14
|
+
"phylotypy.cond_prob_c.cond_prob_cython",
|
|
15
|
+
["src/phylotypy/cond_prob_c/cond_prob_cython.pyx"],
|
|
16
|
+
include_dirs=[np.get_include()],
|
|
17
|
+
extra_compile_args=['-O3'],
|
|
18
|
+
define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")]
|
|
19
|
+
)
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
setup(
|
|
23
|
+
name="phylotypy",
|
|
24
|
+
package_dir={"": "src"},
|
|
25
|
+
packages=find_packages(where="src"),
|
|
26
|
+
ext_modules=cythonize(
|
|
27
|
+
extensions,
|
|
28
|
+
compiler_directives={'language_level': "3"},
|
|
29
|
+
annotate=True),
|
|
30
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
from collections import Counter
|
|
5
|
+
from functools import partial
|
|
6
|
+
from itertools import repeat
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from numpy.typing import NDArray
|
|
11
|
+
|
|
12
|
+
from phylotypy import kmers, classifier
|
|
13
|
+
from phylotypy.utilities import read_fasta
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
def bootstrap(arr: list | np.ndarray, divider: int = 8, num_bs: int = 100) -> NDArray:
|
|
18
|
+
bootstrap_fn = partial(bootstrap_kmers, arr, divider)
|
|
19
|
+
return np.array(list(map(lambda _: bootstrap_fn(), repeat(1, num_bs))))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def bootstrap_kmers(kmer_arr: np.array, kmer_size: int = 8):
|
|
23
|
+
"""Performs a single bootstrap sampling on a kmers array"""
|
|
24
|
+
valid_kmers = kmer_arr[kmer_arr != -1]
|
|
25
|
+
num_removed_kmers = len(kmer_arr) - len(valid_kmers)
|
|
26
|
+
n_kmers = len(valid_kmers) + num_removed_kmers
|
|
27
|
+
return np.random.choice(valid_kmers, n_kmers // kmer_size, replace=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def split_taxa_arr(taxa_arr: np.ndarray) -> np.ndarray:
|
|
31
|
+
"""
|
|
32
|
+
Split array of taxonomy strings into a 2D array where each row is a
|
|
33
|
+
sample and each column is a taxonomic level.
|
|
34
|
+
"""
|
|
35
|
+
return np.array([taxa_str.split(";") for taxa_str in taxa_arr])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def bootstrap_consensus_helper(arr):
|
|
39
|
+
ids, scores = np.apply_along_axis(kmers.get_consensus, axis=0, arr=arr)
|
|
40
|
+
return ids, scores
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def sort_by_indices_helper(values_to_sort, indices_array):
|
|
44
|
+
sorted_indices = np.argsort(indices_array)
|
|
45
|
+
return values_to_sort[sorted_indices]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
def bootstrap_consensus(classified_bs_kmers: np.ndarray, genera_names: np.ndarray):
|
|
50
|
+
"""
|
|
51
|
+
Find consensus taxonomy from bootstrap samples.
|
|
52
|
+
|
|
53
|
+
Parameters:
|
|
54
|
+
classified_bs_kmers: 1D array of integers representing bootstrap samples
|
|
55
|
+
database: Database object containing genera_names mapping;
|
|
56
|
+
database = {conditional_prob:np.array
|
|
57
|
+
genera_idx mapping}
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dictionary with taxonomy consensus and confidence values
|
|
61
|
+
"""
|
|
62
|
+
# Convert the 1D array of ints (classified_bs_kmers) to taxonomy strings
|
|
63
|
+
# using genera_names 1D array
|
|
64
|
+
taxa = genera_names[classified_bs_kmers]
|
|
65
|
+
|
|
66
|
+
# Split into a 2D array of (100,6)
|
|
67
|
+
res = split_taxa_arr(taxa)
|
|
68
|
+
n_levels = res.shape[1]
|
|
69
|
+
|
|
70
|
+
taxa_consensus = np.empty(n_levels, dtype=object)
|
|
71
|
+
confidence_consensus = np.zeros(n_levels, dtype=int)
|
|
72
|
+
|
|
73
|
+
# Use Counter directly instead of apply_along_axis with custom function
|
|
74
|
+
for i in range(n_levels):
|
|
75
|
+
counter = Counter(res[:, i])
|
|
76
|
+
most_common = counter.most_common(1)[0] # Returns (taxon, count)
|
|
77
|
+
taxa_consensus[i] = most_common[0]
|
|
78
|
+
confidence_consensus[i] = most_common[1]
|
|
79
|
+
|
|
80
|
+
return dict(taxonomy=taxa_consensus, confidence=confidence_consensus)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
from time import perf_counter
|
|
86
|
+
|
|
87
|
+
path = Path(__file__).parent
|
|
88
|
+
rdp_small_fasta = path / "../../data/trainset19_072023_small_db.fasta"
|
|
89
|
+
|
|
90
|
+
moving_pics = read_fasta.read_taxa_fasta(path / "../../data/dna_moving_pictures.fasta")
|
|
91
|
+
rdp_df = read_fasta.read_taxa_fasta(rdp_small_fasta)
|
|
92
|
+
|
|
93
|
+
start = perf_counter()
|
|
94
|
+
database = classifier.make_classifier(rdp_df)
|
|
95
|
+
end = perf_counter()
|
|
96
|
+
print(f"Time taken: {(end - start):.2f}")
|
|
97
|
+
|
|
98
|
+
start = perf_counter()
|
|
99
|
+
database1 = kmers.build_kmer_database(rdp_df["sequence"], rdp_df["id"], verbose=True)
|
|
100
|
+
end = perf_counter()
|
|
101
|
+
print(f"Time taken: {(end - start):.2f}")
|
|
102
|
+
|
|
103
|
+
print(np.allclose(database.conditional_prob, database1.conditional_prob))
|
|
104
|
+
print(np.allclose(database.genera_idx, database1.genera_idx))
|
|
105
|
+
print(np.allclose(database.genera_names, database1.genera_names))
|