cdhit-reader 0.2.0__tar.gz → 0.5.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.
- {cdhit-reader-0.2.0/cdhit_reader.egg-info → cdhit_reader-0.5.0}/PKG-INFO +29 -3
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/README.md +18 -0
- cdhit_reader-0.5.0/cdhit_reader/__init__.py +42 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_cli.py +15 -16
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_compare.py +48 -42
- cdhit_reader-0.5.0/cdhit_reader/_compare_otus.py +332 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_fasta.py +60 -33
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_reader.py +95 -166
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_version.py +1 -1
- cdhit_reader-0.5.0/cdhit_reader/test/test_clstr.py +179 -0
- cdhit_reader-0.5.0/cdhit_reader/test/test_clustering.py +66 -0
- cdhit_reader-0.5.0/cdhit_reader/test/test_compare.py +10 -0
- cdhit_reader-0.5.0/cdhit_reader/test/test_compare_otus.py +130 -0
- cdhit_reader-0.5.0/cdhit_reader/test/test_examples.py +44 -0
- cdhit_reader-0.5.0/cdhit_reader/test/test_fasta.py +58 -0
- cdhit_reader-0.5.0/cdhit_reader/test/test_integration.py +30 -0
- cdhit_reader-0.5.0/cdhit_reader/test/test_package_import.py +55 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0/cdhit_reader.egg-info}/PKG-INFO +29 -3
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/SOURCES.txt +9 -1
- cdhit_reader-0.5.0/cdhit_reader.egg-info/entry_points.txt +4 -0
- cdhit_reader-0.5.0/setup.py +9 -0
- cdhit-reader-0.2.0/cdhit_reader/__init__.py +0 -26
- cdhit-reader-0.2.0/cdhit_reader/test/test_clstr.py +0 -88
- cdhit-reader-0.2.0/cdhit_reader.egg-info/entry_points.txt +0 -3
- cdhit-reader-0.2.0/setup.py +0 -5
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/LICENSE.md +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/MANIFEST.in +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/_testit.py +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/test/__init__.py +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/test/aa.clstr +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader/test/nt.clstr +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/dependency_links.txt +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/requires.txt +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/top_level.txt +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/cdhit_reader.egg-info/zip-safe +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/pyproject.toml +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/setup.cfg +0 -0
- {cdhit-reader-0.2.0 → cdhit_reader-0.5.0}/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cdhit-reader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: CD-HIT cluster parser
|
|
5
5
|
Home-page: https://github.com/telatin/cdhit-parser
|
|
6
6
|
Download-URL: https://github.com/telatin/cdhit-parser
|
|
@@ -18,8 +18,16 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
18
18
|
Classifier: Operating System :: OS Independent
|
|
19
19
|
Classifier: Programming Language :: Python
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
-
Provides-Extra: cli
|
|
22
21
|
License-File: LICENSE.md
|
|
22
|
+
Requires-Dist: click>=7.1.2
|
|
23
|
+
Requires-Dist: importlib-resources>=1.4.0
|
|
24
|
+
Requires-Dist: more-itertools>=8.4.0
|
|
25
|
+
Requires-Dist: pytest>=5.4.3
|
|
26
|
+
Requires-Dist: xopen>=1.0.1
|
|
27
|
+
Provides-Extra: cli
|
|
28
|
+
Requires-Dist: plotille; extra == "cli"
|
|
29
|
+
Dynamic: download-url
|
|
30
|
+
Dynamic: license-file
|
|
23
31
|
|
|
24
32
|
# cdhit-parser
|
|
25
33
|
|
|
@@ -52,6 +60,24 @@ Load all clusters in to a list:
|
|
|
52
60
|
clusters = read_cdhit(input).read_items()
|
|
53
61
|
```
|
|
54
62
|
|
|
63
|
+
### Sequence to cluster lookup
|
|
64
|
+
|
|
65
|
+
`Clustering` loads a whole .clstr file and provides a reverse index
|
|
66
|
+
(`seqcluster`) mapping each sequence name to the name of its cluster:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from cdhit_reader import Clustering
|
|
70
|
+
|
|
71
|
+
clustering = Clustering.from_file(input)
|
|
72
|
+
|
|
73
|
+
print(len(clustering)) # number of clusters
|
|
74
|
+
for cluster in clustering: # iterate over the clusters
|
|
75
|
+
print(cluster.name)
|
|
76
|
+
|
|
77
|
+
# Which cluster does a sequence belong to?
|
|
78
|
+
print(clustering.seqcluster["seq1.A"]) # e.g. "Cluster 0"
|
|
79
|
+
```
|
|
80
|
+
|
|
55
81
|
## Read FASTA file
|
|
56
82
|
|
|
57
83
|
```python
|
|
@@ -29,6 +29,24 @@ Load all clusters in to a list:
|
|
|
29
29
|
clusters = read_cdhit(input).read_items()
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
### Sequence to cluster lookup
|
|
33
|
+
|
|
34
|
+
`Clustering` loads a whole .clstr file and provides a reverse index
|
|
35
|
+
(`seqcluster`) mapping each sequence name to the name of its cluster:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from cdhit_reader import Clustering
|
|
39
|
+
|
|
40
|
+
clustering = Clustering.from_file(input)
|
|
41
|
+
|
|
42
|
+
print(len(clustering)) # number of clusters
|
|
43
|
+
for cluster in clustering: # iterate over the clusters
|
|
44
|
+
print(cluster.name)
|
|
45
|
+
|
|
46
|
+
# Which cluster does a sequence belong to?
|
|
47
|
+
print(clustering.seqcluster["seq1.A"]) # e.g. "Cluster 0"
|
|
48
|
+
```
|
|
49
|
+
|
|
32
50
|
## Read FASTA file
|
|
33
51
|
|
|
34
52
|
```python
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
|
|
3
|
+
from ._reader import ParsingError, ClusterSequence, Cluster, Clustering, ClstrReader, read_cdhit, SeqType, Strand
|
|
4
|
+
from ._fasta import FastaParsingError, Sequence, FastaReader, read_fasta
|
|
5
|
+
from ._version import __version__
|
|
6
|
+
|
|
7
|
+
_LAZY_EXPORTS = {
|
|
8
|
+
"cli": ("._cli", "cli"),
|
|
9
|
+
"compare": ("._compare", "compare"),
|
|
10
|
+
"test": ("._testit", "test"),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ParsingError",
|
|
15
|
+
"FastaParsingError",
|
|
16
|
+
"ClusterSequence",
|
|
17
|
+
"Cluster",
|
|
18
|
+
"Clustering",
|
|
19
|
+
"ClstrReader",
|
|
20
|
+
"read_cdhit",
|
|
21
|
+
"FastaReader",
|
|
22
|
+
"read_fasta",
|
|
23
|
+
"SeqType",
|
|
24
|
+
"Strand",
|
|
25
|
+
"Sequence",
|
|
26
|
+
"__version__",
|
|
27
|
+
"cli",
|
|
28
|
+
"compare",
|
|
29
|
+
"test",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def __getattr__(name):
|
|
34
|
+
if name in _LAZY_EXPORTS:
|
|
35
|
+
module_name, attribute_name = _LAZY_EXPORTS[name]
|
|
36
|
+
module = import_module(module_name, __name__)
|
|
37
|
+
return getattr(module, attribute_name)
|
|
38
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def __dir__():
|
|
42
|
+
return sorted(set(globals()) | set(__all__))
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import sys
|
|
3
1
|
from statistics import mean
|
|
4
2
|
|
|
5
3
|
import click
|
|
6
4
|
|
|
7
5
|
from ._reader import read_cdhit
|
|
8
6
|
from ._version import __version__
|
|
9
|
-
#from ._writer import write_fasta
|
|
10
7
|
|
|
11
8
|
|
|
12
9
|
@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
|
|
@@ -22,31 +19,33 @@ def cli(clstr, stats: bool, hist: bool, all: bool):
|
|
|
22
19
|
\b
|
|
23
20
|
Warning
|
|
24
21
|
-------
|
|
25
|
-
The commad line interface is in EXPERIMENTAL stage.
|
|
22
|
+
The commad line interface is in EXPERIMENTAL stage.
|
|
26
23
|
"""
|
|
27
24
|
|
|
28
25
|
nitems = 0
|
|
29
26
|
nseqs = 0
|
|
30
27
|
seq_lens = []
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
with read_cdhit(clstr) as reader:
|
|
29
|
+
for item in reader:
|
|
30
|
+
seq_lens.append(len(item))
|
|
31
|
+
nitems += 1
|
|
32
|
+
nseqs += len(item)
|
|
33
|
+
if all:
|
|
34
|
+
print(item)
|
|
35
|
+
for s in item.sequences:
|
|
36
|
+
print(f" {s}")
|
|
39
37
|
if stats:
|
|
40
38
|
click.echo(f"Input file: {clstr}")
|
|
41
39
|
click.echo(f"Number of clusters: {nitems}")
|
|
42
40
|
click.echo(f"Total sequences: {nseqs}")
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
|
|
42
|
+
if seq_lens:
|
|
43
|
+
msg = f"Cluster size: min {min(seq_lens)}, mean {mean(seq_lens):.2f}, max {max(seq_lens)}"
|
|
44
|
+
click.echo(msg)
|
|
46
45
|
|
|
47
46
|
if hist:
|
|
48
47
|
show_hist(seq_lens)
|
|
49
48
|
|
|
50
49
|
|
|
51
50
|
def show_hist(seq_lens):
|
|
52
|
-
pass
|
|
51
|
+
pass
|
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
from email.policy import default
|
|
2
1
|
import sys
|
|
3
|
-
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import tempfile
|
|
4
5
|
|
|
5
6
|
import click
|
|
7
|
+
from xopen import xopen
|
|
6
8
|
|
|
7
9
|
from ._reader import read_cdhit
|
|
8
10
|
from ._version import __version__
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
|
|
12
|
+
TAG1 = "1:::"
|
|
13
|
+
TAG2 = "2:::"
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
def has_cdhit():
|
|
15
17
|
cmd = ["cd-hit", "-h"]
|
|
16
|
-
# Run cmd and
|
|
18
|
+
# Run cmd and inspect stdout, ignore exit status
|
|
17
19
|
try:
|
|
18
20
|
cdout = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8")
|
|
19
|
-
|
|
20
|
-
return True
|
|
21
|
+
return "CD-HIT" in cdout
|
|
21
22
|
except Exception:
|
|
22
23
|
return False
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def strip_tag(name: str) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Remove the relabeling prefix (TAG1/TAG2, same length) from a sequence name.
|
|
29
|
+
"""
|
|
30
|
+
return name[len(TAG1):]
|
|
31
|
+
|
|
32
|
+
|
|
24
33
|
def read_fasta(path):
|
|
25
34
|
name = None
|
|
26
35
|
comment = ""
|
|
@@ -38,11 +47,13 @@ def read_fasta(path):
|
|
|
38
47
|
seq += line
|
|
39
48
|
yield name, comment, seq
|
|
40
49
|
|
|
50
|
+
|
|
41
51
|
def relabel_fasta(path, prefix, outpath):
|
|
42
52
|
with xopen(outpath, "a") as out:
|
|
43
53
|
for name, comment, seq in read_fasta(path):
|
|
44
54
|
out.write(">{}{}{}{}\n{}\n".format(prefix, name, " " if len(comment) > 1 else "", comment, seq))
|
|
45
55
|
|
|
56
|
+
|
|
46
57
|
def split_cluster(cluster, tag1, tag2):
|
|
47
58
|
pool1, pool2 = [], []
|
|
48
59
|
for sequence in cluster.sequences:
|
|
@@ -52,17 +63,19 @@ def split_cluster(cluster, tag1, tag2):
|
|
|
52
63
|
pool2.append(sequence)
|
|
53
64
|
else:
|
|
54
65
|
raise ValueError("Sequence {} does not start with {} or {}".format(sequence.name, tag1, tag2))
|
|
66
|
+
|
|
67
|
+
|
|
55
68
|
@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
|
|
56
69
|
@click.version_option(__version__)
|
|
57
70
|
@click.argument("fasta1", type=click.Path(exists=True))
|
|
58
71
|
@click.argument("fasta2", type=click.Path(exists=True))
|
|
59
|
-
@click.option("--tag1",
|
|
60
|
-
@click.option("--tag2",
|
|
72
|
+
@click.option("--tag1", help="Name of the first dataset")
|
|
73
|
+
@click.option("--tag2", help="Name of the second dataset")
|
|
61
74
|
@click.option("--id", help="Identity threshold [default: 0.9]", default=0.95, type=float)
|
|
62
75
|
@click.option("--type", type=click.STRING, help="Type of the sequences (nucl or prot)")
|
|
63
|
-
@click.option("--tempdir",type=click.Path(exists=True), help="Temporary directory for intermediate files", default=tempfile.gettempdir())
|
|
76
|
+
@click.option("--tempdir", type=click.Path(exists=True), help="Temporary directory for intermediate files", default=tempfile.gettempdir())
|
|
64
77
|
@click.option("--verbose", default=False, is_flag=True, help="Show verbose information")
|
|
65
|
-
def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float,verbose: bool):
|
|
78
|
+
def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float, verbose: bool):
|
|
66
79
|
"""
|
|
67
80
|
Compare FASTA files
|
|
68
81
|
|
|
@@ -75,12 +88,9 @@ def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float,
|
|
|
75
88
|
click.echo("cd-hit is not installed. Please install it and try again.")
|
|
76
89
|
sys.exit(1)
|
|
77
90
|
# Generate temporary directory inside "tempdir"
|
|
78
|
-
TAG1 = "1:::"
|
|
79
|
-
TAG2 = "2:::"
|
|
80
91
|
# tmp is a temporary directory not to be deleted
|
|
81
92
|
tmp = tempfile.mkdtemp(dir=tempdir, prefix="cdhit_")
|
|
82
93
|
|
|
83
|
-
#tmp = tempfile.TemporaryDirectory(dir=tempdir, prefix="cdhit_reader_", suffix=".tmp")
|
|
84
94
|
if verbose:
|
|
85
95
|
print("Temporary directory: {}".format(tmp), file=sys.stderr)
|
|
86
96
|
|
|
@@ -97,42 +107,41 @@ def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float,
|
|
|
97
107
|
if prefix1 == prefix2:
|
|
98
108
|
print("Warning: prefixes are identical ({}): specify manual prefixes with --tag1 and --tag2".format(prefix1))
|
|
99
109
|
exit(1)
|
|
100
|
-
|
|
110
|
+
|
|
101
111
|
# Delete fasta_file if present:
|
|
102
112
|
if os.path.exists(fasta_file):
|
|
103
113
|
os.remove(fasta_file)
|
|
104
|
-
|
|
114
|
+
|
|
105
115
|
if verbose:
|
|
106
116
|
print("Relabeling {} to {}".format(fasta1, fasta_file), file=sys.stderr)
|
|
107
|
-
|
|
117
|
+
|
|
108
118
|
relabel_fasta(fasta1, TAG1, fasta_file)
|
|
109
119
|
|
|
110
120
|
if verbose:
|
|
111
121
|
print("Relabeling {} to {}".format(fasta2, fasta_file), file=sys.stderr)
|
|
112
|
-
|
|
122
|
+
|
|
113
123
|
relabel_fasta(fasta2, TAG2, fasta_file)
|
|
114
124
|
|
|
115
125
|
tags = {
|
|
116
126
|
TAG1: prefix1,
|
|
117
127
|
TAG2: prefix2
|
|
118
|
-
}
|
|
128
|
+
}
|
|
119
129
|
if verbose:
|
|
120
130
|
print("Relabeling {} to {} (prefix: {})".format(fasta1, fasta_file, prefix1), file=sys.stderr)
|
|
121
131
|
print("Relabeling {} to {} (prefix: {})".format(fasta2, fasta_file, prefix2), file=sys.stderr)
|
|
122
|
-
|
|
123
|
-
cmd = ["cd-hit" if type=="prot" else "cd-hit-est", "-i", fasta_file, "-o", clstr_file, "-c", str(id), "-d", "1000"]
|
|
132
|
+
|
|
133
|
+
cmd = ["cd-hit" if type == "prot" else "cd-hit-est", "-i", fasta_file, "-o", clstr_file, "-c", str(id), "-d", "1000"]
|
|
124
134
|
if verbose:
|
|
125
135
|
print("Running {}".format(" ".join(cmd)), file=sys.stderr)
|
|
126
136
|
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
127
137
|
|
|
128
|
-
|
|
129
|
-
stats = {TAG1: [], TAG2: [], "both": [], "multi": [], "dupl_" + TAG1: [], "dupl_" + TAG2: []}
|
|
138
|
+
stats = {TAG1: [], TAG2: [], "both": [], "multi": [], "dupl_" + TAG1: [], "dupl_" + TAG2: []}
|
|
130
139
|
for cluster in read_cdhit(clstr_file + ".clstr"):
|
|
131
|
-
|
|
132
|
-
pool =
|
|
140
|
+
|
|
141
|
+
pool = cluster.refname[:len(TAG1)]
|
|
133
142
|
if pool != TAG1 and pool != TAG2:
|
|
134
143
|
raise ValueError("Cluster {} does not start with {} or {}".format(cluster.refname, TAG1, TAG2))
|
|
135
|
-
seqname = cluster.refname
|
|
144
|
+
seqname = strip_tag(cluster.refname)
|
|
136
145
|
if len(cluster) == 1:
|
|
137
146
|
# Singleton
|
|
138
147
|
stats[pool].append(seqname)
|
|
@@ -141,31 +150,28 @@ def compare(fasta1, fasta2, tag1: str, tag2: str, tempdir, type: str, id: float,
|
|
|
141
150
|
pair = []
|
|
142
151
|
check = False
|
|
143
152
|
for seq in cluster.sequences:
|
|
144
|
-
sub_pool =
|
|
145
|
-
sub_seqname = (seq.name)
|
|
153
|
+
sub_pool = seq.name[:len(TAG1)]
|
|
154
|
+
sub_seqname = strip_tag(seq.name)
|
|
146
155
|
pair.append(sub_seqname)
|
|
147
156
|
if sub_pool != pool:
|
|
148
157
|
check = True
|
|
149
|
-
if check
|
|
158
|
+
if check:
|
|
150
159
|
stats["both"].append(":".join(pair))
|
|
151
160
|
else:
|
|
152
|
-
stats["dupl_" + pool
|
|
161
|
+
stats["dupl_" + pool].append(":".join(pair))
|
|
153
162
|
else:
|
|
154
|
-
|
|
163
|
+
|
|
155
164
|
stats["multi"].append(",".join(i.name for i in cluster.sequences))
|
|
156
|
-
|
|
157
165
|
|
|
158
|
-
for key,
|
|
159
|
-
print("{} {}".format(key, len(
|
|
160
|
-
for seqnames in
|
|
166
|
+
for key, seqnames_list in stats.items():
|
|
167
|
+
print("{} {}".format(key, len(seqnames_list)), file=sys.stderr)
|
|
168
|
+
for seqnames in seqnames_list:
|
|
161
169
|
key = tags[key] if key in tags else key
|
|
162
|
-
key = "dupl_" + tags[key[-1*len(TAG1):]] if key[-1*len(TAG1):] in tags else key
|
|
170
|
+
key = "dupl_" + tags[key[-1 * len(TAG1):]] if key[-1 * len(TAG1):] in tags else key
|
|
163
171
|
seqnames = seqnames.replace(TAG1, tags[TAG1] + "#").replace(TAG2, tags[TAG2] + "#")
|
|
164
|
-
|
|
172
|
+
|
|
165
173
|
print(key, seqnames, sep="\t")
|
|
166
174
|
|
|
167
|
-
#if not verbose:
|
|
168
|
-
# os.remove(tmp)
|
|
169
175
|
|
|
170
176
|
def show_hist(seq_lens):
|
|
171
177
|
pass
|