catomatic 0.1.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.
- catomatic-0.1.0/LICENSE +21 -0
- catomatic-0.1.0/PKG-INFO +119 -0
- catomatic-0.1.0/README.md +101 -0
- catomatic-0.1.0/catomatic.egg-info/PKG-INFO +119 -0
- catomatic-0.1.0/catomatic.egg-info/SOURCES.txt +13 -0
- catomatic-0.1.0/catomatic.egg-info/dependency_links.txt +1 -0
- catomatic-0.1.0/catomatic.egg-info/entry_points.txt +2 -0
- catomatic-0.1.0/catomatic.egg-info/not-zip-safe +1 -0
- catomatic-0.1.0/catomatic.egg-info/requires.txt +4 -0
- catomatic-0.1.0/catomatic.egg-info/top_level.txt +1 -0
- catomatic-0.1.0/pyproject.toml +3 -0
- catomatic-0.1.0/setup.cfg +35 -0
- catomatic-0.1.0/tests/__init__.py +0 -0
- catomatic-0.1.0/tests/test_BuildCatalogue.py +537 -0
catomatic-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Predicting AMR de novo
|
|
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.
|
catomatic-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: catomatic
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A tool for automatically building catalogues of antibiotic resistance-associated variants
|
|
5
|
+
Home-page: https://github.com/fowler-lab/catomatic
|
|
6
|
+
Author: Dylan Adlard, Philip W Fowler
|
|
7
|
+
Author-email: philip.fowler@ndm.ox.ac.uk
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: resistance catalogue,tuberculosis,clinical microbiology
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: piezo
|
|
15
|
+
Requires-Dist: numpy
|
|
16
|
+
Requires-Dist: scipy
|
|
17
|
+
Requires-Dist: pandas
|
|
18
|
+
|
|
19
|
+
[](https://codecov.io/gh/fowler-lab/catomatic)
|
|
20
|
+
|
|
21
|
+
# catomatic
|
|
22
|
+
|
|
23
|
+
Python code that algorithmically builds antimicrobial resistance catalogues of mutations.
|
|
24
|
+
|
|
25
|
+
## Introduction
|
|
26
|
+
|
|
27
|
+
This method relies on the logic that mutations that do not cause resistance can co-occur with those that do, and if a mutation in isolation (solo) does not cause resistance, then it will also not contribute to the phenotype when not in isolation.
|
|
28
|
+
|
|
29
|
+
Mutations that occur in isolation across specified genes are traversed in sequence, and if their proportion of drug-susceptibility (vs resistance) passes the specified statistical test, they are characterized as benign and removed from the dataset. This step repeats while there are susceptible mutations in isolation. Once the dataset has been 'cleaned' of benign mutations, resistant mutations are classified via their proportions by the specified test, failing which they are added to the catalogue as 'Unclassified'.
|
|
30
|
+
|
|
31
|
+
Construction can either rely on homogenous susceptibility for the particular mutation (and no explicit phenotyping is carried out, other than to unlock susceptible variants), use a Binomial test where the proportion of resistance is tested against a specified background rate, or a Fisher's test where the proportion of resistance is tested against a calculated background rate.
|
|
32
|
+
|
|
33
|
+
Although the method is entirely algorithmic, there are 2 entry points for intervention. Firstly, one is able to 'seed' the method with neutral mutations (such as those gathered in a literature search - often helpful if a gene contains phylogenetic mutations with high prevalence that add noise), and secondly one can add or overwrite classifications and entries to the catalogue, although not recommended unless aggregating.
|
|
34
|
+
|
|
35
|
+
Because the method uses and understands GARC1 grammar, one can supply 'rules' to the catalogue post-hoc - such as `{rpoB@*_fs:R}` for frameshifts in rpoB, which can either simply be added (and would have lower prediction priority to finer grain mutations, such as `rpoB@44_ins`) or can replace any mutations that fall under that rule, effectively aggregating relevant variants.
|
|
36
|
+
|
|
37
|
+
The generated catalogue can be returned either as a dictionary, or as a Pandas dataframe which can be exported in a Piezo compatible format for rapid parsing and resistance predictions.
|
|
38
|
+
|
|
39
|
+
Contingency tables, proportions, p_values, and Wilson's Confidence Intervals are logged under the 'EVIDENCE' column of the catalogue.
|
|
40
|
+
|
|
41
|
+
A workflow with example parameters:
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### Using Conda
|
|
48
|
+
|
|
49
|
+
It is recommended to manage the Python environment and dependencies through Conda. You can install Catomatic within a Conda environment by following these steps:
|
|
50
|
+
|
|
51
|
+
#### Create and Activate Environment
|
|
52
|
+
|
|
53
|
+
First, ensure that you have Conda installed. Then, create and activate a new environment, and install catomatic:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
conda env create -f env.yml
|
|
57
|
+
conda activate catomatic
|
|
58
|
+
pip install .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Running catomatic
|
|
62
|
+
|
|
63
|
+
At the most basic level, the method takes 2 input dataframes: a `samples dataframe` which contains 1 row per sample with 'R' vs 'S' binary phenotypes, and a `mutations dataframe` which contains 1 row per mutation. They have to be joinable on their `UNIQUEID` columns.
|
|
64
|
+
|
|
65
|
+
If exporting to Piezo format, the `MUTATION` column must contain GARC1 grammer (ie `gene@mutation`). One must also supply a path to the `wildcards.json` file, which should contain Piezo wildcards in a json object/dictionary (example file in `/data/bdq_wildcards.json`).
|
|
66
|
+
|
|
67
|
+
If seeding or updating the catalogue, the mutation grammar must match that of the `MUTATION` column.
|
|
68
|
+
|
|
69
|
+
### CLI
|
|
70
|
+
|
|
71
|
+
After installation, the simplest way to run the catomatic catalogue builder is via the command line interface. ` --to_piezo` or `--to_json ` flags will need to specified to save the catalogue (with additional arguments if using --to_piezo)
|
|
72
|
+
|
|
73
|
+
`BuildCatalogue --samples path/to/samples.csv --mutations path/to/mutations.csv --to_json --outfile path/to/out/catalogue.json`
|
|
74
|
+
|
|
75
|
+
or
|
|
76
|
+
|
|
77
|
+
`BuildCatalogue --samples path/to/samples.csv --mutations path/to/mutations.csv --to_piezo --outfile path/to/out/catalogue.csv --genbank_ref '...' --catalogue_name '...' --version '...' --drug '...' --wildcards path/to/wildcards.json`
|
|
78
|
+
|
|
79
|
+
### Python/Jupyter notebook
|
|
80
|
+
|
|
81
|
+
Should you wish to run catomatic in a notebook, for example, you can do so simply by calling BuildCatalogue after import.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from catomatic.CatalogueBuilder import BuildCatalogue
|
|
85
|
+
|
|
86
|
+
#instantiate a catalogue object - this will build the catalogue
|
|
87
|
+
catalogue = BuildCatalogue(samples = samples_df, mutations = mutations_df)
|
|
88
|
+
|
|
89
|
+
#return the catalogue as a dictionary in order of variant addition
|
|
90
|
+
catalogue.return_catalogue()
|
|
91
|
+
|
|
92
|
+
#return the catalogue as a piezo-structured dataframe
|
|
93
|
+
catalogue.build_piezo(genbank_ref='...', catalogue_name='...', version='...', drug='...', wildcards='path/to/wildcards.json')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
More detailed examples on running catomatic can be found in `examples/demo.ipynb`
|
|
97
|
+
|
|
98
|
+
### CLI Parameters
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Description |
|
|
101
|
+
| ------------------ | ------- | ------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `--samples` | `str` | Path to the samples file. Required. |
|
|
103
|
+
| `--mutations` | `str` | Path to the mutations file. Required. |
|
|
104
|
+
| `--FRS` | `float` | Fraction Read Support threshold. Optional. |
|
|
105
|
+
| `--seed` | `list` | List of seed mutations using GARC grammar. Optional. |
|
|
106
|
+
| `--test` | `str` | Type of statistical test to run: `None`, `Binomial`, or `Fisher`. Optional. |
|
|
107
|
+
| `--background` | `float` | Background mutation rate for the binomial test. Required if using test = Binomial. Optional. |
|
|
108
|
+
| `--p` | `float` | Significance level for statistical testing. Optional. Defaults to `0.95`. |
|
|
109
|
+
| `--strict_unlock` | `bool` | Enforce strict unlocking for classifications, which requires p < 0.05. Optional. |
|
|
110
|
+
| `--to_json` | `bool` | Export the catalogue to JSON format. Optional. |
|
|
111
|
+
| `--outfile` | `str` | Path to output file for exporting the catalogue. Used with `--to_json` or `--to_piezo`. Optional. |
|
|
112
|
+
| `--to_piezo` | `bool` | Export catalogue to Piezo format. Optional. |
|
|
113
|
+
| `--genbank_ref` | `str` | GenBank reference for the catalogue. Required if to_piezo = True. Optional. |
|
|
114
|
+
| `--catalogue_name` | `str` | Name of the catalogue. Required if to_piezo = True. Optional. |
|
|
115
|
+
| `--version` | `str` | Version of the catalogue. Required if to_piezo = True. Optional. |
|
|
116
|
+
| `--drug` | `str` | Drug associated with the mutations. Required if to_piezo = True. Optional. |
|
|
117
|
+
| `--wildcards` | `str` | JSON file with wildcard rules. Required if to_piezo = True. Optional. |
|
|
118
|
+
| `--grammar` | `str` | Grammar used in the catalogue. Optional. Defaults to `GARC1`. |
|
|
119
|
+
| `--values` | `str` | Values used for predictions in the catalogue. Optional. Defaults to `RUS`. |
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
[](https://codecov.io/gh/fowler-lab/catomatic)
|
|
2
|
+
|
|
3
|
+
# catomatic
|
|
4
|
+
|
|
5
|
+
Python code that algorithmically builds antimicrobial resistance catalogues of mutations.
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
This method relies on the logic that mutations that do not cause resistance can co-occur with those that do, and if a mutation in isolation (solo) does not cause resistance, then it will also not contribute to the phenotype when not in isolation.
|
|
10
|
+
|
|
11
|
+
Mutations that occur in isolation across specified genes are traversed in sequence, and if their proportion of drug-susceptibility (vs resistance) passes the specified statistical test, they are characterized as benign and removed from the dataset. This step repeats while there are susceptible mutations in isolation. Once the dataset has been 'cleaned' of benign mutations, resistant mutations are classified via their proportions by the specified test, failing which they are added to the catalogue as 'Unclassified'.
|
|
12
|
+
|
|
13
|
+
Construction can either rely on homogenous susceptibility for the particular mutation (and no explicit phenotyping is carried out, other than to unlock susceptible variants), use a Binomial test where the proportion of resistance is tested against a specified background rate, or a Fisher's test where the proportion of resistance is tested against a calculated background rate.
|
|
14
|
+
|
|
15
|
+
Although the method is entirely algorithmic, there are 2 entry points for intervention. Firstly, one is able to 'seed' the method with neutral mutations (such as those gathered in a literature search - often helpful if a gene contains phylogenetic mutations with high prevalence that add noise), and secondly one can add or overwrite classifications and entries to the catalogue, although not recommended unless aggregating.
|
|
16
|
+
|
|
17
|
+
Because the method uses and understands GARC1 grammar, one can supply 'rules' to the catalogue post-hoc - such as `{rpoB@*_fs:R}` for frameshifts in rpoB, which can either simply be added (and would have lower prediction priority to finer grain mutations, such as `rpoB@44_ins`) or can replace any mutations that fall under that rule, effectively aggregating relevant variants.
|
|
18
|
+
|
|
19
|
+
The generated catalogue can be returned either as a dictionary, or as a Pandas dataframe which can be exported in a Piezo compatible format for rapid parsing and resistance predictions.
|
|
20
|
+
|
|
21
|
+
Contingency tables, proportions, p_values, and Wilson's Confidence Intervals are logged under the 'EVIDENCE' column of the catalogue.
|
|
22
|
+
|
|
23
|
+
A workflow with example parameters:
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Using Conda
|
|
30
|
+
|
|
31
|
+
It is recommended to manage the Python environment and dependencies through Conda. You can install Catomatic within a Conda environment by following these steps:
|
|
32
|
+
|
|
33
|
+
#### Create and Activate Environment
|
|
34
|
+
|
|
35
|
+
First, ensure that you have Conda installed. Then, create and activate a new environment, and install catomatic:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
conda env create -f env.yml
|
|
39
|
+
conda activate catomatic
|
|
40
|
+
pip install .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Running catomatic
|
|
44
|
+
|
|
45
|
+
At the most basic level, the method takes 2 input dataframes: a `samples dataframe` which contains 1 row per sample with 'R' vs 'S' binary phenotypes, and a `mutations dataframe` which contains 1 row per mutation. They have to be joinable on their `UNIQUEID` columns.
|
|
46
|
+
|
|
47
|
+
If exporting to Piezo format, the `MUTATION` column must contain GARC1 grammer (ie `gene@mutation`). One must also supply a path to the `wildcards.json` file, which should contain Piezo wildcards in a json object/dictionary (example file in `/data/bdq_wildcards.json`).
|
|
48
|
+
|
|
49
|
+
If seeding or updating the catalogue, the mutation grammar must match that of the `MUTATION` column.
|
|
50
|
+
|
|
51
|
+
### CLI
|
|
52
|
+
|
|
53
|
+
After installation, the simplest way to run the catomatic catalogue builder is via the command line interface. ` --to_piezo` or `--to_json ` flags will need to specified to save the catalogue (with additional arguments if using --to_piezo)
|
|
54
|
+
|
|
55
|
+
`BuildCatalogue --samples path/to/samples.csv --mutations path/to/mutations.csv --to_json --outfile path/to/out/catalogue.json`
|
|
56
|
+
|
|
57
|
+
or
|
|
58
|
+
|
|
59
|
+
`BuildCatalogue --samples path/to/samples.csv --mutations path/to/mutations.csv --to_piezo --outfile path/to/out/catalogue.csv --genbank_ref '...' --catalogue_name '...' --version '...' --drug '...' --wildcards path/to/wildcards.json`
|
|
60
|
+
|
|
61
|
+
### Python/Jupyter notebook
|
|
62
|
+
|
|
63
|
+
Should you wish to run catomatic in a notebook, for example, you can do so simply by calling BuildCatalogue after import.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from catomatic.CatalogueBuilder import BuildCatalogue
|
|
67
|
+
|
|
68
|
+
#instantiate a catalogue object - this will build the catalogue
|
|
69
|
+
catalogue = BuildCatalogue(samples = samples_df, mutations = mutations_df)
|
|
70
|
+
|
|
71
|
+
#return the catalogue as a dictionary in order of variant addition
|
|
72
|
+
catalogue.return_catalogue()
|
|
73
|
+
|
|
74
|
+
#return the catalogue as a piezo-structured dataframe
|
|
75
|
+
catalogue.build_piezo(genbank_ref='...', catalogue_name='...', version='...', drug='...', wildcards='path/to/wildcards.json')
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
More detailed examples on running catomatic can be found in `examples/demo.ipynb`
|
|
79
|
+
|
|
80
|
+
### CLI Parameters
|
|
81
|
+
|
|
82
|
+
| Parameter | Type | Description |
|
|
83
|
+
| ------------------ | ------- | ------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `--samples` | `str` | Path to the samples file. Required. |
|
|
85
|
+
| `--mutations` | `str` | Path to the mutations file. Required. |
|
|
86
|
+
| `--FRS` | `float` | Fraction Read Support threshold. Optional. |
|
|
87
|
+
| `--seed` | `list` | List of seed mutations using GARC grammar. Optional. |
|
|
88
|
+
| `--test` | `str` | Type of statistical test to run: `None`, `Binomial`, or `Fisher`. Optional. |
|
|
89
|
+
| `--background` | `float` | Background mutation rate for the binomial test. Required if using test = Binomial. Optional. |
|
|
90
|
+
| `--p` | `float` | Significance level for statistical testing. Optional. Defaults to `0.95`. |
|
|
91
|
+
| `--strict_unlock` | `bool` | Enforce strict unlocking for classifications, which requires p < 0.05. Optional. |
|
|
92
|
+
| `--to_json` | `bool` | Export the catalogue to JSON format. Optional. |
|
|
93
|
+
| `--outfile` | `str` | Path to output file for exporting the catalogue. Used with `--to_json` or `--to_piezo`. Optional. |
|
|
94
|
+
| `--to_piezo` | `bool` | Export catalogue to Piezo format. Optional. |
|
|
95
|
+
| `--genbank_ref` | `str` | GenBank reference for the catalogue. Required if to_piezo = True. Optional. |
|
|
96
|
+
| `--catalogue_name` | `str` | Name of the catalogue. Required if to_piezo = True. Optional. |
|
|
97
|
+
| `--version` | `str` | Version of the catalogue. Required if to_piezo = True. Optional. |
|
|
98
|
+
| `--drug` | `str` | Drug associated with the mutations. Required if to_piezo = True. Optional. |
|
|
99
|
+
| `--wildcards` | `str` | JSON file with wildcard rules. Required if to_piezo = True. Optional. |
|
|
100
|
+
| `--grammar` | `str` | Grammar used in the catalogue. Optional. Defaults to `GARC1`. |
|
|
101
|
+
| `--values` | `str` | Values used for predictions in the catalogue. Optional. Defaults to `RUS`. |
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: catomatic
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A tool for automatically building catalogues of antibiotic resistance-associated variants
|
|
5
|
+
Home-page: https://github.com/fowler-lab/catomatic
|
|
6
|
+
Author: Dylan Adlard, Philip W Fowler
|
|
7
|
+
Author-email: philip.fowler@ndm.ox.ac.uk
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: resistance catalogue,tuberculosis,clinical microbiology
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: piezo
|
|
15
|
+
Requires-Dist: numpy
|
|
16
|
+
Requires-Dist: scipy
|
|
17
|
+
Requires-Dist: pandas
|
|
18
|
+
|
|
19
|
+
[](https://codecov.io/gh/fowler-lab/catomatic)
|
|
20
|
+
|
|
21
|
+
# catomatic
|
|
22
|
+
|
|
23
|
+
Python code that algorithmically builds antimicrobial resistance catalogues of mutations.
|
|
24
|
+
|
|
25
|
+
## Introduction
|
|
26
|
+
|
|
27
|
+
This method relies on the logic that mutations that do not cause resistance can co-occur with those that do, and if a mutation in isolation (solo) does not cause resistance, then it will also not contribute to the phenotype when not in isolation.
|
|
28
|
+
|
|
29
|
+
Mutations that occur in isolation across specified genes are traversed in sequence, and if their proportion of drug-susceptibility (vs resistance) passes the specified statistical test, they are characterized as benign and removed from the dataset. This step repeats while there are susceptible mutations in isolation. Once the dataset has been 'cleaned' of benign mutations, resistant mutations are classified via their proportions by the specified test, failing which they are added to the catalogue as 'Unclassified'.
|
|
30
|
+
|
|
31
|
+
Construction can either rely on homogenous susceptibility for the particular mutation (and no explicit phenotyping is carried out, other than to unlock susceptible variants), use a Binomial test where the proportion of resistance is tested against a specified background rate, or a Fisher's test where the proportion of resistance is tested against a calculated background rate.
|
|
32
|
+
|
|
33
|
+
Although the method is entirely algorithmic, there are 2 entry points for intervention. Firstly, one is able to 'seed' the method with neutral mutations (such as those gathered in a literature search - often helpful if a gene contains phylogenetic mutations with high prevalence that add noise), and secondly one can add or overwrite classifications and entries to the catalogue, although not recommended unless aggregating.
|
|
34
|
+
|
|
35
|
+
Because the method uses and understands GARC1 grammar, one can supply 'rules' to the catalogue post-hoc - such as `{rpoB@*_fs:R}` for frameshifts in rpoB, which can either simply be added (and would have lower prediction priority to finer grain mutations, such as `rpoB@44_ins`) or can replace any mutations that fall under that rule, effectively aggregating relevant variants.
|
|
36
|
+
|
|
37
|
+
The generated catalogue can be returned either as a dictionary, or as a Pandas dataframe which can be exported in a Piezo compatible format for rapid parsing and resistance predictions.
|
|
38
|
+
|
|
39
|
+
Contingency tables, proportions, p_values, and Wilson's Confidence Intervals are logged under the 'EVIDENCE' column of the catalogue.
|
|
40
|
+
|
|
41
|
+
A workflow with example parameters:
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### Using Conda
|
|
48
|
+
|
|
49
|
+
It is recommended to manage the Python environment and dependencies through Conda. You can install Catomatic within a Conda environment by following these steps:
|
|
50
|
+
|
|
51
|
+
#### Create and Activate Environment
|
|
52
|
+
|
|
53
|
+
First, ensure that you have Conda installed. Then, create and activate a new environment, and install catomatic:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
conda env create -f env.yml
|
|
57
|
+
conda activate catomatic
|
|
58
|
+
pip install .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Running catomatic
|
|
62
|
+
|
|
63
|
+
At the most basic level, the method takes 2 input dataframes: a `samples dataframe` which contains 1 row per sample with 'R' vs 'S' binary phenotypes, and a `mutations dataframe` which contains 1 row per mutation. They have to be joinable on their `UNIQUEID` columns.
|
|
64
|
+
|
|
65
|
+
If exporting to Piezo format, the `MUTATION` column must contain GARC1 grammer (ie `gene@mutation`). One must also supply a path to the `wildcards.json` file, which should contain Piezo wildcards in a json object/dictionary (example file in `/data/bdq_wildcards.json`).
|
|
66
|
+
|
|
67
|
+
If seeding or updating the catalogue, the mutation grammar must match that of the `MUTATION` column.
|
|
68
|
+
|
|
69
|
+
### CLI
|
|
70
|
+
|
|
71
|
+
After installation, the simplest way to run the catomatic catalogue builder is via the command line interface. ` --to_piezo` or `--to_json ` flags will need to specified to save the catalogue (with additional arguments if using --to_piezo)
|
|
72
|
+
|
|
73
|
+
`BuildCatalogue --samples path/to/samples.csv --mutations path/to/mutations.csv --to_json --outfile path/to/out/catalogue.json`
|
|
74
|
+
|
|
75
|
+
or
|
|
76
|
+
|
|
77
|
+
`BuildCatalogue --samples path/to/samples.csv --mutations path/to/mutations.csv --to_piezo --outfile path/to/out/catalogue.csv --genbank_ref '...' --catalogue_name '...' --version '...' --drug '...' --wildcards path/to/wildcards.json`
|
|
78
|
+
|
|
79
|
+
### Python/Jupyter notebook
|
|
80
|
+
|
|
81
|
+
Should you wish to run catomatic in a notebook, for example, you can do so simply by calling BuildCatalogue after import.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from catomatic.CatalogueBuilder import BuildCatalogue
|
|
85
|
+
|
|
86
|
+
#instantiate a catalogue object - this will build the catalogue
|
|
87
|
+
catalogue = BuildCatalogue(samples = samples_df, mutations = mutations_df)
|
|
88
|
+
|
|
89
|
+
#return the catalogue as a dictionary in order of variant addition
|
|
90
|
+
catalogue.return_catalogue()
|
|
91
|
+
|
|
92
|
+
#return the catalogue as a piezo-structured dataframe
|
|
93
|
+
catalogue.build_piezo(genbank_ref='...', catalogue_name='...', version='...', drug='...', wildcards='path/to/wildcards.json')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
More detailed examples on running catomatic can be found in `examples/demo.ipynb`
|
|
97
|
+
|
|
98
|
+
### CLI Parameters
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Description |
|
|
101
|
+
| ------------------ | ------- | ------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `--samples` | `str` | Path to the samples file. Required. |
|
|
103
|
+
| `--mutations` | `str` | Path to the mutations file. Required. |
|
|
104
|
+
| `--FRS` | `float` | Fraction Read Support threshold. Optional. |
|
|
105
|
+
| `--seed` | `list` | List of seed mutations using GARC grammar. Optional. |
|
|
106
|
+
| `--test` | `str` | Type of statistical test to run: `None`, `Binomial`, or `Fisher`. Optional. |
|
|
107
|
+
| `--background` | `float` | Background mutation rate for the binomial test. Required if using test = Binomial. Optional. |
|
|
108
|
+
| `--p` | `float` | Significance level for statistical testing. Optional. Defaults to `0.95`. |
|
|
109
|
+
| `--strict_unlock` | `bool` | Enforce strict unlocking for classifications, which requires p < 0.05. Optional. |
|
|
110
|
+
| `--to_json` | `bool` | Export the catalogue to JSON format. Optional. |
|
|
111
|
+
| `--outfile` | `str` | Path to output file for exporting the catalogue. Used with `--to_json` or `--to_piezo`. Optional. |
|
|
112
|
+
| `--to_piezo` | `bool` | Export catalogue to Piezo format. Optional. |
|
|
113
|
+
| `--genbank_ref` | `str` | GenBank reference for the catalogue. Required if to_piezo = True. Optional. |
|
|
114
|
+
| `--catalogue_name` | `str` | Name of the catalogue. Required if to_piezo = True. Optional. |
|
|
115
|
+
| `--version` | `str` | Version of the catalogue. Required if to_piezo = True. Optional. |
|
|
116
|
+
| `--drug` | `str` | Drug associated with the mutations. Required if to_piezo = True. Optional. |
|
|
117
|
+
| `--wildcards` | `str` | JSON file with wildcard rules. Required if to_piezo = True. Optional. |
|
|
118
|
+
| `--grammar` | `str` | Grammar used in the catalogue. Optional. Defaults to `GARC1`. |
|
|
119
|
+
| `--values` | `str` | Values used for predictions in the catalogue. Optional. Defaults to `RUS`. |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.cfg
|
|
5
|
+
catomatic.egg-info/PKG-INFO
|
|
6
|
+
catomatic.egg-info/SOURCES.txt
|
|
7
|
+
catomatic.egg-info/dependency_links.txt
|
|
8
|
+
catomatic.egg-info/entry_points.txt
|
|
9
|
+
catomatic.egg-info/not-zip-safe
|
|
10
|
+
catomatic.egg-info/requires.txt
|
|
11
|
+
catomatic.egg-info/top_level.txt
|
|
12
|
+
tests/__init__.py
|
|
13
|
+
tests/test_BuildCatalogue.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tests
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = catomatic
|
|
3
|
+
version = 0.1.0
|
|
4
|
+
author = Dylan Adlard, Philip W Fowler
|
|
5
|
+
author_email = philip.fowler@ndm.ox.ac.uk
|
|
6
|
+
description = A tool for automatically building catalogues of antibiotic resistance-associated variants
|
|
7
|
+
long_description = file: README.md
|
|
8
|
+
long_description_content_type = text/markdown
|
|
9
|
+
url = https://github.com/fowler-lab/catomatic
|
|
10
|
+
keywords = resistance catalogue, tuberculosis, clinical microbiology
|
|
11
|
+
license = MIT
|
|
12
|
+
classifiers = Programming Language :: Python :: 3
|
|
13
|
+
|
|
14
|
+
[options]
|
|
15
|
+
packages = find:
|
|
16
|
+
python_requires = >=3.6
|
|
17
|
+
install_requires =
|
|
18
|
+
piezo
|
|
19
|
+
numpy
|
|
20
|
+
scipy
|
|
21
|
+
pandas
|
|
22
|
+
zip_safe = False
|
|
23
|
+
include_package_data = True
|
|
24
|
+
|
|
25
|
+
[options.entry_points]
|
|
26
|
+
console_scripts =
|
|
27
|
+
BuildCatalogue = catomatic.CatalogueBuilder:main
|
|
28
|
+
|
|
29
|
+
[options.package_data]
|
|
30
|
+
mypkg = data/*.csv, data/*.json
|
|
31
|
+
|
|
32
|
+
[egg_info]
|
|
33
|
+
tag_build =
|
|
34
|
+
tag_date = 0
|
|
35
|
+
|
|
File without changes
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import pytest
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from catomatic.CatalogueBuilder import BuildCatalogue
|
|
7
|
+
from scipy.stats import norm, binomtest, fisher_exact
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# a left join of phenotypes and mutations will give:
|
|
12
|
+
"""
|
|
13
|
+
UNIQUEID PHENOTYPE MUTATION
|
|
14
|
+
0 1 R gene@A1S
|
|
15
|
+
1 2 S gene@A1S
|
|
16
|
+
2 3 S NaN
|
|
17
|
+
3 4 S gene@A2S
|
|
18
|
+
4 5 R NaN
|
|
19
|
+
5 6 S NaN
|
|
20
|
+
6 7 S gene@A3S
|
|
21
|
+
7 8 S gene@A3S
|
|
22
|
+
8 9 R gene@A3S
|
|
23
|
+
9 10 R NaN
|
|
24
|
+
"""
|
|
25
|
+
# this should allow for pretty robust statistical condition testing
|
|
26
|
+
# have pushed background very R to test fisher S classifications
|
|
27
|
+
# the only function that uses the backgrounds is test_fisher_build_S (R/U uses explicit tables)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def mutation_data():
|
|
32
|
+
return pd.DataFrame(
|
|
33
|
+
{
|
|
34
|
+
"UNIQUEID": [1, 2, 4, 7, 8, 9],
|
|
35
|
+
"MUTATION": [
|
|
36
|
+
"gene@A1S",
|
|
37
|
+
"gene@A1S",
|
|
38
|
+
"gene@A2S",
|
|
39
|
+
"gene@A3S",
|
|
40
|
+
"gene@A3S",
|
|
41
|
+
"gene@A3S",
|
|
42
|
+
],
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def phenotype_data():
|
|
49
|
+
return pd.DataFrame(
|
|
50
|
+
{
|
|
51
|
+
"UNIQUEID": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
52
|
+
"PHENOTYPE": ["R", "S", "R", "S", "R", "S", "S", "S", "R", "R"],
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def phenotypes_file(phenotype_data, tmp_path):
|
|
59
|
+
file = tmp_path / "phenotypes.csv"
|
|
60
|
+
phenotype_data.to_csv(file, index=False)
|
|
61
|
+
return str(file)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def mutations_file(mutation_data, tmp_path):
|
|
66
|
+
file = tmp_path / "mutations.csv"
|
|
67
|
+
mutation_data.to_csv(file, index=False)
|
|
68
|
+
return str(file)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.fixture
|
|
72
|
+
def output_file(tmp_path):
|
|
73
|
+
return str(tmp_path / "output.json")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.fixture
|
|
77
|
+
def solo_data(phenotype_data, mutation_data):
|
|
78
|
+
return pd.merge(phenotype_data, mutation_data, on=["UNIQUEID"], how="left")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
def wildcards():
|
|
83
|
+
return {"gene@-*?": {"pred": "U", "evid": {}}, "gene@*=": {"pred": "S", "evid": {}}}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.fixture
|
|
87
|
+
def builder(request, phenotype_data, mutation_data):
|
|
88
|
+
# default parameters
|
|
89
|
+
test_type = None
|
|
90
|
+
background = 0.1
|
|
91
|
+
p = 0.95
|
|
92
|
+
strict_unlock = False
|
|
93
|
+
|
|
94
|
+
# check if the test that's calling this fixture is parametrized
|
|
95
|
+
if hasattr(request, "param"):
|
|
96
|
+
# override defaults
|
|
97
|
+
test_type = request.param.get("test", test_type)
|
|
98
|
+
background = request.param.get("background", background)
|
|
99
|
+
p = request.param.get("p", p)
|
|
100
|
+
strict_unlock = request.param.get("strict_unlock", strict_unlock)
|
|
101
|
+
|
|
102
|
+
return BuildCatalogue(
|
|
103
|
+
phenotype_data,
|
|
104
|
+
mutation_data,
|
|
105
|
+
p=p,
|
|
106
|
+
background=background,
|
|
107
|
+
strict_unlock=strict_unlock,
|
|
108
|
+
test=test_type,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_add_mutation(builder):
|
|
113
|
+
# using default parameterisation
|
|
114
|
+
|
|
115
|
+
mutation = "mut4"
|
|
116
|
+
prediction = "R"
|
|
117
|
+
evidence = {"detail": "test"}
|
|
118
|
+
|
|
119
|
+
builder.add_mutation(mutation, prediction, evidence)
|
|
120
|
+
assert mutation in builder.catalogue
|
|
121
|
+
assert builder.catalogue[mutation]["pred"] == prediction
|
|
122
|
+
assert builder.catalogue[mutation]["evid"] == evidence
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_build_contingency(solo_data):
|
|
126
|
+
# for R + S
|
|
127
|
+
x_mut1 = BuildCatalogue.build_contingency(solo_data, "gene@A1S")
|
|
128
|
+
assert x_mut1 == [[1, 1], [3, 1]]
|
|
129
|
+
# for R
|
|
130
|
+
x_mut2 = BuildCatalogue.build_contingency(solo_data, "gene@A2S")
|
|
131
|
+
assert x_mut2 == [[0, 1], [3, 1]]
|
|
132
|
+
# For non-existent mutation - although should be filtered out beforehand
|
|
133
|
+
x_mut4 = BuildCatalogue.build_contingency(solo_data, "gene@A4S")
|
|
134
|
+
assert x_mut4 == [[0, 0], [3, 1]]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_calc_proportion():
|
|
138
|
+
x_tests = [
|
|
139
|
+
([[10, 5], [3, 7]], 10 / (10 + 5)),
|
|
140
|
+
([[20, 0], [5, 5]], 1),
|
|
141
|
+
([[0, 30], [5, 5]], 0),
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
for x, expected in x_tests:
|
|
145
|
+
assert (
|
|
146
|
+
BuildCatalogue.calc_proportion(x) == expected
|
|
147
|
+
), f"Failed for contingency {x}"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_calc_oddsRatio():
|
|
151
|
+
x_tests = [
|
|
152
|
+
([[10, 5], [3, 7]], 45 / 11),
|
|
153
|
+
([[20, 0], [5, 5]], 41),
|
|
154
|
+
([[0, 30], [5, 5]], 1 / 61),
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
for x, expected in x_tests:
|
|
158
|
+
assert (
|
|
159
|
+
BuildCatalogue.calc_oddsRatio(x) == expected
|
|
160
|
+
), f"Failed for contingency {x}"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@pytest.mark.parametrize("builder", [{"p": 0.95}], indirect=True)
|
|
164
|
+
def test_calc_confidenceInterval(builder):
|
|
165
|
+
|
|
166
|
+
x_tests = [
|
|
167
|
+
([[10, 5], [3, 7]], [0.4171, 0.8482]),
|
|
168
|
+
([[20, 0], [5, 5]], [0.8389, 1]),
|
|
169
|
+
([[0, 30], [5, 5]], [0, 0.1135]),
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
for x, expected in x_tests:
|
|
173
|
+
ci = builder.calc_confidenceInterval(x)
|
|
174
|
+
ci = [round(ci[0], 4), round(ci[1], 4)]
|
|
175
|
+
|
|
176
|
+
assert ci == expected, f"Failed for contingency {x}"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.mark.parametrize("builder", [{"p": 0.95}], indirect=True)
|
|
180
|
+
def test_skeleton_build(builder):
|
|
181
|
+
# uses default parameters (ie test=None), which should fire the skeleton build
|
|
182
|
+
|
|
183
|
+
# now test addition when run_iter = False
|
|
184
|
+
mutation = "mut4"
|
|
185
|
+
x = [[27, 21], [3, 22]]
|
|
186
|
+
|
|
187
|
+
builder.skeleton_build(mutation, x)
|
|
188
|
+
# check its actually been added
|
|
189
|
+
assert mutation in builder.catalogue
|
|
190
|
+
# check if the correct phenotype has been added
|
|
191
|
+
assert builder.catalogue[mutation]["pred"] == 'U'
|
|
192
|
+
|
|
193
|
+
expected_e = {
|
|
194
|
+
"proportion": builder.calc_proportion(x),
|
|
195
|
+
"confidence": builder.calc_confidenceInterval(x),
|
|
196
|
+
"contingency": x,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
assert builder.catalogue[mutation]["evid"][0] == expected_e
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@pytest.mark.parametrize(
|
|
203
|
+
"builder",
|
|
204
|
+
[{"test": "Binomial", "background": 0.1, "strict_unlock": False, "p": 0.95}],
|
|
205
|
+
indirect=True,
|
|
206
|
+
)
|
|
207
|
+
def test_binomial_build_RU(builder):
|
|
208
|
+
"""tests when run_iter = False (ie, classiyfing R and U variants)"""
|
|
209
|
+
|
|
210
|
+
# now test additions when run_iter = False
|
|
211
|
+
mutations = ["mut4", "mut5"]
|
|
212
|
+
data_tests = [
|
|
213
|
+
([[27, 21], [3, 22]], "R"),
|
|
214
|
+
([[5, 21], [3, 22]], "U"),
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
for mut in range(len(mutations)):
|
|
218
|
+
mutation = mutations[mut]
|
|
219
|
+
phenotype = data_tests[mut][1]
|
|
220
|
+
x = data_tests[mut][0]
|
|
221
|
+
|
|
222
|
+
p_expected = binomtest(
|
|
223
|
+
x[0][0], (x[0][0] + x[0][1]), 0.1, alternative="two-sided"
|
|
224
|
+
).pvalue
|
|
225
|
+
|
|
226
|
+
expected_e = {
|
|
227
|
+
"proportion": builder.calc_proportion(x),
|
|
228
|
+
"confidence": builder.calc_confidenceInterval(x),
|
|
229
|
+
"p_value": p_expected,
|
|
230
|
+
"contingency": x,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
builder.binomial_build(mutation, x)
|
|
234
|
+
# make sure mutaiton was added
|
|
235
|
+
assert mutation in builder.catalogue, f"Failed for contingency {x}"
|
|
236
|
+
# make sure phenotype was determined correclty
|
|
237
|
+
assert (
|
|
238
|
+
builder.catalogue[mutation]["pred"] == phenotype
|
|
239
|
+
), f"Failed for contingency {x}"
|
|
240
|
+
# make sure evidence was added correclty
|
|
241
|
+
assert (
|
|
242
|
+
builder.catalogue[mutation]["evid"][0] == expected_e
|
|
243
|
+
), f"Failed for contingency {x}"
|
|
244
|
+
|
|
245
|
+
# fyi haven't tested entire decision tree for run_iter=True, but they're are almost identical
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@pytest.mark.parametrize(
|
|
249
|
+
"builder",
|
|
250
|
+
[{"test": "Fisher", "strict_unlock": False, "p": 0.95}],
|
|
251
|
+
indirect=True,
|
|
252
|
+
)
|
|
253
|
+
def test_fisher_build_RU(builder):
|
|
254
|
+
"""tests when run_iter = False (ie classifying R and U variants)"""
|
|
255
|
+
|
|
256
|
+
# now test additions when run_iter = False
|
|
257
|
+
mutations = ["mut4", "mut5"]
|
|
258
|
+
data_tests = [
|
|
259
|
+
([[27, 21], [3, 22]], "R"),
|
|
260
|
+
([[5, 21], [3, 22]], "U"),
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
for mut in range(len(mutations)):
|
|
264
|
+
mutation = mutations[mut]
|
|
265
|
+
phenotype = data_tests[mut][1]
|
|
266
|
+
x = data_tests[mut][0]
|
|
267
|
+
|
|
268
|
+
_, p_expected = fisher_exact(x)
|
|
269
|
+
|
|
270
|
+
expected_e = {
|
|
271
|
+
"proportion": builder.calc_proportion(x),
|
|
272
|
+
"confidence": builder.calc_confidenceInterval(x),
|
|
273
|
+
"p_value": p_expected,
|
|
274
|
+
"contingency": x,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
builder.fishers_build(mutation, x)
|
|
278
|
+
# make sure mutaiton was added
|
|
279
|
+
assert mutation in builder.catalogue, f"Failed for contingency {x}"
|
|
280
|
+
# make sure phenotype was determined correclty
|
|
281
|
+
assert (
|
|
282
|
+
builder.catalogue[mutation]["pred"] == phenotype
|
|
283
|
+
), f"Failed for contingency {x}"
|
|
284
|
+
# make sure evidence was added correclty
|
|
285
|
+
assert (
|
|
286
|
+
builder.catalogue[mutation]["evid"][0] == expected_e
|
|
287
|
+
), f"Failed for contingency {x}"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# the S tests below are conceptually more complex than the RU tests, as these use the
|
|
291
|
+
# instantiation data, and therefore implicitly also test the classify function
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@pytest.mark.parametrize(
|
|
295
|
+
"builder",
|
|
296
|
+
[{"test": "Binomial", "background": 0.9, "strict_unlock": False, "p": 0.95}],
|
|
297
|
+
indirect=True,
|
|
298
|
+
)
|
|
299
|
+
def test_binomial_build_S(builder):
|
|
300
|
+
# test proportion 0 (not strict) + p_calc<self.p (added and not added)
|
|
301
|
+
# using background = 0.9 to force it without needing tonnes of data
|
|
302
|
+
# havent tested propotion 0 (strict) but i think its fine
|
|
303
|
+
|
|
304
|
+
s_entries = {
|
|
305
|
+
key: val for key, val in builder.catalogue.items() if val["pred"] == "S"
|
|
306
|
+
}
|
|
307
|
+
# check only mut2 and mut3 were added
|
|
308
|
+
assert "gene@A1S" not in s_entries
|
|
309
|
+
assert "gene@A2S" in s_entries
|
|
310
|
+
assert "gene@A3S" in s_entries
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@pytest.mark.parametrize(
|
|
314
|
+
"builder",
|
|
315
|
+
[{"test": "Fisher", "strict_unlock": False, "p": 0.5}],
|
|
316
|
+
indirect=True,
|
|
317
|
+
)
|
|
318
|
+
def test_fisher_build_S(builder):
|
|
319
|
+
# test proportion 0 (not strict) + p_calc<self.p (added and not added)
|
|
320
|
+
# using bp value 0.5 and very high R background to force S classifications
|
|
321
|
+
# havent tested propotion 0 (strict) but i think its fine
|
|
322
|
+
|
|
323
|
+
s_entries = {
|
|
324
|
+
key: val for key, val in builder.catalogue.items() if val["pred"] == "S"
|
|
325
|
+
}
|
|
326
|
+
# check only mut2 and mut3 were added
|
|
327
|
+
assert "gene@A1S" not in s_entries
|
|
328
|
+
assert "gene@A2S" in s_entries
|
|
329
|
+
assert "gene@A3S" in s_entries
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@pytest.mark.parametrize("builder", [{"p": 0.95}], indirect=True)
|
|
333
|
+
def test_classify(builder):
|
|
334
|
+
# this a bit redundant as the test_S functions above inplicity test the classify function
|
|
335
|
+
|
|
336
|
+
# check that once there is 1 susceptible entry in the catalogue (gene@A1S), run_iter switches to false
|
|
337
|
+
s_entries = {
|
|
338
|
+
key: val for key, val in builder.catalogue.items() if val["pred"] == "S"
|
|
339
|
+
}
|
|
340
|
+
assert len(s_entries) == 1
|
|
341
|
+
assert "gene@A2S" in s_entries.keys()
|
|
342
|
+
assert builder.run_iter == False
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@pytest.mark.parametrize("builder", [{"p": 0.95}], indirect=True)
|
|
346
|
+
def test_update(builder, wildcards):
|
|
347
|
+
|
|
348
|
+
# check addition to the catalogue with replacement
|
|
349
|
+
assert builder.catalogue["gene@A1S"]["pred"] == 'U'
|
|
350
|
+
builder.update({"gene@A1S": "R"})
|
|
351
|
+
assert builder.catalogue["gene@A1S"]["pred"] == "R"
|
|
352
|
+
# check addition to the catalogue with wildcard and replacement
|
|
353
|
+
builder.update({"gene@*?": "S"}, wildcards, replace=True)
|
|
354
|
+
assert builder.catalogue["gene@*?"]["pred"] == "S"
|
|
355
|
+
assert "gene@A1S" not in builder.catalogue.keys()
|
|
356
|
+
print(builder.catalogue)
|
|
357
|
+
# check addition to the catalogue without replacement
|
|
358
|
+
builder.update({"gene@A5S": "R"}, wildcards, replace=False)
|
|
359
|
+
assert builder.catalogue["gene@A5S"]["pred"] == "R"
|
|
360
|
+
assert builder.catalogue["gene@*?"]["pred"] == "S"
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@pytest.mark.parametrize("builder", [{"p": 0.95}], indirect=True)
|
|
364
|
+
def test_build_piezo(builder, wildcards):
|
|
365
|
+
# build a piezo compitable catalogue df
|
|
366
|
+
catalogue = builder.build_piezo(
|
|
367
|
+
"genbank", "test", "1", "drug", wildcards, grammar="TEST", values="SUR"
|
|
368
|
+
)
|
|
369
|
+
# test basic argument additions to catalogue df
|
|
370
|
+
assert catalogue.GENBANK_REFERENCE[0] == "genbank"
|
|
371
|
+
assert catalogue.CATALOGUE_NAME[0] == "test"
|
|
372
|
+
assert catalogue.CATALOGUE_VERSION[0] == "1"
|
|
373
|
+
assert catalogue.DRUG[0] == "drug"
|
|
374
|
+
# check mutations are in the catalogue, , with correct classification
|
|
375
|
+
assert catalogue[catalogue.MUTATION == "gene@A2S"].PREDICTION.values[0] == "S"
|
|
376
|
+
# check wildcards are in the catalogue, with correct classification
|
|
377
|
+
assert catalogue[catalogue.MUTATION == "gene@*="].PREDICTION.values[0] == "S"
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def test_cli_help():
|
|
381
|
+
result = subprocess.run(
|
|
382
|
+
[sys.executable, "-m", "catomatic.CatalogueBuilder", "--help"],
|
|
383
|
+
stdout=subprocess.PIPE,
|
|
384
|
+
stderr=subprocess.PIPE,
|
|
385
|
+
check=True,
|
|
386
|
+
)
|
|
387
|
+
assert "usage:" in result.stdout.decode()
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_cli_execution(phenotypes_file, mutations_file, output_file):
|
|
391
|
+
result = subprocess.run(
|
|
392
|
+
[
|
|
393
|
+
sys.executable,
|
|
394
|
+
"-m",
|
|
395
|
+
"catomatic.CatalogueBuilder",
|
|
396
|
+
"--samples",
|
|
397
|
+
phenotypes_file,
|
|
398
|
+
"--mutations",
|
|
399
|
+
mutations_file,
|
|
400
|
+
"--to_json",
|
|
401
|
+
"--outfile",
|
|
402
|
+
output_file,
|
|
403
|
+
"--test",
|
|
404
|
+
"Binomial",
|
|
405
|
+
"--background",
|
|
406
|
+
"0.1",
|
|
407
|
+
"--p",
|
|
408
|
+
"0.95",
|
|
409
|
+
"--strict_unlock",
|
|
410
|
+
],
|
|
411
|
+
stdout=subprocess.PIPE,
|
|
412
|
+
stderr=subprocess.PIPE,
|
|
413
|
+
check=False,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
assert result.returncode == 0, "Subprocess failed with exit status: {}".format(
|
|
417
|
+
result.returncode
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def test_to_json_output(phenotypes_file, mutations_file, output_file):
|
|
422
|
+
result = subprocess.run(
|
|
423
|
+
[
|
|
424
|
+
sys.executable,
|
|
425
|
+
"-m",
|
|
426
|
+
"catomatic.CatalogueBuilder",
|
|
427
|
+
"--samples",
|
|
428
|
+
phenotypes_file,
|
|
429
|
+
"--mutations",
|
|
430
|
+
mutations_file,
|
|
431
|
+
"--to_json",
|
|
432
|
+
"--outfile",
|
|
433
|
+
output_file,
|
|
434
|
+
],
|
|
435
|
+
stdout=subprocess.PIPE,
|
|
436
|
+
stderr=subprocess.PIPE,
|
|
437
|
+
check=False,
|
|
438
|
+
)
|
|
439
|
+
assert result.returncode == 0, "Subprocess failed with exit status: {}".format(
|
|
440
|
+
result.returncode
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# Load and verify the JSON output
|
|
444
|
+
with open(output_file, "r") as f:
|
|
445
|
+
data = json.load(f)
|
|
446
|
+
assert isinstance(data, dict)
|
|
447
|
+
assert "gene@A1S" in data
|
|
448
|
+
assert "gene@A2S" in data
|
|
449
|
+
assert "gene@A3S" in data
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def test_missing_piezo(phenotypes_file, mutations_file, output_file):
|
|
453
|
+
result = subprocess.run(
|
|
454
|
+
[
|
|
455
|
+
sys.executable,
|
|
456
|
+
"-m",
|
|
457
|
+
"coverage",
|
|
458
|
+
"run",
|
|
459
|
+
"-m",
|
|
460
|
+
"catomatic.CatalogueBuilder",
|
|
461
|
+
"--samples",
|
|
462
|
+
phenotypes_file,
|
|
463
|
+
"--mutations",
|
|
464
|
+
mutations_file,
|
|
465
|
+
"--to_piezo",
|
|
466
|
+
"--outfile",
|
|
467
|
+
output_file,
|
|
468
|
+
"--genbank_ref",
|
|
469
|
+
"genbank",
|
|
470
|
+
"--catalogue_name",
|
|
471
|
+
"test",
|
|
472
|
+
"--version",
|
|
473
|
+
"1",
|
|
474
|
+
], # missing drug and wildcards
|
|
475
|
+
stdout=subprocess.PIPE,
|
|
476
|
+
stderr=subprocess.PIPE,
|
|
477
|
+
check=False,
|
|
478
|
+
)
|
|
479
|
+
assert result.returncode != 0 # error
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def test_to_piezo_output(phenotypes_file, mutations_file, output_file, tmp_path):
|
|
483
|
+
wildcards_file = tmp_path / "wildcards.json"
|
|
484
|
+
wildcards_file.write_text(
|
|
485
|
+
json.dumps(
|
|
486
|
+
{
|
|
487
|
+
"gene@-*?": {"pred": "U", "evid": {}},
|
|
488
|
+
"gene@*=": {"pred": "S", "evid": {}},
|
|
489
|
+
}
|
|
490
|
+
)
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
result = subprocess.run(
|
|
494
|
+
[
|
|
495
|
+
sys.executable,
|
|
496
|
+
"-m",
|
|
497
|
+
"coverage",
|
|
498
|
+
"run",
|
|
499
|
+
"-m",
|
|
500
|
+
"catomatic.CatalogueBuilder",
|
|
501
|
+
"--samples",
|
|
502
|
+
phenotypes_file,
|
|
503
|
+
"--mutations",
|
|
504
|
+
mutations_file,
|
|
505
|
+
"--to_piezo",
|
|
506
|
+
"--outfile",
|
|
507
|
+
output_file,
|
|
508
|
+
"--genbank_ref",
|
|
509
|
+
"genbank",
|
|
510
|
+
"--catalogue_name",
|
|
511
|
+
"test",
|
|
512
|
+
"--version",
|
|
513
|
+
"1",
|
|
514
|
+
"--drug",
|
|
515
|
+
"drug",
|
|
516
|
+
"--wildcards",
|
|
517
|
+
str(wildcards_file),
|
|
518
|
+
],
|
|
519
|
+
stdout=subprocess.PIPE,
|
|
520
|
+
stderr=subprocess.PIPE,
|
|
521
|
+
check=False,
|
|
522
|
+
)
|
|
523
|
+
if result.returncode != 0:
|
|
524
|
+
print("Error output:", result.stderr.decode())
|
|
525
|
+
assert result.returncode == 0, "Subprocess failed with exit status: {}".format(
|
|
526
|
+
result.returncode
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# Load and verify the Piezo CSV output
|
|
530
|
+
piezo_df = pd.read_csv(output_file)
|
|
531
|
+
assert "GENBANK_REFERENCE" in piezo_df.columns
|
|
532
|
+
assert piezo_df.loc[0, "GENBANK_REFERENCE"] == "genbank"
|
|
533
|
+
assert piezo_df.loc[0, "CATALOGUE_NAME"] == "test"
|
|
534
|
+
assert piezo_df.loc[0, "CATALOGUE_VERSION"] == 1
|
|
535
|
+
assert piezo_df.loc[0, "DRUG"] == "drug"
|
|
536
|
+
assert "gene@A2S" in piezo_df["MUTATION"].values
|
|
537
|
+
assert "gene@*=" in piezo_df["MUTATION"].values
|