diff --git a/CHANGELOG.md b/CHANGELOG.md index 073604f..0272257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,19 +7,66 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- -## [Unreleased] +## [1.5.0] - 2026-10-12 -### Fixed +### Added +- **44 new registered descriptor functions** (plus the generic `aa_scale_descriptors`) with literature references (DOIs in the registry and `docs/references.bib`): + - Composition: `cksaap`, `cksaagp`, `dde`, `asdc`, `distance_pair` (cp13/cp14/cp19/cp20), `natural_vector`, `atomic_composition`. + - Scales (`protpy/scales.py`): `aa_scale_descriptors` with wrappers `z_scales_5`, `vhse`, `atchley_factors`, `t_scales`, `st_scales`, `protfp`, `fasgai`, `blosum_indices`, `ms_whim` (all with `domains=`); `auto_cross_covariance`; `physicochemical_distance_transformation`. + - Physicochemical (`protpy/physicochemical.py`): `charge_fractions`, `ncpr`, `kappa` (reproduces the published Das & Pappu values), `sequence_charge_decoration`, `charge_density`, `hydrophobicity_profile`, `hydrophobic_ratio`, `half_life`, `ivywrel_index`, `cvp_bias`, `flexibility`. + - Complexity (`protpy/complexity.py`): `wootton_federhen_complexity`, `linguistic_complexity`, `lempel_ziv_complexity` (`shannon_entropy` re-exported). + - Signal (`protpy/signal.py`, numpy only): `fft_descriptors`, `dct_descriptors`. + - Structural (`protpy/structural.py`): `aggrescan`, `uversky_charge_hydropathy`, `fold_index`, `disorder_propensity`, `transmembrane_features`, `signal_peptide_features`, `functional_site_counts`, `plaac` (port of the MIT-licensed PLAAC; reproduces its reference output), `antigenicity_surface`, `cysteine_features`. +- **Extensions to existing functions** (defaults unchanged): `reduced_alphabet_composition(alphabet=, k=)` (GAAC/GDPC/GTPC), `conjoint_triad(gap=)` (KSCTriad), `ctd_(property_set="ifeature13")`, `amino_acid_composition`/`dipeptide_composition(region=, n=)` (split composition), `isoelectric_point`/`charge_distribution(pka_scale=)` ("emboss", "bjellqvist", "ipc_protein"), `gravy(scale=)`, `hydrophobic_moment(scale=, angle="helix"/"sheet")`, `extinction_coefficient(absorbance=True)`. +- **`descriptor_set`, `include`, `exclude`** for `all_descriptors` and `encode_dataset`: `"classic"` (default, unchanged), `"extended"` (adds the new descriptors with ≤150 columns) and `"all"`. Set membership comes from the registry's new `descriptor_sets` field. Every parameterised v1.5.0 descriptor is accepted in `params`. +- **Data files** with provenance (`name`, `values`, `reference`, `doi`, `source_note`): `protpy/data/aa_scales.json`, `reduced_alphabets.json`, `residue_tables.json`, `structural_params.json`. +- Internal helpers in `protpy/_helpers.py`. The registry gains `doi`, `in_default_set` and `descriptor_sets`, and `print_descriptor_info` prints the DOI. New registry categories: Scales, Physicochemical, Complexity, Signal, Structural. +- `docs/references.bib` with BibTeX for every registry DOI. +- Tests: golden backwards-compatibility snapshot of every v1.4.2 default output (`tests/test_backwards_compat.py`), helper/registry tests (including registry parameters matching every function signature and registry DOIs present in `docs/references.bib`), one test module per new module, descriptor-set tests (including every v1.5.0 descriptor on 1-8 residue sequences), and an opt-in performance test (`PROTPY_PERF=1`). +- Plan items that could not be implemented without unverifiable source data (e.g. PseKRAAC, Kidera factors, GOR, COILS, Chou–Fasman) are listed with reasons in `planning/BLOCKED.md`. + +### Documentation +- `docs/conf.py` converts protpy's `:name: type` parameter style to numpy style before napoleon runs. Parameter names, types and defaults now render correctly throughout the API docs; previously napoleon emitted nameless `:param :` fields. The Sphinx build is now warning-free (previously 39 warnings). +- `docs/index.rst` lists the v1.5.0 descriptor categories; `docs/api.rst` documents the new modules and `encode_dataset`; `docs/usage.rst` covers `descriptor_set` / `include` / `exclude`; `docs/descriptors.rst` and `DESCRIPTORS.md` have a v1.5.0 section generated from the registry and docstrings. +- New "Scaling features for machine learning" section in `docs/usage.rst` and the README: fit a scikit-learn scaler on the training data rather than rescaling descriptors with fixed theoretical bounds, which compress real values into narrow bands. +- The autocorrelation `normalize` parameter's docs now say what it does: it standardises the property values (z-score); it never rescaled the output to 0–1. +- Docstrings of all new v1.5.0 functions (descriptors and internal helpers) follow the package's docstring style: a description with the formula and a numbered reference, then Parameters and Returns sections. The nine factor-scale functions (`vhse`, `z_scales_5`, ...) are now defined individually, each with its own docstring, instead of being generated by a factory. +- `tests/README.md` lists every test module and test data file; `protpy/data/README.md` describes the new data files. + +### Changed +- pytest now collects only `tests/` (`testpaths`) and skips `build/`, `dist/` and `*.egg-info`, so stale local build copies of the package are no longer collected. +- `encode_dataset(verbose=False)` now also suppresses the descriptor-list line printed for the first sequence. +- `hydrophobic_moment` docstring now names the source of its default scale (Eisenberg, Schwarz, Komarony & Wall 1984). + +### Fixed (post-1.4.2 audit) +- **Autocorrelation crash on fresh installs**: `aaindex1[code]` can return a new record on each access, so `.values.pop('-')` was lost and all three autocorrelation descriptors (and `all_descriptors`/`encode_dataset`) raised `IndexError`. Property values are now copied and filtered to the 20 canonical amino acids; aaindex records are no longer mutated. +- **Moran autocorrelation formula**: multiplied by the property variance instead of dividing by it. +- **CTD groups**: `polarity` groups 2/3 were copied from polarizability (A, D, G, S, T unassigned; 8 residues duplicated) — now `LIFWCMVY / PAGST / HQRKNED`. `normalized_vdwv` group 1 was missing C. +- **Overlapping counts**: dipeptide, tripeptide and conjoint triad used `str.count`, which skips overlapping matches (e.g. `"AA"` in `"AAA"`). +- **Short/repetitive sequences**: APAAComp raised `ZeroDivisionError` for sequences <= 30 residues; dipeptide composition, CTD transition, Moran and Geary crashed or returned NaN for single-residue or homopolymer sequences; lags with no residue pairs now return 0.0. +- **`all_descriptors(params={"ctd_": {"all_ctd": False}})`** raised `TypeError`. +- **Motif `dileucine_sorting`** never matched (literal `xxx` and stray `$`). +- **Boman index** now uses the published Radzicka-Wolfenden solubility scale and `BI = -sum/N`. +- **Isoelectric point** uses bisection over pH 0-14 (previously capped at 12.0 for very basic proteins). +- **PseAAC with AAIndex properties** included the `'-'` entry in normalization. +- **Glycine molecular weight** 75.032 -> 75.067. +- **`aggregation_propensity`**: `charge_threshold` is now an inclusive maximum, as documented; default changed 1 -> 0 so default results are unchanged. +- **`hydrophobic_moment`** registry entry now reports its `1 x 2` output shape. - **MoreauBroto Autocorrelation (Bug 1)**: Two intertwined bugs in `moreaubroto_autocorrelation` — the lag index always read `sequence[j+1]` (hardcoded lag=1) regardless of the current lag `i`, and the accumulator `temp` was not reset between lags, causing values to accumulate across all lags. Both corrected: `sequence[j+1]` → `sequence[j+i]`, and `temp = 0` moved inside the lag loop. - **PseAAC normalization denominator (Bug 2)**: `pseudo_amino_acid_composition` used `1 + weight + Σθ_k` where the Chou (2001) formula requires `1 + weight * Σθ_k`. Fixed operator `+` → `*`. Removed the redundant first normalization loop and unified output computation into a single pass. - **APAAComp properties parameter ignored (Bug 3)**: `amphiphilic_pseudo_amino_acid_composition` accepted a `properties` parameter but the underlying helpers `amphiphilic_correllation_function` and `amphiphilic_sequence_order_correlation_factor` hardcoded `hydrophobicity_` and `hydrophilicity_`, silently ignoring any user-supplied properties. Fixed by threading `properties` through all three functions and pre-normalizing once per call. - **`JSONDecodeError` constructor misuse (Bug 5)**: `sequence_order_coupling_number_` raised `json.JSONDecodeError(msg)` which fails at runtime with `TypeError` because `JSONDecodeError` requires `(msg, doc, pos)` positional arguments. Changed to `raise ValueError(...)`. -### Performance +### Performance (post-1.4.2 audit) +- `kmer_composition`, dipeptide/tripeptide/pair composition count k-mers in one pass; QSO computes each SOCN once; PseAAC normalizes properties once per gap instead of per residue pair. - **`sequence_order_coupling_number_` disk I/O (Issue 6)**: Added `@functools.lru_cache(maxsize=2)` loader `_load_distance_matrix` in `sequence_order.py`. The distance matrix JSON was read from disk on every call; for a sequence with `lag=30`, that was 30 file reads per descriptor invocation. Now both matrices are cached in memory after the first load. - **`normalize_property` O(n²) (Issue 7)**: Refactored to compute `mean` and `std` once before the loop, reducing from O(n²) to O(n) for the 20-amino-acid property dictionaries used in PseAAC and APAAComp descriptors. -### Changed +### Changed (post-1.4.2 audit) +- Invalid `lag`, `lamda`, `k`, `window` (non-integer or negative) and `weight` (non-numeric or negative) now raise `ValueError` instead of being silently replaced by defaults. `lag`/`lamda` larger than the sequence still fall back to 30. +- `all_descriptors` no longer writes `descriptors.csv` by default (`output_file=None`). +- `all_descriptors` no longer includes `amino_acid_pair_composition` (it duplicated dipeptide composition's 400 values). +- `encode_dataset`/`all_descriptors` refuse to overwrite an existing output file with different columns, append rows instead of rewriting the whole file per sequence, and `encode_dataset` replaces rows for re-encoded IDs instead of duplicating them. - **`tripeptide_composition` returns percentages (Gap 8)**: Output values are now fractional percentages (`count / (N-2) * 100`, rounded to 3 dp) consistent with `dipeptide_composition`, instead of raw integer counts. Column dtype is now `float64` instead of `int64`. - **`encode_dataset` verbose parameter (Gap 11)**: Added `verbose: bool = True` parameter to `encode_dataset`. When `False`, all per-sequence stdout status lines are suppressed. Default `True` preserves existing behaviour. diff --git a/DESCRIPTORS.md b/DESCRIPTORS.md index 64567ef..e17c67a 100644 --- a/DESCRIPTORS.md +++ b/DESCRIPTORS.md @@ -295,7 +295,7 @@ Estimates aggregation-prone regions via a sliding-window approach combining Kyte |-----------|------|---------|-------------| | `window` | int | `5` | Sliding window size | | `hydrophobicity_threshold` | float | `2.0` | Minimum mean hydrophobicity | -| `charge_threshold` | int | `1` | Maximum charged residues per window | +| `charge_threshold` | int | `0` | Maximum charged residues per window | ```python result = protpy.aggregation_propensity(protein_seq) @@ -702,6 +702,1417 @@ result = protpy.quasi_sequence_order_all(protein_seq, lag=15, weight=0.05) --- +## v1.5.0 Descriptors + +Descriptors added in v1.5.0. Each lists its formula, output shape, parameters and primary reference. +`descriptor_set="extended"` in `all_descriptors` / `encode_dataset` adds every descriptor below marked **extended**; `"all"` adds the rest as well. +Items from the implementation plan that could not be implemented without unverifiable data are listed in `planning/BLOCKED.md`. + +### Composition + +#### `cksaap` (CKSAAP) + +Composition of k-spaced amino acid pairs: CKSAAP(XY,g) = N_g(XY) / (L-g-1) x 100 for g = 0..gap. + +```text +Calculate Composition of k-Spaced Amino Acid Pairs (CKSAAP) of protein +sequence. CKSAAP describes the frequency of each amino acid pair separated +by g residues within a protein sequence, for each gap g in the range +0..gap. With 400 possible amino acid pairs per gap this creates a +400 * (gap+1) Dimensional vector, calculated as: + +CKSAAP(XY, g) = N_g(XY) / (L - g - 1) * 100 + +where CKSAAP(XY, g) is the composition of the amino acid pair XY with g +residues between X and Y, N_g(XY) is the number of such pairs in the sequence +and L is the length of the sequence. A gap of 0 is the same as the +dipeptide composition. A gap with no pairs (L - g - 1 <= 0) gives values +of 0. +``` + +```python +result = protpy.cksaap(protein_seq) +# Shape: 1 x 400·(gap+1) (default 1 x 2400) +``` + +- **Columns:** `CKSAAP_AA_gap0`, `CKSAAP_AC_gap0`, `CKSAAP_AD_gap0`, `CKSAAP_AE_gap0`, `CKSAAP_AF_gap0`, `CKSAAP_AG_gap0`, … +- **Parameters:** `gap`: int (default=5) — maximum spacing +- **Descriptor set:** all +- **Reference:** Chen, Kurgan & Ruan (2007). BMC Struct. Biol., 7, 25. https://doi.org/10.1186/1472-6807-7-25 + +--- + +#### `cksaagp` (CKSAAGP) + +Composition of k-spaced amino acid group pairs over the iFeature 5-group alphabet. + +```text +Calculate Composition of k-Spaced Amino Acid Group Pairs (CKSAAGP) of protein +sequence. CKSAAGP is the same as CKSAAP but the amino acids are first mapped +to 5 groups: aliphatic (GAVLMI), aromatic (FYW), positive charge (KRH), +negative charge (DE) and uncharged (STCPNQ). With 25 possible group pairs +per gap this creates a 25 * (gap+1) Dimensional vector, calculated as: + +CKSAAGP(G1G2, g) = N_g(G1G2) / (L - g - 1) * 100 + +where CKSAAGP(G1G2, g) is the composition of the group pair G1G2 with g +residues between them, N_g(G1G2) is the number of such pairs in the sequence +and L is the length of the sequence. The 25 values for each gap sum to 100. +``` + +```python +result = protpy.cksaagp(protein_seq) +# Shape: 1 x 25·(gap+1) (default 1 x 150) +``` + +- **Columns:** `CKSAAGP_aliphatic.aliphatic_gap0`, `CKSAAGP_aliphatic.aromatic_gap0`, `CKSAAGP_aliphatic.positive_gap0`, `CKSAAGP_aliphatic.negative_gap0`, `CKSAAGP_aliphatic.uncharged_gap0`, `CKSAAGP_aromatic.aliphatic_gap0`, … +- **Parameters:** `gap`: int (default=5) — maximum spacing +- **Descriptor set:** extended, all +- **Reference:** Chen et al. (2018). Bioinformatics, 34(14), 2499-2502 (iFeature). https://doi.org/10.1093/bioinformatics/bty140 + +--- + +#### `dde` (DDE) + +Dipeptide deviation from expected mean: (Dc - Tm) / sqrt(Tv), with Tm from codon counts. + +```text +Calculate Dipeptide Deviation from Expected Mean (DDE) of protein sequence. +DDE compares the observed dipeptide composition of a protein sequence with +the composition expected from the number of codons that code for each amino +acid. For each of the 400 dipeptides it is calculated as: + +Dc(XY) = N(XY) / (L - 1) + +Tm(XY) = (C_X / 61) * (C_Y / 61) + +Tv(XY) = Tm(XY) * (1 - Tm(XY)) / (L - 1) + +DDE(XY) = (Dc(XY) - Tm(XY)) / sqrt(Tv(XY)) + +where Dc(XY) is the observed composition of dipeptide XY, N(XY) is the +number of XY dipeptides, L is the length of the sequence, Tm(XY) and Tv(XY) +are the theoretical mean and variance, and C_X and C_Y are the number of +codons for amino acids X and Y out of the 61 sense codons. Unlike the +other composition descriptors, DDE values are not percentages. +``` + +```python +result = protpy.dde(protein_seq) +# Shape: 1 x 400 +``` + +- **Columns:** `DDE_AA`, `DDE_AC`, `DDE_AD`, `DDE_AE`, `DDE_AF`, `DDE_AG`, … +- **Parameters:** sequence only +- **Descriptor set:** all +- **Reference:** Saravanan & Gautham (2015). OMICS, 19(10), 648-658. https://doi.org/10.1089/omi.2015.0095 + +--- + +#### `asdc` (ASDC) + +Adaptive skip dipeptide composition: composition of all ordered pairs (s_i, s_j), i < j. + +```text +Calculate Adaptive Skip Dipeptide Composition (ASDC) of protein sequence. +ASDC is a modified dipeptide composition that counts every ordered pair of +amino acids (s_i, s_j), where i < j, at all gaps along the sequence, rather +than just adjacent pairs, calculated as: + +ASDC(XY) = sum_g N_g(XY) / sum_g sum_XY N_g(XY) * 100 + +where ASDC(XY) is the composition of the amino acid pair XY, N_g(XY) is the +number of XY pairs separated by g residues, and the denominator is the total +number of ordered pairs in the sequence, L(L-1)/2, where L is the length of +the sequence. The 400 values sum to 100. +``` + +```python +result = protpy.asdc(protein_seq) +# Shape: 1 x 400 +``` + +- **Columns:** `ASDC_AA`, `ASDC_AC`, `ASDC_AD`, `ASDC_AE`, `ASDC_AF`, `ASDC_AG`, … +- **Parameters:** sequence only +- **Descriptor set:** all +- **Reference:** Wei et al. (2018). Bioinformatics, 34(23), 4007-4016 (ACPred-FL). https://doi.org/10.1093/bioinformatics/bty451 + +--- + +#### `distance_pair` (DP) + +PseAAC of distance-pairs over a reduced cluster profile (cp13/cp14/cp19/cp20). + +```text +Calculate Distance Pair (DP) descriptor of protein sequence. The descriptor +incorporates amino acid distance pairs and a reduced amino acid alphabet into +the general pseudo amino acid composition, as used in iDNA-Prot|dis. +The amino acids are first mapped to the clusters of a cluster profile cp(n), +and the descriptor is the composition of the clusters followed by the +composition of cluster pairs at each distance d = 1..rng: + +DP(c) = N(c) / L * 100 + +DP(c1c2, d) = N_d(c1c2) / (L - d) * 100 + +where N(c) is the number of residues in cluster c, N_d(c1c2) is the number of +residue pairs (s_i, s_i+d) whose residues are in clusters c1 and c2, and L is +the length of the sequence. A distance of 1 is the nearest-neighbour pair. +The available cluster profiles are cp13, cp14, cp19 and cp20 (no reduction). +The published cp(14) lists E twice; the extra E in the first cluster is +dropped, as it is the only reading that gives 14 clusters. +``` + +```python +result = protpy.distance_pair(protein_seq) +# Shape: 1 x (G + G²·rng) (default 1 x 602) +``` + +- **Columns:** `DP_IMV`, `DP_L`, `DP_F`, `DP_WY`, `DP_G`, `DP_P`, … +- **Parameters:** `rng`: int (default=3) — maximum pair distance; `alphabet`: str (default='cp14') — cluster profile +- **Descriptor set:** all +- **Reference:** Liu et al. (2014). PLoS ONE, 9(9), e106691 (iDNA-Prot|dis). https://doi.org/10.1371/journal.pone.0106691 + +--- + +#### `natural_vector` (NV) + +Natural vector: count, mean position and normalised second central moment of each amino acid. + +```text +Calculate Natural Vector (NV) of protein sequence. The natural vector +describes the number of each amino acid in a protein sequence together with +the mean and the normalized central moment of their positions, calculated as: + +n_k = number of occurrences of amino acid k + +mu_k = sum of the positions of k / n_k + +D2_k = sum of (position of k - mu_k)^2 / (n_k * L) + +where positions start at 1, L is the length of the sequence and k is each of +the 20 canonical amino acids. mu_k and D2_k are 0 when amino acid k is +not in the sequence. +``` + +```python +result = protpy.natural_vector(protein_seq) +# Shape: 1 x 60 +``` + +- **Columns:** `NV_n_A`, `NV_n_C`, `NV_n_D`, `NV_n_E`, `NV_n_F`, `NV_n_G`, … +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Yu et al. (2013). J. Theor. Biol., 318, 197-204. https://doi.org/10.1016/j.jtbi.2012.11.005 + +--- + +#### `atomic_composition` (ATC) + +Percentage of C, H, N, O and S atoms over the residues' free amino-acid formulas. + +```text +Calculate Atomic Composition (ATC) of protein sequence. ATC describes the +percentage of carbon, hydrogen, nitrogen, oxygen and sulphur atoms in a +protein sequence, using the molecular formulas of the free amino acids, +calculated as: + +ATC(e) = N_e / (N_C + N_H + N_N + N_O + N_S) * 100 + +where ATC(e) is the atomic composition of element e and N_e is the number of +atoms of element e summed over all residues. By default the formulas of +the free amino acids are summed without removing water, matching Pfeature +. With subtract_water=True, one water molecule is removed for each of the +L - 1 peptide bonds, giving the formula of the peptide (e.g. "GG" -> C4H8N2O3). +``` + +```python +result = protpy.atomic_composition(protein_seq) +# Shape: 1 x 5 +``` + +- **Columns:** `ATC_C`, `ATC_H`, `ATC_N`, `ATC_O`, `ATC_S` +- **Parameters:** `subtract_water`: bool (default=False) — remove (L-1) H2O +- **Descriptor set:** extended, all +- **Reference:** Pande et al. (2023). J. Comput. Biol., 30(2), 204-222 (Pfeature). https://doi.org/10.1089/cmb.2022.0241 + +--- + +### Scales + +#### `z_scales_5` (Z5) + +Mean of the Sandberg 5 z-scales over the sequence (optionally per domain). + +```text +Calculate Sandberg 5 z-scales (Z5) of protein sequence. The z-scales are the +principal components of a set of physicochemical properties measured for 87 +coded and non-coded amino acids. z1 mainly describes lipophilicity, z2 +steric bulk and polarizability, z3 polarity and charge, and z4 and z5 +electronegativity, heat of formation, electrophilicity and hardness. + +Each amino acid in the sequence is replaced by its 5 z-scales and the mean of +each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.z_scales_5(protein_seq) +# Shape: 1 x 5·domains (default 1 x 5) +``` + +- **Columns:** `Z5_1`, `Z5_2`, `Z5_3`, `Z5_4`, `Z5_5` +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** Sandberg et al. (1998). J. Med. Chem., 41(14), 2481-2491. https://doi.org/10.1021/jm9700575 + +--- + +#### `vhse` (VHSE) + +Mean of the VHSE (hydrophobic, steric, electronic) scales over the sequence (optionally per domain). + +```text +Calculate Vectors of Hydrophobic, Steric and Electronic properties (VHSE) of +protein sequence. The VHSE scales are principal components score vectors derived +from physicochemical variables of the 20 coded amino acids. VHSE1-2 describe +hydrophobic properties, VHSE3-4 steric properties and VHSE5-8 electronic +properties. + +Each amino acid in the sequence is replaced by its 8 VHSE scales and the mean of +each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.vhse(protein_seq) +# Shape: 1 x 8·domains (default 1 x 8) +``` + +- **Columns:** `VHSE_1`, `VHSE_2`, `VHSE_3`, `VHSE_4`, `VHSE_5`, `VHSE_6`, … +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** Mei et al. (2005). Biopolymers, 80(6), 775-786. https://doi.org/10.1002/bip.20296 + +--- + +#### `atchley_factors` (AF) + +Mean of the Atchley factors I-V over the sequence (optionally per domain). + +```text +Calculate Atchley Factors (AF) of protein sequence. The Atchley factors are 5 +factors obtained from a factor analysis of 54 amino acid attributes. Factor +I mainly reflects polarity, accessibility and hydrophobicity, factor II +propensity for secondary structure, factor III molecular size, factor IV +relative amino acid composition and factor V electrostatic charge. + +Each amino acid in the sequence is replaced by its 5 Atchley factors and the +mean of each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.atchley_factors(protein_seq) +# Shape: 1 x 5·domains (default 1 x 5) +``` + +- **Columns:** `AF_1`, `AF_2`, `AF_3`, `AF_4`, `AF_5` +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** Atchley et al. (2005). PNAS, 102(18), 6395-6400. https://doi.org/10.1073/pnas.0408677102 + +--- + +#### `t_scales` (T) + +Mean of the topological T-scales over the sequence (optionally per domain). + +```text +Calculate Topological Scales (T-scales) of protein sequence. The T-scales are +principal components of topological descriptors of amino acids, describing their +structural and topological characteristics. + +Each amino acid in the sequence is replaced by its 5 T-scales and the mean of +each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.t_scales(protein_seq) +# Shape: 1 x 5·domains (default 1 x 5) +``` + +- **Columns:** `T_1`, `T_2`, `T_3`, `T_4`, `T_5` +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** Tian, Zhou & Li (2007). J. Mol. Struct., 830, 106-115. https://doi.org/10.1016/j.molstruc.2006.07.004 + +--- + +#### `st_scales` (ST) + +Mean of the structural topology ST-scales over the sequence (optionally per domain). + +```text +Calculate Structural Topology Scales (ST-scales) of protein sequence. The ST- +scales are principal components of 3D structural and topological properties of +amino acids. + +Each amino acid in the sequence is replaced by its 8 ST-scales and the mean of +each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.st_scales(protein_seq) +# Shape: 1 x 8·domains (default 1 x 8) +``` + +- **Columns:** `ST_1`, `ST_2`, `ST_3`, `ST_4`, `ST_5`, `ST_6`, … +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** Yang et al. (2010). Amino Acids, 38(3), 805-816. https://doi.org/10.1007/s00726-009-0287-y + +--- + +#### `protfp` (ProtFP) + +Mean of the ProtFP (PCA, 8 components) over the sequence (optionally per domain). + +```text +Calculate ProtFP (ProtFP) of protein sequence. ProtFP is a set of 8 principal +components calculated from a selection of physicochemical properties in the +AAindex database. + +Each amino acid in the sequence is replaced by its 8 ProtFP components and the +mean of each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.protfp(protein_seq) +# Shape: 1 x 8·domains (default 1 x 8) +``` + +- **Columns:** `ProtFP_1`, `ProtFP_2`, `ProtFP_3`, `ProtFP_4`, `ProtFP_5`, `ProtFP_6`, … +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** van Westen et al. (2013). J. Cheminform., 5, 41. https://doi.org/10.1186/1758-2946-5-41 + +--- + +#### `fasgai` (FASGAI) + +Mean of the FASGAI factor scales over the sequence (optionally per domain). + +```text +Calculate Factor Analysis Scales of Generalized Amino Acid Information (FASGAI) +of protein sequence. The FASGAI scales are 6 factors obtained from a factor +analysis of amino acid properties, describing hydrophobicity, alpha and turn +propensities, bulky properties, compositional characteristics, local flexibility +and electronic properties. + +Each amino acid in the sequence is replaced by its 6 FASGAI factors and the mean +of each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.fasgai(protein_seq) +# Shape: 1 x 6·domains (default 1 x 6) +``` + +- **Columns:** `FASGAI_1`, `FASGAI_2`, `FASGAI_3`, `FASGAI_4`, `FASGAI_5`, `FASGAI_6` +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** Liang & Li (2007). QSAR Comb. Sci., 26(6), 754-763. https://doi.org/10.1002/qsar.200630145 + +--- + +#### `blosum_indices` (BLOSUM) + +Mean of the BLOSUM62-derived indices over the sequence (optionally per domain). + +```text +Calculate BLOSUM Indices (BLOSUM) of protein sequence. The BLOSUM indices are 10 +factors derived from the BLOSUM62 substitution matrix by principal component +analysis and VARIMAX rotation, so they describe the evolutionary similarity of +amino acids. + +Each amino acid in the sequence is replaced by its 10 BLOSUM indices and the +mean of each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.blosum_indices(protein_seq) +# Shape: 1 x 10·domains (default 1 x 10) +``` + +- **Columns:** `BLOSUM_1`, `BLOSUM_2`, `BLOSUM_3`, `BLOSUM_4`, `BLOSUM_5`, `BLOSUM_6`, … +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** Georgiev (2009). J. Comput. Biol., 16(5), 703-723. https://doi.org/10.1089/cmb.2008.0173 + +--- + +#### `ms_whim` (MSWHIM) + +Mean of the MS-WHIM scores over the sequence (optionally per domain). + +```text +Calculate Molecular Surface Weighted Holistic Invariant Molecular Scores (MS- +WHIM) of protein sequence. The MS-WHIM scores are 3 principal components of 3D +descriptors calculated from the molecular electrostatic potential on the surface +of each amino acid. + +Each amino acid in the sequence is replaced by its 3 MS-WHIM scores and the mean +of each is taken over the sequence, calculated as: + +S_j = (1/L) * sum_i z_j(s_i) + +where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale +j for the amino acid at position i and L is the length of the sequence. With +domains > 1 the sequence is split into that many near-equal segments and the +means of each segment are concatenated. +``` + +```python +result = protpy.ms_whim(protein_seq) +# Shape: 1 x 3·domains (default 1 x 3) +``` + +- **Columns:** `MSWHIM_1`, `MSWHIM_2`, `MSWHIM_3` +- **Parameters:** `domains`: int (default=1) — number of near-equal segments +- **Descriptor set:** extended, all +- **Reference:** Zaliani & Gancia (1999). J. Chem. Inf. Comput. Sci., 39(3), 525-533. https://doi.org/10.1021/ci980211b + +--- + +#### `auto_cross_covariance` (ACC) + +Auto/cross-covariance of an amino-acid scale: sum z_j(i) z_k(i+l) / (L-l). + +```text +Calculate Auto Cross Covariance (ACC) of protein sequence. ACC transforms the +amino acid scale values of a sequence into a fixed-length vector that +describes the covariance between the scale dimensions of residues separated +by l positions, for each lag l in the range 1..lag. It is calculated as: + +ACC_jk(l) = sum_{i=1}^{L-l} z_j(s_i) * z_k(s_i+l) / (L - l) + +where ACC_jk(l) is the covariance between dimensions j and k at lag l, z_j(s_i) +is the value of dimension j for the amino acid at position i and L is the length +of the sequence. When j = k this is the auto covariance and when j != k the cross +covariance. The scale can be one of the factor-scale families, which are used as +published, or one or more AAindex accession numbers, whose values are first +standardised over the 20 amino acids. +``` + +```python +result = protpy.auto_cross_covariance(protein_seq) +# Shape: 1 x d²·lag (default 1 x 125) +``` + +- **Columns:** `ACC_1_1_lag1`, `ACC_1_2_lag1`, `ACC_1_3_lag1`, `ACC_1_4_lag1`, `ACC_1_5_lag1`, `ACC_2_1_lag1`, … +- **Parameters:** `scale`: str | list[str] (default='z_scales_5') — factor scale or AAindex code(s); `lag`: int (default=5) — maximum lag; `mode`: str (default='acc') — 'acc', 'ac' or 'cc' +- **Descriptor set:** all +- **Reference:** Wold et al. (1993). Anal. Chim. Acta, 277(2), 239-253. https://doi.org/10.1016/0003-2670(93)80437-P + +--- + +#### `physicochemical_distance_transformation` (PDT) + +Physicochemical distance transformation: mean squared difference of standardised AAindex properties at lag l. + +```text +Calculate Physicochemical Distance Transformation (PDT) of protein sequence. +PDT describes how much a physicochemical property changes between residues +separated by l positions, for each lag l in the range 1..lag. For each +AAindex property it is calculated as: + +PDT_j(l) = sum_{i=1}^{L-l} (P_j(s_i) - P_j(s_i+l))^2 / (L - l) + +where PDT_j(l) is the descriptor value for property j at lag l, P_j(s_i) is the +value of property j for the amino acid at position i, standardised over the 20 +amino acids, and L is the length of the sequence. By default the same 8 AAindex +properties as the autocorrelation descriptors are used. +``` + +```python +result = protpy.physicochemical_distance_transformation(protein_seq) +# Shape: 1 x len(properties)·lag (default 1 x 24) +``` + +- **Columns:** `PDT_CIDH920105_1`, `PDT_CIDH920105_2`, `PDT_CIDH920105_3`, `PDT_BHAR880101_1`, `PDT_BHAR880101_2`, `PDT_BHAR880101_3`, … +- **Parameters:** `lag`: int (default=3) — maximum lag; `properties`: list[str] | None — AAindex codes (default: 8 autocorrelation properties) +- **Descriptor set:** extended, all +- **Reference:** Liu et al. (2012). PLoS ONE, 7(9), e46633. https://doi.org/10.1371/journal.pone.0046633 + +--- + +### Physicochemical + +#### `charge_fractions` (FCR) + +Fraction of charged residues (FCR), positive/negative fractions and charge asymmetry sigma = NCPR²/FCR. + +```text +Calculate the Charge Fractions (FCR) of protein sequence. The fraction of +charged residues (FCR), the fractions of positive and negative residues and +the overall charge asymmetry (sigma) describe the charge content of a protein +sequence, and are key parameters of the conformations of intrinsically +disordered proteins. They are calculated as: + +FCR = (n+ + n-) / L + +FPlus = n+ / L, FMinus = n- / L + +Sigma = NCPR^2 / FCR + +where n+ is the number of positive residues (K, R), n- the number of negative +residues (D, E), L is the length of the sequence and NCPR = (n+ - n-) / L. His +is treated as neutral and sigma is 0 when there are no charged residues. +``` + +```python +result = protpy.charge_fractions(protein_seq) +# Shape: 1 x 4 +``` + +- **Columns:** `Charge_FCR`, `Charge_FPlus`, `Charge_FMinus`, `Charge_Sigma` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Das & Pappu (2013). PNAS, 110(33), 13392-13397. https://doi.org/10.1073/pnas.1304749110 + +--- + +#### `ncpr` (NCPR) + +Net charge per residue: (n+ - n-) / L with K/R positive and D/E negative. + +```text +Calculate the Net Charge Per Residue (NCPR) of protein sequence. NCPR is the +net charge of a protein sequence divided by its length, calculated as: + +NCPR = (n+ - n-) / L + +where n+ is the number of positive residues (K, R), n- the number of negative +residues (D, E) and L is the length of the sequence. +``` + +```python +result = protpy.ncpr(protein_seq) +# Shape: 1 x 1 +``` + +- **Columns:** `NCPR` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Mao et al. (2010). PNAS, 107(18), 8183-8188. https://doi.org/10.1073/pnas.0911107107 + +--- + +#### `kappa` (Kappa) + +Das-Pappu kappa: normalised blob charge-asymmetry deviation measuring segregation of opposite charges (0 mixed, 1 segregated). + +```text +Calculate the Kappa (Kappa) charge patterning parameter of protein sequence. +Kappa describes how well oppositely charged residues are mixed or segregated +along a protein sequence, which strongly affects the conformations of +intrinsically disordered proteins. The sequence is split into overlapping +blobs of g residues and kappa is calculated as: + +sigma_b = (f+ - f-)^2 / (f+ + f-) + +delta_g = mean over all blobs of (sigma_b - sigma)^2 + +kappa_g = delta_g / delta_max,g + +where sigma_b is the charge asymmetry of a blob, f+ and f- are its fractions of +positive (K, R) and negative (D, E) residues, sigma = NCPR^2 / FCR is the charge +asymmetry of the whole sequence and delta_max,g is the largest delta over the +most segregated arrangements of the same composition. The final kappa is the +mean of kappa_g over the blob sizes (5 and 6 by default). Kappa ranges from 0 +(well mixed) to 1 (fully segregated). It is reported as 0 when the sequence has +no charged residues or only one type of charge, and blobs longer than the +sequence are reduced to its length. +``` + +```python +result = protpy.kappa(protein_seq) +# Shape: 1 x (1 + len(blob_sizes)) (default 1 x 3) +``` + +- **Columns:** `Kappa`, `Kappa_5`, `Kappa_6` +- **Parameters:** `blob_sizes`: tuple[int] (default=(5, 6)) — blob sizes averaged +- **Descriptor set:** extended, all +- **Reference:** Das & Pappu (2013). PNAS, 110(33), 13392-13397. https://doi.org/10.1073/pnas.1304749110 + +--- + +#### `sequence_charge_decoration` (SCD) + +Sequence charge decoration: (1/L) sum_{m>n} q_m q_n sqrt(m-n). + +```text +Calculate the Sequence Charge Decoration (SCD) of protein sequence. SCD +describes the patterning of charges along a protein sequence, weighting each +pair of charges by the square root of their separation. It is calculated as: + +SCD = (1/L) * sum_{m=2}^{L} sum_{n=1}^{m-1} q_m * q_n * (m - n)^(1/2) + +where q_m and q_n are the charges of the residues at positions m and n (+1 for +K and R, -1 for D and E, 0 otherwise) and L is the length of the sequence. More +negative values indicate more segregated opposite charges and uncharged +sequences have an SCD of 0. +``` + +```python +result = protpy.sequence_charge_decoration(protein_seq) +# Shape: 1 x 1 +``` + +- **Columns:** `Charge_SCD` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Sawle & Ghosh (2015). J. Chem. Phys., 143(8), 085101. https://doi.org/10.1063/1.4929391 + +--- + +#### `charge_density` (ChargeDensity) + +Net charge at the given pH divided by molecular weight (charges per Da). + +```text +Calculate the Charge Density of protein sequence. Charge density is the net +charge of a protein sequence at a given pH divided by its molecular weight, +calculated as: + +ChargeDensity = NetCharge(pH) / MW + +where NetCharge(pH) is calculated using the charge_distribution function and MW +using the molecular_weight function. +``` + +```python +result = protpy.charge_density(protein_seq) +# Shape: 1 x 1 +``` + +- **Columns:** `ChargeDensity` +- **Parameters:** `ph`: float (default=7.4) — pH +- **Descriptor set:** extended, all +- **Reference:** Müller et al. (2017). Bioinformatics, 33(17), 2753-2755 (modlAMP). https://doi.org/10.1093/bioinformatics/btx285 + +--- + +#### `hydrophobicity_profile` (HP) + +Summary statistics of a sliding-window hydrophobicity profile (ProtScale-style edge weighting). + +```text +Calculate the Hydrophobicity Profile (HP) of protein sequence. Each amino acid +in the sequence is replaced by its value on a hydrophobicity scale and the +values are averaged over a sliding window, giving a profile along the sequence +. The profile is summarised by its mean, maximum, minimum and standard +deviation, and optionally the fraction of windows above a threshold, e.g. 1.6 +for transmembrane segments with the Kyte-Doolittle scale. The window value +is calculated as: + +HP(i) = sum_j w_j * H(s_i+j) / sum_j w_j + +where H is the hydrophobicity scale and w_j are the window weights. With +edge=1.0 all weights are 1 (a plain mean); with edge < 1 the weights fall +linearly from 1 at the centre of the window to edge at its ends, as in ExPASy +ProtScale. Windows longer than the sequence use the whole sequence. +``` + +```python +result = protpy.hydrophobicity_profile(protein_seq) +# Shape: 1 x 4 (1 x 5 with threshold) +``` + +- **Columns:** `HP_kyte_doolittle_w9_mean`, `HP_kyte_doolittle_w9_max`, `HP_kyte_doolittle_w9_min`, `HP_kyte_doolittle_w9_std` +- **Parameters:** `scale`: str (default='kyte_doolittle') — single-value scale or AAindex code; `window`: int (default=9) — window length; `edge`: float (default=1.0) — relative end weight; `threshold`: float | None (default=None) — adds fraction of windows above threshold +- **Descriptor set:** extended, all +- **Reference:** Kyte & Doolittle (1982). J. Mol. Biol., 157(1), 105-132. https://doi.org/10.1016/0022-2836(82)90515-0 + +--- + +#### `hydrophobic_ratio` (HydrophobicRatio) + +APD hydrophobic ratio: percentage of L, I, V, F, M, A, C, W residues. + +```text +Calculate the Hydrophobic Ratio of protein sequence. The hydrophobic ratio is +the percentage of hydrophobic residues in a protein sequence, as defined by +the Antimicrobial Peptide Database (APD), calculated as: + +HydrophobicRatio = N(LIVFMACW) / L * 100 + +where N(LIVFMACW) is the number of L, I, V, F, M, A, C and W residues and L +is the length of the sequence. +``` + +```python +result = protpy.hydrophobic_ratio(protein_seq) +# Shape: 1 x 1 +``` + +- **Columns:** `HydrophobicRatio` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Wang (2020). Protein Sci., 29(1), 8-18 (APD); Wang, Li & Wang (2016). Nucleic Acids Res., 44, D1087 (APD3). https://doi.org/10.1002/pro.3702 + +--- + +#### `half_life` (HalfLife) + +N-end rule half-life (hours) in mammalian, yeast and E. coli systems from the N-terminal residue. + +```text +Calculate the estimated Half Life of protein sequence. The half-life is a +prediction of the time it takes for half of the amount of a protein in a cell +to disappear after its synthesis. It is estimated from the N-terminal residue +using the N-end rule, for 3 model systems: mammalian reticulocytes +(in vitro), yeast and E. coli (in vivo), using the values tabulated by ExPASy +ProtParam. Values given as lower bounds in ProtParam (">20 hours" for yeast, +">10 hours" for E. coli) are reported as the bound. The E. coli value for an +N-terminal proline is unknown in ProtParam and returned as NaN. The estimate is +not applicable to N-terminally modified proteins. +``` + +```python +result = protpy.half_life(protein_seq) +# Shape: 1 x 3 +``` + +- **Columns:** `HalfLife_Mammal_h`, `HalfLife_Yeast_h`, `HalfLife_Ecoli_h` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Bachmair, Finley & Varshavsky (1986). Science, 234(4773), 179-186. https://doi.org/10.1126/science.3018930 + +--- + +#### `ivywrel_index` (IVYWREL) + +Fraction of I, V, Y, W, R, E, L residues (thermophilic adaptation index). + +```text +Calculate the IVYWREL Index of protein sequence. The IVYWREL index is the +fraction of I, V, Y, W, R, E and L residues in a protein sequence, which +correlates with the optimal growth temperature of the organism. It is +calculated as: + +IVYWREL = N(IVYWREL) / L + +where N(IVYWREL) is the number of I, V, Y, W, R, E and L residues and L is the +length of the sequence. +``` + +```python +result = protpy.ivywrel_index(protein_seq) +# Shape: 1 x 1 +``` + +- **Columns:** `IVYWREL` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Zeldovich, Berezovsky & Shakhnovich (2007). PLoS Comput. Biol., 3(1), e5. https://doi.org/10.1371/journal.pcbi.0030005 + +--- + +#### `cvp_bias` (CvP) + +Charged versus polar bias: %(D+E+K+R) - %(N+Q+S+T). + +```text +Calculate the Charged versus Polar Bias (CvP) of protein sequence. The CvP bias +is the difference between the percentages of charged and polar residues in a +protein sequence, an indicator of hyperthermostability, calculated as: + +CvP = %(D + E + K + R) - %(N + Q + S + T) + +where %(X) is the percentage of residues of type X in the sequence. +``` + +```python +result = protpy.cvp_bias(protein_seq) +# Shape: 1 x 1 +``` + +- **Columns:** `CvP_Bias` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Suhre & Claverie (2003). J. Biol. Chem., 278(19), 17198-17202. https://doi.org/10.1074/jbc.M301327200 + +--- + +#### `flexibility` (Flex) + +Summary statistics of the Vihinen average-B-value flexibility profile (window 9, end weight 0.25). + +```text +Calculate the Flexibility (Flex) profile of protein sequence. Each amino acid +in the sequence is replaced by its normalised flexibility parameter (B-value) +and the values are averaged over a sliding window of 9 residues whose end +weights fall linearly to 0.25 (weights 0.25, 0.4375, 0.625, 0.8125, 1, ..., +0.25), as in Vihinen et al.. The profile is summarised by its mean, +maximum, minimum and standard deviation. The average B-values of Vihinen et +al. are used; their parameter sets for residues with 0, 1 or 2 rigid +neighbours are not. Compared with Biopython's ProteinAnalysis.flexibility, +which uses the same values and weights, each window is centred on its middle +residue and the last window is included. +``` + +```python +result = protpy.flexibility(protein_seq) +# Shape: 1 x 4 +``` + +- **Columns:** `Flex_mean`, `Flex_max`, `Flex_min`, `Flex_std` +- **Parameters:** `window`: int (default=9) — window length +- **Descriptor set:** extended, all +- **Reference:** Vihinen, Torkkila & Riikonen (1994). Proteins, 19(2), 141-149. https://doi.org/10.1002/prot.340190207 + +--- + +### Complexity + +#### `wootton_federhen_complexity` (WF) + +Wootton-Federhen local complexity (K1, K2) over sliding windows and the SEG-trigger low-complexity fraction. + +```text +Calculate the Wootton-Federhen Complexity (WF) of protein sequence. The +Wootton-Federhen measures describe the local compositional complexity of a +protein sequence over a sliding window, and are the basis of the SEG program +for finding low-complexity regions. For each window of W residues +they are calculated as: + +K1 = (1/W) * log_20(W! / prod_i n_i!) + +K2 = -sum_i (n_i / W) * log2(n_i / W) + +where K1 is the complexity state, K2 is the Shannon entropy of the window in +bits and n_i is the number of residues of amino acid i in the window. The +low-complexity fraction is the fraction of residues covered by at least one +window with K2 <= k2_trigger. This is the trigger stage of SEG only; SEG's +extension and merging of low-complexity segments are not performed. A window +longer than the sequence uses the whole sequence. +``` + +```python +result = protpy.wootton_federhen_complexity(protein_seq) +# Shape: 1 x 4 +``` + +- **Columns:** `WF_K1_mean`, `WF_K2_mean`, `WF_K2_min`, `WF_LowComplexity_frac` +- **Parameters:** `window`: int (default=12) — window length; `k2_trigger`: float (default=2.2) — K2 trigger (bits) +- **Descriptor set:** extended, all +- **Reference:** Wootton & Federhen (1993). Comput. Chem., 17(2), 149-163. https://doi.org/10.1016/0097-8485(93)85006-X + +--- + +#### `linguistic_complexity` (LC) + +Linguistic complexity: distinct subwords observed / maximum possible (ratio of sums over word lengths). + +```text +Calculate the Linguistic Complexity (LC) of protein sequence. Linguistic +complexity describes how repetitive a sequence is, as the number of distinct +subwords (k-mers) it contains relative to the maximum possible number for a +sequence of its length. It is calculated as: + +LC = sum_{k=1}^{K} U_k / sum_{k=1}^{K} min(20^k, L - k + 1) + +where U_k is the number of distinct k-mers in the sequence, L is the length of +the sequence and K is the longest subword length (by default L, i.e. all +subwords). Repetitive sequences have a low LC and random sequences an LC +close to 1. +``` + +```python +result = protpy.linguistic_complexity(protein_seq) +# Shape: 1 x 1 +``` + +- **Columns:** `LinguisticComplexity` +- **Parameters:** `max_k`: int | None (default=None) — longest word length (default L) +- **Descriptor set:** extended, all +- **Reference:** Troyanskaya et al. (2002). Bioinformatics, 18(5), 679-688. https://doi.org/10.1093/bioinformatics/18.5.679 + +--- + +#### `lempel_ziv_complexity` (LZ) + +Lempel-Ziv (LZ76) complexity c(n) and its normalised value c(n)·log20(n)/n. + +```text +Calculate the Lempel-Ziv Complexity (LZ) of protein sequence. The Lempel-Ziv +(LZ76) complexity is the number of new patterns, c(n), found when a sequence is +read from left to right, each pattern being the shortest substring that cannot +be copied from the preceding part of the sequence. As c(n) grows with the +length of the sequence, a normalised value is also calculated as: + +LZ_Normalized = c(n) * log_20(n) / n + +where n is the length of the sequence and 20 is the size of the amino acid +alphabet. The normalised value is 0 for a single residue. +``` + +```python +result = protpy.lempel_ziv_complexity(protein_seq) +# Shape: 1 x 2 +``` + +- **Columns:** `LZ_Complexity`, `LZ_Normalized` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Lempel & Ziv (1976). IEEE Trans. Inf. Theory, 22(1), 75-81. https://doi.org/10.1109/TIT.1976.1055501 + +--- + +### Signal + +#### `fft_descriptors` (FFT) + +Mean power of the FFT spectrum of a mean-centred property signal (default EIIP) in equal-width frequency bins. + +```text +Calculate Fast Fourier Transform (FFT) descriptors of protein sequence. Each +amino acid in the sequence is replaced by its value on a property scale, by +default the electron-ion interaction potential (EIIP) used in the resonant +recognition model, and the power spectrum of the mean-centred signal is +calculated as: + +P(f) = abs(FFT(x)(f))^2 / L + +where x is the property signal, f is the frequency in cycles per residue +(0 to 0.5) and L is the length of the sequence. To give the same number of +features for any sequence length, the frequency range 0-0.5 is split into +n_bins equal-width bins and the mean power in each bin is returned; bins +containing no frequencies are 0. +``` + +```python +result = protpy.fft_descriptors(protein_seq) +# Shape: 1 x n_bins (default 1 x 10) +``` + +- **Columns:** `FFT_eiip_bin1`, `FFT_eiip_bin2`, `FFT_eiip_bin3`, `FFT_eiip_bin4`, `FFT_eiip_bin5`, `FFT_eiip_bin6`, … +- **Parameters:** `scale`: str | dict (default='eiip') — single-value scale, AAindex code or dict; `n_bins`: int (default=10) — number of frequency bins +- **Descriptor set:** extended, all +- **Reference:** Cosic (1994). IEEE Trans. Biomed. Eng., 41(12), 1101-1114. https://doi.org/10.1109/10.335859 + +--- + +#### `dct_descriptors` (DCT) + +First n orthonormal DCT-II coefficients of a mean-centred property signal (default Kyte-Doolittle). + +```text +Calculate Discrete Cosine Transform (DCT) descriptors of protein sequence. +Each amino acid in the sequence is replaced by its value on a property scale, +by default the Kyte-Doolittle hydropathy, and the first n_coeffs coefficients +of the orthonormal type-II DCT of the mean-centred signal are returned. The +DCT compresses a sequence-length signal into a fixed number of coefficients +, calculated as: + +X_k = s_k * sum_{n=0}^{L-1} x_n * cos(pi * k * (2n + 1) / (2L)) + +where X_k is coefficient k, x_n is the property value at position n, L is the +length of the sequence, and s_0 = sqrt(1/L) and s_k = sqrt(2/L) for k > 0. X_0 +is 0 because the signal is mean-centred. +``` + +```python +result = protpy.dct_descriptors(protein_seq) +# Shape: 1 x n_coeffs (default 1 x 10) +``` + +- **Columns:** `DCT_kyte_doolittle_0`, `DCT_kyte_doolittle_1`, `DCT_kyte_doolittle_2`, `DCT_kyte_doolittle_3`, `DCT_kyte_doolittle_4`, `DCT_kyte_doolittle_5`, … +- **Parameters:** `scale`: str | dict (default='kyte_doolittle') — single-value scale, AAindex code or dict; `n_coeffs`: int (default=10) — number of coefficients (<= L) +- **Descriptor set:** extended, all +- **Reference:** Wang et al. (2017). PLoS ONE, 12(9), e0185587. https://doi.org/10.1371/journal.pone.0185587 + +--- + +### Structural + +#### `aggrescan` (AGG) + +AGGRESCAN aggregation hot-spot descriptors from the a3v propensity profile (hot spots: >=5 residues above -0.02, no Pro). + +```text +Calculate AGGRESCAN (AGG) aggregation descriptors of protein sequence. +AGGRESCAN predicts aggregation "hot spots", short stretches that promote +protein aggregation, from experimental aggregation propensities (a3v) of the +20 amino acids. The a3v values are averaged over a sliding window, giving +the aggregation profile (a4v), with each window value assigned to its central +residue and the off-centre N- and C-terminal residues given the first and last +window values. A hot spot is a run of 5 or more residues with an a4v above the +hot-spot threshold (HST) of -0.02 and containing no proline. Areas are +calculated by trapezoidal integration of the profile, with HST as the zero axis. + +The 12 output values are: the mean a3v of the sequence (a3vSA); the number of +hot spots (nHS) and the number per 100 residues (NnHS); the area of the profile +above HST (AAT) and per residue (AATr); the total area of the hot spots (THSA) +and per residue (THSAr); the total area of the profile (TA); the sum of a4v +(a4vSS) and per 100 residues (Na4vSS); the mean a4v in the hot spots (a4vAHS, +0 if there are none); and the largest single hot-spot area (HSA_max). + +AGGRESCAN chooses a window of 5, 7, 9 or 11 residues from the sequence length +and gives the two extreme residues specific values; neither rule is given in +the published supplementary material, so the window is a parameter and the +extreme residues are treated like the other off-centre residues. +``` + +```python +result = protpy.aggrescan(protein_seq) +# Shape: 1 x 12 +``` + +- **Columns:** `AGG_a3vSA`, `AGG_nHS`, `AGG_NnHS`, `AGG_AAT`, `AGG_AATr`, `AGG_THSA`, … +- **Parameters:** `window`: int (default=5) — odd window length (AGGRESCAN uses 5, 7, 9, 11) +- **Descriptor set:** extended, all +- **Reference:** Conchillo-Solé et al. (2007). BMC Bioinformatics, 8, 65. https://doi.org/10.1186/1471-2105-8-65 + +--- + +#### `uversky_charge_hydropathy` (UV) + +Uversky mean hydropathy , mean net charge and boundary distance HQ = 2.785 - - 1.151. + +```text +Calculate the Uversky Charge-Hydropathy (UV) descriptors of protein sequence. +Uversky et al. showed that natively unfolded proteins can be separated from +folded proteins by their mean net charge and mean hydropathy. These are +calculated as: + + = mean of (KD + 4.5) / 9 + + = abs(n+ - n-) / L + +HQ = 2.785 - - 1.151 + +where KD is the Kyte-Doolittle hydropathy of each residue, n+ and n- are the +numbers of positive (K, R) and negative (D, E) residues and L is the length +of the sequence. HQ is the distance from the boundary = 2.785 - 1.151 +(as restated in); the protein is predicted to be unfolded when HQ < 0. +``` + +```python +result = protpy.uversky_charge_hydropathy(protein_seq) +# Shape: 1 x 4 +``` + +- **Columns:** `UV_MeanH`, `UV_MeanR`, `UV_HQ`, `UV_Unfolded` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Uversky, Gillespie & Fink (2000). Proteins, 41(3), 415-427. https://doi.org/10.1002/1097-0134(20001115)41:3<415::AID-PROT130>3.0.CO;2-7 + +--- + +#### `fold_index` (FI) + +FoldIndex profile summary: windowed 2.785 - abs() - 1.151 and disordered-residue statistics. + +```text +Calculate the FoldIndex (FI) of protein sequence. FoldIndex applies the +Uversky charge-hydropathy boundary over a sliding window to find disordered +regions of a protein sequence. For each window it is calculated as: + +FI = 2.785 - abs() - 1.151 + +where is the mean Kyte-Doolittle hydropathy of the window rescaled to 0-1 +and is the mean net charge of the window (K, R positive; D, E negative). +Residues covered by at least one window with FI < 0 are counted as disordered. +A window longer than the sequence uses the whole sequence, so FI then equals +UV_HQ from uversky_charge_hydropathy. +``` + +```python +result = protpy.fold_index(protein_seq) +# Shape: 1 x 4 +``` + +- **Columns:** `FI_mean`, `FI_min`, `FI_frac_disordered`, `FI_longest_disordered` +- **Parameters:** `window`: int (default=51) — window length +- **Descriptor set:** extended, all +- **Reference:** Prilusky et al. (2005). Bioinformatics, 21(16), 3435-3438. https://doi.org/10.1093/bioinformatics/bti537 + +--- + +#### `disorder_propensity` (DP) + +Fractions of disorder- and order-promoting residues and mean TOP-IDP value. + +```text +Calculate the Disorder Propensity (DP) of protein sequence. Intrinsically +disordered proteins are enriched in disorder-promoting residues (A, R, G, Q, +S, P, E, K) and depleted in order-promoting residues (W, C, F, I, Y, V, L, N) +. The descriptor gives the fraction of each, together with the mean value of +the TOP-IDP scale, a scale of amino acid propensity for intrinsic disorder, +calculated as: + +DP_TOP_IDP_mean = sum_i TOP-IDP(s_i) / L + +where TOP-IDP(s_i) is the TOP-IDP value of the amino acid at position i and L +is the length of the sequence. Higher values indicate a higher propensity for +disorder. +``` + +```python +result = protpy.disorder_propensity(protein_seq) +# Shape: 1 x 3 +``` + +- **Columns:** `DP_frac_disorder_promoting`, `DP_frac_order_promoting`, `DP_TOP_IDP_mean` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Campen et al. (2008). Protein Pept. Lett., 15(9), 956-963. https://doi.org/10.2174/092986608785849164 + +--- + +#### `transmembrane_features` (TM) + +Kyte-Doolittle transmembrane-segment heuristics (segment count, max hydropathy, TM fraction, positive-inside bias). + +```text +Calculate Transmembrane (TM) segment features of protein sequence. Putative +transmembrane segments are found from the Kyte-Doolittle hydropathy profile: +windows of 19 residues with a mean hydropathy above 1.6 indicate a likely +transmembrane segment, and overlapping windows are merged into segments. +The positive-inside rule states that the cytoplasmic side of a transmembrane +segment is enriched in positive residues. The 4 output values are: the +number of segments; the maximum window hydropathy; the fraction of residues +inside a segment; and the positive-inside bias, the number of K and R residues +in the 15 residues before the first segment minus the number in the 15 residues +after it (0 if there is no segment). This is a hydropathy heuristic and not a +topology predictor. +``` + +```python +result = protpy.transmembrane_features(protein_seq) +# Shape: 1 x 4 +``` + +- **Columns:** `TM_n_segments`, `TM_max_KD`, `TM_frac_in_TM`, `TM_positive_inside_bias` +- **Parameters:** `window`: int (default=19) — hydropathy window; `kd_threshold`: float (default=1.6) — segment threshold +- **Descriptor set:** extended, all +- **Reference:** Kyte & Doolittle (1982). J. Mol. Biol., 157(1), 105-132. https://doi.org/10.1016/0022-2836(82)90515-0 + +--- + +#### `signal_peptide_features` (SP) + +N-terminal signal-peptide heuristics: n-region net charge and maximum 8-residue hydropathy. + +```text +Calculate Signal Peptide (SP) features of protein sequence. Signal peptides +have a positively charged N-terminal n-region followed by a hydrophobic +h-region. The 2 output values are: the net charge of the first 5 residues +(K and R +1, D and E -1); and the maximum mean Kyte-Doolittle hydropathy over +8-residue windows within the first n_term residues. The von Heijne +cleavage-site weight matrix is not included. +``` + +```python +result = protpy.signal_peptide_features(protein_seq) +# Shape: 1 x 2 +``` + +- **Columns:** `SP_nregion_charge`, `SP_hregion_maxKD` +- **Parameters:** `n_term`: int (default=30) — N-terminal region length +- **Descriptor set:** extended, all +- **Reference:** von Heijne (1983). Eur. J. Biochem., 133(1), 17-21. https://doi.org/10.1111/j.1432-1033.1983.tb07424.x + +--- + +#### `functional_site_counts` (FS) + +Counts and per-100-residue densities of PROSITE N-glycosylation, ER-retention and PTS1 motifs and tryptic sites. + +```text +Calculate Functional Site (FS) counts of protein sequence. The number of each +functional sequence motif, and its density per 100 residues, is counted for: +N-glycosylation sites (PROSITE PS00001, N-{P}-[ST]-{P}), the C-terminal +endoplasmic reticulum retention signal (PS00014, [KRHQSA]-[DENQ]-E-L at the +C-terminus), the C-terminal peroxisomal targeting signal 1 (PS00342, +[STAGCN]-[RKH]-[LIVMAFY] at the C-terminus) and tryptic cleavage sites +(after K or R, not before P). Overlapping matches are counted. These motifs +complement the default motifs of motif_composition. +``` + +```python +result = protpy.functional_site_counts(protein_seq) +# Shape: 1 x 8 +``` + +- **Columns:** `FS_N_glycosylation`, `FS_ER_retention`, `FS_PTS1`, `FS_tryptic_site`, `FS_N_glycosylation_per100`, `FS_ER_retention_per100`, … +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Sigrist et al. (2013). Nucleic Acids Res., 41(D1), D344-D347 (PROSITE). https://doi.org/10.1093/nar/gks1067 + +--- + +#### `plaac` (PLAAC) + +PLAAC prion-like domain scores: max window LLR, HMM core score, PrD length and presence. + +```text +Calculate the Prion-Like Amino Acid Composition (PLAAC) descriptors of protein +sequence. PLAAC identifies prion-like domains, whose composition resembles +that of yeast prion domains, using the amino acid frequencies of prion domains +and of the S. cerevisiae proteome. Each residue has a log-likelihood ratio: + +LLR(s_i) = ln(f_prion(s_i) / f_background(s_i)) + +The 4 output values are: the maximum sum of LLR over any window of core_length +residues (PLAAC_LLR); the maximum sum of LLR over a window of core_length +residues lying entirely inside a prion-like region of the two-state hidden +Markov model Viterbi parse (PLAAC_COREscore); the length of the prion-like +region containing that core (PLAAC_PrD_length); and whether the sequence has a +prion-like region of at least core_length residues (PLAAC_has_PrD). As in +PLAAC, PLAAC_LLR and PLAAC_COREscore are NaN when they are undefined (a +sequence shorter than core_length, or no prion-like region of that length). +The parameters are ported from the PLAAC source code (MIT License), using the +S. cerevisiae background (alpha = 1). +``` + +```python +result = protpy.plaac(protein_seq) +# Shape: 1 x 4 +``` + +- **Columns:** `PLAAC_LLR`, `PLAAC_COREscore`, `PLAAC_PrD_length`, `PLAAC_has_PrD` +- **Parameters:** `core_length`: int (default=60) — minimal core length +- **Descriptor set:** extended, all +- **Reference:** Lancaster et al. (2014). Bioinformatics, 30(17), 2501-2502. https://doi.org/10.1093/bioinformatics/btu310 + +--- + +#### `antigenicity_surface` (Antigenicity) + +Profile statistics of the Parker hydrophilicity and Janin surface scales over 7-residue windows. + +```text +Calculate the Antigenicity and Surface (Antigenicity) profiles of protein +sequence. Antigenic sites tend to be hydrophilic and exposed on the protein +surface. Each amino acid in the sequence is replaced by its value on the +Parker hydrophilicity scale, derived from HPLC peptide retention times, +and on the Janin scale of free energy of transfer from the inside to the +outside of a globular protein. Each is averaged over a sliding window of 7 +residues, as used by Parker et al., and the profile is summarised by its mean, +maximum, minimum and standard deviation. +``` + +```python +result = protpy.antigenicity_surface(protein_seq) +# Shape: 1 x 8 +``` + +- **Columns:** `PARKER_mean`, `PARKER_max`, `PARKER_min`, `PARKER_std`, `JANIN_mean`, `JANIN_max`, … +- **Parameters:** `window`: int (default=7) — window length +- **Descriptor set:** extended, all +- **Reference:** Parker, Guo & Hodges (1986). Biochemistry, 25(19), 5425-5432. https://doi.org/10.1021/bi00367a013 + +--- + +#### `cysteine_features` (Cys) + +Cysteine count, fraction, parity, maximum disulfides, CxxC motifs and mean spacing. + +```text +Calculate Cysteine (Cys) features of protein sequence. Cysteines form +disulfide bonds and functional motifs such as the CxxC motif of thiol +oxidoreductases. The 6 output values are: the number of cysteines; their +fraction; whether the number is even; the maximum number of disulfide bonds +(the number of cysteines // 2); the number of overlapping C-x-x-C motifs; and +the mean gap between consecutive cysteines (0 if there are fewer than 2). +``` + +```python +result = protpy.cysteine_features(protein_seq) +# Shape: 1 x 6 +``` + +- **Columns:** `Cys_count`, `Cys_frac`, `Cys_even`, `Cys_max_disulfides`, `Cys_CxxC`, `Cys_mean_spacing` +- **Parameters:** sequence only +- **Descriptor set:** extended, all +- **Reference:** Fomenko & Gladyshev (2003). Biochemistry, 42(38), 11214-11225. https://doi.org/10.1021/bi034459s + +--- + +### Extensions to existing descriptors (v1.5.0) + +Defaults are unchanged; the new behaviour is opt-in. + +| Function | New parameter(s) | Effect | Reference | +|---|---|---|---| +| `reduced_alphabet_composition` | `alphabet`, `k` | `alphabet="ifeature5"` with `k=1/2/3` gives GAAC (1 x 5), GDPC (1 x 25), GTPC (1 x 125); cp13/14/19/20 also accepted | Chen et al. 2018, 10.1093/bioinformatics/bty140 | +| `conjoint_triad` | `gap` | `gap>0` gives KSCTriad, triads (s_i, s_{i+g+1}, s_{i+2g+2}) for g = 0..gap, columns suffixed `_gap{g}` | Chen et al. 2018; Shen et al. 2007, 10.1073/pnas.0607879104 | +| `ctd_` | `property_set` | `"ifeature13"` adds 6 AAindex hydrophobicity partitions (13 properties, 1 x 273) | Dubchak et al. 1995, 10.1073/pnas.92.19.8700 | +| `amino_acid_composition`, `dipeptide_composition` | `region`, `n` | `"nterm"`, `"cterm"` or `"split"` (N-terminal / middle / C-terminal blocks, prefixes `NT_`/`MID_`/`CT_`) | Kumar et al. 2006, 10.1074/jbc.M511061200 | +| `isoelectric_point`, `charge_distribution` | `pka_scale` | `"emboss"`, `"bjellqvist"` (with residue-specific terminal pKa) or `"ipc_protein"` | Bjellqvist et al. 1993, 10.1002/elps.11501401163; Kozlowski 2016, 10.1186/s13062-016-0159-9 | +| `gravy` | `scale` | average any single-value scale or AAindex code (column `GRAVY_{scale}`) | per scale | +| `hydrophobic_moment` | `scale`, `angle="helix"/"sheet"` | alternative hydrophobicity scale; named angles 100° / 160° | Eisenberg et al. 1984, 10.1073/pnas.81.1.140 | +| `extinction_coefficient` | `absorbance` | adds `Abs01_Reduced`, `Abs01_Oxidized` (A280 of a 1 g/L solution, ε / MW) | Pace et al. 1995, 10.1002/pro.5560041120 | + +--- + ## Utility Functions ### `all_descriptors` @@ -713,14 +2124,22 @@ Compute all available descriptors for a single protein sequence and return them | `sequence` | str | — | Protein sequence | | `params` | dict or None | `None` | Per-descriptor keyword arguments keyed by descriptor name | | `show_progress` | bool | `False` | Display a `tqdm` progress bar naming each descriptor as it is computed | -| `output_file` | str or None | `"descriptors.csv"` | Path to save results; `None` disables file output | +| `output_file` | str or None | `None` | Path to save results; `None` disables file output | | `save_each_sequence` | bool | `True` | Append this row to the output CSV immediately after computation; useful when calling in a loop | +| `descriptor_set` | str | `"classic"` | `"classic"` (v1.4 set, unchanged), `"extended"` (adds the v1.5 descriptors with ≤150 columns) or `"all"` (adds every v1.5 descriptor) | +| `include` | list[str] or None | `None` | Extra registered descriptors to compute | +| `exclude` | list[str] or None | `None` | Descriptors to skip (classic ones included, e.g. `"tripeptide_composition"`) | ```python import protpy seq = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDISIQEFKD" +# v1.5 descriptor sets +df = protpy.all_descriptors(seq, descriptor_set="extended") +df = protpy.all_descriptors(seq, include=["kappa", "plaac"], exclude=["tripeptide_composition"], + params={"kappa": {"blob_sizes": (5,)}}) + # Basic usage df = protpy.all_descriptors(seq) # Shape: 1 x 10757 (all descriptor features concatenated) @@ -923,6 +2342,9 @@ The descriptors implemented in protpy are based on the following published metho - Dubchak, I., et al. (1995). Prediction of protein folding class using global description of amino acid sequence. *PNAS*, 92(19), 8700–8704. - Dubchak, I., et al. (1999). Recognition of a protein fold in the context of the SCOP classification. *Proteins*, 35(4), 401–407. +**v1.5.0 descriptors** +- Each v1.5.0 descriptor above lists its primary reference and DOI. BibTeX for every reference is in [`docs/references.bib`](docs/references.bib). + **Sequence Order** - Grantham, R. (1974). Amino acid difference formula to help explain protein evolution. *Science*, 185(4154), 862–864. - Schneider, G., & Wrede, P. (1994). The rational design of amino acid sequences by artificial neural networks and simulated molecular evolution. *Biophysical Journal*, 66(2), 335–344. diff --git a/README.md b/README.md index c478308..cc0edf7 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,24 @@ The descriptors available in `protpy` include: +### New in v1.5.0 + +| Category | New descriptors | +|---|---| +| Composition | `cksaap`, `cksaagp`, `dde`, `asdc`, `distance_pair`, `natural_vector`, `atomic_composition` | +| Scales | `z_scales_5`, `vhse`, `atchley_factors`, `t_scales`, `st_scales`, `protfp`, `fasgai`, `blosum_indices`, `ms_whim`, `auto_cross_covariance`, `physicochemical_distance_transformation` | +| Physicochemical | `charge_fractions`, `ncpr`, `kappa`, `sequence_charge_decoration`, `charge_density`, `hydrophobicity_profile`, `hydrophobic_ratio`, `half_life`, `ivywrel_index`, `cvp_bias`, `flexibility` | +| Complexity | `wootton_federhen_complexity`, `linguistic_complexity`, `lempel_ziv_complexity` | +| Signal | `fft_descriptors`, `dct_descriptors` | +| Structural | `aggrescan`, `uversky_charge_hydropathy`, `fold_index`, `disorder_propensity`, `transmembrane_features`, `signal_peptide_features`, `functional_site_counts`, `plaac`, `antigenicity_surface`, `cysteine_features` | +| Extensions | GAAC/GDPC/GTPC (`reduced_alphabet_composition(alphabet="ifeature5", k=...)`), KSCTriad (`conjoint_triad(gap=...)`), 13-property CTD (`ctd_(property_set="ifeature13")`), split AAC/DPC (`region=`), `pka_scale=` for pI/charge, `scale=` for GRAVY/hydrophobic moment, A280 absorbance | + +`all_descriptors` and `encode_dataset` take `descriptor_set="classic"` (default, unchanged output), `"extended"` or `"all"`, plus `include=` / `exclude=` lists: + +```python +df = protpy.all_descriptors(protein_seq, descriptor_set="extended") +``` + More detail of each descriptor is listed in the markdown file: [`DESCRIPTORS.md`](https://github.com/amckenna41/protPy/blob/main/DESCRIPTORS.md) @@ -433,6 +451,23 @@ qso = protpy.quasi_sequence_order(protein_seq, lag=10, weight=0.2, distance_matr +### Scaling features for machine learning + +protpy returns each descriptor on its natural scale (percentages, counts, indices, pH, Da, ...). If your classifier needs features on a common scale such as [0, 1], fit a scaler on your **training** data and reuse it for test and new sequences. This scales every column to the range seen in your data without leaking information from the test set: + +```python +from sklearn.preprocessing import MinMaxScaler + +X_train = protpy.encode_dataset(train_seqs, output_file=None, verbose=False).set_index("sequence_id") +X_test = protpy.encode_dataset(test_seqs, output_file=None, verbose=False).set_index("sequence_id") + +scaler = MinMaxScaler(clip=True).fit(X_train) # fit on training data only +X_train_scaled = scaler.transform(X_train) # every column in [0, 1] +X_test_scaled = scaler.transform(X_test) # clip=True keeps unseen extremes in [0, 1] +``` + +`StandardScaler` is a common alternative, and tree-based models don't need scaling. When cross-validating, put the scaler in a scikit-learn `Pipeline` so it is re-fitted on each training fold. Impute the few `NaN` values that `descriptor_set="extended"` can produce (e.g. with `SimpleImputer`) before scaling. + Documentation ------------- The documentation for `protpy` is hosted on **ReadTheDocs** and is available [here][readthedocs]. diff --git a/ToDo.md b/ToDo.md index 25b766e..673cdaa 100644 --- a/ToDo.md +++ b/ToDo.md @@ -56,7 +56,7 @@ - [X] In "Parameters' and 'returns' , remove space between colon. - [X] Read over code. - [X] Create demo -- [ ] Add equations of descriptors to markdown file. https://bmcbioinformatics.biomedcentral.com/articles/10.1186/s12859-015-0554-8#Sec10 - https://bmcbioinformatics.biomedcentral.com/articles/10.1186/1471-2105-8-300 +- [x] Add equations of descriptors to markdown file (v1.5.0 descriptors in DESCRIPTORS.md). https://bmcbioinformatics.biomedcentral.com/articles/10.1186/s12859-015-0554-8#Sec10 - https://bmcbioinformatics.biomedcentral.com/articles/10.1186/1471-2105-8-300 - [X] Remove python 3.7 references, 3.8 minimum. - [X] In descriptor comments, dimensions of output should be 1 x N rather than N x 1 (N=# of features). - [X] Remove biopython from requirments & setup.py - required for testing. diff --git a/docs/api.rst b/docs/api.rst index 2af9546..934ff95 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -34,7 +34,14 @@ Composition boman_index, aggregation_propensity, hydrophobic_moment, - shannon_entropy + shannon_entropy, + cksaap, + cksaagp, + dde, + asdc, + distance_pair, + natural_vector, + atomic_composition :undoc-members: :show-inheritance: @@ -82,11 +89,51 @@ Sequence Order :undoc-members: :show-inheritance: +Scales +------ + +.. automodule:: protpy.scales + :members: + :undoc-members: + :show-inheritance: + +Physicochemical +--------------- + +.. automodule:: protpy.physicochemical + :members: + :undoc-members: + :show-inheritance: + +Complexity +---------- + +.. automodule:: protpy.complexity + :members: wootton_federhen_complexity, linguistic_complexity, lempel_ziv_complexity + :undoc-members: + :show-inheritance: + +Signal +------ + +.. automodule:: protpy.signal + :members: + :undoc-members: + :show-inheritance: + +Structural +---------- + +.. automodule:: protpy.structural + :members: + :undoc-members: + :show-inheritance: + Encode ------ .. automodule:: protpy.encode - :members: all_descriptors + :members: all_descriptors, encode_dataset :undoc-members: :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index fd4dd2f..c8b5d9d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,7 @@ project = 'protpy' copyright = '2026, AJ McKenna' author = 'AJ McKenna' -release = '1.4.2' +release = '1.5.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -39,3 +39,22 @@ # html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] + + +#protpy docstrings write parameters as ":name: type" under a "Parameters" heading; napoleon +#expects numpy's "name : type", so rewrite those lines before napoleon (priority 500) runs +import re as _re +_FIELD = _re.compile(r"^(\s*):(\w+):\s*(.*)$") + +def _house_style_to_numpy(app, what, name, obj, options, lines): + section = None + for i, line in enumerate(lines): + stripped = line.strip() + if stripped in ("Parameters", "Returns", "Raises") and i + 1 < len(lines) and set(lines[i + 1].strip()) <= {"=", "-"}: + section = stripped + match = _FIELD.match(line) + if section in ("Parameters", "Returns") and match: + lines[i] = f"{match.group(1)}{match.group(2)} : {match.group(3)}" + +def setup(app): + app.connect("autodoc-process-docstring", _house_style_to_numpy, priority=400) diff --git a/docs/descriptors.rst b/docs/descriptors.rst index 3792478..8d43de9 100644 --- a/docs/descriptors.rst +++ b/docs/descriptors.rst @@ -433,7 +433,7 @@ windows and the fraction of the sequence covered. - Minimum mean hydrophobicity * - ``charge_threshold`` - int - - ``1`` + - ``0`` - Maximum charged residues per window .. code-block:: python @@ -1109,6 +1109,1390 @@ the results. ---- +v1.5.0 Descriptors +------------------ + +Descriptors added in v1.5.0, each with its formula, output shape and primary reference. +``descriptor_set="extended"`` in ``all_descriptors`` / ``encode_dataset`` adds the descriptors marked +*extended*; ``"all"`` adds the rest as well. See ``planning/BLOCKED.md`` for plan items that +could not be implemented without unverifiable data. + +cksaap (CKSAAP) — Composition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Composition of k-spaced amino acid pairs: CKSAAP(XY,g) = N_g(XY) / (L-g-1) x 100 for g = 0..gap. + +.. code-block:: text + + Calculate Composition of k-Spaced Amino Acid Pairs (CKSAAP) of protein + sequence. CKSAAP describes the frequency of each amino acid pair separated + by g residues within a protein sequence, for each gap g in the range + 0..gap. With 400 possible amino acid pairs per gap this creates a + 400 * (gap+1) Dimensional vector, calculated as: + + CKSAAP(XY, g) = N_g(XY) / (L - g - 1) * 100 + + where CKSAAP(XY, g) is the composition of the amino acid pair XY with g + residues between X and Y, N_g(XY) is the number of such pairs in the sequence + and L is the length of the sequence. A gap of 0 is the same as the + dipeptide composition. A gap with no pairs (L - g - 1 <= 0) gives values + of 0. + +.. code-block:: python + + result = protpy.cksaap(protein_seq) + # Shape: 1 x 400·(gap+1) (default 1 x 2400) + +* **Parameters:** ``gap``: int (default=5) — maximum spacing +* **Descriptor set:** all +* **Reference:** Chen, Kurgan & Ruan (2007). BMC Struct. Biol., 7, 25. https://doi.org/10.1186/1472-6807-7-25 + +---- + +cksaagp (CKSAAGP) — Composition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Composition of k-spaced amino acid group pairs over the iFeature 5-group alphabet. + +.. code-block:: text + + Calculate Composition of k-Spaced Amino Acid Group Pairs (CKSAAGP) of protein + sequence. CKSAAGP is the same as CKSAAP but the amino acids are first mapped + to 5 groups: aliphatic (GAVLMI), aromatic (FYW), positive charge (KRH), + negative charge (DE) and uncharged (STCPNQ). With 25 possible group pairs + per gap this creates a 25 * (gap+1) Dimensional vector, calculated as: + + CKSAAGP(G1G2, g) = N_g(G1G2) / (L - g - 1) * 100 + + where CKSAAGP(G1G2, g) is the composition of the group pair G1G2 with g + residues between them, N_g(G1G2) is the number of such pairs in the sequence + and L is the length of the sequence. The 25 values for each gap sum to 100. + +.. code-block:: python + + result = protpy.cksaagp(protein_seq) + # Shape: 1 x 25·(gap+1) (default 1 x 150) + +* **Parameters:** ``gap``: int (default=5) — maximum spacing +* **Descriptor set:** extended, all +* **Reference:** Chen et al. (2018). Bioinformatics, 34(14), 2499-2502 (iFeature). https://doi.org/10.1093/bioinformatics/bty140 + +---- + +dde (DDE) — Composition +~~~~~~~~~~~~~~~~~~~~~~~ + +Dipeptide deviation from expected mean: (Dc - Tm) / sqrt(Tv), with Tm from codon counts. + +.. code-block:: text + + Calculate Dipeptide Deviation from Expected Mean (DDE) of protein sequence. + DDE compares the observed dipeptide composition of a protein sequence with + the composition expected from the number of codons that code for each amino + acid. For each of the 400 dipeptides it is calculated as: + + Dc(XY) = N(XY) / (L - 1) + + Tm(XY) = (C_X / 61) * (C_Y / 61) + + Tv(XY) = Tm(XY) * (1 - Tm(XY)) / (L - 1) + + DDE(XY) = (Dc(XY) - Tm(XY)) / sqrt(Tv(XY)) + + where Dc(XY) is the observed composition of dipeptide XY, N(XY) is the + number of XY dipeptides, L is the length of the sequence, Tm(XY) and Tv(XY) + are the theoretical mean and variance, and C_X and C_Y are the number of + codons for amino acids X and Y out of the 61 sense codons. Unlike the + other composition descriptors, DDE values are not percentages. + +.. code-block:: python + + result = protpy.dde(protein_seq) + # Shape: 1 x 400 + +* **Parameters:** sequence only +* **Descriptor set:** all +* **Reference:** Saravanan & Gautham (2015). OMICS, 19(10), 648-658. https://doi.org/10.1089/omi.2015.0095 + +---- + +asdc (ASDC) — Composition +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Adaptive skip dipeptide composition: composition of all ordered pairs (s_i, s_j), i < j. + +.. code-block:: text + + Calculate Adaptive Skip Dipeptide Composition (ASDC) of protein sequence. + ASDC is a modified dipeptide composition that counts every ordered pair of + amino acids (s_i, s_j), where i < j, at all gaps along the sequence, rather + than just adjacent pairs, calculated as: + + ASDC(XY) = sum_g N_g(XY) / sum_g sum_XY N_g(XY) * 100 + + where ASDC(XY) is the composition of the amino acid pair XY, N_g(XY) is the + number of XY pairs separated by g residues, and the denominator is the total + number of ordered pairs in the sequence, L(L-1)/2, where L is the length of + the sequence. The 400 values sum to 100. + +.. code-block:: python + + result = protpy.asdc(protein_seq) + # Shape: 1 x 400 + +* **Parameters:** sequence only +* **Descriptor set:** all +* **Reference:** Wei et al. (2018). Bioinformatics, 34(23), 4007-4016 (ACPred-FL). https://doi.org/10.1093/bioinformatics/bty451 + +---- + +distance_pair (DP) — Composition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PseAAC of distance-pairs over a reduced cluster profile (cp13/cp14/cp19/cp20). + +.. code-block:: text + + Calculate Distance Pair (DP) descriptor of protein sequence. The descriptor + incorporates amino acid distance pairs and a reduced amino acid alphabet into + the general pseudo amino acid composition, as used in iDNA-Prot|dis. + The amino acids are first mapped to the clusters of a cluster profile cp(n), + and the descriptor is the composition of the clusters followed by the + composition of cluster pairs at each distance d = 1..rng: + + DP(c) = N(c) / L * 100 + + DP(c1c2, d) = N_d(c1c2) / (L - d) * 100 + + where N(c) is the number of residues in cluster c, N_d(c1c2) is the number of + residue pairs (s_i, s_i+d) whose residues are in clusters c1 and c2, and L is + the length of the sequence. A distance of 1 is the nearest-neighbour pair. + The available cluster profiles are cp13, cp14, cp19 and cp20 (no reduction). + The published cp(14) lists E twice; the extra E in the first cluster is + dropped, as it is the only reading that gives 14 clusters. + +.. code-block:: python + + result = protpy.distance_pair(protein_seq) + # Shape: 1 x (G + G²·rng) (default 1 x 602) + +* **Parameters:** ``rng``: int (default=3) — maximum pair distance; ``alphabet``: str (default='cp14') — cluster profile +* **Descriptor set:** all +* **Reference:** Liu et al. (2014). PLoS ONE, 9(9), e106691 (iDNA-Prot|dis). https://doi.org/10.1371/journal.pone.0106691 + +---- + +natural_vector (NV) — Composition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Natural vector: count, mean position and normalised second central moment of each amino acid. + +.. code-block:: text + + Calculate Natural Vector (NV) of protein sequence. The natural vector + describes the number of each amino acid in a protein sequence together with + the mean and the normalized central moment of their positions, calculated as: + + n_k = number of occurrences of amino acid k + + mu_k = sum of the positions of k / n_k + + D2_k = sum of (position of k - mu_k)^2 / (n_k * L) + + where positions start at 1, L is the length of the sequence and k is each of + the 20 canonical amino acids. mu_k and D2_k are 0 when amino acid k is + not in the sequence. + +.. code-block:: python + + result = protpy.natural_vector(protein_seq) + # Shape: 1 x 60 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Yu et al. (2013). J. Theor. Biol., 318, 197-204. https://doi.org/10.1016/j.jtbi.2012.11.005 + +---- + +atomic_composition (ATC) — Composition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Percentage of C, H, N, O and S atoms over the residues' free amino-acid formulas. + +.. code-block:: text + + Calculate Atomic Composition (ATC) of protein sequence. ATC describes the + percentage of carbon, hydrogen, nitrogen, oxygen and sulphur atoms in a + protein sequence, using the molecular formulas of the free amino acids, + calculated as: + + ATC(e) = N_e / (N_C + N_H + N_N + N_O + N_S) * 100 + + where ATC(e) is the atomic composition of element e and N_e is the number of + atoms of element e summed over all residues. By default the formulas of + the free amino acids are summed without removing water, matching Pfeature + . With subtract_water=True, one water molecule is removed for each of the + L - 1 peptide bonds, giving the formula of the peptide (e.g. "GG" -> C4H8N2O3). + +.. code-block:: python + + result = protpy.atomic_composition(protein_seq) + # Shape: 1 x 5 + +* **Parameters:** ``subtract_water``: bool (default=False) — remove (L-1) H2O +* **Descriptor set:** extended, all +* **Reference:** Pande et al. (2023). J. Comput. Biol., 30(2), 204-222 (Pfeature). https://doi.org/10.1089/cmb.2022.0241 + +---- + +z_scales_5 (Z5) — Scales +~~~~~~~~~~~~~~~~~~~~~~~~ + +Mean of the Sandberg 5 z-scales over the sequence (optionally per domain). + +.. code-block:: text + + Calculate Sandberg 5 z-scales (Z5) of protein sequence. The z-scales are the + principal components of a set of physicochemical properties measured for 87 + coded and non-coded amino acids. z1 mainly describes lipophilicity, z2 + steric bulk and polarizability, z3 polarity and charge, and z4 and z5 + electronegativity, heat of formation, electrophilicity and hardness. + + Each amino acid in the sequence is replaced by its 5 z-scales and the mean of + each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.z_scales_5(protein_seq) + # Shape: 1 x 5·domains (default 1 x 5) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** Sandberg et al. (1998). J. Med. Chem., 41(14), 2481-2491. https://doi.org/10.1021/jm9700575 + +---- + +vhse (VHSE) — Scales +~~~~~~~~~~~~~~~~~~~~ + +Mean of the VHSE (hydrophobic, steric, electronic) scales over the sequence (optionally per domain). + +.. code-block:: text + + Calculate Vectors of Hydrophobic, Steric and Electronic properties (VHSE) of + protein sequence. The VHSE scales are principal components score vectors derived + from physicochemical variables of the 20 coded amino acids. VHSE1-2 describe + hydrophobic properties, VHSE3-4 steric properties and VHSE5-8 electronic + properties. + + Each amino acid in the sequence is replaced by its 8 VHSE scales and the mean of + each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.vhse(protein_seq) + # Shape: 1 x 8·domains (default 1 x 8) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** Mei et al. (2005). Biopolymers, 80(6), 775-786. https://doi.org/10.1002/bip.20296 + +---- + +atchley_factors (AF) — Scales +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mean of the Atchley factors I-V over the sequence (optionally per domain). + +.. code-block:: text + + Calculate Atchley Factors (AF) of protein sequence. The Atchley factors are 5 + factors obtained from a factor analysis of 54 amino acid attributes. Factor + I mainly reflects polarity, accessibility and hydrophobicity, factor II + propensity for secondary structure, factor III molecular size, factor IV + relative amino acid composition and factor V electrostatic charge. + + Each amino acid in the sequence is replaced by its 5 Atchley factors and the + mean of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.atchley_factors(protein_seq) + # Shape: 1 x 5·domains (default 1 x 5) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** Atchley et al. (2005). PNAS, 102(18), 6395-6400. https://doi.org/10.1073/pnas.0408677102 + +---- + +t_scales (T) — Scales +~~~~~~~~~~~~~~~~~~~~~ + +Mean of the topological T-scales over the sequence (optionally per domain). + +.. code-block:: text + + Calculate Topological Scales (T-scales) of protein sequence. The T-scales are + principal components of topological descriptors of amino acids, describing their + structural and topological characteristics. + + Each amino acid in the sequence is replaced by its 5 T-scales and the mean of + each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.t_scales(protein_seq) + # Shape: 1 x 5·domains (default 1 x 5) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** Tian, Zhou & Li (2007). J. Mol. Struct., 830, 106-115. https://doi.org/10.1016/j.molstruc.2006.07.004 + +---- + +st_scales (ST) — Scales +~~~~~~~~~~~~~~~~~~~~~~~ + +Mean of the structural topology ST-scales over the sequence (optionally per domain). + +.. code-block:: text + + Calculate Structural Topology Scales (ST-scales) of protein sequence. The ST- + scales are principal components of 3D structural and topological properties of + amino acids. + + Each amino acid in the sequence is replaced by its 8 ST-scales and the mean of + each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.st_scales(protein_seq) + # Shape: 1 x 8·domains (default 1 x 8) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** Yang et al. (2010). Amino Acids, 38(3), 805-816. https://doi.org/10.1007/s00726-009-0287-y + +---- + +protfp (ProtFP) — Scales +~~~~~~~~~~~~~~~~~~~~~~~~ + +Mean of the ProtFP (PCA, 8 components) over the sequence (optionally per domain). + +.. code-block:: text + + Calculate ProtFP (ProtFP) of protein sequence. ProtFP is a set of 8 principal + components calculated from a selection of physicochemical properties in the + AAindex database. + + Each amino acid in the sequence is replaced by its 8 ProtFP components and the + mean of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.protfp(protein_seq) + # Shape: 1 x 8·domains (default 1 x 8) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** van Westen et al. (2013). J. Cheminform., 5, 41. https://doi.org/10.1186/1758-2946-5-41 + +---- + +fasgai (FASGAI) — Scales +~~~~~~~~~~~~~~~~~~~~~~~~ + +Mean of the FASGAI factor scales over the sequence (optionally per domain). + +.. code-block:: text + + Calculate Factor Analysis Scales of Generalized Amino Acid Information (FASGAI) + of protein sequence. The FASGAI scales are 6 factors obtained from a factor + analysis of amino acid properties, describing hydrophobicity, alpha and turn + propensities, bulky properties, compositional characteristics, local flexibility + and electronic properties. + + Each amino acid in the sequence is replaced by its 6 FASGAI factors and the mean + of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.fasgai(protein_seq) + # Shape: 1 x 6·domains (default 1 x 6) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** Liang & Li (2007). QSAR Comb. Sci., 26(6), 754-763. https://doi.org/10.1002/qsar.200630145 + +---- + +blosum_indices (BLOSUM) — Scales +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mean of the BLOSUM62-derived indices over the sequence (optionally per domain). + +.. code-block:: text + + Calculate BLOSUM Indices (BLOSUM) of protein sequence. The BLOSUM indices are 10 + factors derived from the BLOSUM62 substitution matrix by principal component + analysis and VARIMAX rotation, so they describe the evolutionary similarity of + amino acids. + + Each amino acid in the sequence is replaced by its 10 BLOSUM indices and the + mean of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.blosum_indices(protein_seq) + # Shape: 1 x 10·domains (default 1 x 10) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** Georgiev (2009). J. Comput. Biol., 16(5), 703-723. https://doi.org/10.1089/cmb.2008.0173 + +---- + +ms_whim (MSWHIM) — Scales +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mean of the MS-WHIM scores over the sequence (optionally per domain). + +.. code-block:: text + + Calculate Molecular Surface Weighted Holistic Invariant Molecular Scores (MS- + WHIM) of protein sequence. The MS-WHIM scores are 3 principal components of 3D + descriptors calculated from the molecular electrostatic potential on the surface + of each amino acid. + + Each amino acid in the sequence is replaced by its 3 MS-WHIM scores and the mean + of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + +.. code-block:: python + + result = protpy.ms_whim(protein_seq) + # Shape: 1 x 3·domains (default 1 x 3) + +* **Parameters:** ``domains``: int (default=1) — number of near-equal segments +* **Descriptor set:** extended, all +* **Reference:** Zaliani & Gancia (1999). J. Chem. Inf. Comput. Sci., 39(3), 525-533. https://doi.org/10.1021/ci980211b + +---- + +auto_cross_covariance (ACC) — Scales +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Auto/cross-covariance of an amino-acid scale: sum z_j(i) z_k(i+l) / (L-l). + +.. code-block:: text + + Calculate Auto Cross Covariance (ACC) of protein sequence. ACC transforms the + amino acid scale values of a sequence into a fixed-length vector that + describes the covariance between the scale dimensions of residues separated + by l positions, for each lag l in the range 1..lag. It is calculated as: + + ACC_jk(l) = sum_{i=1}^{L-l} z_j(s_i) * z_k(s_i+l) / (L - l) + + where ACC_jk(l) is the covariance between dimensions j and k at lag l, z_j(s_i) + is the value of dimension j for the amino acid at position i and L is the length + of the sequence. When j = k this is the auto covariance and when j != k the cross + covariance. The scale can be one of the factor-scale families, which are used as + published, or one or more AAindex accession numbers, whose values are first + standardised over the 20 amino acids. + +.. code-block:: python + + result = protpy.auto_cross_covariance(protein_seq) + # Shape: 1 x d²·lag (default 1 x 125) + +* **Parameters:** ``scale``: str | list[str] (default='z_scales_5') — factor scale or AAindex code(s); ``lag``: int (default=5) — maximum lag; ``mode``: str (default='acc') — 'acc', 'ac' or 'cc' +* **Descriptor set:** all +* **Reference:** Wold et al. (1993). Anal. Chim. Acta, 277(2), 239-253. https://doi.org/10.1016/0003-2670(93)80437-P + +---- + +physicochemical_distance_transformation (PDT) — Scales +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Physicochemical distance transformation: mean squared difference of standardised AAindex properties at lag l. + +.. code-block:: text + + Calculate Physicochemical Distance Transformation (PDT) of protein sequence. + PDT describes how much a physicochemical property changes between residues + separated by l positions, for each lag l in the range 1..lag. For each + AAindex property it is calculated as: + + PDT_j(l) = sum_{i=1}^{L-l} (P_j(s_i) - P_j(s_i+l))^2 / (L - l) + + where PDT_j(l) is the descriptor value for property j at lag l, P_j(s_i) is the + value of property j for the amino acid at position i, standardised over the 20 + amino acids, and L is the length of the sequence. By default the same 8 AAindex + properties as the autocorrelation descriptors are used. + +.. code-block:: python + + result = protpy.physicochemical_distance_transformation(protein_seq) + # Shape: 1 x len(properties)·lag (default 1 x 24) + +* **Parameters:** ``lag``: int (default=3) — maximum lag; ``properties``: list[str] | None — AAindex codes (default: 8 autocorrelation properties) +* **Descriptor set:** extended, all +* **Reference:** Liu et al. (2012). PLoS ONE, 7(9), e46633. https://doi.org/10.1371/journal.pone.0046633 + +---- + +charge_fractions (FCR) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Fraction of charged residues (FCR), positive/negative fractions and charge asymmetry sigma = NCPR²/FCR. + +.. code-block:: text + + Calculate the Charge Fractions (FCR) of protein sequence. The fraction of + charged residues (FCR), the fractions of positive and negative residues and + the overall charge asymmetry (sigma) describe the charge content of a protein + sequence, and are key parameters of the conformations of intrinsically + disordered proteins. They are calculated as: + + FCR = (n+ + n-) / L + + FPlus = n+ / L, FMinus = n- / L + + Sigma = NCPR^2 / FCR + + where n+ is the number of positive residues (K, R), n- the number of negative + residues (D, E), L is the length of the sequence and NCPR = (n+ - n-) / L. His + is treated as neutral and sigma is 0 when there are no charged residues. + +.. code-block:: python + + result = protpy.charge_fractions(protein_seq) + # Shape: 1 x 4 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Das & Pappu (2013). PNAS, 110(33), 13392-13397. https://doi.org/10.1073/pnas.1304749110 + +---- + +ncpr (NCPR) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Net charge per residue: (n+ - n-) / L with K/R positive and D/E negative. + +.. code-block:: text + + Calculate the Net Charge Per Residue (NCPR) of protein sequence. NCPR is the + net charge of a protein sequence divided by its length, calculated as: + + NCPR = (n+ - n-) / L + + where n+ is the number of positive residues (K, R), n- the number of negative + residues (D, E) and L is the length of the sequence. + +.. code-block:: python + + result = protpy.ncpr(protein_seq) + # Shape: 1 x 1 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Mao et al. (2010). PNAS, 107(18), 8183-8188. https://doi.org/10.1073/pnas.0911107107 + +---- + +kappa (Kappa) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Das-Pappu kappa: normalised blob charge-asymmetry deviation measuring segregation of opposite charges (0 mixed, 1 segregated). + +.. code-block:: text + + Calculate the Kappa (Kappa) charge patterning parameter of protein sequence. + Kappa describes how well oppositely charged residues are mixed or segregated + along a protein sequence, which strongly affects the conformations of + intrinsically disordered proteins. The sequence is split into overlapping + blobs of g residues and kappa is calculated as: + + sigma_b = (f+ - f-)^2 / (f+ + f-) + + delta_g = mean over all blobs of (sigma_b - sigma)^2 + + kappa_g = delta_g / delta_max,g + + where sigma_b is the charge asymmetry of a blob, f+ and f- are its fractions of + positive (K, R) and negative (D, E) residues, sigma = NCPR^2 / FCR is the charge + asymmetry of the whole sequence and delta_max,g is the largest delta over the + most segregated arrangements of the same composition. The final kappa is the + mean of kappa_g over the blob sizes (5 and 6 by default). Kappa ranges from 0 + (well mixed) to 1 (fully segregated). It is reported as 0 when the sequence has + no charged residues or only one type of charge, and blobs longer than the + sequence are reduced to its length. + +.. code-block:: python + + result = protpy.kappa(protein_seq) + # Shape: 1 x (1 + len(blob_sizes)) (default 1 x 3) + +* **Parameters:** ``blob_sizes``: tuple[int] (default=(5, 6)) — blob sizes averaged +* **Descriptor set:** extended, all +* **Reference:** Das & Pappu (2013). PNAS, 110(33), 13392-13397. https://doi.org/10.1073/pnas.1304749110 + +---- + +sequence_charge_decoration (SCD) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sequence charge decoration: (1/L) sum_{m>n} q_m q_n sqrt(m-n). + +.. code-block:: text + + Calculate the Sequence Charge Decoration (SCD) of protein sequence. SCD + describes the patterning of charges along a protein sequence, weighting each + pair of charges by the square root of their separation. It is calculated as: + + SCD = (1/L) * sum_{m=2}^{L} sum_{n=1}^{m-1} q_m * q_n * (m - n)^(1/2) + + where q_m and q_n are the charges of the residues at positions m and n (+1 for + K and R, -1 for D and E, 0 otherwise) and L is the length of the sequence. More + negative values indicate more segregated opposite charges and uncharged + sequences have an SCD of 0. + +.. code-block:: python + + result = protpy.sequence_charge_decoration(protein_seq) + # Shape: 1 x 1 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Sawle & Ghosh (2015). J. Chem. Phys., 143(8), 085101. https://doi.org/10.1063/1.4929391 + +---- + +charge_density (ChargeDensity) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Net charge at the given pH divided by molecular weight (charges per Da). + +.. code-block:: text + + Calculate the Charge Density of protein sequence. Charge density is the net + charge of a protein sequence at a given pH divided by its molecular weight, + calculated as: + + ChargeDensity = NetCharge(pH) / MW + + where NetCharge(pH) is calculated using the charge_distribution function and MW + using the molecular_weight function. + +.. code-block:: python + + result = protpy.charge_density(protein_seq) + # Shape: 1 x 1 + +* **Parameters:** ``ph``: float (default=7.4) — pH +* **Descriptor set:** extended, all +* **Reference:** Müller et al. (2017). Bioinformatics, 33(17), 2753-2755 (modlAMP). https://doi.org/10.1093/bioinformatics/btx285 + +---- + +hydrophobicity_profile (HP) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Summary statistics of a sliding-window hydrophobicity profile (ProtScale-style edge weighting). + +.. code-block:: text + + Calculate the Hydrophobicity Profile (HP) of protein sequence. Each amino acid + in the sequence is replaced by its value on a hydrophobicity scale and the + values are averaged over a sliding window, giving a profile along the sequence + . The profile is summarised by its mean, maximum, minimum and standard + deviation, and optionally the fraction of windows above a threshold, e.g. 1.6 + for transmembrane segments with the Kyte-Doolittle scale. The window value + is calculated as: + + HP(i) = sum_j w_j * H(s_i+j) / sum_j w_j + + where H is the hydrophobicity scale and w_j are the window weights. With + edge=1.0 all weights are 1 (a plain mean); with edge < 1 the weights fall + linearly from 1 at the centre of the window to edge at its ends, as in ExPASy + ProtScale. Windows longer than the sequence use the whole sequence. + +.. code-block:: python + + result = protpy.hydrophobicity_profile(protein_seq) + # Shape: 1 x 4 (1 x 5 with threshold) + +* **Parameters:** ``scale``: str (default='kyte_doolittle') — single-value scale or AAindex code; ``window``: int (default=9) — window length; ``edge``: float (default=1.0) — relative end weight; ``threshold``: float | None (default=None) — adds fraction of windows above threshold +* **Descriptor set:** extended, all +* **Reference:** Kyte & Doolittle (1982). J. Mol. Biol., 157(1), 105-132. https://doi.org/10.1016/0022-2836(82)90515-0 + +---- + +hydrophobic_ratio (HydrophobicRatio) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +APD hydrophobic ratio: percentage of L, I, V, F, M, A, C, W residues. + +.. code-block:: text + + Calculate the Hydrophobic Ratio of protein sequence. The hydrophobic ratio is + the percentage of hydrophobic residues in a protein sequence, as defined by + the Antimicrobial Peptide Database (APD), calculated as: + + HydrophobicRatio = N(LIVFMACW) / L * 100 + + where N(LIVFMACW) is the number of L, I, V, F, M, A, C and W residues and L + is the length of the sequence. + +.. code-block:: python + + result = protpy.hydrophobic_ratio(protein_seq) + # Shape: 1 x 1 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Wang (2020). Protein Sci., 29(1), 8-18 (APD); Wang, Li & Wang (2016). Nucleic Acids Res., 44, D1087 (APD3). https://doi.org/10.1002/pro.3702 + +---- + +half_life (HalfLife) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +N-end rule half-life (hours) in mammalian, yeast and E. coli systems from the N-terminal residue. + +.. code-block:: text + + Calculate the estimated Half Life of protein sequence. The half-life is a + prediction of the time it takes for half of the amount of a protein in a cell + to disappear after its synthesis. It is estimated from the N-terminal residue + using the N-end rule, for 3 model systems: mammalian reticulocytes + (in vitro), yeast and E. coli (in vivo), using the values tabulated by ExPASy + ProtParam. Values given as lower bounds in ProtParam (">20 hours" for yeast, + ">10 hours" for E. coli) are reported as the bound. The E. coli value for an + N-terminal proline is unknown in ProtParam and returned as NaN. The estimate is + not applicable to N-terminally modified proteins. + +.. code-block:: python + + result = protpy.half_life(protein_seq) + # Shape: 1 x 3 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Bachmair, Finley & Varshavsky (1986). Science, 234(4773), 179-186. https://doi.org/10.1126/science.3018930 + +---- + +ivywrel_index (IVYWREL) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Fraction of I, V, Y, W, R, E, L residues (thermophilic adaptation index). + +.. code-block:: text + + Calculate the IVYWREL Index of protein sequence. The IVYWREL index is the + fraction of I, V, Y, W, R, E and L residues in a protein sequence, which + correlates with the optimal growth temperature of the organism. It is + calculated as: + + IVYWREL = N(IVYWREL) / L + + where N(IVYWREL) is the number of I, V, Y, W, R, E and L residues and L is the + length of the sequence. + +.. code-block:: python + + result = protpy.ivywrel_index(protein_seq) + # Shape: 1 x 1 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Zeldovich, Berezovsky & Shakhnovich (2007). PLoS Comput. Biol., 3(1), e5. https://doi.org/10.1371/journal.pcbi.0030005 + +---- + +cvp_bias (CvP) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Charged versus polar bias: %(D+E+K+R) - %(N+Q+S+T). + +.. code-block:: text + + Calculate the Charged versus Polar Bias (CvP) of protein sequence. The CvP bias + is the difference between the percentages of charged and polar residues in a + protein sequence, an indicator of hyperthermostability, calculated as: + + CvP = %(D + E + K + R) - %(N + Q + S + T) + + where %(X) is the percentage of residues of type X in the sequence. + +.. code-block:: python + + result = protpy.cvp_bias(protein_seq) + # Shape: 1 x 1 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Suhre & Claverie (2003). J. Biol. Chem., 278(19), 17198-17202. https://doi.org/10.1074/jbc.M301327200 + +---- + +flexibility (Flex) — Physicochemical +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Summary statistics of the Vihinen average-B-value flexibility profile (window 9, end weight 0.25). + +.. code-block:: text + + Calculate the Flexibility (Flex) profile of protein sequence. Each amino acid + in the sequence is replaced by its normalised flexibility parameter (B-value) + and the values are averaged over a sliding window of 9 residues whose end + weights fall linearly to 0.25 (weights 0.25, 0.4375, 0.625, 0.8125, 1, ..., + 0.25), as in Vihinen et al.. The profile is summarised by its mean, + maximum, minimum and standard deviation. The average B-values of Vihinen et + al. are used; their parameter sets for residues with 0, 1 or 2 rigid + neighbours are not. Compared with Biopython's ProteinAnalysis.flexibility, + which uses the same values and weights, each window is centred on its middle + residue and the last window is included. + +.. code-block:: python + + result = protpy.flexibility(protein_seq) + # Shape: 1 x 4 + +* **Parameters:** ``window``: int (default=9) — window length +* **Descriptor set:** extended, all +* **Reference:** Vihinen, Torkkila & Riikonen (1994). Proteins, 19(2), 141-149. https://doi.org/10.1002/prot.340190207 + +---- + +wootton_federhen_complexity (WF) — Complexity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Wootton-Federhen local complexity (K1, K2) over sliding windows and the SEG-trigger low-complexity fraction. + +.. code-block:: text + + Calculate the Wootton-Federhen Complexity (WF) of protein sequence. The + Wootton-Federhen measures describe the local compositional complexity of a + protein sequence over a sliding window, and are the basis of the SEG program + for finding low-complexity regions. For each window of W residues + they are calculated as: + + K1 = (1/W) * log_20(W! / prod_i n_i!) + + K2 = -sum_i (n_i / W) * log2(n_i / W) + + where K1 is the complexity state, K2 is the Shannon entropy of the window in + bits and n_i is the number of residues of amino acid i in the window. The + low-complexity fraction is the fraction of residues covered by at least one + window with K2 <= k2_trigger. This is the trigger stage of SEG only; SEG's + extension and merging of low-complexity segments are not performed. A window + longer than the sequence uses the whole sequence. + +.. code-block:: python + + result = protpy.wootton_federhen_complexity(protein_seq) + # Shape: 1 x 4 + +* **Parameters:** ``window``: int (default=12) — window length; ``k2_trigger``: float (default=2.2) — K2 trigger (bits) +* **Descriptor set:** extended, all +* **Reference:** Wootton & Federhen (1993). Comput. Chem., 17(2), 149-163. https://doi.org/10.1016/0097-8485(93)85006-X + +---- + +linguistic_complexity (LC) — Complexity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Linguistic complexity: distinct subwords observed / maximum possible (ratio of sums over word lengths). + +.. code-block:: text + + Calculate the Linguistic Complexity (LC) of protein sequence. Linguistic + complexity describes how repetitive a sequence is, as the number of distinct + subwords (k-mers) it contains relative to the maximum possible number for a + sequence of its length. It is calculated as: + + LC = sum_{k=1}^{K} U_k / sum_{k=1}^{K} min(20^k, L - k + 1) + + where U_k is the number of distinct k-mers in the sequence, L is the length of + the sequence and K is the longest subword length (by default L, i.e. all + subwords). Repetitive sequences have a low LC and random sequences an LC + close to 1. + +.. code-block:: python + + result = protpy.linguistic_complexity(protein_seq) + # Shape: 1 x 1 + +* **Parameters:** ``max_k``: int | None (default=None) — longest word length (default L) +* **Descriptor set:** extended, all +* **Reference:** Troyanskaya et al. (2002). Bioinformatics, 18(5), 679-688. https://doi.org/10.1093/bioinformatics/18.5.679 + +---- + +lempel_ziv_complexity (LZ) — Complexity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Lempel-Ziv (LZ76) complexity c(n) and its normalised value c(n)·log20(n)/n. + +.. code-block:: text + + Calculate the Lempel-Ziv Complexity (LZ) of protein sequence. The Lempel-Ziv + (LZ76) complexity is the number of new patterns, c(n), found when a sequence is + read from left to right, each pattern being the shortest substring that cannot + be copied from the preceding part of the sequence. As c(n) grows with the + length of the sequence, a normalised value is also calculated as: + + LZ_Normalized = c(n) * log_20(n) / n + + where n is the length of the sequence and 20 is the size of the amino acid + alphabet. The normalised value is 0 for a single residue. + +.. code-block:: python + + result = protpy.lempel_ziv_complexity(protein_seq) + # Shape: 1 x 2 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Lempel & Ziv (1976). IEEE Trans. Inf. Theory, 22(1), 75-81. https://doi.org/10.1109/TIT.1976.1055501 + +---- + +fft_descriptors (FFT) — Signal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mean power of the FFT spectrum of a mean-centred property signal (default EIIP) in equal-width frequency bins. + +.. code-block:: text + + Calculate Fast Fourier Transform (FFT) descriptors of protein sequence. Each + amino acid in the sequence is replaced by its value on a property scale, by + default the electron-ion interaction potential (EIIP) used in the resonant + recognition model, and the power spectrum of the mean-centred signal is + calculated as: + + P(f) = abs(FFT(x)(f))^2 / L + + where x is the property signal, f is the frequency in cycles per residue + (0 to 0.5) and L is the length of the sequence. To give the same number of + features for any sequence length, the frequency range 0-0.5 is split into + n_bins equal-width bins and the mean power in each bin is returned; bins + containing no frequencies are 0. + +.. code-block:: python + + result = protpy.fft_descriptors(protein_seq) + # Shape: 1 x n_bins (default 1 x 10) + +* **Parameters:** ``scale``: str | dict (default='eiip') — single-value scale, AAindex code or dict; ``n_bins``: int (default=10) — number of frequency bins +* **Descriptor set:** extended, all +* **Reference:** Cosic (1994). IEEE Trans. Biomed. Eng., 41(12), 1101-1114. https://doi.org/10.1109/10.335859 + +---- + +dct_descriptors (DCT) — Signal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First n orthonormal DCT-II coefficients of a mean-centred property signal (default Kyte-Doolittle). + +.. code-block:: text + + Calculate Discrete Cosine Transform (DCT) descriptors of protein sequence. + Each amino acid in the sequence is replaced by its value on a property scale, + by default the Kyte-Doolittle hydropathy, and the first n_coeffs coefficients + of the orthonormal type-II DCT of the mean-centred signal are returned. The + DCT compresses a sequence-length signal into a fixed number of coefficients + , calculated as: + + X_k = s_k * sum_{n=0}^{L-1} x_n * cos(pi * k * (2n + 1) / (2L)) + + where X_k is coefficient k, x_n is the property value at position n, L is the + length of the sequence, and s_0 = sqrt(1/L) and s_k = sqrt(2/L) for k > 0. X_0 + is 0 because the signal is mean-centred. + +.. code-block:: python + + result = protpy.dct_descriptors(protein_seq) + # Shape: 1 x n_coeffs (default 1 x 10) + +* **Parameters:** ``scale``: str | dict (default='kyte_doolittle') — single-value scale, AAindex code or dict; ``n_coeffs``: int (default=10) — number of coefficients (<= L) +* **Descriptor set:** extended, all +* **Reference:** Wang et al. (2017). PLoS ONE, 12(9), e0185587. https://doi.org/10.1371/journal.pone.0185587 + +---- + +aggrescan (AGG) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +AGGRESCAN aggregation hot-spot descriptors from the a3v propensity profile (hot spots: >=5 residues above -0.02, no Pro). + +.. code-block:: text + + Calculate AGGRESCAN (AGG) aggregation descriptors of protein sequence. + AGGRESCAN predicts aggregation "hot spots", short stretches that promote + protein aggregation, from experimental aggregation propensities (a3v) of the + 20 amino acids. The a3v values are averaged over a sliding window, giving + the aggregation profile (a4v), with each window value assigned to its central + residue and the off-centre N- and C-terminal residues given the first and last + window values. A hot spot is a run of 5 or more residues with an a4v above the + hot-spot threshold (HST) of -0.02 and containing no proline. Areas are + calculated by trapezoidal integration of the profile, with HST as the zero axis. + + The 12 output values are: the mean a3v of the sequence (a3vSA); the number of + hot spots (nHS) and the number per 100 residues (NnHS); the area of the profile + above HST (AAT) and per residue (AATr); the total area of the hot spots (THSA) + and per residue (THSAr); the total area of the profile (TA); the sum of a4v + (a4vSS) and per 100 residues (Na4vSS); the mean a4v in the hot spots (a4vAHS, + 0 if there are none); and the largest single hot-spot area (HSA_max). + + AGGRESCAN chooses a window of 5, 7, 9 or 11 residues from the sequence length + and gives the two extreme residues specific values; neither rule is given in + the published supplementary material, so the window is a parameter and the + extreme residues are treated like the other off-centre residues. + +.. code-block:: python + + result = protpy.aggrescan(protein_seq) + # Shape: 1 x 12 + +* **Parameters:** ``window``: int (default=5) — odd window length (AGGRESCAN uses 5, 7, 9, 11) +* **Descriptor set:** extended, all +* **Reference:** Conchillo-Solé et al. (2007). BMC Bioinformatics, 8, 65. https://doi.org/10.1186/1471-2105-8-65 + +---- + +uversky_charge_hydropathy (UV) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Uversky mean hydropathy , mean net charge and boundary distance HQ = 2.785 - - 1.151. + +.. code-block:: text + + Calculate the Uversky Charge-Hydropathy (UV) descriptors of protein sequence. + Uversky et al. showed that natively unfolded proteins can be separated from + folded proteins by their mean net charge and mean hydropathy. These are + calculated as: + + = mean of (KD + 4.5) / 9 + + = abs(n+ - n-) / L + + HQ = 2.785 - - 1.151 + + where KD is the Kyte-Doolittle hydropathy of each residue, n+ and n- are the + numbers of positive (K, R) and negative (D, E) residues and L is the length + of the sequence. HQ is the distance from the boundary = 2.785 - 1.151 + (as restated in); the protein is predicted to be unfolded when HQ < 0. + +.. code-block:: python + + result = protpy.uversky_charge_hydropathy(protein_seq) + # Shape: 1 x 4 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Uversky, Gillespie & Fink (2000). Proteins, 41(3), 415-427. https://doi.org/10.1002/1097-0134(20001115)41:3<415::AID-PROT130>3.0.CO;2-7 + +---- + +fold_index (FI) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +FoldIndex profile summary: windowed 2.785 - abs() - 1.151 and disordered-residue statistics. + +.. code-block:: text + + Calculate the FoldIndex (FI) of protein sequence. FoldIndex applies the + Uversky charge-hydropathy boundary over a sliding window to find disordered + regions of a protein sequence. For each window it is calculated as: + + FI = 2.785 - abs() - 1.151 + + where is the mean Kyte-Doolittle hydropathy of the window rescaled to 0-1 + and is the mean net charge of the window (K, R positive; D, E negative). + Residues covered by at least one window with FI < 0 are counted as disordered. + A window longer than the sequence uses the whole sequence, so FI then equals + UV_HQ from uversky_charge_hydropathy. + +.. code-block:: python + + result = protpy.fold_index(protein_seq) + # Shape: 1 x 4 + +* **Parameters:** ``window``: int (default=51) — window length +* **Descriptor set:** extended, all +* **Reference:** Prilusky et al. (2005). Bioinformatics, 21(16), 3435-3438. https://doi.org/10.1093/bioinformatics/bti537 + +---- + +disorder_propensity (DP) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Fractions of disorder- and order-promoting residues and mean TOP-IDP value. + +.. code-block:: text + + Calculate the Disorder Propensity (DP) of protein sequence. Intrinsically + disordered proteins are enriched in disorder-promoting residues (A, R, G, Q, + S, P, E, K) and depleted in order-promoting residues (W, C, F, I, Y, V, L, N) + . The descriptor gives the fraction of each, together with the mean value of + the TOP-IDP scale, a scale of amino acid propensity for intrinsic disorder, + calculated as: + + DP_TOP_IDP_mean = sum_i TOP-IDP(s_i) / L + + where TOP-IDP(s_i) is the TOP-IDP value of the amino acid at position i and L + is the length of the sequence. Higher values indicate a higher propensity for + disorder. + +.. code-block:: python + + result = protpy.disorder_propensity(protein_seq) + # Shape: 1 x 3 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Campen et al. (2008). Protein Pept. Lett., 15(9), 956-963. https://doi.org/10.2174/092986608785849164 + +---- + +transmembrane_features (TM) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Kyte-Doolittle transmembrane-segment heuristics (segment count, max hydropathy, TM fraction, positive-inside bias). + +.. code-block:: text + + Calculate Transmembrane (TM) segment features of protein sequence. Putative + transmembrane segments are found from the Kyte-Doolittle hydropathy profile: + windows of 19 residues with a mean hydropathy above 1.6 indicate a likely + transmembrane segment, and overlapping windows are merged into segments. + The positive-inside rule states that the cytoplasmic side of a transmembrane + segment is enriched in positive residues. The 4 output values are: the + number of segments; the maximum window hydropathy; the fraction of residues + inside a segment; and the positive-inside bias, the number of K and R residues + in the 15 residues before the first segment minus the number in the 15 residues + after it (0 if there is no segment). This is a hydropathy heuristic and not a + topology predictor. + +.. code-block:: python + + result = protpy.transmembrane_features(protein_seq) + # Shape: 1 x 4 + +* **Parameters:** ``window``: int (default=19) — hydropathy window; ``kd_threshold``: float (default=1.6) — segment threshold +* **Descriptor set:** extended, all +* **Reference:** Kyte & Doolittle (1982). J. Mol. Biol., 157(1), 105-132. https://doi.org/10.1016/0022-2836(82)90515-0 + +---- + +signal_peptide_features (SP) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +N-terminal signal-peptide heuristics: n-region net charge and maximum 8-residue hydropathy. + +.. code-block:: text + + Calculate Signal Peptide (SP) features of protein sequence. Signal peptides + have a positively charged N-terminal n-region followed by a hydrophobic + h-region. The 2 output values are: the net charge of the first 5 residues + (K and R +1, D and E -1); and the maximum mean Kyte-Doolittle hydropathy over + 8-residue windows within the first n_term residues. The von Heijne + cleavage-site weight matrix is not included. + +.. code-block:: python + + result = protpy.signal_peptide_features(protein_seq) + # Shape: 1 x 2 + +* **Parameters:** ``n_term``: int (default=30) — N-terminal region length +* **Descriptor set:** extended, all +* **Reference:** von Heijne (1983). Eur. J. Biochem., 133(1), 17-21. https://doi.org/10.1111/j.1432-1033.1983.tb07424.x + +---- + +functional_site_counts (FS) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Counts and per-100-residue densities of PROSITE N-glycosylation, ER-retention and PTS1 motifs and tryptic sites. + +.. code-block:: text + + Calculate Functional Site (FS) counts of protein sequence. The number of each + functional sequence motif, and its density per 100 residues, is counted for: + N-glycosylation sites (PROSITE PS00001, N-{P}-[ST]-{P}), the C-terminal + endoplasmic reticulum retention signal (PS00014, [KRHQSA]-[DENQ]-E-L at the + C-terminus), the C-terminal peroxisomal targeting signal 1 (PS00342, + [STAGCN]-[RKH]-[LIVMAFY] at the C-terminus) and tryptic cleavage sites + (after K or R, not before P). Overlapping matches are counted. These motifs + complement the default motifs of motif_composition. + +.. code-block:: python + + result = protpy.functional_site_counts(protein_seq) + # Shape: 1 x 8 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Sigrist et al. (2013). Nucleic Acids Res., 41(D1), D344-D347 (PROSITE). https://doi.org/10.1093/nar/gks1067 + +---- + +plaac (PLAAC) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PLAAC prion-like domain scores: max window LLR, HMM core score, PrD length and presence. + +.. code-block:: text + + Calculate the Prion-Like Amino Acid Composition (PLAAC) descriptors of protein + sequence. PLAAC identifies prion-like domains, whose composition resembles + that of yeast prion domains, using the amino acid frequencies of prion domains + and of the S. cerevisiae proteome. Each residue has a log-likelihood ratio: + + LLR(s_i) = ln(f_prion(s_i) / f_background(s_i)) + + The 4 output values are: the maximum sum of LLR over any window of core_length + residues (PLAAC_LLR); the maximum sum of LLR over a window of core_length + residues lying entirely inside a prion-like region of the two-state hidden + Markov model Viterbi parse (PLAAC_COREscore); the length of the prion-like + region containing that core (PLAAC_PrD_length); and whether the sequence has a + prion-like region of at least core_length residues (PLAAC_has_PrD). As in + PLAAC, PLAAC_LLR and PLAAC_COREscore are NaN when they are undefined (a + sequence shorter than core_length, or no prion-like region of that length). + The parameters are ported from the PLAAC source code (MIT License), using the + S. cerevisiae background (alpha = 1). + +.. code-block:: python + + result = protpy.plaac(protein_seq) + # Shape: 1 x 4 + +* **Parameters:** ``core_length``: int (default=60) — minimal core length +* **Descriptor set:** extended, all +* **Reference:** Lancaster et al. (2014). Bioinformatics, 30(17), 2501-2502. https://doi.org/10.1093/bioinformatics/btu310 + +---- + +antigenicity_surface (Antigenicity) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Profile statistics of the Parker hydrophilicity and Janin surface scales over 7-residue windows. + +.. code-block:: text + + Calculate the Antigenicity and Surface (Antigenicity) profiles of protein + sequence. Antigenic sites tend to be hydrophilic and exposed on the protein + surface. Each amino acid in the sequence is replaced by its value on the + Parker hydrophilicity scale, derived from HPLC peptide retention times, + and on the Janin scale of free energy of transfer from the inside to the + outside of a globular protein. Each is averaged over a sliding window of 7 + residues, as used by Parker et al., and the profile is summarised by its mean, + maximum, minimum and standard deviation. + +.. code-block:: python + + result = protpy.antigenicity_surface(protein_seq) + # Shape: 1 x 8 + +* **Parameters:** ``window``: int (default=7) — window length +* **Descriptor set:** extended, all +* **Reference:** Parker, Guo & Hodges (1986). Biochemistry, 25(19), 5425-5432. https://doi.org/10.1021/bi00367a013 + +---- + +cysteine_features (Cys) — Structural +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Cysteine count, fraction, parity, maximum disulfides, CxxC motifs and mean spacing. + +.. code-block:: text + + Calculate Cysteine (Cys) features of protein sequence. Cysteines form + disulfide bonds and functional motifs such as the CxxC motif of thiol + oxidoreductases. The 6 output values are: the number of cysteines; their + fraction; whether the number is even; the maximum number of disulfide bonds + (the number of cysteines // 2); the number of overlapping C-x-x-C motifs; and + the mean gap between consecutive cysteines (0 if there are fewer than 2). + +.. code-block:: python + + result = protpy.cysteine_features(protein_seq) + # Shape: 1 x 6 + +* **Parameters:** sequence only +* **Descriptor set:** extended, all +* **Reference:** Fomenko & Gladyshev (2003). Biochemistry, 42(38), 11214-11225. https://doi.org/10.1021/bi034459s + +---- + Descriptor Summary ------------------ diff --git a/docs/index.rst b/docs/index.rst index 3901fac..14080c3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -68,6 +68,71 @@ Sequence Order Descriptors (5) * **Quasi Sequence Order (QSO)** * **Quasi Sequence Order — all matrices** +v1.5.0 Composition Descriptors (7) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* **cksaap** (CKSAAP) +* **cksaagp** (CKSAAGP) +* **dde** (DDE) +* **asdc** (ASDC) +* **distance_pair** (DP) +* **natural_vector** (NV) +* **atomic_composition** (ATC) + +v1.5.0 Scales Descriptors (11) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* **z_scales_5** (Z5) +* **vhse** (VHSE) +* **atchley_factors** (AF) +* **t_scales** (T) +* **st_scales** (ST) +* **protfp** (ProtFP) +* **fasgai** (FASGAI) +* **blosum_indices** (BLOSUM) +* **ms_whim** (MSWHIM) +* **auto_cross_covariance** (ACC) +* **physicochemical_distance_transformation** (PDT) + +v1.5.0 Physicochemical Descriptors (11) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* **charge_fractions** (FCR) +* **ncpr** (NCPR) +* **kappa** (Kappa) +* **sequence_charge_decoration** (SCD) +* **charge_density** (ChargeDensity) +* **hydrophobicity_profile** (HP) +* **hydrophobic_ratio** (HydrophobicRatio) +* **half_life** (HalfLife) +* **ivywrel_index** (IVYWREL) +* **cvp_bias** (CvP) +* **flexibility** (Flex) + +v1.5.0 Complexity Descriptors (3) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* **wootton_federhen_complexity** (WF) +* **linguistic_complexity** (LC) +* **lempel_ziv_complexity** (LZ) + +v1.5.0 Signal Descriptors (2) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* **fft_descriptors** (FFT) +* **dct_descriptors** (DCT) + +v1.5.0 Structural Descriptors (10) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* **aggrescan** (AGG) +* **uversky_charge_hydropathy** (UV) +* **fold_index** (FI) +* **disorder_propensity** (DP) +* **transmembrane_features** (TM) +* **signal_peptide_features** (SP) +* **functional_site_counts** (FS) +* **plaac** (PLAAC) +* **antigenicity_surface** (Antigenicity) +* **cysteine_features** (Cys) + +The v1.5.0 descriptors are added to ``all_descriptors`` with ``descriptor_set="extended"`` or ``"all"``; +see :doc:`usage` and :doc:`descriptors`. + .. note:: A demo of the software is available `on Colab `_ diff --git a/docs/references.bib b/docs/references.bib new file mode 100644 index 0000000..64499d8 --- /dev/null +++ b/docs/references.bib @@ -0,0 +1,2303 @@ +% ===== composition_encoding ===== +% References for composition / encoding / PSSM / embedding descriptors (protpy gap list) +% VERIFIED = DOI confirmed via exact-DOI web search resolving to the paper (Crossref/PubMed APIs blocked in-session). +% Volume/issue/pages partly from standard citation records; spot-check against Crossref when reachable. +% UNVERIFIED entries are commented out at the end. + +@article{chen2007cksaap, + author = {Chen, Ke and Kurgan, Lukasz A. and Ruan, Jishou}, + title = {Prediction of flexible/rigid regions from protein sequences using k-spaced amino acid pairs}, + journal = {BMC Structural Biology}, + year = {2007}, + volume = {7}, + pages = {25}, + doi = {10.1186/1472-6807-7-25} +} + +@article{chen2007crystallization, + author = {Chen, Ke and Kurgan, Lukasz and Rahbari, Mandana}, + title = {Prediction of protein crystallization using collocation of amino acid pairs}, + journal = {Biochemical and Biophysical Research Communications}, + year = {2007}, + volume = {355}, + number = {3}, + pages = {764--769}, + doi = {10.1016/j.bbrc.2007.02.040} +} + +@article{chen2008oglyc, + author = {Chen, Yong-Zi and Tang, Yu-Rong and Sheng, Zhi-Ya and Zhang, Ziding}, + title = {Prediction of mucin-type {O}-glycosylation sites in mammalian proteins using the composition of k-spaced amino acid pairs}, + journal = {BMC Bioinformatics}, + year = {2008}, + volume = {9}, + pages = {101}, + doi = {10.1186/1471-2105-9-101} +} + +@article{chen2018ifeature, + author = {Chen, Zhen and Zhao, Pei and Li, Fuyi and Leier, Andr{\'e} and Marquez-Lago, Tatiana T. and Wang, Yanan and Webb, Geoffrey I. and Smith, A. Ian and Daly, Roger J. and Chou, Kuo-Chen and Song, Jiangning}, + title = {{iFeature}: a {Python} package and web server for features extraction and selection from protein and peptide sequences}, + journal = {Bioinformatics}, + year = {2018}, + volume = {34}, + number = {14}, + pages = {2499--2502}, + doi = {10.1093/bioinformatics/bty140} +} + +@article{saravanan2015dde, + author = {Saravanan, Vijayakumar and Gautham, Namasivayam}, + title = {Harnessing computational biology for exact linear {B}-cell epitope prediction: a novel amino acid composition-based feature descriptor}, + journal = {OMICS: A Journal of Integrative Biology}, + year = {2015}, + volume = {19}, + number = {10}, + pages = {648--658}, + doi = {10.1089/omi.2015.0095} +} + +@article{wei2018acpredfl, + author = {Wei, Leyi and Zhou, Chen and Chen, Huangrong and Song, Jiangning and Su, Ran}, + title = {{ACPred-FL}: a sequence-based predictor using effective feature representation to improve the prediction of anti-cancer peptides}, + journal = {Bioinformatics}, + year = {2018}, + volume = {34}, + number = {23}, + pages = {4007--4016}, + doi = {10.1093/bioinformatics/bty451} +} + +@article{shen2007conjointtriad, + author = {Shen, Juwen and Zhang, Jian and Luo, Xiaomin and Zhu, Weiliang and Yu, Kunqian and Chen, Kaixian and Li, Yixue and Jiang, Hualiang}, + title = {Predicting protein--protein interactions based only on sequences information}, + journal = {Proceedings of the National Academy of Sciences USA}, + year = {2007}, + volume = {104}, + number = {11}, + pages = {4337--4341}, + doi = {10.1073/pnas.0607879104} +} + +@article{liu2014idnaprotdis, + author = {Liu, Bin and Xu, Jinghao and Lan, Xun and Xu, Ruifeng and Zhou, Jiyun and Wang, Xiaolong and Chou, Kuo-Chen}, + title = {{iDNA-Prot|dis}: identifying {DNA}-binding proteins by incorporating amino acid distance-pairs and reduced alphabet profile into the general pseudo amino acid composition}, + journal = {PLoS ONE}, + year = {2014}, + volume = {9}, + number = {9}, + pages = {e106691}, + doi = {10.1371/journal.pone.0106691} +} + +@article{cannata2002alphabets, + author = {Cannata, Nicola and Toppo, Stefano and Romualdi, Chiara and Valle, Giorgio}, + title = {Simplifying amino acid alphabets by means of a branch and bound algorithm and substitution matrices}, + journal = {Bioinformatics}, + year = {2002}, + volume = {18}, + number = {8}, + pages = {1102--1108}, + doi = {10.1093/bioinformatics/18.8.1102} +} + +@article{zuo2017psekraac, + author = {Zuo, Yongchun and Li, Yuan and Chen, Yingli and Li, Guangpeng and Yan, Zhenhe and Yang, Lei}, + title = {{PseKRAAC}: a flexible web server for generating pseudo {K}-tuple reduced amino acids composition}, + journal = {Bioinformatics}, + year = {2017}, + volume = {33}, + number = {1}, + pages = {122--124}, + doi = {10.1093/bioinformatics/btw564} +} + +@article{kumar2006mitpred, + author = {Kumar, Manish and Verma, Ruchi and Raghava, Gajendra P. S.}, + title = {Prediction of mitochondrial proteins using support vector machine and hidden {M}arkov model}, + journal = {Journal of Biological Chemistry}, + year = {2006}, + volume = {281}, + number = {9}, + pages = {5357--5363}, + doi = {10.1074/jbc.M511061200} +} + +@article{lata2007antibp, + author = {Lata, Sneh and Sharma, B. K. and Raghava, Gajendra P. S.}, + title = {Analysis and prediction of antibacterial peptides}, + journal = {BMC Bioinformatics}, + year = {2007}, + volume = {8}, + pages = {263}, + doi = {10.1186/1471-2105-8-263} +} + +@article{lata2010antibp2, + author = {Lata, Sneh and Mishra, Nitish K. and Raghava, Gajendra P. S.}, + title = {{AntiBP2}: improved version of antibacterial peptide prediction}, + journal = {BMC Bioinformatics}, + year = {2010}, + volume = {11}, + number = {Suppl 1}, + pages = {S19}, + doi = {10.1186/1471-2105-11-S1-S19} +} + +@article{dubchak1995ctd, + author = {Dubchak, Inna and Muchnik, Ilya and Holbrook, Stephen R. and Kim, Sung-Hou}, + title = {Prediction of protein folding class using global description of amino acid sequence}, + journal = {Proceedings of the National Academy of Sciences USA}, + year = {1995}, + volume = {92}, + number = {19}, + pages = {8700--8704}, + doi = {10.1073/pnas.92.19.8700} +} + +@article{dubchak1999scop, + author = {Dubchak, Inna and Muchnik, Ilya and Mayor, Christopher and Dralyuk, Igor and Kim, Sung-Hou}, + title = {Recognition of a protein fold in the context of the {SCOP} classification}, + journal = {Proteins: Structure, Function, and Genetics}, + year = {1999}, + volume = {35}, + number = {4}, + pages = {401--407}, + doi = {10.1002/(SICI)1097-0134(19990601)35:4<401::AID-PROT3>3.0.CO;2-K} +} + +@article{pande2023pfeature, + author = {Pande, Akshara and Patiyal, Sumeet and Lathwal, Anjali and Arora, Chakit and Kaur, Dilraj and Dhall, Anjali and Mishra, Gaurav and Kaur, Harpreet and Sharma, Neelam and Jain, Shipra and Usmani, Salman Sadullah and Agrawal, Piyush and Kumar, Rajesh and Kumar, Vinod and Raghava, Gajendra P. S.}, + title = {Pfeature: a tool for computing wide range of protein features and building prediction models}, + journal = {Journal of Computational Biology}, + year = {2023}, + volume = {30}, + number = {2}, + pages = {204--222}, + doi = {10.1089/cmb.2022.0241} +} + +@article{vanheel1991cmv, + author = {van Heel, Marin}, + title = {A new family of powerful multivariate statistical sequence analysis techniques}, + journal = {Journal of Molecular Biology}, + year = {1991}, + volume = {220}, + number = {4}, + pages = {877--887}, + doi = {10.1016/0022-2836(91)90360-I} +} + +@article{shamim2007svmfold, + author = {Shamim, Mohammad Tabrez Anwar and Anwaruddin, Mohammad and Nagarajaram, H. A.}, + title = {Support Vector Machine-based classification of protein folds using the structural properties of amino acid residues and amino acid residue pairs}, + journal = {Bioinformatics}, + year = {2007}, + volume = {23}, + number = {24}, + pages = {3320--3327}, + doi = {10.1093/bioinformatics/btm527} +} + +@article{muhammod2019pyfeat, + author = {Muhammod, Rafsanjani and Ahmed, Sajid and Md Farid, Dewan and Shatabda, Swakkhar and Sharma, Alok and Dehzangi, Abdollah}, + title = {{PyFeat}: a {Python}-based effective feature generation tool for {DNA}, {RNA} and protein sequences}, + journal = {Bioinformatics}, + year = {2019}, + volume = {35}, + number = {19}, + pages = {3831--3833}, + doi = {10.1093/bioinformatics/btz165} +} + +@article{chou2001pseaac, + author = {Chou, Kuo-Chen}, + title = {Prediction of protein cellular attributes using pseudo-amino acid composition}, + journal = {Proteins: Structure, Function, and Bioinformatics}, + year = {2001}, + volume = {43}, + number = {3}, + pages = {246--255}, + doi = {10.1002/prot.1035} +} + +@article{chou2005amphiphilic, + author = {Chou, Kuo-Chen}, + title = {Using amphiphilic pseudo amino acid composition to predict enzyme subfamily classes}, + journal = {Bioinformatics}, + year = {2005}, + volume = {21}, + number = {1}, + pages = {10--19}, + doi = {10.1093/bioinformatics/bth466} +} + +@article{chou2011generalform, + author = {Chou, Kuo-Chen}, + title = {Some remarks on protein attribute prediction and pseudo amino acid composition}, + journal = {Journal of Theoretical Biology}, + year = {2011}, + volume = {273}, + number = {1}, + pages = {236--247}, + doi = {10.1016/j.jtbi.2010.12.024} +} + +@article{shen2008pseaacserver, + author = {Shen, Hong-Bin and Chou, Kuo-Chen}, + title = {{PseAAC}: a flexible web server for generating various kinds of protein pseudo amino acid composition}, + journal = {Analytical Biochemistry}, + year = {2008}, + volume = {373}, + number = {2}, + pages = {386--388}, + doi = {10.1016/j.ab.2007.10.012} +} + +@article{yu2013naturalvector, + author = {Yu, Chenglong and Deng, Mo and Cheng, Shiu-Yuen and Yau, Shek-Chung and He, Rong L. and Yau, Stephen S.-T.}, + title = {Protein space: a natural method for realizing the nature of protein universe}, + journal = {Journal of Theoretical Biology}, + year = {2013}, + volume = {318}, + pages = {197--204}, + doi = {10.1016/j.jtbi.2012.11.005} +} + +@article{cosic1994rrm, + author = {Cosic, Irena}, + title = {Macromolecular bioactivity: is it resonant interaction between macromolecules? -- Theory and applications}, + journal = {IEEE Transactions on Biomedical Engineering}, + year = {1994}, + volume = {41}, + number = {12}, + pages = {1101--1114}, + doi = {10.1109/10.335859} +} + +@article{wang2017pssmdct, + author = {Wang, Yubo and Ding, Yijie and Guo, Fei and Wei, Leyi and Tang, Jijun}, + title = {Improved detection of {DNA}-binding proteins via compression technology on {PSSM} information}, + journal = {PLoS ONE}, + year = {2017}, + volume = {12}, + number = {9}, + pages = {e0185587}, + doi = {10.1371/journal.pone.0185587} +} + +@article{wen2005dwt, + author = {Wen, Zhi-Ning and Wang, Ke-Long and Li, Meng-Long and Nie, Fu-Sheng and Yang, Yi}, + title = {Analyzing functional similarity of protein sequences with discrete wavelet transform}, + journal = {Computational Biology and Chemistry}, + year = {2005}, + volume = {29}, + number = {3}, + pages = {220--228}, + doi = {10.1016/j.compbiolchem.2005.04.007} +} + +@article{shi2007multiscale, + author = {Shi, Jian-Yu and Zhang, Shao-Wu and Pan, Quan and Cheng, Yong-Mei and Xie, Jun}, + title = {Prediction of protein subcellular localization by support vector machines using multi-scale energy and pseudo amino acid composition}, + journal = {Amino Acids}, + year = {2007}, + volume = {33}, + number = {1}, + pages = {69--74}, + doi = {10.1007/s00726-006-0475-y} +} + +@article{jeffrey1990cgr, + author = {Jeffrey, H. Joel}, + title = {Chaos game representation of gene structure}, + journal = {Nucleic Acids Research}, + year = {1990}, + volume = {18}, + number = {8}, + pages = {2163--2170}, + doi = {10.1093/nar/18.8.2163} +} + +@article{fiser1994cgr, + author = {Fiser, Andr{\'a}s and Tusn{\'a}dy, G{\'a}bor E. and Simon, Istv{\'a}n}, + title = {Chaos game representation of protein structures}, + journal = {Journal of Molecular Graphics}, + year = {1994}, + volume = {12}, + number = {4}, + pages = {302--304}, + doi = {10.1016/0263-7855(94)80109-6} +} + +@article{basu1997cgr, + author = {Basu, Sumanta and Pan, Archana and Dutta, Chitra and Das, Jyotirmoy}, + title = {Chaos game representation of proteins}, + journal = {Journal of Molecular Graphics and Modelling}, + year = {1997}, + volume = {15}, + number = {5}, + pages = {279--289}, + doi = {10.1016/S1093-3263(97)00106-X} +} + +@article{xiao2008grey, + author = {Xiao, Xuan and Lin, Wei-Zhong and Chou, Kuo-Chen}, + title = {Using grey dynamic modeling and pseudo amino acid composition to predict protein structural classes}, + journal = {Journal of Computational Chemistry}, + year = {2008}, + volume = {29}, + number = {12}, + pages = {2018--2024}, + doi = {10.1002/jcc.20955} +} + +@article{lin2011idnaprot, + author = {Lin, Wei-Zhong and Fang, Jian-An and Xiao, Xuan and Chou, Kuo-Chen}, + title = {{iDNA-Prot}: identification of {DNA} binding proteins using random forest with grey model}, + journal = {PLoS ONE}, + year = {2011}, + volume = {6}, + number = {9}, + pages = {e24756}, + doi = {10.1371/journal.pone.0024756} +} + +@article{lempel1976complexity, + author = {Lempel, Abraham and Ziv, Jacob}, + title = {On the complexity of finite sequences}, + journal = {IEEE Transactions on Information Theory}, + year = {1976}, + volume = {22}, + number = {1}, + pages = {75--81}, + doi = {10.1109/TIT.1976.1055501} +} + +@article{diao2008lzprotein, + author = {Diao, Yuanbo and Ma, Deyuan and Wen, Zhining and Yin, Jiajun and Xiang, Jie and Li, Menglong}, + title = {Using pseudo amino acid composition to predict transmembrane regions in protein: cellular automata and {Lempel-Ziv} complexity}, + journal = {Amino Acids}, + year = {2008}, + volume = {34}, + number = {1}, + pages = {111--117}, + doi = {10.1007/s00726-007-0550-z} +} + +@article{henikoff1992blosum, + author = {Henikoff, Steven and Henikoff, Jorja G.}, + title = {Amino acid substitution matrices from protein blocks}, + journal = {Proceedings of the National Academy of Sciences USA}, + year = {1992}, + volume = {89}, + number = {22}, + pages = {10915--10919}, + doi = {10.1073/pnas.89.22.10915} +} + +@article{lin2002aesnn3, + author = {Lin, Kuang and May, Alex C. W. and Taylor, William R.}, + title = {Amino acid encoding schemes from protein structure alignments: multi-dimensional vectors to describe residue types}, + journal = {Journal of Theoretical Biology}, + year = {2002}, + volume = {216}, + number = {3}, + pages = {361--365}, + doi = {10.1006/jtbi.2001.2512} +} + +@article{chen2022ifeatureomega, + author = {Chen, Zhen and Liu, Xuhan and Zhao, Pei and Li, Chen and Wang, Yanan and Li, Fuyi and Akutsu, Tatsuya and Bain, Chris and Gasser, Robin B. and Li, Junzhou and Yang, Zuoren and Gao, Xin and Kurgan, Lukasz and Song, Jiangning}, + title = {{iFeatureOmega}: an integrative platform for engineering, visualization and analysis of features from molecular sequences, structural and ligand data sets}, + journal = {Nucleic Acids Research}, + year = {2022}, + volume = {50}, + number = {W1}, + pages = {W434--W447}, + doi = {10.1093/nar/gkac351} +} + +@article{altschul1997psiblast, + author = {Altschul, Stephen F. and Madden, Thomas L. and Sch{\"a}ffer, Alejandro A. and Zhang, Jinghui and Zhang, Zheng and Miller, Webb and Lipman, David J.}, + title = {Gapped {BLAST} and {PSI-BLAST}: a new generation of protein database search programs}, + journal = {Nucleic Acids Research}, + year = {1997}, + volume = {25}, + number = {17}, + pages = {3389--3402}, + doi = {10.1093/nar/25.17.3389} +} + +@article{wang2017possum, + author = {Wang, Jiawei and Yang, Bingjiao and Revote, Jerico and Leier, Andr{\'e} and Marquez-Lago, Tatiana T. and Webb, Geoffrey and Song, Jiangning and Chou, Kuo-Chen and Lithgow, Trevor}, + title = {{POSSUM}: a bioinformatics toolkit for generating numerical sequence feature descriptors based on {PSSM} profiles}, + journal = {Bioinformatics}, + year = {2017}, + volume = {33}, + number = {17}, + pages = {2756--2758}, + doi = {10.1093/bioinformatics/btx302} +} + +@article{sharma2013bigrampssm, + author = {Sharma, Alok and Lyons, James and Dehzangi, Abdollah and Paliwal, Kuldip K.}, + title = {A feature extraction technique using bi-gram probabilities of position specific scoring matrix for protein fold recognition}, + journal = {Journal of Theoretical Biology}, + year = {2013}, + volume = {320}, + pages = {41--46}, + doi = {10.1016/j.jtbi.2012.12.008} +} + +@article{liu2010aacpssm, + author = {Liu, Taigang and Zheng, Xiaoqi and Wang, Jun}, + title = {Prediction of protein structural class for low-similarity sequences using support vector machine and {PSI-BLAST} profile}, + journal = {Biochimie}, + year = {2010}, + volume = {92}, + number = {10}, + pages = {1330--1334}, + doi = {10.1016/j.biochi.2010.06.013} +} + +@article{zou2013t4se, + author = {Zou, Lingyun and Nan, Chonghan and Hu, Fuquan}, + title = {Accurate prediction of bacterial type {IV} secreted effectors using amino acid composition and {PSSM} profiles}, + journal = {Bioinformatics}, + year = {2013}, + volume = {29}, + number = {24}, + pages = {3135--3142}, + doi = {10.1093/bioinformatics/btt554} +} + +@article{dong2009acc, + author = {Dong, Qiwen and Zhou, Shuigeng and Guan, Jihong}, + title = {A new taxonomy-based protein fold recognition approach based on autocross-covariance transformation}, + journal = {Bioinformatics}, + year = {2009}, + volume = {25}, + number = {20}, + pages = {2655--2662}, + doi = {10.1093/bioinformatics/btp500} +} + +@article{lin2023esm2, + author = {Lin, Zeming and Akin, Halil and Rao, Roshan and Hie, Brian and Zhu, Zhongkai and Lu, Wenting and Smetanin, Nikita and Verkuil, Robert and Kabeli, Ori and Shmueli, Yaniv and dos Santos Costa, Allan and Fazel-Zarandi, Maryam and Sercu, Tom and Candido, Salvatore and Rives, Alexander}, + title = {Evolutionary-scale prediction of atomic-level protein structure with a language model}, + journal = {Science}, + year = {2023}, + volume = {379}, + number = {6637}, + pages = {1123--1130}, + doi = {10.1126/science.ade2574} +} + +@article{elnaggar2022prottrans, + author = {Elnaggar, Ahmed and Heinzinger, Michael and Dallago, Christian and Rehawi, Ghalia and Wang, Yu and Jones, Llion and Gibbs, Tom and Feher, Tamas and Angerer, Christoph and Steinegger, Martin and Bhowmik, Debsindhu and Rost, Burkhard}, + title = {{ProtTrans}: toward understanding the language of life through self-supervised learning}, + journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence}, + year = {2022}, + volume = {44}, + number = {10}, + pages = {7112--7127}, + doi = {10.1109/TPAMI.2021.3095381} +} + +@article{chen2020ilearn, + author = {Chen, Zhen and Zhao, Pei and Li, Fuyi and Marquez-Lago, Tatiana T. and Leier, Andr{\'e} and Revote, Jerico and Zhu, Yan and Powell, David R. and Akutsu, Tatsuya and Webb, Geoffrey I. and Chou, Kuo-Chen and Smith, A. Ian and Daly, Roger J. and Li, Jian and Song, Jiangning}, + title = {{iLearn}: an integrated platform and meta-learner for feature engineering, machine-learning analysis and modeling of {DNA}, {RNA} and protein sequence data}, + journal = {Briefings in Bioinformatics}, + year = {2020}, + volume = {21}, + number = {3}, + pages = {1047--1057}, + doi = {10.1093/bib/bbz041} +} + +@article{chen2021ilearnplus, + author = {Chen, Zhen and Zhao, Pei and Li, Chen and Li, Fuyi and Xiang, Dongxu and Chen, Yong-Zi and Akutsu, Tatsuya and Daly, Roger J. and Webb, Geoffrey I. and Zhao, Quanzhi and Kurgan, Lukasz and Song, Jiangning}, + title = {{iLearnPlus}: a comprehensive and automated machine-learning platform for nucleic acid and protein sequence analysis, prediction and visualization}, + journal = {Nucleic Acids Research}, + year = {2021}, + volume = {49}, + number = {10}, + pages = {e60}, + doi = {10.1093/nar/gkab122} +} + +@article{cao2013propy, + author = {Cao, Dong-Sheng and Xu, Qing-Song and Liang, Yi-Zeng}, + title = {propy: a tool to generate various modes of {Chou's} {PseAAC}}, + journal = {Bioinformatics}, + year = {2013}, + volume = {29}, + number = {7}, + pages = {960--962}, + doi = {10.1093/bioinformatics/btt072} +} + +@article{dong2018pybiomed, + author = {Dong, Jie and Yao, Zhi-Jiang and Zhang, Lin and Luo, Feijun and Lin, Qinlu and Lu, Ai-Ping and Chen, Alex F. and Cao, Dong-Sheng}, + title = {{PyBioMed}: a python library for various molecular representations of chemicals, proteins and {DNAs} and their interactions}, + journal = {Journal of Cheminformatics}, + year = {2018}, + volume = {10}, + pages = {16}, + doi = {10.1186/s13321-018-0270-2} +} + +@article{xiao2015protr, + author = {Xiao, Nan and Cao, Dong-Sheng and Zhu, Min-Feng and Xu, Qing-Song}, + title = {protr/{ProtrWeb}: {R} package and web server for generating various numerical representation schemes of protein sequences}, + journal = {Bioinformatics}, + year = {2015}, + volume = {31}, + number = {11}, + pages = {1857--1859}, + doi = {10.1093/bioinformatics/btv042} +} + +@article{sequeira2022propythia, + author = {Sequeira, Ana Marta and Lousa, Diana and Rocha, Miguel}, + title = {{ProPythia}: a {Python} package for protein classification based on machine and deep learning}, + journal = {Neurocomputing}, + year = {2022}, + volume = {484}, + pages = {172--182}, + doi = {10.1016/j.neucom.2021.07.102} +} + +@article{amerifar2022ftrcool, + author = {Amerifar, Sare and Norouzi, Mahmood and Ghandi, Mahmoud}, + title = {A tool for feature extraction from biological sequences}, + journal = {Briefings in Bioinformatics}, + year = {2022}, + volume = {23}, + number = {3}, + pages = {bbac108}, + doi = {10.1093/bib/bbac108} +} + +% ---------------- UNVERIFIED (DOI not confirmed) ---------------- + +% @article{chou2007memtype2l, +% author = {Chou, Kuo-Chen and Shen, Hong-Bin}, +% title = {{MemType-2L}: a web server for predicting membrane proteins and their types by incorporating evolution information through {Pse-PSSM}}, +% journal = {Biochemical and Biophysical Research Communications}, +% year = {2007}, +% volume = {360}, +% number = {2}, +% pages = {339--345}, +% note = {UNVERIFIED DOI; recalled as 10.1016/j.bbrc.2007.06.027. ScienceDirect PII S0006291X07012521} +% } + +% @inproceedings{juan2009dppssm, +% author = {Juan, Eric Y. T. and Li, W. J. and Jhang, J. H. and Chiu, C. H.}, +% title = {Predicting protein subcellular localizations for gram-negative bacteria using {DP-PSSM} and support vector machines}, +% booktitle = {2009 International Conference on Complex, Intelligent and Software Intensive Systems}, +% publisher = {IEEE}, +% year = {2009}, +% note = {UNVERIFIED: DOI/venue/pages not confirmed; IEEE Xplore document 5066887} +% } + +% @article{wei2017opf, +% author = {Wei, Leyi and Xing, Pengwei and Zeng, Jiancang and Chen, JinXiu and Su, Ran and Guo, Fei}, +% title = {Improved prediction of protein--protein interactions using novel negative samples, features, and an ensemble classifier}, +% journal = {Artificial Intelligence in Medicine}, +% year = {2017}, +% volume = {83}, +% pages = {67--74}, +% note = {UNVERIFIED DOI; ScienceDirect PII S0933365716305693} +% } + +% prodec: no peer-reviewed paper identified; cite software repository. +% ===== scales_physchem ===== +% References: amino-acid scales / physicochemical / charge patterning / stability / complexity descriptors (protpy gap list) +% VERIFIED = exact DOI fetched from Crossref, OpenAlex, a publisher landing page or PubMed, and metadata matched. +% UNVERIFIED entries (no DOI, or DOI not confirmed) are commented out at the end. + +@article{hellberg1987zscales, + author = {Hellberg, Sven and Sjöström, Michael and Skagerberg, Bert and Wold, Svante}, + title = {{Peptide quantitative structure-activity relationships, a multivariate approach}}, + journal = {Journal of Medicinal Chemistry}, + year = {1987}, + volume = {30}, + number = {7}, + pages = {1126--1135}, + doi = {10.1021/jm00390a003}, + note = {Crossref record; original 3 z-scales} +} + +@article{sandberg1998zscales, + author = {Sandberg, Maria and Eriksson, Lennart and Jonsson, Jörgen and Sjöström, Michael and Wold, Svante}, + title = {{New chemical descriptors relevant for the design of biologically active peptides. A multivariate characterization of 87 amino acids}}, + journal = {Journal of Medicinal Chemistry}, + year = {1998}, + volume = {41}, + number = {14}, + pages = {2481--2491}, + doi = {10.1021/jm9700575}, + note = {Crossref record; extended 5 z-scales, 87 amino acids} +} + +@article{mei2005vhse, + author = {Mei, H. and Liao, Z. H. and Zhou, Y. and Li, S. Z.}, + title = {{A new set of amino acid descriptors and its application in peptide QSARs}}, + journal = {Biopolymers}, + year = {2005}, + volume = {80}, + number = {6}, + pages = {775--786}, + doi = {10.1002/bip.20296}, + note = {Crossref record} +} + +@article{kidera1985factors, + author = {Kidera, Akinori and Konishi, Yasuo and Oka, Masahito and Ooi, Tatsuo and Scheraga, Harold A.}, + title = {{Statistical analysis of the physical properties of the 20 naturally occurring amino acids}}, + journal = {Journal of Protein Chemistry}, + year = {1985}, + volume = {4}, + number = {1}, + pages = {23--55}, + doi = {10.1007/BF01025492}, + note = {Springer publisher landing page} +} + +@article{atchley2005factors, + author = {Atchley, William R. and Zhao, Jieping and Fernandes, Andrew D. and Drüke, Tanja}, + title = {{Solving the protein sequence metric problem}}, + journal = {Proceedings of the National Academy of Sciences of the USA}, + year = {2005}, + volume = {102}, + number = {18}, + pages = {6395--6400}, + doi = {10.1073/pnas.0408677102}, + note = {Crossref record; peptides.py docs print a truncated DOI (…040867710), and the correct one is …0408677102} +} + +@article{tian2007tscales, + author = {Tian, Feifei and Zhou, Peng and Li, Zhiliang}, + title = {{T-scale as a novel vector of topological descriptors for amino acids and its application in QSARs of peptides}}, + journal = {Journal of Molecular Structure}, + year = {2007}, + volume = {830}, + number = {1--3}, + pages = {106--115}, + doi = {10.1016/j.molstruc.2006.07.004}, + note = {Crossref record} +} + +@article{yang2010stscales, + author = {Yang, L. and Shu, M. and Ma, K. and Mei, H. and Jiang, Y. and Li, Z.}, + title = {{ST-scale as a novel amino acid descriptor and its application in QSAM of peptides and analogues}}, + journal = {Amino Acids}, + year = {2010}, + volume = {38}, + number = {3}, + pages = {805--816}, + doi = {10.1007/s00726-009-0287-y}, + note = {Crossref record; online 2009, issue 2010} +} + +@article{vanwesten2013protfp1, + author = {van Westen, Gerard J. P. and Swier, Remco F. and Wegner, Jörg K. and IJzerman, Adriaan P. and van Vlijmen, Herman W. T. and Bender, Andreas}, + title = {{Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets}}, + journal = {Journal of Cheminformatics}, + year = {2013}, + volume = {5}, + pages = {41}, + doi = {10.1186/1758-2946-5-41}, + note = {Crossref record; introduces ProtFP (PCA of 3D-free AAindex properties)} +} + +@article{vanwesten2013protfp2, + author = {van Westen, Gerard J. P. and Swier, Remco F. and Cortes-Ciriano, Isidro and Wegner, Jörg K. and Overington, John P. and IJzerman, Adriaan P. and van Vlijmen, Herman W. T. and Bender, Andreas}, + title = {{Benchmarking of protein descriptor sets in proteochemometric modeling (part 2): modeling performance of 13 amino acid descriptor sets}}, + journal = {Journal of Cheminformatics}, + year = {2013}, + volume = {5}, + pages = {42}, + doi = {10.1186/1758-2946-5-42}, + note = {OpenAlex record; companion benchmark of all A-section scales} +} + +@article{liang2007fasgai, + author = {Liang, Guizhao and Li, Zhiliang}, + title = {{Factor analysis scale of generalized amino acid information as the source of a new set of descriptors for elucidating the structure and activity relationships of cationic antimicrobial peptides}}, + journal = {QSAR \& Combinatorial Science}, + year = {2007}, + volume = {26}, + number = {6}, + pages = {754--763}, + doi = {10.1002/qsar.200630145}, + note = {OpenAlex record; original FASGAI paper} +} + +@article{liang2008fasgai, + author = {Liang, Guizhao and Chen, G. and Niu, W. and Li, Zhiliang}, + title = {{Factor analysis scales of generalized amino acid information as applied in predicting interactions between the human amphiphysin-1 SH3 domains and their peptide ligands}}, + journal = {Chemical Biology \& Drug Design}, + year = {2008}, + volume = {71}, + number = {4}, + pages = {345--351}, + doi = {10.1111/j.1747-0285.2008.00641.x}, + note = {PubMed 18318694 record; this is the reference peptides.py cites} +} + +@article{georgiev2009blosum, + author = {Georgiev, Alexander G.}, + title = {{Interpretable numerical descriptors of amino acid space}}, + journal = {Journal of Computational Biology}, + year = {2009}, + volume = {16}, + number = {5}, + pages = {703--723}, + doi = {10.1089/cmb.2008.0173}, + note = {Crossref record} +} + +@article{zaliani1999mswhim, + author = {Zaliani, Andrea and Gancia, Emanuela}, + title = {{MS-WHIM scores for amino acids: a new 3D-description for peptide QSAR and QSPR studies}}, + journal = {Journal of Chemical Information and Computer Sciences}, + year = {1999}, + volume = {39}, + number = {3}, + pages = {525--533}, + doi = {10.1021/ci980211b}, + note = {Crossref record} +} + +@article{cruciani2004properties, + author = {Cruciani, Gabriele and Baroni, Massimo and Carosati, Emanuele and Clementi, Monica and Valigi, Roberta and Clementi, Sergio}, + title = {{Peptide studies by means of principal properties of amino acids derived from MIF descriptors}}, + journal = {Journal of Chemometrics}, + year = {2004}, + volume = {18}, + number = {3--4}, + pages = {146--155}, + doi = {10.1002/cem.856}, + note = {OpenAlex record} +} + +@article{sneath1966vectors, + author = {Sneath, Peter H. A.}, + title = {{Relations between chemical structure and biological activity in peptides}}, + journal = {Journal of Theoretical Biology}, + year = {1966}, + volume = {12}, + number = {2}, + pages = {157--195}, + doi = {10.1016/0022-5193(66)90112-3}, + note = {OpenAlex record; peptides.py docs misprint the year as 1996} +} + +@article{venkatarajan2001pcp, + author = {Venkatarajan, Mathura S. and Braun, Werner}, + title = {{New quantitative descriptors of amino acids based on multidimensional scaling of a large number of physical-chemical properties}}, + journal = {Journal of Molecular Modeling}, + year = {2001}, + volume = {7}, + number = {12}, + pages = {445--453}, + doi = {10.1007/s00894-001-0058-5}, + note = {Crossref record; Crossref lists the authors in reverse order, while the article byline is Venkatarajan \& Braun} +} + +@article{tong2017svger, + author = {Tong, J. and Li, L. and Bai, M. and Li, K.}, + title = {{A new descriptor of amino acids-SVGER and its applications in peptide QSAR}}, + journal = {Molecular Informatics}, + year = {2017}, + volume = {36}, + number = {5--6}, + pages = {1501023}, + doi = {10.1002/minf.201501023}, + note = {Crossref record; online 2016, issue 2017. only initials confirmed} +} + +@article{shu2013vstpv, + author = {Shu, Mao and Yu, Rui and Zhang, Yunru and Wang, Juan and Yang, Li and Wang, Li and Lin, Zhihua}, + title = {{Predicting the activity of antimicrobial peptides with amino acid topological information}}, + journal = {Medicinal Chemistry}, + year = {2013}, + volume = {9}, + number = {1}, + pages = {32--44}, + doi = {10.2174/157340613804488350}, + note = {OpenAlex record; online 2012, issue 2013. peptides.py lists 6 authors, and OpenAlex has 7 (adds Yang L)} +} + +@article{vicatos2005prin, + author = {Vicatos, Spyridon and Reddy, Boojala V. B. and Kaznessis, Yiannis}, + title = {{Prediction of distant residue contacts with the use of evolutionary information}}, + journal = {Proteins: Structure, Function, and Bioinformatics}, + year = {2005}, + volume = {58}, + number = {4}, + pages = {935--949}, + doi = {10.1002/prot.20370}, + note = {OpenAlex record; this is the source peptides.py cites for PRIN (3 principal components of amino-acid properties)} +} + +@article{wold1993acc, + author = {Wold, Svante and Jonsson, Jörgen and Sjöström, Michael and Sandberg, Maria and Rännar, Stefan}, + title = {{DNA and peptide sequences and chemical processes multivariately modelled by principal component analysis and partial least-squares projections to latent structures}}, + journal = {Analytica Chimica Acta}, + year = {1993}, + volume = {277}, + number = {2}, + pages = {239--253}, + doi = {10.1016/0003-2670(93)80437-P}, + note = {OpenAlex record; introduces ACC transform of z-scale sequences} +} + +@article{lapinsh2002acc, + author = {Lapinsh, Maris and Prusis, Peteris and Lundstedt, Torbjörn and Wikberg, Jarl E. S.}, + title = {{Proteochemometrics modeling of the interaction of amine G-protein coupled receptors with a diverse set of ligands}}, + journal = {Molecular Pharmacology}, + year = {2002}, + volume = {61}, + number = {6}, + pages = {1465--1475}, + doi = {10.1124/mol.61.6.1465}, + note = {OpenAlex record; ACC of z-scales in proteochemometrics, which is the usual implementation reference. Earlier PCM paper: Lapinsh et al. 2001 BBA 1525:180–190, https://doi.org/10.1016/S0304-4165(00)00187-2, also VERIFIED via OpenAlex} +} + +@article{feng2000nmba, + author = {Feng, Zhi-Ping and Zhang, Chun-Ting}, + title = {{Prediction of membrane protein types based on the hydrophobic index of amino acids}}, + journal = {Journal of Protein Chemistry}, + year = {2000}, + volume = {19}, + number = {4}, + pages = {269--275}, + doi = {10.1023/A:1007091128394}, + note = {OpenAlex record; the normalised form used in PROFEAT/protr} +} + +@article{horne1988autocorr, + author = {Horne, David S.}, + title = {{Prediction of protein helix content from an autocorrelation analysis of sequence hydrophobicities}}, + journal = {Biopolymers}, + year = {1988}, + volume = {27}, + number = {3}, + pages = {451--477}, + doi = {10.1002/bip.360270308}, + note = {OpenAlex record; early protein-sequence autocorrelation} +} + +@article{liu2012pdt, + author = {Liu, Bin and Wang, Xiaolong and Chen, Qingcai and Dong, Qiwen and Lan, Xun}, + title = {{Using amino acid physicochemical distance transformation for fast protein remote homology detection}}, + journal = {PLoS ONE}, + year = {2012}, + volume = {7}, + number = {9}, + pages = {e46633}, + doi = {10.1371/journal.pone.0046633}, + note = {OpenAlex record} +} + +@article{das2013kappa, + author = {Das, Rahul K. and Pappu, Rohit V.}, + title = {{Conformations of intrinsically disordered proteins are influenced by linear sequence distributions of oppositely charged residues}}, + journal = {Proceedings of the National Academy of Sciences of the USA}, + year = {2013}, + volume = {110}, + number = {33}, + pages = {13392--13397}, + doi = {10.1073/pnas.1304749110}, + note = {Crossref record; introduces κ and the FCR–NCPR diagram-of-states} +} + +@article{mao2010ncpr, + author = {Mao, Albert H. and Crick, Scott L. and Vitalis, Andreas and Chicoine, Caitlin L. and Pappu, Rohit V.}, + title = {{Net charge per residue modulates conformational ensembles of intrinsically disordered proteins}}, + journal = {Proceedings of the National Academy of Sciences of the USA}, + year = {2010}, + volume = {107}, + number = {18}, + pages = {8183--8188}, + doi = {10.1073/pnas.0911107107}, + note = {OpenAlex record; NCPR/FCR as order parameters} +} + +@article{martin2016omega, + author = {Martin, Erik W. and Holehouse, Alex S. and Grace, Christy R. and Hughes, Alex and Pappu, Rohit V. and Mittag, Tanja}, + title = {{Sequence determinants of the conformational properties of an intrinsically disordered protein prior to and upon multisite phosphorylation}}, + journal = {Journal of the American Chemical Society}, + year = {2016}, + volume = {138}, + number = {47}, + pages = {15323--15335}, + doi = {10.1021/jacs.6b10272}, + note = {OpenAlex record} +} + +@article{sawle2015scd, + author = {Sawle, Lucas and Ghosh, Kingshuk}, + title = {{A theoretical method to compute sequence dependent configurational properties in charged polymers and proteins}}, + journal = {Journal of Chemical Physics}, + year = {2015}, + volume = {143}, + number = {8}, + pages = {085101}, + doi = {10.1063/1.4929391}, + note = {OpenAlex record} +} + +@article{zheng2020shd, + author = {Zheng, Wenwei and Dignon, Gregory L. and Brown, Matthew and Kim, Young C. and Mittal, Jeetain}, + title = {{Hydropathy patterning complements charge patterning to describe conformational preferences of disordered proteins}}, + journal = {Journal of Physical Chemistry Letters}, + year = {2020}, + volume = {11}, + number = {9}, + pages = {3408--3415}, + doi = {10.1021/acs.jpclett.0c00288}, + note = {OpenAlex record} +} + +@article{holehouse2017cider, + author = {Holehouse, Alex S. and Das, Rahul K. and Ahad, James N. and Richardson, Mary O. G. and Pappu, Rohit V.}, + title = {{CIDER: resources to analyze sequence-ensemble relationships of intrinsically disordered proteins}}, + journal = {Biophysical Journal}, + year = {2017}, + volume = {112}, + number = {1}, + pages = {16--21}, + doi = {10.1016/j.bpj.2016.11.3200}, + note = {OpenAlex record} +} + +@article{rice2000emboss, + author = {Rice, Peter and Longden, Ian and Bleasby, Alan}, + title = {{EMBOSS: the European Molecular Biology Open Software Suite}}, + journal = {Trends in Genetics}, + year = {2000}, + volume = {16}, + number = {6}, + pages = {276--277}, + doi = {10.1016/S0168-9525(00)02024-2}, + note = {OpenAlex record; source of the EMBOSS iep pKa table} +} + +@article{bjellqvist1993pi, + author = {Bjellqvist, Bengt and Hughes, Graham J. and Pasquali, Christian and Paquet, Nicole and Ravier, Florence and Sanchez, Jean-Charles and Frutiger, Séverine and Hochstrasser, Denis}, + title = {{The focusing positions of polypeptides in immobilized pH gradients can be predicted from their amino acid sequences}}, + journal = {Electrophoresis}, + year = {1993}, + volume = {14}, + number = {10}, + pages = {1023--1031}, + doi = {10.1002/elps.11501401163}, + note = {OpenAlex record; OpenAlex lists issue 1, while peptides.py and the print issue give 10} +} + +@article{bjellqvist1994pi, + author = {Bjellqvist, Bengt and Basse, Bodil and Olsen, Eydfinnur and Celis, Julio E.}, + title = {{Reference points for comparisons of two-dimensional maps of proteins from different human cell types defined in a pH scale where isoelectric points correlate with polypeptide compositions}}, + journal = {Electrophoresis}, + year = {1994}, + volume = {15}, + pages = {529--539}, + doi = {10.1002/elps.1150150171}, + note = {Crossref record; second Bjellqvist pK set used by ProtParam. Crossref's issue field (1) looks unreliable, so it is left out} +} + +@article{kozlowski2016ipc, + author = {Kozlowski, Lukasz P.}, + title = {{IPC – Isoelectric Point Calculator}}, + journal = {Biology Direct}, + year = {2016}, + volume = {11}, + pages = {55}, + doi = {10.1186/s13062-016-0159-9}, + note = {OpenAlex record} +} + +@article{muller2017modlamp, + author = {Müller, Alex T. and Gabernet, Gisela and Hiss, Jan A. and Schneider, Gisbert}, + title = {{modlAMP: Python for antimicrobial peptides}}, + journal = {Bioinformatics}, + year = {2017}, + volume = {33}, + number = {17}, + pages = {2753--2755}, + doi = {10.1093/bioinformatics/btx285}, + note = {OpenAlex record; charge density = net charge / MW} +} + +@article{kyte1982hydropathy, + author = {Kyte, Jack and Doolittle, Russell F.}, + title = {{A simple method for displaying the hydropathic character of a protein}}, + journal = {Journal of Molecular Biology}, + year = {1982}, + volume = {157}, + number = {1}, + pages = {105--132}, + doi = {10.1016/0022-2836(82)90515-0}, + note = {OpenAlex record} +} + +@article{hopp1981hydrophilicity, + author = {Hopp, Thomas P. and Woods, Kenneth R.}, + title = {{Prediction of protein antigenic determinants from amino acid sequences}}, + journal = {Proceedings of the National Academy of Sciences of the USA}, + year = {1981}, + volume = {78}, + number = {6}, + pages = {3824--3828}, + doi = {10.1073/pnas.78.6.3824}, + note = {OpenAlex record} +} + +@article{eisenberg1982moment, + author = {Eisenberg, David and Weiss, Robert M. and Terwilliger, Thomas C.}, + title = {{The helical hydrophobic moment: a measure of the amphiphilicity of a helix}}, + journal = {Nature}, + year = {1982}, + volume = {299}, + number = {5881}, + pages = {371--374}, + doi = {10.1038/299371a0}, + note = {OpenAlex record; original hydrophobic moment} +} + +@article{eisenberg1984momentplot, + author = {Eisenberg, David and Schwarz, Erich M. and Komaromy, Michael C. and Wall, Randolph}, + title = {{Analysis of membrane and surface protein sequences with the hydrophobic moment plot}}, + journal = {Journal of Molecular Biology}, + year = {1984}, + volume = {179}, + number = {1}, + pages = {125--142}, + doi = {10.1016/0022-2836(84)90309-7}, + note = {OpenAlex record; normalised consensus scale + moment-plot membrane/surface/globular classification} +} + +@article{eisenberg1984periodicity, + author = {Eisenberg, David and Weiss, Robert M. and Terwilliger, Thomas C.}, + title = {{The hydrophobic moment detects periodicity in protein hydrophobicity}}, + journal = {Proceedings of the National Academy of Sciences of the USA}, + year = {1984}, + volume = {81}, + number = {1}, + pages = {140--144}, + doi = {10.1073/pnas.81.1.140}, + note = {OpenAlex record; moment as implemented in peptides.py/modlAMP} +} + +@article{eisenberg1984annrev, + author = {Eisenberg, David}, + title = {{Three-dimensional structure of membrane and surface proteins}}, + journal = {Annual Review of Biochemistry}, + year = {1984}, + volume = {53}, + pages = {595--623}, + doi = {10.1146/annurev.bi.53.070184.003115}, + note = {OpenAlex record; consensus scale review} +} + +@article{wimley1996interface, + author = {Wimley, William C. and White, Stephen H.}, + title = {{Experimentally determined hydrophobicity scale for proteins at membrane interfaces}}, + journal = {Nature Structural Biology}, + year = {1996}, + volume = {3}, + number = {10}, + pages = {842--848}, + doi = {10.1038/nsb1096-842}, + note = {OpenAlex record} +} + +@article{hessa2005translocon, + author = {Hessa, Tara and Kim, Hyun and Bihlmaier, Karl and Lundin, Carolina and Boekel, Jorrit and Andersson, Helena and Nilsson, IngMarie and White, Stephen H. and von Heijne, Gunnar}, + title = {{Recognition of transmembrane helices by the endoplasmic reticulum translocon}}, + journal = {Nature}, + year = {2005}, + volume = {433}, + number = {7024}, + pages = {377--381}, + doi = {10.1038/nature03216}, + note = {OpenAlex record} +} + +@article{hessa2007code, + author = {Hessa, Tara and Meindl-Beinker, Nadja M. and Bernsel, Andreas and Kim, Hyun and Sato, Yoko and Lerch-Bader, Mirjam and Nilsson, IngMarie and White, Stephen H. and von Heijne, Gunnar}, + title = {{Molecular code for transmembrane-helix recognition by the Sec61 translocon}}, + journal = {Nature}, + year = {2007}, + volume = {450}, + number = {7172}, + pages = {1026--1030}, + doi = {10.1038/nature06387}, + note = {OpenAlex record; position-dependent ΔG_app} +} + +@article{moon2011scale, + author = {Moon, C. Preston and Fleming, Karen G.}, + title = {{Side-chain hydrophobicity scale derived from transmembrane protein folding into lipid bilayers}}, + journal = {Proceedings of the National Academy of Sciences of the USA}, + year = {2011}, + volume = {108}, + number = {25}, + pages = {10174--10177}, + doi = {10.1073/pnas.1103979108}, + note = {Crossref record} +} + +@article{engelman1986ges, + author = {Engelman, Donald M. and Steitz, Thomas A. and Goldman, Adrian}, + title = {{Identifying nonpolar transbilayer helices in amino acid sequences of membrane proteins}}, + journal = {Annual Review of Biophysics and Biophysical Chemistry}, + year = {1986}, + volume = {15}, + pages = {321--353}, + doi = {10.1146/annurev.bb.15.060186.001541}, + note = {Annual Reviews publisher landing page} +} + +@article{juretic2009aasi, + author = {Juretić, Davor and Vukičević, Damir and Ilić, Nada and Antcheva, Nikolinka and Tossi, Alessandro}, + title = {{Computational design of highly selective antimicrobial peptides}}, + journal = {Journal of Chemical Information and Modeling}, + year = {2009}, + volume = {49}, + number = {12}, + pages = {2873--2882}, + doi = {10.1021/ci900327a}, + note = {OpenAlex record. The DOI is confirmed, but the attribution of the AASI scale to this paper follows modlAMP's documentation and was not checked against the article text} +} + +@article{suyama2003domcut, + author = {Suyama, Mikita and Ohara, Osamu}, + title = {{DomCut: prediction of inter-domain linker regions in amino acid sequences}}, + journal = {Bioinformatics}, + year = {2003}, + volume = {19}, + number = {5}, + pages = {673--674}, + doi = {10.1093/bioinformatics/btg031}, + note = {OpenAlex record} +} + +@article{wang2016apd3, + author = {Wang, Guangshun and Li, Xia and Wang, Zhe}, + title = {{APD3: the antimicrobial peptide database as a tool for research and education}}, + journal = {Nucleic Acids Research}, + year = {2016}, + volume = {44}, + number = {D1}, + pages = {D1087--D1093}, + doi = {10.1093/nar/gkv1278}, + note = {OpenAlex record; APD definition of hydrophobic ratio, implemented in modlAMP (see D5). Online 2015} +} + +@article{bachmair1986nend, + author = {Bachmair, Andreas and Finley, Daniel and Varshavsky, Alexander}, + title = {{In vivo half-life of a protein is a function of its amino-terminal residue}}, + journal = {Science}, + year = {1986}, + volume = {234}, + number = {4773}, + pages = {179--186}, + doi = {10.1126/science.3018930}, + note = {Crossref record} +} + +@article{gonda1989nend, + author = {Gonda, David K. and Bachmair, Andreas and Wünning, Ingrid and Tobias, John W. and Lane, William S. and Varshavsky, Alexander}, + title = {{Universality and structure of the N-end rule}}, + journal = {Journal of Biological Chemistry}, + year = {1989}, + volume = {264}, + number = {28}, + pages = {16700--16712}, + doi = {10.1016/S0021-9258(19)84762-2}, + note = {Crossref record; mammalian (reticulocyte) values} +} + +@article{tobias1991nend, + author = {Tobias, John W. and Shrader, Thomas E. and Rocap, Gabrielle and Varshavsky, Alexander}, + title = {{The N-end rule in bacteria}}, + journal = {Science}, + year = {1991}, + volume = {254}, + number = {5036}, + pages = {1374--1377}, + doi = {10.1126/science.1962196}, + note = {Crossref record; E. coli values} +} + +@article{varshavsky1996nend, + author = {Varshavsky, Alexander}, + title = {{The N-end rule: functions, mysteries, uses}}, + journal = {Proceedings of the National Academy of Sciences of the USA}, + year = {1996}, + volume = {93}, + number = {22}, + pages = {12142--12149}, + doi = {10.1073/pnas.93.22.12142}, + note = {OpenAlex record; review} +} + +@article{pace1995extinction, + author = {Pace, C. Nick and Vajdos, Felix and Fee, Lanette and Grimsley, Gerald and Gray, Theronica}, + title = {{How to measure and predict the molar absorption coefficient of a protein}}, + journal = {Protein Science}, + year = {1995}, + volume = {4}, + number = {11}, + pages = {2411--2423}, + doi = {10.1002/pro.5560041120}, + note = {Crossref record; ProtParam coefficients} +} + +@article{gill1989extinction, + author = {Gill, Stanley C. and von Hippel, Peter H.}, + title = {{Calculation of protein extinction coefficients from amino acid sequence data}}, + journal = {Analytical Biochemistry}, + year = {1989}, + volume = {182}, + number = {2}, + pages = {319--326}, + doi = {10.1016/0003-2697(89)90602-7}, + note = {OpenAlex record} +} + +@incollection{gasteiger2005expasy, + author = {Gasteiger, Elisabeth and Hoogland, Christine and Gattiker, Alexandre and Duvaud, Séverine and Wilkins, Marc R. and Appel, Ron D. and Bairoch, Amos}, + title = {Protein identification and analysis tools on the ExPASy server}, + editor = {Walker, John M.}, + booktitle = {The Proteomics Protocols Handbook}, + publisher = {Humana Press}, + year = {2005}, + pages = {571--607}, + doi = {10.1385/1-59259-890-0:571}, + note = {Springer publisher landing page; covers both ProtParam and ProtScale} +} + +@article{vihinen1994flex, + author = {Vihinen, Mauno and Torkkila, Esa and Riikonen, Pentti}, + title = {{Accuracy of protein flexibility predictions}}, + journal = {Proteins: Structure, Function, and Bioinformatics}, + year = {1994}, + volume = {19}, + number = {2}, + pages = {141--149}, + doi = {10.1002/prot.340190207}, + note = {Crossref record; Biopython flexibility() scale} +} + +@article{karplus1985flex, + author = {Karplus, P. Andrew and Schulz, Georg E.}, + title = {{Prediction of chain flexibility in proteins}}, + journal = {Naturwissenschaften}, + year = {1985}, + volume = {72}, + number = {4}, + pages = {212--213}, + doi = {10.1007/BF01195768}, + note = {Springer publisher landing page} +} + +@article{zeldovich2007ivywrel, + author = {Zeldovich, Konstantin B. and Berezovsky, Igor N. and Shakhnovich, Eugene I.}, + title = {{Protein and DNA sequence determinants of thermophilic adaptation}}, + journal = {PLoS Computational Biology}, + year = {2007}, + volume = {3}, + number = {1}, + pages = {e5}, + doi = {10.1371/journal.pcbi.0030005}, + note = {PLOS publisher landing page} +} + +@article{suhre2003cvp, + author = {Suhre, Karsten and Claverie, Jean-Michel}, + title = {{Genomic correlates of hyperthermostability, an update}}, + journal = {Journal of Biological Chemistry}, + year = {2003}, + volume = {278}, + number = {19}, + pages = {17198--17202}, + doi = {10.1074/jbc.M301327200}, + note = {OpenAlex record; CvP bias = (E+K) − (Q+H)} +} + +@article{akashi2002cost, + author = {Akashi, Hiroshi and Gojobori, Takashi}, + title = {{Metabolic efficiency and amino acid composition in the proteomes of Escherichia coli and Bacillus subtilis}}, + journal = {Proceedings of the National Academy of Sciences of the USA}, + year = {2002}, + volume = {99}, + number = {6}, + pages = {3695--3700}, + doi = {10.1073/pnas.062526999}, + note = {Crossref record} +} + +@article{craig1998cost, + author = {Craig, Catherine L. and Weber, Robert S.}, + title = {{Selection costs of amino acid substitutions in ColE1 and ColIa gene clusters harbored by Escherichia coli}}, + journal = {Molecular Biology and Evolution}, + year = {1998}, + volume = {15}, + number = {6}, + pages = {774--776}, + doi = {10.1093/oxfordjournals.molbev.a025981}, + note = {OpenAlex record} +} + +@article{wagner2005cost, + author = {Wagner, Andreas}, + title = {{Energy constraints on the evolution of gene expression}}, + journal = {Molecular Biology and Evolution}, + year = {2005}, + volume = {22}, + number = {6}, + pages = {1365--1374}, + doi = {10.1093/molbev/msi126}, + note = {Crossref record} +} + +@article{heizer2006cost, + author = {Heizer, Esley M. and Raiford, Douglas W. and Raymer, Michael L. and Doom, Travis E. and Miller, Robert V. and Krane, Dan E.}, + title = {{Amino acid cost and codon-usage biases in 6 prokaryotic genomes: a whole-genome analysis}}, + journal = {Molecular Biology and Evolution}, + year = {2006}, + volume = {23}, + number = {9}, + pages = {1670--1680}, + doi = {10.1093/molbev/msl029}, + note = {OpenAlex record} +} + +@article{wilkinson1991solubility, + author = {Wilkinson, David L. and Harrison, Roger G.}, + title = {{Predicting the solubility of recombinant proteins in Escherichia coli}}, + journal = {Bio/Technology}, + year = {1991}, + volume = {9}, + number = {5}, + pages = {443--448}, + doi = {10.1038/nbt0591-443}, + note = {Nature publisher landing page} +} + +@article{davis1999solubility, + author = {Davis, Gregory D. and Elisee, Claude and Newham, Denton M. and Harrison, Roger G.}, + title = {{New fusion protein systems designed to give soluble expression in Escherichia coli}}, + journal = {Biotechnology and Bioengineering}, + year = {1999}, + volume = {65}, + number = {4}, + pages = {382--388}, + doi = {10.1002/(SICI)1097-0290(19991120)65:4<382::AID-BIT2>3.0.CO;2-I}, + note = {OpenAlex record; revised two-parameter model} +} + +@article{nakashima1986class, + author = {Nakashima, Hiroshi and Nishikawa, Ken and Ooi, Tatsuo}, + title = {{The folding type of a protein is relevant to the amino acid composition}}, + journal = {Journal of Biochemistry}, + year = {1986}, + volume = {99}, + number = {1}, + pages = {153--162}, + doi = {10.1093/oxfordjournals.jbchem.a135454}, + note = {OpenAlex record} +} + +@article{chou1995class, + author = {Chou, Kuo-Chen}, + title = {{A novel approach to predicting protein structural classes in a (20–1)-D amino acid composition space}}, + journal = {Proteins: Structure, Function, and Bioinformatics}, + year = {1995}, + volume = {21}, + number = {4}, + pages = {319--344}, + doi = {10.1002/prot.340210406}, + note = {OpenAlex record} +} + +@article{wootton1993seg, + author = {Wootton, John C. and Federhen, Scott}, + title = {{Statistics of local complexity in amino acid sequences and sequence databases}}, + journal = {Computers \& Chemistry}, + year = {1993}, + volume = {17}, + number = {2}, + pages = {149--163}, + doi = {10.1016/0097-8485(93)85006-X}, + note = {Crossref record} +} + +@article{wootton1996seg, + author = {Wootton, John C. and Federhen, Scott}, + title = {{Analysis of compositionally biased regions in sequence databases}}, + journal = {Methods in Enzymology}, + year = {1996}, + volume = {266}, + pages = {554--571}, + doi = {10.1016/S0076-6879(96)66035-2}, + note = {OpenAlex record} +} + +@article{troyanskaya2002lc, + author = {Troyanskaya, Olga G. and Arbell, Ora and Koren, Yair and Landau, Gad M. and Bolshoy, Alexander}, + title = {{Sequence complexity profiles of prokaryotic genomic sequences: a fast algorithm for calculating linguistic complexity}}, + journal = {Bioinformatics}, + year = {2002}, + volume = {18}, + number = {5}, + pages = {679--688}, + doi = {10.1093/bioinformatics/18.5.679}, + note = {OUP publisher landing page} +} + +@article{tsallis1988entropy, + author = {Tsallis, Constantino}, + title = {{Possible generalization of Boltzmann-Gibbs statistics}}, + journal = {Journal of Statistical Physics}, + year = {1988}, + volume = {52}, + number = {1--2}, + pages = {479--487}, + doi = {10.1007/BF01016429}, + note = {Springer publisher landing page} +} + +@article{osorio2015peptides, + author = {Osorio, Daniel and Rondón-Villarreal, Paola and Torres, Rodrigo}, + title = {{Peptides: a package for data mining of antimicrobial peptides}}, + journal = {The R Journal}, + year = {2015}, + volume = {7}, + number = {1}, + pages = {4--14}, + doi = {10.32614/RJ-2015-001}, + note = {Crossref record; the R ancestor of peptides.py} +} + +@article{cock2009biopython, + author = {Cock, Peter J. A. and Antao, Tiago and Chang, Jeffrey T. and Chapman, Brad A. and Cox, Cymon J. and Dalke, Andrew and Friedberg, Iddo and Hamelryck, Thomas and Kauff, Frank and Wilczynski, Bartek and de Hoon, Michiel J. L.}, + title = {{Biopython: freely available Python tools for computational molecular biology and bioinformatics}}, + journal = {Bioinformatics}, + year = {2009}, + volume = {25}, + number = {11}, + pages = {1422--1423}, + doi = {10.1093/bioinformatics/btp163}, + note = {Crossref record} +} + +% ---------------- UNVERIFIED (no DOI or DOI not confirmed) ---------------- +% @article{sjostrom1995acc, +% author = {Sjöström, Michael and Rännar, Stefan and Wieslander, Åke}, +% title = {{Polypeptide sequence property relationships in Escherichia coli based on auto cross covariances}}, +% journal = {Chemometrics and Intelligent Laboratory Systems}, +% year = {1995}, +% volume = {29}, +% number = {2}, +% pages = {295--305}, +% note = {no DOI could be confirmed. The guessed DOI returned 404 and Crossref lookup was rate-limited. Volume and pages are from memory} +% } + +% @article{broto1984autocorr, +% author = {Broto, P. and Moreau, G. and Vandycke, C.}, +% title = {{Molecular structures: perception, autocorrelation descriptor and SAR studies}}, +% journal = {European Journal of Medicinal Chemistry}, +% year = {1984}, +% volume = {19}, +% pages = {66--70}, +% note = {original Moreau-Broto autocorrelation for small molecules. No DOI was found and the details are from memory} +% } + +% @book{nelson2005lehninger, +% author = {Nelson, David L. and Cox, Michael M.}, +% title = {Lehninger Principles of Biochemistry (4th ed.)}, +% publisher = {W. H. Freeman}, +% year = {2005}, +% note = {textbook with no DOI. peptides.py cites the 4th ed. (2005), so pin the edition whose table you use} +% } + +% @incollection{trifonov1990lc, +% author = {Trifonov, Edward N.}, +% title = {Making sense of the human genome}, +% editor = {Sarma, R. H. and Sarma, M. H.}, +% booktitle = {Structure \& Methods, Vol. 1: Human Genome Initiative \& DNA Recombination}, +% publisher = {Adenine Press}, +% year = {1990}, +% pages = {69--77}, +% note = {book chapter with no DOI, and the details are from memory} +% } + +% @inproceedings{renyi1961entropy, +% author = {Rényi, Alfréd}, +% title = {On measures of entropy and information}, +% booktitle = {Proceedings of the Fourth Berkeley Symposium on Mathematical Statistics and Probability}, +% year = {1961}, +% volume = {1}, +% pages = {547--561}, +% note = {proceedings with no DOI (hosted on Project Euclid). Details are from memory and were not fetched} +% } + +% @article{shannon1948entropy, +% author = {Shannon, Claude E.}, +% title = {{A mathematical theory of communication}}, +% journal = {Bell System Technical Journal}, +% year = {1948}, +% volume = {27}, +% number = {3}, +% pages = {379--423}, +% doi = {10.1002/j.1538-7305.1948.tb01338.x}, +% note = {DOI is taken from the peptides.py docs and was not checked independently because of rate limits} +% } + +% @incollection{dayhoff1978pam, +% author = {Dayhoff, Margaret O. and Schwartz, Robert M. and Orcutt, Bruce C.}, +% title = {A model of evolutionary change in proteins}, +% editor = {Dayhoff, Margaret O.}, +% booktitle = {Atlas of Protein Sequence and Structure, Vol. 5, Suppl. 3}, +% publisher = {National Biomedical Research Foundation}, +% year = {1978}, +% pages = {345--352}, +% note = {Atlas chapter with no DOI, and the details are from memory} +% } + +% @misc{larralde_peptidespy, +% author = {Larralde, Martin}, +% title = {peptides.py: physicochemical properties, indices and descriptors for amino-acid sequences (software)}, +% howpublished = {GitHub: althonos/peptides.py (GPL-3.0)}, +% year = {2021}, +% note = {no DOI, JOSS paper or CITATION file found in the repository. Cite the repo and version. The year is the first-release year and was not confirmed} +% } +% ===== structural ===== +% References for sequence-based structural/functional protein descriptors (protpy gap analysis) +% Every entry below is VERIFIED: its DOI was resolved against Crossref, OpenAlex or Europe PMC on 2026-10-11. +% No UNVERIFIED entries remain, so nothing is commented out. +% UTF-8 encoded; use biber/biblatex or add \usepackage[utf8]{inputenc}. + +% ---- 1. AGGRESCAN ---- +@article{ConchilloSole2007aggrescan, + author = {Conchillo-Sol{\'e}, Oscar and de Groot, Natalia S. and Avil{\'e}s, Francesc X. and Vendrell, Josep and Daura, Xavier and Ventura, Salvador}, + title = {{AGGRESCAN}: a server for the prediction and evaluation of "hot spots" of aggregation in polypeptides}, + journal = {BMC Bioinformatics}, + year = {2007}, + volume = {8}, + pages = {65}, + doi = {10.1186/1471-2105-8-65} +} + +% ---- 2. Uversky charge-hydropathy ---- +@article{Uversky2000natively, + author = {Uversky, Vladimir N. and Gillespie, Joel R. and Fink, Anthony L.}, + title = {Why are "natively unfolded" proteins unstructured under physiologic conditions?}, + journal = {Proteins: Structure, Function, and Bioinformatics}, + year = {2000}, + volume = {41}, + number = {3}, + pages = {415--427}, + doi = {10.1002/1097-0134(20001115)41:3<415::AID-PROT130>3.0.CO;2-7} +} + +% ---- 3. FoldIndex ---- +@article{Prilusky2005foldindex, + author = {Prilusky, Jaime and Felder, Clifford E. and Zeev-Ben-Mordehai, Tzviya and Rydberg, Edwin H. and Man, Orna and Beckmann, Jacques S. and Silman, Israel and Sussman, Joel L.}, + title = {{FoldIndex}{\copyright}: a simple tool to predict whether a given protein sequence is intrinsically unfolded}, + journal = {Bioinformatics}, + year = {2005}, + volume = {21}, + number = {16}, + pages = {3435--3438}, + doi = {10.1093/bioinformatics/bti537} +} + +% ---- 4. FoldUnfold ---- +@article{Galzitskaya2006foldunfold, + author = {Galzitskaya, Oxana V. and Garbuzynskiy, Sergiy O. and Lobanov, Michail Yu.}, + title = {{FoldUnfold}: web server for the prediction of disordered regions in protein chain}, + journal = {Bioinformatics}, + year = {2006}, + volume = {22}, + number = {23}, + pages = {2948--2949}, + doi = {10.1093/bioinformatics/btl504} +} + +% ---- 5. IUPred family ---- +@article{Dosztanyi2005pairwise, + author = {Doszt{\'a}nyi, Zsuzsanna and Csizm{\'o}k, Veronika and Tompa, P{\'e}ter and Simon, Istv{\'a}n}, + title = {The pairwise energy content estimated from amino acid composition discriminates between folded and intrinsically unstructured proteins}, + journal = {Journal of Molecular Biology}, + year = {2005}, + volume = {347}, + number = {4}, + pages = {827--839}, + doi = {10.1016/j.jmb.2005.01.071} +} + +@article{Dosztanyi2005iupred, + author = {Doszt{\'a}nyi, Zsuzsanna and Csizm{\'o}k, Veronika and Tompa, P{\'e}ter and Simon, Istv{\'a}n}, + title = {{IUPred}: web server for the prediction of intrinsically unstructured regions of proteins based on estimated energy content}, + journal = {Bioinformatics}, + year = {2005}, + volume = {21}, + number = {16}, + pages = {3433--3434}, + doi = {10.1093/bioinformatics/bti541} +} + +@article{Meszaros2018iupred2a, + author = {M{\'e}sz{\'a}ros, B{\'a}lint and Erd{\H{o}}s, G{\'a}bor and Doszt{\'a}nyi, Zsuzsanna}, + title = {{IUPred2A}: context-dependent prediction of protein disorder as a function of redox state and protein binding}, + journal = {Nucleic Acids Research}, + year = {2018}, + volume = {46}, + number = {W1}, + pages = {W329--W337}, + doi = {10.1093/nar/gky384} +} + +@article{Erdos2021iupred3, + author = {Erd{\H{o}}s, G{\'a}bor and Pajkos, M{\'a}ty{\'a}s and Doszt{\'a}nyi, Zsuzsanna}, + title = {{IUPred3}: prediction of protein disorder enhanced with unambiguous experimental annotation and visualization of evolutionary conservation}, + journal = {Nucleic Acids Research}, + year = {2021}, + volume = {49}, + number = {W1}, + pages = {W297--W303}, + doi = {10.1093/nar/gkab408} +} + +% ---- 6. Disorder-promoting residues / TOP-IDP ---- +@article{Dunker2001idp, + author = {Dunker, A. Keith and Lawson, J. David and Brown, Celeste J. and Williams, Ryan M. and Romero, Pedro and Oh, Jeong S. and Oldfield, Christopher J. and Campen, Andrew M. and Ratliff, Catherine M. and Hipps, Kerry W. and Ausio, Juan and Nissen, Mark S. and Reeves, Raymond and Kang, ChulHee and Kissinger, Charles R. and Bailey, Robert W. and Griswold, Michael D. and Chiu, Wah and Garner, Ethan C. and Obradovic, Zoran}, + title = {Intrinsically disordered protein}, + journal = {Journal of Molecular Graphics and Modelling}, + year = {2001}, + volume = {19}, + number = {1}, + pages = {26--59}, + doi = {10.1016/S1093-3263(00)00138-8} +} + +@article{Romero2001complexity, + author = {Romero, Pedro and Obradovic, Zoran and Li, Xiaohong and Garner, Ethan C. and Brown, Celeste J. and Dunker, A. Keith}, + title = {Sequence complexity of disordered protein}, + journal = {Proteins: Structure, Function, and Bioinformatics}, + year = {2001}, + volume = {42}, + number = {1}, + pages = {38--48}, + doi = {10.1002/1097-0134(20010101)42:1<38::AID-PROT50>3.0.CO;2-3} +} + +@article{Campen2008topidp, + author = {Campen, Andrew and Williams, Ryan M. and Brown, Celeste J. and Meng, Jingwei and Uversky, Vladimir N. and Dunker, A. Keith}, + title = {{TOP-IDP}-scale: a new amino acid scale measuring propensity for intrinsic disorder}, + journal = {Protein \& Peptide Letters}, + year = {2008}, + volume = {15}, + number = {9}, + pages = {956--963}, + doi = {10.2174/092986608785849164} +} + +% ---- 7. Review of simple disorder predictors ---- +@article{Szilagyi2008twilight, + author = {Szil{\'a}gyi, Andr{\'a}s and Gy{\"o}rffy, D{\'a}niel and Z{\'a}vodszky, P{\'e}ter}, + title = {The twilight zone between protein order and disorder}, + journal = {Biophysical Journal}, + year = {2008}, + volume = {95}, + number = {4}, + pages = {1612--1626}, + doi = {10.1529/biophysj.108.131151} +} + +% ---- 8. metapredict ---- +@article{Emenecker2021metapredict, + author = {Emenecker, Ryan J. and Griffith, Daniel and Holehouse, Alex S.}, + title = {Metapredict: a fast, accurate, and easy-to-use predictor of consensus disorder and structure}, + journal = {Biophysical Journal}, + year = {2021}, + volume = {120}, + number = {20}, + pages = {4312--4319}, + doi = {10.1016/j.bpj.2021.08.039} +} + +@misc{Emenecker2022metapredictV2, + author = {Emenecker, Ryan J. and Griffith, Daniel and Holehouse, Alex S.}, + title = {Metapredict {V2}: an update to metapredict, a fast, accurate, and easy-to-use predictor of consensus disorder and structure}, + howpublished = {bioRxiv preprint 2022.06.06.494887}, + year = {2022}, + doi = {10.1101/2022.06.06.494887} +} + +@misc{Lotthammer2024metapredictV3, + author = {Lotthammer, Jeffrey M. and Hern{\'a}ndez-Garc{\'i}a, Jorge and Griffith, Daniel P. and Weijers, Dolf and Holehouse, Alex S. and Emenecker, Ryan J.}, + title = {Metapredict enables accurate disorder prediction across the Tree of Life}, + howpublished = {bioRxiv preprint 2024.11.05.622168}, + year = {2024}, + doi = {10.1101/2024.11.05.622168}, + note = {metapredict V3 preprint; check for a peer-reviewed version before citing} +} + +% ---- 9. Transmembrane hydropathy / topology ---- +@article{vonHeijne1986positive, + author = {von Heijne, Gunnar}, + title = {The distribution of positively charged residues in bacterial inner membrane proteins correlates with the trans-membrane topology}, + journal = {The EMBO Journal}, + year = {1986}, + volume = {5}, + number = {11}, + pages = {3021--3027}, + doi = {10.1002/j.1460-2075.1986.tb04601.x} +} + +@article{vonHeijne1992membrane, + author = {von Heijne, Gunnar}, + title = {Membrane protein structure prediction: hydrophobicity analysis and the positive-inside rule}, + journal = {Journal of Molecular Biology}, + year = {1992}, + volume = {225}, + number = {2}, + pages = {487--494}, + doi = {10.1016/0022-2836(92)90934-C} +} + +@article{Krogh2001tmhmm, + author = {Krogh, Anders and Larsson, Bj{\"o}rn and von Heijne, Gunnar and Sonnhammer, Erik L. L.}, + title = {Predicting transmembrane protein topology with a hidden {Markov} model: application to complete genomes}, + journal = {Journal of Molecular Biology}, + year = {2001}, + volume = {305}, + number = {3}, + pages = {567--580}, + doi = {10.1006/jmbi.2000.4315} +} + +% ---- 10. Signal peptides ---- +@article{vonHeijne1983patterns, + author = {von Heijne, Gunnar}, + title = {Patterns of amino acids near signal-sequence cleavage sites}, + journal = {European Journal of Biochemistry}, + year = {1983}, + volume = {133}, + number = {1}, + pages = {17--21}, + doi = {10.1111/j.1432-1033.1983.tb07424.x} +} + +@article{vonHeijne1986cleavage, + author = {von Heijne, Gunnar}, + title = {A new method for predicting signal sequence cleavage sites}, + journal = {Nucleic Acids Research}, + year = {1986}, + volume = {14}, + number = {11}, + pages = {4683--4690}, + doi = {10.1093/nar/14.11.4683} +} + +@article{Teufel2022signalp6, + author = {Teufel, Felix and Almagro Armenteros, Jos{\'e} Juan and Johansen, Alexander Rosenberg and G{\'i}slason, Magn{\'u}s Halld{\'o}r and Pihl, Silas Irby and Tsirigos, Konstantinos D. and Winther, Ole and Brunak, S{\o}ren and von Heijne, Gunnar and Nielsen, Henrik}, + title = {{SignalP} 6.0 predicts all five types of signal peptides using protein language models}, + journal = {Nature Biotechnology}, + year = {2022}, + volume = {40}, + number = {7}, + pages = {1023--1025}, + doi = {10.1038/s41587-021-01156-3} +} + +% ---- 11. Motifs ---- +@article{Sigrist2013prosite, + author = {Sigrist, Christian J. A. and de Castro, Edouard and Cerutti, Lorenzo and Cuche, B{\'e}atrice A. and Hulo, Nicolas and Bridge, Alan and Bougueleret, Lydie and Xenarios, Ioannis}, + title = {New and continuing developments at {PROSITE}}, + journal = {Nucleic Acids Research}, + year = {2013}, + volume = {41}, + number = {D1}, + pages = {D344--D347}, + doi = {10.1093/nar/gks1067} +} + +@article{Marshall1972glycoproteins, + author = {Marshall, R. D.}, + title = {Glycoproteins}, + journal = {Annual Review of Biochemistry}, + year = {1972}, + volume = {41}, + pages = {673--702}, + doi = {10.1146/annurev.bi.41.070172.003325} +} + +@article{Gavel1990sequon, + author = {Gavel, Yvonne and von Heijne, Gunnar}, + title = {Sequence differences between glycosylated and non-glycosylated {Asn-X-Thr/Ser} acceptor sites: implications for protein engineering}, + journal = {Protein Engineering}, + year = {1990}, + volume = {3}, + number = {5}, + pages = {433--442}, + doi = {10.1093/protein/3.5.433} +} + +@article{Kalderon1984cell, + author = {Kalderon, Daniel and Roberts, Bruce L. and Richardson, William D. and Smith, Alan E.}, + title = {A short amino acid sequence able to specify nuclear location}, + journal = {Cell}, + year = {1984}, + volume = {39}, + number = {3}, + pages = {499--509}, + doi = {10.1016/0092-8674(84)90457-4} +} + +@article{Kalderon1984nature, + author = {Kalderon, Daniel and Richardson, William D. and Markham, Alexander F. and Smith, Alan E.}, + title = {Sequence requirements for nuclear location of simian virus 40 large-{T} antigen}, + journal = {Nature}, + year = {1984}, + volume = {311}, + number = {5981}, + pages = {33--38}, + doi = {10.1038/311033a0} +} + +@article{Kosugi2009cnls, + author = {Kosugi, Shunichi and Hasebe, Masako and Tomita, Masaru and Yanagawa, Hiroshi}, + title = {Systematic identification of cell cycle-dependent yeast nucleocytoplasmic shuttling proteins by prediction of composite motifs}, + journal = {Proceedings of the National Academy of Sciences}, + year = {2009}, + volume = {106}, + number = {25}, + pages = {10171--10176}, + doi = {10.1073/pnas.0900604106} +} + +@article{laCour2004nes, + author = {la Cour, Tanja and Kiemer, Lars and M{\o}lgaard, Anne and Gupta, Ramneek and Skriver, Karen and Brunak, S{\o}ren}, + title = {Analysis and prediction of leucine-rich nuclear export signals}, + journal = {Protein Engineering, Design and Selection}, + year = {2004}, + volume = {17}, + number = {6}, + pages = {527--536}, + doi = {10.1093/protein/gzh062} +} + +@article{Munro1987kdel, + author = {Munro, Sean and Pelham, Hugh R. B.}, + title = {A {C}-terminal signal prevents secretion of luminal {ER} proteins}, + journal = {Cell}, + year = {1987}, + volume = {48}, + number = {5}, + pages = {899--907}, + doi = {10.1016/0092-8674(87)90086-9} +} + +@article{Gould1989pts1, + author = {Gould, Stephen J. and Keller, Gilbert-Andr{\'e} and Hosken, Nancy and Wilkinson, Jack and Subramani, Suresh}, + title = {A conserved tripeptide sorts proteins to peroxisomes}, + journal = {Journal of Cell Biology}, + year = {1989}, + volume = {108}, + number = {5}, + pages = {1657--1664}, + doi = {10.1083/jcb.108.5.1657}, + pmid = {2654139} +} + +@article{Olsen2004trypsin, + author = {Olsen, Jesper V. and Ong, Shao-En and Mann, Matthias}, + title = {Trypsin cleaves exclusively {C}-terminal to arginine and lysine residues}, + journal = {Molecular \& Cellular Proteomics}, + year = {2004}, + volume = {3}, + number = {6}, + pages = {608--614}, + doi = {10.1074/mcp.T400003-MCP200} +} + +% ---- 12. Secondary-structure propensity ---- +@article{Chou1974parameters, + author = {Chou, Peter Y. and Fasman, Gerald D.}, + title = {Conformational parameters for amino acids in helical, $\beta$-sheet, and random coil regions calculated from proteins}, + journal = {Biochemistry}, + year = {1974}, + volume = {13}, + number = {2}, + pages = {211--222}, + doi = {10.1021/bi00699a001} +} + +@article{Chou1974prediction, + author = {Chou, Peter Y. and Fasman, Gerald D.}, + title = {Prediction of protein conformation}, + journal = {Biochemistry}, + year = {1974}, + volume = {13}, + number = {2}, + pages = {222--245}, + doi = {10.1021/bi00699a002} +} + +@article{Chou1978advenzymol, + author = {Chou, Peter Y. and Fasman, Gerald D.}, + title = {Prediction of the secondary structure of proteins from their amino acid sequence}, + journal = {Advances in Enzymology and Related Areas of Molecular Biology}, + year = {1978}, + volume = {47}, + pages = {45--148}, + doi = {10.1002/9780470122921.ch2} +} + +@article{Garnier1978gor, + author = {Garnier, Jean and Osguthorpe, David J. and Robson, Barry}, + title = {Analysis of the accuracy and implications of simple methods for predicting the secondary structure of globular proteins}, + journal = {Journal of Molecular Biology}, + year = {1978}, + volume = {120}, + number = {1}, + pages = {97--120}, + doi = {10.1016/0022-2836(78)90297-8} +} + +@article{Garnier1996gor4, + author = {Garnier, Jean and Gibrat, Jean-Fran{\c{c}}ois and Robson, Barry}, + title = {{GOR} method for predicting protein secondary structure from amino acid sequence}, + journal = {Methods in Enzymology}, + year = {1996}, + volume = {266}, + pages = {540--553}, + doi = {10.1016/S0076-6879(96)66034-0} +} + +@article{Levitt1978conformational, + author = {Levitt, Michael}, + title = {Conformational preferences of amino acids in globular proteins}, + journal = {Biochemistry}, + year = {1978}, + volume = {17}, + number = {20}, + pages = {4277--4285}, + doi = {10.1021/bi00613a026} +} + +@article{Deleage1987class, + author = {Del{\'e}age, Gilbert and Roux, Bernard}, + title = {An algorithm for protein secondary structure prediction based on class prediction}, + journal = {Protein Engineering}, + year = {1987}, + volume = {1}, + number = {4}, + pages = {289--294}, + doi = {10.1093/protein/1.4.289} +} + +% ---- 13. Coiled coils ---- +@article{Lupas1991coils, + author = {Lupas, Andrei and Van Dyke, Marc and Stock, Jeff}, + title = {Predicting coiled coils from protein sequences}, + journal = {Science}, + year = {1991}, + volume = {252}, + number = {5009}, + pages = {1162--1164}, + doi = {10.1126/science.252.5009.1162} +} + +% ---- 14. FoldAmyloid ---- +@article{Garbuzynskiy2010foldamyloid, + author = {Garbuzynskiy, Sergiy O. and Lobanov, Michail Yu. and Galzitskaya, Oxana V.}, + title = {{FoldAmyloid}: a method of prediction of amyloidogenic regions from protein sequence}, + journal = {Bioinformatics}, + year = {2010}, + volume = {26}, + number = {3}, + pages = {326--332}, + doi = {10.1093/bioinformatics/btp691} +} + +% ---- 15. PLAAC ---- +@article{Lancaster2014plaac, + author = {Lancaster, Alex K. and Nutter-Upham, Andrew and Lindquist, Susan and King, Oliver D.}, + title = {{PLAAC}: a web and command-line application to identify proteins with prion-like amino acid composition}, + journal = {Bioinformatics}, + year = {2014}, + volume = {30}, + number = {17}, + pages = {2501--2502}, + doi = {10.1093/bioinformatics/btu310} +} + +@article{Alberti2009prions, + author = {Alberti, Simon and Halfmann, Randal and King, Oliver and Kapila, Atul and Lindquist, Susan}, + title = {A systematic survey identifies prions and illuminates sequence features of prionogenic proteins}, + journal = {Cell}, + year = {2009}, + volume = {137}, + number = {1}, + pages = {146--158}, + doi = {10.1016/j.cell.2009.02.044} +} + +% ---- 16. Antigenicity / surface / flexibility scales ---- +@article{Emini1985surface, + author = {Emini, Emilio A. and Hughes, James V. and Perlow, Deborah S. and Boger, Joshua}, + title = {Induction of hepatitis {A} virus-neutralizing antibody by a virus-specific synthetic peptide}, + journal = {Journal of Virology}, + year = {1985}, + volume = {55}, + number = {3}, + pages = {836--839}, + doi = {10.1128/jvi.55.3.836-839.1985} +} + +@article{Parker1986hydrophilicity, + author = {Parker, J. M. Robert and Guo, Dean and Hodges, Robert S.}, + title = {New hydrophilicity scale derived from high-performance liquid chromatography peptide retention data: correlation of predicted surface residues with antigenicity and {X}-ray-derived accessible sites}, + journal = {Biochemistry}, + year = {1986}, + volume = {25}, + number = {19}, + pages = {5425--5432}, + doi = {10.1021/bi00367a013} +} + +@article{Kolaskar1990antigenic, + author = {Kolaskar, A. S. and Tongaonkar, Prasad C.}, + title = {A semi-empirical method for prediction of antigenic determinants on protein antigens}, + journal = {FEBS Letters}, + year = {1990}, + volume = {276}, + number = {1--2}, + pages = {172--174}, + doi = {10.1016/0014-5793(90)80535-Q} +} + +@article{Janin1979surface, + author = {Janin, Jo{\"e}l}, + title = {Surface and inside volumes in globular proteins}, + journal = {Nature}, + year = {1979}, + volume = {277}, + number = {5696}, + pages = {491--492}, + doi = {10.1038/277491a0} +} + + +% ---- 17. Phase separation ---- +@article{Martin2020valence, + author = {Martin, Erik W. and Holehouse, Alex S. and Peran, Ivan and Farag, Mina and Incicco, J. Jeremias and Bremer, Anne and Grace, Christy R. and Soranno, Andrea and Pappu, Rohit V. and Mittag, Tanja}, + title = {Valence and patterning of aromatic residues determine the phase behavior of prion-like domains}, + journal = {Science}, + year = {2020}, + volume = {367}, + number = {6478}, + pages = {694--699}, + doi = {10.1126/science.aaw8653} +} + +@article{Vernon2018pscore, + author = {Vernon, Robert McCoy and Chong, Paul Andrew and Tsang, Brian and Kim, Tae Hun and Bah, Alaji and Farber, Patrick and Lin, Hong and Forman-Kay, Julie Deborah}, + title = {Pi-Pi contacts are an overlooked protein feature relevant to phase separation}, + journal = {eLife}, + year = {2018}, + volume = {7}, + pages = {e31486}, + doi = {10.7554/eLife.31486} +} + +@article{Bolognesi2016catgranule, + author = {Bolognesi, Benedetta and Lorenzo Gotor, Nieves and Dhar, Riddhiman and Cirillo, Davide and Baldrighi, Marta and Tartaglia, Gian Gaetano and Lehner, Ben}, + title = {A concentration-dependent liquid phase separation can cause toxicity upon increased protein expression}, + journal = {Cell Reports}, + year = {2016}, + volume = {16}, + number = {1}, + pages = {222--231}, + doi = {10.1016/j.celrep.2016.05.076} +} + +@article{Hardenberg2020fuzdrop, + author = {Hardenberg, Maarten and Horvath, Attila and Ambrus, Viktor and Fuxreiter, Monika and Vendruscolo, Michele}, + title = {Widespread occurrence of the droplet state of proteins in the human proteome}, + journal = {Proceedings of the National Academy of Sciences}, + year = {2020}, + volume = {117}, + number = {52}, + pages = {33254--33262}, + doi = {10.1073/pnas.2007670117} +} + +% ---- 18. PPII propensity ---- +@article{Rucker2003ppii, + author = {Rucker, Adam L. and Pager, Cara T. and Campbell, Mary N. and Qualls, Joseph E. and Creamer, Trevor P.}, + title = {Host-guest scale of left-handed polyproline {II} helix formation}, + journal = {Proteins: Structure, Function, and Bioinformatics}, + year = {2003}, + volume = {53}, + number = {1}, + pages = {68--75}, + doi = {10.1002/prot.10477} +} + +@article{Shi2005ppii, + author = {Shi, Zhengshuang and Chen, Kang and Liu, Zhigang and Ng, Amanda and Bracken, W. Clay and Kallenbach, Neville R.}, + title = {Polyproline {II} propensities from {GGXGG} peptides reveal an anticorrelation with $\beta$-sheet scales}, + journal = {Proceedings of the National Academy of Sciences}, + year = {2005}, + volume = {102}, + number = {50}, + pages = {17964--17968}, + doi = {10.1073/pnas.0507124102} +} + +@article{Elam2013ppii, + author = {Elam, W. Austin and Schrank, Travis P. and Campagnolo, Andrew J. and Hilser, Vincent J.}, + title = {Evolutionary conservation of the polyproline {II} conformation surrounding intrinsically disordered phosphorylation sites}, + journal = {Protein Science}, + year = {2013}, + volume = {22}, + number = {4}, + pages = {405--417}, + doi = {10.1002/pro.2217} +} + +% ---- 19. CxxC motifs ---- +@article{Fomenko2003cxxc, + author = {Fomenko, Dmitri E. and Gladyshev, Vadim N.}, + title = {Identity and functions of {CxxC}-derived motifs}, + journal = {Biochemistry}, + year = {2003}, + volume = {42}, + number = {38}, + pages = {11214--11225}, + doi = {10.1021/bi034459s} +} + +% ---- 20. Excluded tools (background only) ---- +@article{FernandezEscamilla2004tango, + author = {Fernandez-Escamilla, Ana-Maria and Rousseau, Frederic and Schymkowitz, Joost and Serrano, Luis}, + title = {Prediction of sequence-dependent and mutational effects on the aggregation of peptides and proteins}, + journal = {Nature Biotechnology}, + year = {2004}, + volume = {22}, + number = {10}, + pages = {1302--1306}, + doi = {10.1038/nbt1012} +} + +@article{Sormanni2015camsol, + author = {Sormanni, Pietro and Aprile, Francesco A. and Vendruscolo, Michele}, + title = {The {CamSol} method of rational design of protein mutants with enhanced solubility}, + journal = {Journal of Molecular Biology}, + year = {2015}, + volume = {427}, + number = {2}, + pages = {478--490}, + doi = {10.1016/j.jmb.2014.09.026} +} + +@article{Munoz1994agadir, + author = {Mu{\~n}oz, Victor and Serrano, Luis}, + title = {Elucidating the folding problem of helical peptides using empirical parameters}, + journal = {Nature Structural Biology}, + year = {1994}, + volume = {1}, + number = {6}, + pages = {399--409}, + doi = {10.1038/nsb0694-399} +} + +@article{Kall2004phobius, + author = {K{\"a}ll, Lukas and Krogh, Anders and Sonnhammer, Erik L. L.}, + title = {A combined transmembrane topology and signal peptide prediction method}, + journal = {Journal of Molecular Biology}, + year = {2004}, + volume = {338}, + number = {5}, + pages = {1027--1036}, + doi = {10.1016/j.jmb.2004.03.016} +} + +@article{Tartaglia2008zyggregator, + author = {Tartaglia, Gian Gaetano and Pawar, Amol P. and Campioni, Silvia and Dobson, Christopher M. and Chiti, Fabrizio and Vendruscolo, Michele}, + title = {Prediction of aggregation-prone regions in structured proteins}, + journal = {Journal of Molecular Biology}, + year = {2008}, + volume = {380}, + number = {2}, + pages = {425--436}, + doi = {10.1016/j.jmb.2008.05.013} +} + +@article{MaurerStroh2010waltz, + author = {Maurer-Stroh, Sebastian and Debulpaep, Maja and Kuemmerer, Nico and Lopez de la Paz, Manuela and Martins, Ivo Cristiano and Reumers, Joke and Morris, Kyle L. and Copland, Alastair and Serpell, Louise and Serrano, Luis and Schymkowitz, Joost W. H. and Rousseau, Frederic}, + title = {Exploring the sequence determinants of amyloid structure using position-specific scoring matrices}, + journal = {Nature Methods}, + year = {2010}, + volume = {7}, + number = {3}, + pages = {237--242}, + doi = {10.1038/nmeth.1432} +} + +@article{Walsh2014pasta2, + author = {Walsh, Ian and Seno, Flavio and Tosatto, Silvio C. E. and Trovato, Antonio}, + title = {{PASTA} 2.0: an improved server for protein aggregation prediction}, + journal = {Nucleic Acids Research}, + year = {2014}, + volume = {42}, + number = {W1}, + pages = {W301--W307}, + doi = {10.1093/nar/gku399} +} + +@article{Hebditch2017proteinsol, + author = {Hebditch, Max and Carballo-Amador, M. Alejandro and Charonis, Spyros and Curtis, Robin and Warwicker, Jim}, + title = {Protein--Sol: a web tool for predicting protein solubility from sequence}, + journal = {Bioinformatics}, + year = {2017}, + volume = {33}, + number = {19}, + pages = {3098--3100}, + doi = {10.1093/bioinformatics/btx345} +} + + +% ===== added from Crossref (protpy registry) ===== +@article{Chou_2000, title={Prediction of Protein Subcellular Locations by Incorporating Quasi-Sequence-Order Effect}, volume={278}, ISSN={0006-291X}, url={http://dx.doi.org/10.1006/bbrc.2000.3815}, DOI={10.1006/bbrc.2000.3815}, number={2}, journal={Biochemical and Biophysical Research Communications}, publisher={Elsevier BV}, author={Chou, Kuo-Chen}, year={2000}, month=Nov, pages={477–483} } + +@article{Hollas_2003, title={An Analysis of the Autocorrelation Descriptor for Molecules}, volume={33}, ISSN={1572-8897}, url={http://dx.doi.org/10.1023/A:1023247831238}, DOI={10.1023/a:1023247831238}, number={2}, journal={Journal of Mathematical Chemistry}, publisher={Springer Science and Business Media LLC}, author={Hollas, Boris}, year={2003}, month=Feb, pages={91–101} } + +@article{Boman_2003, title={Antibacterial peptides: basic facts and emerging concepts}, volume={254}, ISSN={1365-2796}, url={http://dx.doi.org/10.1046/j.1365-2796.2003.01228.x}, DOI={10.1046/j.1365-2796.2003.01228.x}, number={3}, journal={Journal of Internal Medicine}, publisher={Wiley}, author={Boman, H. G.}, year={2003}, month=Aug, pages={197–215} } + +@article{Lobry_1994, title={Hydrophobicity, expressivity and aromaticity are the major trends of amino-acid usage in 999 Escherichia coli chromosome-encoded genes}, volume={22}, ISSN={1362-4962}, url={http://dx.doi.org/10.1093/nar/22.15.3174}, DOI={10.1093/nar/22.15.3174}, number={15}, journal={Nucleic Acids Research}, publisher={Oxford University Press (OUP)}, author={Lobry, J.R. and Gautier, C.}, year={1994}, pages={3174–3180} } + +@article{1980, ISSN={1756-2651}, url={http://dx.doi.org/10.1093/oxfordjournals.jbchem.a133168}, DOI={10.1093/oxfordjournals.jbchem.a133168}, journal={The Journal of Biochemistry}, publisher={Oxford University Press (OUP)}, year={1980}, month=Oct } + +@article{Guruprasad_1990, title={Correlation between stability of a protein and its dipeptide composition: a novel approach for predicting + in vivo + stability of a protein from its primary sequence}, volume={4}, ISSN={1741-0134}, url={http://dx.doi.org/10.1093/protein/4.2.155}, DOI={10.1093/protein/4.2.155}, number={2}, journal={“Protein Engineering, Design and Selection”}, publisher={Oxford University Press (OUP)}, author={Guruprasad, Kunchur and Reddy, B.V.Bhasker and Pandit, Madhusudan W.}, year={1990}, pages={155–161} } + +@article{Ong_2007, title={Efficacy of different protein descriptors in predicting protein functional families}, volume={8}, ISSN={1471-2105}, url={http://dx.doi.org/10.1186/1471-2105-8-300}, DOI={10.1186/1471-2105-8-300}, number={1}, journal={BMC Bioinformatics}, publisher={Springer Science and Business Media LLC}, author={Ong, Serene AK and Lin, Hong Huang and Chen, Yu Zong and Li, Ze Rong and Cao, Zhiwei}, year={2007}, month=Aug } + + +% ===== added from Crossref (protpy registry) ===== +@article{Wang_2019, title={The antimicrobial peptide database provides a platform for decoding the design principles of naturally occurring antimicrobial peptides}, volume={29}, ISSN={1469-896X}, url={http://dx.doi.org/10.1002/pro.3702}, DOI={10.1002/pro.3702}, number={1}, journal={Protein Science}, publisher={Wiley}, author={Wang, Guangshun}, year={2019}, month=Aug, pages={8–18} } diff --git a/docs/usage.rst b/docs/usage.rst index 2f1565f..bbf1c5b 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -470,7 +470,7 @@ The descriptors that accept custom parameters and their available keyword argume * - ``motif_composition`` - ``motifs`` (dict[str, str] | None, default None) * - ``aggregation_propensity`` - - ``window`` (int, default 5), ``hydrophobicity_threshold`` (float, default 2.0), ``charge_threshold`` (int, default 1) + - ``window`` (int, default 5), ``hydrophobicity_threshold`` (float, default 2.0), ``charge_threshold`` (int, default 0) * - ``hydrophobic_moment`` - ``window`` (int, default 11), ``angle`` (float, default 100) * - ``pseudo_amino_acid_composition`` @@ -492,6 +492,65 @@ The descriptors that accept custom parameters and their available keyword argume Passing an unrecognised descriptor name or a descriptor that takes no extra parameters will raise a ``ValueError``. Passing a non-dict value for ``params`` will raise a ``TypeError``. +Every v1.5.0 descriptor that takes parameters can also be configured through ``params`` +(see ``protpy.get_descriptor_info(name)["parameters"]``). + +**Descriptor sets** — ``descriptor_set`` chooses how much is computed. ``"classic"`` (the +default) is the v1.4 set above with an unchanged output; ``"extended"`` adds every v1.5.0 +descriptor whose default output has at most 150 columns (factor scales, charge patterning, +hydrophobicity/stability, complexity, signal and structural heuristics); ``"all"`` also adds +the high-dimensional ones (CKSAAP, DDE, ASDC, distance pair, auto/cross covariance). +``include`` and ``exclude`` add or remove individual descriptors by name: + +.. code-block:: python + + df = protpy.all_descriptors(protein_seq, descriptor_set="extended") + df = protpy.all_descriptors(protein_seq, descriptor_set="all") + df = protpy.all_descriptors(protein_seq, + include=["kappa", "plaac"], + exclude=["tripeptide_composition"], + params={"kappa": {"blob_sizes": (5,)}}) + + # the same arguments work for whole datasets + df = protpy.encode_dataset(sequences, descriptor_set="extended", output_file=None) + +A few extended descriptors are ``NaN`` where their method leaves them undefined: +``plaac`` LLR/COREscore for sequences shorter than the core length or without a +prion-like domain, and ``half_life`` (E. coli) for an N-terminal proline. + +Scaling Features for Machine Learning +------------------------------------- + +protpy returns each descriptor on its natural scale: percentages, counts, indices, pH +values, Da and so on. Many classifiers expect features on a common scale, for example +[0, 1]. Rather than rescaling inside protpy, fit a scaler on your **training** data and +reuse it for validation, test and new sequences. This scales every column (whatever +descriptors you computed) to the range actually seen in your data, and it is the +standard way to avoid leaking information from the test set: + +.. code-block:: python + + from sklearn.preprocessing import MinMaxScaler + + X_train = protpy.encode_dataset(train_seqs, output_file=None, verbose=False).set_index("sequence_id") + X_test = protpy.encode_dataset(test_seqs, output_file=None, verbose=False).set_index("sequence_id") + + scaler = MinMaxScaler(clip=True).fit(X_train) # fit on training data only + X_train_scaled = scaler.transform(X_train) # every column in [0, 1] + X_test_scaled = scaler.transform(X_test) # clip=True keeps unseen extremes in [0, 1] + +Notes: + +* ``StandardScaler`` (zero mean, unit variance) is a common alternative; tree-based models + (random forests, gradient boosting) do not need scaling at all. +* Wrap the scaler and model in a scikit-learn ``Pipeline`` when cross-validating, so the + scaler is re-fitted on each training fold. +* ``descriptor_set="extended"`` can produce a few ``NaN`` values (see above); impute them + first, e.g. with ``sklearn.impute.SimpleImputer``. +* Scaling with each descriptor's theoretical minimum and maximum is generally a poor + substitute: real proteins sit far from those extremes, so values end up compressed into + a narrow band (e.g. instability-index bounds are roughly [-159, 583] while typical values + are 20-60). .. note:: A demo of the software is available `here `_. \ No newline at end of file diff --git a/examples/protpy_tutorial.ipynb b/examples/protpy_tutorial.ipynb index fd0559a..cb151f6 100644 --- a/examples/protpy_tutorial.ipynb +++ b/examples/protpy_tutorial.ipynb @@ -1708,6 +1708,32 @@ "plt.show()\n" ] }, + { + "cell_type": "markdown", + "id": "261dbc43", + "metadata": {}, + "source": [ + "## New in v1.5.0 — extended descriptor set\n", + "\n", + "v1.5.0 adds 44 literature-referenced descriptors (factor scales, charge patterning, complexity, signal and structural heuristics). `descriptor_set=\"extended\"` adds every new descriptor with at most 150 columns to `all_descriptors`; `\"all\"` adds the high-dimensional ones too. The default (`\"classic\"`) output is unchanged." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5f33308", + "metadata": {}, + "outputs": [], + "source": [ + "classic = protpy.all_descriptors(SEQ_MEDIUM, _show_descriptor_list=False)\n", + "extended = protpy.all_descriptors(SEQ_MEDIUM, descriptor_set=\"extended\", _show_descriptor_list=False)\n", + "print(f\"classic: {classic.shape[1]} features, extended: {extended.shape[1]} features\")\n", + "\n", + "# a few of the new descriptors\n", + "new_cols = [\"Kappa\", \"Charge_SCD\", \"UV_HQ\", \"PLAAC_has_PrD\", \"LZ_Normalized\", \"AGG_nHS\", \"VHSE_1\"]\n", + "extended[new_cols]" + ] + }, { "cell_type": "markdown", "id": "a067958b", diff --git a/planning/BLOCKED.md b/planning/BLOCKED.md new file mode 100644 index 0000000..68dd1e8 --- /dev/null +++ b/planning/BLOCKED.md @@ -0,0 +1,45 @@ +# v1.5.0 blocked items + +Items from IMPLEMENTATION_PLAN.md that could not be implemented without inventing data. +Each entry names the table that is missing and where it would have to come from. + +| # | Item | Missing data | Notes | +|---|---|---|---| +| 6 | `psekraac` | The 16 PseKRAAC reduced-alphabet type tables (types 1–16, every cluster size) | The Zuo et al. 2017 supplement (`bioinformatics_33_1_122_s1.doc`, fetched 2026-10-12) only lists the 16 types and their source papers; it does not contain the cluster tables. They would have to be transcribed from the 16 source papers (Liang 2015, Ogul 2007, Kosiol 2004, Zuo 2010, Melo 2006, Rakshit 2008, Susko 2007, Liu 2002, Li 2007, Peterson 2009, Cannata 2002, Li 2003) or obtained from the PseKRAAC server authors. The iFeature `PseKRAAC/` copies have no licence. | +| 9 | `z_scales` (Hellberg z1–z3) | Hellberg et al. 1987 Table (J Med Chem, 10.1021/jm00390a003) | Paywalled; no permissively licensed reproduction found. van Westen 2013's "Z-scales (3)" is a subset of Sandberg's 5, not Hellberg's, so it can't be substituted. `z_scales_5` is shipped. | +| 12 | `kidera_factors` | Kidera et al. 1985 10-factor table (J Protein Chem, 10.1007/BF01025492) | Paywalled; the only copy found was peptides.py (GPL, cross-check only). | +| 20 | `cruciani_properties` | Cruciani et al. 2004 3-property table (J Chemometrics, 10.1002/cem.856) | Paywalled; the only copy found was peptides.py (GPL, cross-check only). | +| 26 | `omega` | The exact Ω definition: binary alphabet encoding, blob size(s) and δ_max construction | Martin et al. 2016 only says Ω is "analogous to κ" and puts the calculation in its Supporting Information. The SI isn't open access (Europe PMC has no supplementary bundle; ACS and PMC downloads are behind browser challenges), so the definition and a reproducible reference value couldn't be checked. | +| 31 | `membrane_position` | The Eisenberg 1984 hydrophobic-moment-plot boundary lines (globular / surface / transmembrane) | Eisenberg, Schwarz et al. 1984 J Mol Biol (10.1016/0022-2836(84)90309-7) is paywalled (403). | +| 35 | `wilkinson_harrison_solubility` | λ1, λ2 (and the sign of λ2) and CV′ of the revised model | Davis et al. 1999 (Wiley) and Wilkinson & Harrison 1991 are paywalled. The Harrison lab calculator (biotech.ou.edu) fails TLS and the Internet Archive was offline. | +| Phase 4 scales | `hessa`, `moon_fleming` and `aasi` single-value scales (used by `hydrophobicity_profile`, E6 `gravy(scale=)` and E7 `hydrophobic_moment(scale=)`) | Hessa 2005 ΔG_app (Nature, paywalled); Moon & Fleming 2011 (values are in SI Table S1, not available); Juretić 2009 AASI (paywalled). Available scales: kyte_doolittle, hopp_woods, eisenberg, wimley_white, engelman, domcut, plus any AAindex1 code. | +| 47 (partial) | `signal_peptide_features`: `SP_vonHeijne_max` and `SP_small_m3_m1` | The von Heijne 1986 eukaryotic cleavage-site weight matrix | The NAR 1986 paper (PMC311474) is only a scanned PDF behind a browser challenge. `SP_small_m3_m1` depends on the matrix-selected site. Shipped: `SP_nregion_charge`, `SP_hregion_maxKD`. | +| 48 (partial) | `functional_site_counts`: monopartite/bipartite NLS and NES patterns | Verified regex consensus patterns | Kosugi 2009 (PMC2695404) defines NLS classes as activity profiles, not regexes; la Cour 2004 is behind a Cloudflare check. Shipped: N-glycosylation, ER retention and PTS1 as the PROSITE patterns PS00001, PS00014 and PS00342 (which are broader than the plan's narrower variants), plus tryptic sites. | +| 49 | `chou_fasman` | The full Chou–Fasman 1978 nucleation/extension/overlap rules and turn threshold | Chou & Fasman 1974/1978 are paywalled. Secondary sources are incomplete and disagree on the turn cutoff (7.5e-3 vs 7.5e-5). The Pα/Pβ/Pt and bend-frequency tables are in AAindex (CHOP780201–203, 212–215), but the rules can't be verified. | +| 50 | `gor` | GOR I 3×17×20 directional information tables | Garnier, Osguthorpe & Robson 1978 J Mol Biol is paywalled. | +| 51 | `coiled_coil` | Lupas 1991 MTK profile matrix and Gaussian parameters | Science 1991 is paywalled; the only machine-readable copy (ncoils) is GPL-2.0+. | +| 52 | `fold_amyloid` | Garbuzynskiy 2010 expected-contacts scale and threshold | Bioinformatics 2010 is behind a Cloudflare check and the FoldAmyloid server didn't respond. Campen 2008 lists a "FoldUnfold" contact scale, but it can't be confirmed to be the FoldAmyloid scale. | +| 54 (partial) | `antigenicity_surface`: Emini, Karplus–Schulz, Kolaskar–Tongaonkar | Emini surface-probability values/formula; Karplus–Schulz rigid-neighbour definition; Kolaskar–Tongaonkar scale | Emini 1985 (PMC255070) is a scanned PDF behind a browser challenge; Karplus & Schulz 1985 and Kolaskar & Tongaonkar 1990 are paywalled, and none of the three is on ExPASy ProtScale. Shipped: Parker (window 7) and Janin profiles. | +| 46 (partial) | `transmembrane_features`: `TM_min_dGapp` | Hessa 2005 position-independent ΔG_app scale | Nature 2005 is paywalled. The other four columns are shipped. | + +## Shipped with caveats + +- **Factor scales (Phase 2).** `z_scales_5`, `vhse`, `t_scales`, `st_scales`, `fasgai`, `blosum_indices` and `ms_whim` were transcribed from van Westen et al. 2013 Additional file 2 (CC-BY), because the primary papers are paywalled. Seven cells differ from peptides.py and are flagged `UNRESOLVED` in `protpy/data/aa_scales.json`: + - FASGAI: V F1, P F6 + - ST: Q ST1, E ST2 + - VHSE: L VHSE3 + - z5: C Z3, L Z4 + + Check these against the primary tables before release. +- **`distance_pair`.** The plan specified G + G²·(rng+1) outputs over distances 0..rng. Liu 2014 (Eqs. 5–12) defines d = 1..rng, giving G + G²·rng (602 for cp14, d = 3, as the paper states), and that is what is implemented. The paper's printed cp(14) lists E twice; the stray E in the first cluster is dropped, the only 14-cluster reading. +- **`kappa`.** δ_max is found by enumerating the segregated arrangements (positive block, negative block, neutral residues split over the three gaps), as the plan specifies, because the Das & Pappu SI is behind a browser challenge. It reproduces the published κ of the patterned (Glu-Lys)25 variants sv1, sv2, sv7, sv8, sv19 and sv30 to within 0.002. The irregular variants could not be read reliably from the Fig. 1 image and are not tested. +- **`isoelectric_point(pka_scale="emboss")`.** Uses EMBOSS 6.6.0 `Epk.dat`, whose N-terminal pKa differs from the 8.6 that Kozlowski 2016 Table 1 attributes to EMBOSS. +- **`flexibility`.** Uses Vihinen's average parameter set (AAindex VINM940101, the same table Biopython uses) with the window-9, 0.25-edge weighting. The neighbour-dependent sets (VINM940102–104) need Vihinen's definition of a rigid neighbour, which is paywalled, so they are not used. +- **`hydrophobic_moment(scale=None)`.** Keeps protpy's existing table, so the default output is unchanged. That table is the Eisenberg, Schwarz, Komarony & Wall 1984 J Mol Biol normalised consensus scale (A = 0.62; confirmed against ExPASy ProtScale). `scale="eisenberg"` is AAindex EISD840101 (Eisenberg 1984 Annu Rev Biochem, A = 0.25), a different published consensus scale. +- **`half_life`.** The E. coli value for an N-terminal Pro is "?" in ProtParam and is returned as NaN. +- **`linguistic_complexity`.** The Troyanskaya 2002 full text is behind a Cloudflare check. The ratio-of-sums form in the plan is used: Σ distinct k-mers / Σ min(20^k, L−k+1). This is consistent with the abstract, which says the method counts "the number of subwords present" with suffix trees. Some sources use a product-of-ratios form instead. +- **`wootton_federhen_complexity`.** Implements the SEG trigger stage only (K2 ≤ trigger windows), not SEG's extension threshold or interval optimisation, as the plan specifies. +- **`aggrescan`.** The a3v values and the hot-spot rules (HST −0.02, ≥5 residues, no Pro, trapezoidal areas) come from the paper's Additional files 1–2. The rule that picks the window size from sequence length, and the charge-corrected values for the two terminal residues, aren't in the published material. So `window` is a parameter (default 5) and the terminal residues take the edge-window value. Aβ42 gives the paper's two hot spots. +- **`fold_index`.** The default window of 51 comes from the plan; Prilusky 2005 and the FoldIndex server couldn't be accessed to confirm it. The 2.785 and 1.151 constants are confirmed via Das & Pappu 2013 (PMC). +- **`plaac`.** Ported from the MIT-licensed PLAAC Java source. It reproduces PLAAC 1.1.0's MOT3 reference output exactly (LLR 40.751, COREscore 40.751, PRDlen 296). +- **`atomic_composition`.** By default it matches Pfeature (no water removed); `subtract_water=True` gives the true peptide formula. Pfeature's W and Y formulas each have one H too many, and protpy uses the correct formulas. diff --git a/protpy/__init__.py b/protpy/__init__.py index a91feca..74b7dd7 100644 --- a/protpy/__init__.py +++ b/protpy/__init__.py @@ -3,6 +3,11 @@ from .conjoint_triad import * from .ctd import * from .sequence_order import * +from .scales import * +from .physicochemical import * +from .complexity import * +from .signal import * +from .structural import * from .utils import get_descriptor_info, list_descriptors, print_descriptor_info from .encode import all_descriptors, encode_dataset, read_sequences_from_file @@ -12,7 +17,7 @@ #protPy package metadata __name__ = 'protpy' -__version__ = "1.4.2" +__version__ = "1.5.0" __description__ = "Python package for generating various biochemical, physicochemical and structural descriptors/features of protein sequences." __author__ = 'AJ McKenna: https://github.com/amckenna41' __authorEmail__ = 'amckenna41@qub.ac.uk' @@ -39,5 +44,15 @@ "conjoint_triad", "ctd_composition", "ctd_transition", "ctd_distribution", "ctd_", "sequence_order_coupling_number_", "sequence_order_coupling_number", "sequence_order_coupling_number_all", "quasi_sequence_order", "quasi_sequence_order_all", - "shannon_entropy" + "shannon_entropy", + #v1.5.0 + "cksaap", "cksaagp", "dde", "asdc", "distance_pair", "natural_vector", "atomic_composition", + "z_scales_5", "vhse", "atchley_factors", "t_scales", "st_scales", "protfp", "fasgai", + "blosum_indices", "ms_whim", "auto_cross_covariance", "physicochemical_distance_transformation", + "charge_fractions", "ncpr", "kappa", "sequence_charge_decoration", "charge_density", + "hydrophobicity_profile", "hydrophobic_ratio", "half_life", "ivywrel_index", "cvp_bias", "flexibility", + "wootton_federhen_complexity", "linguistic_complexity", "lempel_ziv_complexity", + "fft_descriptors", "dct_descriptors", + "aggrescan", "uversky_charge_hydropathy", "fold_index", "disorder_propensity", "transmembrane_features", + "signal_peptide_features", "functional_site_counts", "plaac", "antigenicity_surface", "cysteine_features", ] \ No newline at end of file diff --git a/protpy/_constants.py b/protpy/_constants.py index 9b8118a..0f02cb3 100644 --- a/protpy/_constants.py +++ b/protpy/_constants.py @@ -43,3 +43,37 @@ def _validate_sequence(sequence: str) -> str: f"Invalid amino acid found in protein sequence: {aa}." ) return sequence + + +def _validate_int(name: str, value, minimum: int = 0) -> int: + """ + Parse ``value`` as an int and check it is >= ``minimum``. + + Raises + ====== + ValueError + if ``value`` cannot be parsed as an int or is below ``minimum``. + """ + if isinstance(value, bool): + raise ValueError(f"Invalid {name} value input: {value}.") + try: + value = int(value) + except (ValueError, TypeError): + raise ValueError(f"Invalid {name} value input: {value}.") + if value < minimum: + raise ValueError(f"{name} must be >= {minimum}, got {value}.") + return value + + +def _validate_weight(weight) -> float: + """ + Check that ``weight`` is a non-negative number. + + Raises + ====== + ValueError + if ``weight`` is not a number or is negative. + """ + if isinstance(weight, bool) or not isinstance(weight, (int, float)) or weight < 0: + raise ValueError(f"weight must be a non-negative number, got {weight}.") + return weight diff --git a/protpy/_helpers.py b/protpy/_helpers.py new file mode 100644 index 0000000..7a0a977 --- /dev/null +++ b/protpy/_helpers.py @@ -0,0 +1,331 @@ +################################################################################ +############# Internal Helpers ############## +################################################################################ + +from __future__ import annotations + +""" +Internal engines shared by the descriptor modules added in v1.5.0. Nothing in +this module is exported from the protpy namespace. +""" + +import copy +import json +import os +from collections import Counter +from functools import lru_cache + +import numpy as np +import pandas as pd + +from ._constants import amino_acids + +_DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") + + +@lru_cache(maxsize=None) +def _read_json(filename: str) -> dict: + """ + Read a JSON data file from the protpy/data directory. Files are cached + after the first read. + + Parameters + ========== + :filename: str + name of the JSON file in protpy/data. + + Returns + ======= + :tables: dict + contents of the JSON file. + """ + with open(os.path.join(_DATA_DIR, filename)) as f: + return json.load(f) + + +def _load_json_table(filename: str, key: str) -> dict: + """ + Get a copy of a data table from a JSON file in the protpy/data directory. + Each table has the keys name, values, reference, doi and source_note. A copy + is returned so the cached table can't be modified. + + Parameters + ========== + :filename: str + name of the JSON file in protpy/data. + :key: str + name of the table in the file. + + Returns + ======= + :table: dict + copy of the data table. + + Raises + ====== + KeyError + if the table is not in the file; the message lists the available tables. + """ + tables = _read_json(filename) + if key not in tables: + raise KeyError(f"Table '{key}' not found in {filename}. Valid tables are: {sorted(tables)}.") + #copy so callers cannot mutate the cached table + return copy.deepcopy(tables[key]) + + +def _scale_values(scale: str | dict) -> dict: + """ + Get the values of an amino acid scale for each amino acid. The scale can be a + table name in the aa_scales.json data file, an AAindex accession number or a + dict of amino acid values. + + Parameters + ========== + :scale: str | dict + scale name, AAindex accession number or dict of amino acid values. + + Returns + ======= + :values: dict + dict of scale values (a float or a list of floats) for each amino acid. + + Raises + ====== + ValueError + if the scale is not a protpy scale or an AAindex accession number. + """ + if isinstance(scale, dict): + return scale + tables = _read_json("aa_scales.json") + if scale in tables: + return tables[scale]["values"] + from .autocorrelation import _get_properties + try: + return _get_properties(scale, normalize=False)[scale] + except ValueError: + raise ValueError(f"Unknown scale '{scale}'. Use a protpy scale name {sorted(tables)}, " + "an AAindex1 accession code, or a {amino_acid: value} dict.") from None + + +def _property_matrix(sequence: str, scales: str | list[str] | dict, standardize: bool = False) -> np.ndarray: + """ + Convert a protein sequence into a matrix of amino acid property values, with + one row per residue and one column per property dimension. The scales can be a + factor-scale family or single-value scale from the aa_scales.json data file, + an AAindex accession number, a list of AAindex accession numbers or a dict of + amino acid values (a float or a list of floats per amino acid). + + Parameters + ========== + :sequence: str + protein sequence. + :scales: str | list[str] | dict + scale name, AAindex accession number, list of accession numbers or dict of + amino acid values. + :standardize: bool (default=False) + standardise each dimension to zero mean and unit standard deviation over + the 20 amino acids. + + Returns + ======= + :matrix: np.ndarray + property values of the sequence, of the shape L x d, where L is the length + of the sequence and d is the number of property dimensions. + + Raises + ====== + ValueError + if the scale is missing values for any of the 20 amino acids. + """ + if isinstance(scales, (list, tuple)): + tables = [_scale_values(s) for s in scales] + table = {aa: [t[aa] for t in tables] for aa in amino_acids} + else: + table = _scale_values(scales) + missing = [aa for aa in amino_acids if aa not in table] + if missing: + raise ValueError(f"Scale is missing values for amino acids: {missing}.") + #rows ordered by amino_acids so standardisation is over exactly the 20 residues + aa_matrix = np.array([np.atleast_1d(np.asarray(table[aa], dtype=float)) for aa in amino_acids]) + if standardize: + std = aa_matrix.std(axis=0) + aa_matrix = np.divide(aa_matrix - aa_matrix.mean(axis=0), std, + out=np.zeros_like(aa_matrix), where=std != 0) + index = {aa: i for i, aa in enumerate(amino_acids)} + return aa_matrix[[index[aa] for aa in sequence]] + + +def _sliding_windows(values: np.ndarray, window: int, edge: float = 1.0) -> np.ndarray: + """ + Calculate the weighted mean of a profile over a sliding window. With edge=1.0 + the weights are all 1 (a plain mean); with edge < 1 the weights fall linearly + from 1 at the centre of the window to edge at its ends, as in ExPASy ProtScale + and Biopython's protein_scale. A window longer than the profile uses the whole + profile. + + Parameters + ========== + :values: np.ndarray + profile values along the sequence. + :window: int + length of the sliding window. + :edge: float (default=1.0) + relative weight of the ends of the window, between 0 and 1. + + Returns + ======= + :windows: np.ndarray + weighted mean of each window, of length L - window + 1. + """ + values = np.asarray(values, dtype=float) + window = min(window, len(values)) + half = (window - 1) / 2 + dist = np.minimum(np.arange(window), np.arange(window)[::-1]) + weights = edge + (1 - edge) * dist / half if half else np.ones(window) + weights = np.minimum(weights, 1.0) + return np.convolve(values, weights[::-1], mode="valid") / weights.sum() + + +def _profile_stats(profile: np.ndarray, prefix: str, stats=("mean", "max", "min", "std"), + threshold: float | None = None) -> dict: + """ + Summarise a profile by its mean, maximum, minimum and standard deviation, and + optionally the fraction of values above a threshold. + + Parameters + ========== + :profile: np.ndarray + profile values. + :prefix: str + prefix for the names of the statistics. + :stats: tuple (default=("mean", "max", "min", "std")) + statistics to calculate. + :threshold: float | None (default=None) + if set, also calculate the fraction of values above it (frac_above). + + Returns + ======= + :stats: dict + dict of statistic names (prefix_stat) and values. + """ + profile = np.asarray(profile, dtype=float) + funcs = {"mean": np.mean, "max": np.max, "min": np.min, "std": np.std} + out = {f"{prefix}_{s}": float(funcs[s](profile)) for s in stats} + if threshold is not None: + out[f"{prefix}_frac_above"] = float(np.mean(profile > threshold)) + return out + + +def _gapped_pair_counts(sequence: str, gap: int, alphabet: dict[str, str] | None = None) -> Counter: + """ + Count the pairs of residues (s_i, s_i+gap+1) in a protein sequence, with gap + residues between them. The residues can first be mapped to groups using a + reduced alphabet. + + Parameters + ========== + :sequence: str + protein sequence. + :gap: int + number of residues between the two residues of a pair. + :alphabet: dict | None (default=None) + dict mapping each amino acid to its group; None counts the amino acids. + + Returns + ======= + :counts: Counter + count of each pair, keyed by a tuple of the two residues or groups. + """ + seq = [alphabet[aa] for aa in sequence] if alphabet else list(sequence) + step = gap + 1 + return Counter((seq[i], seq[i + step]) for i in range(len(seq) - step)) + + +def _kmer_counts(sequence: str, k: int, alphabet: dict[str, str] | None = None) -> Counter: + """ + Count the overlapping k-mers of a protein sequence. The residues can first be + mapped to groups using a reduced alphabet. + + Parameters + ========== + :sequence: str + protein sequence. + :k: int + length of the k-mers. + :alphabet: dict | None (default=None) + dict mapping each amino acid to its group; None counts the amino acids. + + Returns + ======= + :counts: Counter + count of each k-mer, keyed by a tuple of its residues or groups. + """ + seq = [alphabet[aa] for aa in sequence] if alphabet else list(sequence) + return Counter(tuple(seq[i:i + k]) for i in range(len(seq) - k + 1)) + + +def _charge_vector(sequence: str, his: bool = False) -> np.ndarray: + """ + Get the charge of each residue of a protein sequence: +1 for K and R, -1 for + D and E and 0 otherwise. His is neutral unless his=True. + + Parameters + ========== + :sequence: str + protein sequence. + :his: bool (default=False) + treat His as positive. + + Returns + ======= + :charges: np.ndarray + charge of each residue. + """ + positive = "KRH" if his else "KR" + return np.array([1 if aa in positive else -1 if aa in "DE" else 0 for aa in sequence], dtype=float) + + +def _runs(mask: np.ndarray, min_len: int = 1) -> list[tuple[int, int]]: + """ + Find the runs of consecutive True values in a boolean array, e.g. the + segments of a profile above a threshold. + + Parameters + ========== + :mask: np.ndarray + boolean values along the sequence. + :min_len: int (default=1) + minimum length of a run. + + Returns + ======= + :runs: list + list of (start, end) indices of each run, where end is exclusive. + """ + mask = np.concatenate(([False], np.asarray(mask, dtype=bool), [False])) + edges = np.flatnonzero(np.diff(mask.astype(int))) + return [(int(s), int(e)) for s, e in zip(edges[::2], edges[1::2]) if e - s >= min_len] + + +def _to_df(values: list, columns: list[str]) -> pd.DataFrame: + """ + Convert a list of descriptor values into a single-row dataframe. Floats are + rounded to 3 decimal places and booleans and numpy integers are converted to + ints. + + Parameters + ========== + :values: list + descriptor values. + :columns: list[str] + column names. + + Returns + ======= + :descriptor_df: pd.DataFrame + pandas dataframe of the descriptor values, of the shape 1 x N, where N is + the number of values. + """ + values = [round(float(v), 3) if isinstance(v, (float, np.floating)) else + int(v) if isinstance(v, (bool, np.bool_, np.integer)) else v for v in values] + return pd.DataFrame([values], columns=columns) diff --git a/protpy/autocorrelation.py b/protpy/autocorrelation.py index 60e189d..1281c37 100644 --- a/protpy/autocorrelation.py +++ b/protpy/autocorrelation.py @@ -4,7 +4,6 @@ from __future__ import annotations -import numpy as np import pandas as pd import math from aaindex import aaindex1 @@ -25,7 +24,7 @@ """ #list of amino acids -from ._constants import amino_acids, _validate_sequence +from ._constants import amino_acids, _validate_sequence, _validate_int ############################### MoreauBroto Autocorrelation ############################### @@ -46,9 +45,9 @@ def moreaubroto_autocorrelation(sequence: str, lag: int = 30, The MBAuto descriptor is a type of Autocorrelation descriptor that uses the property values as the basis for measurement [2]. Each autocorrelation will generate the number of features depending on the lag value and number of - properties input with total features = lag * number of properties. The output - autocorrelation can also be normalized by setting the normalize parameter to - true, this occurs by default. Using the default 8 properties with default lag + properties input with total features = lag * number of properties. By default + (normalize=True) each property is standardised over the 20 amino acids before the + calculation; the output itself is not bounded. Using the default 8 properties with default lag value of 30, 240 features are generated, the default 8 properties are: AccNo. CIDH920105 - Normalized Average Hydrophobicity Scales @@ -69,7 +68,8 @@ def moreaubroto_autocorrelation(sequence: str, lag: int = 30, :properties: list list of AAI index record codes/accession numbers for the physicochemical properties to use in the calculation. Defaults to the 8 standard AAIndex properties listed above. :normalize: bool (default=True) - rescale/normalize MoreauBroto Autocorrelation values into range of 0-1. + standardise each property (zero mean, unit standard deviation over the 20 + amino acids) before the calculation. This does not bound the output to [0, 1]. Returns ======= @@ -82,49 +82,13 @@ def moreaubroto_autocorrelation(sequence: str, lag: int = 30, #validate input protein sequence sequence = _validate_sequence(sequence) - #validate lag, set default lag if invalid value input - if (lag>=len(sequence) or (lag<0) or not (isinstance(lag, int))): + #validate lag; fall back to the default of 30 when lag exceeds the sequence length + lag = _validate_int("lag", lag) + if lag >= len(sequence): lag = 30 - #validate that at least 1 property input to function - if (properties == "" or properties == []): - raise ValueError('At least one property must be passed into function.') - - #if 1 property passed into function, wrap it to an iterable list - if (isinstance(properties, str)): - properties = [properties] - - #initialise dicts to store AAI properties and values - aai_properties = {} - for prop in properties: - #raise value error if accession number not found in aaindex1 - if not (prop in aaindex1.record_codes()): - raise ValueError(f"Property {prop} not found in list of available properties in the aaindex1:\n{properties}.") - aai_properties[prop] = {} - - #iterate through list of properties, getting property values for accession numbers from AAIndex1 - for prop in list(aai_properties.keys()): - #get property values from AAIndex - aaindex1[prop].values.pop('-', None) - prop_amino_acid_values = aaindex1[prop].values - - #normalise property values if applicable, calculate mean and std dev - aai_property_vals = {} - if (normalize): - for i, j in prop_amino_acid_values.items(): - aai_property_vals[i] = (j - (sum(prop_amino_acid_values.values()) / - len(prop_amino_acid_values.values()))) / _std(prop_amino_acid_values.values(), ddof=0) - else: - #use raw (unnormalized) property values - for i, j in prop_amino_acid_values.items(): - aai_property_vals[i] = j - aa_counter = 0 - - #assign property and associated amino acid values to aai_property_vals array - for i, j in aai_property_vals.items(): - aai_property_vals[amino_acids[aa_counter]] = aai_property_vals[i] - aa_counter+=1 - aai_properties[prop] = aai_property_vals + #get (optionally normalized) property values for each AAIndex accession number + aai_properties = _get_properties(properties, normalize) #store resultant autocorrelation values moreaubroto_autocorrelation = {} @@ -135,12 +99,9 @@ def moreaubroto_autocorrelation(sequence: str, lag: int = 30, temp = 0 # reset per lag for j in range(len(sequence)-i): temp = temp + aai_properties[key][sequence[j]] * aai_properties[key][sequence[j+i]] - if (len(sequence) - i == 0): - moreaubroto_autocorrelation["MBAuto_" + key + "_" + str(i)] = round( - temp / (len(sequence)), 3) - else: - moreaubroto_autocorrelation["MBAuto_" + key + "_" + str(i)] = round( - temp / (len(sequence) - i), 3) + #lags with no residue pairs (lag >= sequence length) are 0 + moreaubroto_autocorrelation["MBAuto_" + key + "_" + str(i)] = ( + round(temp / (len(sequence) - i), 3) if len(sequence) > i else 0.0) #transform values and columns to DataFrame moreaubroto_autocorrelation_df = pd.DataFrame([list(moreaubroto_autocorrelation.values())], @@ -167,7 +128,8 @@ def moran_autocorrelation(sequence: str, lag: int = 30, :properties: list list of AAI index record codes/accession numbers for the physicochemical properties to use in the calculation. Defaults to the same 8 standard AAIndex properties as MBAuto. :normalize: bool (default=True) - rescale/normalize MoreauBroto Autocorrelation values into range of 0-1. + standardise each property (zero mean, unit standard deviation over the 20 + amino acids) before the calculation. This does not bound the output to [0, 1]. Returns ======= @@ -180,50 +142,13 @@ def moran_autocorrelation(sequence: str, lag: int = 30, #validate input protein sequence sequence = _validate_sequence(sequence) - #validate lag, set default lag if invalid value input - if (lag>=len(sequence) or (lag<0) or not (isinstance(lag, int))): + #validate lag; fall back to the default of 30 when lag exceeds the sequence length + lag = _validate_int("lag", lag) + if lag >= len(sequence): lag = 30 - #validate at least 1 property input to function - if (properties == "" or properties == []): - raise ValueError('At least one property must be passed into function.') - - #if 1 property passed into function, wrap it to an iterable list - if (isinstance(properties, str)): - properties = [properties] - - #initialise dicts to store AAI properties and values - aai_properties = {} - for prop in properties: - #raise value error if accession number not found in aaindex1 - if not (prop in aaindex1.record_codes()): - raise ValueError(f"Property {prop} not found in list of available properties in the aaindex1:\n{properties}.") - aai_properties[prop] = {} - - #iterate through list of properties, getting property values for accession numbers from AAIndex1 - for prop in list(aai_properties.keys()): - #get property values from AAIndex - aaindex1[prop].values.pop('-', None) - prop_aminoacid_values = aaindex1[prop].values - - aai_property_vals = {} - - #normalise property values, calculate mean and std dev - if (normalize): - for i, j in prop_aminoacid_values.items(): - aai_property_vals[i] = (j - (sum(prop_aminoacid_values.values()) / - len(prop_aminoacid_values.values()))) / _std(prop_aminoacid_values.values(), ddof=0) - else: - #use raw (unnormalized) property values - for i, j in prop_aminoacid_values.items(): - aai_property_vals[i] = j - aa_counter = 0 - - #assign property and associated amino acid values to aai_property_vals array - for i, j in aai_property_vals.items(): - aai_property_vals[amino_acids[aa_counter]] = aai_property_vals[i] - aa_counter+=1 - aai_properties[prop] = aai_property_vals + #get (optionally normalized) property values for each AAIndex accession number + aai_properties = _get_properties(properties, normalize) #store resultant autocorrelation values moran_autocorrelation = {} @@ -243,12 +168,9 @@ def moran_autocorrelation(sequence: str, lag: int = 30, for j in range(len(sequence) - i): temp = temp + (aai_properties[key][sequence[j]] - prop_mean) * ( aai_properties[key][sequence[j+i]] - prop_mean) - if len(sequence) - i == 0: - moran_autocorrelation["MAuto_" + key + "_" + str(i)] = round( - temp / ((len(sequence)) / k), 5) - else: - moran_autocorrelation["MAuto_" + key + "_" + str(i)] = round( - temp / ((len(sequence) - i) / k), 5) + #Moran: (1/(N-d) * sum) / variance; 0 when there are no pairs or no variance + moran_autocorrelation["MAuto_" + key + "_" + str(i)] = ( + round(temp / (len(sequence) - i) / k, 5) if (len(sequence) > i and k > 0) else 0.0) #transform values and columns to DataFrame moran_autocorrelation_df = pd.DataFrame([list(moran_autocorrelation.values())], @@ -275,7 +197,8 @@ def geary_autocorrelation(sequence: str, lag: int = 30, :properties: list list of AAI index record codes/accession numbers for the physicochemical properties to use in the calculation. Defaults to the same 8 standard AAIndex properties as MBAuto. :normalize: bool (default=True) - rescale/normalize MoreauBroto Autocorrelation values into range of 0-1. + standardise each property (zero mean, unit standard deviation over the 20 + amino acids) before the calculation. This does not bound the output to [0, 1]. Returns ======= @@ -288,50 +211,13 @@ def geary_autocorrelation(sequence: str, lag: int = 30, #validate input protein sequence sequence = _validate_sequence(sequence) - #validate lag, set default lag if invalid value input - if (lag>=len(sequence) or (lag<0) or not (isinstance(lag, int))): + #validate lag; fall back to the default of 30 when lag exceeds the sequence length + lag = _validate_int("lag", lag) + if lag >= len(sequence): lag = 30 - #validate at least 1 property input to function - if (properties == "" or properties == []): - raise ValueError('At least one property must be passed into function.') - - #if 1 property passed into function, wrap it to an iterable list - if (isinstance(properties, str)): - properties = [properties] - - #initialise dicts to store AAI properties and values - aai_properties = {} - for prop in properties: - #raise value error if accession number not found in aaindex1 - if not (prop in aaindex1.record_codes()): - raise ValueError(f"Property {prop} not found in list of available properties in the aaindex1:\n{properties}.") - aai_properties[prop] = {} - - #iterate through list of properties, getting property values for accession numbers from AAIndex1 - for prop in list(aai_properties.keys()): - #get property values from AAIndex & reshape - aaindex1[prop].values.pop('-', None) - prop_aminoacid_values = aaindex1[prop].values - - aai_property_vals = {} - - #normalise property values, calculate mean and std dev - if (normalize): - for i, j in prop_aminoacid_values.items(): - aai_property_vals[i] = (j - (sum(prop_aminoacid_values.values()) / - len(prop_aminoacid_values.values()))) / _std(prop_aminoacid_values.values(), ddof=0) - else: - #use raw (unnormalized) property values - for i, j in prop_aminoacid_values.items(): - aai_property_vals[i] = j - aa_counter = 0 - - #assign property and associated amino acid values to aai_property_vals array - for i, j in aai_property_vals.items(): - aai_property_vals[amino_acids[aa_counter]] = aai_property_vals[i] - aa_counter+=1 - aai_properties[prop] = aai_property_vals + #get (optionally normalized) property values for each AAIndex accession number + aai_properties = _get_properties(properties, normalize) #store resultant autocorrelation values geary_autocorrelation = {} @@ -341,18 +227,15 @@ def geary_autocorrelation(sequence: str, lag: int = 30, cc = [] for aa in sequence: cc.append(aai_properties[key][aa]) - k = ((np.std(cc, ddof=0)) ** 2) * len(sequence) / (len(sequence) - 1) + k = ((_std(cc, ddof=0)) ** 2) * len(sequence) / (len(sequence) - 1) if len(sequence) > 1 else 0.0 for i in range(1, lag+1): temp = 0 for j in range(len(sequence) - i): temp = (temp + ( aai_properties[key][sequence[j]] - aai_properties[key][sequence[j+i]]) ** 2) - if len(sequence) - i == 0: - geary_autocorrelation["GAuto_" + key + "_" + str(i)] = round( - temp / (2* (len(sequence))) / k, 3) - else: - geary_autocorrelation["GAuto_" + key + "_" + str(i)] = round( - temp / (2* (len(sequence) -i)) / k, 3) + #0 when there are no pairs or no variance + geary_autocorrelation["GAuto_" + key + "_" + str(i)] = ( + round(temp / (2* (len(sequence) -i)) / k, 3) if (len(sequence) > i and k > 0) else 0.0) #transform values and columns to DataFrame geary_autocorrelation_df = pd.DataFrame([list(geary_autocorrelation.values())], @@ -360,6 +243,48 @@ def geary_autocorrelation(sequence: str, lag: int = 30, return geary_autocorrelation_df +def _get_properties(properties: list[str] | str, normalize: bool) -> dict[str, dict[str, float]]: + """ + Get the values of the 20 canonical amino acids for each AAIndex1 accession number, + optionally normalized to zero mean and unit standard deviation. Values are copied + so the aaindex records themselves are never modified. + + Parameters + ========== + :properties: list[str] | str + AAIndex1 accession number(s). + :normalize: bool + normalize the property values. + + Returns + ======= + :aai_properties: dict + {accession_number: {amino_acid: value}}. + """ + #validate that at least 1 property input to function + if (properties == "" or properties == [] or properties is None): + raise ValueError('At least one property must be passed into function.') + + #if 1 property passed into function, wrap it to an iterable list + if (isinstance(properties, str)): + properties = [properties] + + record_codes = aaindex1.record_codes() + aai_properties = {} + for prop in properties: + #raise value error if accession number not found in aaindex1 + if prop not in record_codes: + raise ValueError(f"Property {prop} not found in list of available properties in the aaindex1:\n{properties}.") + #keep only the 20 canonical amino acids (drops the '-' gap entry) + vals = {aa: v for aa, v in aaindex1[prop].values.items() if aa in amino_acids} + if normalize: + mean = sum(vals.values()) / len(vals) + std = _std(list(vals.values()), ddof=0) + vals = {aa: (v - mean) / std if std else 0.0 for aa, v in vals.items()} + aai_properties[prop] = vals + + return aai_properties + def _std(array: list[float], ddof: int = 1) -> float: """ Calculate the standard deviation of the array data, with associated means Delta Degrees diff --git a/protpy/complexity.py b/protpy/complexity.py new file mode 100644 index 0000000..cb0839b --- /dev/null +++ b/protpy/complexity.py @@ -0,0 +1,219 @@ +################################################################################ +############# Sequence Complexity ############## +################################################################################ + +from __future__ import annotations + +""" +Sequence complexity descriptors: Wootton-Federhen (SEG-like) complexity, +linguistic complexity and Lempel-Ziv complexity. ``shannon_entropy`` lives in +the composition module and is re-exported here. + +References +========== +[1] Wootton, J. C., & Federhen, S. (1993). Statistics of local complexity in amino + acid sequences and sequence databases. Comput Chem, 17(2), 149-163. + https://doi.org/10.1016/0097-8485(93)85006-X +[2] Wootton, J. C., & Federhen, S. (1996). Analysis of compositionally biased + regions in sequence databases. Methods Enzymol, 266, 554-571. + https://doi.org/10.1016/S0076-6879(96)66035-2 +[3] Troyanskaya, O. G., Arbell, O., Koren, Y., Landau, G. M., & Bolshoy, A. + (2002). Sequence complexity profiles of prokaryotic genomic sequences: a fast + algorithm for calculating linguistic complexity. Bioinformatics, 18(5), + 679-688. https://doi.org/10.1093/bioinformatics/18.5.679 +[4] Lempel, A., & Ziv, J. (1976). On the complexity of finite sequences. IEEE + Trans Inf Theory, 22(1), 75-81. https://doi.org/10.1109/TIT.1976.1055501 +[5] Diao, Y. et al. (2008). Using pseudo amino acid composition to predict + transmembrane regions in protein: cellular automata and Lempel-Ziv + complexity. Amino Acids, 34(1), 111-117. https://doi.org/10.1007/s00726-007-0550-z +""" + +import math +from collections import Counter + +import numpy as np +import pandas as pd + +from ._constants import _validate_sequence, _validate_int +from ._helpers import _to_df +from .composition import shannon_entropy + +__all__ = [ + "wootton_federhen_complexity", "linguistic_complexity", "lempel_ziv_complexity", "shannon_entropy", +] + +########################### Wootton-Federhen Complexity ################################### + +def wootton_federhen_complexity(sequence: str, window: int = 12, k2_trigger: float = 2.2) -> pd.DataFrame: + """ + Calculate the Wootton-Federhen Complexity (WF) of protein sequence. The + Wootton-Federhen measures describe the local compositional complexity of a + protein sequence over a sliding window, and are the basis of the SEG program + for finding low-complexity regions [1, 2]. For each window of W residues + they are calculated as: + + K1 = (1/W) * log_20(W! / prod_i n_i!) + + K2 = -sum_i (n_i / W) * log2(n_i / W) + + where K1 is the complexity state, K2 is the Shannon entropy of the window in + bits and n_i is the number of residues of amino acid i in the window. The + low-complexity fraction is the fraction of residues covered by at least one + window with K2 <= k2_trigger. This is the trigger stage of SEG only; SEG's + extension and merging of low-complexity segments are not performed. A window + longer than the sequence uses the whole sequence. + + Parameters + ========== + :sequence: str + protein sequence. + :window: int (default=12) + length of the sliding window W (the SEG default). + :k2_trigger: float (default=2.2) + K2 value in bits at or below which a window is low complexity (the SEG + default). + + Returns + ======= + :wf_df: pd.DataFrame + pandas dataframe of Wootton-Federhen complexity for protein sequence. + Dataframe will be of the shape 1 x 4, with the columns WF_K1_mean, + WF_K2_mean, WF_K2_min and WF_LowComplexity_frac. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + window = min(_validate_int("window", window, 1), len(sequence)) + + k1, k2 = [], [] + covered = np.zeros(len(sequence), dtype=bool) + counts = Counter(sequence[:window]) + for start in range(len(sequence) - window + 1): + if start: + #slide the window by one residue + counts[sequence[start - 1]] -= 1 + counts[sequence[start + window - 1]] += 1 + n = [c for c in counts.values() if c] + log_states = math.lgamma(window + 1) - sum(math.lgamma(c + 1) for c in n) + k1.append(log_states / math.log(20) / window) + entropy = 0.0 - sum(c / window * math.log2(c / window) for c in n) + k2.append(entropy) + if entropy <= k2_trigger: + covered[start:start + window] = True + return _to_df([np.mean(k1), np.mean(k2), min(k2), covered.mean()], + ["WF_K1_mean", "WF_K2_mean", "WF_K2_min", "WF_LowComplexity_frac"]) + +############################### Linguistic Complexity ##################################### + +def linguistic_complexity(sequence: str, max_k: int | None = None) -> pd.DataFrame: + """ + Calculate the Linguistic Complexity (LC) of protein sequence. Linguistic + complexity describes how repetitive a sequence is, as the number of distinct + subwords (k-mers) it contains relative to the maximum possible number for a + sequence of its length [3]. It is calculated as: + + LC = sum_{k=1}^{K} U_k / sum_{k=1}^{K} min(20^k, L - k + 1) + + where U_k is the number of distinct k-mers in the sequence, L is the length of + the sequence and K is the longest subword length (by default L, i.e. all + subwords). Repetitive sequences have a low LC and random sequences an LC + close to 1. + + Parameters + ========== + :sequence: str + protein sequence. + :max_k: int | None (default=None) + longest subword length K; None uses the length of the sequence. + + Returns + ======= + :lc_df: pd.DataFrame + pandas dataframe of LC for protein sequence. Dataframe will be of the + shape 1 x 1, with the column LinguisticComplexity. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + L = len(sequence) + max_k = L if max_k is None else min(_validate_int("max_k", max_k, 1), L) + + observed = possible = 0 + for k in range(1, max_k + 1): + n_kmers = L - k + 1 + observed += len({sequence[i:i + k] for i in range(n_kmers)}) + #20^k overtakes L - k + 1 quickly; avoid huge integers + possible += n_kmers if k > math.log(n_kmers, 20) else min(20 ** k, n_kmers) + return _to_df([observed / possible], ["LinguisticComplexity"]) + +############################### Lempel-Ziv Complexity ##################################### + +def _lz76(sequence: str) -> int: + """ + Calculate the number of components in the Lempel-Ziv (LZ76) parsing of a + sequence [4], using the Kaspar-Schuster algorithm. The sequence is read from + left to right and each component is the shortest substring that cannot be + copied from the preceding part of the sequence. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :c: int + number of components c(n) in the LZ76 parsing. + """ + n = len(sequence) + if n == 1: + return 1 + c, i, k, l, k_max = 1, 0, 1, 1, 1 + while True: + if sequence[i + k - 1] == sequence[l + k - 1]: + k += 1 + if l + k > n: + c += 1 + break + else: + k_max = max(k, k_max) + i += 1 + if i == l: + #no earlier copy: the component ends here + c += 1 + l += k_max + if l + 1 > n: + break + i, k, k_max = 0, 1, 1 + else: + k = 1 + return c + + +def lempel_ziv_complexity(sequence: str) -> pd.DataFrame: + """ + Calculate the Lempel-Ziv Complexity (LZ) of protein sequence. The Lempel-Ziv + (LZ76) complexity is the number of new patterns, c(n), found when a sequence is + read from left to right, each pattern being the shortest substring that cannot + be copied from the preceding part of the sequence [4, 5]. As c(n) grows with the + length of the sequence, a normalised value is also calculated as: + + LZ_Normalized = c(n) * log_20(n) / n + + where n is the length of the sequence and 20 is the size of the amino acid + alphabet. The normalised value is 0 for a single residue. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :lz_df: pd.DataFrame + pandas dataframe of LZ complexity for protein sequence. Dataframe will be + of the shape 1 x 2, with the columns LZ_Complexity and LZ_Normalized. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + n = len(sequence) + c = _lz76(sequence) + return _to_df([c, c * math.log(n, 20) / n if n > 1 else 0.0], ["LZ_Complexity", "LZ_Normalized"]) diff --git a/protpy/composition.py b/protpy/composition.py index 827fed5..27befde 100644 --- a/protpy/composition.py +++ b/protpy/composition.py @@ -6,6 +6,8 @@ import re import itertools +from collections import Counter +import numpy as np import pandas as pd import math from aaindex import aaindex1 @@ -61,9 +63,42 @@ class prediction: new methods of statistical classification. Proc Int Conf [19] Eisenberg, D., Weiss, R. M., & Terwilliger, T. C. (1982). The helical hydrophobic moment: a measure of the amphiphilicity of a helix. Nature, 299, 371-374. https://doi.org/10.1038/299371a0 +[20] Chen, K., Kurgan, L. A., & Ruan, J. (2007). Prediction of flexible/rigid + regions from protein sequences using k-spaced amino acid pairs. BMC + Structural Biology, 7, 25. https://doi.org/10.1186/1472-6807-7-25 +[21] Chen, Z. et al. (2018). iFeature: a Python package and web server for + features extraction and selection from protein and peptide sequences. + Bioinformatics, 34(14), 2499-2502. https://doi.org/10.1093/bioinformatics/bty140 +[22] Saravanan, V., & Gautham, N. (2015). Harnessing computational biology for + exact linear B-cell epitope prediction: a novel amino acid composition-based + feature descriptor. OMICS, 19(10), 648-658. https://doi.org/10.1089/omi.2015.0095 +[23] Wei, L. et al. (2018). ACPred-FL: a sequence-based predictor using effective + feature representation to improve the prediction of anti-cancer peptides. + Bioinformatics, 34(23), 4007-4016. https://doi.org/10.1093/bioinformatics/bty451 +[24] Liu, B. et al. (2014). iDNA-Prot|dis: identifying DNA-binding proteins by + incorporating amino acid distance-pairs and reduced alphabet profile into + the general pseudo amino acid composition. PLoS ONE, 9(9), e106691. + https://doi.org/10.1371/journal.pone.0106691 +[25] Cannata, N. et al. (2002). Simplifying amino acid alphabets by means of a + branch and bound algorithm and substitution matrices. Bioinformatics, 18(8), + 1102-1108. https://doi.org/10.1093/bioinformatics/18.8.1102 +[26] Yu, C. et al. (2013). Protein space: a natural method for realizing the + nature of protein universe. Journal of Theoretical Biology, 318, 197-204. + https://doi.org/10.1016/j.jtbi.2012.11.005 +[27] Pande, A. et al. (2023). Computing wide range of protein/peptide features + from their sequence and structure. Journal of Computational Biology, 30(2), + 204-222. https://doi.org/10.1089/cmb.2022.0241 +[28] Kumar, M., Verma, R., & Raghava, G. P. S. (2006). Prediction of + mitochondrial proteins using support vector machine and hidden Markov + model. Journal of Biological Chemistry, 281(9), 5357-5363. + https://doi.org/10.1074/jbc.M511061200 +[29] Lata, S., Sharma, B. K., & Raghava, G. P. S. (2007). Analysis and + prediction of antibacterial peptides. BMC Bioinformatics, 8, 263. + https://doi.org/10.1186/1471-2105-8-263 """ #list of amino acids -from ._constants import amino_acids, _validate_sequence +from ._constants import amino_acids, _validate_sequence, _validate_int, _validate_weight +from ._helpers import _load_json_table, _read_json, _gapped_pair_counts, _kmer_counts, _to_df, _property_matrix #physicochemical values for all amino acids for hydrophobicity, hydrophilicty and residue mass #values taken from http://www.csbio.sjtu.edu.cn/bioinf/PseAAC/ParaValue.htm @@ -91,7 +126,7 @@ class prediction: new methods of statistical classification. Proc Int Conf ################################# Amino Acid Composition ################################## -def amino_acid_composition(sequence: str) -> pd.DataFrame: +def amino_acid_composition(sequence: str, region: str | None = None, n: int = 25) -> pd.DataFrame: """ Calculate Amino Acid Composition (AAComp) of protein sequence. AAComp describes the fraction of each amino acid type within a protein sequence, @@ -107,6 +142,14 @@ def amino_acid_composition(sequence: str) -> pd.DataFrame: ========== :sequence: str protein sequence. + :region: str | None (default=None) + restrict the calculation to part of the sequence: None (whole sequence), + "nterm" (first ``n`` residues, columns prefixed ``NT_``), "cterm" (last + ``n`` residues, ``CT_``) or "split" (N-terminal, middle and C-terminal + blocks concatenated, prefixed ``NT_``, ``MID_``, ``CT_``; split + composition [28, 29]). + :n: int (default=25) + terminal block length used by ``region``. Returns ======= @@ -118,22 +161,24 @@ def amino_acid_composition(sequence: str) -> pd.DataFrame: """ #validate input protein sequence sequence = _validate_sequence(sequence) + if region is not None: + return _regional(amino_acid_composition, sequence, region, n) - amino_acid_composition = {} + aa_composition = {} #iterate through each amino acid, calculating each composition for aa in amino_acids: - amino_acid_composition[aa] = round(float(sequence.count(aa)) / len(sequence) * 100, 3) + aa_composition[aa] = round(float(sequence.count(aa)) / len(sequence) * 100, 3) #transform values and columns to DataFrame - amino_acid_composition_df = pd.DataFrame([list(amino_acid_composition.values())], - columns=list(amino_acid_composition.keys())) + amino_acid_composition_df = pd.DataFrame([list(aa_composition.values())], + columns=list(aa_composition.keys())) return amino_acid_composition_df ################################## Dipeptide Composition ################################## -def dipeptide_composition(sequence: str) -> pd.DataFrame: +def dipeptide_composition(sequence: str, region: str | None = None, n: int = 25) -> pd.DataFrame: """ Calculate Dipeptide Composition (DPComp) for protein sequence. Dipeptide composition is the fraction of each dipeptide type within a @@ -152,6 +197,14 @@ def dipeptide_composition(sequence: str) -> pd.DataFrame: ========== :sequence: str protein sequence. + :region: str | None (default=None) + restrict the calculation to part of the sequence: None (whole sequence), + "nterm" (first ``n`` residues, columns prefixed ``NT_``), "cterm" (last + ``n`` residues, ``CT_``) or "split" (N-terminal, middle and C-terminal + blocks concatenated, prefixed ``NT_``, ``MID_``, ``CT_``; split + composition [28, 29]). + :n: int (default=25) + terminal block length used by ``region``. Returns ======= @@ -163,22 +216,70 @@ def dipeptide_composition(sequence: str) -> pd.DataFrame: """ #validate input protein sequence sequence = _validate_sequence(sequence) + if region is not None: + return _regional(dipeptide_composition, sequence, region, n) - dipeptide_composition = {} + dipep_composition = {} + + #count overlapping dipeptides with a sliding window (str.count misses overlaps, e.g. "AA" in "AAA") + total_dipeptides = len(sequence) - 1 + counts = Counter(sequence[i:i+2] for i in range(total_dipeptides)) #iterate through each amino acid, calculating each dipeptide combination's composition for i in amino_acids: for j in amino_acids: dipep = i + j - dipeptide_composition[dipep] = round( - float(sequence.count(dipep)) / (len(sequence)-1) * 100, 2) + dipep_composition[dipep] = round( + counts[dipep] / total_dipeptides * 100, 2) if total_dipeptides > 0 else 0.0 #transform values and columns to DataFrame - dipeptide_composition_df = pd.DataFrame([list(dipeptide_composition.values())], - columns=list(dipeptide_composition.keys())) + dipeptide_composition_df = pd.DataFrame([list(dipep_composition.values())], + columns=list(dipep_composition.keys())) return dipeptide_composition_df +def _regional(func, sequence: str, region: str, n: int) -> pd.DataFrame: + """ + Calculate a composition descriptor over part of a protein sequence. The + N-terminal ("nterm"), C-terminal ("cterm") or N-terminal, middle and + C-terminal ("split") regions are passed to the descriptor function and the + resulting columns are prefixed with NT_, MID_ and CT_, respectively. Split + composition was used for predicting mitochondrial proteins and antibacterial + peptides [28, 29]. + + Parameters + ========== + :func: function + composition descriptor function to apply to each region, e.g. + amino_acid_composition. + :sequence: str + validated protein sequence. + :region: str + region of the sequence to use: "nterm", "cterm" or "split". + :n: int + length of the N- and C-terminal regions. + + Returns + ======= + :regional_df: pd.DataFrame + pandas dataframe of the descriptor values for each region, with the + region prefix added to each column. For "split" the dataframe will be + of the shape 1 x 3N, where N is the number of features of the descriptor. + """ + n = _validate_int("n", n, 1) + parts = {"nterm": [("NT_", sequence[:n])], "cterm": [("CT_", sequence[-n:])], + "split": [("NT_", sequence[:n]), ("MID_", sequence[n:-n]), ("CT_", sequence[-n:])]} + if region not in parts: + raise ValueError(f"Invalid region '{region}'. Valid regions are: None, {list(parts)}.") + #the middle block of a split must be non-empty + if region == "split" and len(sequence) <= 2 * n: + raise ValueError(f"region='split' needs a sequence longer than 2n ({2 * n}), got length {len(sequence)}.") + frames = [] + for prefix, part in parts[region]: + df = func(part) + frames.append(df.add_prefix(prefix)) + return pd.concat(frames, axis=1) + ################################## Tripeptide Composition ################################# def tripeptide_composition(sequence: str) -> pd.DataFrame: @@ -224,8 +325,9 @@ def tripeptide_composition(sequence: str) -> pd.DataFrame: #get fractional frequency of each tripeptide (percentage), consistent with dipeptide_composition total_tripeptides = len(sequence) - 2 + counts = Counter(sequence[i:i+3] for i in range(max(total_tripeptides, 0))) for i in tripeptides: - tripeptide_composition[i] = round(sequence.count(i) / total_tripeptides * 100, 3) if total_tripeptides > 0 else 0.0 + tripeptide_composition[i] = round(counts[i] / total_tripeptides * 100, 3) if total_tripeptides > 0 else 0.0 #transform values and columns to DataFrame tripeptide_composition_df = pd.DataFrame([list(tripeptide_composition.values())], @@ -243,7 +345,7 @@ def tripeptide_composition(sequence: str) -> pd.DataFrame: "W": -0.9, "Y": -1.3 } -def gravy(sequence: str) -> pd.DataFrame: +def gravy(sequence: str, scale: str = "kyte_doolittle") -> pd.DataFrame: """ Calculate the Grand Average of Hydropathy (GRAVY) for a protein sequence. GRAVY is the sum of the Kyte-Doolittle hydropathy values of all residues @@ -254,10 +356,16 @@ def gravy(sequence: str) -> pd.DataFrame: A positive GRAVY value indicates an overall hydrophobic protein; a negative value indicates an overall hydrophilic protein. + Another single-value scale can be averaged with ``scale`` ("hopp_woods", + "eisenberg", "wimley_white", "engelman", "domcut" or any AAindex1 accession + code); the column is then named ``GRAVY_{scale}``. + Parameters ========== :sequence: str protein sequence. + :scale: str (default="kyte_doolittle") + hydrophobicity scale to average. Returns ======= @@ -268,6 +376,12 @@ def gravy(sequence: str) -> pd.DataFrame: #validate input protein sequence sequence = _validate_sequence(sequence) + if scale != "kyte_doolittle": + values = _property_matrix(sequence, scale) + if values.shape[1] != 1: + raise ValueError(f"Scale '{scale}' is multi-dimensional; use a single-value scale.") + return _to_df([values.mean()], [f"GRAVY_{scale}"]) + #sum Kyte-Doolittle hydropathy values and divide by sequence length gravy_score = round(sum(kyte_doolittle_[aa] for aa in sequence) / len(sequence), 3) @@ -418,16 +532,45 @@ def instability_index(sequence: str) -> pd.DataFrame: "N_term": 8.0, "C_term": 3.1 } -def isoelectric_point(sequence: str) -> pd.DataFrame: +def _pka_set(pka_scale: str) -> dict: + """ + Get a set of pKa values used in the isoelectric point and charge + calculations from the residue_tables.json data file. + + Parameters + ========== + :pka_scale: str + name of the pKa set: "protpy", "emboss", "bjellqvist" or "ipc_protein". + + Returns + ======= + :pka_set: dict + dict of pKa values for each ionisable group. + """ + sets = _load_json_table("residue_tables.json", "pka_sets")["values"] + if pka_scale not in sets: + raise ValueError(f"Invalid pka_scale '{pka_scale}'. Valid scales are: {list(sets)}.") + return sets[pka_scale] + +def isoelectric_point(sequence: str, pka_scale: str = "protpy") -> pd.DataFrame: """ Calculate the theoretical Isoelectric Point (pI) of a protein sequence. pI is the pH at which the net charge of the protein is zero. It is computed - by iteratively adjusting pH until positive and negative charges balance [11]. + by bisecting the pH range 0-14 until positive and negative charges balance [11]. + + ``pka_scale`` selects the pKa values: "protpy" (default, unchanged), "emboss" + (EMBOSS Epk.dat), "bjellqvist" (Bjellqvist 1993/1994, including the + residue-specific N-terminal pKa values for A, M, S, P, T, V, E and C-terminal + values for D and E) or "ipc_protein" (Kozlowski 2016). The same charge model + (K, R, H, N-terminus positive; D, E, C, Y, C-terminus negative) is used for + every scale. Parameters ========== :sequence: str protein sequence. + :pka_scale: str (default="protpy") + pKa value set: "protpy", "emboss", "bjellqvist" or "ipc_protein". Returns ======= @@ -437,25 +580,38 @@ def isoelectric_point(sequence: str) -> pd.DataFrame: #validate input protein sequence sequence = _validate_sequence(sequence) + if pka_scale == "protpy": + pka = pka_values_ + else: + pka = dict(_pka_set(pka_scale)) + #residue-specific terminal pKa values (Bjellqvist) + pka["N_term"] = pka.get("N_term_by_residue", {}).get(sequence[0], pka["N_term"]) + pka["C_term"] = pka.get("C_term_by_residue", {}).get(sequence[-1], pka["C_term"]) + #count ionisable residues counts = {aa: sequence.count(aa) for aa in "DEHCYKR"} - #iterative charge-balance: adjust pH by +/- 0.001 until net charge < 1e-4 - ph = 7.0 - for _ in range(5000): - pos = (counts["K"] / (1 + 10 ** (ph - pka_values_["K"])) + - counts["R"] / (1 + 10 ** (ph - pka_values_["R"])) + - counts["H"] / (1 + 10 ** (ph - pka_values_["H"])) + - 1.0 / (1 + 10 ** (ph - pka_values_["N_term"]))) - neg = (counts["D"] / (1 + 10 ** (pka_values_["D"] - ph)) + - counts["E"] / (1 + 10 ** (pka_values_["E"] - ph)) + - counts["C"] / (1 + 10 ** (pka_values_["C"] - ph)) + - counts["Y"] / (1 + 10 ** (pka_values_["Y"] - ph)) + - 1.0 / (1 + 10 ** (pka_values_["C_term"] - ph))) - net_charge = pos - neg - if abs(net_charge) < 1e-4: - break - ph += 0.001 if net_charge > 0 else -0.001 + def _net_charge(ph: float) -> float: + pos = (counts["K"] / (1 + 10 ** (ph - pka["K"])) + + counts["R"] / (1 + 10 ** (ph - pka["R"])) + + counts["H"] / (1 + 10 ** (ph - pka["H"])) + + 1.0 / (1 + 10 ** (ph - pka["N_term"]))) + neg = (counts["D"] / (1 + 10 ** (pka["D"] - ph)) + + counts["E"] / (1 + 10 ** (pka["E"] - ph)) + + counts["C"] / (1 + 10 ** (pka["C"] - ph)) + + counts["Y"] / (1 + 10 ** (pka["Y"] - ph)) + + 1.0 / (1 + 10 ** (pka["C_term"] - ph))) + return pos - neg + + #net charge decreases monotonically with pH, so bisect over the full 0-14 range + low, high = 0.0, 14.0 + for _ in range(50): + mid = (low + high) / 2 + if _net_charge(mid) > 0: + low = mid + else: + high = mid + ph = (low + high) / 2 #transform to DataFrame isoelectric_point_df = pd.DataFrame([[round(ph, 3)]], columns=["IsoelectricPoint"]) @@ -467,7 +623,7 @@ def isoelectric_point(sequence: str) -> pd.DataFrame: #average residue molecular weights (Da) for each of the 20 standard amino acids [12] residue_molecular_weight_ = { "A": 89.094, "C": 121.154, "D": 133.104, "E": 147.130, "F": 165.192, - "G": 75.032, "H": 155.156, "I": 131.174, "K": 146.189, "L": 131.174, + "G": 75.067, "H": 155.156, "I": 131.174, "K": 146.189, "L": 131.174, "M": 149.208, "N": 132.119, "P": 115.132, "Q": 146.146, "R": 174.203, "S": 105.093, "T": 119.119, "V": 117.148, "W": 204.228, "Y": 181.191 } @@ -501,7 +657,7 @@ def molecular_weight(sequence: str) -> pd.DataFrame: ############################## Charge Distribution ######################################## -def charge_distribution(sequence: str, ph: float = 7.4) -> pd.DataFrame: +def charge_distribution(sequence: str, ph: float = 7.4, pka_scale: str = "protpy") -> pd.DataFrame: """ Calculate Charge Distribution of a protein sequence at a given pH. Positive charge is contributed by Lys (K), Arg (R) and His (H); negative charge by @@ -517,6 +673,9 @@ def charge_distribution(sequence: str, ph: float = 7.4) -> pd.DataFrame: protein sequence. :ph: float (default=7.4) pH at which to evaluate charges. + :pka_scale: str (default="protpy") + pKa value set for K, R, H, D and E: "protpy", "emboss", "bjellqvist" or + "ipc_protein" (see ``isoelectric_point``). Termini are not included. Returns ======= @@ -529,6 +688,8 @@ def charge_distribution(sequence: str, ph: float = 7.4) -> pd.DataFrame: #pKa values for charged residues pka_charged_ = {"K": 10.5, "R": 12.5, "H": 6.0, "D": 3.9, "E": 4.1} + if pka_scale != "protpy": + pka_charged_ = {aa: _pka_set(pka_scale)[aa] for aa in pka_charged_} #compute positive charge (K, R, H) using Henderson-Hasselbalch pos = sum(sequence.count(aa) / (1 + 10 ** (ph - pka_charged_[aa])) for aa in ("K", "R", "H")) @@ -656,9 +817,8 @@ def kmer_composition(sequence: str, k: int = 2) -> pd.DataFrame: #validate input protein sequence sequence = _validate_sequence(sequence) - #validate k; default to 2 if invalid; raise error for impractically large k - if not isinstance(k, int) or k < 1: - k = 2 + #validate k; raise error for impractically large k + k = _validate_int("k", k, 1) if k > 4: raise ValueError( f"k={k} would generate {20**k:,} columns (20^k). Maximum supported k is 4." @@ -668,11 +828,11 @@ def kmer_composition(sequence: str, k: int = 2) -> pd.DataFrame: all_kmers = ["".join(p) for p in itertools.product(amino_acids, repeat=k)] total = len(sequence) - k + 1 + #count every (overlapping) k-mer in one pass, divide by total k-mer count + counts = Counter(sequence[i:i+k] for i in range(max(total, 0))) kmer_comp = {} - #count each k-mer occurrence, divide by total k-mer count for kmer in all_kmers: - cnt = sum(1 for i in range(total) if sequence[i:i+k] == kmer) - kmer_comp[kmer] = round(cnt / total * 100, 3) if total > 0 else 0.0 + kmer_comp[kmer] = round(counts[kmer] / total * 100, 3) if total > 0 else 0.0 kmer_composition_df = pd.DataFrame([list(kmer_comp.values())], columns=list(kmer_comp.keys())) @@ -696,47 +856,102 @@ def kmer_composition(sequence: str, k: int = 2) -> pd.DataFrame: ] } -def reduced_alphabet_composition(sequence: str, alphabet_size: int = 6) -> pd.DataFrame: +def reduced_alphabet_composition(sequence: str, alphabet_size: int = 6, alphabet: str | None = None, + k: int = 1) -> pd.DataFrame: """ Calculate Reduced Alphabet Composition of a protein sequence. The 20 standard amino acids are clustered into a smaller alphabet of physicochemically similar - groups. The fraction of residues belonging to each group is returned [1]. + groups. The percentage of residues belonging to each group is returned [1]. Supported alphabet sizes: 2, 3, 4, 6 (default=6). Invalid sizes are reset to 6. + A named alphabet can be used instead with ``alphabet``. ``alphabet="ifeature5"`` + (aliphatic GAVLMI, aromatic FYW, positive KRH, negative DE, uncharged STCPNQ) + gives grouped amino acid composition (GAAC, k=1, 1 x 5), grouped dipeptide + composition (GDPC, k=2, 1 x 25) and grouped tripeptide composition (GTPC, + k=3, 1 x 125) [21]. The cluster profiles "cp13", "cp14", "cp19" and "cp20" [24] + are also accepted. With k > 1 overlapping k-mers of groups are counted and + divided by (L - k + 1): + + GkPC(g1..gk) = N(g1..gk) / (L - k + 1) x 100 + Parameters ========== :sequence: str protein sequence. :alphabet_size: int (default=6) - number of amino acid groups (2, 3, 4 or 6). + number of amino acid groups (2, 3, 4 or 6); ignored when ``alphabet`` is set. + :alphabet: str | None (default=None) + named alphabet: "ifeature5", "cp13", "cp14", "cp19" or "cp20". + :k: int (default=1) + group k-mer length, 1-3. Returns ======= :reduced_alphabet_df: pd.DataFrame - pandas DataFrame of shape 1 x alphabet_size. + pandas DataFrame of shape 1 x G^k, where G is the number of groups. Values + are percentages. """ #validate input protein sequence sequence = _validate_sequence(sequence) + k = _validate_int("k", k, 1) + if k > 3: + raise ValueError(f"k must be 1, 2 or 3, got {k}.") + + if alphabet is None: + #default to 6 if an unsupported size is requested + if alphabet_size not in reduced_alphabets_: + alphabet_size = 6 + groups = [(str(idx + 1), group) for idx, group in enumerate(reduced_alphabets_[alphabet_size])] + prefix = "ReducedAlphabet" + else: + groups = _named_alphabet(alphabet) + prefix = {1: "GAAC", 2: "GDPC", 3: "GTPC"}[k] if alphabet == "ifeature5" else f"RA_{alphabet}" - #default to 6 if an unsupported size is requested - if alphabet_size not in reduced_alphabets_: - alphabet_size = 6 - - groups = reduced_alphabets_[alphabet_size] n = len(sequence) - values = [] - col_names = [] - #compute fractional composition for each group - for idx, group in enumerate(groups): - freq = round(sum(1 for aa in sequence if aa in group) / n * 100, 3) - values.append(freq) - col_names.append(f"ReducedAlphabet_{idx + 1}") + if k == 1 and alphabet is None: + values = [] + col_names = [] + #compute percentage composition for each group + for label, group in groups: + values.append(round(sum(1 for aa in sequence if aa in group) / n * 100, 3)) + col_names.append(f"{prefix}_{label}") + return pd.DataFrame([values], columns=col_names) + + mapping = {aa: label for label, group in groups for aa in group} + counts = _kmer_counts(sequence, k, mapping) + total = n - k + 1 + labels = [label for label, _ in groups] + combos = list(itertools.product(labels, repeat=k)) + values = [counts[c] / total * 100 if total > 0 else 0.0 for c in combos] + return _to_df(values, [f"{prefix}_" + ".".join(c) for c in combos]) + + +def _named_alphabet(alphabet: str) -> list[tuple[str, str]]: + """ + Get the groups of a named reduced amino acid alphabet from the + reduced_alphabets.json data file. The available alphabets are the 5-group + iFeature alphabet (ifeature5) [21] and the cp13, cp14, cp19 and cp20 cluster + profiles [24]. - reduced_alphabet_df = pd.DataFrame([values], columns=col_names) + Parameters + ========== + :alphabet: str + name of the reduced alphabet: "ifeature5", "cp13", "cp14", "cp19" or "cp20". - return reduced_alphabet_df + Returns + ======= + :groups: list + list of (label, residues) tuples, one per group of the alphabet. + """ + valid = ["ifeature5", "cp13", "cp14", "cp19", "cp20"] + if alphabet not in valid: + raise ValueError(f"Invalid alphabet '{alphabet}'. Valid alphabets are: {valid}.") + values = _load_json_table("reduced_alphabets.json", alphabet)["values"] + if isinstance(values, dict): + return list(values.items()) + return [(group, group) for group in values] ############################## Motif Composition ########################################## @@ -748,7 +963,7 @@ def reduced_alphabet_composition(sequence: str, alphabet_size: int = 6) -> pd.Da "CxxC_zinc_finger": r"C.{2}C", "CAAX_prenylation": r"C[ACFILMVWY]{2}[ACEILMQV]$", "cAMP_PKA": r"RR.S", - "dileucine_sorting": r"[DE]xxxL[LI]$", + "dileucine_sorting": r"[DE]...L[LI]", "PEST_region": r"P.{1,10}[ESD].{1,10}T" } @@ -824,13 +1039,13 @@ def _aa_class(aa): return "Charged" total = len(sequence) - 1 + counts = Counter(sequence[k:k+2] for k in range(total)) pair_comp = {} #build the 400 annotated columns and compute frequencies for i in amino_acids: for j in amino_acids: label = f"{i}{j}_{_aa_class(i)}-{_aa_class(j)}" - count = sum(1 for k in range(total) if sequence[k] == i and sequence[k+1] == j) - pair_comp[label] = round(count / total * 100, 3) if total > 0 else 0.0 + pair_comp[label] = round(counts[i + j] / total * 100, 3) if total > 0 else 0.0 pair_composition_df = pd.DataFrame([list(pair_comp.values())], columns=list(pair_comp.keys())) @@ -878,7 +1093,7 @@ def aliphatic_index(sequence: str) -> pd.DataFrame: ############################### Extinction Coefficient #################################### -def extinction_coefficient(sequence: str) -> pd.DataFrame: +def extinction_coefficient(sequence: str, absorbance: bool = False) -> pd.DataFrame: """ Calculate the molar Extinction Coefficient of a protein sequence at 280 nm. The coefficient is derived from the number of Trp (W), Tyr (Y) and @@ -889,15 +1104,20 @@ def extinction_coefficient(sequence: str) -> pd.DataFrame: where SS is the number of disulfide bonds (Cys_count // 2) for the oxidised form, and 0 for the reduced form. Both are returned. + With ``absorbance=True`` the A280 of a 1 g/L (0.1%) solution is added, + Abs 0.1% = EC / MW, for both forms (Pace et al. 1995, Gill & von Hippel 1989). + Parameters ========== :sequence: str protein sequence. + :absorbance: bool (default=False) + also return ``Abs01_Reduced`` and ``Abs01_Oxidized``. Returns ======= :extinction_coefficient_df: pd.DataFrame - pandas DataFrame of shape 1 x 2 with columns + pandas DataFrame of shape 1 x 2 (1 x 4 with ``absorbance``) with columns ``ExtCoeff_Reduced`` and ``ExtCoeff_Oxidized`` (M⁻¹cm⁻¹). """ #validate input protein sequence @@ -917,25 +1137,30 @@ def extinction_coefficient(sequence: str) -> pd.DataFrame: columns=["ExtCoeff_Reduced", "ExtCoeff_Oxidized"] ) + if absorbance: + mw = molecular_weight(sequence)["MolecularWeight"].iloc[0] + extinction_coefficient_df["Abs01_Reduced"] = round(ec_reduced / mw, 3) + extinction_coefficient_df["Abs01_Oxidized"] = round(ec_oxidized / mw, 3) + return extinction_coefficient_df ################################ Boman Index ############################################## -#solubility scale from Boman (2003) [18] +#side-chain solubility values (Radzicka & Wolfenden) used by Boman (2003) [18] boman_scale_ = { - "A": 2.1, "C": -0.7, "D": -3.5, "E": -3.5, "F": 2.8, "G": 2.1, - "H": -3.2, "I": 4.5, "K": -3.9, "L": 4.5, "M": 1.9, "N": -4.8, - "P": -1.6, "Q": -3.5, "R": -0.8, "S": -0.8, "T": -0.7, "V": 4.2, - "W": -0.9, "Y": -1.3 + "A": 1.81, "C": 1.28, "D": -8.72, "E": -6.81, "F": 2.98, "G": 0.94, + "H": -4.66, "I": 4.92, "K": -5.55, "L": 4.92, "M": 2.35, "N": -6.64, + "P": 0.00, "Q": -5.54, "R": -14.92, "S": -3.40, "T": -2.57, "V": 4.04, + "W": 2.33, "Y": -0.14 } def boman_index(sequence: str) -> pd.DataFrame: """ Calculate the Boman Index (potential protein interaction index) for a protein sequence. The index is the sum of the solubility values of - all residues divided by the sequence length [18]: + all residues divided by the sequence length, negated [18]: - BI = sum(solubility(aa_i)) / N + BI = -sum(solubility(aa_i)) / N Higher values indicate a greater tendency to bind other proteins or cell membranes (e.g. antimicrobial peptides). Values >2.48 suggest @@ -954,8 +1179,8 @@ def boman_index(sequence: str) -> pd.DataFrame: #validate input protein sequence sequence = _validate_sequence(sequence) - #sum solubility values and divide by sequence length - bi = round(sum(boman_scale_[aa] for aa in sequence) / len(sequence), 3) + #negated mean of the solubility values + bi = round(-sum(boman_scale_[aa] for aa in sequence) / len(sequence), 3) boman_index_df = pd.DataFrame([[bi]], columns=["BomanIndex"]) @@ -963,13 +1188,13 @@ def boman_index(sequence: str) -> pd.DataFrame: ############################### Aggregation Propensity #################################### -def aggregation_propensity(sequence: str, window: int = 5, hydrophobicity_threshold: float = 2.0, charge_threshold: int = 1) -> pd.DataFrame: +def aggregation_propensity(sequence: str, window: int = 5, hydrophobicity_threshold: float = 2.0, charge_threshold: int = 0) -> pd.DataFrame: """ Calculate the Aggregation Propensity of a protein sequence. Aggregation- prone regions (APRs) are identified using a sliding window: a window is classified as an APR when its mean Kyte-Doolittle hydrophobicity exceeds ``hydrophobicity_threshold`` and the number of charged residues (D, E, K, - R) is below ``charge_threshold`` [8]. + R) is at most ``charge_threshold`` [8]. Parameters ========== @@ -979,7 +1204,7 @@ def aggregation_propensity(sequence: str, window: int = 5, hydrophobicity_thresh sliding window length in residues. :hydrophobicity_threshold: float (default=2.0) minimum mean Kyte-Doolittle hydrophobicity for an APR window. - :charge_threshold: int (default=1) + :charge_threshold: int (default=0) maximum number of charged residues (D, E, K, R) allowed in an APR. Returns @@ -992,6 +1217,8 @@ def aggregation_propensity(sequence: str, window: int = 5, hydrophobicity_thresh #validate input protein sequence sequence = _validate_sequence(sequence) + window = _validate_int("window", window, 1) + n = len(sequence) charged = frozenset("DEKR") #track which residue positions are covered by at least one APR @@ -1003,7 +1230,7 @@ def aggregation_propensity(sequence: str, window: int = 5, hydrophobicity_thresh seg = sequence[i:i + window] avg_hydro = sum(kyte_doolittle_[aa] for aa in seg) / window n_charged = sum(1 for aa in seg if aa in charged) - if avg_hydro >= hydrophobicity_threshold and n_charged < charge_threshold: + if avg_hydro >= hydrophobicity_threshold and n_charged <= charge_threshold: apr_count += 1 for j in range(i, i + window): covered[j] = True @@ -1066,7 +1293,8 @@ def shannon_entropy(sequence: str) -> pd.DataFrame: ############################### Hydrophobic Moment ######################################## -def hydrophobic_moment(sequence: str, window: int = 11, angle: float = 100) -> pd.DataFrame: +def hydrophobic_moment(sequence: str, window: int = 11, angle: float | str = 100, + scale: str | None = None) -> pd.DataFrame: """ Calculate the mean and maximum Hydrophobic Moment of a protein sequence. The hydrophobic moment measures the amphiphilicity of a helical segment — @@ -1077,9 +1305,15 @@ def hydrophobic_moment(sequence: str, window: int = 11, angle: float = 100) -> p mu = sqrt( (sum H_i * sin(i * angle))^2 + (sum H_i * cos(i * angle))^2 ) / window - where H_i is the Eisenberg hydrophobicity of residue i and angle is in - degrees. Default angle of 100° corresponds to an alpha-helix; 160° is - commonly used for beta-strands. + where H_i is the hydrophobicity of residue i and angle is in degrees. + Default angle of 100° corresponds to an alpha-helix; 160° is used for + beta-strands (Eisenberg et al. 1984). + + By default H is protpy's normalised hydrophobicity table (A = 0.62), which is + the normalised consensus scale of Eisenberg, Schwarz, Komarony & Wall (1984, + J Mol Biol 179:125-142) as listed by ExPASy ProtScale. ``scale`` selects + another single-value scale, e.g. "eisenberg" (AAindex EISD840101, Eisenberg + 1984 Annu Rev Biochem, A = 0.25), "kyte_doolittle" or any AAindex1 code. Parameters ========== @@ -1087,8 +1321,10 @@ def hydrophobic_moment(sequence: str, window: int = 11, angle: float = 100) -> p protein sequence. :window: int (default=11) sliding window length in residues. - :angle: float (default=100) - residue rotation angle in degrees (100° = alpha-helix). + :angle: float | str (default=100) + residue rotation angle in degrees, or "helix" (100) / "sheet" (160). + :scale: str | None (default=None) + hydrophobicity scale; None uses protpy's default table. Returns ======= @@ -1099,6 +1335,19 @@ def hydrophobic_moment(sequence: str, window: int = 11, angle: float = 100) -> p #validate input protein sequence sequence = _validate_sequence(sequence) + window = _validate_int("window", window, 1) + if isinstance(angle, str): + if angle not in ("helix", "sheet"): + raise ValueError(f"angle must be a number, 'helix' or 'sheet', got '{angle}'.") + angle = {"helix": 100, "sheet": 160}[angle] + if scale is None: + h_scale = hydrophobicity_ + else: + values = _property_matrix("".join(amino_acids), scale) + if values.shape[1] != 1: + raise ValueError(f"Scale '{scale}' is multi-dimensional; use a single-value scale.") + h_scale = dict(zip(amino_acids, values[:, 0])) + n = len(sequence) #use full sequence as single window when shorter than default window effective_window = min(window, n) @@ -1108,8 +1357,8 @@ def hydrophobic_moment(sequence: str, window: int = 11, angle: float = 100) -> p #compute hydrophobic moment for each window position using Eisenberg scale for i in range(n - effective_window + 1): seg = sequence[i:i + effective_window] - hsin = sum(hydrophobicity_[aa] * math.sin(j * angle_rad) for j, aa in enumerate(seg)) - hcos = sum(hydrophobicity_[aa] * math.cos(j * angle_rad) for j, aa in enumerate(seg)) + hsin = sum(h_scale[aa] * math.sin(j * angle_rad) for j, aa in enumerate(seg)) + hcos = sum(h_scale[aa] * math.cos(j * angle_rad) for j, aa in enumerate(seg)) mu = math.sqrt(hsin ** 2 + hcos ** 2) / effective_window moments.append(mu) @@ -1159,13 +1408,11 @@ def pseudo_amino_acid_composition(sequence: str, lamda: int = 30, weight: float #validate input protein sequence sequence = _validate_sequence(sequence) - #set lamda to its default value of 30 if <0, or > sequence len or not an int - if ((lamda < 0) or (lamda > len(sequence)) or not isinstance(lamda, int)): - lamda = 30 - - #validate weight, set default weight of 0.05 if invalid value input - if ((weight < 0) or not (isinstance(weight, (float, int)))): - weight = 0.05 + #validate lamda & weight; lamda falls back to its default of 30 when > sequence length + lamda = _validate_int("lamda", lamda) + if lamda > len(sequence): + lamda = 30 + weight = _validate_weight(weight) #normalize None to empty list before further processing if properties is None: @@ -1185,7 +1432,9 @@ def pseudo_amino_acid_composition(sequence: str, lamda: int = 30, weight: float for prop in properties: if (prop not in aaindex1.record_codes()): raise ValueError(f"Accession number not found in aaindex1: {prop}.") - property_values.append(aaindex1[prop].values) + #keep only the 20 canonical amino acids (drops the '-' gap entry) + property_values.append( + {aa: v for aa, v in aaindex1[prop].values.items() if aa in amino_acids}) psuedo_aac = {} @@ -1235,15 +1484,23 @@ def sequence_order_correlation_factor(sequence: str, k: int = 1, properties: lis # default to empty list if not provided (avoid mutable default argument) if properties is None: properties = [] + + # If there are no valid residue pairs for the requested gap, return 0.0. + if len(sequence) <= k: + return 0.0 + + #normalize each property once rather than for every residue pair + normalized_props = [normalize_property(prop) for prop in properties] correlation_factor = [] - #iterate through sequence using correlation function to calculate sequence order - #between its amino acids with gap=k + #iterate through sequence calculating the correlation (as in correlation_function) + #between its amino acids with gap=k for i in range(len(sequence) - k): aa1 = sequence[i] aa2 = sequence[i + k] - correlation_factor.append(correlation_function(aa1, aa2, properties)) - + theta = sum(math.pow(norm[aa1] - norm[aa2], 2) for norm in normalized_props) + correlation_factor.append(round(theta / len(normalized_props), 3)) + #round array of correlation factors correlation_factor_ = round(sum(correlation_factor) / (len(sequence) - k), 3) @@ -1300,8 +1557,13 @@ def normalize_property(properties: dict[str, float]) -> dict[str, float]: dict of normalized property values. """ vals = list(properties.values()) + if not vals: + return {} + mean = sum(vals) / len(vals) std = _std(vals, mean, ddof=0) + if std == 0: + return {aa: 0.0 for aa, _ in properties.items()} return {aa: (v - mean) / std for aa, v in properties.items()} def _std(prop: list[float], mean: float, ddof: int = 1) -> float: @@ -1349,7 +1611,7 @@ def amphiphilic_pseudo_amino_acid_composition(sequence: str, lamda: int = 30, we rank of correlation. Number of calculable descriptors depends on lambda value. :weight: float weighting factor. - :properties: list[dict] | None (default=None → [hydrophobicity_, hydrophilicity_]) + :properties: list[dict] | None (default=None → [``hydrophobicity_``, ``hydrophilicity_``]) list of property dicts mapping each amino acid to a float value. When None the default hydrophobicity and hydrophilicity scales are used. @@ -1367,13 +1629,11 @@ def amphiphilic_pseudo_amino_acid_composition(sequence: str, lamda: int = 30, we #validate input protein sequence sequence = _validate_sequence(sequence) - #set lamda to its default value of 30 if <0, or > sequence len or not an int - if ((lamda < 0) or (lamda > len(sequence)) or not isinstance(lamda, int)): + #validate lamda & weight; lamda falls back to its default of 30 when > sequence length + lamda = _validate_int("lamda", lamda) + if lamda > len(sequence): lamda = 30 - - #validate weight, set default weight of 0.5 if invalid value input - if ((weight < 0) or not (isinstance(weight, (float, int)))): - weight = 0.5 + weight = _validate_weight(weight) #cast properties to list if not isinstance(properties, list): @@ -1423,7 +1683,7 @@ def amphiphilic_sequence_order_correlation_factor(sequence: str, k: int = 1, protein sequence. :k: int (default=1) gap between amino acids in the sequence. - :properties: list[dict] | None (default=None → [hydrophobicity_, hydrophilicity_]) + :properties: list[dict] | None (default=None → [``hydrophobicity_``, ``hydrophilicity_``]) list of property dicts. Pre-normalized once per call. Returns @@ -1434,6 +1694,11 @@ def amphiphilic_sequence_order_correlation_factor(sequence: str, k: int = 1, if properties is None: properties = [hydrophobicity_, hydrophilicity_] + #no residue pairs for the requested gap: every correlation factor is 0 + denom = len(sequence) - k + if denom <= 0: + return [0.0] * len(properties) + #pre-normalize all properties once rather than inside every pair evaluation normalized_props = [normalize_property(prop) for prop in properties] n_props = len(properties) @@ -1445,7 +1710,6 @@ def amphiphilic_sequence_order_correlation_factor(sequence: str, k: int = 1, for p_idx, norm in enumerate(normalized_props): accumulators[p_idx].append(round(norm[aa1] * norm[aa2], 3)) - denom = len(sequence) - k return [round(sum(acc) / denom, 3) for acc in accumulators] def amphiphilic_correllation_function(Ri: str = "S", Rj: str = "D", @@ -1472,4 +1736,341 @@ def amphiphilic_correllation_function(Ri: str = "S", Rj: str = "D", if normalized_props is None: normalized_props = [normalize_property(hydrophobicity_), normalize_property(hydrophilicity_)] - return [round(prop[Ri] * prop[Rj], 3) for prop in normalized_props] \ No newline at end of file + return [round(prop[Ri] * prop[Rj], 3) for prop in normalized_props] +####################### Composition of k-Spaced Amino Acid Pairs ###################### + +def cksaap(sequence: str, gap: int = 5) -> pd.DataFrame: + """ + Calculate Composition of k-Spaced Amino Acid Pairs (CKSAAP) of protein + sequence. CKSAAP describes the frequency of each amino acid pair separated + by g residues within a protein sequence, for each gap g in the range + 0..gap. With 400 possible amino acid pairs per gap this creates a + 400 * (gap+1) Dimensional vector, calculated as: + + CKSAAP(XY, g) = N_g(XY) / (L - g - 1) * 100 + + where CKSAAP(XY, g) is the composition of the amino acid pair XY with g + residues between X and Y, N_g(XY) is the number of such pairs in the sequence + and L is the length of the sequence [20]. A gap of 0 is the same as the + dipeptide composition. A gap with no pairs (L - g - 1 <= 0) gives values + of 0. + + Parameters + ========== + :sequence: str + protein sequence. + :gap: int (default=5) + maximum number of residues between the two amino acids of a pair. + + Returns + ======= + :cksaap_df: pd.DataFrame + pandas dataframe of CKSAAP values for protein sequence. Dataframe will + be of the shape 1 x 400 * (gap+1), where 400 * (gap+1) is the number of + features calculated from the descriptor (400 amino acid pairs for each gap) + and 1 is the input sequence. By default, the shape will be 1 x 2400. Column + names are of the form CKSAAP_XY_gapg, e.g. CKSAAP_AC_gap2. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + gap = _validate_int("gap", gap) + + pairs = list(itertools.product(amino_acids, repeat=2)) + values, columns = [], [] + for g in range(gap + 1): + counts = _gapped_pair_counts(sequence, g) + total = len(sequence) - g - 1 + values += [counts[p] / total * 100 if total > 0 else 0.0 for p in pairs] + columns += [f"CKSAAP_{x}{y}_gap{g}" for x, y in pairs] + return _to_df(values, columns) + +################# Composition of k-Spaced Amino Acid Group Pairs ####################### + +def cksaagp(sequence: str, gap: int = 5) -> pd.DataFrame: + """ + Calculate Composition of k-Spaced Amino Acid Group Pairs (CKSAAGP) of protein + sequence. CKSAAGP is the same as CKSAAP but the amino acids are first mapped + to 5 groups: aliphatic (GAVLMI), aromatic (FYW), positive charge (KRH), + negative charge (DE) and uncharged (STCPNQ). With 25 possible group pairs + per gap this creates a 25 * (gap+1) Dimensional vector, calculated as: + + CKSAAGP(G1G2, g) = N_g(G1G2) / (L - g - 1) * 100 + + where CKSAAGP(G1G2, g) is the composition of the group pair G1G2 with g + residues between them, N_g(G1G2) is the number of such pairs in the sequence + and L is the length of the sequence [21]. The 25 values for each gap sum to 100. + + Parameters + ========== + :sequence: str + protein sequence. + :gap: int (default=5) + maximum number of residues between the two amino acids of a pair. + + Returns + ======= + :cksaagp_df: pd.DataFrame + pandas dataframe of CKSAAGP values for protein sequence. Dataframe will + be of the shape 1 x 25 * (gap+1), where 25 * (gap+1) is the number of + features calculated from the descriptor (25 group pairs for each gap) and + 1 is the input sequence. By default, the shape will be 1 x 150. Column + names are of the form CKSAAGP_G1.G2_gapg, e.g. CKSAAGP_aliphatic.aromatic_gap0. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + gap = _validate_int("gap", gap) + + groups = _named_alphabet("ifeature5") + mapping = {aa: label for label, residues in groups for aa in residues} + pairs = list(itertools.product([label for label, _ in groups], repeat=2)) + values, columns = [], [] + for g in range(gap + 1): + counts = _gapped_pair_counts(sequence, g, mapping) + total = len(sequence) - g - 1 + values += [counts[p] / total * 100 if total > 0 else 0.0 for p in pairs] + columns += [f"CKSAAGP_{a}.{b}_gap{g}" for a, b in pairs] + return _to_df(values, columns) + +############################ Dipeptide Deviation from Expected Mean ###################### + +def dde(sequence: str) -> pd.DataFrame: + """ + Calculate Dipeptide Deviation from Expected Mean (DDE) of protein sequence. + DDE compares the observed dipeptide composition of a protein sequence with + the composition expected from the number of codons that code for each amino + acid. For each of the 400 dipeptides it is calculated as: + + Dc(XY) = N(XY) / (L - 1) + + Tm(XY) = (C_X / 61) * (C_Y / 61) + + Tv(XY) = Tm(XY) * (1 - Tm(XY)) / (L - 1) + + DDE(XY) = (Dc(XY) - Tm(XY)) / sqrt(Tv(XY)) + + where Dc(XY) is the observed composition of dipeptide XY, N(XY) is the + number of XY dipeptides, L is the length of the sequence, Tm(XY) and Tv(XY) + are the theoretical mean and variance, and C_X and C_Y are the number of + codons for amino acids X and Y out of the 61 sense codons [22]. Unlike the + other composition descriptors, DDE values are not percentages. + + Parameters + ========== + :sequence: str + protein sequence, at least 2 residues long. + + Returns + ======= + :dde_df: pd.DataFrame + pandas dataframe of DDE values for protein sequence. Dataframe will be of + the shape 1 x 400, where 400 is the number of features calculated from the + descriptor (20^2 for the 20 canonical amino acids) and 1 is the input + sequence. Column names are of the form DDE_XY. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + if len(sequence) < 2: + raise ValueError("DDE needs a sequence of at least 2 residues.") + + codons = _load_json_table("residue_tables.json", "codon_counts")["values"] + counts = _gapped_pair_counts(sequence, 0) + total = len(sequence) - 1 + values, columns = [], [] + for x, y in itertools.product(amino_acids, repeat=2): + tm = (codons[x] / 61) * (codons[y] / 61) + tv = tm * (1 - tm) / total + values.append((counts[(x, y)] / total - tm) / math.sqrt(tv)) + columns.append(f"DDE_{x}{y}") + return _to_df(values, columns) + +################################ Adaptive Skip Dipeptide Composition ##################### + +def asdc(sequence: str) -> pd.DataFrame: + """ + Calculate Adaptive Skip Dipeptide Composition (ASDC) of protein sequence. + ASDC is a modified dipeptide composition that counts every ordered pair of + amino acids (s_i, s_j), where i < j, at all gaps along the sequence, rather + than just adjacent pairs, calculated as: + + ASDC(XY) = sum_g N_g(XY) / sum_g sum_XY N_g(XY) * 100 + + where ASDC(XY) is the composition of the amino acid pair XY, N_g(XY) is the + number of XY pairs separated by g residues, and the denominator is the total + number of ordered pairs in the sequence, L(L-1)/2, where L is the length of + the sequence [23]. The 400 values sum to 100. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :asdc_df: pd.DataFrame + pandas dataframe of ASDC values for protein sequence. Dataframe will be of + the shape 1 x 400, where 400 is the number of features calculated from the + descriptor (20^2 for the 20 canonical amino acids) and 1 is the input + sequence. Column names are of the form ASDC_XY. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + + index = {aa: i for i, aa in enumerate(amino_acids)} + #integer matmul: exact counts, and avoids spurious BLAS float warnings + onehot = np.zeros((len(sequence), 20), dtype=np.int64) + onehot[np.arange(len(sequence)), [index[aa] for aa in sequence]] = 1 + #rows of `before` count each residue type strictly before position j + before = np.cumsum(onehot, axis=0) - onehot + pair_counts = before.T @ onehot + total = len(sequence) * (len(sequence) - 1) / 2 + values = (pair_counts / total * 100).ravel() if total else np.zeros(400) + return _to_df(list(values), [f"ASDC_{x}{y}" for x, y in itertools.product(amino_acids, repeat=2)]) + +#################################### Distance Pair ####################################### + +def distance_pair(sequence: str, rng: int = 3, alphabet: str = "cp14") -> pd.DataFrame: + """ + Calculate Distance Pair (DP) descriptor of protein sequence. The descriptor + incorporates amino acid distance pairs and a reduced amino acid alphabet into + the general pseudo amino acid composition, as used in iDNA-Prot|dis [24, 25]. + The amino acids are first mapped to the clusters of a cluster profile cp(n), + and the descriptor is the composition of the clusters followed by the + composition of cluster pairs at each distance d = 1..rng: + + DP(c) = N(c) / L * 100 + + DP(c1c2, d) = N_d(c1c2) / (L - d) * 100 + + where N(c) is the number of residues in cluster c, N_d(c1c2) is the number of + residue pairs (s_i, s_i+d) whose residues are in clusters c1 and c2, and L is + the length of the sequence. A distance of 1 is the nearest-neighbour pair. + The available cluster profiles are cp13, cp14, cp19 and cp20 (no reduction). + The published cp(14) lists E twice; the extra E in the first cluster is + dropped, as it is the only reading that gives 14 clusters. + + Parameters + ========== + :sequence: str + protein sequence. + :rng: int (default=3) + maximum distance between the two residues of a pair. + :alphabet: str (default="cp14") + cluster profile to use: "cp13", "cp14", "cp19" or "cp20". + + Returns + ======= + :distance_pair_df: pd.DataFrame + pandas dataframe of DP values for protein sequence. Dataframe will be of + the shape 1 x (G + G^2 * rng), where G is the number of clusters, + G + G^2 * rng is the number of features calculated from the descriptor and + 1 is the input sequence. By default (cp14, rng=3) the shape will be 1 x 602. + Column names are of the form DP_c and DP_c1.c2_dd, where c are the cluster + residues, e.g. DP_IMV and DP_IMV.L_d1. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + rng = _validate_int("rng", rng) + if alphabet not in ("cp13", "cp14", "cp19", "cp20"): + raise ValueError(f"Invalid alphabet '{alphabet}'. Valid alphabets are: ['cp13', 'cp14', 'cp19', 'cp20'].") + + labels = [label for label, _ in _named_alphabet(alphabet)] + mapping = {aa: label for label in labels for aa in label} + comp = Counter(mapping[aa] for aa in sequence) + values = [comp[c] / len(sequence) * 100 for c in labels] + columns = [f"DP_{c}" for c in labels] + pairs = list(itertools.product(labels, repeat=2)) + for d in range(1, rng + 1): + counts = _gapped_pair_counts(sequence, d - 1, mapping) + total = len(sequence) - d + values += [counts[p] / total * 100 if total > 0 else 0.0 for p in pairs] + columns += [f"DP_{a}.{b}_d{d}" for a, b in pairs] + return _to_df(values, columns) + +#################################### Natural Vector ###################################### + +def natural_vector(sequence: str) -> pd.DataFrame: + """ + Calculate Natural Vector (NV) of protein sequence. The natural vector + describes the number of each amino acid in a protein sequence together with + the mean and the normalized central moment of their positions, calculated as: + + n_k = number of occurrences of amino acid k + + mu_k = sum of the positions of k / n_k + + D2_k = sum of (position of k - mu_k)^2 / (n_k * L) + + where positions start at 1, L is the length of the sequence and k is each of + the 20 canonical amino acids [26]. mu_k and D2_k are 0 when amino acid k is + not in the sequence. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :natural_vector_df: pd.DataFrame + pandas dataframe of NV values for protein sequence. Dataframe will be of + the shape 1 x 60, where 60 is the number of features calculated from the + descriptor (3 values for each of the 20 amino acids) and 1 is the input + sequence. Column names are of the form NV_n_X, NV_mu_X and NV_D2_X. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + + L = len(sequence) + positions = {aa: [] for aa in amino_acids} + for i, aa in enumerate(sequence, start=1): + positions[aa].append(i) + n = [len(positions[aa]) for aa in amino_acids] + mu = [sum(positions[aa]) / len(positions[aa]) if positions[aa] else 0.0 for aa in amino_acids] + d2 = [sum((p - m) ** 2 for p in positions[aa]) / (len(positions[aa]) * L) if positions[aa] else 0.0 + for aa, m in zip(amino_acids, mu)] + columns = ([f"NV_n_{aa}" for aa in amino_acids] + [f"NV_mu_{aa}" for aa in amino_acids] + + [f"NV_D2_{aa}" for aa in amino_acids]) + return _to_df(n + mu + d2, columns) + +#################################### Atomic Composition ################################## + +def atomic_composition(sequence: str, subtract_water: bool = False) -> pd.DataFrame: + """ + Calculate Atomic Composition (ATC) of protein sequence. ATC describes the + percentage of carbon, hydrogen, nitrogen, oxygen and sulphur atoms in a + protein sequence, using the molecular formulas of the free amino acids, + calculated as: + + ATC(e) = N_e / (N_C + N_H + N_N + N_O + N_S) * 100 + + where ATC(e) is the atomic composition of element e and N_e is the number of + atoms of element e summed over all residues [27]. By default the formulas of + the free amino acids are summed without removing water, matching Pfeature + [27]. With subtract_water=True, one water molecule is removed for each of the + L - 1 peptide bonds, giving the formula of the peptide (e.g. "GG" -> C4H8N2O3). + + Parameters + ========== + :sequence: str + protein sequence. + :subtract_water: bool (default=False) + remove one H2O per peptide bond. + + Returns + ======= + :atomic_composition_df: pd.DataFrame + pandas dataframe of ATC values for protein sequence. Dataframe will be of + the shape 1 x 5, where 5 is the number of features calculated from the + descriptor (one per element) and 1 is the input sequence. Column names + are ATC_C, ATC_H, ATC_N, ATC_O and ATC_S. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + + formulas = _load_json_table("residue_tables.json", "atomic_formulas")["values"] + atoms = {e: sum(formulas[aa][e] for aa in sequence) for e in "CHNOS"} + if subtract_water: + atoms["H"] -= 2 * (len(sequence) - 1) + atoms["O"] -= len(sequence) - 1 + total = sum(atoms.values()) + return _to_df([atoms[e] / total * 100 for e in "CHNOS"], [f"ATC_{e}" for e in "CHNOS"]) diff --git a/protpy/conjoint_triad.py b/protpy/conjoint_triad.py index a91b2c6..e4153f2 100644 --- a/protpy/conjoint_triad.py +++ b/protpy/conjoint_triad.py @@ -4,6 +4,7 @@ from __future__ import annotations +from collections import Counter import pandas as pd """ @@ -11,9 +12,13 @@ ========== [1] J. Shen et al., “Predicting protein-protein interactions based only on sequences information,” Proc. Natl. Acad. Sci. U. S. A., vol. 104, no. 11, pp. 4337–4341, 2007. + https://doi.org/10.1073/pnas.0607879104 +[2] Chen, Z. et al. (2018). iFeature: a Python package and web server for features + extraction and selection from protein and peptide sequences. Bioinformatics, + 34(14), 2499-2502. https://doi.org/10.1093/bioinformatics/bty140 """ #list of amino acids -from ._constants import amino_acids, _validate_sequence +from ._constants import amino_acids, _validate_sequence, _validate_int #amino acid triads aa_triads = { @@ -26,7 +31,7 @@ 7: ["C"], } -def conjoint_triad(sequence: str) -> pd.DataFrame: +def conjoint_triad(sequence: str, gap: int = 0) -> pd.DataFrame: """ Calculate Conjoint Triad features (CTriad) for a protein sequence. The descriptor mainly considers neighbor relationships in protein sequences by encoding @@ -35,10 +40,17 @@ def conjoint_triad(sequence: str) -> pd.DataFrame: descriptor calculates 343 different features (7x7x7), with the output being of shape 1 x 343 for a sequence. + With ``gap > 0`` the k-spaced conjoint triad (KSCTriad) is returned: for each + g = 0..gap, triads (s_i, s_{i+g+1}, s_{i+2g+2}) are counted, giving an output + of shape 1 x 343·(gap+1) with columns suffixed ``_gap{g}`` [2]. Values are + raw counts, as for the default descriptor. + Parameters ========== :sequence: str protein sequence. + :gap: int (default=0) + maximum residue spacing for KSCTriad; 0 gives the original conjoint triad. Returns ======= @@ -49,6 +61,7 @@ def conjoint_triad(sequence: str) -> pd.DataFrame: """ #validate input protein sequence sequence = _validate_sequence(sequence) + gap = _validate_int("gap", gap) conjoint_triad = {} _aa_triads = {} @@ -63,12 +76,19 @@ def conjoint_triad(sequence: str) -> pd.DataFrame: for i in _aa_triads: protein_num = protein_num.replace(i, str(_aa_triads[i])) - #calculate 7 triads for amino acids - for i in range(1, 8): - for j in range(1, 8): - for k in range(1, 8): - temp = str(i) + str(j) + str(k) - conjoint_triad["conj_triad_" + temp] = protein_num.count(temp) + for g in range(gap + 1): + #count overlapping (g-spaced) triads with a sliding window (str.count misses overlaps, e.g. "111" in "11111") + step = g + 1 + triad_counts = Counter(protein_num[i] + protein_num[i+step] + protein_num[i+2*step] + for i in range(len(protein_num) - 2*step)) + suffix = f"_gap{g}" if gap > 0 else "" + + #calculate 7 triads for amino acids + for i in range(1, 8): + for j in range(1, 8): + for k in range(1, 8): + temp = str(i) + str(j) + str(k) + conjoint_triad["conj_triad_" + temp + suffix] = triad_counts[temp] #transform descriptor values to dataframe conjoint_triad_df = pd.DataFrame([list(conjoint_triad.values())], columns=list(conjoint_triad.keys())) diff --git a/protpy/ctd.py b/protpy/ctd.py index 7dd07c9..ae3d43a 100644 --- a/protpy/ctd.py +++ b/protpy/ctd.py @@ -11,6 +11,7 @@ #list of amino acids from ._constants import amino_acids, _validate_sequence +from ._helpers import _load_json_table """ References @@ -23,15 +24,18 @@ classification. Proteins: Structure, Function and Genetics, 1999, 35, 401-407. [3] Gromiha, M. M. (2010). Protein Sequence Analysis. In M. M. Gromiha (Ed.), Protein Bioinformatics (pp. 29–62). Elsevier. +[4] Chen, Z. et al. (2018). iFeature: a Python package and web server for features + extraction and selection from protein and peptide sequences. Bioinformatics, + 34(14), 2499-2502. https://doi.org/10.1093/bioinformatics/bty140 """ hydrophobicity = {"name": "hydrophobicity", "1": "RKEDQN", "2": "GASTPHY", "3": "CLVIMFW"} # '1' -> Polar; '2' -> Neutral, '3' -> Hydrophobicity -normalized_vdwv = {"name": "normalized_vdwv", "1": "GASTPD", "2": "NVEQIL", "3": "MHKFRYW"} +normalized_vdwv = {"name": "normalized_vdwv", "1": "GASTPDC", "2": "NVEQIL", "3": "MHKFRYW"} # '1' -> (0-2.78); '2' -> (2.95-4.0), '3' -> (4.03-8.08) -polarity = {"name": "polarity", "1": "LIFWCMVY", "2": "CPNVEQIL", "3": "KMHFRYW"} +polarity = {"name": "polarity", "1": "LIFWCMVY", "2": "PAGST", "3": "HQRKNED"} # '1' -> (4.9-6.2); '2' -> (8.0-9.2), '3' -> (10.4-13.0) charge = {"name": "charge", "1": "KR", "2": "ANCQGHILMFPSTWYV", "3": "DE"} @@ -57,6 +61,22 @@ "polarizability": polarizability } +#six extra hydrophobicity partitions used by iFeature's 13-property CTD [4]; kept out of +#ctd_properties so the default 7-property outputs are unchanged +ctd_properties_ifeature_extra = { + name: {"name": name, **groups} + for name, groups in _load_json_table("reduced_alphabets.json", "ctd_ifeature_extra")["values"].items() + } + +#property sets accepted by ctd_(property_set=...) +ctd_property_sets = { + "default": ctd_properties, + "ifeature13": {**ctd_properties, **ctd_properties_ifeature_extra}, + } + +#every property accepted by the single-property CTD functions +_all_ctd_properties = ctd_property_sets["ifeature13"] + def str_to_num(sequence: str, property: dict[str, str]) -> str: """ Convert sequences str to number from input physicochemical property. @@ -115,13 +135,13 @@ def ctd_composition(sequence: str, property: str = "hydrophobicity") -> pd.DataF sequence = _validate_sequence(sequence) #get closest matched CTD property - using cutoff of 0.8 - property_matches = get_close_matches(property, ctd_properties.keys(), cutoff=0.8) + property_matches = get_close_matches(property, _all_ctd_properties.keys(), cutoff=0.8) #raise error if no matching property found if (property_matches != []): - prop = ctd_properties[property_matches[0]] + prop = _all_ctd_properties[property_matches[0]] else: - raise ValueError(f"Invalid property '{property}'. Available properties are: {list(ctd_properties.keys())}.") + raise ValueError(f"Invalid property '{property}'. Available properties are: {list(_all_ctd_properties.keys())}.") #convert sequence to number seq = str_to_num(sequence, prop) @@ -166,26 +186,31 @@ def ctd_transition(sequence: str, property: str = "hydrophobicity") -> pd.DataFr sequence = _validate_sequence(sequence) #get closest matched CTD property - using cutoff of 0.8 - property_matches = get_close_matches(property, ctd_properties.keys(), cutoff=0.8) + property_matches = get_close_matches(property, _all_ctd_properties.keys(), cutoff=0.8) #raise error if no matching property found if (property_matches != []): - prop = ctd_properties[property_matches[0]] + prop = _all_ctd_properties[property_matches[0]] else: - raise ValueError(f"Invalid property '{property}'. Available properties are: {list(ctd_properties.keys())}.") + raise ValueError(f"Invalid property '{property}'. Available properties are: {list(_all_ctd_properties.keys())}.") #convert sequence to number seq = str_to_num(sequence, prop) ctd_transition_ = {} + #a single residue has no transitions + n_pairs = len(sequence) - 1 + if n_pairs == 0: + n_pairs = 1 + #calculate descriptor values, append to ctd_transition_ dict ctd_transition_["CTD_T_12_" + prop["name"]] = round( - float(seq.count("12") + seq.count("21")) / (len(sequence)-1), 3) + float(seq.count("12") + seq.count("21")) / n_pairs, 3) ctd_transition_["CTD_T_13_" + prop["name"]] = round( - float(seq.count("13") + seq.count("31")) / (len(sequence)-1), 3) + float(seq.count("13") + seq.count("31")) / n_pairs, 3) ctd_transition_["CTD_T_23_" + prop["name"]] = round( - float(seq.count("23") + seq.count("32")) / (len(sequence)-1), 3) + float(seq.count("23") + seq.count("32")) / n_pairs, 3) #transform values and columns to DataFrame ctd_transition_df = pd.DataFrame([list(ctd_transition_.values())], columns=list(ctd_transition_.keys())) @@ -219,13 +244,13 @@ def ctd_distribution(sequence: str, property: str = "hydrophobicity") -> pd.Data sequence = _validate_sequence(sequence) #get closest matched CTD property - using cutoff of 0.8 - property_matches = get_close_matches(property, ctd_properties.keys(), cutoff=0.8) + property_matches = get_close_matches(property, _all_ctd_properties.keys(), cutoff=0.8) #raise error if no matching property found if (property_matches != []): - prop = ctd_properties[property_matches[0]] + prop = _all_ctd_properties[property_matches[0]] else: - raise ValueError(f"Invalid property '{property}'. Available properties are: {list(ctd_properties.keys())}.") + raise ValueError(f"Invalid property '{property}'. Available properties are: {list(_all_ctd_properties.keys())}.") #convert sequence to number seq = str_to_num(sequence, prop) @@ -271,7 +296,8 @@ def ctd_distribution(sequence: str, property: str = "hydrophobicity") -> pd.Data return ctd_distribution_df -def ctd_(sequence: str, property: str = "hydrophobicity", all_ctd: bool = True) -> pd.DataFrame: +def ctd_(sequence: str, property: str = "hydrophobicity", all_ctd: bool = True, + property_set: str = "default") -> pd.DataFrame: """ Calculate all Composition, Transition and Distribution (CTD) features of protein sequences. Composition is the number of amino acids of a particular property (e.g., hydrophobicity) @@ -294,8 +320,12 @@ def ctd_(sequence: str, property: str = "hydrophobicity", all_ctd: bool = True) protein sequence. :property: str (default="hydrophocity") physicochemical property name to use when calculating descriptor. - :ctd: bool + :all_ctd: bool (default=True) calculate all CTD descriptors and concatenate together. + :property_set: str (default="default") + property set used when ``all_ctd=True``: "default" (the 7 properties + above, 1 x 147) or "ifeature13" (adds the six AAindex hydrophobicity + partitions used by iFeature, 13 properties, 1 x 273) [4]. Returns ======= @@ -306,7 +336,9 @@ def ctd_(sequence: str, property: str = "hydrophobicity", all_ctd: bool = True) """ #validate input protein sequence sequence = _validate_sequence(sequence) - + if property_set not in ctd_property_sets: + raise ValueError(f"Invalid property_set '{property_set}'. Available sets are: {list(ctd_property_sets)}.") + #initialise ctd dataframes comp_df = pd.DataFrame() trans_df = pd.DataFrame() @@ -319,7 +351,7 @@ def ctd_(sequence: str, property: str = "hydrophobicity", all_ctd: bool = True) distr_df = ctd_distribution(sequence, property=property) else: #if using all calculable properties, calculate CTD descriptors for each property - for prop in ctd_properties: + for prop in ctd_property_sets[property_set]: comp = ctd_composition(sequence, property=prop) comp_df = pd.concat([comp_df, comp], axis=1) trans = ctd_transition(sequence, property=prop) diff --git a/protpy/data/README.md b/protpy/data/README.md index 0192133..b0ae0ae 100644 --- a/protpy/data/README.md +++ b/protpy/data/README.md @@ -1,8 +1,15 @@ -# Data required for protPy sequence order descriptors +# Data files used by protpy descriptors * `grantham-physicochemical-distance-matrix.json` - grantham distance score for each amino acid combination, used in the calculation of the sequence-order related protein descriptors [[1]](#references). * `schneider-werde-physicochemical-distance-matrix.json` - schneider-werde distance score for each amino acid combination, used in the calculation of the sequence-order related protein descriptors [[2]](#references). +Added in v1.5.0. Each file maps a table name to `{"name", "values", "reference", "doi", "source_note"}`; `source_note` records where and when every table was transcribed. + +* `aa_scales.json` - single-value hydrophobicity/property scales (Kyte-Doolittle, Hopp-Woods, Eisenberg, Wimley-White, Engelman, DomCut, EIIP, Vihinen flexibility, Parker, Janin) and the factor-scale families (z-scales 5, VHSE, Atchley, T-, ST-scales, ProtFP, FASGAI, BLOSUM indices, MS-WHIM). +* `reduced_alphabets.json` - iFeature 5-group alphabet, cp13/cp14/cp19/cp20 cluster profiles and the six extra iFeature CTD partitions. +* `residue_tables.json` - sense-codon counts, free amino-acid formulas, pKa sets (protpy, EMBOSS, Bjellqvist, IPC_protein) and the N-end rule half-life table. +* `structural_params.json` - AGGRESCAN a3v, TOP-IDP, order/disorder-promoting residues and the PLAAC model parameters. + References ---------- \[1\]: Grantham R. Amino acid difference formula to help explain protein evolution. Science. 1974 Sep 6;185(4154):862-4. doi: 10.1126/science.185.4154.862. PMID: 4843792.
diff --git a/protpy/data/aa_scales.json b/protpy/data/aa_scales.json new file mode 100644 index 0000000..1419328 --- /dev/null +++ b/protpy/data/aa_scales.json @@ -0,0 +1,1874 @@ +{ + "kyte_doolittle": { + "name": "Hydropathy index (Kyte-Doolittle, 1982)", + "values": { + "A": 1.8, + "C": 2.5, + "D": -3.5, + "E": -3.5, + "F": 2.8, + "G": -0.4, + "H": -3.2, + "I": 4.5, + "K": -3.9, + "L": 3.8, + "M": 1.9, + "N": -3.5, + "P": -1.6, + "Q": -3.5, + "R": -4.5, + "S": -0.8, + "T": -0.7, + "V": 4.2, + "W": -0.9, + "Y": -1.3 + }, + "reference": "Kyte, J. and Doolittle, R.F. 'A simple method for displaying the hydropathic character of a protein' J. Mol. Biol. 157, 105-132 (1982)", + "doi": "10.1016/0022-2836(82)90515-0", + "source_note": "AAindex1 record KYTJ820101 (Kawashima et al. 2008), which reproduces the published table; copied 2026-10-12." + }, + "hopp_woods": { + "name": "Hydrophilicity value (Hopp-Woods, 1981)", + "values": { + "A": -0.5, + "C": -1.0, + "D": 3.0, + "E": 3.0, + "F": -2.5, + "G": 0.0, + "H": -0.5, + "I": -1.8, + "K": 3.0, + "L": -1.8, + "M": -1.3, + "N": 0.2, + "P": 0.0, + "Q": 0.2, + "R": 3.0, + "S": 0.3, + "T": -0.4, + "V": -1.5, + "W": -3.4, + "Y": -2.3 + }, + "reference": "Hopp, T.P. and Woods, K.R. 'Prediction of protein antigenic determinants from amino acid sequecces' Proc. Natl. Acad. Sci. USA 78, 3824-3828 (1981)", + "doi": "10.1073/pnas.78.6.3824", + "source_note": "AAindex1 record HOPT810101 (Kawashima et al. 2008), which reproduces the published table; copied 2026-10-12." + }, + "eisenberg": { + "name": "Consensus normalized hydrophobicity scale (Eisenberg, 1984)", + "values": { + "A": 0.25, + "C": 0.04, + "D": -0.72, + "E": -0.62, + "F": 0.61, + "G": 0.16, + "H": -0.4, + "I": 0.73, + "K": -1.1, + "L": 0.53, + "M": 0.26, + "N": -0.64, + "P": -0.07, + "Q": -0.69, + "R": -1.76, + "S": -0.26, + "T": -0.18, + "V": 0.54, + "W": 0.37, + "Y": 0.02 + }, + "reference": "Eisenberg, D. 'Three-dimensional structure of membrane and surface proteins' Ann. Rev. Biochem. 53, 595-623 (1984) Original references: Eisenberg, D., Weiss, R.M., Terwilliger, T.C. and Wilcox, W. Faraday Symp. Chem. Soc. 17, 109-120 (1982) Eisenberg, D., Weiss, R.M. and Terwilliger, T.C. The hydrophobic moment detects periodicity in protein hydrophobicity Proc. Natl. Acad. Sci. USA 81, 140-144 (1984)", + "doi": "10.1146/annurev.bi.53.070184.003115", + "source_note": "AAindex1 record EISD840101 (Kawashima et al. 2008), which reproduces the published table; copied 2026-10-12." + }, + "wimley_white": { + "name": "Free energies of transfer of AcWl-X-LL peptides from bilayer interface to water (Wimley-White, 1996)", + "values": { + "A": 4.08, + "C": 4.49, + "D": 3.02, + "E": 2.23, + "F": 5.38, + "G": 4.24, + "H": 4.08, + "I": 4.52, + "K": 3.77, + "L": 4.81, + "M": 4.48, + "N": 3.83, + "P": 3.8, + "Q": 3.67, + "R": 3.91, + "S": 4.12, + "T": 4.11, + "V": 4.18, + "W": 6.1, + "Y": 5.19 + }, + "reference": "Wimley, W.C. and White, S. 'Experimentally determined hydrophobicity scale for proteins at membrane interfaces' Nature Structual biol. 3, 842-848 (1996)", + "doi": "10.1038/nsb1096-842", + "source_note": "AAindex1 record WIMW960101 (Kawashima et al. 2008), which reproduces the published table; copied 2026-10-12." + }, + "engelman": { + "name": "Hydrophobicity index (Engelman et al., 1986)", + "values": { + "A": -1.6, + "C": -2.0, + "D": 9.2, + "E": 8.2, + "F": -3.7, + "G": -1.0, + "H": 3.0, + "I": -3.1, + "K": 8.8, + "L": -2.8, + "M": -3.4, + "N": 4.8, + "P": 0.2, + "Q": 4.1, + "R": 12.3, + "S": -0.6, + "T": -1.2, + "V": -2.6, + "W": -1.9, + "Y": 0.7 + }, + "reference": "Engelman, D.M., Steitz, T.A. and Goldman, A. 'Identifying Nonpolar Transbilayer Helices in Amino Acid Sequences of Membrane Proteins' Ann.Rev.Biophys.Biophys.Chem. 15, 321-353 (1986)", + "doi": "10.1146/annurev.bb.15.060186.001541", + "source_note": "AAindex1 record ENGD860101 (Kawashima et al. 2008), which reproduces the published table; copied 2026-10-12." + }, + "domcut": { + "name": "Linker propensity index (Suyama-Ohara, 2003)", + "values": { + "A": -0.058, + "C": 0.447, + "D": 0.016, + "E": -0.128, + "F": 0.24, + "G": 0.331, + "H": 0.195, + "I": 0.06, + "K": -0.112, + "L": 0.138, + "M": 0.275, + "N": 0.027, + "P": -0.478, + "Q": -0.073, + "R": 0.0, + "S": -0.177, + "T": -0.163, + "V": -0.052, + "W": 0.564, + "Y": 0.322 + }, + "reference": "Suyama, M. and Ohara, O. 'DomCut: Prediction of inter-domain linker regions in amino acid sequences' Bioinformatics 19, 673-674 (2003)", + "doi": "10.1093/bioinformatics/btg031", + "source_note": "AAindex1 record SUYM030101 (Kawashima et al. 2008), which reproduces the published table; copied 2026-10-12." + }, + "eiip": { + "name": "Electron-ion interaction potential values (Cosic, 1994)", + "values": { + "A": 0.0373, + "C": 0.0829, + "D": 0.1263, + "E": 0.0058, + "F": 0.0946, + "G": 0.005, + "H": 0.0242, + "I": 0.0, + "K": 0.0371, + "L": 0.0, + "M": 0.0823, + "N": 0.0036, + "P": 0.0198, + "Q": 0.0761, + "R": 0.0959, + "S": 0.0829, + "T": 0.0941, + "V": 0.0057, + "W": 0.0548, + "Y": 0.0516 + }, + "reference": "Cosic, I. 'Macromolecular bioactivity: is it resonant interaction between macromolecules?--Theory and applications' IEEE Trans Biomed Eng. 41, 1101-1114 (1994) (values are cited from Protein Eng. 15:193-203)", + "doi": "10.1109/10.335859", + "source_note": "AAindex1 record COSI940101 (Kawashima et al. 2008), which reproduces the published table; copied 2026-10-12." + }, + "z_scales_5": { + "name": "z_scales_5", + "values": { + "A": [ + 0.24, + -2.32, + 0.6, + -0.14, + 1.3 + ], + "C": [ + 0.84, + -1.67, + 3.71, + 0.18, + -2.65 + ], + "D": [ + 3.98, + 0.93, + 1.93, + -2.46, + 0.75 + ], + "E": [ + 3.11, + 0.26, + -0.11, + -3.04, + -0.25 + ], + "F": [ + -4.22, + 1.94, + 1.06, + 0.54, + -0.62 + ], + "G": [ + 2.05, + -4.06, + 0.36, + -0.82, + -0.38 + ], + "H": [ + 2.47, + 1.95, + 0.26, + 3.9, + 0.09 + ], + "I": [ + -3.89, + -1.73, + -1.71, + -0.84, + 0.26 + ], + "K": [ + 2.29, + 0.89, + -2.49, + 1.49, + 0.31 + ], + "L": [ + -4.28, + -1.3, + -1.49, + 0.72, + 0.84 + ], + "M": [ + -2.85, + -0.22, + 0.47, + 1.94, + -0.98 + ], + "N": [ + 3.05, + 1.62, + 1.04, + -1.15, + 1.61 + ], + "P": [ + -1.66, + 0.27, + 1.84, + 0.7, + 2.0 + ], + "Q": [ + 1.75, + 0.5, + -1.44, + -1.34, + 0.66 + ], + "R": [ + 3.52, + 2.5, + -3.5, + 1.99, + -0.17 + ], + "S": [ + 2.39, + -1.07, + 1.15, + -1.39, + 0.67 + ], + "T": [ + 0.75, + -2.18, + -1.12, + -1.46, + -0.4 + ], + "V": [ + -2.59, + -2.64, + -1.54, + -0.85, + -0.02 + ], + "W": [ + -4.36, + 3.94, + 0.59, + 3.44, + -1.59 + ], + "Y": [ + -2.54, + 2.44, + 0.43, + 0.04, + -1.47 + ] + }, + "reference": "Sandberg M. et al. (1998). New chemical descriptors relevant for the design of biologically active peptides. A multivariate characterization of 87 amino acids. J Med Chem, 41(14), 2481-2491.", + "doi": "10.1021/jm9700575", + "source_note": "Transcribed 2026-10-12 programmatically from van Westen G.J.P. et al. (2013). Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets. J Cheminform, 5, 41. Additional file 2 (CC-BY Pipeline Pilot component 'Protein Descriptor (Published)'), which embeds the published table; block appending properties Z1..Z5. Secondary source: the primary paper is paywalled and was not available to check. UNRESOLVED: differs from peptides.py at C Z3 (3.71 here, 3.75 in peptides.py), L Z4 (0.72 vs -0.72); check against the primary paper." + }, + "vhse": { + "name": "vhse", + "values": { + "A": [ + 0.15, + -1.11, + -1.35, + -0.92, + 0.02, + -0.91, + 0.36, + -0.48 + ], + "C": [ + 0.18, + -1.67, + -0.46, + -0.21, + 0.0, + 1.2, + -1.61, + -0.19 + ], + "D": [ + -1.15, + 0.67, + -0.41, + -0.01, + -2.68, + 1.31, + 0.03, + 0.56 + ], + "E": [ + -1.18, + 0.4, + 0.1, + 0.36, + -2.16, + -0.17, + 0.91, + 0.02 + ], + "F": [ + 1.52, + 0.61, + 0.96, + -0.16, + 0.25, + 0.28, + -1.33, + -0.2 + ], + "G": [ + -0.2, + -1.53, + -2.63, + 2.28, + -0.53, + -1.18, + 2.01, + -1.34 + ], + "H": [ + -0.43, + -0.25, + 0.37, + 0.19, + 0.51, + 1.28, + 0.93, + 0.65 + ], + "I": [ + 1.27, + -0.14, + 0.3, + -1.8, + 0.3, + -1.61, + -0.16, + -0.13 + ], + "K": [ + -1.17, + 0.7, + 0.7, + 0.8, + 1.64, + 0.67, + 1.63, + 0.13 + ], + "L": [ + 1.36, + 0.07, + 0.26, + -0.8, + 0.22, + -1.37, + 0.08, + -0.62 + ], + "M": [ + 1.01, + -0.53, + 0.43, + 0.0, + 0.23, + 0.1, + -0.86, + -0.68 + ], + "N": [ + -0.99, + 0.0, + -0.37, + 0.69, + -0.55, + 0.85, + 0.73, + -0.8 + ], + "P": [ + 0.22, + -0.17, + -0.5, + 0.05, + -0.01, + -1.34, + -0.19, + 3.56 + ], + "Q": [ + -0.96, + 0.12, + 0.18, + 0.16, + 0.09, + 0.42, + -0.2, + -0.41 + ], + "R": [ + -1.47, + 1.45, + 1.24, + 1.27, + 1.55, + 1.47, + 1.3, + 0.83 + ], + "S": [ + -0.67, + -0.86, + -1.07, + -0.41, + -0.32, + 0.27, + -0.64, + 0.11 + ], + "T": [ + -0.34, + -0.51, + -0.55, + -1.06, + -0.06, + -0.01, + -0.79, + 0.39 + ], + "V": [ + 0.76, + -0.92, + -0.17, + -1.91, + 0.22, + -1.4, + -0.24, + -0.03 + ], + "W": [ + 1.5, + 2.06, + 1.79, + 0.75, + 0.75, + -0.13, + -1.01, + -0.85 + ], + "Y": [ + 0.61, + 1.6, + 1.17, + 0.73, + 0.53, + 0.25, + -0.96, + -0.52 + ] + }, + "reference": "Mei H. et al. (2005). A new set of amino acid descriptors and its application in peptide QSARs. Biopolymers, 80(6), 775-786.", + "doi": "10.1002/bip.20296", + "source_note": "Transcribed 2026-10-12 programmatically from van Westen G.J.P. et al. (2013). Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets. J Cheminform, 5, 41. Additional file 2 (CC-BY Pipeline Pilot component 'Protein Descriptor (Published)'), which embeds the published table; block appending properties VHSE1..VHSE8. Secondary source: the primary paper is paywalled and was not available to check. UNRESOLVED: differs from peptides.py at L VHSE3 (0.26 here, 0.36 in peptides.py); check against the primary paper." + }, + "st_scales": { + "name": "st_scales", + "values": { + "A": [ + -1.552, + -0.791, + -0.627, + 0.237, + -0.461, + -2.229, + 0.283, + 1.221 + ], + "C": [ + -1.276, + -0.401, + 0.134, + 0.859, + -0.196, + -0.72, + 0.639, + -0.857 + ], + "D": [ + -0.907, + -0.054, + -0.781, + -0.248, + 1.12, + 0.101, + -0.245, + -0.075 + ], + "E": [ + -0.629, + 0.39, + -0.38, + -0.366, + 0.635, + 0.514, + 0.175, + 0.367 + ], + "F": [ + -0.019, + 0.024, + 1.08, + -0.22, + -0.937, + 0.57, + -0.357, + 0.278 + ], + "G": [ + -1.844, + -0.018, + -0.184, + 0.573, + -0.728, + -3.317, + 0.166, + 2.522 + ], + "H": [ + -0.225, + 0.361, + 0.079, + -1.037, + 0.568, + 0.273, + 1.208, + -0.001 + ], + "I": [ + -0.785, + -1.01, + -0.349, + -0.097, + -0.402, + 1.091, + -0.139, + -0.764 + ], + "K": [ + -0.504, + 0.245, + 0.297, + -0.065, + -0.387, + 1.011, + 0.525, + 0.553 + ], + "L": [ + -0.826, + -0.379, + 0.038, + -0.059, + -0.625, + 1.025, + -0.229, + -0.129 + ], + "M": [ + -0.693, + 0.498, + 0.658, + 0.457, + -0.231, + 1.064, + 0.248, + -0.778 + ], + "N": [ + -0.888, + -0.057, + -0.651, + -0.214, + 0.917, + 0.164, + -0.14, + -0.166 + ], + "P": [ + -1.049, + -0.407, + -0.067, + -0.066, + -0.813, + -0.89, + 0.021, + -0.894 + ], + "Q": [ + -0.622, + 0.228, + -0.193, + -0.105, + 0.418, + 0.474, + 0.172, + 0.408 + ], + "R": [ + -0.059, + 0.731, + -0.013, + -0.096, + -0.253, + 0.3, + 1.256, + 0.854 + ], + "S": [ + -1.343, + -0.311, + -0.917, + -0.049, + 0.549, + -1.533, + 0.166, + 0.28 + ], + "T": [ + -1.061, + -0.928, + -0.911, + -0.063, + 0.538, + -0.775, + -0.147, + -0.717 + ], + "V": [ + -1.133, + -0.893, + -0.325, + 0.303, + -0.561, + -0.175, + -0.02, + -0.311 + ], + "W": [ + 0.853, + 0.039, + 0.26, + -1.163, + 0.16, + -0.202, + 1.01, + 0.195 + ], + "Y": [ + 0.308, + 0.569, + 1.1, + -0.464, + -0.144, + -0.354, + -1.099, + 0.162 + ] + }, + "reference": "Yang L. et al. (2010). ST-scale as a novel amino acid descriptor and its application in QSAM of peptides and analogues. Amino Acids, 38(3), 805-816.", + "doi": "10.1007/s00726-009-0287-y", + "source_note": "Transcribed 2026-10-12 programmatically from van Westen G.J.P. et al. (2013). Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets. J Cheminform, 5, 41. Additional file 2 (CC-BY Pipeline Pilot component 'Protein Descriptor (Published)'), which embeds the published table; block appending properties ST1..ST8. Secondary source: the primary paper is paywalled and was not available to check. UNRESOLVED: differs from peptides.py at Q ST1 (-0.622 here, -0.662 in peptides.py), E ST2 (0.39 vs -0.39); check against the primary paper." + }, + "t_scales": { + "name": "t_scales", + "values": { + "A": [ + -9.11, + -1.63, + 0.63, + 1.04, + 2.26 + ], + "C": [ + -7.35, + -0.86, + -0.33, + 0.8, + 0.98 + ], + "D": [ + -4.65, + 0.75, + 1.39, + -0.4, + 1.05 + ], + "E": [ + -3.03, + 1.82, + 0.51, + -0.58, + 0.43 + ], + "F": [ + 0.49, + -0.94, + -0.63, + -1.27, + -0.44 + ], + "G": [ + -10.61, + -1.21, + -0.12, + 0.75, + 3.25 + ], + "H": [ + -1.01, + -1.31, + 0.01, + -1.81, + -0.21 + ], + "I": [ + -4.25, + -0.28, + -0.15, + 1.4, + -0.21 + ], + "K": [ + -2.59, + 2.34, + -1.69, + 0.41, + -0.21 + ], + "L": [ + -4.38, + 0.28, + -0.49, + 1.45, + 0.02 + ], + "M": [ + -4.08, + 0.98, + -2.34, + 1.64, + -0.79 + ], + "N": [ + -4.62, + 0.66, + 1.16, + -0.22, + 0.93 + ], + "P": [ + -5.11, + -3.54, + -0.53, + -0.36, + -0.29 + ], + "Q": [ + -3.0, + 1.72, + 0.28, + -0.39, + 0.33 + ], + "R": [ + 0.23, + 3.89, + -1.16, + -0.39, + -0.06 + ], + "S": [ + -7.44, + -0.65, + 0.68, + -0.17, + 1.58 + ], + "T": [ + -5.97, + -0.62, + 1.11, + 0.31, + 0.95 + ], + "V": [ + -5.87, + -0.94, + 0.28, + 1.1, + 0.48 + ], + "W": [ + 5.73, + -2.67, + -0.07, + -1.96, + -0.54 + ], + "Y": [ + 2.08, + -0.47, + 0.07, + -1.67, + -0.35 + ] + }, + "reference": "Tian F., Zhou P. & Li Z. (2007). T-scale as a novel vector of topological descriptors for amino acids and its application in QSARs of peptides. J Mol Struct, 830, 106-115.", + "doi": "10.1016/j.molstruc.2006.07.004", + "source_note": "Transcribed 2026-10-12 programmatically from van Westen G.J.P. et al. (2013). Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets. J Cheminform, 5, 41. Additional file 2 (CC-BY Pipeline Pilot component 'Protein Descriptor (Published)'), which embeds the published table; block appending properties T1..T5. Secondary source: the primary paper is paywalled and was not available to check. Cross-checked: identical to peptides.py." + }, + "ms_whim": { + "name": "ms_whim", + "values": { + "A": [ + -0.73, + 0.2, + -0.62 + ], + "C": [ + -0.66, + 0.26, + -0.27 + ], + "D": [ + 0.11, + -1.0, + -0.96 + ], + "E": [ + 0.24, + -0.39, + -0.04 + ], + "F": [ + 0.76, + 0.85, + -0.34 + ], + "G": [ + -0.31, + -0.28, + -0.75 + ], + "H": [ + 0.84, + 0.67, + -0.78 + ], + "I": [ + -0.91, + 0.83, + -0.25 + ], + "K": [ + -0.51, + 0.08, + 0.6 + ], + "L": [ + -0.74, + 0.72, + -0.16 + ], + "M": [ + -0.7, + 1.0, + -0.32 + ], + "N": [ + 0.14, + 0.2, + -0.66 + ], + "P": [ + -0.43, + 0.73, + -0.6 + ], + "Q": [ + 0.3, + 1.0, + -0.3 + ], + "R": [ + -0.22, + 0.27, + 1.0 + ], + "S": [ + -0.8, + 0.61, + -1.0 + ], + "T": [ + -0.58, + 0.85, + -0.89 + ], + "V": [ + -1.0, + 0.79, + -0.58 + ], + "W": [ + 1.0, + 0.98, + -0.47 + ], + "Y": [ + 0.97, + 0.66, + -0.16 + ] + }, + "reference": "Zaliani A. & Gancia E. (1999). MS-WHIM scores for amino acids: a new 3D-description for peptide QSAR and QSPR studies. J Chem Inf Comput Sci, 39(3), 525-533.", + "doi": "10.1021/ci980211b", + "source_note": "Transcribed 2026-10-12 programmatically from van Westen G.J.P. et al. (2013). Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets. J Cheminform, 5, 41. Additional file 2 (CC-BY Pipeline Pilot component 'Protein Descriptor (Published)'), which embeds the published table; block appending properties MW1..MW3. Secondary source: the primary paper is paywalled and was not available to check. Cross-checked: identical to peptides.py." + }, + "fasgai": { + "name": "fasgai", + "values": { + "A": [ + 0.207, + 0.821, + -1.009, + 1.387, + 0.063, + -0.6 + ], + "C": [ + 0.997, + 0.021, + -1.419, + -2.08, + -0.799, + 0.502 + ], + "D": [ + -1.298, + -0.444, + -0.584, + -0.175, + -0.259, + -1.762 + ], + "E": [ + -1.349, + 1.388, + -0.361, + 0.213, + 0.424, + -1.303 + ], + "F": [ + 1.247, + 0.293, + 1.336, + -0.026, + 0.012, + -0.015 + ], + "G": [ + -0.205, + -2.219, + -1.656, + 1.229, + -1.115, + -1.146 + ], + "H": [ + -0.27, + 0.461, + -0.024, + -1.407, + 0.001, + 0.169 + ], + "I": [ + 1.524, + 0.536, + 0.809, + 0.734, + -0.196, + 0.427 + ], + "K": [ + -1.387, + 0.572, + 0.285, + 0.333, + -0.169, + 1.157 + ], + "L": [ + 1.2, + 1.128, + 0.703, + 1.904, + 0.536, + -0.141 + ], + "M": [ + 0.886, + 1.346, + 0.277, + -0.913, + 0.007, + -0.265 + ], + "N": [ + -1.009, + -0.939, + -0.428, + -0.397, + -0.539, + -0.605 + ], + "P": [ + -0.407, + -2.038, + -0.564, + -0.128, + 3.847, + -1.008 + ], + "Q": [ + -0.88, + 0.381, + -0.044, + -0.455, + -0.04, + 0.405 + ], + "R": [ + -1.229, + 0.378, + 0.516, + -0.328, + -0.052, + 2.728 + ], + "S": [ + -0.495, + -0.847, + -1.079, + 0.582, + 0.035, + -0.068 + ], + "T": [ + -0.032, + -0.45, + -0.61, + 0.341, + 0.117, + 0.577 + ], + "V": [ + 1.332, + 0.545, + 0.029, + 1.026, + -0.229, + 1.038 + ], + "W": [ + 0.844, + -0.075, + 2.069, + -1.36, + -0.81, + -0.38 + ], + "Y": [ + 0.329, + -0.858, + 1.753, + -0.479, + -0.835, + 0.289 + ] + }, + "reference": "Liang G. & Li Z. (2007). Factor analysis scale of generalized amino acid information as the source of a new set of descriptors for elucidating the structure and activity relationships of cationic antimicrobial peptides. QSAR Comb Sci, 26(6), 754-763.", + "doi": "10.1002/qsar.200630145", + "source_note": "Transcribed 2026-10-12 programmatically from van Westen G.J.P. et al. (2013). Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets. J Cheminform, 5, 41. Additional file 2 (CC-BY Pipeline Pilot component 'Protein Descriptor (Published)'), which embeds the published table; block appending properties F1..F6. Secondary source: the primary paper is paywalled and was not available to check. UNRESOLVED: differs from peptides.py at V F1 (1.332 here, -1.332 in peptides.py), P F6 (-1.008 vs -1.108); check against the primary paper." + }, + "blosum_indices": { + "name": "blosum_indices", + "values": { + "A": [ + 0.077, + -0.916, + 0.526, + 0.004, + 0.24, + 0.19, + 0.656, + -0.047, + 1.357, + 0.333 + ], + "C": [ + -1.084, + -1.112, + 1.562, + 0.814, + 1.828, + -1.048, + -0.742, + 0.379, + -0.121, + -0.102 + ], + "D": [ + 1.551, + 0.005, + 0.323, + 0.493, + -0.991, + 0.01, + -1.615, + 0.526, + -0.15, + -0.282 + ], + "E": [ + 1.477, + 0.229, + -0.67, + -0.355, + -0.284, + -0.075, + -1.014, + 0.363, + 0.769, + 0.298 + ], + "F": [ + -1.619, + 1.007, + -0.311, + 0.623, + -0.549, + 0.29, + -0.021, + 0.098, + 0.433, + -1.288 + ], + "G": [ + 0.849, + 0.174, + 1.726, + 0.093, + -0.548, + 1.186, + 1.213, + 0.874, + 0.009, + 0.242 + ], + "H": [ + 0.716, + 1.548, + -0.802, + 1.547, + 0.35, + -0.785, + 0.655, + -0.076, + -0.186, + 0.99 + ], + "I": [ + -1.462, + -1.126, + -0.761, + 0.382, + -0.599, + 0.276, + -0.132, + 0.198, + -0.216, + 0.207 + ], + "K": [ + 1.135, + -0.039, + -0.802, + -0.849, + 0.819, + 0.097, + 0.213, + 0.129, + 0.176, + -0.85 + ], + "L": [ + -1.406, + -0.856, + -0.879, + -0.172, + 0.032, + 0.344, + 0.109, + 0.146, + -0.436, + -0.021 + ], + "M": [ + -0.963, + -0.585, + -0.972, + -0.528, + 0.236, + 0.365, + 0.062, + 0.208, + -0.56, + 0.361 + ], + "N": [ + 1.511, + 0.215, + -0.046, + 1.009, + 0.12, + 0.834, + -0.033, + -0.57, + -1.2, + -0.139 + ], + "P": [ + 0.883, + -0.675, + 0.382, + -0.869, + -1.243, + -2.023, + 0.845, + -0.352, + -0.421, + -0.298 + ], + "Q": [ + 1.094, + 0.296, + -0.871, + -0.718, + 0.5, + -0.08, + -0.442, + 0.202, + 0.384, + 0.667 + ], + "R": [ + 1.014, + 0.189, + -0.86, + -0.609, + 1.277, + 0.195, + 0.661, + 0.175, + -0.219, + -0.52 + ], + "S": [ + 0.844, + -0.448, + 0.423, + 0.317, + 0.2, + 0.541, + 0.009, + -0.797, + 0.624, + -0.129 + ], + "T": [ + 0.188, + -0.733, + 0.178, + -0.012, + 0.022, + 0.378, + -0.304, + -1.958, + 0.149, + 0.063 + ], + "V": [ + -1.127, + -1.227, + -0.633, + 0.064, + -0.596, + 0.158, + 0.014, + 0.016, + 0.251, + 0.607 + ], + "W": [ + -1.577, + 2.281, + 1.166, + -1.61, + 0.122, + 0.239, + -0.542, + -0.398, + -0.349, + 0.499 + ], + "Y": [ + -1.142, + 1.74, + -0.582, + 0.747, + -0.119, + -0.475, + 0.241, + -0.251, + 0.713, + -0.251 + ] + }, + "reference": "Georgiev A.G. (2009). Interpretable numerical descriptors of amino acid space. J Comput Biol, 16(5), 703-723.", + "doi": "10.1089/cmb.2008.0173", + "source_note": "Transcribed 2026-10-12 programmatically from van Westen G.J.P. et al. (2013). Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets. J Cheminform, 5, 41. Additional file 2 (CC-BY Pipeline Pilot component 'Protein Descriptor (Published)'), which embeds the published table; block appending properties B1..B10. Secondary source: the primary paper is paywalled and was not available to check. Cross-checked: agrees with peptides.py (2 dp) after rounding." + }, + "protfp": { + "name": "protfp", + "values": { + "A": [ + -0.1, + -4.94, + -2.13, + 1.7, + -0.39, + 1.06, + -1.39, + 0.97 + ], + "C": [ + 4.62, + -3.54, + 1.5, + -1.26, + 3.27, + -0.34, + -0.47, + -0.23 + ], + "D": [ + -6.61, + 0.94, + -3.04, + -4.58, + 0.48, + -1.31, + 0.1, + 0.94 + ], + "E": [ + -5.1, + 2.2, + -3.59, + -2.26, + -2.14, + 1.35, + -0.45, + -1.31 + ], + "F": [ + 6.76, + 0.88, + 0.89, + -1.12, + -0.49, + -0.55, + -0.87, + 1.05 + ], + "G": [ + -5.7, + -8.72, + 4.18, + -1.35, + -0.31, + 2.91, + 0.32, + -0.11 + ], + "H": [ + 0.17, + 2.14, + 1.2, + 0.71, + 1.16, + -0.38, + -1.85, + -2.79 + ], + "I": [ + 6.58, + -1.73, + -2.49, + 1.09, + -0.34, + -0.28, + 1.97, + -0.92 + ], + "K": [ + -4.99, + 5.0, + 0.7, + 3.0, + -1.23, + 1.41, + 0.19, + 0.87 + ], + "L": [ + 5.76, + -1.33, + -1.71, + 0.63, + -1.7, + 0.71, + -0.05, + -0.51 + ], + "M": [ + 5.11, + 0.19, + -1.02, + 0.15, + 0.13, + -0.3, + -2.95, + 0.5 + ], + "N": [ + -4.88, + 0.81, + 0.14, + -0.14, + 1.23, + -0.65, + 1.02, + -1.94 + ], + "P": [ + -3.82, + -2.31, + 3.45, + 1.0, + -3.22, + -3.54, + -0.36, + -0.3 + ], + "Q": [ + -3.95, + 2.88, + -0.83, + 0.52, + 0.9, + 0.55, + -0.08, + 0.64 + ], + "R": [ + -2.79, + 6.6, + 1.21, + 2.07, + 1.67, + 0.76, + 0.0, + 0.32 + ], + "S": [ + -4.57, + -2.55, + -0.67, + 1.11, + 0.99, + -1.02, + 0.11, + 0.65 + ], + "T": [ + -2.0, + -1.77, + -0.7, + 1.02, + 1.06, + -1.2, + 0.74, + 1.65 + ], + "V": [ + 5.04, + -2.9, + -2.29, + 1.38, + 0.06, + 0.08, + 1.79, + -0.38 + ], + "W": [ + 7.33, + 4.55, + 2.77, + -2.41, + -1.08, + 1.04, + 0.23, + 0.59 + ], + "Y": [ + 3.14, + 3.59, + 2.45, + -1.27, + -0.06, + -0.29, + 1.99, + 0.3 + ] + }, + "reference": "van Westen G.J.P. et al. (2013). Benchmarking of protein descriptor sets in proteochemometric modeling (part 1): comparative study of 13 amino acid descriptor sets. J Cheminform, 5, 41.", + "doi": "10.1186/1758-2946-5-41", + "source_note": "ProtFP (PCA, 8 components) from Additional file 2 of the primary paper (block appending PCA1..PCA8); transcribed 2026-10-12. Cross-checked: identical to peptides.py." + }, + "atchley_factors": { + "name": "atchley_factors", + "values": { + "A": [ + -0.591, + -1.302, + -0.733, + 1.57, + -0.146 + ], + "C": [ + -1.343, + 0.465, + -0.862, + -1.02, + -0.255 + ], + "D": [ + 1.05, + 0.302, + -3.656, + -0.259, + -3.242 + ], + "E": [ + 1.357, + -1.453, + 1.477, + 0.113, + -0.837 + ], + "F": [ + -1.006, + -0.59, + 1.891, + -0.397, + 0.412 + ], + "G": [ + -0.384, + 1.652, + 1.33, + 1.045, + 2.064 + ], + "H": [ + 0.336, + -0.417, + -1.673, + -1.474, + -0.078 + ], + "I": [ + -1.239, + -0.547, + 2.131, + 0.393, + 0.816 + ], + "K": [ + 1.831, + -0.561, + 0.533, + -0.277, + 1.648 + ], + "L": [ + -1.019, + -0.987, + -1.505, + 1.266, + -0.912 + ], + "M": [ + -0.663, + -1.524, + 2.219, + -1.005, + 1.212 + ], + "N": [ + 0.945, + 0.828, + 1.299, + -0.169, + 0.933 + ], + "P": [ + 0.189, + 2.081, + -1.628, + 0.421, + -1.392 + ], + "Q": [ + 0.931, + -0.179, + -3.005, + -0.503, + -1.853 + ], + "R": [ + 1.538, + -0.055, + 1.502, + 0.44, + 2.897 + ], + "S": [ + -0.228, + 1.399, + -4.76, + 0.67, + -2.647 + ], + "T": [ + -0.032, + 0.326, + 2.213, + 0.908, + 1.313 + ], + "V": [ + -1.337, + -0.279, + -0.544, + 1.242, + -1.262 + ], + "W": [ + -0.595, + 0.009, + 0.672, + -2.128, + -0.184 + ], + "Y": [ + 0.26, + 0.83, + 3.097, + -0.838, + 1.512 + ] + }, + "reference": "Atchley W.R. et al. (2005). Solving the protein sequence metric problem. PNAS, 102(18), 6395-6400.", + "doi": "10.1073/pnas.0408677102", + "source_note": "Table 2 of the primary paper (PMC1088356), transcribed programmatically 2026-10-12. Cross-checked: agrees with peptides.py to the 3 dp published." + }, + "vihinen_flexibility": { + "name": "Normalized flexibility parameters (B-values), average (Vihinen et al., 1994)", + "values": { + "A": 0.984, + "C": 0.906, + "D": 1.068, + "E": 1.094, + "F": 0.915, + "G": 1.031, + "H": 0.95, + "I": 0.927, + "K": 1.102, + "L": 0.935, + "M": 0.952, + "N": 1.048, + "P": 1.049, + "Q": 1.037, + "R": 1.008, + "S": 1.046, + "T": 0.997, + "V": 0.931, + "W": 0.904, + "Y": 0.929 + }, + "reference": "Vihinen, M., Torkkila, E. and Riikonen, P. 'Accuracy of protein flexibility predictions' Proteins 19, 141-149 (1994)", + "doi": "10.1002/prot.340190207", + "source_note": "AAindex1 record VINM940101 (average normalised B-values of Vihinen et al. 1994; identical to Biopython ProtParamData.Flex); copied 2026-10-12." + }, + "parker": { + "name": "HPLC parameter (Parker et al., 1986)", + "values": { + "A": 2.1, + "C": 1.4, + "D": 10.0, + "E": 7.8, + "F": -9.2, + "G": 5.7, + "H": 2.1, + "I": -8.0, + "K": 5.7, + "L": -9.2, + "M": -4.2, + "N": 7.0, + "P": 2.1, + "Q": 6.0, + "R": 4.2, + "S": 6.5, + "T": 5.2, + "V": -3.7, + "W": -10.0, + "Y": -1.9 + }, + "reference": "Parker, J.M.R., Guo, D. and Hodges, R.S. 'New hydrophilicity scale derived from high-performance liquid chromatography peptide retention data: Correlation of predicted surface residues with antigencity and x-ray-derived accessible sites' Biochemistry 25, 5425-5432 (1986)", + "doi": "10.1021/bi00367a013", + "source_note": "AAindex1 record PARJ860101, identical to ExPASy ProtScale 'Hphob. / Parker'; copied 2026-10-12." + }, + "janin": { + "name": "Transfer free energy (Janin, 1979)", + "values": { + "A": 0.3, + "C": 0.9, + "D": -0.6, + "E": -0.7, + "F": 0.5, + "G": 0.3, + "H": -0.1, + "I": 0.7, + "K": -1.8, + "L": 0.5, + "M": 0.4, + "N": -0.5, + "P": -0.3, + "Q": -0.7, + "R": -1.4, + "S": -0.1, + "T": -0.2, + "V": 0.6, + "W": 0.3, + "Y": -0.4 + }, + "reference": "Janin, J. 'Surface and inside volumes in globular proteins' Nature 277, 491-492 (1979)", + "doi": "10.1038/277491a0", + "source_note": "AAindex1 record JANJ790102, identical to ExPASy ProtScale 'Hphob. / Janin'; copied 2026-10-12." + } +} \ No newline at end of file diff --git a/protpy/data/reduced_alphabets.json b/protpy/data/reduced_alphabets.json new file mode 100644 index 0000000..89bbf57 --- /dev/null +++ b/protpy/data/reduced_alphabets.json @@ -0,0 +1,151 @@ +{ + "ifeature5": { + "name": "iFeature 5-group alphabet (GAAC/CKSAAGP)", + "values": { + "aliphatic": "GAVLMI", + "aromatic": "FYW", + "positive": "KRH", + "negative": "DE", + "uncharged": "STCPNQ" + }, + "reference": "iFeature: Chen Z. et al. (2018). iFeature: a Python package and web server for features extraction and selection from protein and peptide sequences. Bioinformatics, 34(14), 2499-2502.", + "doi": "10.1093/bioinformatics/bty140", + "source_note": "Groups as defined for GAAC in the iFeature paper and its reference implementation; transcribed 2026-10-12." + }, + "cp13": { + "name": "cp(13) cluster profile", + "values": [ + "MF", + "IL", + "V", + "A", + "C", + "WYQHP", + "G", + "T", + "S", + "N", + "RK", + "D", + "E" + ], + "reference": "Liu B. et al. (2014). iDNA-Prot|dis: identifying DNA-binding proteins by incorporating amino acid distance-pairs and reduced alphabet profile into the general pseudo amino acid composition. PLoS ONE, 9(9), e106691.", + "doi": "10.1371/journal.pone.0106691", + "source_note": "Eq. 10 of the paper (image pone.0106691.e027); transcribed 2026-10-12." + }, + "cp14": { + "name": "cp(14) cluster profile", + "values": [ + "IMV", + "L", + "F", + "WY", + "G", + "P", + "C", + "A", + "S", + "T", + "N", + "HRKQ", + "E", + "D" + ], + "reference": "Liu B. et al. (2014). iDNA-Prot|dis: identifying DNA-binding proteins by incorporating amino acid distance-pairs and reduced alphabet profile into the general pseudo amino acid composition. PLoS ONE, 9(9), e106691.", + "doi": "10.1371/journal.pone.0106691", + "source_note": "Eq. 10 of the paper prints cp(14) = {EIMV; L; F; WY; G; P; C; A; S; T; N; HRKQ; E; D}, listing E twice. The paper fixes n(c)=14 (dimension 14+14x14x3=602), so the stray E in the first cluster is a typo: dropping it is the only reading with 14 clusters that partitions the 20 residues. Transcribed 2026-10-12." + }, + "cp19": { + "name": "cp(19) cluster profile", + "values": [ + "P", + "G", + "E", + "K", + "R", + "Q", + "D", + "S", + "N", + "T", + "H", + "C", + "I", + "V", + "W", + "YF", + "A", + "L", + "M" + ], + "reference": "Liu B. et al. (2014). iDNA-Prot|dis: identifying DNA-binding proteins by incorporating amino acid distance-pairs and reduced alphabet profile into the general pseudo amino acid composition. PLoS ONE, 9(9), e106691.", + "doi": "10.1371/journal.pone.0106691", + "source_note": "Eq. 10 of the paper (image pone.0106691.e027); transcribed 2026-10-12." + }, + "cp20": { + "name": "cp(20) cluster profile (no reduction)", + "values": [ + "A", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "K", + "L", + "M", + "N", + "P", + "Q", + "R", + "S", + "T", + "V", + "W", + "Y" + ], + "reference": "Liu B. et al. (2014). iDNA-Prot|dis: identifying DNA-binding proteins by incorporating amino acid distance-pairs and reduced alphabet profile into the general pseudo amino acid composition. PLoS ONE, 9(9), e106691.", + "doi": "10.1371/journal.pone.0106691", + "source_note": "cp(20) is the conventional 20 amino acid profile (Liu 2014, Section 2); 2026-10-12." + }, + "ctd_ifeature_extra": { + "name": "Six additional CTD property partitions (iFeature 13-property CTD)", + "values": { + "hydrophobicity_ARGP820101": { + "1": "QSTNGDE", + "2": "RAHCKMV", + "3": "LYPFIW" + }, + "hydrophobicity_ZIMJ680101": { + "1": "QNGSWTDERA", + "2": "HMCKV", + "3": "LPFYI" + }, + "hydrophobicity_PONP930101": { + "1": "KPDESNQT", + "2": "GRHA", + "3": "YMFWLCVI" + }, + "hydrophobicity_CASG920101": { + "1": "KDEQPSRNTG", + "2": "AHYMLV", + "3": "FIWC" + }, + "hydrophobicity_ENGD860101": { + "1": "RDKENQHYP", + "2": "SGTAW", + "3": "CVLIMF" + }, + "hydrophobicity_FASG890101": { + "1": "KERSQD", + "2": "NTPG", + "3": "AYHWVMFLIC" + } + }, + "reference": "iFeature: Chen Z. et al. (2018). iFeature: a Python package and web server for features extraction and selection from protein and peptide sequences. Bioinformatics, 34(14), 2499-2502. Following Dubchak I. et al. (1995). PNAS, 92(19), 8700-8704.", + "doi": "10.1093/bioinformatics/bty140", + "source_note": "Three-group partitions for the six AAindex hydrophobicity properties used by iFeature's 13-property CTD (Table/Supplementary of iFeature, cross-checked against the iFeature reference implementation; data only, no code copied). Transcribed 2026-10-12." + } +} \ No newline at end of file diff --git a/protpy/data/residue_tables.json b/protpy/data/residue_tables.json new file mode 100644 index 0000000..90516eb --- /dev/null +++ b/protpy/data/residue_tables.json @@ -0,0 +1,351 @@ +{ + "codon_counts": { + "name": "Number of sense codons per amino acid (standard genetic code)", + "values": { + "A": 4, + "C": 2, + "D": 2, + "E": 2, + "F": 2, + "G": 4, + "H": 2, + "I": 3, + "K": 2, + "L": 6, + "M": 1, + "N": 2, + "P": 4, + "Q": 2, + "R": 6, + "S": 6, + "T": 4, + "V": 4, + "W": 1, + "Y": 2 + }, + "reference": "Saravanan V. & Gautham N. (2015). Harnessing computational biology for exact linear B-cell epitope prediction: a novel amino acid composition-based feature descriptor. OMICS, 19(10), 648-658.", + "doi": "10.1089/omi.2015.0095", + "source_note": "Standard genetic code (NCBI translation table 1), 61 sense codons, as used for the DDE theoretical mean; 2026-10-12." + }, + "atomic_formulas": { + "name": "Molecular formulas of the free amino acids", + "values": { + "A": { + "C": 3, + "H": 7, + "N": 1, + "O": 2, + "S": 0 + }, + "R": { + "C": 6, + "H": 14, + "N": 4, + "O": 2, + "S": 0 + }, + "N": { + "C": 4, + "H": 8, + "N": 2, + "O": 3, + "S": 0 + }, + "D": { + "C": 4, + "H": 7, + "N": 1, + "O": 4, + "S": 0 + }, + "C": { + "C": 3, + "H": 7, + "N": 1, + "O": 2, + "S": 1 + }, + "Q": { + "C": 5, + "H": 10, + "N": 2, + "O": 3, + "S": 0 + }, + "E": { + "C": 5, + "H": 9, + "N": 1, + "O": 4, + "S": 0 + }, + "G": { + "C": 2, + "H": 5, + "N": 1, + "O": 2, + "S": 0 + }, + "H": { + "C": 6, + "H": 9, + "N": 3, + "O": 2, + "S": 0 + }, + "I": { + "C": 6, + "H": 13, + "N": 1, + "O": 2, + "S": 0 + }, + "L": { + "C": 6, + "H": 13, + "N": 1, + "O": 2, + "S": 0 + }, + "K": { + "C": 6, + "H": 14, + "N": 2, + "O": 2, + "S": 0 + }, + "M": { + "C": 5, + "H": 11, + "N": 1, + "O": 2, + "S": 1 + }, + "F": { + "C": 9, + "H": 11, + "N": 1, + "O": 2, + "S": 0 + }, + "P": { + "C": 5, + "H": 9, + "N": 1, + "O": 2, + "S": 0 + }, + "S": { + "C": 3, + "H": 7, + "N": 1, + "O": 3, + "S": 0 + }, + "T": { + "C": 4, + "H": 9, + "N": 1, + "O": 3, + "S": 0 + }, + "W": { + "C": 11, + "H": 12, + "N": 2, + "O": 2, + "S": 0 + }, + "Y": { + "C": 9, + "H": 11, + "N": 1, + "O": 3, + "S": 0 + }, + "V": { + "C": 5, + "H": 11, + "N": 1, + "O": 2, + "S": 0 + } + }, + "reference": "Pande A. et al. (2023). Computing wide range of protein/peptide features from their sequence and structure. J Comput Biol, 30(2), 204-222.", + "doi": "10.1089/cmb.2022.0241", + "source_note": "Standard free amino-acid formulas; cross-checked against Pfeature's Standalone/Data/atom.csv. Pfeature sums free amino-acid formulas without removing water (protpy's default convention). Its table over-counts H by one for W (C11H13N2O2) and Y (C9H12NO3); the correct formulas C11H12N2O2 and C9H11NO3 are used here, so W/Y-containing sequences differ slightly from Pfeature output. Transcribed 2026-10-12." + }, + "pka_sets": { + "name": "pKa sets for isoelectric point / charge calculations", + "values": { + "protpy": { + "D": 3.9, + "E": 4.1, + "H": 6.0, + "C": 8.3, + "Y": 10.1, + "K": 10.5, + "R": 12.5, + "N_term": 8.0, + "C_term": 3.1 + }, + "emboss": { + "N_term": 7.5, + "C_term": 3.6, + "C": 8.5, + "D": 3.9, + "E": 4.1, + "H": 6.5, + "K": 10.8, + "R": 12.5, + "Y": 10.1 + }, + "bjellqvist": { + "N_term": 7.5, + "C_term": 3.55, + "K": 10.0, + "R": 12.0, + "H": 5.98, + "D": 4.05, + "E": 4.45, + "C": 9.0, + "Y": 10.0, + "N_term_by_residue": { + "A": 7.59, + "M": 7.0, + "S": 6.93, + "P": 8.36, + "T": 6.82, + "V": 7.44, + "E": 7.7 + }, + "C_term_by_residue": { + "D": 4.55, + "E": 4.75 + } + }, + "ipc_protein": { + "N_term": 9.094, + "C_term": 2.869, + "C": 7.555, + "D": 3.872, + "E": 4.412, + "H": 5.637, + "K": 9.052, + "R": 11.84, + "Y": 10.85 + } + }, + "reference": "protpy: v1.4 defaults. emboss: Rice P. et al. (2000) EMBOSS, Trends Genet 16(6):276-277. bjellqvist: Bjellqvist B. et al. (1993) Electrophoresis 14:1023-1031 and (1994) Electrophoresis 15:529-539. ipc_protein: Kozlowski L.P. (2016) IPC - Isoelectric Point Calculator. Biol Direct 11:55.", + "doi": "10.1186/s13062-016-0159-9", + "source_note": "Transcribed programmatically 2026-10-12. emboss: EMBOSS 6.6.0 data file emboss/data/Epk.dat (values only; N_term 7.5, whereas Kozlowski 2016 Table 1 lists 8.6 for EMBOSS). ipc_protein: Table 1 of Kozlowski 2016 (PMC5075173). bjellqvist: Biopython IsoelectricPoint.py tables (attribution; BSD-3), cross-checked against the Bjellqvist row of Kozlowski 2016 Table 1; residue-specific terminal values from Bjellqvist 1994 via Biopython." + }, + "n_end_half_life": { + "name": "N-end rule half-life (hours) by N-terminal residue", + "values": { + "A": { + "mammal": 4.4, + "yeast": 20.0, + "ecoli": 10.0 + }, + "R": { + "mammal": 1.0, + "yeast": 0.03333333333333333, + "ecoli": 0.03333333333333333 + }, + "N": { + "mammal": 1.4, + "yeast": 0.05, + "ecoli": 10.0 + }, + "D": { + "mammal": 1.1, + "yeast": 0.05, + "ecoli": 10.0 + }, + "C": { + "mammal": 1.2, + "yeast": 20.0, + "ecoli": 10.0 + }, + "Q": { + "mammal": 0.8, + "yeast": 0.16666666666666666, + "ecoli": 10.0 + }, + "E": { + "mammal": 1.0, + "yeast": 0.5, + "ecoli": 10.0 + }, + "G": { + "mammal": 30.0, + "yeast": 20.0, + "ecoli": 10.0 + }, + "H": { + "mammal": 3.5, + "yeast": 0.16666666666666666, + "ecoli": 10.0 + }, + "I": { + "mammal": 20.0, + "yeast": 0.5, + "ecoli": 10.0 + }, + "L": { + "mammal": 5.5, + "yeast": 0.05, + "ecoli": 0.03333333333333333 + }, + "K": { + "mammal": 1.3, + "yeast": 0.05, + "ecoli": 0.03333333333333333 + }, + "M": { + "mammal": 30.0, + "yeast": 20.0, + "ecoli": 10.0 + }, + "F": { + "mammal": 1.1, + "yeast": 0.05, + "ecoli": 0.03333333333333333 + }, + "P": { + "mammal": 20.0, + "yeast": 20.0, + "ecoli": null + }, + "S": { + "mammal": 1.9, + "yeast": 20.0, + "ecoli": 10.0 + }, + "T": { + "mammal": 7.2, + "yeast": 20.0, + "ecoli": 10.0 + }, + "W": { + "mammal": 2.8, + "yeast": 0.05, + "ecoli": 0.03333333333333333 + }, + "Y": { + "mammal": 2.8, + "yeast": 0.16666666666666666, + "ecoli": 0.03333333333333333 + }, + "V": { + "mammal": 100.0, + "yeast": 20.0, + "ecoli": 10.0 + } + }, + "reference": "Bachmair A. et al. (1986) Science 234:179-186 (mammalian reticulocytes, yeast); Gonda D.K. et al. (1989) J Biol Chem 264:16700-16712; Tobias J.W. et al. (1991) Science 254:1374-1377 (E. coli); as tabulated by ExPASy ProtParam (Gasteiger et al. 2005).", + "doi": "10.1126/science.3018930", + "source_note": "Table 'amino acids and the corresponding half-life' of https://web.expasy.org/protparam/protparam-doc.html, parsed 2026-10-12. Minutes converted to hours. Lower bounds ('>20 hour', '>10 hour') are stored as the bound; the E. coli value for Pro is '?' in the source and stored as null." + } +} \ No newline at end of file diff --git a/protpy/data/structural_params.json b/protpy/data/structural_params.json new file mode 100644 index 0000000..4c82ff6 --- /dev/null +++ b/protpy/data/structural_params.json @@ -0,0 +1,135 @@ +{ + "aggrescan_a3v": { + "name": "AGGRESCAN amino-acid aggregation propensity (a3v)", + "values": { + "A": -0.036, + "C": 0.604, + "D": -1.836, + "E": -1.412, + "F": 1.754, + "G": -0.535, + "H": -1.033, + "I": 1.822, + "K": -0.931, + "L": 1.38, + "M": 0.91, + "N": -1.302, + "P": -0.334, + "Q": -1.231, + "R": -1.24, + "S": -0.294, + "T": -0.159, + "V": 1.594, + "W": 1.037, + "Y": 1.159 + }, + "reference": "Conchillo-Sol\u00e9 O. et al. (2007). AGGRESCAN: a server for the prediction and evaluation of 'hot spots' of aggregation in polypeptides. BMC Bioinformatics, 8, 65.", + "doi": "10.1186/1471-2105-8-65", + "source_note": "Additional file 1 of the paper (12859_2006_1437_MOESM1_ESM.pdf), parsed 2026-10-12. Hot-spot threshold -0.02 and hot-spot rules from Additional file 2." + }, + "top_idp": { + "name": "TOP-IDP disorder propensity scale", + "values": { + "A": 0.06, + "C": 0.02, + "D": 0.192, + "E": 0.736, + "F": -0.697, + "G": 0.166, + "H": 0.303, + "I": -0.486, + "K": 0.586, + "L": -0.326, + "M": -0.397, + "N": 0.007, + "P": 0.987, + "Q": 0.318, + "R": 0.18, + "S": 0.341, + "T": 0.059, + "V": -0.121, + "W": -0.884, + "Y": -0.51 + }, + "reference": "Campen A. et al. (2008). TOP-IDP-scale: a new amino acid scale measuring propensity for intrinsic disorder. Protein Pept Lett, 15(9), 956-963.", + "doi": "10.2174/092986608785849164", + "source_note": "Table 2 of the paper (PMC2676888), parsed 2026-10-12." + }, + "disorder_residue_classes": { + "name": "Order- and disorder-promoting residues", + "values": { + "disorder_promoting": "ARGQSPEK", + "order_promoting": "WCFIYVLN" + }, + "reference": "Dunker A.K. et al. (2001). Intrinsically disordered protein. J Mol Graph Model, 19(1), 26-59.", + "doi": "10.1016/S1093-3263(00)00138-8", + "source_note": "Residue lists as restated in Campen et al. 2008 (PMC2676888, Introduction), which cites Dunker 2001; 2026-10-12." + }, + "plaac": { + "name": "PLAAC prion-like domain model (S. cerevisiae, 28 prion domains)", + "values": { + "fg": { + "A": 0.04865, + "C": 0.00219, + "D": 0.01638, + "E": 0.00783, + "F": 0.02537, + "G": 0.07603, + "H": 0.0181, + "I": 0.02018, + "K": 0.01641, + "L": 0.02639, + "M": 0.02975, + "N": 0.25885, + "P": 0.05126, + "Q": 0.15178, + "R": 0.025, + "S": 0.10988, + "T": 0.03841, + "V": 0.01972, + "W": 0.00157, + "Y": 0.05624 + }, + "bg": { + "A": 0.055, + "C": 0.0126, + "D": 0.0586, + "E": 0.0655, + "F": 0.0441, + "G": 0.0498, + "H": 0.0217, + "I": 0.0655, + "K": 0.0735, + "L": 0.095, + "M": 0.0207, + "N": 0.0615, + "P": 0.0438, + "Q": 0.0396, + "R": 0.0444, + "S": 0.0899, + "T": 0.0592, + "V": 0.0556, + "W": 0.0104, + "Y": 0.0337 + }, + "transition": [ + [ + 0.999, + 0.001 + ], + [ + 0.02, + 0.98 + ] + ], + "initial": [ + 0.9524, + 0.0476 + ], + "epsilon": 1e-05 + }, + "reference": "Lancaster A.K. et al. (2014). PLAAC: a web and command-line application to identify proteins with prion-like amino acid composition. Bioinformatics, 30(17), 2501-2502.", + "doi": "10.1093/bioinformatics/btu310", + "source_note": "Ported 2026-10-12 from the PLAAC source (github.com/whitehead/plaac, cli/src/plaac.java: prd_freq_scer_28, bg_freq_scer, prionhmm1). MIT License, Copyright (c) 2009-2014 Whitehead Institute for Biomedical Research; (c) 2011 Boston Biomedical Research Institute; (c) 2013 University of Massachusetts Medical School." + } +} \ No newline at end of file diff --git a/protpy/encode.py b/protpy/encode.py index de8a326..13da35c 100644 --- a/protpy/encode.py +++ b/protpy/encode.py @@ -5,6 +5,7 @@ from __future__ import annotations import os +import csv import pandas as pd from tqdm import tqdm as _tqdm @@ -23,7 +24,6 @@ kmer_composition, reduced_alphabet_composition, motif_composition, - amino_acid_pair_composition, aliphatic_index, extinction_coefficient, boman_index, @@ -45,6 +45,11 @@ quasi_sequence_order_all, ) from ._constants import _validate_sequence +from . import (composition as _composition, autocorrelation as _autocorrelation, ctd as _ctd, + conjoint_triad as _conjoint_triad_module, sequence_order as _sequence_order, scales as _scales, + physicochemical as _physicochemical, complexity as _complexity, signal as _signal, + structural as _structural) +from .utils import _DESCRIPTOR_REGISTRY #descriptors that accept extra keyword arguments beyond sequence _PARAMETERISED_DESCRIPTORS: frozenset[str] = frozenset({ @@ -62,21 +67,78 @@ "ctd_", "sequence_order_coupling_number_all", "quasi_sequence_order_all", -}) +}) | frozenset( + #v1.5.0 descriptors outside the classic set that take parameters + name for name, meta in _DESCRIPTOR_REGISTRY.items() + if not meta.get("in_default_set", True) and set(meta["parameters"]) - {"sequence"} +) + +#descriptor sets accepted by all_descriptors / encode_dataset +_DESCRIPTOR_SETS = ("classic", "extended", "all") + +#modules holding each registry "module" value +_MODULES = { + "composition": _composition, "autocorrelation": _autocorrelation, "ctd": _ctd, + "conjoint_triad": _conjoint_triad_module, "sequence_order": _sequence_order, "scales": _scales, + "physicochemical": _physicochemical, "complexity": _complexity, "signal": _signal, + "structural": _structural, +} + + +def _set_members(descriptor_set: str) -> list[str]: + """ + Get the descriptors outside the classic set that belong to a descriptor set, + in the order of the descriptor registry. Membership is taken from the + descriptor_sets field of each registry entry. + + Parameters + ========== + :descriptor_set: str + descriptor set: "classic", "extended" or "all". + + Returns + ======= + :members: list[str] + names of the descriptors added by the set; empty for "classic". + """ + if descriptor_set == "classic": + return [] + return [name for name, meta in _DESCRIPTOR_REGISTRY.items() + if not meta.get("in_default_set", True) and descriptor_set in meta.get("descriptor_sets", [])] def all_descriptors( sequence: str, params: dict[str, dict] | None = None, show_progress: bool = False, - output_file: str | None = "descriptors.csv", + output_file: str | None = None, save_each_sequence: bool = True, + descriptor_set: str = "classic", + include: list[str] | None = None, + exclude: list[str] | None = None, _show_descriptor_list: bool = True, ) -> pd.DataFrame: """ Calculate all available protpy descriptors for a protein sequence and return them concatenated into a single DataFrame (1 x N_total). + ``descriptor_set`` chooses which descriptors are computed: + + - ``"classic"`` (default): the v1.4 descriptor set described below, with an + unchanged output. + - ``"extended"``: classic plus every v1.5 descriptor whose default output + has at most 150 columns (factor scales, charge patterning, + hydrophobicity/stability, complexity, signal and structural descriptors). + - ``"all"``: extended plus the high-dimensional v1.5 descriptors (CKSAAP, + DDE, ASDC, distance pair and auto/cross covariance). + + Set membership comes from the descriptor registry (``descriptor_sets``). + ``include`` adds further registered descriptors by name and ``exclude`` + removes descriptors by name (classic ones included, e.g. "ctd_" or + "tripeptide_composition"). Some extended descriptors are NaN where their + method leaves them undefined (``plaac`` LLR/COREscore for short or + non-prion sequences, ``half_life`` E. coli for an N-terminal Pro). + The combined variants of multi-property descriptors are used to avoid redundancy: ``ctd_`` is called with ``all_ctd=True`` (covers ctd_composition, ctd_transition and ctd_distribution across all 7 @@ -117,6 +179,10 @@ def all_descriptors( sequence_order_coupling_number_all → lag quasi_sequence_order_all → lag, weight + plus any v1.5 descriptor with parameters (see + ``protpy.get_descriptor_info(name)["parameters"]``), e.g. + ``{"kappa": {"blob_sizes": (5,)}, "aggrescan": {"window": 7}}``. + Example:: df = protpy.all_descriptors(seq, params={ @@ -131,11 +197,13 @@ def all_descriptors( When ``True``, display a ``tqdm`` progress bar that names the descriptor currently being computed and tracks overall progress. - :output_file: str | None (default="descriptors.csv") - Path to the CSV file where results are written. Pass ``None`` to - disable all file output. When calling this function in a loop over + :output_file: str | None (default=None) + Path to the CSV file where results are written. ``None`` (default) + disables all file output. When calling this function in a loop over multiple sequences the rows are accumulated in the file across calls; - each call appends one row to any previously saved sequences. + each call appends one row to any previously saved sequences. A + ``ValueError`` is raised, and the file left untouched, if an existing + file has different columns. :save_each_sequence: bool (default=True) When ``True``, the output CSV is updated once after all descriptors @@ -146,6 +214,15 @@ def all_descriptors( nothing is written to the CSV within this call. Has no effect when ``output_file`` is ``None``. + :descriptor_set: str (default="classic") + "classic", "extended" or "all" (see above). + + :include: list[str] | None (default=None) + extra registered descriptor names to compute. + + :exclude: list[str] | None (default=None) + descriptor names to skip. + Returns ======= :descriptors_df: pd.DataFrame @@ -179,6 +256,14 @@ def all_descriptors( if not isinstance(params, dict): raise TypeError(f"params must be a dict, got {type(params)}.") + if descriptor_set not in _DESCRIPTOR_SETS: + raise ValueError(f"Invalid descriptor_set '{descriptor_set}'. Valid sets are: {list(_DESCRIPTOR_SETS)}.") + include, exclude = list(include or []), set(exclude or []) + unregistered = [n for n in include + sorted(exclude) if n not in _DESCRIPTOR_REGISTRY] + if unregistered: + raise ValueError(f"Unknown descriptor name(s) in include/exclude: {unregistered}. " + "See protpy.list_descriptors().") + unknown = set(params) - _PARAMETERISED_DESCRIPTORS if unknown: raise ValueError( @@ -193,63 +278,60 @@ def _p(name: str) -> dict: # Only include kmer_composition when the user requests a k value that is unique. _kmer_k = _p("kmer_composition").get("k", 2) - steps: list[tuple[str, object]] = [ + steps: list[tuple[str, str, object]] = [ # ── Composition ──────────────────────────────────────────────────────── - ("[Composition] Amino acid composition", lambda: amino_acid_composition(sequence)), - ("[Composition] Dipeptide composition", lambda: dipeptide_composition(sequence)), - ("[Composition] Tripeptide composition", lambda: tripeptide_composition(sequence)), - ("[Composition] GRAVY index", lambda: gravy(sequence)), - ("[Composition] Aromaticity", lambda: aromaticity(sequence)), - ("[Composition] Instability index", lambda: instability_index(sequence)), - ("[Composition] Isoelectric point", lambda: isoelectric_point(sequence)), - ("[Composition] Molecular weight", lambda: molecular_weight(sequence)), - ("[Composition] Charge distribution", lambda: charge_distribution(sequence, **_p("charge_distribution"))), - ("[Composition] Hydrophobic / polar / charged comp.", lambda: hydrophobic_polar_charged_composition(sequence)), - ("[Composition] Secondary structure propensity", lambda: secondary_structure_propensity(sequence)), - *([("[Composition] K-mer composition", lambda: kmer_composition(sequence, **_p("kmer_composition")))] + ("[Composition] Amino acid composition", "amino_acid_composition", lambda: amino_acid_composition(sequence)), + ("[Composition] Dipeptide composition", "dipeptide_composition", lambda: dipeptide_composition(sequence)), + ("[Composition] Tripeptide composition", "tripeptide_composition", lambda: tripeptide_composition(sequence)), + ("[Composition] GRAVY index", "gravy", lambda: gravy(sequence)), + ("[Composition] Aromaticity", "aromaticity", lambda: aromaticity(sequence)), + ("[Composition] Instability index", "instability_index", lambda: instability_index(sequence)), + ("[Composition] Isoelectric point", "isoelectric_point", lambda: isoelectric_point(sequence)), + ("[Composition] Molecular weight", "molecular_weight", lambda: molecular_weight(sequence)), + ("[Composition] Charge distribution", "charge_distribution", lambda: charge_distribution(sequence, **_p("charge_distribution"))), + ("[Composition] Hydrophobic / polar / charged comp.", "hydrophobic_polar_charged_composition", lambda: hydrophobic_polar_charged_composition(sequence)), + ("[Composition] Secondary structure propensity", "secondary_structure_propensity", lambda: secondary_structure_propensity(sequence)), + *([("[Composition] K-mer composition", "kmer_composition", lambda: kmer_composition(sequence, **_p("kmer_composition")))] if _kmer_k not in (2, 3) else []), - ("[Composition] Reduced alphabet composition", lambda: reduced_alphabet_composition(sequence, **_p("reduced_alphabet_composition"))), - ("[Composition] Motif composition", lambda: motif_composition(sequence, **_p("motif_composition"))), - ("[Composition] Amino acid pair composition", lambda: amino_acid_pair_composition(sequence)), - ("[Composition] Aliphatic index", lambda: aliphatic_index(sequence)), - ("[Composition] Extinction coefficient", lambda: extinction_coefficient(sequence)), - ("[Composition] Boman index", lambda: boman_index(sequence)), - ("[Composition] Aggregation propensity", lambda: aggregation_propensity(sequence, **_p("aggregation_propensity"))), - ("[Composition] Hydrophobic moment", lambda: hydrophobic_moment(sequence, **_p("hydrophobic_moment"))), - ("[Composition] Shannon entropy", lambda: shannon_entropy(sequence)), - ("[Composition] Pseudo amino acid composition", lambda: pseudo_amino_acid_composition(sequence, **_p("pseudo_amino_acid_composition"))), - ("[Composition] Amphiphilic pseudo amino acid comp.", lambda: amphiphilic_pseudo_amino_acid_composition(sequence, **_p("amphiphilic_pseudo_amino_acid_composition"))), + ("[Composition] Reduced alphabet composition", "reduced_alphabet_composition", lambda: reduced_alphabet_composition(sequence, **_p("reduced_alphabet_composition"))), + ("[Composition] Motif composition", "motif_composition", lambda: motif_composition(sequence, **_p("motif_composition"))), + ("[Composition] Aliphatic index", "aliphatic_index", lambda: aliphatic_index(sequence)), + ("[Composition] Extinction coefficient", "extinction_coefficient", lambda: extinction_coefficient(sequence)), + ("[Composition] Boman index", "boman_index", lambda: boman_index(sequence)), + ("[Composition] Aggregation propensity", "aggregation_propensity", lambda: aggregation_propensity(sequence, **_p("aggregation_propensity"))), + ("[Composition] Hydrophobic moment", "hydrophobic_moment", lambda: hydrophobic_moment(sequence, **_p("hydrophobic_moment"))), + ("[Composition] Shannon entropy", "shannon_entropy", lambda: shannon_entropy(sequence)), + ("[Composition] Pseudo amino acid composition", "pseudo_amino_acid_composition", lambda: pseudo_amino_acid_composition(sequence, **_p("pseudo_amino_acid_composition"))), + ("[Composition] Amphiphilic pseudo amino acid comp.", "amphiphilic_pseudo_amino_acid_composition", lambda: amphiphilic_pseudo_amino_acid_composition(sequence, **_p("amphiphilic_pseudo_amino_acid_composition"))), # ── Autocorrelation ──────────────────────────────────────────────────── - ("[Autocorrelation] Moreaubroto autocorrelation", lambda: moreaubroto_autocorrelation(sequence, **_p("moreaubroto_autocorrelation"))), - ("[Autocorrelation] Moran autocorrelation", lambda: moran_autocorrelation(sequence, **_p("moran_autocorrelation"))), - ("[Autocorrelation] Geary autocorrelation", lambda: geary_autocorrelation(sequence, **_p("geary_autocorrelation"))), + ("[Autocorrelation] Moreaubroto autocorrelation", "moreaubroto_autocorrelation", lambda: moreaubroto_autocorrelation(sequence, **_p("moreaubroto_autocorrelation"))), + ("[Autocorrelation] Moran autocorrelation", "moran_autocorrelation", lambda: moran_autocorrelation(sequence, **_p("moran_autocorrelation"))), + ("[Autocorrelation] Geary autocorrelation", "geary_autocorrelation", lambda: geary_autocorrelation(sequence, **_p("geary_autocorrelation"))), # ── Conjoint Triad ───────────────────────────────────────────────────── - ("[Conjoint Triad] Conjoint triad", lambda: _conjoint_triad(sequence)), + ("[Conjoint Triad] Conjoint triad", "conjoint_triad", lambda: _conjoint_triad(sequence)), # ── CTD ──────────────────────────────────────────────────────────────── - ("[CTD] Composition, transition & distribution",lambda: ctd_(sequence, all_ctd=True, **_p("ctd_"))), + ("[CTD] Composition, transition & distribution", "ctd_", lambda: ctd_(sequence, **{"all_ctd": True, **_p("ctd_")})), # ── Sequence Order ───────────────────────────────────────────────────── - ("[Sequence Order] Sequence order coupling number", lambda: sequence_order_coupling_number_all(sequence, **_p("sequence_order_coupling_number_all"))), - ("[Sequence Order] Quasi sequence order", lambda: quasi_sequence_order_all(sequence, **_p("quasi_sequence_order_all"))), + ("[Sequence Order] Sequence order coupling number", "sequence_order_coupling_number_all", lambda: sequence_order_coupling_number_all(sequence, **_p("sequence_order_coupling_number_all"))), + ("[Sequence Order] Quasi sequence order", "quasi_sequence_order_all", lambda: quasi_sequence_order_all(sequence, **_p("quasi_sequence_order_all"))), ] - descriptor_names = [label.split("]", 1)[1].strip() for label, _ in steps] + #v1.5 descriptors from the chosen set and include, then apply exclude + extra = _set_members(descriptor_set) + [n for n in include if n not in _set_members(descriptor_set)] + classic_names = {name for _, name, _ in steps} + for name in extra: + if name in classic_names: + continue + meta = _DESCRIPTOR_REGISTRY[name] + func = getattr(_MODULES[meta["module"]], name) + steps.append((f"[{meta['category']}] {name}", name, + lambda func=func, name=name: func(sequence, **_p(name)))) + steps = [step for step in steps if step[1] not in exclude] + + descriptor_names = [label.split("]", 1)[1].strip() for label, _, _ in steps] if _show_descriptor_list: print("Calculating descriptors: " + ", ".join(descriptor_names)) - # Load any rows already saved from previous sequences in this run so they - # are preserved when we rewrite the file after each checkpoint. - existing_df: pd.DataFrame | None = None - if output_file is not None and os.path.isfile(output_file): - try: - existing_df = pd.read_csv(output_file) - except Exception: - existing_df = None - - def _checkpoint(frames_so_far: list[pd.DataFrame]) -> None: - partial = pd.concat(frames_so_far, axis=1) - out = pd.concat([existing_df, partial], ignore_index=True) if existing_df is not None else partial - out.to_csv(output_file, index=False) - frames: list[pd.DataFrame] = [] if show_progress: @@ -259,20 +341,21 @@ def _checkpoint(frames_so_far: list[pd.DataFrame]) -> None: dynamic_ncols=True, bar_format="{desc} {percentage:3.0f}%|{bar:28}| {n:02d}/{total} [{elapsed}<{remaining}]", ) - for label, fn in steps: + for label, _, fn in steps: bar.set_description(label) frames.append(fn()) bar.update(1) bar.set_description(f"All {len(steps)} descriptors computed") bar.close() else: - for label, fn in steps: + for _, _, fn in steps: frames.append(fn()) result = pd.concat(frames, axis=1) if output_file is not None and save_each_sequence: - _checkpoint(frames) + _prepare_output_file(output_file, list(result.columns)) + result.to_csv(output_file, mode="a", header=False, index=False) return result @@ -286,6 +369,9 @@ def encode_dataset( save_each_sequence: bool = True, strip_gaps: bool = False, verbose: bool = True, + descriptor_set: str = "classic", + include: list[str] | None = None, + exclude: list[str] | None = None, ) -> pd.DataFrame: """ Compute all available descriptors for every sequence in a dataset and @@ -318,10 +404,13 @@ def encode_dataset( :output_file: str | None (default="descriptors.csv") Path to the CSV file where results are written. Each completed row - (identifier prepended) is flushed immediately after that sequence + (identifier prepended) is appended immediately after that sequence finishes, so progress is preserved if the process is interrupted. - Rows already present in the file from a previous run are retained. - Pass ``None`` to disable all file output. + Rows already present in the file from a previous run are retained, + except rows whose identifier is encoded again in this run, which are + replaced. A ``ValueError`` is raised, and the file left untouched, if + an existing file has different columns. Pass ``None`` to disable all + file output. :save_each_sequence: bool (default=True) When ``True``, the CSV is updated after each sequence completes. @@ -340,6 +429,15 @@ def encode_dataset( (``"Encoding sequence N/total: id"``). Set to ``False`` to suppress all stdout output from this function. + :descriptor_set: str (default="classic") + "classic", "extended" or "all"; see ``all_descriptors``. + + :include: list[str] | None (default=None) + extra descriptor names; see ``all_descriptors``. + + :exclude: list[str] | None (default=None) + descriptor names to skip; see ``all_descriptors``. + Returns ======= :result: pd.DataFrame @@ -394,14 +492,6 @@ def encode_dataset( f"sequences must be a list[str] or pd.DataFrame, got {type(sequences)}." ) - # Snapshot any previously saved rows so they are preserved on each flush. - initial_df: pd.DataFrame | None = None - if output_file is not None and os.path.isfile(output_file): - try: - initial_df = pd.read_csv(output_file) - except Exception: - pass - completed: list[pd.DataFrame] = [] total = len(seqs) @@ -422,27 +512,52 @@ def encode_dataset( show_progress=show_progress, output_file=None, save_each_sequence=False, - _show_descriptor_list=(idx == 1), + descriptor_set=descriptor_set, + include=include, + exclude=exclude, + _show_descriptor_list=(idx == 1 and verbose), ) row_df.insert(0, id_col_name, seq_id) completed.append(row_df) if output_file is not None and save_each_sequence: - new_rows = pd.concat(completed, ignore_index=True) - # Only prepend the initial snapshot when its columns exactly match the - # current run (mismatches arise from stale CSVs with mangled duplicate - # column names written by a prior buggy run). - if initial_df is not None and list(initial_df.columns) == list(new_rows.columns): - out = pd.concat([initial_df, new_rows], ignore_index=True) - else: - out = new_rows - out.to_csv(output_file, index=False) + if idx == 1: + _prepare_output_file(output_file, list(row_df.columns), replace_ids=ids) + row_df.to_csv(output_file, mode="a", header=False, index=False) if not completed: return pd.DataFrame() return pd.concat(completed, ignore_index=True) +def _prepare_output_file(output_file: str, columns: list, replace_ids: list | None = None) -> None: + """ + Get ``output_file`` ready for rows to be appended. A missing or empty file + gets a header row. An existing file must have exactly ``columns`` as its + header, otherwise a ValueError is raised and the file is not modified. + When ``replace_ids`` is given, existing rows whose first column matches + one of them are removed so a re-run does not duplicate them. + """ + if not os.path.isfile(output_file) or os.path.getsize(output_file) == 0: + with open(output_file, "w", newline="") as fh: + csv.writer(fh).writerow(columns) + return + + with open(output_file, newline="") as fh: + rows = list(csv.reader(fh)) + if rows[0] != [str(c) for c in columns]: + raise ValueError( + f"Existing output file '{output_file}' has different columns from this run; " + "refusing to overwrite it. Pass a different output_file or remove the file." + ) + if replace_ids: + replace = {str(i) for i in replace_ids} + kept = [rows[0]] + [r for r in rows[1:] if r and r[0] not in replace] + if len(kept) != len(rows): + with open(output_file, "w", newline="") as fh: + csv.writer(fh).writerows(kept) + + def _parse_fasta(filepath: str) -> list[str]: sequences: list[str] = [] current: list[str] = [] diff --git a/protpy/physicochemical.py b/protpy/physicochemical.py new file mode 100644 index 0000000..5e11f56 --- /dev/null +++ b/protpy/physicochemical.py @@ -0,0 +1,590 @@ +################################################################################ +############# Physicochemical ############## +################################################################################ + +from __future__ import annotations + +""" +Charge and charge-patterning descriptors (FCR, NCPR, kappa, SCD, charge +density). + +Charges follow the Das & Pappu convention: K, R = +1; D, E = -1; H = 0. + +References +========== +[1] Mao, A. H., Crick, S. L., Vitalis, A., Chicoine, C. L., & Pappu, R. V. + (2010). Net charge per residue modulates conformational ensembles of + intrinsically disordered proteins. PNAS, 107(18), 8183-8188. + https://doi.org/10.1073/pnas.0911107107 +[2] Das, R. K., & Pappu, R. V. (2013). Conformations of intrinsically disordered + proteins are influenced by linear sequence distributions of oppositely + charged residues. PNAS, 110(33), 13392-13397. + https://doi.org/10.1073/pnas.1304749110 +[3] Sawle, L., & Ghosh, K. (2015). A theoretical method to compute sequence + dependent configurational properties in charged polymers and proteins. + J Chem Phys, 143(8), 085101. https://doi.org/10.1063/1.4929391 +[4] Müller, A. T., Gabernet, G., Hiss, J. A., & Schneider, G. (2017). modlAMP: + Python for antimicrobial peptides. Bioinformatics, 33(17), 2753-2755. + https://doi.org/10.1093/bioinformatics/btx285 +[5] Kyte, J., & Doolittle, R. F. (1982). A simple method for displaying the + hydropathic character of a protein. J Mol Biol, 157(1), 105-132. + https://doi.org/10.1016/0022-2836(82)90515-0 +[6] Gasteiger, E. et al. (2005). Protein identification and analysis tools on the + ExPASy server. In The Proteomics Protocols Handbook (pp. 571-607). Humana + Press. https://doi.org/10.1385/1-59259-890-0:571 +[7] Wang, G. (2020). The antimicrobial peptide database provides a platform for + decoding the design principles of naturally occurring antimicrobial peptides. + Protein Sci, 29(1), 8-18. https://doi.org/10.1002/pro.3702 +[8] Bachmair, A., Finley, D., & Varshavsky, A. (1986). In vivo half-life of a + protein is a function of its amino-terminal residue. Science, 234(4773), + 179-186. https://doi.org/10.1126/science.3018930 +[9] Gonda, D. K. et al. (1989). Universality and structure of the N-end rule. + J Biol Chem, 264(28), 16700-16712. https://doi.org/10.1016/S0021-9258(19)84762-2 +[10] Tobias, J. W. et al. (1991). The N-end rule in bacteria. Science, 254(5036), + 1374-1377. https://doi.org/10.1126/science.1962196 +[11] Zeldovich, K. B., Berezovsky, I. N., & Shakhnovich, E. I. (2007). Protein and + DNA sequence determinants of thermophilic adaptation. PLoS Comput Biol, 3(1), + e5. https://doi.org/10.1371/journal.pcbi.0030005 +[12] Suhre, K., & Claverie, J.-M. (2003). Genomic correlates of hyperthermostability, + an update. J Biol Chem, 278(19), 17198-17202. https://doi.org/10.1074/jbc.M301327200 +[13] Vihinen, M., Torkkila, E., & Riikonen, P. (1994). Accuracy of protein + flexibility predictions. Proteins, 19(2), 141-149. + https://doi.org/10.1002/prot.340190207 +""" + +import numpy as np +import pandas as pd + +from ._constants import _validate_sequence, _validate_int +from ._helpers import _charge_vector, _to_df, _property_matrix, _sliding_windows, _profile_stats, _load_json_table +from .composition import charge_distribution, molecular_weight + +__all__ = [ + "charge_fractions", "ncpr", "kappa", "sequence_charge_decoration", "charge_density", + "hydrophobicity_profile", "hydrophobic_ratio", "half_life", "ivywrel_index", "cvp_bias", "flexibility", +] + +################################## Charge Fractions ####################################### + +def charge_fractions(sequence: str) -> pd.DataFrame: + """ + Calculate the Charge Fractions (FCR) of protein sequence. The fraction of + charged residues (FCR), the fractions of positive and negative residues and + the overall charge asymmetry (sigma) describe the charge content of a protein + sequence, and are key parameters of the conformations of intrinsically + disordered proteins [1, 2]. They are calculated as: + + FCR = (n+ + n-) / L + + FPlus = n+ / L, FMinus = n- / L + + Sigma = NCPR^2 / FCR + + where n+ is the number of positive residues (K, R), n- the number of negative + residues (D, E), L is the length of the sequence and NCPR = (n+ - n-) / L. His + is treated as neutral and sigma is 0 when there are no charged residues. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :charge_fractions_df: pd.DataFrame + pandas dataframe of charge fractions for protein sequence. Dataframe will + be of the shape 1 x 4, where 4 is the number of features calculated from + the descriptor and 1 is the input sequence. Column names are Charge_FCR, + Charge_FPlus, Charge_FMinus and Charge_Sigma. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + + q = _charge_vector(sequence) + f_plus, f_minus = np.mean(q > 0), np.mean(q < 0) + fcr, ncpr_ = f_plus + f_minus, f_plus - f_minus + sigma = ncpr_ ** 2 / fcr if fcr else 0.0 + #prefixed so "FCR" cannot clash with the tripeptide-composition column of the same name + return _to_df([fcr, f_plus, f_minus, sigma], ["Charge_FCR", "Charge_FPlus", "Charge_FMinus", "Charge_Sigma"]) + +#################################### NCPR ################################################# + +def ncpr(sequence: str) -> pd.DataFrame: + """ + Calculate the Net Charge Per Residue (NCPR) of protein sequence. NCPR is the + net charge of a protein sequence divided by its length [1], calculated as: + + NCPR = (n+ - n-) / L + + where n+ is the number of positive residues (K, R), n- the number of negative + residues (D, E) and L is the length of the sequence. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :ncpr_df: pd.DataFrame + pandas dataframe of NCPR for protein sequence. Dataframe will be of the + shape 1 x 1, with the column NCPR. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + return _to_df([_charge_vector(sequence).mean()], ["NCPR"]) + +#################################### Kappa ################################################ + +def _blob_sigma(pos: np.ndarray, neg: np.ndarray, g: int) -> np.ndarray: + """ + Calculate the charge asymmetry of every overlapping blob (window) of size g + along one or more sequences, as used in the kappa calculation [2], where: + + sigma_b = (f+ - f-)^2 / (f+ + f-) + + and f+ and f- are the fractions of positive and negative residues in the blob. + Uncharged blobs have a charge asymmetry of 0. + + Parameters + ========== + :pos: np.ndarray + 1 for positive residues and 0 otherwise; one row per sequence when 2-D. + :neg: np.ndarray + 1 for negative residues and 0 otherwise, same shape as pos. + :g: int + blob size. + + Returns + ======= + :blob_sigma: np.ndarray + charge asymmetry of each blob, with one row per sequence when 2-D. + """ + kernel = np.ones(g) + if pos.ndim == 1: + p = np.convolve(pos, kernel, mode="valid") + m = np.convolve(neg, kernel, mode="valid") + else: + #blob counts from prefix sums, one row per candidate sequence + cp = np.concatenate([np.zeros((pos.shape[0], 1)), np.cumsum(pos, axis=1)], axis=1) + cm = np.concatenate([np.zeros((neg.shape[0], 1)), np.cumsum(neg, axis=1)], axis=1) + p, m = cp[:, g:] - cp[:, :-g], cm[:, g:] - cm[:, :-g] + total = p + m + return np.divide((p - m) ** 2, total * g, out=np.zeros_like(total, dtype=float), where=total > 0) + + +def _delta(pos: np.ndarray, neg: np.ndarray, g: int, sigma: float) -> np.ndarray: + """ + Calculate the mean squared deviation (delta) of the blob charge asymmetries + from the charge asymmetry of the whole sequence, as used in the kappa + calculation [2]. + + Parameters + ========== + :pos: np.ndarray + 1 for positive residues and 0 otherwise; one row per sequence when 2-D. + :neg: np.ndarray + 1 for negative residues and 0 otherwise, same shape as pos. + :g: int + blob size. + :sigma: float + charge asymmetry of the whole sequence. + + Returns + ======= + :delta: np.ndarray + delta of each sequence (a single value for a 1-D input). + """ + return np.mean((_blob_sigma(pos, neg, g) - sigma) ** 2, axis=-1) + + +def _delta_max(n_pos: int, n_neg: int, length: int, g: int, sigma: float) -> float: + """ + Calculate the maximum delta of a sequence composition, used to normalise kappa + [2]. The maximum is taken over the most segregated arrangements of the + composition: a block of positive residues and a block of negative residues, + with the neutral residues split across the three gaps around them. Blobs lying + entirely in a neutral flank contribute the same value whatever the length of + the flank, so for each gap between the blocks only flank lengths up to g - 1 + need to be tried. This evaluates every distinct arrangement in O(n0 * g) + candidates, where n0 is the number of neutral residues. + + Parameters + ========== + :n_pos: int + number of positive residues. + :n_neg: int + number of negative residues. + :length: int + length of the sequence. + :g: int + blob size. + :sigma: float + charge asymmetry of the whole sequence. + + Returns + ======= + :best: float + maximum delta over the segregated arrangements. + """ + n0 = length - n_pos - n_neg + candidates = set() + for b in range(n0 + 1): + rest = n0 - b + for k in range(min(g, rest + 1)): + candidates.add((k, b)) + candidates.add((rest - k, b)) + candidates = np.array(sorted(candidates)) + idx = np.arange(length) + best = 0.0 + #chunk the (candidate x residue) arrays to bound memory on long sequences + for chunk in np.array_split(candidates, max(1, len(candidates) // 1000)): + a, b = chunk[:, :1], chunk[:, 1:] + pos = ((idx >= a) & (idx < a + n_pos)).astype(float) + neg = ((idx >= a + n_pos + b) & (idx < a + n_pos + b + n_neg)).astype(float) + best = max(best, float(_delta(pos, neg, g, sigma).max())) + return best + + +def kappa(sequence: str, blob_sizes: tuple[int, ...] = (5, 6)) -> pd.DataFrame: + """ + Calculate the Kappa (Kappa) charge patterning parameter of protein sequence. + Kappa describes how well oppositely charged residues are mixed or segregated + along a protein sequence, which strongly affects the conformations of + intrinsically disordered proteins [2]. The sequence is split into overlapping + blobs of g residues and kappa is calculated as: + + sigma_b = (f+ - f-)^2 / (f+ + f-) + + delta_g = mean over all blobs of (sigma_b - sigma)^2 + + kappa_g = delta_g / delta_max,g + + where sigma_b is the charge asymmetry of a blob, f+ and f- are its fractions of + positive (K, R) and negative (D, E) residues, sigma = NCPR^2 / FCR is the charge + asymmetry of the whole sequence and delta_max,g is the largest delta over the + most segregated arrangements of the same composition. The final kappa is the + mean of kappa_g over the blob sizes (5 and 6 by default). Kappa ranges from 0 + (well mixed) to 1 (fully segregated). It is reported as 0 when the sequence has + no charged residues or only one type of charge, and blobs longer than the + sequence are reduced to its length. + + Parameters + ========== + :sequence: str + protein sequence. + :blob_sizes: tuple (default=(5, 6)) + blob sizes g to average kappa over. + + Returns + ======= + :kappa_df: pd.DataFrame + pandas dataframe of kappa values for protein sequence. Dataframe will be of + the shape 1 x (1 + number of blob sizes), where the first column (Kappa) is + the mean and the remaining columns (Kappa_g) are the values for each blob + size. By default the shape will be 1 x 3. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + blob_sizes = [_validate_int("blob size", g, 1) for g in blob_sizes] + if not blob_sizes: + raise ValueError("At least one blob size must be given.") + + q = _charge_vector(sequence) + pos, neg = (q > 0).astype(float), (q < 0).astype(float) + n_pos, n_neg, L = int(pos.sum()), int(neg.sum()), len(sequence) + values = [] + for g in blob_sizes: + if n_pos == 0 or n_neg == 0: + values.append(0.0) + continue + g = min(g, L) + sigma = (n_pos - n_neg) ** 2 / (L * (n_pos + n_neg)) + d_max = _delta_max(n_pos, n_neg, L, g, sigma) + values.append(min(float(_delta(pos, neg, g, sigma)) / d_max, 1.0) if d_max else 0.0) + return _to_df([float(np.mean(values))] + values, ["Kappa"] + [f"Kappa_{g}" for g in blob_sizes]) + +########################### Sequence Charge Decoration #################################### + +def sequence_charge_decoration(sequence: str) -> pd.DataFrame: + """ + Calculate the Sequence Charge Decoration (SCD) of protein sequence. SCD + describes the patterning of charges along a protein sequence, weighting each + pair of charges by the square root of their separation [3]. It is calculated as: + + SCD = (1/L) * sum_{m=2}^{L} sum_{n=1}^{m-1} q_m * q_n * (m - n)^(1/2) + + where q_m and q_n are the charges of the residues at positions m and n (+1 for + K and R, -1 for D and E, 0 otherwise) and L is the length of the sequence. More + negative values indicate more segregated opposite charges and uncharged + sequences have an SCD of 0. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :scd_df: pd.DataFrame + pandas dataframe of SCD for protein sequence. Dataframe will be of the + shape 1 x 1, with the column Charge_SCD. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + + q = _charge_vector(sequence) + idx = np.flatnonzero(q) + #only charged residues contribute; pairwise over them is O(n_charged^2) + m, n = np.triu_indices(len(idx), k=1) + scd = np.sum(q[idx[m]] * q[idx[n]] * np.sqrt(idx[n] - idx[m])) / len(sequence) + #prefixed so "SCD" cannot clash with the tripeptide-composition column of the same name + return _to_df([scd], ["Charge_SCD"]) + +#################################### Charge Density ####################################### + +def charge_density(sequence: str, ph: float = 7.4) -> pd.DataFrame: + """ + Calculate the Charge Density of protein sequence. Charge density is the net + charge of a protein sequence at a given pH divided by its molecular weight [4], + calculated as: + + ChargeDensity = NetCharge(pH) / MW + + where NetCharge(pH) is calculated using the charge_distribution function and MW + using the molecular_weight function. + + Parameters + ========== + :sequence: str + protein sequence. + :ph: float (default=7.4) + pH at which to calculate the net charge. + + Returns + ======= + :charge_density_df: pd.DataFrame + pandas dataframe of charge density (charges per Da) for protein sequence. + Dataframe will be of the shape 1 x 1, with the column ChargeDensity. The + value is rounded to 6 decimal places because it is typically of order 1e-4. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + + net = charge_distribution(sequence, ph=ph)["NetCharge"].iloc[0] + mw = molecular_weight(sequence)["MolecularWeight"].iloc[0] + return pd.DataFrame([[round(net / mw, 6)]], columns=["ChargeDensity"]) + +############################### Hydrophobicity Profile #################################### + +def hydrophobicity_profile(sequence: str, scale: str = "kyte_doolittle", window: int = 9, edge: float = 1.0, + threshold: float | None = None) -> pd.DataFrame: + """ + Calculate the Hydrophobicity Profile (HP) of protein sequence. Each amino acid + in the sequence is replaced by its value on a hydrophobicity scale and the + values are averaged over a sliding window, giving a profile along the sequence + [5, 6]. The profile is summarised by its mean, maximum, minimum and standard + deviation, and optionally the fraction of windows above a threshold, e.g. 1.6 + for transmembrane segments with the Kyte-Doolittle scale [5]. The window value + is calculated as: + + HP(i) = sum_j w_j * H(s_i+j) / sum_j w_j + + where H is the hydrophobicity scale and w_j are the window weights. With + edge=1.0 all weights are 1 (a plain mean); with edge < 1 the weights fall + linearly from 1 at the centre of the window to edge at its ends, as in ExPASy + ProtScale. Windows longer than the sequence use the whole sequence. + + Parameters + ========== + :sequence: str + protein sequence. + :scale: str (default="kyte_doolittle") + single-value hydrophobicity scale: "kyte_doolittle", "hopp_woods", + "eisenberg", "wimley_white", "engelman", "domcut" or any AAindex + accession number. + :window: int (default=9) + length of the sliding window. + :edge: float (default=1.0) + relative weight of the ends of the window, between 0 and 1. + :threshold: float | None (default=None) + if set, also calculate the fraction of windows with a value above it. + + Returns + ======= + :profile_df: pd.DataFrame + pandas dataframe of hydrophobicity profile statistics for protein sequence. + Dataframe will be of the shape 1 x 4 (1 x 5 with a threshold), with the + columns HP__w_mean, _max, _min, _std and _frac_above. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + window = _validate_int("window", window, 1) + if not 0 <= edge <= 1: + raise ValueError(f"edge must be between 0 and 1, got {edge}.") + + values = _property_matrix(sequence, scale) + if values.shape[1] != 1: + raise ValueError(f"Scale '{scale}' is multi-dimensional; use a single-value scale.") + profile = _sliding_windows(values[:, 0], window, edge) + stats = _profile_stats(profile, f"HP_{scale}_w{window}", threshold=threshold) + return _to_df(list(stats.values()), list(stats)) + +################################ Hydrophobic Ratio ######################################## + +#hydrophobic residues as defined by the APD [7] +_apd_hydrophobic = frozenset("LIVFMACW") + +def hydrophobic_ratio(sequence: str) -> pd.DataFrame: + """ + Calculate the Hydrophobic Ratio of protein sequence. The hydrophobic ratio is + the percentage of hydrophobic residues in a protein sequence, as defined by + the Antimicrobial Peptide Database (APD) [7], calculated as: + + HydrophobicRatio = N(LIVFMACW) / L * 100 + + where N(LIVFMACW) is the number of L, I, V, F, M, A, C and W residues and L + is the length of the sequence. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :hydrophobic_ratio_df: pd.DataFrame + pandas dataframe of hydrophobic ratio for protein sequence. Dataframe will + be of the shape 1 x 1, with the column HydrophobicRatio. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + return _to_df([sum(aa in _apd_hydrophobic for aa in sequence) / len(sequence) * 100], ["HydrophobicRatio"]) + +#################################### Half Life ############################################ + +def half_life(sequence: str) -> pd.DataFrame: + """ + Calculate the estimated Half Life of protein sequence. The half-life is a + prediction of the time it takes for half of the amount of a protein in a cell + to disappear after its synthesis. It is estimated from the N-terminal residue + using the N-end rule [8, 9, 10], for 3 model systems: mammalian reticulocytes + (in vitro), yeast and E. coli (in vivo), using the values tabulated by ExPASy + ProtParam [6]. Values given as lower bounds in ProtParam (">20 hours" for yeast, + ">10 hours" for E. coli) are reported as the bound. The E. coli value for an + N-terminal proline is unknown in ProtParam and returned as NaN. The estimate is + not applicable to N-terminally modified proteins. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :half_life_df: pd.DataFrame + pandas dataframe of half-lives (hours) for protein sequence. Dataframe will + be of the shape 1 x 3, with the columns HalfLife_Mammal_h, HalfLife_Yeast_h + and HalfLife_Ecoli_h. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + + row = _load_json_table("residue_tables.json", "n_end_half_life")["values"][sequence[0]] + values = [np.nan if row[k] is None else row[k] for k in ("mammal", "yeast", "ecoli")] + return _to_df(values, ["HalfLife_Mammal_h", "HalfLife_Yeast_h", "HalfLife_Ecoli_h"]) + +################################## IVYWREL Index ########################################## + +def ivywrel_index(sequence: str) -> pd.DataFrame: + """ + Calculate the IVYWREL Index of protein sequence. The IVYWREL index is the + fraction of I, V, Y, W, R, E and L residues in a protein sequence, which + correlates with the optimal growth temperature of the organism [11]. It is + calculated as: + + IVYWREL = N(IVYWREL) / L + + where N(IVYWREL) is the number of I, V, Y, W, R, E and L residues and L is the + length of the sequence. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :ivywrel_df: pd.DataFrame + pandas dataframe of IVYWREL index for protein sequence. Dataframe will be + of the shape 1 x 1, with the column IVYWREL. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + return _to_df([sum(aa in "IVYWREL" for aa in sequence) / len(sequence)], ["IVYWREL"]) + +#################################### CvP Bias ############################################# + +def cvp_bias(sequence: str) -> pd.DataFrame: + """ + Calculate the Charged versus Polar Bias (CvP) of protein sequence. The CvP bias + is the difference between the percentages of charged and polar residues in a + protein sequence, an indicator of hyperthermostability [12], calculated as: + + CvP = %(D + E + K + R) - %(N + Q + S + T) + + where %(X) is the percentage of residues of type X in the sequence. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :cvp_df: pd.DataFrame + pandas dataframe of CvP bias for protein sequence. Dataframe will be of the + shape 1 x 1, with the column CvP_Bias. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + charged = sum(aa in "DEKR" for aa in sequence) + polar = sum(aa in "NQST" for aa in sequence) + return _to_df([(charged - polar) / len(sequence) * 100], ["CvP_Bias"]) + +################################### Flexibility ########################################### + +def flexibility(sequence: str, window: int = 9) -> pd.DataFrame: + """ + Calculate the Flexibility (Flex) profile of protein sequence. Each amino acid + in the sequence is replaced by its normalised flexibility parameter (B-value) + and the values are averaged over a sliding window of 9 residues whose end + weights fall linearly to 0.25 (weights 0.25, 0.4375, 0.625, 0.8125, 1, ..., + 0.25), as in Vihinen et al. [13]. The profile is summarised by its mean, + maximum, minimum and standard deviation. The average B-values of Vihinen et + al. are used; their parameter sets for residues with 0, 1 or 2 rigid + neighbours are not. Compared with Biopython's ProteinAnalysis.flexibility, + which uses the same values and weights, each window is centred on its middle + residue and the last window is included. + + Parameters + ========== + :sequence: str + protein sequence. + :window: int (default=9) + length of the sliding window (the parameters were optimised for 9). + + Returns + ======= + :flexibility_df: pd.DataFrame + pandas dataframe of flexibility profile statistics for protein sequence. + Dataframe will be of the shape 1 x 4, with the columns Flex_mean, Flex_max, + Flex_min and Flex_std. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + window = _validate_int("window", window, 1) + + profile = _sliding_windows(_property_matrix(sequence, "vihinen_flexibility")[:, 0], window, edge=0.25) + stats = _profile_stats(profile, "Flex") + return _to_df(list(stats.values()), list(stats)) diff --git a/protpy/scales.py b/protpy/scales.py new file mode 100644 index 0000000..121a86f --- /dev/null +++ b/protpy/scales.py @@ -0,0 +1,574 @@ +################################################################################ +############# Amino Acid Scales ############## +################################################################################ + +from __future__ import annotations + +""" +Amino-acid factor scales (z-scales, VHSE, Atchley factors, T-/ST-scales, +ProtFP, FASGAI, BLOSUM indices, MS-WHIM) and the auto/cross-covariance and +physicochemical distance transformations built on them. + +References +========== +[1] Sandberg, M. et al. (1998). New chemical descriptors relevant for the design + of biologically active peptides. A multivariate characterization of 87 amino + acids. J Med Chem, 41(14), 2481-2491. https://doi.org/10.1021/jm9700575 +[2] Mei, H. et al. (2005). A new set of amino acid descriptors and its application + in peptide QSARs. Biopolymers, 80(6), 775-786. https://doi.org/10.1002/bip.20296 +[3] Atchley, W. R. et al. (2005). Solving the protein sequence metric problem. + PNAS, 102(18), 6395-6400. https://doi.org/10.1073/pnas.0408677102 +[4] Tian, F., Zhou, P., & Li, Z. (2007). T-scale as a novel vector of topological + descriptors for amino acids and its application in QSARs of peptides. J Mol + Struct, 830, 106-115. https://doi.org/10.1016/j.molstruc.2006.07.004 +[5] Yang, L. et al. (2010). ST-scale as a novel amino acid descriptor and its + application in QSAM of peptides and analogues. Amino Acids, 38(3), 805-816. + https://doi.org/10.1007/s00726-009-0287-y +[6] van Westen, G. J. P. et al. (2013). Benchmarking of protein descriptor sets in + proteochemometric modeling (part 1): comparative study of 13 amino acid + descriptor sets. J Cheminform, 5, 41. https://doi.org/10.1186/1758-2946-5-41 +[7] Liang, G., & Li, Z. (2007). Factor analysis scale of generalized amino acid + information as the source of a new set of descriptors for elucidating the + structure and activity relationships of cationic antimicrobial peptides. QSAR + Comb Sci, 26(6), 754-763. https://doi.org/10.1002/qsar.200630145 +[8] Georgiev, A. G. (2009). Interpretable numerical descriptors of amino acid + space. J Comput Biol, 16(5), 703-723. https://doi.org/10.1089/cmb.2008.0173 +[9] Zaliani, A., & Gancia, E. (1999). MS-WHIM scores for amino acids: a new + 3D-description for peptide QSAR and QSPR studies. J Chem Inf Comput Sci, + 39(3), 525-533. https://doi.org/10.1021/ci980211b +[10] Wold, S. et al. (1993). DNA and peptide sequences and chemical processes + multivariately modelled by principal component analysis and partial + least-squares projections to latent structures. Anal Chim Acta, 277(2), + 239-253. https://doi.org/10.1016/0003-2670(93)80437-P +[11] Lapinsh, M. et al. (2002). Development of proteo-chemometrics: a novel + technology for the analysis of drug-receptor interactions. Mol Pharmacol, + 61(6), 1465. https://doi.org/10.1124/mol.61.6.1465 +[12] Liu, B. et al. (2012). Using amino acid physicochemical distance + transformation for fast protein remote homology detection. PLoS ONE, 7(9), + e46633. https://doi.org/10.1371/journal.pone.0046633 + +The values of [1, 2, 4, 5, 7, 8, 9] were transcribed from the CC-BY descriptor +component published with [6]; see protpy/data/aa_scales.json for provenance. +""" + +import itertools + +import numpy as np +import pandas as pd + +from ._constants import _validate_sequence, _validate_int +from ._helpers import _property_matrix, _read_json, _to_df + +__all__ = [ + "aa_scale_descriptors", "z_scales_5", "vhse", "atchley_factors", "t_scales", "st_scales", + "protfp", "fasgai", "blosum_indices", "ms_whim", "auto_cross_covariance", + "physicochemical_distance_transformation", +] + +#factor-scale families: name -> column abbreviation +factor_scales_ = { + "z_scales_5": "Z5", + "vhse": "VHSE", + "atchley_factors": "AF", + "t_scales": "T", + "st_scales": "ST", + "protfp": "ProtFP", + "fasgai": "FASGAI", + "blosum_indices": "BLOSUM", + "ms_whim": "MSWHIM", +} + +#default AAindex properties for PDT (same 8 as the autocorrelation descriptors) +_default_pdt_properties = ["CIDH920105", "BHAR880101", "CHAM820101", "CHAM820102", + "CHOC760101", "BIGC670101", "CHAM810101", "DAYM780201"] + +################################## Factor Scale Descriptors ############################### + +def aa_scale_descriptors(sequence: str, scale: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate an amino acid factor-scale descriptor of protein sequence. Factor + scales describe each amino acid with a small number of values (dimensions) + derived from many physicochemical properties, typically by principal component + or factor analysis [1-9]. Each amino acid in the sequence is replaced by its + d-dimensional scale vector and the mean of each dimension is taken over the + sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for dimension j (j = 1,2,..,d), z_j(s_i) is + the value of dimension j for the amino acid at position i and L is the length + of the sequence. With domains > 1 the sequence is split into that many + near-equal contiguous segments and the means of each segment are concatenated. + The individual scales are also available as their own functions, e.g. vhse(). + + Parameters + ========== + :sequence: str + protein sequence. + :scale: str + factor-scale family to use: "z_scales_5", "vhse", "atchley_factors", + "t_scales", "st_scales", "protfp", "fasgai", "blosum_indices" or "ms_whim". + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over, + at most the length of the sequence. + + Returns + ======= + :scale_df: pd.DataFrame + pandas dataframe of factor-scale values for protein sequence. Dataframe + will be of the shape 1 x d * domains, where d is the number of dimensions + of the scale, d * domains is the number of features calculated from the + descriptor and 1 is the input sequence. Column names are of the form + ABBR_j, or ABBR_j_dk when domains > 1, e.g. VHSE_1 or VHSE_1_d2. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + if scale not in factor_scales_: + raise ValueError(f"Invalid scale '{scale}'. Valid scales are: {list(factor_scales_)}.") + domains = _validate_int("domains", domains, 1) + if domains > len(sequence): + raise ValueError(f"domains ({domains}) cannot exceed the sequence length ({len(sequence)}).") + + abbr = factor_scales_[scale] + matrix = _property_matrix(sequence, scale) + values, columns = [], [] + for k, segment in enumerate(np.array_split(matrix, domains), start=1): + values += list(segment.mean(axis=0)) + suffix = f"_d{k}" if domains > 1 else "" + columns += [f"{abbr}_{j}{suffix}" for j in range(1, matrix.shape[1] + 1)] + return _to_df(values, columns) + + +def z_scales_5(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate Sandberg 5 z-scales (Z5) of protein sequence. The z-scales are the + principal components of a set of physicochemical properties measured for 87 + coded and non-coded amino acids [1]. z1 mainly describes lipophilicity, z2 + steric bulk and polarizability, z3 polarity and charge, and z4 and z5 + electronegativity, heat of formation, electrophilicity and hardness. + + Each amino acid in the sequence is replaced by its 5 z-scales and the mean of + each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :z_scales_5_df: pd.DataFrame + pandas dataframe of Z5 values for protein sequence. Dataframe will be + of the shape 1 x 5 * domains, where 5 * domains is the number of + features calculated from the descriptor (5 scales for each domain) and + 1 is the input sequence. Column names are of the form Z5_j, or + Z5_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "z_scales_5", domains) + +def vhse(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate Vectors of Hydrophobic, Steric and Electronic properties (VHSE) of + protein sequence. The VHSE scales are principal components score vectors derived + from physicochemical variables of the 20 coded amino acids [2]. VHSE1-2 describe + hydrophobic properties, VHSE3-4 steric properties and VHSE5-8 electronic + properties. + + Each amino acid in the sequence is replaced by its 8 VHSE scales and the mean of + each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :vhse_df: pd.DataFrame + pandas dataframe of VHSE values for protein sequence. Dataframe will be + of the shape 1 x 8 * domains, where 8 * domains is the number of + features calculated from the descriptor (8 scales for each domain) and + 1 is the input sequence. Column names are of the form VHSE_j, or + VHSE_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "vhse", domains) + +def atchley_factors(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate Atchley Factors (AF) of protein sequence. The Atchley factors are 5 + factors obtained from a factor analysis of 54 amino acid attributes [3]. Factor + I mainly reflects polarity, accessibility and hydrophobicity, factor II + propensity for secondary structure, factor III molecular size, factor IV + relative amino acid composition and factor V electrostatic charge. + + Each amino acid in the sequence is replaced by its 5 Atchley factors and the + mean of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :atchley_factors_df: pd.DataFrame + pandas dataframe of AF values for protein sequence. Dataframe will be + of the shape 1 x 5 * domains, where 5 * domains is the number of + features calculated from the descriptor (5 scales for each domain) and + 1 is the input sequence. Column names are of the form AF_j, or + AF_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "atchley_factors", domains) + +def t_scales(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate Topological Scales (T-scales) of protein sequence. The T-scales are + principal components of topological descriptors of amino acids, describing their + structural and topological characteristics [4]. + + Each amino acid in the sequence is replaced by its 5 T-scales and the mean of + each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :t_scales_df: pd.DataFrame + pandas dataframe of T values for protein sequence. Dataframe will be + of the shape 1 x 5 * domains, where 5 * domains is the number of + features calculated from the descriptor (5 scales for each domain) and + 1 is the input sequence. Column names are of the form T_j, or + T_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "t_scales", domains) + +def st_scales(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate Structural Topology Scales (ST-scales) of protein sequence. The ST- + scales are principal components of 3D structural and topological properties of + amino acids [5]. + + Each amino acid in the sequence is replaced by its 8 ST-scales and the mean of + each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :st_scales_df: pd.DataFrame + pandas dataframe of ST values for protein sequence. Dataframe will be + of the shape 1 x 8 * domains, where 8 * domains is the number of + features calculated from the descriptor (8 scales for each domain) and + 1 is the input sequence. Column names are of the form ST_j, or + ST_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "st_scales", domains) + +def protfp(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate ProtFP (ProtFP) of protein sequence. ProtFP is a set of 8 principal + components calculated from a selection of physicochemical properties in the + AAindex database [6]. + + Each amino acid in the sequence is replaced by its 8 ProtFP components and the + mean of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :protfp_df: pd.DataFrame + pandas dataframe of ProtFP values for protein sequence. Dataframe will be + of the shape 1 x 8 * domains, where 8 * domains is the number of + features calculated from the descriptor (8 scales for each domain) and + 1 is the input sequence. Column names are of the form ProtFP_j, or + ProtFP_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "protfp", domains) + +def fasgai(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate Factor Analysis Scales of Generalized Amino Acid Information (FASGAI) + of protein sequence. The FASGAI scales are 6 factors obtained from a factor + analysis of amino acid properties, describing hydrophobicity, alpha and turn + propensities, bulky properties, compositional characteristics, local flexibility + and electronic properties [7]. + + Each amino acid in the sequence is replaced by its 6 FASGAI factors and the mean + of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :fasgai_df: pd.DataFrame + pandas dataframe of FASGAI values for protein sequence. Dataframe will be + of the shape 1 x 6 * domains, where 6 * domains is the number of + features calculated from the descriptor (6 scales for each domain) and + 1 is the input sequence. Column names are of the form FASGAI_j, or + FASGAI_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "fasgai", domains) + +def blosum_indices(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate BLOSUM Indices (BLOSUM) of protein sequence. The BLOSUM indices are 10 + factors derived from the BLOSUM62 substitution matrix by principal component + analysis and VARIMAX rotation, so they describe the evolutionary similarity of + amino acids [8]. + + Each amino acid in the sequence is replaced by its 10 BLOSUM indices and the + mean of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :blosum_indices_df: pd.DataFrame + pandas dataframe of BLOSUM values for protein sequence. Dataframe will be + of the shape 1 x 10 * domains, where 10 * domains is the number of + features calculated from the descriptor (10 scales for each domain) and + 1 is the input sequence. Column names are of the form BLOSUM_j, or + BLOSUM_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "blosum_indices", domains) + +def ms_whim(sequence: str, domains: int = 1) -> pd.DataFrame: + """ + Calculate Molecular Surface Weighted Holistic Invariant Molecular Scores (MS- + WHIM) of protein sequence. The MS-WHIM scores are 3 principal components of 3D + descriptors calculated from the molecular electrostatic potential on the surface + of each amino acid [9]. + + Each amino acid in the sequence is replaced by its 3 MS-WHIM scores and the mean + of each is taken over the sequence, calculated as: + + S_j = (1/L) * sum_i z_j(s_i) + + where S_j is the descriptor value for scale j, z_j(s_i) is the value of scale + j for the amino acid at position i and L is the length of the sequence. With + domains > 1 the sequence is split into that many near-equal segments and the + means of each segment are concatenated. + + Parameters + ========== + :sequence: str + protein sequence. + :domains: int (default=1) + number of near-equal segments of the sequence to calculate the means over. + + Returns + ======= + :ms_whim_df: pd.DataFrame + pandas dataframe of MSWHIM values for protein sequence. Dataframe will be + of the shape 1 x 3 * domains, where 3 * domains is the number of + features calculated from the descriptor (3 scales for each domain) and + 1 is the input sequence. Column names are of the form MSWHIM_j, or + MSWHIM_j_dk when domains > 1. + """ + return aa_scale_descriptors(sequence, "ms_whim", domains) + +############################## Auto/Cross Covariance ###################################### + +def auto_cross_covariance(sequence: str, scale: str | list[str] = "z_scales_5", lag: int = 5, + mode: str = "acc") -> pd.DataFrame: + """ + Calculate Auto Cross Covariance (ACC) of protein sequence. ACC transforms the + amino acid scale values of a sequence into a fixed-length vector that + describes the covariance between the scale dimensions of residues separated + by l positions, for each lag l in the range 1..lag [10, 11]. It is calculated as: + + ACC_jk(l) = sum_{i=1}^{L-l} z_j(s_i) * z_k(s_i+l) / (L - l) + + where ACC_jk(l) is the covariance between dimensions j and k at lag l, z_j(s_i) + is the value of dimension j for the amino acid at position i and L is the length + of the sequence. When j = k this is the auto covariance and when j != k the cross + covariance. The scale can be one of the factor-scale families, which are used as + published, or one or more AAindex accession numbers, whose values are first + standardised over the 20 amino acids. + + Parameters + ========== + :sequence: str + protein sequence. + :scale: str | list[str] (default="z_scales_5") + factor-scale family, single-value scale name, AAindex accession number or + list of AAindex accession numbers. + :lag: int (default=5) + maximum lag, must be less than the length of the sequence. + :mode: str (default="acc") + which covariances to return: "acc" (all dimension pairs), "ac" (auto + covariance only, j = k) or "cc" (cross covariance only, j != k). + + Returns + ======= + :acc_df: pd.DataFrame + pandas dataframe of ACC values for protein sequence. Dataframe will be of + the shape 1 x N, where N is the number of features calculated from the + descriptor and 1 is the input sequence; N is d^2 * lag for "acc", d * lag + for "ac" and d(d-1) * lag for "cc", where d is the number of dimensions of + the scale. By default (5 z-scales, lag=5) the shape will be 1 x 125. Column + names are of the form ACC_j_k_lagl, e.g. ACC_1_2_lag3. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + lag = _validate_int("lag", lag, 1) + if lag >= len(sequence): + raise ValueError(f"lag ({lag}) must be less than the sequence length ({len(sequence)}).") + if mode not in ("acc", "ac", "cc"): + raise ValueError(f"Invalid mode '{mode}'. Valid modes are: ['acc', 'ac', 'cc'].") + + #published scale tables are used as-is; raw AAindex properties are standardised + standardize = not (isinstance(scale, str) and scale in _read_json("aa_scales.json")) + z = _property_matrix(sequence, scale, standardize=standardize) + d = z.shape[1] + pairs = [(j, k) for j, k in itertools.product(range(d), repeat=2) + if mode == "acc" or (mode == "ac") == (j == k)] + values, columns = [], [] + for l in range(1, lag + 1): + #cov[j, k] = mean over i of z[i, j] * z[i + l, k] + #einsum instead of matmul: macOS Accelerate BLAS raises spurious float warnings + cov = np.einsum("ij,ik->jk", z[:-l], z[l:]) / (len(sequence) - l) + values += [cov[j, k] for j, k in pairs] + columns += [f"ACC_{j + 1}_{k + 1}_lag{l}" for j, k in pairs] + return _to_df(values, columns) + +########################## Physicochemical Distance Transformation ######################## + +def physicochemical_distance_transformation(sequence: str, lag: int = 3, + properties: list[str] | None = None) -> pd.DataFrame: + """ + Calculate Physicochemical Distance Transformation (PDT) of protein sequence. + PDT describes how much a physicochemical property changes between residues + separated by l positions, for each lag l in the range 1..lag [12]. For each + AAindex property it is calculated as: + + PDT_j(l) = sum_{i=1}^{L-l} (P_j(s_i) - P_j(s_i+l))^2 / (L - l) + + where PDT_j(l) is the descriptor value for property j at lag l, P_j(s_i) is the + value of property j for the amino acid at position i, standardised over the 20 + amino acids, and L is the length of the sequence. By default the same 8 AAindex + properties as the autocorrelation descriptors are used. + + Parameters + ========== + :sequence: str + protein sequence. + :lag: int (default=3) + maximum lag, must be less than the length of the sequence. + :properties: list[str] | None (default=None) + list of AAindex accession numbers for the properties to use. Defaults to + the 8 properties used by the autocorrelation descriptors. + + Returns + ======= + :pdt_df: pd.DataFrame + pandas dataframe of PDT values for protein sequence. Dataframe will be of + the shape 1 x N, where N is the number of features calculated from the + descriptor (number of properties * lag) and 1 is the input sequence. By + default the shape will be 1 x 24. Column names are of the form + PDT__l, e.g. PDT_CIDH920105_1. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + lag = _validate_int("lag", lag, 1) + if lag >= len(sequence): + raise ValueError(f"lag ({lag}) must be less than the sequence length ({len(sequence)}).") + properties = list(properties) if properties is not None else _default_pdt_properties + if not properties: + raise ValueError("At least one property must be given.") + + p = _property_matrix(sequence, properties, standardize=True) + values, columns = [], [] + for j, code in enumerate(properties): + for l in range(1, lag + 1): + values.append(np.mean((p[:-l, j] - p[l:, j]) ** 2)) + columns.append(f"PDT_{code}_{l}") + return _to_df(values, columns) diff --git a/protpy/sequence_order.py b/protpy/sequence_order.py index e783c51..1629ec1 100644 --- a/protpy/sequence_order.py +++ b/protpy/sequence_order.py @@ -31,7 +31,7 @@ """ #list of amino acids -from ._constants import amino_acids, _validate_sequence +from ._constants import amino_acids, _validate_sequence, _validate_int, _validate_weight @functools.lru_cache(maxsize=2) def _load_distance_matrix(name: str) -> dict: @@ -129,11 +129,8 @@ def sequence_order_coupling_number(sequence: str, lag: int = 30, #validate input protein sequence sequence = _validate_sequence(sequence) - #raise value error if int cant be parsed from input lag - try: - lag = int(lag) - except (ValueError, TypeError): - raise ValueError(f"Invalid lag value input: {lag}.") + #raise value error if a non-negative int cant be parsed from input lag + lag = _validate_int("lag", lag) #set dataframe column name prefixes based on input distance matrix, raise error if invalid name input col_prefix = "" @@ -145,7 +142,7 @@ def sequence_order_coupling_number(sequence: str, lag: int = 30, raise ValueError(f"Invalid distance matrix name input {distance_matrix}, values can be schneider-wrede or grantham.") #validate lag, set default lag of 30 if invalid value input - if (lag>=len(sequence) or (lag<0)): + if (lag>=len(sequence)): lag = 30 seq_order = {} @@ -191,14 +188,11 @@ def sequence_order_coupling_number_all(sequence: str, lag: int = 30) -> pd.DataF #validate input protein sequence sequence = _validate_sequence(sequence) - #raise value error if int cant be parsed from input lag - try: - lag = int(lag) - except (ValueError, TypeError): - raise ValueError(f"Invalid lag value input: {lag}.") + #raise value error if a non-negative int cant be parsed from input lag + lag = _validate_int("lag", lag) #validate lag, set default lag of 30 if invalid value input - if (lag>=len(sequence) or (lag<0)): + if (lag>=len(sequence)): lag = 30 #calculate SOCN using schneider-wrede distance matrix @@ -248,19 +242,15 @@ def quasi_sequence_order(sequence: str, lag: int = 30, weight: float = 0.1, #validate input protein sequence sequence = _validate_sequence(sequence) - #raise value error if int can't be parsed from input lag - try: - lag = int(lag) - except (ValueError, TypeError): - raise ValueError(f"Invalid lag value input: {lag}.") + #raise value error if a non-negative int can't be parsed from input lag + lag = _validate_int("lag", lag) #validate lag, set default lag of 30 if invalid value input - if (lag>=len(sequence) or (lag<0)): + if (lag>=len(sequence)): lag = 30 - #validate weight, set default weight of 0.1 if invalid value input - if ((weight < 0) or not (isinstance(weight, (float, int)))): - weight = 0.1 + #validate weight + weight = _validate_weight(weight) #set dataframe column name prefixes based on input distance matrix, raise error if invalid name input col_prefix = "" @@ -272,29 +262,19 @@ def quasi_sequence_order(sequence: str, lag: int = 30, weight: float = 0.1, raise ValueError(f"Invalid distance matrix name input {distance_matrix}, values can be schneider-wrede or grantham.") quasi_sequence_order = {} - right_part = 0.0 - #calculate first 20 quasi sequence order using sequence order coupling number for - #proteins using lag and specificed physicochemical distance matrix - for aa in range(lag): - right_part = right_part + sequence_order_coupling_number_(sequence, aa+1, distance_matrix) + #calculate SOCN for each gap up to lag once, using the specified distance matrix + _right_part = [sequence_order_coupling_number_(sequence, i+1, distance_matrix) for i in range(lag)] #get amino acid composition aa_comp = composition.amino_acid_composition(sequence) - #iterate over amino acids, calculating descriptor value - temp = 1 + weight * right_part + #iterate over amino acids, calculating first 20 descriptor values + temp = 1 + weight * sum(_right_part) for index, aa in enumerate(amino_acids): quasi_sequence_order[col_prefix + str(index + 1)] = round(aa_comp[aa].values[0] / temp, 6) - - #calculate SOCN up until maxlag - _right_part = [] - for i in range(lag): - _right_part.append( - sequence_order_coupling_number_(sequence, i+1, distance_matrix)) - #calculate the last maxlag descriptor values - temp = 1 + weight * sum(_right_part) + #calculate the last lag descriptor values for index in range(20, 20 + lag): quasi_sequence_order[col_prefix + str(index + 1)] = round( weight * _right_part[index - 20] / temp, 6) @@ -335,19 +315,15 @@ def quasi_sequence_order_all(sequence: str, lag: int = 30, weight: float = 0.1) #validate input protein sequence sequence = _validate_sequence(sequence) - #raise value error if int can't be parsed from input lag - try: - lag = int(lag) - except (ValueError, TypeError): - raise ValueError(f"Invalid lag value input: {lag}.") + #raise value error if a non-negative int can't be parsed from input lag + lag = _validate_int("lag", lag) #validate lag, set default lag of 30 if invalid value input - if (lag>=len(sequence) or (lag<0)): + if (lag>=len(sequence)): lag = 30 - #validate weight, set default weight if invalid value input - if ((weight < 0) or not (isinstance(weight, (float, int)))): - weight = 0.1 + #validate weight + weight = _validate_weight(weight) #calculate quasi seq order using schneider distance matrix quasi_schneider = quasi_sequence_order(sequence, lag, weight=weight, diff --git a/protpy/signal.py b/protpy/signal.py new file mode 100644 index 0000000..3a320af --- /dev/null +++ b/protpy/signal.py @@ -0,0 +1,155 @@ +################################################################################ +############# Signal Processing ############## +################################################################################ + +from __future__ import annotations + +""" +Signal-processing descriptors of amino-acid property signals: binned FFT power +spectrum and DCT coefficients. numpy only. + +References +========== +[1] Cosic, I. (1994). Macromolecular bioactivity: is it resonant interaction + between macromolecules? Theory and applications. IEEE Trans Biomed Eng, + 41(12), 1101-1114. https://doi.org/10.1109/10.335859 +[2] Wang, Y., Ding, Y., Guo, F., Wei, L., & Tang, J. (2017). Improved detection + of DNA-binding proteins via compression technology on PSSM information. + PLoS ONE, 12(9), e0185587. https://doi.org/10.1371/journal.pone.0185587 + (DCT used as a sequence-length-invariant compression of a residue signal) +""" + +import numpy as np +import pandas as pd + +from ._constants import _validate_sequence, _validate_int +from ._helpers import _property_matrix, _to_df + +__all__ = ["fft_descriptors", "dct_descriptors"] + + +def _signal(sequence: str, scale: str | dict) -> tuple[np.ndarray, str]: + """ + Convert a protein sequence into a numerical signal using a single-value amino + acid scale, with the mean of the signal subtracted. + + Parameters + ========== + :sequence: str + protein sequence. + :scale: str | dict + single-value scale name, AAindex accession number or dict of amino acid + values. + + Returns + ======= + :signal: np.ndarray + mean-centred property values along the sequence. + :name: str + name of the scale used in column names ("custom" for a dict). + """ + values = _property_matrix(sequence, scale) + if values.shape[1] != 1: + raise ValueError(f"Scale '{scale}' is multi-dimensional; use a single-value scale.") + x = values[:, 0] + return x - x.mean(), scale if isinstance(scale, str) else "custom" + +#################################### FFT ################################################## + +def fft_descriptors(sequence: str, scale: str | dict = "eiip", n_bins: int = 10) -> pd.DataFrame: + """ + Calculate Fast Fourier Transform (FFT) descriptors of protein sequence. Each + amino acid in the sequence is replaced by its value on a property scale, by + default the electron-ion interaction potential (EIIP) used in the resonant + recognition model [1], and the power spectrum of the mean-centred signal is + calculated as: + + P(f) = abs(FFT(x)(f))^2 / L + + where x is the property signal, f is the frequency in cycles per residue + (0 to 0.5) and L is the length of the sequence. To give the same number of + features for any sequence length, the frequency range 0-0.5 is split into + n_bins equal-width bins and the mean power in each bin is returned; bins + containing no frequencies are 0. + + Parameters + ========== + :sequence: str + protein sequence. + :scale: str | dict (default="eiip") + single-value scale name, AAindex accession number or dict of amino acid + values. + :n_bins: int (default=10) + number of frequency bins. + + Returns + ======= + :fft_df: pd.DataFrame + pandas dataframe of FFT descriptors for protein sequence. Dataframe will be + of the shape 1 x n_bins, where n_bins is the number of features calculated + from the descriptor and 1 is the input sequence. Column names are of the + form FFT__bin, e.g. FFT_eiip_bin1. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + n_bins = _validate_int("n_bins", n_bins, 1) + + x, name = _signal(sequence, scale) + power = np.abs(np.fft.rfft(x)) ** 2 / len(x) + freqs = np.fft.rfftfreq(len(x)) + #bin index of each frequency; f = 0.5 belongs to the last bin + bins = np.minimum((freqs / 0.5 * n_bins).astype(int), n_bins - 1) + sums = np.bincount(bins, weights=power, minlength=n_bins) + counts = np.bincount(bins, minlength=n_bins) + values = np.divide(sums, counts, out=np.zeros(n_bins), where=counts > 0) + return _to_df(list(values), [f"FFT_{name}_bin{i}" for i in range(1, n_bins + 1)]) + +#################################### DCT ################################################## + +def dct_descriptors(sequence: str, scale: str | dict = "kyte_doolittle", n_coeffs: int = 10) -> pd.DataFrame: + """ + Calculate Discrete Cosine Transform (DCT) descriptors of protein sequence. + Each amino acid in the sequence is replaced by its value on a property scale, + by default the Kyte-Doolittle hydropathy, and the first n_coeffs coefficients + of the orthonormal type-II DCT of the mean-centred signal are returned. The + DCT compresses a sequence-length signal into a fixed number of coefficients + [2], calculated as: + + X_k = s_k * sum_{n=0}^{L-1} x_n * cos(pi * k * (2n + 1) / (2L)) + + where X_k is coefficient k, x_n is the property value at position n, L is the + length of the sequence, and s_0 = sqrt(1/L) and s_k = sqrt(2/L) for k > 0. X_0 + is 0 because the signal is mean-centred. + + Parameters + ========== + :sequence: str + protein sequence. + :scale: str | dict (default="kyte_doolittle") + single-value scale name, AAindex accession number or dict of amino acid + values. + :n_coeffs: int (default=10) + number of coefficients, at most the length of the sequence. + + Returns + ======= + :dct_df: pd.DataFrame + pandas dataframe of DCT descriptors for protein sequence. Dataframe will be + of the shape 1 x n_coeffs, where n_coeffs is the number of features + calculated from the descriptor and 1 is the input sequence. Column names + are of the form DCT__, e.g. DCT_kyte_doolittle_0. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + n_coeffs = _validate_int("n_coeffs", n_coeffs, 1) + if n_coeffs > len(sequence): + raise ValueError(f"n_coeffs ({n_coeffs}) cannot exceed the sequence length ({len(sequence)}).") + + x, name = _signal(sequence, scale) + L = len(x) + k = np.arange(n_coeffs)[:, None] + basis = np.cos(np.pi * k * (2 * np.arange(L) + 1) / (2 * L)) + scale_factors = np.where(k[:, 0] == 0, np.sqrt(1 / L), np.sqrt(2 / L)) + #elementwise sum instead of matmul: macOS Accelerate BLAS raises spurious float warnings + coeffs = scale_factors * (basis * x).sum(axis=1) + return _to_df(list(coeffs), [f"DCT_{name}_{i}" for i in range(n_coeffs)]) diff --git a/protpy/structural.py b/protpy/structural.py new file mode 100644 index 0000000..0b96fff --- /dev/null +++ b/protpy/structural.py @@ -0,0 +1,641 @@ +################################################################################ +############# Structural & Functional Heuristics ############## +################################################################################ + +from __future__ import annotations + +""" +Sequence-based structural and functional heuristics: aggregation hot spots +(AGGRESCAN), charge-hydropathy disorder (Uversky, FoldIndex), disorder +propensity, transmembrane and signal-peptide features, motif counts, +prion-like domains (PLAAC), antigenicity profiles and cysteine features. + +References +========== +[1] Conchillo-Solé, O. et al. (2007). AGGRESCAN: a server for the prediction and + evaluation of "hot spots" of aggregation in polypeptides. BMC Bioinformatics, + 8, 65. https://doi.org/10.1186/1471-2105-8-65 +[2] Uversky, V. N., Gillespie, J. R., & Fink, A. L. (2000). Why are "natively + unfolded" proteins unstructured under physiologic conditions? Proteins, + 41(3), 415-427. https://doi.org/10.1002/1097-0134(20001115)41:3<415::AID-PROT130>3.0.CO;2-7 +[3] Prilusky, J. et al. (2005). FoldIndex: a simple tool to predict whether a + given protein sequence is intrinsically unfolded. Bioinformatics, 21(16), + 3435-3438. https://doi.org/10.1093/bioinformatics/bti537 +[4] Dunker, A. K. et al. (2001). Intrinsically disordered protein. J Mol Graph + Model, 19(1), 26-59. https://doi.org/10.1016/S1093-3263(00)00138-8 +[5] Campen, A. et al. (2008). TOP-IDP-scale: a new amino acid scale measuring + propensity for intrinsic disorder. Protein Pept Lett, 15(9), 956-963. + https://doi.org/10.2174/092986608785849164 +[6] Kyte, J., & Doolittle, R. F. (1982). A simple method for displaying the + hydropathic character of a protein. J Mol Biol, 157(1), 105-132. + https://doi.org/10.1016/0022-2836(82)90515-0 +[7] von Heijne, G. (1986). The distribution of positively charged residues in + bacterial inner membrane proteins correlates with the trans-membrane + topology. EMBO J, 5(11), 3021-3027. + https://doi.org/10.1002/j.1460-2075.1986.tb04601.x +[8] von Heijne, G. (1983). Patterns of amino acids near signal-sequence cleavage + sites. Eur J Biochem, 133(1), 17-21. + https://doi.org/10.1111/j.1432-1033.1983.tb07424.x +[9] Sigrist, C. J. A. et al. (2013). New and continuing developments at PROSITE. + Nucleic Acids Res, 41(D1), D344-D347. https://doi.org/10.1093/nar/gks1067 +[10] Olsen, J. V., Ong, S.-E., & Mann, M. (2004). Trypsin cleaves exclusively + C-terminal to arginine and lysine residues. Mol Cell Proteomics, 3(6), + 608-614. https://doi.org/10.1074/mcp.T400003-MCP200 +[11] Lancaster, A. K., Nutter-Upham, A., Lindquist, S., & King, O. D. (2014). + PLAAC: a web and command-line application to identify proteins with + prion-like amino acid composition. Bioinformatics, 30(17), 2501-2502. + https://doi.org/10.1093/bioinformatics/btu310 +[12] Parker, J. M. R., Guo, D., & Hodges, R. S. (1986). New hydrophilicity scale + derived from high-performance liquid chromatography peptide retention data. + Biochemistry, 25(19), 5425-5432. https://doi.org/10.1021/bi00367a013 +[13] Janin, J. (1979). Surface and inside volumes in globular proteins. Nature, + 277, 491-492. https://doi.org/10.1038/277491a0 +[14] Fomenko, D. E., & Gladyshev, V. N. (2003). Identity and functions of CxxC- + derived motifs. Biochemistry, 42(38), 11214-11225. https://doi.org/10.1021/bi034459s +[15] Das, R. K., & Pappu, R. V. (2013). Conformations of intrinsically disordered + proteins are influenced by linear sequence distributions of oppositely + charged residues. PNAS, 110(33), 13392-13397. + https://doi.org/10.1073/pnas.1304749110 +""" + +import math +import re + +import numpy as np +import pandas as pd + +from ._constants import _validate_sequence, _validate_int +from ._helpers import (_charge_vector, _load_json_table, _property_matrix, _profile_stats, _runs, + _sliding_windows, _to_df) +from .composition import kyte_doolittle_ + +__all__ = [ + "aggrescan", "uversky_charge_hydropathy", "fold_index", "disorder_propensity", + "transmembrane_features", "signal_peptide_features", "functional_site_counts", "plaac", + "antigenicity_surface", "cysteine_features", +] + +################################### AGGRESCAN ############################################# + +#np.trapz was renamed np.trapezoid in numpy 2.0 +_trapz = getattr(np, "trapezoid", None) or np.trapz + +#hot-spot threshold: SwissProt-frequency-weighted mean a3v [1] +_AGGRESCAN_HST = -0.02 + +def aggrescan(sequence: str, window: int = 5) -> pd.DataFrame: + """ + Calculate AGGRESCAN (AGG) aggregation descriptors of protein sequence. + AGGRESCAN predicts aggregation "hot spots", short stretches that promote + protein aggregation, from experimental aggregation propensities (a3v) of the + 20 amino acids [1]. The a3v values are averaged over a sliding window, giving + the aggregation profile (a4v), with each window value assigned to its central + residue and the off-centre N- and C-terminal residues given the first and last + window values. A hot spot is a run of 5 or more residues with an a4v above the + hot-spot threshold (HST) of -0.02 and containing no proline. Areas are + calculated by trapezoidal integration of the profile, with HST as the zero axis. + + The 12 output values are: the mean a3v of the sequence (a3vSA); the number of + hot spots (nHS) and the number per 100 residues (NnHS); the area of the profile + above HST (AAT) and per residue (AATr); the total area of the hot spots (THSA) + and per residue (THSAr); the total area of the profile (TA); the sum of a4v + (a4vSS) and per 100 residues (Na4vSS); the mean a4v in the hot spots (a4vAHS, + 0 if there are none); and the largest single hot-spot area (HSA_max). + + AGGRESCAN chooses a window of 5, 7, 9 or 11 residues from the sequence length + and gives the two extreme residues specific values; neither rule is given in + the published supplementary material, so the window is a parameter and the + extreme residues are treated like the other off-centre residues. + + Parameters + ========== + :sequence: str + protein sequence. + :window: int (default=5) + odd sliding window length (AGGRESCAN uses 5, 7, 9 or 11); reduced to the + largest odd length that fits a short sequence. + + Returns + ======= + :aggrescan_df: pd.DataFrame + pandas dataframe of AGGRESCAN descriptors for protein sequence. Dataframe + will be of the shape 1 x 12, where 12 is the number of features calculated + from the descriptor and 1 is the input sequence. Column names start + with AGG, e.g. AGG_nHS and AGG_THSA. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + window = _validate_int("window", window, 1) + if window % 2 == 0: + raise ValueError(f"window must be odd, got {window}.") + L = len(sequence) + window = min(window, L if L % 2 else L - 1) + + a3v = _property_matrix(sequence, _load_json_table("structural_params.json", "aggrescan_a3v")["values"])[:, 0] + centres = _sliding_windows(a3v, window) + half = window // 2 + a4v = np.concatenate([np.full(half, centres[0]), centres, np.full(half, centres[-1])]) + above = a4v - _AGGRESCAN_HST + + #hot spots: >= 5 consecutive residues above HST, broken by proline + is_hs = (above > 0) & np.array([aa != "P" for aa in sequence]) + hot_spots = _runs(is_hs, min_len=5) + hsa = [_trapz(above[s:e]) for s, e in hot_spots] + aat = float(_trapz(np.clip(above, 0, None))) + thsa = float(sum(hsa)) + values = [a3v.mean(), len(hot_spots), len(hot_spots) / L * 100, aat, aat / L, thsa, thsa / L, + float(_trapz(above)), a4v.sum(), a4v.sum() / L * 100, + float(np.mean([a4v[s:e].mean() for s, e in hot_spots])) if hot_spots else 0.0, + max(hsa) if hsa else 0.0] + columns = ["AGG_a3vSA", "AGG_nHS", "AGG_NnHS", "AGG_AAT", "AGG_AATr", "AGG_THSA", "AGG_THSAr", + "AGG_TA", "AGG_a4vSS", "AGG_Na4vSS", "AGG_a4vAHS", "AGG_HSA_max"] + return _to_df(values, columns) + +######################### Uversky Charge-Hydropathy / FoldIndex ########################### + +def _mean_hydropathy(sequence: str) -> float: + """ + Calculate the mean hydropathy of a protein sequence using the Kyte-Doolittle + scale rescaled to the range 0-1, (KD + 4.5) / 9, as used by Uversky et al. [2]. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :mean_h: float + mean rescaled hydropathy of the sequence. + """ + return sum((kyte_doolittle_[aa] + 4.5) / 9 for aa in sequence) / len(sequence) + + +def uversky_charge_hydropathy(sequence: str) -> pd.DataFrame: + """ + Calculate the Uversky Charge-Hydropathy (UV) descriptors of protein sequence. + Uversky et al. showed that natively unfolded proteins can be separated from + folded proteins by their mean net charge and mean hydropathy [2]. These are + calculated as: + + = mean of (KD + 4.5) / 9 + + = abs(n+ - n-) / L + + HQ = 2.785 - - 1.151 + + where KD is the Kyte-Doolittle hydropathy of each residue, n+ and n- are the + numbers of positive (K, R) and negative (D, E) residues and L is the length + of the sequence. HQ is the distance from the boundary = 2.785 - 1.151 + (as restated in [15]); the protein is predicted to be unfolded when HQ < 0. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :uversky_df: pd.DataFrame + pandas dataframe of charge-hydropathy descriptors for protein sequence. + Dataframe will be of the shape 1 x 4, with the columns UV_MeanH, UV_MeanR, + UV_HQ and UV_Unfolded (1 if predicted unfolded, else 0). + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + h = _mean_hydropathy(sequence) + r = abs(_charge_vector(sequence).mean()) + hq = 2.785 * h - r - 1.151 + return _to_df([h, r, hq, int(hq < 0)], ["UV_MeanH", "UV_MeanR", "UV_HQ", "UV_Unfolded"]) + + +def fold_index(sequence: str, window: int = 51) -> pd.DataFrame: + """ + Calculate the FoldIndex (FI) of protein sequence. FoldIndex applies the + Uversky charge-hydropathy boundary over a sliding window to find disordered + regions of a protein sequence [3]. For each window it is calculated as: + + FI = 2.785 - abs() - 1.151 + + where is the mean Kyte-Doolittle hydropathy of the window rescaled to 0-1 + and is the mean net charge of the window (K, R positive; D, E negative). + Residues covered by at least one window with FI < 0 are counted as disordered. + A window longer than the sequence uses the whole sequence, so FI then equals + UV_HQ from uversky_charge_hydropathy. + + Parameters + ========== + :sequence: str + protein sequence. + :window: int (default=51) + length of the sliding window. + + Returns + ======= + :fold_index_df: pd.DataFrame + pandas dataframe of FoldIndex descriptors for protein sequence. Dataframe + will be of the shape 1 x 4, with the columns FI_mean, FI_min, + FI_frac_disordered and FI_longest_disordered (in residues). + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + window = min(_validate_int("window", window, 1), len(sequence)) + + h = _sliding_windows([(kyte_doolittle_[aa] + 4.5) / 9 for aa in sequence], window) + r = np.abs(_sliding_windows(_charge_vector(sequence), window)) + fi = 2.785 * h - r - 1.151 + disordered = np.zeros(len(sequence), dtype=bool) + for start in np.flatnonzero(fi < 0): + disordered[start:start + window] = True + runs = _runs(disordered) + longest = max((e - s for s, e in runs), default=0) + return _to_df([fi.mean(), fi.min(), disordered.mean(), longest], + ["FI_mean", "FI_min", "FI_frac_disordered", "FI_longest_disordered"]) + +################################ Disorder Propensity ###################################### + +def disorder_propensity(sequence: str) -> pd.DataFrame: + """ + Calculate the Disorder Propensity (DP) of protein sequence. Intrinsically + disordered proteins are enriched in disorder-promoting residues (A, R, G, Q, + S, P, E, K) and depleted in order-promoting residues (W, C, F, I, Y, V, L, N) + [4]. The descriptor gives the fraction of each, together with the mean value of + the TOP-IDP scale, a scale of amino acid propensity for intrinsic disorder [5], + calculated as: + + DP_TOP_IDP_mean = sum_i TOP-IDP(s_i) / L + + where TOP-IDP(s_i) is the TOP-IDP value of the amino acid at position i and L + is the length of the sequence. Higher values indicate a higher propensity for + disorder. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :disorder_df: pd.DataFrame + pandas dataframe of disorder propensity descriptors for protein sequence. + Dataframe will be of the shape 1 x 3, with the columns + DP_frac_disorder_promoting, DP_frac_order_promoting and DP_TOP_IDP_mean. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + classes = _load_json_table("structural_params.json", "disorder_residue_classes")["values"] + top_idp = _load_json_table("structural_params.json", "top_idp")["values"] + L = len(sequence) + return _to_df([sum(aa in classes["disorder_promoting"] for aa in sequence) / L, + sum(aa in classes["order_promoting"] for aa in sequence) / L, + sum(top_idp[aa] for aa in sequence) / L], + ["DP_frac_disorder_promoting", "DP_frac_order_promoting", "DP_TOP_IDP_mean"]) + +############################## Transmembrane Features ##################################### + +def transmembrane_features(sequence: str, window: int = 19, kd_threshold: float = 1.6) -> pd.DataFrame: + """ + Calculate Transmembrane (TM) segment features of protein sequence. Putative + transmembrane segments are found from the Kyte-Doolittle hydropathy profile: + windows of 19 residues with a mean hydropathy above 1.6 indicate a likely + transmembrane segment [6], and overlapping windows are merged into segments. + The positive-inside rule states that the cytoplasmic side of a transmembrane + segment is enriched in positive residues [7]. The 4 output values are: the + number of segments; the maximum window hydropathy; the fraction of residues + inside a segment; and the positive-inside bias, the number of K and R residues + in the 15 residues before the first segment minus the number in the 15 residues + after it (0 if there is no segment). This is a hydropathy heuristic and not a + topology predictor. + + Parameters + ========== + :sequence: str + protein sequence. + :window: int (default=19) + length of the hydropathy window. + :kd_threshold: float (default=1.6) + mean hydropathy above which a window is part of a segment. + + Returns + ======= + :tm_df: pd.DataFrame + pandas dataframe of transmembrane features for protein sequence. Dataframe + will be of the shape 1 x 4, with the columns TM_n_segments, TM_max_KD, + TM_frac_in_TM and TM_positive_inside_bias. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + window = min(_validate_int("window", window, 1), len(sequence)) + + profile = _sliding_windows([kyte_doolittle_[aa] for aa in sequence], window) + in_tm = np.zeros(len(sequence), dtype=bool) + for start in np.flatnonzero(profile > kd_threshold): + in_tm[start:start + window] = True + segments = _runs(in_tm) + bias = 0 + if segments: + s, e = segments[0] + bias = sum(aa in "KR" for aa in sequence[max(0, s - 15):s]) - sum(aa in "KR" for aa in sequence[e:e + 15]) + return _to_df([len(segments), profile.max(), in_tm.mean(), bias], + ["TM_n_segments", "TM_max_KD", "TM_frac_in_TM", "TM_positive_inside_bias"]) + +############################## Signal Peptide Features #################################### + +def signal_peptide_features(sequence: str, n_term: int = 30) -> pd.DataFrame: + """ + Calculate Signal Peptide (SP) features of protein sequence. Signal peptides + have a positively charged N-terminal n-region followed by a hydrophobic + h-region [8]. The 2 output values are: the net charge of the first 5 residues + (K and R +1, D and E -1); and the maximum mean Kyte-Doolittle hydropathy over + 8-residue windows within the first n_term residues. The von Heijne + cleavage-site weight matrix is not included. + + Parameters + ========== + :sequence: str + protein sequence. + :n_term: int (default=30) + length of the N-terminal region to examine. + + Returns + ======= + :sp_df: pd.DataFrame + pandas dataframe of signal peptide features for protein sequence. + Dataframe will be of the shape 1 x 2, with the columns SP_nregion_charge + and SP_hregion_maxKD. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + n_term = _validate_int("n_term", n_term, 1) + + region = sequence[:n_term] + charge = _charge_vector(sequence[:5]).sum() + h = _sliding_windows([kyte_doolittle_[aa] for aa in region], 8).max() + return _to_df([int(charge), h], ["SP_nregion_charge", "SP_hregion_maxKD"]) + +############################## Functional Site Counts ##################################### + +#PROSITE patterns [9] and the trypsin rule [10]; lookaheads count overlapping matches +functional_motifs_ = { + "N_glycosylation": r"(?=N[^P][ST][^P])", #PS00001 N-{P}-[ST]-{P} + "ER_retention": r"[KRHQSA][DENQ]EL$", #PS00014 [KRHQSA]-[DENQ]-E-L> + "PTS1": r"[STAGCN][RKH][LIVMAFY]$", #PS00342 [STAGCN]-[RKH]-[LIVMAFY]> + "tryptic_site": r"[KR](?!P)", #trypsin: after K/R, not before P +} + +def functional_site_counts(sequence: str) -> pd.DataFrame: + """ + Calculate Functional Site (FS) counts of protein sequence. The number of each + functional sequence motif, and its density per 100 residues, is counted for: + N-glycosylation sites (PROSITE PS00001, N-{P}-[ST]-{P}), the C-terminal + endoplasmic reticulum retention signal (PS00014, [KRHQSA]-[DENQ]-E-L at the + C-terminus), the C-terminal peroxisomal targeting signal 1 (PS00342, + [STAGCN]-[RKH]-[LIVMAFY] at the C-terminus) [9] and tryptic cleavage sites + (after K or R, not before P) [10]. Overlapping matches are counted. These motifs + complement the default motifs of motif_composition. + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :sites_df: pd.DataFrame + pandas dataframe of functional site counts for protein sequence. Dataframe + will be of the shape 1 x 8, where 8 is the number of features calculated + from the descriptor (4 counts and 4 densities) and 1 is the input sequence. + Column names are FS_ and FS__per100, e.g. FS_N_glycosylation. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + counts = {name: len(re.findall(pattern, sequence)) for name, pattern in functional_motifs_.items()} + values = list(counts.values()) + [c / len(sequence) * 100 for c in counts.values()] + columns = [f"FS_{n}" for n in counts] + [f"FS_{n}_per100" for n in counts] + return _to_df(values, columns) + +###################################### PLAAC ############################################## + +def _plaac_model() -> tuple[dict, dict, np.ndarray, np.ndarray]: + """ + Get the parameters of the PLAAC prion-like domain model [11]: the amino acid + frequencies of S. cerevisiae prion domains (foreground) and of the + S. cerevisiae proteome (background), and the transition and initial + probabilities of the two-state hidden Markov model. Ported from the PLAAC + source code (MIT License). + + Returns + ======= + :bg: dict + background amino acid frequencies. + :fg: dict + prion-domain amino acid frequencies. + :log_t: np.ndarray + log transition probabilities of the hidden Markov model (2 x 2). + :log_i: np.ndarray + log initial probabilities of the hidden Markov model. + """ + p = _load_json_table("structural_params.json", "plaac")["values"] + eps = p["epsilon"] + + def _normalise(freqs: dict) -> dict: + #as PLAAC: normalise, add epsilon for X and *, normalise again + """ + Normalise amino acid frequencies as PLAAC does: normalise to sum to 1, then + add a small probability for the unknown residue (X) and stop (*) symbols and + normalise again. + + Parameters + ========== + :freqs: dict + amino acid frequencies. + + Returns + ======= + :freqs: dict + normalised amino acid frequencies. + """ + total = sum(freqs.values()) + freqs = {aa: v / total for aa, v in freqs.items()} + total = 1 + 2 * eps + return {aa: v / total for aa, v in freqs.items()} + + return (_normalise(p["bg"]), _normalise(p["fg"]), np.log(np.array(p["transition"])), + np.log(np.array(p["initial"]))) + + +def _viterbi(sequence: str, bg: dict, fg: dict, log_t: np.ndarray, log_i: np.ndarray) -> np.ndarray: + """ + Calculate the most likely sequence of states of the PLAAC two-state hidden + Markov model (background or prion-like) for a protein sequence using the + Viterbi algorithm [11]. + + Parameters + ========== + :sequence: str + protein sequence. + :bg: dict + background amino acid frequencies. + :fg: dict + prion-domain amino acid frequencies. + :log_t: np.ndarray + log transition probabilities. + :log_i: np.ndarray + log initial probabilities. + + Returns + ======= + :path: np.ndarray + state of each residue, 0 for background and 1 for prion-like. + """ + emit = np.log(np.array([[bg[aa], fg[aa]] for aa in sequence])) + score = log_i + emit[0] + back = np.zeros((len(sequence), 2), dtype=int) + for i in range(1, len(sequence)): + cand = score[:, None] + log_t + back[i] = np.argmax(cand, axis=0) + score = cand[back[i], [0, 1]] + emit[i] + path = np.zeros(len(sequence), dtype=int) + path[-1] = int(np.argmax(score)) + for i in range(len(sequence) - 1, 0, -1): + path[i - 1] = back[i, path[i]] + return path + + +def _max_window_sum(values: np.ndarray, width: int) -> float: + """ + Calculate the maximum sum of values over any window of a given width. + + Parameters + ========== + :values: np.ndarray + values along the sequence. + :width: int + window width. + + Returns + ======= + :max_sum: float + maximum window sum. + """ + csum = np.concatenate([[0.0], np.cumsum(values)]) + return float((csum[width:] - csum[:-width]).max()) + + +def plaac(sequence: str, core_length: int = 60) -> pd.DataFrame: + """ + Calculate the Prion-Like Amino Acid Composition (PLAAC) descriptors of protein + sequence. PLAAC identifies prion-like domains, whose composition resembles + that of yeast prion domains, using the amino acid frequencies of prion domains + and of the S. cerevisiae proteome [11]. Each residue has a log-likelihood ratio: + + LLR(s_i) = ln(f_prion(s_i) / f_background(s_i)) + + The 4 output values are: the maximum sum of LLR over any window of core_length + residues (PLAAC_LLR); the maximum sum of LLR over a window of core_length + residues lying entirely inside a prion-like region of the two-state hidden + Markov model Viterbi parse (PLAAC_COREscore); the length of the prion-like + region containing that core (PLAAC_PrD_length); and whether the sequence has a + prion-like region of at least core_length residues (PLAAC_has_PrD). As in + PLAAC, PLAAC_LLR and PLAAC_COREscore are NaN when they are undefined (a + sequence shorter than core_length, or no prion-like region of that length). + The parameters are ported from the PLAAC source code (MIT License), using the + S. cerevisiae background (alpha = 1). + + Parameters + ========== + :sequence: str + protein sequence. + :core_length: int (default=60) + minimum length of a prion-like domain core. + + Returns + ======= + :plaac_df: pd.DataFrame + pandas dataframe of PLAAC descriptors for protein sequence. Dataframe will + be of the shape 1 x 4, with the columns PLAAC_LLR, PLAAC_COREscore, + PLAAC_PrD_length and PLAAC_has_PrD. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + core_length = _validate_int("core_length", core_length, 1) + + bg, fg, log_t, log_i = _plaac_model() + llr = np.array([math.log(fg[aa] / bg[aa]) for aa in sequence]) + window_llr = _max_window_sum(llr, core_length) if len(sequence) >= core_length else np.nan + + core, prd_length = np.nan, 0 + for s, e in _runs(_viterbi(sequence, bg, fg, log_t, log_i) == 1, min_len=core_length): + score = _max_window_sum(llr[s:e], core_length) + if np.isnan(core) or score > core: + core, prd_length = score, e - s + return _to_df([window_llr, core, prd_length, int(prd_length > 0)], + ["PLAAC_LLR", "PLAAC_COREscore", "PLAAC_PrD_length", "PLAAC_has_PrD"]) + +############################### Antigenicity / Surface #################################### + +def antigenicity_surface(sequence: str, window: int = 7) -> pd.DataFrame: + """ + Calculate the Antigenicity and Surface (Antigenicity) profiles of protein + sequence. Antigenic sites tend to be hydrophilic and exposed on the protein + surface. Each amino acid in the sequence is replaced by its value on the + Parker hydrophilicity scale, derived from HPLC peptide retention times [12], + and on the Janin scale of free energy of transfer from the inside to the + outside of a globular protein [13]. Each is averaged over a sliding window of 7 + residues, as used by Parker et al., and the profile is summarised by its mean, + maximum, minimum and standard deviation. + + Parameters + ========== + :sequence: str + protein sequence. + :window: int (default=7) + length of the sliding window. + + Returns + ======= + :antigenicity_df: pd.DataFrame + pandas dataframe of antigenicity and surface profile statistics for protein + sequence. Dataframe will be of the shape 1 x 8, with the columns + PARKER_mean, PARKER_max, PARKER_min, PARKER_std and the same for JANIN. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + window = _validate_int("window", window, 1) + stats = {} + for name in ("parker", "janin"): + profile = _sliding_windows(_property_matrix(sequence, name)[:, 0], window) + stats.update(_profile_stats(profile, name.upper())) + return _to_df(list(stats.values()), list(stats)) + +################################ Cysteine Features ######################################## + +def cysteine_features(sequence: str) -> pd.DataFrame: + """ + Calculate Cysteine (Cys) features of protein sequence. Cysteines form + disulfide bonds and functional motifs such as the CxxC motif of thiol + oxidoreductases [14]. The 6 output values are: the number of cysteines; their + fraction; whether the number is even; the maximum number of disulfide bonds + (the number of cysteines // 2); the number of overlapping C-x-x-C motifs; and + the mean gap between consecutive cysteines (0 if there are fewer than 2). + + Parameters + ========== + :sequence: str + protein sequence. + + Returns + ======= + :cys_df: pd.DataFrame + pandas dataframe of cysteine features for protein sequence. Dataframe will + be of the shape 1 x 6, with the columns Cys_count, Cys_frac, Cys_even, + Cys_max_disulfides, Cys_CxxC and Cys_mean_spacing. + """ + #validate input protein sequence + sequence = _validate_sequence(sequence) + positions = [i for i, aa in enumerate(sequence) if aa == "C"] + n = len(positions) + spacing = float(np.mean(np.diff(positions))) if n > 1 else 0.0 + return _to_df([n, n / len(sequence), int(n % 2 == 0), n // 2, + len(re.findall(r"(?=C..C)", sequence)), spacing], + ["Cys_count", "Cys_frac", "Cys_even", "Cys_max_disulfides", "Cys_CxxC", "Cys_mean_spacing"]) diff --git a/protpy/utils.py b/protpy/utils.py index fbd3a39..35e9ddf 100644 --- a/protpy/utils.py +++ b/protpy/utils.py @@ -22,10 +22,14 @@ "abbreviation": "AAComp", "parameters": { "sequence": "str — protein sequence", + "region": "str | None (default=None) — None, 'nterm', 'cterm' or 'split' (NT_/MID_/CT_ blocks; split AAC, Kumar et al. 2006)", + "n": "int (default=25) — terminal block length", }, "output_shape": "1 x 20", "speed": "Fast", "reference": "[1] Gromiha, M. M. (2010). Protein Bioinformatics. Elsevier.", + "doi": None, + "in_default_set": True, }, "dipeptide_composition": { "description": ( @@ -37,10 +41,14 @@ "abbreviation": "DPComp", "parameters": { "sequence": "str — protein sequence", + "region": "str | None (default=None) — None, 'nterm', 'cterm' or 'split' (NT_/MID_/CT_ blocks)", + "n": "int (default=25) — terminal block length", }, "output_shape": "1 x 400", "speed": "Fast", "reference": "[1] Gromiha, M. M. (2010). Protein Bioinformatics. Elsevier.", + "doi": None, + "in_default_set": True, }, "tripeptide_composition": { "description": ( @@ -55,6 +63,8 @@ "output_shape": "1 x 8000", "speed": "Moderate", "reference": "[1] Gromiha, M. M. (2010). Protein Bioinformatics. Elsevier.", + "doi": None, + "in_default_set": True, }, "gravy": { "description": ( @@ -66,10 +76,13 @@ "abbreviation": "GRAVY", "parameters": { "sequence": "str — protein sequence", + "scale": "str (default='kyte_doolittle') — single-value scale or AAindex code (column GRAVY_{scale})", }, "output_shape": "1 x 1", "speed": "Fast", "reference": "[8] Kyte & Doolittle (1982). J. Mol. Biol., 157(1), 105-132.", + "doi": "10.1016/0022-2836(82)90515-0", + "in_default_set": True, }, "aromaticity": { "description": ( @@ -84,6 +97,8 @@ "output_shape": "1 x 1", "speed": "Fast", "reference": "[9] Lobry & Gautier (1994). Nucleic Acids Research, 22(15), 3174-3180.", + "doi": "10.1093/nar/22.15.3174", + "in_default_set": True, }, "instability_index": { "description": ( @@ -99,6 +114,8 @@ "output_shape": "1 x 1", "speed": "Fast", "reference": "[10] Guruprasad et al. (1990). Protein Engineering, 4(2), 155-161.", + "doi": "10.1093/protein/4.2.155", + "in_default_set": True, }, "isoelectric_point": { "description": ( @@ -110,10 +127,13 @@ "abbreviation": "pI", "parameters": { "sequence": "str — protein sequence", + "pka_scale": "str (default='protpy') — pKa set: 'protpy', 'emboss', 'bjellqvist' or 'ipc_protein'", }, "output_shape": "1 x 1", "speed": "Fast", "reference": "[11] Bjellqvist et al. (1993). Electrophoresis, 14(1), 1023-1031.", + "doi": "10.1002/elps.11501401163", + "in_default_set": True, }, "molecular_weight": { "description": ( @@ -129,6 +149,8 @@ "output_shape": "1 x 1", "speed": "Fast", "reference": "[12] Gasteiger et al. (2005). The Proteomics Protocols Handbook.", + "doi": "10.1385/1-59259-890-0:571", + "in_default_set": True, }, "charge_distribution": { "description": ( @@ -141,10 +163,13 @@ "parameters": { "sequence": "str — protein sequence", "ph": "float (default=7.4) — pH for charge calculation", + "pka_scale": "str (default='protpy') — pKa set: 'protpy', 'emboss', 'bjellqvist' or 'ipc_protein'", }, "output_shape": "1 x 3", "speed": "Fast", "reference": "[13] Cameselle-Teijeiro (1979). Biochemical Education, 7(3), 69-70.", + "doi": None, + "in_default_set": True, }, "hydrophobic_polar_charged_composition": { "description": ( @@ -161,6 +186,8 @@ "output_shape": "1 x 3", "speed": "Fast", "reference": "[1] Gromiha, M. M. (2010). Protein Bioinformatics. Elsevier.", + "doi": None, + "in_default_set": True, }, "secondary_structure_propensity": { "description": ( @@ -176,6 +203,8 @@ "output_shape": "1 x 3", "speed": "Fast", "reference": "[14] Chou & Fasman (1974). Biochemistry, 13(2), 211-222.", + "doi": "10.1021/bi00699a001", + "in_default_set": True, }, "kmer_composition": { "description": ( @@ -192,6 +221,8 @@ "output_shape": "1 x 20^k (e.g. 1 x 400 for k=2)", "speed": "Fast (k≤3) / Slow (k=4)", "reference": "[1] Gromiha, M. M. (2010). Protein Bioinformatics. Elsevier.", + "doi": None, + "in_default_set": True, }, "reduced_alphabet_composition": { "description": ( @@ -204,10 +235,14 @@ "parameters": { "sequence": "str — protein sequence", "alphabet_size": "int (default=6) — number of groups (2, 3, 4 or 6)", + "alphabet": "str | None (default=None) — 'ifeature5' (GAAC/GDPC/GTPC), 'cp13', 'cp14', 'cp19' or 'cp20'", + "k": "int (default=1) — group k-mer length, 1-3", }, "output_shape": "1 x alphabet_size", "speed": "Fast", "reference": "[1] Gromiha, M. M. (2010). Protein Bioinformatics. Elsevier.", + "doi": None, + "in_default_set": True, }, "motif_composition": { "description": ( @@ -225,6 +260,8 @@ "output_shape": "1 x M (M = number of motifs, default M=8)", "speed": "Fast", "reference": "[15] PROSITE: https://prosite.expasy.org/", + "doi": "10.1093/nar/gks1067", + "in_default_set": True, }, "amino_acid_pair_composition": { "description": ( @@ -240,6 +277,8 @@ "output_shape": "1 x 400", "speed": "Moderate", "reference": "[1] Gromiha, M. M. (2010). Protein Bioinformatics. Elsevier.", + "doi": None, + "in_default_set": True, }, "aliphatic_index": { "description": ( @@ -256,6 +295,8 @@ "output_shape": "1 x 1", "speed": "Fast", "reference": "[16] Ikai (1980). J. Biochemistry, 88(6), 1895-1898.", + "doi": "10.1093/oxfordjournals.jbchem.a133168", + "in_default_set": True, }, "extinction_coefficient": { "description": ( @@ -267,14 +308,17 @@ "abbreviation": "ExtCoeff", "parameters": { "sequence": "str — protein sequence", + "absorbance": "bool (default=False) — add Abs01_Reduced / Abs01_Oxidized (A280 of 1 g/L)", }, "output_shape": "1 x 2 [ExtCoeff_Reduced, ExtCoeff_Oxidized]", "speed": "Fast", "reference": "[17] Gasteiger et al. (2005). The Proteomics Protocols Handbook.", + "doi": "10.1385/1-59259-890-0:571", + "in_default_set": True, }, "boman_index": { "description": ( - "Boman Index (potential protein interaction index) — sum of residue " + "Boman Index (potential protein interaction index) — negated sum of residue " "solubility values divided by sequence length. Values > 2.48 suggest " "high protein-binding potential." ), @@ -287,6 +331,8 @@ "output_shape": "1 x 1", "speed": "Fast", "reference": "[18] Boman (2003). J. Internal Medicine, 254(3), 197-215.", + "doi": "10.1046/j.1365-2796.2003.01228.x", + "in_default_set": True, }, "aggregation_propensity": { "description": ( @@ -301,11 +347,13 @@ "sequence": "str — protein sequence", "window": "int (default=5) — sliding window length", "hydrophobicity_threshold": "float (default=2.0) — minimum mean Kyte-Doolittle hydrophobicity", - "charge_threshold": "int (default=1) — max charged residues allowed in an APR window", + "charge_threshold": "int (default=0) — max charged residues allowed in an APR window", }, "output_shape": "1 x 2 [AggregProneRegions, AggregProneFraction]", "speed": "Fast", "reference": "[8] Kyte & Doolittle (1982). J. Mol. Biol., 157(1), 105-132.", + "doi": "10.1016/0022-2836(82)90515-0", + "in_default_set": True, }, "hydrophobic_moment": { "description": ( @@ -319,11 +367,14 @@ "parameters": { "sequence": "str — protein sequence", "window": "int (default=11) — helical window size", - "angle": "float (default=100) — residue rotation angle in degrees (100° for alpha-helix)", + "angle": "float | str (default=100) — rotation angle in degrees, or 'helix' (100) / 'sheet' (160)", + "scale": "str | None (default=None) — hydrophobicity scale; None keeps protpy's default table", }, - "output_shape": "1 x 1", + "output_shape": "1 x 2 [HydrophobicMoment_Mean, HydrophobicMoment_Max]", "speed": "Fast", "reference": "[19] Eisenberg et al. (1982). Nature, 299, 371-374.", + "doi": "10.1038/299371a0", + "in_default_set": True, }, "shannon_entropy": { "description": ( @@ -339,6 +390,8 @@ "output_shape": "1 x 1", "speed": "Fast", "reference": "[1] Gromiha, M. M. (2010). Protein Bioinformatics. Elsevier.", + "doi": None, + "in_default_set": True, }, "pseudo_amino_acid_composition": { "description": ( @@ -358,6 +411,8 @@ "output_shape": "1 x (20 + lamda) (default 1 x 50)", "speed": "Moderate", "reference": "[4] Chou, K-C. (2001). PROTEINS, 43, 246-255.", + "doi": "10.1002/prot.1035", + "in_default_set": True, }, "amphiphilic_pseudo_amino_acid_composition": { "description": ( @@ -377,6 +432,8 @@ "output_shape": "1 x (20 + 2·lamda) (default 1 x 80)", "speed": "Moderate", "reference": "[5] Chou, K-C. (2005). Bioinformatics, 21(1), 10-19.", + "doi": "10.1093/bioinformatics/bth466", + "in_default_set": True, }, # ── Autocorrelation ──────────────────────────────────────────────────────── "moreaubroto_autocorrelation": { @@ -392,11 +449,13 @@ "sequence": "str — protein sequence", "lag": "int (default=30) — max lag between residue pairs", "properties": "list[str] (default=8 AAIndex codes) — AAIndex1 accession numbers", - "normalize": "bool (default=True) — normalise property values before calculation", + "normalize": "bool (default=True) — standardise property values (z-score) before calculation", }, "output_shape": "1 x (lag × n_properties) (default 1 x 240)", "speed": "Moderate", "reference": "[1] Hollas (2003). J. Math. Chem., 33(2), 91-101.", + "doi": "10.1023/A:1023247831238", + "in_default_set": True, }, "moran_autocorrelation": { "description": ( @@ -411,11 +470,13 @@ "sequence": "str — protein sequence", "lag": "int (default=30) — max lag between residue pairs", "properties": "list[str] (default=8 AAIndex codes) — AAIndex1 accession numbers", - "normalize": "bool (default=True) — normalise property values", + "normalize": "bool (default=True) — standardise property values (z-score) before calculation", }, "output_shape": "1 x (lag × n_properties) (default 1 x 240)", "speed": "Moderate", "reference": "[2] Ong et al. (2007). BMC Bioinformatics, 8, 300.", + "doi": "10.1186/1471-2105-8-300", + "in_default_set": True, }, "geary_autocorrelation": { "description": ( @@ -430,11 +491,13 @@ "sequence": "str — protein sequence", "lag": "int (default=30) — max lag between residue pairs", "properties": "list[str] (default=8 AAIndex codes) — AAIndex1 accession numbers", - "normalize": "bool (default=True) — normalise property values", + "normalize": "bool (default=True) — standardise property values (z-score) before calculation", }, "output_shape": "1 x (lag × n_properties) (default 1 x 240)", "speed": "Moderate", "reference": "[2] Ong et al. (2007). BMC Bioinformatics, 8, 300.", + "doi": "10.1186/1471-2105-8-300", + "in_default_set": True, }, # ── Conjoint Triad ───────────────────────────────────────────────────────── "conjoint_triad": { @@ -448,10 +511,13 @@ "abbreviation": "CTriad", "parameters": { "sequence": "str — protein sequence", + "gap": "int (default=0) — >0 gives KSCTriad (columns suffixed _gap{g})", }, "output_shape": "1 x 343", "speed": "Fast", "reference": "[1] Shen et al. (2007). PNAS, 104(11), 4337-4341.", + "doi": "10.1073/pnas.0607879104", + "in_default_set": True, }, # ── CTD ──────────────────────────────────────────────────────────────────── "ctd_composition": { @@ -469,6 +535,8 @@ "output_shape": "1 x 3 (per property)", "speed": "Fast", "reference": "[1] Dubchak et al. (1995). PNAS, 92, 8700-8704.", + "doi": "10.1073/pnas.92.19.8700", + "in_default_set": True, }, "ctd_transition": { "description": ( @@ -485,6 +553,8 @@ "output_shape": "1 x 3 (per property)", "speed": "Fast", "reference": "[1] Dubchak et al. (1995). PNAS, 92, 8700-8704.", + "doi": "10.1073/pnas.92.19.8700", + "in_default_set": True, }, "ctd_distribution": { "description": ( @@ -502,6 +572,8 @@ "output_shape": "1 x 15 (per property)", "speed": "Fast", "reference": "[1] Dubchak et al. (1995). PNAS, 92, 8700-8704.", + "doi": "10.1073/pnas.92.19.8700", + "in_default_set": True, }, "ctd_": { "description": ( @@ -516,10 +588,13 @@ "sequence": "str — protein sequence", "property": "str (default='hydrophobicity') — CTD property (used when all_ctd=False)", "all_ctd": "bool (default=True) — if True, compute over all 7 properties", + "property_set": "str (default='default') — 'default' (7 properties) or 'ifeature13' (13 properties, 1 x 273)", }, "output_shape": "1 x 21 (single property) / 1 x 147 (all 7 properties)", "speed": "Fast (single) / Moderate (all)", "reference": "[1] Dubchak et al. (1995). PNAS, 92, 8700-8704.", + "doi": "10.1073/pnas.92.19.8700", + "in_default_set": True, }, # ── Sequence Order ───────────────────────────────────────────────────────── "sequence_order_coupling_number_": { @@ -539,6 +614,8 @@ "output_shape": "scalar float", "speed": "Fast", "reference": "[1] Chou, K-C. (2000). Biochem. Biophys. Res. Commun., 278, 477-483.", + "doi": "10.1006/bbrc.2000.3815", + "in_default_set": True, }, "sequence_order_coupling_number": { "description": ( @@ -556,6 +633,8 @@ "output_shape": "1 x lag (default 1 x 30)", "speed": "Moderate", "reference": "[1] Chou, K-C. (2000). Biochem. Biophys. Res. Commun., 278, 477-483.", + "doi": "10.1006/bbrc.2000.3815", + "in_default_set": True, }, "sequence_order_coupling_number_all": { "description": ( @@ -572,6 +651,8 @@ "output_shape": "1 x (lag × 2) (default 1 x 60)", "speed": "Moderate", "reference": "[1] Chou, K-C. (2000). Biochem. Biophys. Res. Commun., 278, 477-483.", + "doi": "10.1006/bbrc.2000.3815", + "in_default_set": True, }, "quasi_sequence_order": { "description": ( @@ -591,6 +672,8 @@ "output_shape": "1 x (lag + 20) (default 1 x 50)", "speed": "Moderate", "reference": "[1] Chou, K-C. (2000). Biochem. Biophys. Res. Commun., 278, 477-483.", + "doi": "10.1006/bbrc.2000.3815", + "in_default_set": True, }, "quasi_sequence_order_all": { "description": ( @@ -607,6 +690,708 @@ "output_shape": "1 x ((lag + 20) × 2) (default 1 x 100)", "speed": "Moderate", "reference": "[1] Chou, K-C. (2000). Biochem. Biophys. Res. Commun., 278, 477-483.", + "doi": "10.1006/bbrc.2000.3815", + "in_default_set": True, + }, + "cksaap": { + "description": "Composition of k-spaced amino acid pairs: CKSAAP(XY,g) = N_g(XY) / (L-g-1) x 100 for g = 0..gap.", + "module": "composition", + "category": "Composition", + "abbreviation": "CKSAAP", + "parameters": { + "sequence": "str — protein sequence", + "gap": "int (default=5) — maximum spacing", + }, + "output_shape": "1 x 400·(gap+1) (default 1 x 2400)", + "speed": "Moderate", + "reference": "Chen, Kurgan & Ruan (2007). BMC Struct. Biol., 7, 25.", + "doi": "10.1186/1472-6807-7-25", + "descriptor_sets": ["all"], + "in_default_set": False, + }, + "cksaagp": { + "description": "Composition of k-spaced amino acid group pairs over the iFeature 5-group alphabet.", + "module": "composition", + "category": "Composition", + "abbreviation": "CKSAAGP", + "parameters": { + "sequence": "str — protein sequence", + "gap": "int (default=5) — maximum spacing", + }, + "output_shape": "1 x 25·(gap+1) (default 1 x 150)", + "speed": "Fast", + "reference": "Chen et al. (2018). Bioinformatics, 34(14), 2499-2502 (iFeature).", + "doi": "10.1093/bioinformatics/bty140", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "dde": { + "description": "Dipeptide deviation from expected mean: (Dc - Tm) / sqrt(Tv), with Tm from codon counts.", + "module": "composition", + "category": "Composition", + "abbreviation": "DDE", + "parameters": { + "sequence": "str — protein sequence (length >= 2)", + }, + "output_shape": "1 x 400", + "speed": "Fast", + "reference": "Saravanan & Gautham (2015). OMICS, 19(10), 648-658.", + "doi": "10.1089/omi.2015.0095", + "descriptor_sets": ["all"], + "in_default_set": False, + }, + "asdc": { + "description": "Adaptive skip dipeptide composition: composition of all ordered pairs (s_i, s_j), i < j.", + "module": "composition", + "category": "Composition", + "abbreviation": "ASDC", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 400", + "speed": "Fast", + "reference": "Wei et al. (2018). Bioinformatics, 34(23), 4007-4016 (ACPred-FL).", + "doi": "10.1093/bioinformatics/bty451", + "descriptor_sets": ["all"], + "in_default_set": False, + }, + "distance_pair": { + "description": "PseAAC of distance-pairs over a reduced cluster profile (cp13/cp14/cp19/cp20).", + "module": "composition", + "category": "Composition", + "abbreviation": "DP", + "parameters": { + "sequence": "str — protein sequence", + "rng": "int (default=3) — maximum pair distance", + "alphabet": "str (default='cp14') — cluster profile", + }, + "output_shape": "1 x (G + G²·rng) (default 1 x 602)", + "speed": "Fast", + "reference": "Liu et al. (2014). PLoS ONE, 9(9), e106691 (iDNA-Prot|dis).", + "doi": "10.1371/journal.pone.0106691", + "descriptor_sets": ["all"], + "in_default_set": False, + }, + "natural_vector": { + "description": "Natural vector: count, mean position and normalised second central moment of each amino acid.", + "module": "composition", + "category": "Composition", + "abbreviation": "NV", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 60", + "speed": "Fast", + "reference": "Yu et al. (2013). J. Theor. Biol., 318, 197-204.", + "doi": "10.1016/j.jtbi.2012.11.005", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "atomic_composition": { + "description": "Percentage of C, H, N, O and S atoms over the residues' free amino-acid formulas.", + "module": "composition", + "category": "Composition", + "abbreviation": "ATC", + "parameters": { + "sequence": "str — protein sequence", + "subtract_water": "bool (default=False) — remove (L-1) H2O", + }, + "output_shape": "1 x 5", + "speed": "Fast", + "reference": "Pande et al. (2023). J. Comput. Biol., 30(2), 204-222 (Pfeature).", + "doi": "10.1089/cmb.2022.0241", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "z_scales_5": { + "description": "Mean of the Sandberg 5 z-scales over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "Z5", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 5·domains (default 1 x 5)", + "speed": "Fast", + "reference": "Sandberg et al. (1998). J. Med. Chem., 41(14), 2481-2491.", + "doi": "10.1021/jm9700575", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "vhse": { + "description": "Mean of the VHSE (hydrophobic, steric, electronic) scales over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "VHSE", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 8·domains (default 1 x 8)", + "speed": "Fast", + "reference": "Mei et al. (2005). Biopolymers, 80(6), 775-786.", + "doi": "10.1002/bip.20296", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "atchley_factors": { + "description": "Mean of the Atchley factors I-V over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "AF", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 5·domains (default 1 x 5)", + "speed": "Fast", + "reference": "Atchley et al. (2005). PNAS, 102(18), 6395-6400.", + "doi": "10.1073/pnas.0408677102", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "t_scales": { + "description": "Mean of the topological T-scales over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "T", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 5·domains (default 1 x 5)", + "speed": "Fast", + "reference": "Tian, Zhou & Li (2007). J. Mol. Struct., 830, 106-115.", + "doi": "10.1016/j.molstruc.2006.07.004", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "st_scales": { + "description": "Mean of the structural topology ST-scales over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "ST", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 8·domains (default 1 x 8)", + "speed": "Fast", + "reference": "Yang et al. (2010). Amino Acids, 38(3), 805-816.", + "doi": "10.1007/s00726-009-0287-y", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "protfp": { + "description": "Mean of the ProtFP (PCA, 8 components) over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "ProtFP", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 8·domains (default 1 x 8)", + "speed": "Fast", + "reference": "van Westen et al. (2013). J. Cheminform., 5, 41.", + "doi": "10.1186/1758-2946-5-41", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "fasgai": { + "description": "Mean of the FASGAI factor scales over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "FASGAI", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 6·domains (default 1 x 6)", + "speed": "Fast", + "reference": "Liang & Li (2007). QSAR Comb. Sci., 26(6), 754-763.", + "doi": "10.1002/qsar.200630145", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "blosum_indices": { + "description": "Mean of the BLOSUM62-derived indices over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "BLOSUM", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 10·domains (default 1 x 10)", + "speed": "Fast", + "reference": "Georgiev (2009). J. Comput. Biol., 16(5), 703-723.", + "doi": "10.1089/cmb.2008.0173", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "ms_whim": { + "description": "Mean of the MS-WHIM scores over the sequence (optionally per domain).", + "module": "scales", + "category": "Scales", + "abbreviation": "MSWHIM", + "parameters": { + "sequence": "str — protein sequence", + "domains": "int (default=1) — number of near-equal segments", + }, + "output_shape": "1 x 3·domains (default 1 x 3)", + "speed": "Fast", + "reference": "Zaliani & Gancia (1999). J. Chem. Inf. Comput. Sci., 39(3), 525-533.", + "doi": "10.1021/ci980211b", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "auto_cross_covariance": { + "description": "Auto/cross-covariance of an amino-acid scale: sum z_j(i) z_k(i+l) / (L-l).", + "module": "scales", + "category": "Scales", + "abbreviation": "ACC", + "parameters": { + "sequence": "str — protein sequence", + "scale": "str | list[str] (default='z_scales_5') — factor scale or AAindex code(s)", + "lag": "int (default=5) — maximum lag", + "mode": "str (default='acc') — 'acc', 'ac' or 'cc'", + }, + "output_shape": "1 x d²·lag (default 1 x 125)", + "speed": "Fast", + "reference": "Wold et al. (1993). Anal. Chim. Acta, 277(2), 239-253.", + "doi": "10.1016/0003-2670(93)80437-P", + "descriptor_sets": ["all"], + "in_default_set": False, + }, + "physicochemical_distance_transformation": { + "description": "Physicochemical distance transformation: mean squared difference of standardised AAindex properties at lag l.", + "module": "scales", + "category": "Scales", + "abbreviation": "PDT", + "parameters": { + "sequence": "str — protein sequence", + "lag": "int (default=3) — maximum lag", + "properties": "list[str] | None — AAindex codes (default: 8 autocorrelation properties)", + }, + "output_shape": "1 x len(properties)·lag (default 1 x 24)", + "speed": "Fast", + "reference": "Liu et al. (2012). PLoS ONE, 7(9), e46633.", + "doi": "10.1371/journal.pone.0046633", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "charge_fractions": { + "description": "Fraction of charged residues (FCR), positive/negative fractions and charge asymmetry sigma = NCPR²/FCR.", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "FCR", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 4", + "speed": "Fast", + "reference": "Das & Pappu (2013). PNAS, 110(33), 13392-13397.", + "doi": "10.1073/pnas.1304749110", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "ncpr": { + "description": "Net charge per residue: (n+ - n-) / L with K/R positive and D/E negative.", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "NCPR", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 1", + "speed": "Fast", + "reference": "Mao et al. (2010). PNAS, 107(18), 8183-8188.", + "doi": "10.1073/pnas.0911107107", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "kappa": { + "description": "Das-Pappu kappa: normalised blob charge-asymmetry deviation measuring segregation of opposite charges (0 mixed, 1 segregated).", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "Kappa", + "parameters": { + "sequence": "str — protein sequence", + "blob_sizes": "tuple[int] (default=(5, 6)) — blob sizes averaged", + }, + "output_shape": "1 x (1 + len(blob_sizes)) (default 1 x 3)", + "speed": "Moderate", + "reference": "Das & Pappu (2013). PNAS, 110(33), 13392-13397.", + "doi": "10.1073/pnas.1304749110", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "sequence_charge_decoration": { + "description": "Sequence charge decoration: (1/L) sum_{m>n} q_m q_n sqrt(m-n).", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "SCD", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 1", + "speed": "Fast", + "reference": "Sawle & Ghosh (2015). J. Chem. Phys., 143(8), 085101.", + "doi": "10.1063/1.4929391", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "charge_density": { + "description": "Net charge at the given pH divided by molecular weight (charges per Da).", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "ChargeDensity", + "parameters": { + "sequence": "str — protein sequence", + "ph": "float (default=7.4) — pH", + }, + "output_shape": "1 x 1", + "speed": "Fast", + "reference": "Müller et al. (2017). Bioinformatics, 33(17), 2753-2755 (modlAMP).", + "doi": "10.1093/bioinformatics/btx285", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "hydrophobicity_profile": { + "description": "Summary statistics of a sliding-window hydrophobicity profile (ProtScale-style edge weighting).", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "HP", + "parameters": { + "sequence": "str — protein sequence", + "scale": "str (default='kyte_doolittle') — single-value scale or AAindex code", + "window": "int (default=9) — window length", + "edge": "float (default=1.0) — relative end weight", + "threshold": "float | None (default=None) — adds fraction of windows above threshold", + }, + "output_shape": "1 x 4 (1 x 5 with threshold)", + "speed": "Fast", + "reference": "Kyte & Doolittle (1982). J. Mol. Biol., 157(1), 105-132.", + "doi": "10.1016/0022-2836(82)90515-0", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "hydrophobic_ratio": { + "description": "APD hydrophobic ratio: percentage of L, I, V, F, M, A, C, W residues.", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "HydrophobicRatio", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 1", + "speed": "Fast", + "reference": "Wang (2020). Protein Sci., 29(1), 8-18 (APD); Wang, Li & Wang (2016). Nucleic Acids Res., 44, D1087 (APD3).", + "doi": "10.1002/pro.3702", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "half_life": { + "description": "N-end rule half-life (hours) in mammalian, yeast and E. coli systems from the N-terminal residue.", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "HalfLife", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 3", + "speed": "Fast", + "reference": "Bachmair, Finley & Varshavsky (1986). Science, 234(4773), 179-186.", + "doi": "10.1126/science.3018930", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "ivywrel_index": { + "description": "Fraction of I, V, Y, W, R, E, L residues (thermophilic adaptation index).", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "IVYWREL", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 1", + "speed": "Fast", + "reference": "Zeldovich, Berezovsky & Shakhnovich (2007). PLoS Comput. Biol., 3(1), e5.", + "doi": "10.1371/journal.pcbi.0030005", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "cvp_bias": { + "description": "Charged versus polar bias: %(D+E+K+R) - %(N+Q+S+T).", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "CvP", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 1", + "speed": "Fast", + "reference": "Suhre & Claverie (2003). J. Biol. Chem., 278(19), 17198-17202.", + "doi": "10.1074/jbc.M301327200", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "flexibility": { + "description": "Summary statistics of the Vihinen average-B-value flexibility profile (window 9, end weight 0.25).", + "module": "physicochemical", + "category": "Physicochemical", + "abbreviation": "Flex", + "parameters": { + "sequence": "str — protein sequence", + "window": "int (default=9) — window length", + }, + "output_shape": "1 x 4", + "speed": "Fast", + "reference": "Vihinen, Torkkila & Riikonen (1994). Proteins, 19(2), 141-149.", + "doi": "10.1002/prot.340190207", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "wootton_federhen_complexity": { + "description": "Wootton-Federhen local complexity (K1, K2) over sliding windows and the SEG-trigger low-complexity fraction.", + "module": "complexity", + "category": "Complexity", + "abbreviation": "WF", + "parameters": { + "sequence": "str — protein sequence", + "window": "int (default=12) — window length", + "k2_trigger": "float (default=2.2) — K2 trigger (bits)", + }, + "output_shape": "1 x 4", + "speed": "Fast", + "reference": "Wootton & Federhen (1993). Comput. Chem., 17(2), 149-163.", + "doi": "10.1016/0097-8485(93)85006-X", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "linguistic_complexity": { + "description": "Linguistic complexity: distinct subwords observed / maximum possible (ratio of sums over word lengths).", + "module": "complexity", + "category": "Complexity", + "abbreviation": "LC", + "parameters": { + "sequence": "str — protein sequence", + "max_k": "int | None (default=None) — longest word length (default L)", + }, + "output_shape": "1 x 1", + "speed": "Fast", + "reference": "Troyanskaya et al. (2002). Bioinformatics, 18(5), 679-688.", + "doi": "10.1093/bioinformatics/18.5.679", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "lempel_ziv_complexity": { + "description": "Lempel-Ziv (LZ76) complexity c(n) and its normalised value c(n)·log20(n)/n.", + "module": "complexity", + "category": "Complexity", + "abbreviation": "LZ", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 2", + "speed": "Fast", + "reference": "Lempel & Ziv (1976). IEEE Trans. Inf. Theory, 22(1), 75-81.", + "doi": "10.1109/TIT.1976.1055501", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "fft_descriptors": { + "description": "Mean power of the FFT spectrum of a mean-centred property signal (default EIIP) in equal-width frequency bins.", + "module": "signal", + "category": "Signal", + "abbreviation": "FFT", + "parameters": { + "sequence": "str — protein sequence", + "scale": "str | dict (default='eiip') — single-value scale, AAindex code or dict", + "n_bins": "int (default=10) — number of frequency bins", + }, + "output_shape": "1 x n_bins (default 1 x 10)", + "speed": "Fast", + "reference": "Cosic (1994). IEEE Trans. Biomed. Eng., 41(12), 1101-1114.", + "doi": "10.1109/10.335859", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "dct_descriptors": { + "description": "First n orthonormal DCT-II coefficients of a mean-centred property signal (default Kyte-Doolittle).", + "module": "signal", + "category": "Signal", + "abbreviation": "DCT", + "parameters": { + "sequence": "str — protein sequence", + "scale": "str | dict (default='kyte_doolittle') — single-value scale, AAindex code or dict", + "n_coeffs": "int (default=10) — number of coefficients (<= L)", + }, + "output_shape": "1 x n_coeffs (default 1 x 10)", + "speed": "Fast", + "reference": "Wang et al. (2017). PLoS ONE, 12(9), e0185587.", + "doi": "10.1371/journal.pone.0185587", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "aggrescan": { + "description": "AGGRESCAN aggregation hot-spot descriptors from the a3v propensity profile (hot spots: >=5 residues above -0.02, no Pro).", + "module": "structural", + "category": "Structural", + "abbreviation": "AGG", + "parameters": { + "sequence": "str — protein sequence", + "window": "int (default=5) — odd window length (AGGRESCAN uses 5, 7, 9, 11)", + }, + "output_shape": "1 x 12", + "speed": "Fast", + "reference": "Conchillo-Solé et al. (2007). BMC Bioinformatics, 8, 65.", + "doi": "10.1186/1471-2105-8-65", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "uversky_charge_hydropathy": { + "description": "Uversky mean hydropathy , mean net charge and boundary distance HQ = 2.785 - - 1.151.", + "module": "structural", + "category": "Structural", + "abbreviation": "UV", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 4", + "speed": "Fast", + "reference": "Uversky, Gillespie & Fink (2000). Proteins, 41(3), 415-427.", + "doi": "10.1002/1097-0134(20001115)41:3<415::AID-PROT130>3.0.CO;2-7", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "fold_index": { + "description": "FoldIndex profile summary: windowed 2.785 - abs() - 1.151 and disordered-residue statistics.", + "module": "structural", + "category": "Structural", + "abbreviation": "FI", + "parameters": { + "sequence": "str — protein sequence", + "window": "int (default=51) — window length", + }, + "output_shape": "1 x 4", + "speed": "Fast", + "reference": "Prilusky et al. (2005). Bioinformatics, 21(16), 3435-3438.", + "doi": "10.1093/bioinformatics/bti537", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "disorder_propensity": { + "description": "Fractions of disorder- and order-promoting residues and mean TOP-IDP value.", + "module": "structural", + "category": "Structural", + "abbreviation": "DP", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 3", + "speed": "Fast", + "reference": "Campen et al. (2008). Protein Pept. Lett., 15(9), 956-963.", + "doi": "10.2174/092986608785849164", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "transmembrane_features": { + "description": "Kyte-Doolittle transmembrane-segment heuristics (segment count, max hydropathy, TM fraction, positive-inside bias).", + "module": "structural", + "category": "Structural", + "abbreviation": "TM", + "parameters": { + "sequence": "str — protein sequence", + "window": "int (default=19) — hydropathy window", + "kd_threshold": "float (default=1.6) — segment threshold", + }, + "output_shape": "1 x 4", + "speed": "Fast", + "reference": "Kyte & Doolittle (1982). J. Mol. Biol., 157(1), 105-132.", + "doi": "10.1016/0022-2836(82)90515-0", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "signal_peptide_features": { + "description": "N-terminal signal-peptide heuristics: n-region net charge and maximum 8-residue hydropathy.", + "module": "structural", + "category": "Structural", + "abbreviation": "SP", + "parameters": { + "sequence": "str — protein sequence", + "n_term": "int (default=30) — N-terminal region length", + }, + "output_shape": "1 x 2", + "speed": "Fast", + "reference": "von Heijne (1983). Eur. J. Biochem., 133(1), 17-21.", + "doi": "10.1111/j.1432-1033.1983.tb07424.x", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "functional_site_counts": { + "description": "Counts and per-100-residue densities of PROSITE N-glycosylation, ER-retention and PTS1 motifs and tryptic sites.", + "module": "structural", + "category": "Structural", + "abbreviation": "FS", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 8", + "speed": "Fast", + "reference": "Sigrist et al. (2013). Nucleic Acids Res., 41(D1), D344-D347 (PROSITE).", + "doi": "10.1093/nar/gks1067", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "plaac": { + "description": "PLAAC prion-like domain scores: max window LLR, HMM core score, PrD length and presence.", + "module": "structural", + "category": "Structural", + "abbreviation": "PLAAC", + "parameters": { + "sequence": "str — protein sequence", + "core_length": "int (default=60) — minimal core length", + }, + "output_shape": "1 x 4", + "speed": "Fast", + "reference": "Lancaster et al. (2014). Bioinformatics, 30(17), 2501-2502.", + "doi": "10.1093/bioinformatics/btu310", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "antigenicity_surface": { + "description": "Profile statistics of the Parker hydrophilicity and Janin surface scales over 7-residue windows.", + "module": "structural", + "category": "Structural", + "abbreviation": "Antigenicity", + "parameters": { + "sequence": "str — protein sequence", + "window": "int (default=7) — window length", + }, + "output_shape": "1 x 8", + "speed": "Fast", + "reference": "Parker, Guo & Hodges (1986). Biochemistry, 25(19), 5425-5432.", + "doi": "10.1021/bi00367a013", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, + }, + "cysteine_features": { + "description": "Cysteine count, fraction, parity, maximum disulfides, CxxC motifs and mean spacing.", + "module": "structural", + "category": "Structural", + "abbreviation": "Cys", + "parameters": { + "sequence": "str — protein sequence", + }, + "output_shape": "1 x 6", + "speed": "Fast", + "reference": "Fomenko & Gladyshev (2003). Biochemistry, 42(38), 11214-11225.", + "doi": "10.1021/bi034459s", + "descriptor_sets": ["extended", "all"], + "in_default_set": False, }, } @@ -645,6 +1430,8 @@ def get_descriptor_info(name: str) -> dict: - ``output_shape`` : string describing output DataFrame shape - ``speed`` : speed rating (Fast / Moderate / Slow) - ``reference`` : primary citation + - ``doi`` : DOI of the primary citation (None for textbooks / sources without one) + - ``in_default_set`` : whether the descriptor is part of the classic ``all_descriptors`` set Raises ====== @@ -683,7 +1470,8 @@ def list_descriptors(category: str | None = None) -> list[str]: :category: str | None (default=None) If provided, only descriptors belonging to this category are returned. Available categories: "Composition", "Autocorrelation", "Conjoint Triad", - "CTD", "Sequence Order". Case-insensitive. + "CTD", "Sequence Order", "Scales", "Physicochemical", "Complexity", + "Signal", "Structural". Case-insensitive. Returns ======= @@ -737,4 +1525,6 @@ def print_descriptor_info(name: str) -> None: for param, desc in info["parameters"].items(): print(f" - {param}: {desc}") print(f"\n Reference: {info['reference']}") + if info.get("doi"): + print(f" DOI : https://doi.org/{info['doi']}") print(f"{'='*60}\n") diff --git a/pyproject.toml b/pyproject.toml index ca78273..5bbc974 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "protpy" -version = "1.4.2" +version = "1.5.0" description = "Python package for generating various biochemical, physicochemical and structural descriptors/features of protein sequences." readme = "README.md" license = { text = "MIT" } @@ -82,4 +82,5 @@ where = ["."] "protpy.data" = ["*.json"] [tool.pytest.ini_options] -norecursedirs = ["docs/_build"] +testpaths = ["tests"] +norecursedirs = ["docs/_build", "build", "dist", "*.egg-info"] diff --git a/tests/README.md b/tests/README.md index dff9d87..a681ed8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -7,16 +7,29 @@ All tests in the project were ran using Python's unittest testing framework (htt python3 -m unittest discover tests ``` -You can add the flag *-b* to suppress some of the verbose output when running the unittests. +You can add the flag *-b* to suppress some of the verbose output when running the unittests. The CI workflow runs the same tests with `python -m pytest`. Unit tests ---------- -* `test_protpy.py` - unit tests for overall `protpy` package. -* `test_autocorrellation.py` - unit tests for autocorrelation module and functionality. +* `test_protpy.py` - unit tests for overall `protpy` package (version, exported descriptor names). +* `test_autocorrelation.py` - unit tests for autocorrelation module and functionality. * `test_composition.py` - unit tests for composition module and functionality, including pseudo composition and amphiphillic composition. +* `test_composition_extended.py` - v1.5.0 composition descriptors (CKSAAP, CKSAAGP, DDE, ASDC, distance pair, natural vector, atomic composition) and the GAAC/GDPC/GTPC, KSCTriad, 13-property CTD and split-composition extensions. * `test_conjoint_triad.py` - unit tests for conjoint triad module and functionality. * `test_ctd.py` - unit tests for CTD module and functionality. * `test_sequence_order.py` - unit tests for sequence order module and functionality. +* `test_scales.py` - factor scales, auto/cross covariance and PDT (optional cross-check against `peptides`, skipped if not installed). +* `test_physicochemical.py` - charge patterning (including the published Das & Pappu kappa values), pKa scales, hydrophobicity profile, half-life, flexibility and the GRAVY / hydrophobic moment / extinction coefficient extensions. +* `test_complexity.py` - Wootton-Federhen, linguistic and Lempel-Ziv complexity. +* `test_signal.py` - FFT and DCT descriptors. +* `test_structural.py` - AGGRESCAN, Uversky/FoldIndex, disorder propensity, TM and signal-peptide features, functional sites, PLAAC (against PLAAC's reference output), antigenicity and cysteine features. +* `test_encode.py` - `all_descriptors`, `encode_dataset` and `read_sequences_from_file`. +* `test_descriptor_sets.py` - `descriptor_set`, `include` and `exclude`, and every v1.5.0 descriptor on 1-8 residue sequences. +* `test_utils.py` - descriptor registry and metadata functions. +* `test_helpers.py` - internal helpers, data-table provenance, registry parameters matching function signatures, and registry DOIs present in `docs/references.bib`. +* `test_regressions.py` - regression tests for bugs fixed in the post-1.4.2 audit. +* `test_backwards_compat.py` - golden snapshot: every v1.4.2 default output must be unchanged. Regenerate only when deliberately re-baselining: `python tests/test_backwards_compat.py --regen`. +* `test_performance.py` - timing test for `descriptor_set="all"` on a 500-residue sequence; skipped unless `PROTPY_PERF=1` is set. Test Files ---------- @@ -24,10 +37,14 @@ Test Files * `test_data/test_fasta2.fasta` - Nucleoprotein (P59595 · NCAP_SARS) [[2]](#references). * `test_data/test_fasta3.fasta` - Envelope small membrane protein (P59637 · VEMP_SARS) [[3]](#references). * `test_data/test_fasta4.fasta` - Nucleoprotein (P0DTC9 · NCAP_SARS2) [[4]](#references). +* `test_data/structural_reference.fasta` - MOT3 (from the MIT-licensed PLAAC repository example), glycophorin A (P02724) [[5]](#references) and amyloid-β 1-42 (P05067 residues 672-713) [[6]](#references), used by `test_structural.py`. +* `test_data/golden_v1_4_2.json.gz` - golden outputs used by `test_backwards_compat.py`. References ---------- \[1\]: https://www.uniprot.org/uniprotkb/P59594/entry
\[2\]: https://www.uniprot.org/uniprotkb/P59595/entry
\[3\]: https://www.uniprot.org/uniprotkb/P59637/entry
-\[4\]: https://www.uniprot.org/uniprotkb/P0DTC9/entry \ No newline at end of file +\[4\]: https://www.uniprot.org/uniprotkb/P0DTC9/entry
+\[5\]: https://www.uniprot.org/uniprotkb/P02724/entry
+\[6\]: https://www.uniprot.org/uniprotkb/P05067/entry \ No newline at end of file diff --git a/tests/test_autocorrelation.py b/tests/test_autocorrelation.py index f1e3e2a..3581251 100644 --- a/tests/test_autocorrelation.py +++ b/tests/test_autocorrelation.py @@ -259,7 +259,7 @@ def test_moran_autocorrelation(self): properties=["CIDH920105"], normalize=False) moran_nf_expected = pd.DataFrame(columns=["MAuto_CIDH920105_1", "MAuto_CIDH920105_2", "MAuto_CIDH920105_3", "MAuto_CIDH920105_4", "MAuto_CIDH920105_5"]) - moran_nf_expected.loc[0] = [-0.05779, -0.0779, -0.04184, -0.02121, -0.05371] + moran_nf_expected.loc[0] = [-0.05781, -0.07792, -0.04185, -0.02122, -0.05372] self.assertIsInstance(moran_nf_seq1, pd.DataFrame, 'Expected output to be of type DataFrame, got {}.'.format(type(moran_nf_seq1))) self.assertEqual(moran_nf_seq1.shape, (1, lag), diff --git a/tests/test_backwards_compat.py b/tests/test_backwards_compat.py new file mode 100644 index 0000000..1ce365f --- /dev/null +++ b/tests/test_backwards_compat.py @@ -0,0 +1,84 @@ +########################################################################### +############### protPy - Backwards Compatibility Tests ############### +########################################################################### + +import gzip +import json +import os +import sys +import unittest + +from Bio import SeqIO + +import protpy + +TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_data") +GOLDEN = os.path.join(TEST_DATA, "golden_v1_4_2.json.gz") + +#public descriptors that existed in v1.4.2; their default outputs must never change +V1_4_2_DESCRIPTORS = [ + "amino_acid_composition", "dipeptide_composition", "tripeptide_composition", + "gravy", "aromaticity", "instability_index", "isoelectric_point", "molecular_weight", + "charge_distribution", "hydrophobic_polar_charged_composition", "secondary_structure_propensity", + "kmer_composition", "reduced_alphabet_composition", "motif_composition", + "amino_acid_pair_composition", "aliphatic_index", "extinction_coefficient", "boman_index", + "aggregation_propensity", "hydrophobic_moment", + "moreaubroto_autocorrelation", "moran_autocorrelation", "geary_autocorrelation", + "pseudo_amino_acid_composition", "amphiphilic_pseudo_amino_acid_composition", + "conjoint_triad", "ctd_composition", "ctd_transition", "ctd_distribution", "ctd_", + "sequence_order_coupling_number_", "sequence_order_coupling_number", + "sequence_order_coupling_number_all", "quasi_sequence_order", "quasi_sequence_order_all", + "shannon_entropy", +] + + +def _sequences() -> dict: + seqs = {} + for name in sorted(os.listdir(TEST_DATA)): + #only the original v1.4.2 test sequences are pinned + if name.startswith("test_fasta") and name.endswith(".fasta"): + with open(os.path.join(TEST_DATA, name)) as f: + seqs[name] = str(next(SeqIO.parse(f, "fasta")).seq) + return seqs + + +def _record(df) -> dict: + #json round-trips python floats exactly, so exact comparison is safe + if not hasattr(df, "columns"): #scalar-returning descriptor + return {"type": type(df).__name__, "value": df} + values = [df.iloc[0, i] for i in range(df.shape[1])] + return {"columns": [str(c) for c in df.columns], "dtypes": [str(d) for d in df.dtypes], + "values": [v.item() if hasattr(v, "item") else v for v in values], "shape": list(df.shape)} + + +def compute_snapshot() -> dict: + """Compute every default output that the golden snapshot pins.""" + out = {} + for fname, seq in _sequences().items(): + out[f"{fname}::all_descriptors"] = _record(protpy.all_descriptors(seq, _show_descriptor_list=False)) + for desc in V1_4_2_DESCRIPTORS: + out[f"{fname}::{desc}"] = _record(getattr(protpy, desc)(seq)) + return out + + +class ProtpyBackwardsCompatTests(unittest.TestCase): + """Default descriptor outputs must be identical to the v1.4.2 (post-audit) baseline.""" + + def test_golden_snapshot(self): + with gzip.open(GOLDEN, "rt") as f: + golden = json.load(f) + current = json.loads(json.dumps(compute_snapshot())) + self.assertEqual(sorted(golden), sorted(current)) + for key, expected in golden.items(): + with self.subTest(key=key): + self.assertEqual(current[key], expected) + + +if __name__ == "__main__": + #regenerate only when deliberately re-baselining: python tests/test_backwards_compat.py --regen + if "--regen" in sys.argv: + with gzip.open(GOLDEN, "wt") as f: + json.dump(compute_snapshot(), f) + print(f"wrote {GOLDEN}") + else: + unittest.main() diff --git a/tests/test_complexity.py b/tests/test_complexity.py new file mode 100644 index 0000000..2ce8e96 --- /dev/null +++ b/tests/test_complexity.py @@ -0,0 +1,67 @@ +########################################################################### +############### protPy - Complexity Module Tests (Phase 5) ############ +########################################################################### + +import math +import random +import unittest + +import protpy +from protpy.complexity import _lz76 + +SEQ = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGD" + + +class ProtpyComplexityTests(unittest.TestCase): + + def _check_invalid(self, func): + with self.assertRaises(TypeError): + func(None) + with self.assertRaises(ValueError): + func("") + with self.assertRaises(ValueError): + func("AJX") + + def test_wootton_federhen(self): + df = protpy.wootton_federhen_complexity(SEQ) + self.assertEqual(list(df.columns), ["WF_K1_mean", "WF_K2_mean", "WF_K2_min", "WF_LowComplexity_frac"]) + #poly-A: K1 = K2 = 0, everything low complexity + self.assertEqual(protpy.wootton_federhen_complexity("A" * 30).values[0].tolist(), [0.0, 0.0, 0.0, 1.0]) + #12 distinct residues in one window: K2 = log2(12), K1 = log20(12!)/12 + df = protpy.wootton_federhen_complexity("ACDEFGHIKLMN") + self.assertEqual(df["WF_K2_mean"].iloc[0], round(math.log2(12), 3)) + self.assertEqual(df["WF_K1_mean"].iloc[0], round(math.log(math.factorial(12), 20) / 12, 3)) + self.assertEqual(df["WF_LowComplexity_frac"].iloc[0], 0.0) + #a poly-Q tract inside a complex sequence is partly flagged + frac = protpy.wootton_federhen_complexity(SEQ + "Q" * 20 + SEQ)["WF_LowComplexity_frac"].iloc[0] + self.assertTrue(0 < frac < 1) + self._check_invalid(protpy.wootton_federhen_complexity) + + def test_linguistic_complexity(self): + random.seed(0) + rand = "".join(random.choice(protpy.amino_acids) for _ in range(50)) + self.assertGreater(protpy.linguistic_complexity(rand).iloc[0, 0], 0.95) + self.assertLess(protpy.linguistic_complexity("A" * 50).iloc[0, 0], 0.1) + #"ACA": k=1 {A,C} of 2 possible (min(20,3)=3)... observed 2+2+1=5, possible 3+2+1=6 + self.assertEqual(protpy.linguistic_complexity("ACA").iloc[0, 0], round(5 / 6, 3)) + self.assertEqual(protpy.linguistic_complexity("ACA", max_k=1).iloc[0, 0], round(2 / 3, 3)) + self._check_invalid(protpy.linguistic_complexity) + + def test_lempel_ziv(self): + #Lempel & Ziv (1976) example 0001101001000101 parses as 0.001.10.100.1000.101 -> 6 + self.assertEqual(_lz76("0001101001000101".replace("0", "A").replace("1", "C")), 6) + self.assertEqual(_lz76("A" * 20), 2) + df = protpy.lempel_ziv_complexity(SEQ) + self.assertEqual(list(df.columns), ["LZ_Complexity", "LZ_Normalized"]) + c = df["LZ_Complexity"].iloc[0] + self.assertEqual(df["LZ_Normalized"].iloc[0], round(c * math.log(len(SEQ), 20) / len(SEQ), 3)) + self.assertEqual(protpy.lempel_ziv_complexity("A").values[0].tolist(), [1, 0.0]) + self._check_invalid(protpy.lempel_ziv_complexity) + + def test_shannon_reexport(self): + from protpy import complexity + self.assertIs(complexity.shannon_entropy, protpy.shannon_entropy) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_composition.py b/tests/test_composition.py index d4500e3..0963701 100644 --- a/tests/test_composition.py +++ b/tests/test_composition.py @@ -11,6 +11,7 @@ unittest.TestLoader.sortTestMethodsUsing = None import protpy as protpy +from protpy.composition import normalize_property class ProtpyCompositionTests(unittest.TestCase): """ @@ -231,7 +232,7 @@ def test_dipeptide_composition(self): dipeptide_comp_seq1 = protpy.dipeptide_composition(self.protein_seq1) #testing first 10 columns dipeptide_comp_seq1_expected_values = pd.DataFrame(columns=["AA", "AC", "AD", "AE", "AF", "AG", "AH", "AI", "AK", "AL"]) - dipeptide_comp_seq1_expected_values.loc[0] = [0.72, 0.16, 0.48, 0.4, 0.24, 0.48, 0.0, 0.56, 0.16, 0.48] + dipeptide_comp_seq1_expected_values.loc[0] = [0.8, 0.16, 0.48, 0.4, 0.24, 0.48, 0.0, 0.56, 0.16, 0.48] self.assertIsInstance(dipeptide_comp_seq1, pd.DataFrame, 'Expected output to be of type DataFrame, got {}.'.format(type(dipeptide_comp_seq1))) @@ -460,6 +461,18 @@ def test_tripeptide_composition(self): with self.assertRaises(TypeError): tripeptide_composition_seq8 = protpy.tripeptide_composition(invalid_seq8) + def test_short_sequence_pseudo_amino_acid_composition(self): + """Short sequences should not crash when lamda exceeds the usable distance.""" + result = protpy.pseudo_amino_acid_composition("A") + self.assertIsInstance(result, pd.DataFrame) + self.assertEqual(result.shape, (1, 50)) + self.assertFalse(result.isnull().any().any()) + + def test_zero_variance_property_normalization(self): + """Properties with zero variance should normalize safely to zeros.""" + result = normalize_property({"A": 1.0, "C": 1.0, "D": 1.0}) + self.assertEqual(result, {"A": 0.0, "C": 0.0, "D": 0.0}) + # @unittest.skip("Descriptor can take quite a bit of time to calculate therefore skipping.") #** def test_pseudo_amino_acid_composition(self): """ Testing pseudo amino acid composition protein descriptor attributes and functionality. """ @@ -753,16 +766,16 @@ def test_isoelectric_point(self): 'Expected 5.412 for seq1, got {}.'.format(pi_seq1["IsoelectricPoint"].values[0])) #2.) pi_seq2 = protpy.isoelectric_point(self.protein_seq2) - self.assertEqual(pi_seq2["IsoelectricPoint"].values[0], 10.514, - 'Expected 10.514 for seq2, got {}.'.format(pi_seq2["IsoelectricPoint"].values[0])) + self.assertEqual(pi_seq2["IsoelectricPoint"].values[0], 10.515, + 'Expected 10.515 for seq2, got {}.'.format(pi_seq2["IsoelectricPoint"].values[0])) #3.) pi_seq3 = protpy.isoelectric_point(self.protein_seq3) self.assertEqual(pi_seq3["IsoelectricPoint"].values[0], 6.134, 'Expected 6.134 for seq3, got {}.'.format(pi_seq3["IsoelectricPoint"].values[0])) #4.) pi_seq4 = protpy.isoelectric_point(self.protein_seq4) - self.assertEqual(pi_seq4["IsoelectricPoint"].values[0], 10.472, - 'Expected 10.472 for seq4, got {}.'.format(pi_seq4["IsoelectricPoint"].values[0])) + self.assertEqual(pi_seq4["IsoelectricPoint"].values[0], 10.473, + 'Expected 10.473 for seq4, got {}.'.format(pi_seq4["IsoelectricPoint"].values[0])) #5.) with self.assertRaises(ValueError): protpy.isoelectric_point("ABCDEF") @@ -787,20 +800,20 @@ def test_molecular_weight(self): 'Incorrect columns: {}.'.format(list(mw_seq1.columns))) self.assertTrue(mw_seq1.any().isnull().sum() == 0, 'Expected no null values.') - self.assertEqual(mw_seq1["MolecularWeight"].values[0], 139122.355, - 'Expected 139122.355 for seq1, got {}.'.format(mw_seq1["MolecularWeight"].values[0])) + self.assertEqual(mw_seq1["MolecularWeight"].values[0], 139125.12, + 'Expected 139125.12 for seq1, got {}.'.format(mw_seq1["MolecularWeight"].values[0])) #2.) mw_seq2 = protpy.molecular_weight(self.protein_seq2) - self.assertEqual(mw_seq2["MolecularWeight"].values[0], 46023.455, - 'Expected 46023.455 for seq2, got {}.'.format(mw_seq2["MolecularWeight"].values[0])) + self.assertEqual(mw_seq2["MolecularWeight"].values[0], 46025.03, + 'Expected 46025.03 for seq2, got {}.'.format(mw_seq2["MolecularWeight"].values[0])) #3.) mw_seq3 = protpy.molecular_weight(self.protein_seq3) - self.assertEqual(mw_seq3["MolecularWeight"].values[0], 8360.93, - 'Expected 8360.93 for seq3, got {}.'.format(mw_seq3["MolecularWeight"].values[0])) + self.assertEqual(mw_seq3["MolecularWeight"].values[0], 8361.0, + 'Expected 8361.0 for seq3, got {}.'.format(mw_seq3["MolecularWeight"].values[0])) #4.) mw_seq4 = protpy.molecular_weight(self.protein_seq4) - self.assertEqual(mw_seq4["MolecularWeight"].values[0], 45624.194, - 'Expected 45624.194 for seq4, got {}.'.format(mw_seq4["MolecularWeight"].values[0])) + self.assertEqual(mw_seq4["MolecularWeight"].values[0], 45625.699, + 'Expected 45625.699 for seq4, got {}.'.format(mw_seq4["MolecularWeight"].values[0])) #5.) with self.assertRaises(ValueError): protpy.molecular_weight("ABCDEF") @@ -1164,13 +1177,13 @@ def test_boman_index(self): self.assertEqual(list(bi_seq1.columns), ["BomanIndex"], 'Incorrect columns: {}.'.format(list(bi_seq1.columns))) self.assertTrue(bi_seq1.any().isnull().sum() == 0, 'Expected no null values.') - self.assertEqual(bi_seq1["BomanIndex"].values[0], 0.119) + self.assertEqual(bi_seq1["BomanIndex"].values[0], 1.198) #2.) - self.assertEqual(protpy.boman_index(self.protein_seq2)["BomanIndex"].values[0], -0.498) + self.assertEqual(protpy.boman_index(self.protein_seq2)["BomanIndex"].values[0], 2.598) #3.) - self.assertEqual(protpy.boman_index(self.protein_seq3)["BomanIndex"].values[0], 1.237) + self.assertEqual(protpy.boman_index(self.protein_seq3)["BomanIndex"].values[0], -0.354) #4.) - self.assertEqual(protpy.boman_index(self.protein_seq4)["BomanIndex"].values[0], -0.455) + self.assertEqual(protpy.boman_index(self.protein_seq4)["BomanIndex"].values[0], 2.519) #5.) with self.assertRaises(ValueError): protpy.boman_index("ABCDEF") with self.assertRaises(ValueError): protpy.boman_index("OOOOO") diff --git a/tests/test_composition_extended.py b/tests/test_composition_extended.py new file mode 100644 index 0000000..fe8e12a --- /dev/null +++ b/tests/test_composition_extended.py @@ -0,0 +1,183 @@ +########################################################################### +######### protPy - v1.5.0 Composition Descriptor Tests (Phase 1) ####### +########################################################################### + +import itertools +import math +import unittest + +import numpy as np + +import protpy +from protpy.ctd import ctd_property_sets + +SEQ = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKVKALPDAQ" +AA = protpy.amino_acids + + +class ProtpyCompositionExtendedTests(unittest.TestCase): + + def _check_invalid(self, func, **bad): + with self.assertRaises(TypeError): + func(123) + with self.assertRaises(ValueError): + func("") + with self.assertRaises(ValueError): + func("ACDXZ") + for k, v in bad.items(): + with self.assertRaises(ValueError, msg=f"{func.__name__}({k}={v})"): + func(SEQ, **{k: v}) + + def test_cksaap(self): + df = protpy.cksaap(SEQ) + self.assertEqual(df.shape, (1, 2400)) + self.assertEqual(df.columns[0], "CKSAAP_AA_gap0") + self.assertEqual(df.columns[-1], "CKSAAP_YY_gap5") + self.assertTrue(all(dt == np.float64 for dt in df.dtypes)) + #gap=0 equals the dipeptide composition (which is rounded to 2 dp) + dpc = protpy.dipeptide_composition(SEQ) + gap0 = protpy.cksaap(SEQ, gap=0) + np.testing.assert_allclose(gap0.values[0], dpc.values[0], atol=0.006) + self.assertEqual(protpy.cksaap("AAAA", gap=1)["CKSAAP_AA_gap1"].iloc[0], 100.0) + #spacing with no pairs -> zeros + self.assertEqual(protpy.cksaap("AC", gap=2).filter(like="_gap2").values.sum(), 0) + self._check_invalid(protpy.cksaap, gap=-1) + + def test_cksaagp(self): + df = protpy.cksaagp(SEQ) + self.assertEqual(df.shape, (1, 150)) + self.assertEqual(df.columns[0], "CKSAAGP_aliphatic.aliphatic_gap0") + for g in range(6): + self.assertAlmostEqual(df.filter(like=f"_gap{g}").values.sum(), 100, delta=0.05) + #G and A are both aliphatic + self.assertEqual(protpy.cksaagp("GAG", gap=0)["CKSAAGP_aliphatic.aliphatic_gap0"].iloc[0], 100.0) + self._check_invalid(protpy.cksaagp, gap="x") + + def test_dde(self): + df = protpy.dde("ACDEFGHIKL") + self.assertEqual(df.shape, (1, 400)) + self.assertEqual(df.columns[1], "DDE_AC") + #hand computation of the AC cell: one AC dipeptide, A has 4 codons, C has 2 + dc, tm = 1 / 9, (4 / 61) * (2 / 61) + expected = (dc - tm) / math.sqrt(tm * (1 - tm) / 9) + self.assertAlmostEqual(df["DDE_AC"].iloc[0], round(expected, 3)) + #an absent dipeptide is negative + self.assertLess(df["DDE_AA"].iloc[0], 0) + with self.assertRaises(ValueError): + protpy.dde("A") + self._check_invalid(protpy.dde) + + def test_asdc(self): + df = protpy.asdc("ACA") + self.assertEqual(df.shape, (1, 400)) + for col in ("ASDC_AC", "ASDC_AA", "ASDC_CA"): + self.assertEqual(df[col].iloc[0], 33.333) + self.assertAlmostEqual(protpy.asdc(SEQ).values.sum(), 100, delta=0.05) + #brute force on SEQ + counts = {} + for i, j in itertools.combinations(range(len(SEQ)), 2): + counts[SEQ[i] + SEQ[j]] = counts.get(SEQ[i] + SEQ[j], 0) + 1 + total = len(SEQ) * (len(SEQ) - 1) / 2 + self.assertEqual(protpy.asdc(SEQ)["ASDC_KV"].iloc[0], round(counts["KV"] / total * 100, 3)) + self.assertEqual(protpy.asdc("A").values.sum(), 0) + self._check_invalid(protpy.asdc) + + def test_distance_pair(self): + df = protpy.distance_pair(SEQ) + self.assertEqual(df.shape, (1, 14 + 14 * 14 * 3)) + self.assertEqual(df.columns[0], "DP_IMV") + self.assertIn("DP_IMV.L_d1", df.columns) + self.assertEqual(protpy.distance_pair(SEQ, rng=3, alphabet="cp13").shape, (1, 13 + 169 * 3)) + self.assertEqual(protpy.distance_pair(SEQ, rng=1, alphabet="cp20").shape, (1, 420)) + #composition block sums to 100; each distance block sums to 100 + self.assertAlmostEqual(df.iloc[0, :14].sum(), 100, delta=0.05) + self.assertAlmostEqual(df.filter(like="_d2").values.sum(), 100, delta=0.05) + #I, M and V share a cp14 cluster + self.assertEqual(protpy.distance_pair("IMV", rng=2)["DP_IMV.IMV_d2"].iloc[0], 100.0) + self._check_invalid(protpy.distance_pair, rng=-1, alphabet="cp15") + + def test_reduced_alphabets_partition(self): + from protpy.composition import _named_alphabet + for name in ("ifeature5", "cp13", "cp14", "cp19", "cp20"): + residues = "".join(g for _, g in _named_alphabet(name)) + self.assertEqual(sorted(residues), AA, name) + self.assertEqual(len(_named_alphabet("cp14")), 14) + + def test_natural_vector(self): + df = protpy.natural_vector("ACA") + self.assertEqual(df.shape, (1, 60)) + #A at positions 1 and 3: n=2, mu=2, D2 = ((1-2)^2 + (3-2)^2) / (2*3) + self.assertEqual(df["NV_n_A"].iloc[0], 2) + self.assertEqual(df["NV_mu_A"].iloc[0], 2.0) + self.assertEqual(df["NV_D2_A"].iloc[0], round(2 / 6, 3)) + self.assertEqual(df["NV_mu_C"].iloc[0], 2.0) + self.assertEqual(df["NV_D2_C"].iloc[0], 0.0) + self.assertEqual(df["NV_mu_W"].iloc[0], 0.0) + self._check_invalid(protpy.natural_vector) + + def test_atomic_composition(self): + df = protpy.atomic_composition("GG", subtract_water=True) + self.assertEqual(list(df.columns), ["ATC_C", "ATC_H", "ATC_N", "ATC_O", "ATC_S"]) + #C4H8N2O3 -> 17 atoms + np.testing.assert_allclose(df.values[0], np.round(np.array([4, 8, 2, 3, 0]) / 17 * 100, 3)) + #default: free amino acids, no water removed (Pfeature convention): 2 x C2H5NO2 + np.testing.assert_allclose(protpy.atomic_composition("GG").values[0], [20, 50, 10, 20, 0]) + self.assertGreater(protpy.atomic_composition("MC")["ATC_S"].iloc[0], 0) + self.assertAlmostEqual(protpy.atomic_composition(SEQ).values.sum(), 100, delta=0.01) + self._check_invalid(protpy.atomic_composition) + + #extensions to existing functions + + def test_reduced_alphabet_extension(self): + #defaults unchanged + self.assertEqual(list(protpy.reduced_alphabet_composition(SEQ).columns), [f"ReducedAlphabet_{i}" for i in range(1, 7)]) + gaac = protpy.reduced_alphabet_composition("GAFKD", alphabet="ifeature5") + self.assertEqual(list(gaac.columns), ["GAAC_aliphatic", "GAAC_aromatic", "GAAC_positive", "GAAC_negative", "GAAC_uncharged"]) + self.assertEqual(gaac.values[0].tolist(), [40.0, 20.0, 20.0, 20.0, 0.0]) + self.assertEqual(protpy.reduced_alphabet_composition(SEQ, alphabet="ifeature5", k=2).shape, (1, 25)) + gtpc = protpy.reduced_alphabet_composition(SEQ, alphabet="ifeature5", k=3) + self.assertEqual(gtpc.shape, (1, 125)) + self.assertEqual(gtpc.columns[0], "GTPC_aliphatic.aliphatic.aliphatic") + self.assertAlmostEqual(gtpc.values.sum(), 100, delta=0.1) + with self.assertRaises(ValueError): + protpy.reduced_alphabet_composition(SEQ, k=4) + with self.assertRaises(ValueError): + protpy.reduced_alphabet_composition(SEQ, alphabet="nope") + + def test_ksctriad(self): + self.assertEqual(protpy.conjoint_triad(SEQ).shape, (1, 343)) + df = protpy.conjoint_triad("AAAAAAA", gap=1) + self.assertEqual(df.shape, (1, 686)) + #gap 0: 5 contiguous triads; gap 1: (i, i+2, i+4) -> 3 triads + self.assertEqual(df["conj_triad_111_gap0"].iloc[0], 5) + self.assertEqual(df["conj_triad_111_gap1"].iloc[0], 3) + with self.assertRaises(ValueError): + protpy.conjoint_triad(SEQ, gap=-1) + + def test_ctd_ifeature13(self): + for name, prop in ctd_property_sets["ifeature13"].items(): + groups = prop["1"] + prop["2"] + prop["3"] + self.assertEqual(sorted(groups), AA, name) + self.assertEqual(len(ctd_property_sets["ifeature13"]), 13) + self.assertEqual(protpy.ctd_(SEQ).shape, (1, 147)) + self.assertEqual(protpy.ctd_(SEQ, property_set="ifeature13").shape, (1, 273)) + self.assertEqual(protpy.ctd_composition(SEQ, property="hydrophobicity_ENGD860101").shape, (1, 3)) + with self.assertRaises(ValueError): + protpy.ctd_(SEQ, property_set="nope") + + def test_region(self): + seq = "A" * 25 + "C" * 10 + "D" * 25 + df = protpy.amino_acid_composition(seq, region="split") + self.assertEqual(df.shape, (1, 60)) + self.assertEqual((df["NT_A"].iloc[0], df["MID_C"].iloc[0], df["CT_D"].iloc[0]), (100.0, 100.0, 100.0)) + self.assertEqual(protpy.amino_acid_composition(seq, region="nterm", n=5)["NT_A"].iloc[0], 100.0) + self.assertEqual(protpy.dipeptide_composition(seq, region="cterm").shape, (1, 400)) + self.assertEqual(protpy.dipeptide_composition(seq, region="split").columns[400], "MID_AA") + with self.assertRaises(ValueError): + protpy.amino_acid_composition("A" * 50, region="split") + with self.assertRaises(ValueError): + protpy.amino_acid_composition(seq, region="middle") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_conjoint_triad.py b/tests/test_conjoint_triad.py index e9f126e..fb7969c 100644 --- a/tests/test_conjoint_triad.py +++ b/tests/test_conjoint_triad.py @@ -43,7 +43,7 @@ def test_conjoint_triad(self): conjoint_triad_seq1 = protpy.conjoint_triad(self.protein_seq1) #testing first 10 columns conjoint_triad_seq1_expected_values = pd.DataFrame(columns=["conj_triad_111", "conj_triad_112", "conj_triad_113", "conj_triad_114", "conj_triad_115", "conj_triad_116", "conj_triad_117", "conj_triad_121", "conj_triad_122", "conj_triad_123"]) - conjoint_triad_seq1_expected_values.loc[0] = [7, 17, 11, 3, 6, 6, 2, 16, 11, 19] + conjoint_triad_seq1_expected_values.loc[0] = [9, 17, 11, 3, 6, 6, 2, 16, 11, 19] self.assertIsInstance(conjoint_triad_seq1, pd.DataFrame, 'Expected output to be a DataFrame, got {}.'.format(type(conjoint_triad_seq1))) diff --git a/tests/test_data/golden_v1_4_2.json.gz b/tests/test_data/golden_v1_4_2.json.gz new file mode 100644 index 0000000..b8d222b Binary files /dev/null and b/tests/test_data/golden_v1_4_2.json.gz differ diff --git a/tests/test_data/structural_reference.fasta b/tests/test_data/structural_reference.fasta new file mode 100644 index 0000000..03a4b70 --- /dev/null +++ b/tests/test_data/structural_reference.fasta @@ -0,0 +1,6 @@ +>MOT3 S. cerevisiae Mot3 (from the PLAAC repository example cli/example/MOT3.fasta, MIT License) +MNADHHLQQQQQQRQQHQQQQHQQQQHQHQHQQQQHTILQNVSNTNNIGSDSLASQPFNTTTVSSNKDDVMVNSGARELPMPLHQQQYIYPYYQYTSNNSNNNNVTAGNNMSASPIVHNNSNNSNNSNISASDYTVANNSTSNNNNNNNNNNNNNNNIHPNQFTAAANMNSNAAAAAYYSFPTANMPIPQQDQQYMFNPASYISHYYSAVNSNNNGNNAANNGSNNSSHSAPAPAPGPPHHHHHHSNTHNNLNNGGAVNTNNAPQHHPTIITDQFQFQLQQNPSPNLNLNINPAQPLHLPPGWKINTMPQPRPTTAPNHPPAPVPSSNPVASNLVPAPSSDHKYIHQCQFCEKSFKRKSWLKRHLLSHSQQRHFLCPWCLSRQKRKDNLLQHMKLKHTNYLLDELKKNNIIFNYNNSSSSNNNNDNNNNNNSNSASGSGGAGAAAAAATAPENEDGNGYDTNIKTLINDGVLNKDDVKRVLNNLIVSHNK +>GYPA_HUMAN UniProt P02724 glycophorin-A (single-pass transmembrane) +MYGKIIFVLLLSEIVSISALSTTEVAMHTSTSSSVTKSYISSQTNDTHKRDTYAATPRAHEVSEISVRTVYPPEEETGERVQLAHHFSEPEITLIIFGVMAGVIGTILLISYGIRRLIKKSPSDVKPLPSPDTDVPLSSVEIENPETSDQ +>ABETA42 UniProt P05067 residues 672-713 (amyloid-beta 1-42) +DAEFRHDSGYEVHHQKLVFFAEDVGSNKGAIIGLMVGGVVIA diff --git a/tests/test_descriptor_sets.py b/tests/test_descriptor_sets.py new file mode 100644 index 0000000..a0a13d7 --- /dev/null +++ b/tests/test_descriptor_sets.py @@ -0,0 +1,99 @@ +########################################################################### +######### protPy - descriptor_set / include / exclude (Phase 8) ####### +########################################################################### + +import os +import tempfile +import unittest + +import protpy +from protpy.encode import _set_members +from protpy.utils import _DESCRIPTOR_REGISTRY + +SEQ = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDAKLVVSTEQW" + + +class ProtpyDescriptorSetTests(unittest.TestCase): + + def _all(self, seq=SEQ, **kw): + return protpy.all_descriptors(seq, _show_descriptor_list=False, **kw) + + def test_classic_is_default(self): + self.assertTrue(self._all().equals(self._all(descriptor_set="classic"))) + + def test_sets_are_nested_and_unique(self): + classic, extended, full = self._all(), self._all(descriptor_set="extended"), self._all(descriptor_set="all") + self.assertLess(classic.shape[1], extended.shape[1]) + self.assertLess(extended.shape[1], full.shape[1]) + self.assertEqual(list(extended.columns[:classic.shape[1]]), list(classic.columns)) + self.assertTrue(set(extended.columns) <= set(full.columns)) + for df in (classic, extended, full): + self.assertFalse(df.columns.duplicated().any(), df.columns[df.columns.duplicated()].tolist()) + + def test_set_membership_from_registry(self): + extended = _set_members("extended") + self.assertIn("kappa", extended) + self.assertIn("plaac", extended) + for name in ("cksaap", "dde", "asdc", "distance_pair", "auto_cross_covariance"): + self.assertNotIn(name, extended) + self.assertIn(name, _set_members("all")) + self.assertEqual(_set_members("classic"), []) + #extended descriptors have at most 150 columns by default + for name in extended: + meta = _DESCRIPTOR_REGISTRY[name] + func = getattr(protpy, name) + self.assertLessEqual(func(SEQ).shape[1], 150, name) + + def test_include_exclude_params(self): + df = self._all(include=["kappa"], exclude=["tripeptide_composition", "ctd_"], + params={"kappa": {"blob_sizes": (5,)}}) + self.assertIn("Kappa_5", df.columns) + self.assertNotIn("Kappa_6", df.columns) + self.assertNotIn("AAA", df.columns) + self.assertFalse(any(c.startswith("CTD_") for c in df.columns)) + #including a member of the set again does not duplicate it + self.assertEqual(self._all(descriptor_set="extended", include=["kappa"]).shape, + self._all(descriptor_set="extended").shape) + with self.assertRaises(ValueError): + self._all(descriptor_set="everything") + with self.assertRaises(ValueError): + self._all(include=["not_a_descriptor"]) + with self.assertRaises(ValueError): + self._all(params={"cysteine_features": {"x": 1}}) + + def test_short_sequence_extended(self): + short = SEQ[:31] + df = self._all(short, descriptor_set="extended") + self.assertEqual(df.shape[0], 1) + #only documented NaNs: PLAAC LLR/COREscore for sequences shorter than the core length + self.assertEqual(sorted(df.columns[df.isnull().any()]), ["PLAAC_COREscore", "PLAAC_LLR"]) + + def test_new_descriptors_on_very_short_sequences(self): + #every v1.5 descriptor either works or raises its documented ValueError on tiny inputs + documented = {"dde": "at least 2 residues", "auto_cross_covariance": "lag", + "physicochemical_distance_transformation": "lag", "dct_descriptors": "n_coeffs"} + for name, meta in _DESCRIPTOR_REGISTRY.items(): + if meta["in_default_set"]: + continue + for seq in ("A", "AC", "MKTAYIAK"): + with self.subTest(name=name, seq=seq): + try: + df = getattr(protpy, name)(seq) + except ValueError as e: + self.assertIn(name, documented) + self.assertIn(documented[name], str(e)) + continue + self.assertEqual(df.shape[0], 1) + + def test_encode_dataset_passthrough(self): + with tempfile.TemporaryDirectory() as tmp: + out = os.path.join(tmp, "enc.csv") + df = protpy.encode_dataset([SEQ, SEQ[::-1]], output_file=out, verbose=False, + descriptor_set="extended", exclude=["tripeptide_composition"]) + self.assertEqual(df.shape[0], 2) + self.assertIn("Kappa", df.columns) + self.assertNotIn("AAA", df.columns) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 0000000..8111868 --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,116 @@ +########################################################################### +############### protPy - Internal Helpers Tests ############### +########################################################################### + +import os +import re +import unittest + +import numpy as np + +import protpy +from protpy import _helpers as h +from protpy.utils import _DESCRIPTOR_REGISTRY + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +class ProtpyHelpersTests(unittest.TestCase): + + def test_load_json_table(self): + t = h._load_json_table("reduced_alphabets.json", "ifeature5") + self.assertEqual(set(t), {"name", "values", "reference", "doi", "source_note"}) + t["values"]["aromatic"] = "X" #a copy: the cache must be unaffected + self.assertEqual(h._load_json_table("reduced_alphabets.json", "ifeature5")["values"]["aromatic"], "FYW") + with self.assertRaisesRegex(KeyError, "Valid tables"): + h._load_json_table("reduced_alphabets.json", "nope") + + def test_every_data_table_has_provenance(self): + data = os.path.join(ROOT, "protpy", "data") + for fname in ("aa_scales.json", "reduced_alphabets.json", "residue_tables.json", "structural_params.json"): + if not os.path.exists(os.path.join(data, fname)): + continue + for key, table in h._read_json(fname).items(): + with self.subTest(table=f"{fname}:{key}"): + self.assertTrue(table["reference"]) + self.assertTrue(table["source_note"]) + self.assertIn("doi", table) + + def test_property_matrix(self): + m = h._property_matrix("ACA", "KYTJ820101") + np.testing.assert_allclose(m[:, 0], [1.8, 2.5, 1.8]) + m = h._property_matrix("AC", ["KYTJ820101", "HOPT810101"]) + self.assertEqual(m.shape, (2, 2)) + m = h._property_matrix("AAA", {aa: [1.0, float(i)] for i, aa in enumerate(protpy.amino_acids)}, standardize=True) + np.testing.assert_allclose(m[:, 0], 0) #constant column -> 0, no division by zero + self.assertAlmostEqual(m[0, 1], (0 - 9.5) / np.std(np.arange(20))) + with self.assertRaises(ValueError): + h._property_matrix("A", "NOT_A_SCALE") + with self.assertRaises(ValueError): + h._property_matrix("A", {"A": 1.0}) + + def test_sliding_windows(self): + np.testing.assert_allclose(h._sliding_windows([1, 2, 3, 4, 5], 3), [2, 3, 4]) + #window longer than profile -> one window + np.testing.assert_allclose(h._sliding_windows([1, 2, 3], 9), [2]) + #edge weighting: weights 0.5,1,0.5 for window 3 + np.testing.assert_allclose(h._sliding_windows([0, 4, 0], 3, edge=0.5), [2.0]) + #window 5, edge 0.2 -> weights 0.2,0.6,1,0.6,0.2 + w = np.array([0.2, 0.6, 1, 0.6, 0.2]) + x = np.array([1., 2, 3, 4, 7]) + np.testing.assert_allclose(h._sliding_windows(x, 5, edge=0.2), [(w * x).sum() / w.sum()]) + + def test_profile_stats(self): + out = h._profile_stats(np.array([1., 2, 3]), "P", threshold=1.5) + self.assertEqual(out, {"P_mean": 2.0, "P_max": 3.0, "P_min": 1.0, "P_std": np.std([1, 2, 3]), + "P_frac_above": 2 / 3}) + + def test_counts(self): + self.assertEqual(h._gapped_pair_counts("AAAA", 1)[("A", "A")], 2) + self.assertEqual(h._gapped_pair_counts("ACDE", 0, {"A": "x", "C": "x", "D": "y", "E": "y"}), + {("x", "x"): 1, ("x", "y"): 1, ("y", "y"): 1}) + self.assertEqual(h._kmer_counts("AAAA", 3)[("A", "A", "A")], 2) + self.assertEqual(sum(h._kmer_counts("A", 2).values()), 0) + + def test_charge_and_runs(self): + np.testing.assert_array_equal(h._charge_vector("KRDEHA"), [1, 1, -1, -1, 0, 0]) + np.testing.assert_array_equal(h._charge_vector("H", his=True), [1]) + self.assertEqual(h._runs([1, 1, 0, 1, 0, 1, 1, 1]), [(0, 2), (3, 4), (5, 8)]) + self.assertEqual(h._runs([1, 1, 0, 1, 0, 1, 1, 1], min_len=2), [(0, 2), (5, 8)]) + self.assertEqual(h._runs([0, 0]), []) + + def test_to_df(self): + df = h._to_df([1.23456, np.float64(2.0004), np.int64(3), True], ["a", "b", "c", "d"]) + self.assertEqual(df.shape, (1, 4)) + self.assertEqual(df.iloc[0].tolist(), [1.235, 2.0, 3, 1]) + + +class ProtpyRegistryTests(unittest.TestCase): + + def test_every_descriptor_registered_with_reference(self): + for name in protpy.descriptor_names: + with self.subTest(name=name): + self.assertIn(name, _DESCRIPTOR_REGISTRY) + meta = _DESCRIPTOR_REGISTRY[name] + self.assertTrue(meta["reference"]) + self.assertIn("doi", meta) + self.assertIn("in_default_set", meta) + + def test_registry_parameters_match_signatures(self): + import inspect + for name, meta in _DESCRIPTOR_REGISTRY.items(): + with self.subTest(name=name): + self.assertEqual(list(inspect.signature(getattr(protpy, name)).parameters), list(meta["parameters"])) + + def test_registry_dois_in_bib(self): + with open(os.path.join(ROOT, "docs", "references.bib")) as f: + bib = {d.lower() for d in re.findall(r"doi\s*=\s*\{([^}]*)\}", f.read(), flags=re.I)} + for name, meta in _DESCRIPTOR_REGISTRY.items(): + if meta["doi"] is None: + continue #textbooks / sources without a DOI + with self.subTest(name=name): + self.assertTrue(meta["doi"].lower() in bib, meta["doi"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_performance.py b/tests/test_performance.py new file mode 100644 index 0000000..422ba65 --- /dev/null +++ b/tests/test_performance.py @@ -0,0 +1,25 @@ +########################################################################### +############### protPy - Performance Test (Phase 8) ########### +########################################################################### + +import os +import random +import time +import unittest + +import protpy + + +@unittest.skipUnless(os.environ.get("PROTPY_PERF"), "set PROTPY_PERF=1 to run the performance test") +class ProtpyPerformanceTests(unittest.TestCase): + + def test_all_descriptors_500_residues(self): + random.seed(0) + seq = "".join(random.choice(protpy.amino_acids) for _ in range(500)) + start = time.perf_counter() + protpy.all_descriptors(seq, descriptor_set="all", _show_descriptor_list=False) + self.assertLess(time.perf_counter() - start, 2.0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_physicochemical.py b/tests/test_physicochemical.py new file mode 100644 index 0000000..d6946d2 --- /dev/null +++ b/tests/test_physicochemical.py @@ -0,0 +1,182 @@ +########################################################################### +########### protPy - Physicochemical Module Tests (Phases 3-4) ######## +########################################################################### + +import itertools +import math +import unittest + +import numpy as np + +import protpy + +SEQ = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGD" + +#(Glu-Lys)25 sequence variants and kappa values from Das & Pappu 2013, Fig. 1 +#(the regularly patterned variants, each 25 E + 25 K) +DAS_PAPPU_SV = { + "sv1": ("EK" * 25, 0.0009), + "sv2": ("EEEKKK" * 8 + "EK", 0.0025), + "sv7": ("EEEEKKKK" * 6 + "EK", 0.0450), + "sv8": ("KKKKEEEE" * 6 + "KE", 0.0450), + "sv19": ("EEEEEKKKKK" * 5, 0.1941), + "sv30": ("E" * 25 + "K" * 25, 1.0000), +} + + +class ProtpyChargeTests(unittest.TestCase): + + def _check_invalid(self, func): + with self.assertRaises(TypeError): + func(5) + with self.assertRaises(ValueError): + func("") + with self.assertRaises(ValueError): + func("AXB") + + def test_charge_fractions(self): + df = protpy.charge_fractions("KKDD") + self.assertEqual(list(df.columns), ["Charge_FCR", "Charge_FPlus", "Charge_FMinus", "Charge_Sigma"]) + self.assertEqual(df.values[0].tolist(), [1.0, 0.5, 0.5, 0.0]) + #H is neutral; sigma = NCPR^2 / FCR + df = protpy.charge_fractions("KKKDAAHH") + self.assertEqual(df["Charge_FCR"].iloc[0], 0.5) + self.assertEqual(df["Charge_Sigma"].iloc[0], round(0.25 ** 2 / 0.5, 3)) + self.assertEqual(protpy.charge_fractions("AAAA")["Charge_Sigma"].iloc[0], 0.0) + self._check_invalid(protpy.charge_fractions) + + def test_ncpr(self): + self.assertEqual(protpy.ncpr("KKKA")["NCPR"].iloc[0], 0.75) + self.assertEqual(protpy.ncpr("KRDE")["NCPR"].iloc[0], 0.0) + self._check_invalid(protpy.ncpr) + + def test_kappa_published_values(self): + for name, (seq, ref) in DAS_PAPPU_SV.items(): + with self.subTest(sv=name): + self.assertEqual((len(seq), seq.count("E"), seq.count("K")), (50, 25, 25)) + self.assertAlmostEqual(protpy.kappa(seq)["Kappa"].iloc[0], ref, delta=0.02) + + def test_kappa(self): + df = protpy.kappa(SEQ) + self.assertEqual(list(df.columns), ["Kappa", "Kappa_5", "Kappa_6"]) + self.assertTrue(0 <= df["Kappa"].iloc[0] <= 1) + #documented edge cases: no charge / one charge type -> 0 + self.assertEqual(protpy.kappa("AAAAAAA")["Kappa"].iloc[0], 0.0) + self.assertEqual(protpy.kappa("KKAAKK")["Kappa"].iloc[0], 0.0) + #short sequence: blob reduced to the length, no crash + self.assertFalse(protpy.kappa("KD").isnull().any().any()) + self.assertEqual(protpy.kappa(SEQ, blob_sizes=(5,)).shape, (1, 2)) + with self.assertRaises(ValueError): + protpy.kappa(SEQ, blob_sizes=()) + self._check_invalid(protpy.kappa) + + def test_scd(self): + self.assertEqual(protpy.sequence_charge_decoration("AAAA")["Charge_SCD"].iloc[0], 0.0) + #brute force + q = [1 if aa in "KR" else -1 if aa in "DE" else 0 for aa in SEQ] + ref = sum(q[m] * q[n] * math.sqrt(m - n) for m in range(len(q)) for n in range(m)) / len(q) + self.assertAlmostEqual(protpy.sequence_charge_decoration(SEQ)["Charge_SCD"].iloc[0], round(ref, 3)) + #segregated charges are more negative than mixed + self.assertLess(protpy.sequence_charge_decoration("E" * 25 + "K" * 25)["Charge_SCD"].iloc[0], + protpy.sequence_charge_decoration("EK" * 25)["Charge_SCD"].iloc[0]) + self._check_invalid(protpy.sequence_charge_decoration) + + def test_charge_density(self): + df = protpy.charge_density(SEQ, ph=7.0) + net = protpy.charge_distribution(SEQ, ph=7.0)["NetCharge"].iloc[0] + mw = protpy.molecular_weight(SEQ)["MolecularWeight"].iloc[0] + self.assertAlmostEqual(df["ChargeDensity"].iloc[0], net / mw, places=6) + self._check_invalid(protpy.charge_density) + + def test_pka_scale(self): + #defaults unchanged + self.assertEqual(protpy.isoelectric_point(SEQ).iloc[0, 0], protpy.isoelectric_point(SEQ, pka_scale="protpy").iloc[0, 0]) + values = {s: protpy.isoelectric_point(SEQ, pka_scale=s).iloc[0, 0] for s in ("emboss", "bjellqvist", "ipc_protein")} + self.assertEqual(len(set(values.values())), 3) + for s in ("emboss", "bjellqvist", "ipc_protein"): + self.assertEqual(protpy.charge_distribution(SEQ, pka_scale=s).shape, (1, 3)) + with self.assertRaises(ValueError): + protpy.isoelectric_point(SEQ, pka_scale="nope") + with self.assertRaises(ValueError): + protpy.charge_distribution(SEQ, pka_scale="nope") + + def test_bjellqvist_matches_biopython(self): + from Bio.SeqUtils.IsoelectricPoint import IsoelectricPoint + for seq in (SEQ, "ACDEFGHIKLMNPQRSTVWY", "PEPTIDEKRD", "MAAAKE"): + with self.subTest(seq=seq): + self.assertAlmostEqual(protpy.isoelectric_point(seq, pka_scale="bjellqvist").iloc[0, 0], + IsoelectricPoint(seq).pi(), delta=0.01) + + +class ProtpyHydrophobicityStabilityTests(unittest.TestCase): + + def test_hydrophobicity_profile(self): + df = protpy.hydrophobicity_profile(SEQ) + self.assertEqual(list(df.columns), [f"HP_kyte_doolittle_w9_{s}" for s in ("mean", "max", "min", "std")]) + self.assertEqual(protpy.hydrophobicity_profile(SEQ, threshold=1.6).shape, (1, 5)) + self.assertEqual(protpy.hydrophobicity_profile(SEQ, scale="HOPT810101").shape, (1, 4)) + #window longer than sequence -> one window = GRAVY + self.assertEqual(protpy.hydrophobicity_profile("ACD", window=9)["HP_kyte_doolittle_w9_mean"].iloc[0], + protpy.gravy("ACD")["GRAVY"].iloc[0]) + with self.assertRaises(ValueError): + protpy.hydrophobicity_profile(SEQ, edge=2) + with self.assertRaises(ValueError): + protpy.hydrophobicity_profile(SEQ, scale="vhse") + with self.assertRaises(ValueError): + protpy.hydrophobicity_profile(SEQ, window=0) + + def test_hydrophobicity_profile_matches_biopython(self): + from Bio.SeqUtils.ProtParam import ProteinAnalysis + from Bio.SeqUtils.ProtParamData import kd + for edge in (1.0, 0.4): + ref = ProteinAnalysis(SEQ).protein_scale(kd, 9, edge) + df = protpy.hydrophobicity_profile(SEQ, window=9, edge=edge) + self.assertAlmostEqual(df["HP_kyte_doolittle_w9_mean"].iloc[0], round(np.mean(ref), 3), places=3) + self.assertAlmostEqual(df["HP_kyte_doolittle_w9_max"].iloc[0], round(max(ref), 3), places=3) + + def test_hydrophobic_ratio(self): + #APD example: TP10 is 61% hydrophobic (13 of 21 residues) + self.assertEqual(protpy.hydrophobic_ratio("AGYLLGKINLKALAALAKKIL")["HydrophobicRatio"].iloc[0], round(13 / 21 * 100, 3)) + self.assertEqual(protpy.hydrophobic_ratio("GGKK")["HydrophobicRatio"].iloc[0], 0.0) + + def test_half_life(self): + df = protpy.half_life("MKT") + self.assertEqual(df.values[0].tolist(), [30.0, 20.0, 10.0]) + self.assertEqual(protpy.half_life("RAA").values[0].tolist(), [1.0, round(2 / 60, 3), round(2 / 60, 3)]) + self.assertTrue(np.isnan(protpy.half_life("PAA")["HalfLife_Ecoli_h"].iloc[0])) + + def test_ivywrel_cvp(self): + self.assertEqual(protpy.ivywrel_index("IVYWRELA")["IVYWREL"].iloc[0], round(7 / 8, 3)) + self.assertEqual(protpy.cvp_bias("DEKRNQST")["CvP_Bias"].iloc[0], 0.0) + self.assertEqual(protpy.cvp_bias("DDAA")["CvP_Bias"].iloc[0], 50.0) + + def test_flexibility(self): + df = protpy.flexibility(SEQ) + self.assertEqual(list(df.columns), ["Flex_mean", "Flex_max", "Flex_min", "Flex_std"]) + #Biopython uses the same table and weights (but an off-by-one centre and drops the last window) + from Bio.SeqUtils.ProtParam import ProteinAnalysis + ref = ProteinAnalysis(SEQ).flexibility() + self.assertAlmostEqual(df["Flex_mean"].iloc[0], np.mean(ref), delta=0.01) + + def test_extensions(self): + self.assertEqual(list(protpy.gravy(SEQ).columns), ["GRAVY"]) + hw = protpy.gravy("AD", scale="hopp_woods") + self.assertEqual(list(hw.columns), ["GRAVY_hopp_woods"]) + self.assertEqual(hw.iloc[0, 0], (-0.5 + 3.0) / 2) + self.assertEqual(protpy.hydrophobic_moment(SEQ, angle="helix").values.tolist(), + protpy.hydrophobic_moment(SEQ).values.tolist()) + self.assertEqual(protpy.hydrophobic_moment(SEQ, angle="sheet").values.tolist(), + protpy.hydrophobic_moment(SEQ, angle=160).values.tolist()) + self.assertNotEqual(protpy.hydrophobic_moment(SEQ, scale="eisenberg").values.tolist(), + protpy.hydrophobic_moment(SEQ).values.tolist()) + with self.assertRaises(ValueError): + protpy.hydrophobic_moment(SEQ, angle="coil") + ext = protpy.extinction_coefficient(SEQ + "W", absorbance=True) + self.assertEqual(ext.shape, (1, 4)) + mw = protpy.molecular_weight(SEQ + "W").iloc[0, 0] + self.assertAlmostEqual(ext["Abs01_Reduced"].iloc[0], round(ext["ExtCoeff_Reduced"].iloc[0] / mw, 3)) + self.assertEqual(protpy.extinction_coefficient(SEQ).shape, (1, 2)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_protpy.py b/tests/test_protpy.py index a2862ae..bd99ad5 100644 --- a/tests/test_protpy.py +++ b/tests/test_protpy.py @@ -22,8 +22,8 @@ class ProtpyTests(unittest.TestCase): """ def test_protpy_metadata(self): """ Testing correct protpy version and metadata. """ - self.assertEqual(protpy.__version__, "1.4.2", - "protpy version is not correct, expected version 1.4.2, got version {}".format(protpy.__version__)) + self.assertEqual(protpy.__version__, "1.5.0", + "protpy version is not correct, expected version 1.5.0, got version {}".format(protpy.__version__)) self.assertEqual(protpy.__name__, "protpy", "protpy software name is not correct, expected protpy, got {}.".format(protpy.__name__)) self.assertEqual(protpy.__author__, "AJ McKenna: https://github.com/amckenna41", @@ -47,8 +47,13 @@ def test_protpy_metadata(self): def test_valid_descriptors(self): """ Testing correct list of available descriptors is included in package attribute. """ - self.assertEqual(len(protpy.descriptor_names), 36, - "Expected there to be 36 total descriptors, got {}.".format(len(protpy.descriptor_names))) + from protpy.utils import _DESCRIPTOR_REGISTRY + #every exported descriptor is registered (and vice versa) and callable + self.assertEqual(sorted(protpy.descriptor_names), sorted(_DESCRIPTOR_REGISTRY), + "descriptor_names and the descriptor registry should list the same descriptors.") + self.assertEqual(len(set(protpy.descriptor_names)), len(protpy.descriptor_names)) + for name in protpy.descriptor_names: + self.assertTrue(callable(getattr(protpy, name)), name) self.assertIsInstance(protpy.descriptor_names, list, "all_descriptors should be of type list, got {}.".format(type(protpy.descriptor_names))) # composition descriptors diff --git a/tests/test_regressions.py b/tests/test_regressions.py new file mode 100644 index 0000000..44ce071 --- /dev/null +++ b/tests/test_regressions.py @@ -0,0 +1,84 @@ +"""Regression tests for bugs fixed in the post-1.4.2 audit.""" +import os +import tempfile +import unittest + +import pandas as pd +from aaindex import aaindex1 + +import protpy +from protpy.ctd import ctd_properties + +SEQ = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGD" + + +class ProtpyRegressionTests(unittest.TestCase): + + def test_overlapping_counts(self): + self.assertEqual(protpy.dipeptide_composition("AAA")["AA"].iloc[0], 100.0) + self.assertEqual(protpy.tripeptide_composition("AAAA")["AAA"].iloc[0], 100.0) + self.assertEqual(protpy.conjoint_triad("AAAAA")["conj_triad_111"].iloc[0], 3) + + def test_ctd_groups_partition_all_amino_acids(self): + for name, prop in ctd_properties.items(): + groups = prop["1"] + prop["2"] + prop["3"] + self.assertEqual(sorted(groups), sorted(protpy.amino_acids), name) + + def test_moran_matches_reference_formula(self): + vals = {aa: v for aa, v in aaindex1["CIDH920105"].values.items() if aa in protpy.amino_acids} + mean = sum(vals.values()) / 20 + std = (sum((v - mean) ** 2 for v in vals.values()) / 20) ** 0.5 + p = [(vals[aa] - mean) / std for aa in SEQ] + n, pbar = len(p), sum(p) / len(p) + var = sum((x - pbar) ** 2 for x in p) / n + ref = sum((p[i] - pbar) * (p[i + 1] - pbar) for i in range(n - 1)) / (n - 1) / var + got = protpy.moran_autocorrelation(SEQ, lag=1, properties=["CIDH920105"]).iloc[0, 0] + self.assertAlmostEqual(got, ref, places=4) + + def test_short_and_repetitive_sequences_do_not_crash(self): + funcs = [protpy.dipeptide_composition, protpy.ctd_transition, protpy.moreaubroto_autocorrelation, + protpy.moran_autocorrelation, protpy.geary_autocorrelation, + protpy.pseudo_amino_acid_composition, protpy.amphiphilic_pseudo_amino_acid_composition, + protpy.quasi_sequence_order] + for seq in ["A", "AAAAAAAAAA", "ACDEFGHIKL"]: + for func in funcs: + df = func(seq) + self.assertFalse(df.isnull().any().any(), f"{func.__name__}({seq})") + + def test_invalid_params_raise(self): + with self.assertRaises(ValueError): + protpy.moran_autocorrelation(SEQ, lag=-1) + with self.assertRaises(ValueError): + protpy.quasi_sequence_order(SEQ, weight="x") + with self.assertRaises(ValueError): + protpy.kmer_composition(SEQ, k=0) + with self.assertRaises(ValueError): + protpy.aggregation_propensity(SEQ, window=0) + + def test_misc_descriptor_fixes(self): + self.assertEqual(protpy.motif_composition("DAAALLK")["dileucine_sorting"].iloc[0], 1) + self.assertGreater(protpy.isoelectric_point("RRRRRRRRRR").iloc[0, 0], 12.0) + self.assertEqual(protpy.molecular_weight("G").iloc[0, 0], 75.067) + self.assertEqual(protpy.hydrophobic_moment(SEQ).shape, (1, 2)) + + def test_ctd_all_ctd_param(self): + df = protpy.all_descriptors(SEQ, params={"ctd_": {"all_ctd": False}}, _show_descriptor_list=False) + self.assertEqual(df.filter(like="CTD_").shape[1], 21) + + def test_encode_dataset_file_safety(self): + with tempfile.TemporaryDirectory() as tmp: + path = os.path.join(tmp, "out.csv") + pd.DataFrame({"x": [1]}).to_csv(path, index=False) + with self.assertRaises(ValueError): + protpy.encode_dataset([SEQ], output_file=path, verbose=False) + self.assertEqual(list(pd.read_csv(path).columns), ["x"]) # untouched + + path = os.path.join(tmp, "enc.csv") + protpy.encode_dataset([SEQ, SEQ], output_file=path, verbose=False) + protpy.encode_dataset([SEQ], output_file=path, verbose=False) # re-run seq1 + ids = pd.read_csv(path)["sequence_id"].tolist() + self.assertEqual(sorted(ids), ["seq1", "seq2"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_scales.py b/tests/test_scales.py new file mode 100644 index 0000000..89825cc --- /dev/null +++ b/tests/test_scales.py @@ -0,0 +1,103 @@ +########################################################################### +############### protPy - Scales Module Tests (Phase 2) ############# +########################################################################### + +import unittest + +import numpy as np + +import protpy +from protpy._helpers import _read_json +from protpy.scales import factor_scales_ + +SEQ = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGD" +DIMS = {"z_scales_5": 5, "vhse": 8, "atchley_factors": 5, "t_scales": 5, "st_scales": 8, + "protfp": 8, "fasgai": 6, "blosum_indices": 10, "ms_whim": 3} + + +class ProtpyScalesTests(unittest.TestCase): + + def test_tables(self): + tables = _read_json("aa_scales.json") + for name, d in DIMS.items(): + values = tables[name]["values"] + self.assertEqual(sorted(values), protpy.amino_acids, name) + self.assertTrue(all(len(v) == d for v in values.values()), name) + + def test_wrappers(self): + tables = _read_json("aa_scales.json") + for name, d in DIMS.items(): + func = getattr(protpy, name) + df = func(SEQ) + abbr = factor_scales_[name] + self.assertEqual(df.shape, (1, d), name) + self.assertEqual(list(df.columns), [f"{abbr}_{j}" for j in range(1, d + 1)]) + self.assertTrue(all(dt == np.float64 for dt in df.dtypes)) + #poly-A gives A's row + np.testing.assert_allclose(func("AAAAA").values[0], np.round(tables[name]["values"]["A"], 3)) + #domains + df3 = func(SEQ[:30], domains=3) + self.assertEqual(df3.shape, (1, 3 * d)) + self.assertEqual(df3.columns[d], f"{abbr}_1_d2") + #mean of the 3 equal domains is the whole-sequence mean + np.testing.assert_allclose(df3.values[0].reshape(3, d).mean(axis=0), func(SEQ[:30]).values[0], atol=2e-3) + + def test_invalid(self): + for func in (protpy.vhse, protpy.auto_cross_covariance, protpy.physicochemical_distance_transformation): + with self.assertRaises(TypeError): + func(1) + with self.assertRaises(ValueError): + func("") + with self.assertRaises(ValueError): + func("AXC") + with self.assertRaises(ValueError): + protpy.vhse("ACD", domains=4) + with self.assertRaises(ValueError): + protpy.vhse("ACD", domains=0) + with self.assertRaises(ValueError): + protpy.aa_scale_descriptors(SEQ, "kidera") + + def test_auto_cross_covariance(self): + df = protpy.auto_cross_covariance(SEQ) + self.assertEqual(df.shape, (1, 25 * 5)) + self.assertEqual(protpy.auto_cross_covariance(SEQ, mode="ac").shape, (1, 25)) + self.assertEqual(protpy.auto_cross_covariance(SEQ, mode="cc").shape, (1, 100)) + #brute force + z = _read_json("aa_scales.json")["z_scales_5"]["values"] + L, lag, j, k = len(SEQ), 3, 1, 4 + ref = sum(z[SEQ[i]][j] * z[SEQ[i + lag]][k] for i in range(L - lag)) / (L - lag) + self.assertAlmostEqual(df[f"ACC_{j + 1}_{k + 1}_lag{lag}"].iloc[0], round(ref, 3)) + #AAindex codes are accepted and standardised + self.assertEqual(protpy.auto_cross_covariance(SEQ, scale=["KYTJ820101", "HOPT810101"], lag=2).shape, (1, 8)) + with self.assertRaises(ValueError): + protpy.auto_cross_covariance("ACD", lag=3) + with self.assertRaises(ValueError): + protpy.auto_cross_covariance(SEQ, mode="x") + + def test_pdt(self): + df = protpy.physicochemical_distance_transformation(SEQ) + self.assertEqual(df.shape, (1, 24)) + self.assertEqual(df.columns[0], "PDT_CIDH920105_1") + self.assertTrue((protpy.physicochemical_distance_transformation("A" * 10).values == 0).all()) + self.assertTrue((df.values >= 0).all()) + with self.assertRaises(ValueError): + protpy.physicochemical_distance_transformation("AC", lag=2) + with self.assertRaises(ValueError): + protpy.physicochemical_distance_transformation(SEQ, properties=[]) + + def test_cross_check_peptides(self): + """Optional cross-check against peptides.py (GPL; test-only, never imported by protpy).""" + try: + import peptides.tables as T + except ImportError: + self.skipTest("peptides not installed") + tables = _read_json("aa_scales.json") + for name, ref in (("ms_whim", T.MSWHIM), ("t_scales", T.T_SCALES), ("protfp", T.PROTFP)): + keys = sorted(ref, key=lambda k: int("".join(c for c in k if c.isdigit()))) + for i, key in enumerate(keys): + for aa, row in tables[name]["values"].items(): + self.assertAlmostEqual(row[i], ref[key][aa], places=6, msg=f"{name} {key} {aa}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_signal.py b/tests/test_signal.py new file mode 100644 index 0000000..de0b9e8 --- /dev/null +++ b/tests/test_signal.py @@ -0,0 +1,67 @@ +########################################################################### +############### protPy - Signal Module Tests (Phase 6) ########### +########################################################################### + +import math +import unittest + +import numpy as np + +import protpy + +SEQ = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGD" +#a two-valued scale turns A/C patterns into square waves +WAVE = {aa: (1.0 if aa == "A" else -1.0) for aa in protpy.amino_acids} + + +class ProtpySignalTests(unittest.TestCase): + + def test_fft(self): + df = protpy.fft_descriptors(SEQ) + self.assertEqual(list(df.columns), [f"FFT_eiip_bin{i}" for i in range(1, 11)]) + self.assertTrue((df.values >= 0).all()) + #period-2 signal: all energy at f = 0.5 (last bin) + df = protpy.fft_descriptors("AC" * 32, scale=WAVE) + self.assertEqual(list(df.columns)[0], "FFT_custom_bin1") + self.assertGreater(df["FFT_custom_bin10"].iloc[0], 0) + self.assertEqual(df.iloc[0, :9].sum(), 0) + #period-8 square wave: fundamental at f = 0.125 -> bin 3 holds the most power + df = protpy.fft_descriptors("AAAACCCC" * 8, scale=WAVE) + self.assertEqual(int(np.argmax(df.values[0])), 2) + #constant signal has no power + self.assertEqual(protpy.fft_descriptors("A" * 20).values.sum(), 0) + self.assertEqual(protpy.fft_descriptors(SEQ, n_bins=4).shape, (1, 4)) + with self.assertRaises(ValueError): + protpy.fft_descriptors(SEQ, n_bins=0) + with self.assertRaises(ValueError): + protpy.fft_descriptors(SEQ, scale="vhse") + + def test_dct(self): + df = protpy.dct_descriptors(SEQ) + self.assertEqual(list(df.columns), [f"DCT_kyte_doolittle_{i}" for i in range(10)]) + #explicit O(L^2) DCT-II + kd = protpy.composition.kyte_doolittle_ + x = np.array([kd[aa] for aa in SEQ]); x = x - x.mean() + L = len(x) + for k in range(10): + ref = sum(x[n] * math.cos(math.pi * k * (2 * n + 1) / (2 * L)) for n in range(L)) + ref *= math.sqrt((1 if k == 0 else 2) / L) + self.assertAlmostEqual(df.iloc[0, k], round(ref, 3), places=3) + #orthonormal: all L coefficients preserve the signal energy + full = protpy.dct_descriptors(SEQ, n_coeffs=L) + self.assertAlmostEqual((full.values ** 2).sum(), (x ** 2).sum(), delta=0.05) + with self.assertRaises(ValueError): + protpy.dct_descriptors("ACD", n_coeffs=4) + + def test_invalid(self): + for func in (protpy.fft_descriptors, protpy.dct_descriptors): + with self.assertRaises(TypeError): + func(3.0) + with self.assertRaises(ValueError): + func("") + with self.assertRaises(ValueError): + func("AZ") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_structural.py b/tests/test_structural.py new file mode 100644 index 0000000..9a65838 --- /dev/null +++ b/tests/test_structural.py @@ -0,0 +1,118 @@ +########################################################################### +############### protPy - Structural Module Tests (Phase 7) ########### +########################################################################### + +import math +import os +import unittest + +import numpy as np +from Bio import SeqIO + +import protpy + +SEQ = "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGD" +with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_data", "structural_reference.fasta")) as _f: + REF = {r.id: str(r.seq) for r in SeqIO.parse(_f, "fasta")} + + +class ProtpyStructuralTests(unittest.TestCase): + + def test_invalid(self): + for name in ("aggrescan", "uversky_charge_hydropathy", "fold_index", "disorder_propensity", + "transmembrane_features", "signal_peptide_features", "functional_site_counts", "plaac", + "antigenicity_surface", "cysteine_features"): + func = getattr(protpy, name) + with self.subTest(func=name): + with self.assertRaises(TypeError): + func(["A"]) + with self.assertRaises(ValueError): + func("") + with self.assertRaises(ValueError): + func("ABX") + + def test_aggrescan(self): + df = protpy.aggrescan(REF["ABETA42"]) + self.assertEqual(df.shape, (1, 12)) + self.assertTrue(all(c.startswith("AGG_") for c in df.columns)) + #Abeta42 has two hot spots: the central hydrophobic cluster and the C-terminus [Table 1 of the paper] + self.assertEqual(df["AGG_nHS"].iloc[0], 2) + self.assertAlmostEqual(df["AGG_NnHS"].iloc[0], round(2 / 42 * 100, 3)) + #poly-I is all hot spot; poly-D none; proline breaks hot spots + self.assertEqual(protpy.aggrescan("I" * 12)["AGG_nHS"].iloc[0], 1) + self.assertEqual(protpy.aggrescan("D" * 12)["AGG_nHS"].iloc[0], 0) + self.assertEqual(protpy.aggrescan("IIIIIPIIIII")["AGG_nHS"].iloc[0], 2) + self.assertEqual(protpy.aggrescan("IIIIPIIII")["AGG_nHS"].iloc[0], 0) + #a3vSA is the mean propensity: I = 1.822 + self.assertEqual(protpy.aggrescan("I" * 6)["AGG_a3vSA"].iloc[0], 1.822) + self.assertFalse(protpy.aggrescan("ACD").isnull().any().any()) + with self.assertRaises(ValueError): + protpy.aggrescan(SEQ, window=6) + + def test_uversky_fold_index(self): + df = protpy.uversky_charge_hydropathy("KKKKKEEEEE") + h = sum((-3.9 + 4.5) / 9 for _ in range(5)) / 10 + sum((-3.5 + 4.5) / 9 for _ in range(5)) / 10 + self.assertEqual(df["UV_MeanH"].iloc[0], round(h, 3)) + self.assertEqual(df["UV_MeanR"].iloc[0], 0.0) + self.assertEqual(df["UV_Unfolded"].iloc[0], 1) + self.assertEqual(protpy.uversky_charge_hydropathy("LIVFLIVF")["UV_Unfolded"].iloc[0], 0) + #window >= L: FoldIndex equals the Uversky score + fi = protpy.fold_index(SEQ, window=100) + self.assertEqual(fi["FI_mean"].iloc[0], protpy.uversky_charge_hydropathy(SEQ)["UV_HQ"].iloc[0]) + df = protpy.fold_index("LIVF" * 20 + "EKSPEKSQEDKPS" * 6, window=21) + self.assertTrue(0 < df["FI_frac_disordered"].iloc[0] < 1) + self.assertGreater(df["FI_longest_disordered"].iloc[0], 21) + + def test_disorder_propensity(self): + df = protpy.disorder_propensity("WP") + self.assertEqual(df.values[0].tolist(), [0.5, 0.5, round((-0.884 + 0.987) / 2, 3)]) + + def test_transmembrane(self): + df = protpy.transmembrane_features(REF["GYPA_HUMAN"]) + self.assertGreaterEqual(df["TM_n_segments"].iloc[0], 1) + self.assertGreater(df["TM_max_KD"].iloc[0], 1.6) + self.assertEqual(protpy.transmembrane_features(SEQ)["TM_n_segments"].iloc[0], 0) + #K/R before the segment, none after -> positive bias (and the reverse) + tm = "S" * 10 + "L" * 22 + "S" * 10 + self.assertGreater(protpy.transmembrane_features("KRKRK" + tm + "SSSSS")["TM_positive_inside_bias"].iloc[0], 0) + self.assertLess(protpy.transmembrane_features("SSSSS" + tm + "KRKRK")["TM_positive_inside_bias"].iloc[0], 0) + + def test_signal_peptide(self): + df = protpy.signal_peptide_features("MKKRLLLLLLLLAAAASA" + SEQ) + self.assertEqual(df["SP_nregion_charge"].iloc[0], 3) + self.assertGreater(df["SP_hregion_maxKD"].iloc[0], 2.5) + + def test_functional_sites(self): + df = protpy.functional_site_counts("NKSANPTANGTAKDEL") + #N-glyc: NKSA yes, NPTA no (P at X), NGTA yes + self.assertEqual(df["FS_N_glycosylation"].iloc[0], 2) + self.assertEqual(df["FS_ER_retention"].iloc[0], 1) + self.assertEqual(protpy.functional_site_counts("AAASKL")["FS_PTS1"].iloc[0], 1) + self.assertEqual(protpy.functional_site_counts("AKPAKRA")["FS_tryptic_site"].iloc[0], 2) + self.assertEqual(df["FS_ER_retention_per100"].iloc[0], round(100 / 16, 3)) + + def test_plaac(self): + #reference output of PLAAC 1.1.0 for MOT3 (cli/example/MOT3-candidates-plaac-1.1.0.tsv) + df = protpy.plaac(REF["MOT3"]) + self.assertEqual(df["PLAAC_LLR"].iloc[0], 40.751) + self.assertEqual(df["PLAAC_COREscore"].iloc[0], 40.751) + self.assertEqual(df["PLAAC_PrD_length"].iloc[0], 296) + self.assertEqual(df["PLAAC_has_PrD"].iloc[0], 1) + df = protpy.plaac(SEQ) + self.assertTrue(math.isnan(df["PLAAC_LLR"].iloc[0])) + self.assertEqual(df["PLAAC_has_PrD"].iloc[0], 0) + self.assertEqual(protpy.plaac(REF["GYPA_HUMAN"])["PLAAC_has_PrD"].iloc[0], 0) + + def test_antigenicity(self): + df = protpy.antigenicity_surface(SEQ) + self.assertEqual(list(df.columns), [f"{s}_{t}" for s in ("PARKER", "JANIN") for t in ("mean", "max", "min", "std")]) + self.assertEqual(protpy.antigenicity_surface("D" * 10)["PARKER_mean"].iloc[0], 10.0) + + def test_cysteine(self): + df = protpy.cysteine_features("CAACAAACAC") + self.assertEqual(df.values[0].tolist(), [4, 0.4, 1, 2, 1, 3.0]) + self.assertEqual(protpy.cysteine_features("AAA")["Cys_mean_spacing"].iloc[0], 0.0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_utils.py b/tests/test_utils.py index 160c0ef..9b601f1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -79,7 +79,8 @@ class ProtpyUtilsTests(unittest.TestCase): "abbreviation", "parameters", "output_shape", "speed", "reference", } - VALID_CATEGORIES = {"Composition", "Autocorrelation", "Conjoint Triad", "CTD", "Sequence Order"} + VALID_CATEGORIES = {"Composition", "Autocorrelation", "Conjoint Triad", "CTD", "Sequence Order", + "Scales", "Physicochemical", "Complexity", "Signal", "Structural"} VALID_SPEEDS = {"Fast", "Moderate", "Slow"} # ── Registry integrity ────────────────────────────────────────────────────────