Download model/utils/featurizer_utils.py from OneScience-Group/MULTI-evolve: direct link, hf CLI and curl.
- Browser
- Download file 11.2 kB
-
https://huggingface.co/OneScience-Group/MULTI-evolve/resolve/main/model/utils/featurizer_utils.py
- Command line
-
hf download hf://OneScience-Group/MULTI-evolve/model/utils/featurizer_utils.py
-
curl -L -o featurizer_utils.py https://huggingface.co/OneScience-Group/MULTI-evolve/resolve/main/model/utils/featurizer_utils.py
11.2 kB
| # This module contains utility functions and classes for feature extraction from protein sequences | |
| import numpy as np | |
| import string | |
| from typing import Tuple, List | |
| import os | |
| from Bio import SeqIO | |
| from scipy.spatial.distance import cdist | |
| """Base Featurizer Functions""" | |
| # Code acquired from Chloe Hsu's 2022 paper and github: https://github.com/chloechsu/combining-evolutionary-and-assay-labelled-data/blob/main/src/utils/georgiev_utils.py | |
| # This section of code is copied from ProFET (Ofer & Linial, DOI: 10.1093/bioinformatics/btv345) | |
| # Original comment by the ProFET authors: 'Acquired from georgiev's paper of | |
| # AAscales using helper script "GetTextData.py". + RegEx cleaning DOI: 10.1089/cmb.2008.0173' | |
| gg_1 = {'Q': -2.54, 'L': 2.72, 'T': -0.65, 'C': 2.66, 'I': 3.1, 'G': 0.15, 'V': 2.64, 'K': -3.89, 'M': 1.89, 'F': 3.12, 'N': -2.02, 'R': -2.8, 'H': -0.39, 'E': -3.08, 'W': 1.89, 'A': 0.57, 'D': -2.46, 'Y': 0.79, 'S': -1.1, 'P': -0.58, '*': 0} | |
| gg_2 = {'Q': 1.82, 'L': 1.88, 'T': -1.6, 'C': -1.52, 'I': 0.37, 'G': -3.49, 'V': 0.03, 'K': 1.47, 'M': 3.88, 'F': 0.68, 'N': -1.92, 'R': 0.31, 'H': 1, 'E': 3.45, 'W': -0.09, 'A': 3.37, 'D': -0.66, 'Y': -2.62, 'S': -2.05, 'P': -4.33, '*': 0} | |
| gg_3 = {'Q': -0.82, 'L': 1.92, 'T': -1.39, 'C': -3.29, 'I': 0.26, 'G': -2.97, 'V': -0.67, 'K': 1.95, 'M': -1.57, 'F': 2.4, 'N': 0.04, 'R': 2.84, 'H': -0.63, 'E': 0.05, 'W': 4.21, 'A': -3.66, 'D': -0.57, 'Y': 4.11, 'S': -2.19, 'P': -0.02, '*': 0} | |
| gg_4 = {'Q': -1.85, 'L': 5.33, 'T': 0.63, 'C': -3.77, 'I': 1.04, 'G': 2.06, 'V': 2.34, 'K': 1.17, 'M': -3.58, 'F': -0.35, 'N': -0.65, 'R': 0.25, 'H': -3.49, 'E': 0.62, 'W': -2.77, 'A': 2.34, 'D': 0.14, 'Y': -0.63, 'S': 1.36, 'P': -0.21, '*': 0} | |
| gg_5 = {'Q': 0.09, 'L': 0.08, 'T': 1.35, 'C': 2.96, 'I': -0.05, 'G': 0.7, 'V': 0.64, 'K': 0.53, 'M': -2.55, 'F': -0.88, 'N': 1.61, 'R': 0.2, 'H': 0.05, 'E': -0.49, 'W': 0.72, 'A': -1.07, 'D': 0.75, 'Y': 1.89, 'S': 1.78, 'P': -8.31, '*': 0} | |
| gg_6 = {'Q': 0.6, 'L': 0.09, 'T': -2.45, 'C': -2.23, 'I': -1.18, 'G': 7.47, 'V': -2.01, 'K': 0.1, 'M': 2.07, 'F': 1.62, 'N': 2.08, 'R': -0.37, 'H': 0.41, 'E': 0, 'W': 0.86, 'A': -0.4, 'D': 0.24, 'Y': -0.53, 'S': -3.36, 'P': -1.82, '*': 0} | |
| gg_7 = {'Q': 0.25, 'L': 0.27, 'T': -0.65, 'C': 0.44, 'I': -0.21, 'G': 0.41, 'V': -0.33, 'K': 4.01, 'M': 0.84, 'F': -0.15, 'N': 0.4, 'R': 3.81, 'H': 1.61, 'E': -5.66, 'W': -1.07, 'A': 1.23, 'D': -5.15, 'Y': -1.3, 'S': 1.39, 'P': -0.12, '*': 0} | |
| gg_8 = {'Q': 2.11, 'L': -4.06, 'T': 3.43, 'C': -3.49, 'I': 3.45, 'G': 1.62, 'V': 3.93, 'K': -0.01, 'M': 1.85, 'F': -0.41, 'N': -2.47, 'R': 0.98, 'H': -0.6, 'E': -0.11, 'W': -1.66, 'A': -2.32, 'D': -1.17, 'Y': 1.31, 'S': -1.21, 'P': -1.18, '*': 0} | |
| gg_9 = {'Q': -1.92, 'L': 0.43, 'T': 0.34, 'C': 2.22, 'I': 0.86, 'G': -0.47, 'V': -0.21, 'K': -0.26, 'M': -2.05, 'F': 4.2, 'N': -0.07, 'R': 2.43, 'H': 3.55, 'E': 1.49, 'W': -5.87, 'A': -2.01, 'D': 0.73, 'Y': -0.56, 'S': -2.83, 'P': 0, '*': 0} | |
| gg_10 = {'Q': -1.67, 'L': -1.2, 'T': 0.24, 'C': -3.78, 'I': 1.98, 'G': -2.9, 'V': 1.27, 'K': -1.66, 'M': 0.78, 'F': 0.73, 'N': 7.02, 'R': -0.99, 'H': 1.52, 'E': -2.26, 'W': -0.66, 'A': 1.31, 'D': 1.5, 'Y': -0.95, 'S': 0.39, 'P': -0.66, '*': 0} | |
| gg_11 = {'Q': 0.7, 'L': 0.67, 'T': -0.53, 'C': 1.98, 'I': 0.89, 'G': -0.98, 'V': 0.43, 'K': 5.86, 'M': 1.53, 'F': -0.56, 'N': 1.32, 'R': -4.9, 'H': -2.28, 'E': -1.62, 'W': -2.49, 'A': -1.14, 'D': 1.51, 'Y': 1.91, 'S': -2.92, 'P': 0.64, '*': 0} | |
| gg_12 = {'Q': -0.27, 'L': -0.29, 'T': 1.91, 'C': -0.43, 'I': -1.67, 'G': -0.62, 'V': -1.71, 'K': -0.06, 'M': 2.44, 'F': 3.54, 'N': -2.44, 'R': 2.09, 'H': -3.12, 'E': -3.97, 'W': -0.3, 'A': 0.19, 'D': 5.61, 'Y': -1.26, 'S': 1.27, 'P': -0.92, '*': 0} | |
| gg_13 = {'Q': -0.99, 'L': -2.47, 'T': 2.66, 'C': -1.03, 'I': -1.02, 'G': -0.11, 'V': -2.93, 'K': 1.38, 'M': -0.26, 'F': 5.25, 'N': 0.37, 'R': -3.08, 'H': -1.45, 'E': 2.3, 'W': -0.5, 'A': 1.66, 'D': -3.85, 'Y': 1.57, 'S': 2.86, 'P': -0.37, '*': 0} | |
| gg_14 = {'Q': -1.56, 'L': -4.79, 'T': -3.07, 'C': 0.93, 'I': -1.21, 'G': 0.15, 'V': 4.22, 'K': 1.78, 'M': -3.09, 'F': 1.73, 'N': -0.89, 'R': 0.82, 'H': -0.77, 'E': -0.06, 'W': 1.64, 'A': 4.39, 'D': 1.28, 'Y': 0.2, 'S': -1.88, 'P': 0.17, '*': 0} | |
| gg_15 = {'Q': 6.22, 'L': 0.8, 'T': 0.2, 'C': 1.43, 'I': -1.78, 'G': -0.53, 'V': 1.06, 'K': -2.71, 'M': -1.39, 'F': 2.14, 'N': 3.13, 'R': 1.32, 'H': -4.18, 'E': -0.35, 'W': -0.72, 'A': 0.18, 'D': -1.98, 'Y': -0.76, 'S': -2.42, 'P': 0.36, '*': 0} | |
| gg_16 = {'Q': -0.18, 'L': -1.43, 'T': -2.2, 'C': 1.45, 'I': 5.71, 'G': 0.35, 'V': -1.31, 'K': 1.62, 'M': -1.02, 'F': 1.1, 'N': 0.79, 'R': 0.69, 'H': -2.91, 'E': 1.51, 'W': 1.75, 'A': -2.6, 'D': 0.05, 'Y': -5.19, 'S': 1.75, 'P': 0.08, '*': 0} | |
| gg_17 = {'Q': 2.72, 'L': 0.63, 'T': 3.73, 'C': -1.15, 'I': 1.54, 'G': 0.3, 'V': -1.97, 'K': 0.96, 'M': -4.32, 'F': 0.68, 'N': -1.54, 'R': -2.62, 'H': 3.37, 'E': -2.29, 'W': 2.73, 'A': 1.49, 'D': 0.9, 'Y': -2.56, 'S': -2.77, 'P': 0.16, '*': 0} | |
| gg_18 = {'Q': 4.35, 'L': -0.24, 'T': -5.46, 'C': -1.64, 'I': 2.11, 'G': 0.32, 'V': -1.21, 'K': -1.09, 'M': -1.34, 'F': 1.46, 'N': -1.71, 'R': -1.49, 'H': 1.87, 'E': -1.47, 'W': -2.2, 'A': 0.46, 'D': 1.38, 'Y': 2.87, 'S': 3.36, 'P': -0.34, '*': 0} | |
| gg_19 = {'Q': 0.92, 'L': 1.01, 'T': -0.73, 'C': -1.05, 'I': -4.18, 'G': 0.05, 'V': 4.77, 'K': 1.36, 'M': 0.09, 'F': 2.33, 'N': -0.25, 'R': -2.57, 'H': 2.17, 'E': 0.15, 'W': 0.9, 'A': -4.22, 'D': -0.03, 'Y': -3.43, 'S': 2.67, 'P': 0.04, '*': 0} | |
| # Package all georgiev parameters | |
| georgiev_parameters = [gg_1, gg_2, gg_3, gg_4, gg_5, gg_6, gg_7, gg_8, gg_9, | |
| gg_10, gg_11, gg_12, gg_13, gg_14, gg_15, gg_16, gg_17, | |
| gg_18, gg_19] | |
| def get_georgiev_params_for_aa(aa: str) -> List[float]: | |
| """ | |
| Get Georgiev parameters for a given amino acid. | |
| Args: | |
| aa (str): Amino acid character. | |
| Returns: | |
| List[float]: List of Georgiev parameters for the given amino acid. | |
| """ | |
| return [gg[aa] for gg in georgiev_parameters] | |
| def get_georgiev_params_for_seq(s: str) -> np.ndarray: | |
| """ | |
| Get Georgiev parameters for a given sequence. | |
| Args: | |
| s (str): Amino acid sequence. | |
| Returns: | |
| np.ndarray: Array of Georgiev parameters for the given sequence. | |
| """ | |
| return np.array([get_georgiev_params_for_aa(aa) for aa in s]) | |
| def seqs_to_georgiev(seqs: List[str]) -> np.ndarray: | |
| """ | |
| Convert a list of sequences to Georgiev parameters. | |
| Args: | |
| seqs (List[str]): List of amino acid sequences. | |
| Returns: | |
| np.ndarray: Array of Georgiev parameters for the given sequences. | |
| """ | |
| return np.stack([get_georgiev_params_for_seq(s) for s in seqs]) | |
| def featurize_aa_idx(seqs: List[str]) -> np.ndarray: | |
| """ | |
| Featurize amino acid sequences using PCA features. | |
| Args: | |
| seqs (List[str]): List of amino acid sequences. | |
| Returns: | |
| np.ndarray: Array of featurized sequences. | |
| """ | |
| # Code adapted from Sam Gelman's 2021 paper and github: https://github.com/gitter-lab/nn4dms.git | |
| # list of chars that can be encountered in any sequence | |
| CHARS = ["*", "A", "C", "D", "E", "F", "G", "H", "I", "K", "L", | |
| "M", "N", "P", "Q", "R", "S", "T", "V", "W", "Y"] | |
| # dictionary mapping chars->int | |
| C2I_MAPPING = {c: i for i, c in enumerate(CHARS)} | |
| current_directory = os.path.dirname(os.path.abspath(__file__)) | |
| aa_features = np.load(os.path.join(current_directory, "pca-19.npy")) | |
| aa_features = np.insert(aa_features, 0, np.zeros(aa_features.shape[1]), axis=0) | |
| X = [] | |
| for seq in seqs: | |
| seq_feat = [] | |
| for aa in seq: | |
| seq_feat.append(aa_features[C2I_MAPPING[aa]]) | |
| seq_feat = np.array(seq_feat) | |
| X.append(seq_feat) | |
| X = np.stack(X) | |
| return X | |
| """MSA Featurizer Utils""" | |
| # This is an efficient way to delete lowercase characters and insertion characters from a string | |
| deletekeys = dict.fromkeys(string.ascii_lowercase) | |
| deletekeys["."] = None | |
| deletekeys["*"] = None | |
| translation = str.maketrans(deletekeys) | |
| def read_sequence(filename: str) -> Tuple[str, str]: | |
| """ | |
| Reads the first (reference) sequences from a fasta or MSA file. | |
| Args: | |
| filename (str): Path to the fasta or MSA file. | |
| Returns: | |
| Tuple[str, str]: Description and sequence of the first record. | |
| """ | |
| record = next(SeqIO.parse(filename, "fasta")) | |
| return record.description, str(record.seq) | |
| def remove_insertions(sequence: str) -> str: | |
| """ | |
| Removes any insertions into the sequence. Needed to load aligned sequences in an MSA. | |
| Args: | |
| sequence (str): Amino acid sequence. | |
| Returns: | |
| str: Sequence with insertions removed. | |
| """ | |
| return sequence.translate(translation) | |
| def read_msa(filename: str) -> List[Tuple[str, str]]: | |
| """ | |
| Reads the sequences from an MSA file, automatically removes insertions. | |
| Args: | |
| filename (str): Path to the MSA file. | |
| Returns: | |
| List[Tuple[str, str]]: List of tuples containing description and sequence. | |
| """ | |
| return [(record.description, remove_insertions(str(record.seq))) for record in SeqIO.parse(filename, "fasta")] | |
| def greedy_select(msa: List[Tuple[str, str]], num_seqs: int, mode: str = "max") -> List[Tuple[str, str]]: | |
| """ | |
| Select sequences from the MSA to maximize the hamming distance. | |
| Args: | |
| msa (List[Tuple[str, str]]): List of tuples containing description and sequence. | |
| num_seqs (int): Number of sequences to select. | |
| mode (str): Selection mode, either "max" or "min". | |
| Returns: | |
| List[Tuple[str, str]]: List of selected sequences. | |
| """ | |
| assert mode in ("max", "min") | |
| if len(msa) <= num_seqs: | |
| return msa | |
| array = np.array([list(seq) for _, seq in msa], dtype=np.bytes_).view(np.uint8) | |
| optfunc = np.argmax if mode == "max" else np.argmin | |
| all_indices = np.arange(len(msa)) | |
| indices = [0] | |
| pairwise_distances = np.zeros((0, len(msa))) | |
| for _ in range(num_seqs - 1): | |
| dist = cdist(array[indices[-1:]], array, "hamming") | |
| pairwise_distances = np.concatenate([pairwise_distances, dist]) | |
| shifted_distance = np.delete(pairwise_distances, indices, axis=1).mean(0) | |
| shifted_index = optfunc(shifted_distance) | |
| index = np.delete(all_indices, indices)[shifted_index] | |
| indices.append(index) | |
| indices = sorted(indices) | |
| return [msa[idx] for idx in indices] | |
| def msa_splicer(msa: List[Tuple[str, str]]) -> List[Tuple[str, str]]: | |
| """ | |
| Splice the MSA into only positions where the first sequence is (remove extra positions in the MSA). | |
| Args: | |
| msa (List[Tuple[str, str]]): List of tuples containing description and sequence. | |
| Returns: | |
| List[Tuple[str, str]]: List of spliced sequences. | |
| """ | |
| # Get the positions where the first sequence in the MSA is not empty | |
| base_sequence = msa[0][1] | |
| positions = [i for i, _ in enumerate(base_sequence) if base_sequence[i] != '-'] | |
| # Apply the positions over the entire MSA | |
| spliced_msa = [] | |
| for name, seq in msa: | |
| spliced_seq = [seq[i] for i in positions] | |
| spliced_msa.append((name, ''.join(spliced_seq))) | |
| return spliced_msa |