ecbackoff 1.1__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.
ecbackoff-1.1/LICENSE ADDED
@@ -0,0 +1,35 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the authors of ecbackoff
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.
22
+
23
+ Third-party data
24
+
25
+ The ecbackoff code is licensed under MIT above. The bundled reference and
26
+ example sequences are subsets of UniProtKB/Swiss-Prot from the UniProt
27
+ Consortium (https://www.uniprot.org/uniprot). The reference sequences were
28
+ selected from release 2026_04. The reference table combines selected
29
+ UniProtKB/Swiss-Prot annotations with data from the ENZYME nomenclature
30
+ database, maintained by the Swiss-Prot group at the SIB Swiss Institute
31
+ of Bioinformatics (https://enzyme.expasy.org/). The sequences and
32
+ annotations were selected and reformatted for ecbackoff.
33
+
34
+ The source databases are licensed under the Creative Commons Attribution
35
+ 4.0 International license (https://creativecommons.org/licenses/by/4.0/).
ecbackoff-1.1/PKG-INFO ADDED
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: ecbackoff
3
+ Version: 1.1
4
+ Summary: Inherit an enzyme's EC number only where the neighbourhood of its nearest characterized relative supports it; otherwise three digits, or nothing.
5
+ License-Expression: MIT AND CC-BY-4.0
6
+ Keywords: enzyme,EC number,function transfer,annotation,MMseqs2,bioinformatics
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ # ecbackoff
18
+
19
+ **Inherit an enzyme's EC number only where the neighbourhood of its nearest relative supports it; otherwise say three digits, or nothing.**
20
+
21
+ Most enzyme annotation still inherits the EC number of the nearest characterized relative when sequence identity passes a threshold, by convention 60% for the complete EC number and 40% for its first three digits. `ecbackoff` is an alternative to that global rule. For every query protein it looks at the *neighbourhood* of the nearest characterized enzyme (how close it is, how many relatives there are, whether they agree, how many different functions they carry, how common the nearest function is), estimates the probability that inheriting would be wrong, and then says one of three things:
22
+
23
+ | statement | meaning |
24
+ |---|---|
25
+ | `EC4` | inherit the complete four-digit EC number(s) of the nearest enzyme |
26
+ | `EC3` | the sequence is probably in the same sub-subclass (first three digits) but the full number should not be inherited |
27
+ | `none` | the neighbourhood does not support a statement; do not inherit |
28
+
29
+ It is a rule for deciding whether to inherit, not a function predictor. It needs no deep-learning framework and no GPU: Python standard library plus one MMseqs2 search.
30
+
31
+ The method and its evaluation are described in: *[manuscript, to be added]*.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install ecbackoff # once it is on PyPI; or: pip install ecbackoff-<version>-py3-none-any.whl
37
+ ecbackoff selftest # checks the installation, no MMseqs2 needed
38
+ ecbackoff run -q my_proteins.fasta -o result.tsv --threads 8
39
+ ```
40
+
41
+ The package has no Python dependencies and contains the model and the reference data (about 6 MB). **MMseqs2 is not installed by pip**; the `run` command needs it on the `PATH` (for example `conda install -c bioconda mmseqs2`, or the release at https://github.com/soedinglab/MMseqs2). Everything below that says `python ecbackoff.py` also works as `ecbackoff` or `python -m ecbackoff`.
42
+
43
+ ## Run from the unpacked folder (without pip)
44
+
45
+ Requirements: Python 3.8 or newer; [MMseqs2](https://github.com/soedinglab/MMseqs2) on the `PATH` (or given with `--mmseqs`) for the `run` command (the model was built and checked with 18.8cc5c, commit 8cc5c; another version gives a warning). Nothing else to install: unpack the folder and run `python ecbackoff.py`.
46
+
47
+ Tested on Linux (`selftest` with Python 3.9 and 3.11; the end-to-end checks below with Python 3.11 and MMseqs2 18.8cc5c) and on Windows 11 (Python 3.14 and the official Windows build of MMseqs2 18-8cc5c, `mmseqs-win64.zip`; the program, its self-test and the command-line tests passed there, and the Windows search gave the same hit table and the same result table for the 30 bundled examples as Linux; paths with spaces and Chinese characters worked). Not tested on macOS.
48
+
49
+ ```bash
50
+ python ecbackoff.py selftest # checks the files and the logic, no MMseqs2 needed
51
+ python ecbackoff.py run -q my_proteins.fasta -o result.tsv --threads 8
52
+ ```
53
+
54
+ **MMseqs2 on Windows.** Unpack `mmseqs-win64.zip`, then run `mmseqs\mmseqs.bat` once: it installs the BusyBox helper programs that MMseqs2's search workflows need (it may ask for administrator rights, or run `mmseqs\bin\busybox.exe --install -s mmseqs\bin` yourself). Without this step `run` stops with "Failed to execute ... easysearch.sh". Then put `mmseqs\bin` on the `PATH`, or pass `--mmseqs C:\path\to\mmseqs\bin\mmseqs.exe`.
55
+
56
+ Size and speed: the unpacked folder is about 6 MB; a run needs room for the decompressed reference (about 10 MB) and the MMseqs2 work files (`--tmpdir` chooses where). A run on 400 proteins with 8 threads took under 5 seconds on a Linux cluster node and about 24 seconds on a Windows PC, search included.
57
+
58
+ If you prefer to run the search yourself:
59
+
60
+ ```bash
61
+ python ecbackoff.py reference -o reference.fasta # the bundled reference sequences
62
+ mmseqs easy-search my_proteins.fasta reference.fasta hits.m8 tmp -s 7.5 -e 1e-3 --max-seqs 20000 --alignment-mode 3 \
63
+ --format-output query,target,fident,qcov,tcov,evalue,bits
64
+ python ecbackoff.py score -q my_proteins.fasta --hits hits.m8 -o result.tsv
65
+ ```
66
+
67
+ The search settings matter: the model was trained on hit tables from exactly this search. Hit tables from BLAST, DIAMOND or other MMseqs2 settings have not been validated.
68
+
69
+ **Identifiers.** The identifier is the first word of each FASTA header. MMseqs2 prints the accession of UniProt-style headers (`sp|P12345|NAME` appears as `P12345` in the hit table); the program accepts both forms. Hits to the query itself (same identifier, or the accession inside a UniProt-style identifier) are dropped unless you give `--keep-self`; this matters only if your queries include enzymes that are in the reference.
70
+
71
+ **Input checks.** The program stops with a message, before searching, on a missing or empty file, a file that is not FASTA, a repeated identifier; and on a hit table with the wrong number of columns, percentages instead of fractions, or identifiers that are not in the FASTA. It tolerates a byte-order mark, Windows line ends, lower case and wrapped lines. Empty sequences are not searched and get `none` with a note.
72
+
73
+ **Record of a run.** With `-o result.tsv` the program also writes `result.tsv.params.json`: version, command line, mode and cutoffs, prediction year, MMseqs2 version and settings, checksums of the inputs, counts. `--no-params` switches this off.
74
+
75
+ ## Output
76
+
77
+ One row per query, in the order of the FASTA, tab-separated. Example (first rows of `build/tool400_output.tsv`, cutoff mode):
78
+
79
+ | query | nearest_enzyme | identity | p_wrong_4digit | p_wrong_3digit | statement | statement_ec | identity_rule |
80
+ |---|---|---|---|---|---|---|---|
81
+ | Q3YZV5 | P52006 | 0.985 | 0.0036 | 0.0002 | EC4 | 3.6.1.12;3.6.1.23;3.6.1.9 | EC4 |
82
+ | P68068 | Q8WWM9 | 0.256 | 0.1733 | 0.0525 | EC3 | 1.15.1 | none |
83
+ | P0DD38 | P0C0J0 | 1.000 | 0.0026 | 0.0017 | EC4 | 3.4.22.10 | EC4 |
84
+ | P41498 | P40347 | 0.444 | 0.0769 | 0.0501 | EC4 | 3.1.3.2;3.1.3.48 | EC3 |
85
+
86
+ (Columns `nearest_ec`, `n_neighbours` and `note` left out here. The 400 proteins of that file were sampled from proteins that have a neighbour, so every one of them has a statement or a probability; in a real batch, proteins without a neighbour get `none`. Rows 2 and 4 show where the rule differs from the identity rule: a protein at 26% identity receives the sub-subclass where the identity rule says nothing, and one at 44% identity receives the full number where the identity rule says three digits.)
87
+
88
+ | column | content |
89
+ |---|---|
90
+ | `query` | identifier |
91
+ | `nearest_enzyme` | accession of the nearest reference enzyme (neighbours need coverage of at least 70% of both sequences) |
92
+ | `identity` | identity to it |
93
+ | `nearest_ec` | its EC number(s) |
94
+ | `n_neighbours` | number of qualifying neighbours |
95
+ | `p_wrong_4digit` | estimated probability that none of the nearest enzyme's EC numbers is among the protein's |
96
+ | `p_wrong_3digit` | estimated probability that the first three digits of the nearest enzyme's EC numbers share nothing with the protein's (never above `p_wrong_4digit`, because a three-digit error is also a four-digit error) |
97
+ | `statement` | `EC4`, `EC3` or `none` |
98
+ | `statement_ec` | the EC number(s) for `EC4`; the sub-subclass(es) for `EC3` |
99
+ | `identity_rule` | what the plain 60%/40% identity rule would say, for comparison |
100
+ | `note` | why there is no statement, if there is none |
101
+
102
+ A protein with no neighbour covering 70% of both sequences gets `none` and empty probabilities.
103
+
104
+ ## Modes
105
+
106
+ * `--mode cutoff` (default). `EC4` if `p_wrong_4digit` is at most `--cutoff4` (default 0.10); otherwise `EC3` if `p_wrong_3digit` is at most `--cutoff3` (default 0.10); otherwise `none`. Works for any batch size, including a single protein. The defaults were chosen after looking at the replay, so the numbers below for this mode are descriptive.
107
+ * `--mode match`. Gives exactly as many `EC4` and `EC3` statements as the identity rule gives on this batch (`--identity-hi 0.6`, `--identity-lo 0.4`) and chooses which proteins receive them by risk. This is the comparison made in the paper, and the headline numbers below are from it; it is meaningful only for batches of several hundred proteins, because for a very small batch it collapses to the identity rule.
108
+
109
+ Use the probabilities for ranking and for choosing your own cutoffs; see the limits before relying on their absolute values. `--year` changes the prediction year, which enters two features (years since the nearest enzyme was established, and the number of earlier proteins carrying its EC number); values other than the frozen 2027 are outside what was validated.
110
+
111
+ ## Evidence
112
+
113
+ All numbers are retrospective. They come from replays of UniProtKB/Swiss-Prot in forward time in which each protein was annotated using only what was known before it, with models trained on earlier data only, and the statements were scored against the experimentally characterized function. Intervals are 95% intervals from resampling sequence clusters.
114
+
115
+ * **Main replay, 2000–2024, 10,559 proteins with a characterized neighbour (one 2026 snapshot dated by the papers cited).** At the same number of statements as the 60%/40% identity rule (4,013 four-digit and 3,255 three-digit statements; `match` mode): 3.1% (2.6–3.6) of the model's statements were wrong against 7.5% (5.5–9.9) for the identity rule (difference −4.4 points, −6.9 to −2.5; four digits 3.5% against 9.7%, three digits 2.6% against 4.8%); 3.0 more correct statements per 100 proteins. At the error rate of the 60% rule the model could give a four-digit statement to 61% of the proteins instead of 38%.
116
+ * **Release-clock replay with the labels recorded at the time (3,572 proteins, 2016–2026).** 7.8% (6.3–9.3) wrong against 15.4% (11.8–19.1) for the identity rule, same number of statements.
117
+ * **The 60% rule itself was about as accurate as intended:** 9.7% of its four-digit statements were wrong. The gain is fewer errors, or more statements at the same error rate, not a repair of a miscalibrated rule.
118
+ * **How much is family information.** An identity rule calibrated within Pfam families on earlier proteins (our own implementation, not the software of any earlier method) was wrong in 4.3% (3.7–4.9) in the main replay. Calibration within families gave 49–80% of the gain over the global thresholds, depending on the replay. The neighbourhood model's further gain over that rule was 1.2 points (0.5–1.8) in the main replay and 3.9 on the release clock, and was **not shown** (0.45 points, −0.96 to 0.03) after removing the 32.6% of proteins whose own or nearest enzyme's EC number was created after the test year. Treat the neighbourhood's advantage over a family-calibrated rule as the least secure result.
119
+ * **Default cutoffs in the main replay** (post hoc): `EC4` for 41.3% of proteins with 3.9% wrong; `EC3` for a further 41.9% with 4.5% wrong; `none` for 16.9%.
120
+ * **Proteins from sequence clusters with no earlier member** (11.9% of the replay; the identity rules make almost no statement there): the model ranked transfer failures with an AUC of 0.79 (identity 0.54), post hoc; the default cutoffs gave `EC4` to 57 of 1,256 proteins with 8.8% wrong and `EC3` to 347 with 9.8% wrong, so errors are more frequent than in the replay as a whole.
121
+
122
+ ## Limits (please read)
123
+
124
+ * **It cannot name a function that is absent from the reference.** About one in five proteins that have a characterized homolog carry a function that no earlier protein in Swiss-Prot had; for those, every `EC4` statement is wrong, and the rule only tries to keep them out. About half of the errors it removes are of this kind.
125
+ * **The probabilities are approximate.** They followed observed frequencies in the replay but ran somewhat high in the upper deciles, and in an external check on present-day proteins with independent experimental annotations, transfer failed more often than predicted (16.7% against 13.0% for proteins with a propagated EC number; 62.8% against 39.6% for proteins with no catalytic annotation). Choose cutoffs on data of your own if you can.
126
+ * **Only proteins with a neighbour covering 70% of both sequences are scored**; multidomain proteins and fragments often get `none`.
127
+ * **The reference is Swiss-Prot 2026_04 only**: 18,517 experimentally characterized enzymes with a complete EC number (5,289 EC numbers). It is a static snapshot; a protein whose relatives are not in it gets `none`.
128
+ * **All evidence is retrospective**, on labels, evidence tags and Pfam assignments as they stand in 2026; the main replay includes EC numbers created after the year replayed (see above). The only prospective test is the protocol for scoring future releases with the frozen model, which has not yet produced a result.
129
+ * **The model was fitted on enzymes that someone chose to study**, and the replay says nothing about proteins that a rule rejects.
130
+ * **The three-digit model and the cutoffs were added when this tool was built**: the three-digit model has the same features, scaler and training rows as the frozen four-digit model, and was not part of a pre-specified analysis. The risk of a three-digit error is capped at the risk of a four-digit error; in the replay this changed no statement.
131
+ * **Other search tools or settings are not validated**, and queries that are themselves in the reference are not what the model was evaluated on.
132
+ * Tested on Linux and Windows 11, not on macOS (see above).
133
+
134
+ ## How it works
135
+
136
+ For each query the program takes all reference enzymes aligned over at least 70% of both sequences, ranked by bit score, and computes 18 features: identity, log bit score and the two coverages of the nearest one; the relative bit-score gap to the second; log number of neighbours; the fraction of the next nine that share an EC number, or its first three digits, with the nearest; whether there is only one neighbour; log number of distinct EC numbers and of distinct sub-subclasses among the ten nearest; the number of EC numbers of the nearest; log query length and absolute log length ratio; log number of earlier protein–EC pairs carrying the nearest enzyme's EC number; log number of distinct EC numbers among all neighbours; identity to the nearest enzyme that shares no EC number with the nearest; and years since the nearest enzyme was established. Two logistic regressions (standardized features, C = 1) turn them into `p_wrong_4digit` and `p_wrong_3digit`. All coefficients are in `data/model.json`.
137
+
138
+ Three of the features carry most of the signal: identity to the nearest enzyme, the number of distinct EC numbers among all neighbours, and how many earlier proteins carry the nearest enzyme's EC number.
139
+
140
+ ## Files
141
+
142
+ (This table describes the unpacked folder of the zip / tar.gz release. In the pip package the program is `ecbackoff/__init__.py` and `data/` and `example/` sit inside the installed `ecbackoff` folder; `build/` is only in the release.)
143
+
144
+ | file | content |
145
+ |---|---|
146
+ | `ecbackoff.py` | the program |
147
+ | `data/model.json` | scaler and coefficients of the two models, search settings, evidence |
148
+ | `data/reference.tsv.gz` | reference enzymes: accession, length, year of entry, EC numbers with the year each was established |
149
+ | `data/reference.fasta.gz` | their sequences (for the search) |
150
+ | `data/CHECKSUMS.sha256` | checksums of the data and example files, checked by `selftest` |
151
+ | `example/` | 30 example proteins, their MMseqs2 hits and the expected output of the frozen model; used by `selftest` |
152
+ | `build/` | the scripts that built the bundle and ran the checks below, with their outputs. `build_bundle.py` and the job scripts read the project's files on the cluster and cannot be run elsewhere; `edge_tests.py` (needs MMseqs2) and `win_tests.py` only need this folder. They are provenance, not part of using the tool |
153
+
154
+ ## Checks
155
+
156
+ * **`selftest`** (passes with Python 3.9 and 3.11): the checksums of the data and example files; the reference table and the reference FASTA list the same 18,517 enzymes; the 30 bundled example proteins reproduce the released scores of the frozen model from their stored hits (maximum difference 5e-11); the three-digit risk is never above the four-digit risk; `match` mode gives the numbers of statements of the identity rule; and edge cases built from the example: a query with no neighbour, an empty sequence, a `sp|ACCESSION|NAME` identifier whose hits carry the accession only, a hit to the query itself (dropped, and kept with `--keep-self`), and a hit table with percentages (refused).
157
+ * **End-to-end on 400 proteins** from the released score table of the frozen model, with the program running the MMseqs2 search itself: all 400 have the same nearest enzyme as in the released table, and the two probabilities agree with the released values to within the four decimals of the output table (`build/verify_400.json`; the largest differences, 4.96e-5 and 5.00e-5, are the rounding of the fourth decimal). In an earlier build the 18 features agreed with the stored features to 1e-9 and did not depend on the number of search threads (`build/diag.py`).
158
+ * **`run` and `score`** (with the hit table written by `run --keep-hits`) give identical tables.
159
+ * **Command line with the real search** (`build/edge_tests.py`, 18 checks, all passed; `build/edge_tests.json`): UniProt-style identifiers (MMseqs2 prints only the accession, which the program resolves); a file with a byte-order mark, Windows line ends, lower case and wrapped lines; empty and unmatched queries; the parameter file; refusal of repeated identifiers, a missing or non-FASTA file, a wrong `--mmseqs`, a missing `--tmpdir`, percentages, unknown identifiers and too few columns in a hit table; `--tmpdir` being used and cleaned up; a pipe closed early; the `reference` command.
160
+ * **Speed:** the two runs on 400 proteins in the final check took under 5 seconds each with 8 threads (`build/check_1986889.out`).
161
+ * **Windows 11, Python 3.14, official MMseqs2 Windows build (checked after the Linux build; the scripts in `build/` that ran the Linux checks were made portable for this):** `selftest` passed; `score` on the 30 examples reproduced the released scores to the four printed decimals; `run` on the 30 examples gave a hit table identical to the Linux one (after sorting) and an identical result table; the 18 command-line checks of `build/edge_tests.py` passed with the real search; 18 further checks of `score`, input validation and error messages passed with paths containing spaces and Chinese characters; a run on 400 proteins took about 24 seconds with 8 threads.
162
+ * **Not checked:** macOS; other MMseqs2 versions; hit tables from other search tools; batches of many thousands of proteins.
163
+
164
+ ## Licence and data
165
+
166
+ The ecbackoff code is licensed under MIT. The reference and example sequences are subsets of [UniProtKB/Swiss-Prot](https://www.uniprot.org/uniprot) from the UniProt Consortium. The reference table combines selected EC annotations with data from the [ENZYME nomenclature database](https://enzyme.expasy.org/) maintained by the Swiss-Prot group at the SIB Swiss Institute of Bioinformatics. The sequences and annotations were selected and reformatted for ecbackoff. Both source databases use [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). See `LICENSE`.
167
+
168
+ ## Citation
169
+
170
+ Please cite the manuscript *[to be added when it is public]* and the resources used:
171
+
172
+ * The UniProt Consortium. UniProt: the Universal Protein Knowledgebase in 2025. *Nucleic Acids Res.* 53, D609–D617 (2025).
173
+ * Bairoch, A. The ENZYME database in 2000. *Nucleic Acids Res.* 28, 304-305 (2000). [DOI](https://doi.org/10.1093/nar/28.1.304).
174
+ * Steinegger, M. & Söding, J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. *Nat. Biotechnol.* 35, 1026–1028 (2017).
@@ -0,0 +1,158 @@
1
+ # ecbackoff
2
+
3
+ **Inherit an enzyme's EC number only where the neighbourhood of its nearest relative supports it; otherwise say three digits, or nothing.**
4
+
5
+ Most enzyme annotation still inherits the EC number of the nearest characterized relative when sequence identity passes a threshold, by convention 60% for the complete EC number and 40% for its first three digits. `ecbackoff` is an alternative to that global rule. For every query protein it looks at the *neighbourhood* of the nearest characterized enzyme (how close it is, how many relatives there are, whether they agree, how many different functions they carry, how common the nearest function is), estimates the probability that inheriting would be wrong, and then says one of three things:
6
+
7
+ | statement | meaning |
8
+ |---|---|
9
+ | `EC4` | inherit the complete four-digit EC number(s) of the nearest enzyme |
10
+ | `EC3` | the sequence is probably in the same sub-subclass (first three digits) but the full number should not be inherited |
11
+ | `none` | the neighbourhood does not support a statement; do not inherit |
12
+
13
+ It is a rule for deciding whether to inherit, not a function predictor. It needs no deep-learning framework and no GPU: Python standard library plus one MMseqs2 search.
14
+
15
+ The method and its evaluation are described in: *[manuscript, to be added]*.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install ecbackoff # once it is on PyPI; or: pip install ecbackoff-<version>-py3-none-any.whl
21
+ ecbackoff selftest # checks the installation, no MMseqs2 needed
22
+ ecbackoff run -q my_proteins.fasta -o result.tsv --threads 8
23
+ ```
24
+
25
+ The package has no Python dependencies and contains the model and the reference data (about 6 MB). **MMseqs2 is not installed by pip**; the `run` command needs it on the `PATH` (for example `conda install -c bioconda mmseqs2`, or the release at https://github.com/soedinglab/MMseqs2). Everything below that says `python ecbackoff.py` also works as `ecbackoff` or `python -m ecbackoff`.
26
+
27
+ ## Run from the unpacked folder (without pip)
28
+
29
+ Requirements: Python 3.8 or newer; [MMseqs2](https://github.com/soedinglab/MMseqs2) on the `PATH` (or given with `--mmseqs`) for the `run` command (the model was built and checked with 18.8cc5c, commit 8cc5c; another version gives a warning). Nothing else to install: unpack the folder and run `python ecbackoff.py`.
30
+
31
+ Tested on Linux (`selftest` with Python 3.9 and 3.11; the end-to-end checks below with Python 3.11 and MMseqs2 18.8cc5c) and on Windows 11 (Python 3.14 and the official Windows build of MMseqs2 18-8cc5c, `mmseqs-win64.zip`; the program, its self-test and the command-line tests passed there, and the Windows search gave the same hit table and the same result table for the 30 bundled examples as Linux; paths with spaces and Chinese characters worked). Not tested on macOS.
32
+
33
+ ```bash
34
+ python ecbackoff.py selftest # checks the files and the logic, no MMseqs2 needed
35
+ python ecbackoff.py run -q my_proteins.fasta -o result.tsv --threads 8
36
+ ```
37
+
38
+ **MMseqs2 on Windows.** Unpack `mmseqs-win64.zip`, then run `mmseqs\mmseqs.bat` once: it installs the BusyBox helper programs that MMseqs2's search workflows need (it may ask for administrator rights, or run `mmseqs\bin\busybox.exe --install -s mmseqs\bin` yourself). Without this step `run` stops with "Failed to execute ... easysearch.sh". Then put `mmseqs\bin` on the `PATH`, or pass `--mmseqs C:\path\to\mmseqs\bin\mmseqs.exe`.
39
+
40
+ Size and speed: the unpacked folder is about 6 MB; a run needs room for the decompressed reference (about 10 MB) and the MMseqs2 work files (`--tmpdir` chooses where). A run on 400 proteins with 8 threads took under 5 seconds on a Linux cluster node and about 24 seconds on a Windows PC, search included.
41
+
42
+ If you prefer to run the search yourself:
43
+
44
+ ```bash
45
+ python ecbackoff.py reference -o reference.fasta # the bundled reference sequences
46
+ mmseqs easy-search my_proteins.fasta reference.fasta hits.m8 tmp -s 7.5 -e 1e-3 --max-seqs 20000 --alignment-mode 3 \
47
+ --format-output query,target,fident,qcov,tcov,evalue,bits
48
+ python ecbackoff.py score -q my_proteins.fasta --hits hits.m8 -o result.tsv
49
+ ```
50
+
51
+ The search settings matter: the model was trained on hit tables from exactly this search. Hit tables from BLAST, DIAMOND or other MMseqs2 settings have not been validated.
52
+
53
+ **Identifiers.** The identifier is the first word of each FASTA header. MMseqs2 prints the accession of UniProt-style headers (`sp|P12345|NAME` appears as `P12345` in the hit table); the program accepts both forms. Hits to the query itself (same identifier, or the accession inside a UniProt-style identifier) are dropped unless you give `--keep-self`; this matters only if your queries include enzymes that are in the reference.
54
+
55
+ **Input checks.** The program stops with a message, before searching, on a missing or empty file, a file that is not FASTA, a repeated identifier; and on a hit table with the wrong number of columns, percentages instead of fractions, or identifiers that are not in the FASTA. It tolerates a byte-order mark, Windows line ends, lower case and wrapped lines. Empty sequences are not searched and get `none` with a note.
56
+
57
+ **Record of a run.** With `-o result.tsv` the program also writes `result.tsv.params.json`: version, command line, mode and cutoffs, prediction year, MMseqs2 version and settings, checksums of the inputs, counts. `--no-params` switches this off.
58
+
59
+ ## Output
60
+
61
+ One row per query, in the order of the FASTA, tab-separated. Example (first rows of `build/tool400_output.tsv`, cutoff mode):
62
+
63
+ | query | nearest_enzyme | identity | p_wrong_4digit | p_wrong_3digit | statement | statement_ec | identity_rule |
64
+ |---|---|---|---|---|---|---|---|
65
+ | Q3YZV5 | P52006 | 0.985 | 0.0036 | 0.0002 | EC4 | 3.6.1.12;3.6.1.23;3.6.1.9 | EC4 |
66
+ | P68068 | Q8WWM9 | 0.256 | 0.1733 | 0.0525 | EC3 | 1.15.1 | none |
67
+ | P0DD38 | P0C0J0 | 1.000 | 0.0026 | 0.0017 | EC4 | 3.4.22.10 | EC4 |
68
+ | P41498 | P40347 | 0.444 | 0.0769 | 0.0501 | EC4 | 3.1.3.2;3.1.3.48 | EC3 |
69
+
70
+ (Columns `nearest_ec`, `n_neighbours` and `note` left out here. The 400 proteins of that file were sampled from proteins that have a neighbour, so every one of them has a statement or a probability; in a real batch, proteins without a neighbour get `none`. Rows 2 and 4 show where the rule differs from the identity rule: a protein at 26% identity receives the sub-subclass where the identity rule says nothing, and one at 44% identity receives the full number where the identity rule says three digits.)
71
+
72
+ | column | content |
73
+ |---|---|
74
+ | `query` | identifier |
75
+ | `nearest_enzyme` | accession of the nearest reference enzyme (neighbours need coverage of at least 70% of both sequences) |
76
+ | `identity` | identity to it |
77
+ | `nearest_ec` | its EC number(s) |
78
+ | `n_neighbours` | number of qualifying neighbours |
79
+ | `p_wrong_4digit` | estimated probability that none of the nearest enzyme's EC numbers is among the protein's |
80
+ | `p_wrong_3digit` | estimated probability that the first three digits of the nearest enzyme's EC numbers share nothing with the protein's (never above `p_wrong_4digit`, because a three-digit error is also a four-digit error) |
81
+ | `statement` | `EC4`, `EC3` or `none` |
82
+ | `statement_ec` | the EC number(s) for `EC4`; the sub-subclass(es) for `EC3` |
83
+ | `identity_rule` | what the plain 60%/40% identity rule would say, for comparison |
84
+ | `note` | why there is no statement, if there is none |
85
+
86
+ A protein with no neighbour covering 70% of both sequences gets `none` and empty probabilities.
87
+
88
+ ## Modes
89
+
90
+ * `--mode cutoff` (default). `EC4` if `p_wrong_4digit` is at most `--cutoff4` (default 0.10); otherwise `EC3` if `p_wrong_3digit` is at most `--cutoff3` (default 0.10); otherwise `none`. Works for any batch size, including a single protein. The defaults were chosen after looking at the replay, so the numbers below for this mode are descriptive.
91
+ * `--mode match`. Gives exactly as many `EC4` and `EC3` statements as the identity rule gives on this batch (`--identity-hi 0.6`, `--identity-lo 0.4`) and chooses which proteins receive them by risk. This is the comparison made in the paper, and the headline numbers below are from it; it is meaningful only for batches of several hundred proteins, because for a very small batch it collapses to the identity rule.
92
+
93
+ Use the probabilities for ranking and for choosing your own cutoffs; see the limits before relying on their absolute values. `--year` changes the prediction year, which enters two features (years since the nearest enzyme was established, and the number of earlier proteins carrying its EC number); values other than the frozen 2027 are outside what was validated.
94
+
95
+ ## Evidence
96
+
97
+ All numbers are retrospective. They come from replays of UniProtKB/Swiss-Prot in forward time in which each protein was annotated using only what was known before it, with models trained on earlier data only, and the statements were scored against the experimentally characterized function. Intervals are 95% intervals from resampling sequence clusters.
98
+
99
+ * **Main replay, 2000–2024, 10,559 proteins with a characterized neighbour (one 2026 snapshot dated by the papers cited).** At the same number of statements as the 60%/40% identity rule (4,013 four-digit and 3,255 three-digit statements; `match` mode): 3.1% (2.6–3.6) of the model's statements were wrong against 7.5% (5.5–9.9) for the identity rule (difference −4.4 points, −6.9 to −2.5; four digits 3.5% against 9.7%, three digits 2.6% against 4.8%); 3.0 more correct statements per 100 proteins. At the error rate of the 60% rule the model could give a four-digit statement to 61% of the proteins instead of 38%.
100
+ * **Release-clock replay with the labels recorded at the time (3,572 proteins, 2016–2026).** 7.8% (6.3–9.3) wrong against 15.4% (11.8–19.1) for the identity rule, same number of statements.
101
+ * **The 60% rule itself was about as accurate as intended:** 9.7% of its four-digit statements were wrong. The gain is fewer errors, or more statements at the same error rate, not a repair of a miscalibrated rule.
102
+ * **How much is family information.** An identity rule calibrated within Pfam families on earlier proteins (our own implementation, not the software of any earlier method) was wrong in 4.3% (3.7–4.9) in the main replay. Calibration within families gave 49–80% of the gain over the global thresholds, depending on the replay. The neighbourhood model's further gain over that rule was 1.2 points (0.5–1.8) in the main replay and 3.9 on the release clock, and was **not shown** (0.45 points, −0.96 to 0.03) after removing the 32.6% of proteins whose own or nearest enzyme's EC number was created after the test year. Treat the neighbourhood's advantage over a family-calibrated rule as the least secure result.
103
+ * **Default cutoffs in the main replay** (post hoc): `EC4` for 41.3% of proteins with 3.9% wrong; `EC3` for a further 41.9% with 4.5% wrong; `none` for 16.9%.
104
+ * **Proteins from sequence clusters with no earlier member** (11.9% of the replay; the identity rules make almost no statement there): the model ranked transfer failures with an AUC of 0.79 (identity 0.54), post hoc; the default cutoffs gave `EC4` to 57 of 1,256 proteins with 8.8% wrong and `EC3` to 347 with 9.8% wrong, so errors are more frequent than in the replay as a whole.
105
+
106
+ ## Limits (please read)
107
+
108
+ * **It cannot name a function that is absent from the reference.** About one in five proteins that have a characterized homolog carry a function that no earlier protein in Swiss-Prot had; for those, every `EC4` statement is wrong, and the rule only tries to keep them out. About half of the errors it removes are of this kind.
109
+ * **The probabilities are approximate.** They followed observed frequencies in the replay but ran somewhat high in the upper deciles, and in an external check on present-day proteins with independent experimental annotations, transfer failed more often than predicted (16.7% against 13.0% for proteins with a propagated EC number; 62.8% against 39.6% for proteins with no catalytic annotation). Choose cutoffs on data of your own if you can.
110
+ * **Only proteins with a neighbour covering 70% of both sequences are scored**; multidomain proteins and fragments often get `none`.
111
+ * **The reference is Swiss-Prot 2026_04 only**: 18,517 experimentally characterized enzymes with a complete EC number (5,289 EC numbers). It is a static snapshot; a protein whose relatives are not in it gets `none`.
112
+ * **All evidence is retrospective**, on labels, evidence tags and Pfam assignments as they stand in 2026; the main replay includes EC numbers created after the year replayed (see above). The only prospective test is the protocol for scoring future releases with the frozen model, which has not yet produced a result.
113
+ * **The model was fitted on enzymes that someone chose to study**, and the replay says nothing about proteins that a rule rejects.
114
+ * **The three-digit model and the cutoffs were added when this tool was built**: the three-digit model has the same features, scaler and training rows as the frozen four-digit model, and was not part of a pre-specified analysis. The risk of a three-digit error is capped at the risk of a four-digit error; in the replay this changed no statement.
115
+ * **Other search tools or settings are not validated**, and queries that are themselves in the reference are not what the model was evaluated on.
116
+ * Tested on Linux and Windows 11, not on macOS (see above).
117
+
118
+ ## How it works
119
+
120
+ For each query the program takes all reference enzymes aligned over at least 70% of both sequences, ranked by bit score, and computes 18 features: identity, log bit score and the two coverages of the nearest one; the relative bit-score gap to the second; log number of neighbours; the fraction of the next nine that share an EC number, or its first three digits, with the nearest; whether there is only one neighbour; log number of distinct EC numbers and of distinct sub-subclasses among the ten nearest; the number of EC numbers of the nearest; log query length and absolute log length ratio; log number of earlier protein–EC pairs carrying the nearest enzyme's EC number; log number of distinct EC numbers among all neighbours; identity to the nearest enzyme that shares no EC number with the nearest; and years since the nearest enzyme was established. Two logistic regressions (standardized features, C = 1) turn them into `p_wrong_4digit` and `p_wrong_3digit`. All coefficients are in `data/model.json`.
121
+
122
+ Three of the features carry most of the signal: identity to the nearest enzyme, the number of distinct EC numbers among all neighbours, and how many earlier proteins carry the nearest enzyme's EC number.
123
+
124
+ ## Files
125
+
126
+ (This table describes the unpacked folder of the zip / tar.gz release. In the pip package the program is `ecbackoff/__init__.py` and `data/` and `example/` sit inside the installed `ecbackoff` folder; `build/` is only in the release.)
127
+
128
+ | file | content |
129
+ |---|---|
130
+ | `ecbackoff.py` | the program |
131
+ | `data/model.json` | scaler and coefficients of the two models, search settings, evidence |
132
+ | `data/reference.tsv.gz` | reference enzymes: accession, length, year of entry, EC numbers with the year each was established |
133
+ | `data/reference.fasta.gz` | their sequences (for the search) |
134
+ | `data/CHECKSUMS.sha256` | checksums of the data and example files, checked by `selftest` |
135
+ | `example/` | 30 example proteins, their MMseqs2 hits and the expected output of the frozen model; used by `selftest` |
136
+ | `build/` | the scripts that built the bundle and ran the checks below, with their outputs. `build_bundle.py` and the job scripts read the project's files on the cluster and cannot be run elsewhere; `edge_tests.py` (needs MMseqs2) and `win_tests.py` only need this folder. They are provenance, not part of using the tool |
137
+
138
+ ## Checks
139
+
140
+ * **`selftest`** (passes with Python 3.9 and 3.11): the checksums of the data and example files; the reference table and the reference FASTA list the same 18,517 enzymes; the 30 bundled example proteins reproduce the released scores of the frozen model from their stored hits (maximum difference 5e-11); the three-digit risk is never above the four-digit risk; `match` mode gives the numbers of statements of the identity rule; and edge cases built from the example: a query with no neighbour, an empty sequence, a `sp|ACCESSION|NAME` identifier whose hits carry the accession only, a hit to the query itself (dropped, and kept with `--keep-self`), and a hit table with percentages (refused).
141
+ * **End-to-end on 400 proteins** from the released score table of the frozen model, with the program running the MMseqs2 search itself: all 400 have the same nearest enzyme as in the released table, and the two probabilities agree with the released values to within the four decimals of the output table (`build/verify_400.json`; the largest differences, 4.96e-5 and 5.00e-5, are the rounding of the fourth decimal). In an earlier build the 18 features agreed with the stored features to 1e-9 and did not depend on the number of search threads (`build/diag.py`).
142
+ * **`run` and `score`** (with the hit table written by `run --keep-hits`) give identical tables.
143
+ * **Command line with the real search** (`build/edge_tests.py`, 18 checks, all passed; `build/edge_tests.json`): UniProt-style identifiers (MMseqs2 prints only the accession, which the program resolves); a file with a byte-order mark, Windows line ends, lower case and wrapped lines; empty and unmatched queries; the parameter file; refusal of repeated identifiers, a missing or non-FASTA file, a wrong `--mmseqs`, a missing `--tmpdir`, percentages, unknown identifiers and too few columns in a hit table; `--tmpdir` being used and cleaned up; a pipe closed early; the `reference` command.
144
+ * **Speed:** the two runs on 400 proteins in the final check took under 5 seconds each with 8 threads (`build/check_1986889.out`).
145
+ * **Windows 11, Python 3.14, official MMseqs2 Windows build (checked after the Linux build; the scripts in `build/` that ran the Linux checks were made portable for this):** `selftest` passed; `score` on the 30 examples reproduced the released scores to the four printed decimals; `run` on the 30 examples gave a hit table identical to the Linux one (after sorting) and an identical result table; the 18 command-line checks of `build/edge_tests.py` passed with the real search; 18 further checks of `score`, input validation and error messages passed with paths containing spaces and Chinese characters; a run on 400 proteins took about 24 seconds with 8 threads.
146
+ * **Not checked:** macOS; other MMseqs2 versions; hit tables from other search tools; batches of many thousands of proteins.
147
+
148
+ ## Licence and data
149
+
150
+ The ecbackoff code is licensed under MIT. The reference and example sequences are subsets of [UniProtKB/Swiss-Prot](https://www.uniprot.org/uniprot) from the UniProt Consortium. The reference table combines selected EC annotations with data from the [ENZYME nomenclature database](https://enzyme.expasy.org/) maintained by the Swiss-Prot group at the SIB Swiss Institute of Bioinformatics. The sequences and annotations were selected and reformatted for ecbackoff. Both source databases use [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). See `LICENSE`.
151
+
152
+ ## Citation
153
+
154
+ Please cite the manuscript *[to be added when it is public]* and the resources used:
155
+
156
+ * The UniProt Consortium. UniProt: the Universal Protein Knowledgebase in 2025. *Nucleic Acids Res.* 53, D609–D617 (2025).
157
+ * Bairoch, A. The ENZYME database in 2000. *Nucleic Acids Res.* 28, 304-305 (2000). [DOI](https://doi.org/10.1093/nar/28.1.304).
158
+ * Steinegger, M. & Söding, J. MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. *Nat. Biotechnol.* 35, 1026–1028 (2017).
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ecbackoff"
7
+ dynamic = ["version"]
8
+ description = "Inherit an enzyme's EC number only where the neighbourhood of its nearest characterized relative supports it; otherwise three digits, or nothing."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT AND CC-BY-4.0"
12
+ license-files = ["LICENSE"]
13
+ keywords = ["enzyme", "EC number", "function transfer", "annotation", "MMseqs2", "bioinformatics"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Science/Research",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
20
+ ]
21
+ dependencies = []
22
+
23
+ [project.scripts]
24
+ ecbackoff = "ecbackoff:main"
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["src"]
28
+
29
+ [tool.setuptools.package-data]
30
+ ecbackoff = ["data/*", "example/*"]
31
+
32
+ [tool.setuptools.dynamic]
33
+ version = {attr = "ecbackoff.VERSION"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+