maphelios 0.8.3__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.
- maphelios-0.8.3/LICENSE +21 -0
- maphelios-0.8.3/PKG-INFO +77 -0
- maphelios-0.8.3/README.md +31 -0
- maphelios-0.8.3/pyproject.toml +59 -0
- maphelios-0.8.3/setup.cfg +4 -0
- maphelios-0.8.3/src/maphelios/__init__.py +5 -0
- maphelios-0.8.3/src/maphelios/__main__.py +6 -0
- maphelios-0.8.3/src/maphelios/cli.py +106 -0
- maphelios-0.8.3/src/maphelios/comparison.py +352 -0
- maphelios-0.8.3/src/maphelios/genome_funcs.py +136 -0
- maphelios-0.8.3/src/maphelios/helper.py +169 -0
- maphelios-0.8.3/src/maphelios/insert.py +241 -0
- maphelios-0.8.3/src/maphelios/mapping.py +699 -0
- maphelios-0.8.3/src/maphelios/pacbio.py +620 -0
- maphelios-0.8.3/src/maphelios.egg-info/PKG-INFO +77 -0
- maphelios-0.8.3/src/maphelios.egg-info/SOURCES.txt +18 -0
- maphelios-0.8.3/src/maphelios.egg-info/dependency_links.txt +1 -0
- maphelios-0.8.3/src/maphelios.egg-info/entry_points.txt +2 -0
- maphelios-0.8.3/src/maphelios.egg-info/requires.txt +13 -0
- maphelios-0.8.3/src/maphelios.egg-info/top_level.txt +1 -0
maphelios-0.8.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Zimmermann-Kogadeeva Group
|
|
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.
|
maphelios-0.8.3/PKG-INFO
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maphelios
|
|
3
|
+
Version: 0.8.3
|
|
4
|
+
Summary: Package for making genome related plots
|
|
5
|
+
Author-email: Bartosz Bartmanski <bartosz.bartmanski@embl.de>, Katarina Erbstein <katarina.erbstein@embl.de>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024 Zimmermann-Kogadeeva Group
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://git.embl.de/grp-zimmermann-kogadeeva/Maphelios
|
|
29
|
+
Project-URL: Bug tracker, https://git.embl.de/grp-zimmermann-kogadeeva/Maphelios/issues/
|
|
30
|
+
Requires-Python: >=3.9
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Requires-Dist: biopython
|
|
34
|
+
Requires-Dist: bcbio-gff
|
|
35
|
+
Requires-Dist: dna_features_viewer
|
|
36
|
+
Requires-Dist: matplotlib
|
|
37
|
+
Requires-Dist: pandas
|
|
38
|
+
Requires-Dist: platformdirs
|
|
39
|
+
Requires-Dist: pysam>=0.23.0
|
|
40
|
+
Requires-Dist: pycirclize>=1.9.1
|
|
41
|
+
Requires-Dist: scipy>=1.13.1
|
|
42
|
+
Provides-Extra: streamlit
|
|
43
|
+
Requires-Dist: streamlit; extra == "streamlit"
|
|
44
|
+
Requires-Dist: seaborn; extra == "streamlit"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Maphelios
|
|
49
|
+
|
|
50
|
+
Package to find and plot inserts alongside the genome or contigs.
|
|
51
|
+
|
|
52
|
+
BLAST is required. You can follow the instructions to download and install
|
|
53
|
+
BLAST
|
|
54
|
+
[here](https://blast.ncbi.nlm.nih.gov/doc/blast-help/downloadblastdata.html).
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
pip install git+https://git.embl.org/grp-zimmermann-kogadeeva/Maphelios.git
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Running
|
|
63
|
+
|
|
64
|
+
### Docker
|
|
65
|
+
|
|
66
|
+
To run the streamlit app with docker:
|
|
67
|
+
```
|
|
68
|
+
docker run -it --rm -p 8501:8501 docker://registry.git.embl.org/grp-zimmermann-kogadeeva/maphelios:0.7.3
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Apptainer
|
|
72
|
+
|
|
73
|
+
To run the streamlit app with apptainer:
|
|
74
|
+
```
|
|
75
|
+
apptainer exec --no-home --writable-tmpfs --workdir /app docker://registry.git.embl.org/grp-zimmermann-kogadeeva/maphelios:0.7.3 streamlit run /app/app/app.py --server.headless true
|
|
76
|
+
```
|
|
77
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
# Maphelios
|
|
3
|
+
|
|
4
|
+
Package to find and plot inserts alongside the genome or contigs.
|
|
5
|
+
|
|
6
|
+
BLAST is required. You can follow the instructions to download and install
|
|
7
|
+
BLAST
|
|
8
|
+
[here](https://blast.ncbi.nlm.nih.gov/doc/blast-help/downloadblastdata.html).
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
pip install git+https://git.embl.org/grp-zimmermann-kogadeeva/Maphelios.git
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Running
|
|
17
|
+
|
|
18
|
+
### Docker
|
|
19
|
+
|
|
20
|
+
To run the streamlit app with docker:
|
|
21
|
+
```
|
|
22
|
+
docker run -it --rm -p 8501:8501 docker://registry.git.embl.org/grp-zimmermann-kogadeeva/maphelios:0.7.3
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Apptainer
|
|
26
|
+
|
|
27
|
+
To run the streamlit app with apptainer:
|
|
28
|
+
```
|
|
29
|
+
apptainer exec --no-home --writable-tmpfs --workdir /app docker://registry.git.embl.org/grp-zimmermann-kogadeeva/maphelios:0.7.3 streamlit run /app/app/app.py --server.headless true
|
|
30
|
+
```
|
|
31
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "maphelios"
|
|
7
|
+
version = "0.8.3"
|
|
8
|
+
requires-python = ">=3.9"
|
|
9
|
+
description = "Package for making genome related plots"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = {file = "LICENSE"}
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Bartosz Bartmanski", email = "bartosz.bartmanski@embl.de" },
|
|
14
|
+
{ name = "Katarina Erbstein", email = "katarina.erbstein@embl.de" }
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"biopython",
|
|
19
|
+
"bcbio-gff",
|
|
20
|
+
"dna_features_viewer",
|
|
21
|
+
"matplotlib",
|
|
22
|
+
"pandas",
|
|
23
|
+
"platformdirs",
|
|
24
|
+
"pysam>=0.23.0",
|
|
25
|
+
"pycirclize>=1.9.1",
|
|
26
|
+
"scipy>=1.13.1",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
streamlit = ["streamlit", "seaborn"]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
maphelios = "maphelios.__main__:main"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
"Homepage" = "https://git.embl.de/grp-zimmermann-kogadeeva/Maphelios"
|
|
37
|
+
"Bug tracker" = "https://git.embl.de/grp-zimmermann-kogadeeva/Maphelios/issues/"
|
|
38
|
+
|
|
39
|
+
[tool.isort]
|
|
40
|
+
profile = "black"
|
|
41
|
+
src_paths = ["src", "tests"]
|
|
42
|
+
|
|
43
|
+
[tool.bumpver]
|
|
44
|
+
current_version = "0.8.3"
|
|
45
|
+
version_pattern = "MAJOR.MINOR.PATCH"
|
|
46
|
+
commit_message = "Bump version {old_version} -> {new_version}"
|
|
47
|
+
commit = true
|
|
48
|
+
tag = true
|
|
49
|
+
push = true
|
|
50
|
+
|
|
51
|
+
[tool.bumpver.file_patterns]
|
|
52
|
+
"pyproject.toml" = ['current_version = "{version}"', 'version = "{version}"']
|
|
53
|
+
"src/maphelios/__init__.py" = ['^__version__ = "{version}"$']
|
|
54
|
+
|
|
55
|
+
[dependency-groups]
|
|
56
|
+
dev = [
|
|
57
|
+
"ipympl>=0.8.8",
|
|
58
|
+
"jupyterlab>=2.3.2",
|
|
59
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
|
|
5
|
+
from .main import Pipeline
|
|
6
|
+
from .plotting import plot_insert, plot_insert_dists, plot_genome
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_args(*args):
|
|
10
|
+
# Create the CLI using argparse
|
|
11
|
+
# TODO: maybe switch from argparse to click
|
|
12
|
+
parser = argparse.ArgumentParser(description="Generating plots")
|
|
13
|
+
|
|
14
|
+
subparsers = parser.add_subparsers(help="sub-command help")
|
|
15
|
+
|
|
16
|
+
parser_ncbi = subparsers.add_parser("ncbi", help="Download genome from NCBI")
|
|
17
|
+
parser_ncbi.add_argument("search_term", help="search term for BLAST")
|
|
18
|
+
parser_ncbi.add_argument("email", help="Your email address - needed by BLAST")
|
|
19
|
+
parser_ncbi.add_argument(
|
|
20
|
+
"--retmax",
|
|
21
|
+
default=200,
|
|
22
|
+
help="Maximum number of records to download from NCBI",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
parser_genome = subparsers.add_parser("file", help="Specify a local genome.")
|
|
26
|
+
parser_genome.add_argument("path_to_genome")
|
|
27
|
+
|
|
28
|
+
parser.add_argument("seq_file", help="file with the sequences")
|
|
29
|
+
parser.add_argument("output_prefix", help="Output directory")
|
|
30
|
+
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--fwd_suffix", default="_F", help="Suffix for forward seq. default='_F'"
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--rev_suffix", default="_R", help="Suffix for reverse seq. default='_R'"
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--insert_types", help="matched, unmatched or both inserts", default="both"
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--filter",
|
|
42
|
+
default=0.5,
|
|
43
|
+
help="Threshold value for filtering out blast results with low coverage",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Get all the command line arguments
|
|
47
|
+
return parser.parse_args(*args)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main():
|
|
51
|
+
args = get_args()
|
|
52
|
+
if "path_to_genome" in args:
|
|
53
|
+
search_term = None
|
|
54
|
+
email = None
|
|
55
|
+
retmax = None
|
|
56
|
+
path_to_genome = args.path_to_genome
|
|
57
|
+
else:
|
|
58
|
+
search_term = args.search_term
|
|
59
|
+
email = args.email
|
|
60
|
+
retmax = int(args.retmax)
|
|
61
|
+
path_to_genome = None
|
|
62
|
+
|
|
63
|
+
pipeline = Pipeline(
|
|
64
|
+
args.seq_file,
|
|
65
|
+
args.output_prefix,
|
|
66
|
+
genome_file=path_to_genome,
|
|
67
|
+
search_term=search_term,
|
|
68
|
+
email=email,
|
|
69
|
+
retmax=retmax,
|
|
70
|
+
fwd_suffix=args.fwd_suffix,
|
|
71
|
+
rev_suffix=args.rev_suffix,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Create linear plots of inserts with annotations
|
|
75
|
+
for seq_id in pipeline.seq_ids:
|
|
76
|
+
inserts = pipeline.get_inserts(
|
|
77
|
+
seq_id, insert_types=args.insert_types, filter_threshold=args.filter
|
|
78
|
+
)
|
|
79
|
+
for i, insert in enumerate(inserts):
|
|
80
|
+
fig, axs = plt.subplots(2, 1, figsize=(10, 10), height_ratios=[3, 5])
|
|
81
|
+
axs = plot_insert(insert, pipeline.genome, axs=axs)
|
|
82
|
+
fig.savefig(pipeline.work_dir / f"{seq_id}_hit{i}.png", dpi=300)
|
|
83
|
+
plt.close()
|
|
84
|
+
|
|
85
|
+
# Get all inserts (used in both next plots)
|
|
86
|
+
inserts = pipeline.get_all_inserts(args.insert_types, filter_threshold=args.filter)
|
|
87
|
+
|
|
88
|
+
# Create a plot of genome / all contigs as circular plot with inserts
|
|
89
|
+
# layered on top
|
|
90
|
+
fig, ax = plt.subplots(figsize=(10, 30))
|
|
91
|
+
ax = plot_on_genome(inserts, pipeline.genome, ax=ax)
|
|
92
|
+
fig.savefig(pipeline.work_dir / "genome_plot.png", dpi=300)
|
|
93
|
+
|
|
94
|
+
# Plots of distributions of insert sizes
|
|
95
|
+
fig, axs = plt.subplots(1, 3, figsize=(12, 5))
|
|
96
|
+
axs = plot_insert_dists(inserts, axs=axs)
|
|
97
|
+
fig.savefig(pipeline.work_dir / "insert_length_dist.png", dpi=300)
|
|
98
|
+
|
|
99
|
+
df = pipeline.to_dataframe(
|
|
100
|
+
insert_types=args.insert_types, filter_threshold=args.filter
|
|
101
|
+
)
|
|
102
|
+
df.to_csv(pipeline.work_dir / "inserts.csv", index=False)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
main()
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from itertools import chain, cycle
|
|
3
|
+
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from dna_features_viewer import CircularGraphicRecord
|
|
8
|
+
from matplotlib import color_sequences
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Clusters(dict):
|
|
12
|
+
def __init__(self, clusters, genomes=None):
|
|
13
|
+
super(Clusters, self).__init__(clusters)
|
|
14
|
+
|
|
15
|
+
self.genomes = genomes or {g for g, clust_idx in self.keys()}
|
|
16
|
+
self.cluster_labels = list(self.keys())
|
|
17
|
+
|
|
18
|
+
def __contains__(self, item):
|
|
19
|
+
return item in self.cluster_labels
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def insert_ids(self):
|
|
23
|
+
insert_ids = {
|
|
24
|
+
genome: list(
|
|
25
|
+
chain.from_iterable(
|
|
26
|
+
(
|
|
27
|
+
insert_ids
|
|
28
|
+
for (g, clust_idx), insert_ids in self.items()
|
|
29
|
+
if g == genome
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
for genome in self.genomes
|
|
34
|
+
}
|
|
35
|
+
return insert_ids
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def insert_labels(self):
|
|
39
|
+
seq_labels = defaultdict(dict)
|
|
40
|
+
for g, clust_idx in self.cluster_labels:
|
|
41
|
+
insert_ids = self.__getitem__((g, clust_idx))
|
|
42
|
+
# Set it such that only one label is given per cluster
|
|
43
|
+
for insert_id in insert_ids:
|
|
44
|
+
seq_labels[g][insert_id] = None
|
|
45
|
+
seq_labels[g][insert_ids[-1]] = f"Cluster {clust_idx}"
|
|
46
|
+
return seq_labels
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def other_repr(self):
|
|
50
|
+
other = defaultdict(list)
|
|
51
|
+
for (g, clust_idx), insert_ids in self.items():
|
|
52
|
+
other[g].append((clust_idx, insert_ids))
|
|
53
|
+
return other
|
|
54
|
+
|
|
55
|
+
def subset(self, selection):
|
|
56
|
+
return Clusters(
|
|
57
|
+
{
|
|
58
|
+
(g, clust_idx): self.__getitem__((g, clust_idx))
|
|
59
|
+
for g, clust_idx in selection
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Comparison(dict):
|
|
65
|
+
|
|
66
|
+
def __init__(self, *args, **kwargs):
|
|
67
|
+
super(Comparison, self).__init__(*args, **kwargs)
|
|
68
|
+
|
|
69
|
+
self.insert_ids = {g: mapping.insert_ids for g, mapping in self.items()}
|
|
70
|
+
|
|
71
|
+
def get_inserts(
|
|
72
|
+
self,
|
|
73
|
+
genomes=None,
|
|
74
|
+
seq_ids=None,
|
|
75
|
+
insert_ids=None,
|
|
76
|
+
insert_type="both",
|
|
77
|
+
filter_threshold=None,
|
|
78
|
+
**kwargs,
|
|
79
|
+
):
|
|
80
|
+
if genomes is None:
|
|
81
|
+
genomes = self.keys()
|
|
82
|
+
|
|
83
|
+
return [
|
|
84
|
+
self.__getitem__(g).get(
|
|
85
|
+
insert_ids=insert_ids,
|
|
86
|
+
seq_ids=seq_ids,
|
|
87
|
+
insert_type=insert_type,
|
|
88
|
+
filter_threshold=filter_threshold,
|
|
89
|
+
)
|
|
90
|
+
for g in genomes
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
def get_inserts_df(
|
|
94
|
+
self, genomes=None, insert_type="both", filter_threshold=None, **kwargs
|
|
95
|
+
):
|
|
96
|
+
if genomes is None:
|
|
97
|
+
genomes = self.keys()
|
|
98
|
+
|
|
99
|
+
inserts_dfs = [
|
|
100
|
+
self.__getitem__(genome)
|
|
101
|
+
.to_dataframe(insert_type=insert_type, filter_threshold=filter_threshold)
|
|
102
|
+
.assign(genome=genome)
|
|
103
|
+
for genome in genomes
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
if len(inserts_dfs):
|
|
107
|
+
return pd.concat(inserts_dfs, ignore_index=True)
|
|
108
|
+
|
|
109
|
+
def get_insert_presence_df(
|
|
110
|
+
self, genomes=None, insert_type="both", filter_threshold=None, **kwargs
|
|
111
|
+
):
|
|
112
|
+
if genomes is None:
|
|
113
|
+
genomes = self.keys()
|
|
114
|
+
|
|
115
|
+
dfs = [
|
|
116
|
+
pd.DataFrame(
|
|
117
|
+
self.__getitem__(g).get_seq_ids(insert_type, filter_threshold),
|
|
118
|
+
columns=["insert_ids"],
|
|
119
|
+
).assign(genome=g)
|
|
120
|
+
for g in genomes
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
all_seq_ids = sorted(
|
|
124
|
+
set(chain.from_iterable(self.__getitem__(g).seq_ids for g in genomes))
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
df_insert_presence = (
|
|
128
|
+
pd.concat(dfs, ignore_index=True)
|
|
129
|
+
.groupby(["insert_ids", "genome"], as_index=False)
|
|
130
|
+
.agg(num_inserts=pd.NamedAgg("insert_ids", "count"))
|
|
131
|
+
.pivot(index="insert_ids", columns="genome", values="num_inserts")
|
|
132
|
+
.reindex(columns=genomes, index=all_seq_ids)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return df_insert_presence
|
|
136
|
+
|
|
137
|
+
def get_clusters(
|
|
138
|
+
self,
|
|
139
|
+
genomes=None,
|
|
140
|
+
insert_type="both",
|
|
141
|
+
filter_threshold=None,
|
|
142
|
+
plain_dict=True,
|
|
143
|
+
**kwargs,
|
|
144
|
+
):
|
|
145
|
+
if genomes is None:
|
|
146
|
+
genomes = self.keys()
|
|
147
|
+
|
|
148
|
+
if plain_dict:
|
|
149
|
+
return {
|
|
150
|
+
g: self.__getitem__(g).get_clusters(insert_type, filter_threshold)
|
|
151
|
+
for g in genomes
|
|
152
|
+
}
|
|
153
|
+
else:
|
|
154
|
+
return Clusters(
|
|
155
|
+
{
|
|
156
|
+
(g, clust_idx): cluster
|
|
157
|
+
for g in genomes
|
|
158
|
+
for clust_idx, cluster in enumerate(
|
|
159
|
+
self.__getitem__(g).get_clusters(insert_type, filter_threshold)
|
|
160
|
+
)
|
|
161
|
+
},
|
|
162
|
+
genomes=genomes,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def get_labels(
|
|
166
|
+
self,
|
|
167
|
+
seq_ids,
|
|
168
|
+
insert_ids=None,
|
|
169
|
+
genomes=None,
|
|
170
|
+
insert_type="both",
|
|
171
|
+
filter_threshold=None,
|
|
172
|
+
):
|
|
173
|
+
if genomes is None:
|
|
174
|
+
genomes = self.keys()
|
|
175
|
+
|
|
176
|
+
if insert_ids is None:
|
|
177
|
+
insert_ids = {g: None for g in genomes}
|
|
178
|
+
|
|
179
|
+
if seq_ids is not None or insert_ids is not None:
|
|
180
|
+
return {
|
|
181
|
+
g: self.__getitem__(g).get_labels(
|
|
182
|
+
seq_ids, insert_ids.get(g), genomes, insert_type, filter_threshold
|
|
183
|
+
)
|
|
184
|
+
for g in genomes
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
def get_genes_df(
|
|
188
|
+
self,
|
|
189
|
+
seq_ids=None,
|
|
190
|
+
insert_ids=None,
|
|
191
|
+
insert_type="both",
|
|
192
|
+
filter_threshold=None,
|
|
193
|
+
buffer=4000,
|
|
194
|
+
**kwargs,
|
|
195
|
+
):
|
|
196
|
+
if seq_ids is None:
|
|
197
|
+
seq_ids = {x: None for x in self.keys()}
|
|
198
|
+
elif isinstance(seq_ids, (list, tuple)):
|
|
199
|
+
seq_ids = {x: seq_ids for x in self.keys()}
|
|
200
|
+
|
|
201
|
+
if insert_ids is None:
|
|
202
|
+
insert_ids = {x: None for x in self.keys()}
|
|
203
|
+
elif isinstance(insert_ids, (list, tuple)):
|
|
204
|
+
insert_ids = {x: insert_ids for x in self.keys()}
|
|
205
|
+
|
|
206
|
+
genomes = set(seq_ids.keys()) | set(insert_ids.keys())
|
|
207
|
+
|
|
208
|
+
genes_dfs = [
|
|
209
|
+
self.__getitem__(g)
|
|
210
|
+
.genes_to_dataframe(
|
|
211
|
+
seq_ids=seq_ids.get(g),
|
|
212
|
+
insert_ids=insert_ids.get(g),
|
|
213
|
+
insert_type=insert_type,
|
|
214
|
+
filter_threshold=filter_threshold,
|
|
215
|
+
buffer=buffer,
|
|
216
|
+
)
|
|
217
|
+
.assign(genome=g)
|
|
218
|
+
for g in genomes
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
if len(genes_dfs):
|
|
222
|
+
drop_by = ["start", "end", "type", "insert_idx", "seq_id", "genome"]
|
|
223
|
+
df_genes = pd.concat(genes_dfs, ignore_index=True).drop_duplicates(
|
|
224
|
+
subset=drop_by
|
|
225
|
+
)
|
|
226
|
+
df_genes.insert(0, "genome", df_genes.pop("genome"))
|
|
227
|
+
return df_genes
|
|
228
|
+
|
|
229
|
+
def get_graphic_features(
|
|
230
|
+
self,
|
|
231
|
+
seq_ids=None,
|
|
232
|
+
insert_ids=None,
|
|
233
|
+
genomes_order=None,
|
|
234
|
+
seq_labels=None,
|
|
235
|
+
show_contig_labels=True,
|
|
236
|
+
insert_type="both",
|
|
237
|
+
filter_threshold=None,
|
|
238
|
+
palette="tab10",
|
|
239
|
+
**kwargs,
|
|
240
|
+
):
|
|
241
|
+
if seq_ids is None:
|
|
242
|
+
seq_ids = {x: None for x in self.keys()}
|
|
243
|
+
elif isinstance(seq_ids, (list, tuple)):
|
|
244
|
+
seq_ids = {x: seq_ids for x in self.keys()}
|
|
245
|
+
|
|
246
|
+
if insert_ids is None:
|
|
247
|
+
insert_ids = {x: None for x in self.keys()}
|
|
248
|
+
elif isinstance(insert_ids, (list, tuple)):
|
|
249
|
+
insert_ids = {x: insert_ids for x in self.keys()}
|
|
250
|
+
|
|
251
|
+
if genomes_order is None:
|
|
252
|
+
genomes_order = set(seq_ids.keys()) | set(insert_ids.keys())
|
|
253
|
+
|
|
254
|
+
if seq_labels is None:
|
|
255
|
+
seq_labels = {x: None for x in self.keys()}
|
|
256
|
+
|
|
257
|
+
palette = cycle(color_sequences[palette])
|
|
258
|
+
|
|
259
|
+
res = {
|
|
260
|
+
g: self.__getitem__(g).get_graphic_features(
|
|
261
|
+
seq_ids=seq_ids.get(g),
|
|
262
|
+
insert_ids=insert_ids.get(g),
|
|
263
|
+
insert_type=insert_type,
|
|
264
|
+
filter_threshold=filter_threshold,
|
|
265
|
+
contig_labels=show_contig_labels,
|
|
266
|
+
seq_labels=seq_labels.get(g),
|
|
267
|
+
col1=col,
|
|
268
|
+
col2=col,
|
|
269
|
+
linecolor=col,
|
|
270
|
+
**kwargs,
|
|
271
|
+
)
|
|
272
|
+
for g, col in zip(genomes_order, palette)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return res
|
|
276
|
+
|
|
277
|
+
def plot(
|
|
278
|
+
self,
|
|
279
|
+
seq_ids=None,
|
|
280
|
+
insert_ids=None,
|
|
281
|
+
genomes_order=None,
|
|
282
|
+
seq_labels=None,
|
|
283
|
+
insert_type="both",
|
|
284
|
+
filter_threshold=None,
|
|
285
|
+
feature_kwargs=None,
|
|
286
|
+
show_contig_labels=True,
|
|
287
|
+
show_titles=False,
|
|
288
|
+
palette="tab10",
|
|
289
|
+
facet_wrap=None,
|
|
290
|
+
fig=None,
|
|
291
|
+
**kwargs,
|
|
292
|
+
):
|
|
293
|
+
feature_kwargs = {} if feature_kwargs is None else feature_kwargs
|
|
294
|
+
|
|
295
|
+
# Get the graphic feature objects
|
|
296
|
+
res = self.get_graphic_features(
|
|
297
|
+
seq_ids,
|
|
298
|
+
insert_ids,
|
|
299
|
+
genomes_order,
|
|
300
|
+
seq_labels,
|
|
301
|
+
show_contig_labels,
|
|
302
|
+
insert_type,
|
|
303
|
+
filter_threshold,
|
|
304
|
+
palette,
|
|
305
|
+
**feature_kwargs,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Set all the plotting options based on input
|
|
309
|
+
if "figsize" not in kwargs:
|
|
310
|
+
kwargs["figsize"] = (10, 8)
|
|
311
|
+
|
|
312
|
+
genome_labels = res.keys()
|
|
313
|
+
num_genomes = len(genome_labels)
|
|
314
|
+
|
|
315
|
+
# Set the number of axes in the figure based on facet_wrap option
|
|
316
|
+
nrows, ncols = 1, 1
|
|
317
|
+
if facet_wrap is not None:
|
|
318
|
+
ncols = min(facet_wrap, num_genomes)
|
|
319
|
+
nrows = num_genomes // ncols
|
|
320
|
+
|
|
321
|
+
# Create figure and axes if not provided
|
|
322
|
+
if fig is None:
|
|
323
|
+
fig, axs = plt.subplots(nrows=nrows, ncols=ncols, **kwargs)
|
|
324
|
+
else:
|
|
325
|
+
axs = fig.get_axes()
|
|
326
|
+
|
|
327
|
+
# Check that axs is iterable even if a single axes object
|
|
328
|
+
if isinstance(axs, plt.Axes):
|
|
329
|
+
axs = np.array([axs])
|
|
330
|
+
|
|
331
|
+
# Make and label all the plots
|
|
332
|
+
if facet_wrap is None:
|
|
333
|
+
features = list(chain.from_iterable([x[1] for x in res.values()]))
|
|
334
|
+
rec = CircularGraphicRecord(max([x[0] for x in res.values()]), features)
|
|
335
|
+
_ = rec.plot(axs[0], annotate_inline=False)
|
|
336
|
+
|
|
337
|
+
if show_titles:
|
|
338
|
+
axs[0].set_title(", ".join(list(genome_labels)))
|
|
339
|
+
|
|
340
|
+
else:
|
|
341
|
+
for (label, (seq_len, features)), ax in zip(res.items(), axs.flatten()):
|
|
342
|
+
rec = CircularGraphicRecord(seq_len, features)
|
|
343
|
+
_ = rec.plot(ax, annotate_inline=False)
|
|
344
|
+
|
|
345
|
+
if show_titles:
|
|
346
|
+
ax.set_title(label)
|
|
347
|
+
|
|
348
|
+
# Remove redundant axis
|
|
349
|
+
for ax in axs[num_genomes:]:
|
|
350
|
+
ax.axis("off")
|
|
351
|
+
|
|
352
|
+
return fig
|