<jupyter_start><jupyter_text>Infilling Human Antibody Sequences using SapiensTry out this notebook in your browser: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/Merck/Sapiens/main?labpath=notebooks%2F01_sapiens_antibody_infilling.ipynb)<jupyter_code>import sapiens<jupyter_output><empty_output><jupyter_text>Infilling an antibody sequencePositions marked with * or X will be infilled with the most likely human residues, given the rest of the sequence<jupyter_code>seq = '**QLV*SGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDYRFDMGFDYWGQGTTVTVSS'

print(seq)
print(sapiens.predict_masked(seq, 'H'))# H or L for Heavy or Light (kappa and lambda are using the same model)
print()<jupyter_output>**QLV*SGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDYRFDMGFDYWGQGTTVTVSS
QVQLVQSGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDYRFDMGFDYWGQGTTVTVSS<jupyter_text>Predictions can even be made in CDR3 regions<jupyter_code>seq = 'QVLQVESGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDY*****FDYWGQGTTVTVSS'

print(seq)
print(sapiens.predict_masked(seq, 'H'))
print()<jupyter_output>QVLQVESGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDY*****FDYWGQGTTVTVSS
QVLQVESGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDYGDSGYFDYWGQGTTVTVSS<jupyter_text>Use `predict_best_score` to humanize all positions, not just masks:<jupyter_code>seq = 'QVLQVESGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDY*****FDYWGQGTTVTVSS'

print(seq)
print(sapiens.predict_best_score(seq, 'H'))
print()<jupyter_output>QVLQVESGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDY*****FDYWGQGTTVTVSS
QVQLVQSGAEVKKPGASVKVSCKASGYTFTNYYMHWVRQAPGQGLEWMGWINPSNGGTNFAQKFQGRVTLTTDTSTTTAYMELRSLRSDDTAVYYCARRDYGDSGYFDYWGQGTLVTVSS<jupyter_text>Hundreds of sequences per second can be processed:<jupyter_code>%%timeit

sapiens.predict_masked(
    '******SGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDY*****FDYWGQGTTVTVSS',
    'H'  # H or L (kappa and lambda are using the same model)
)<jupyter_output>9.12 ms ± 227 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)<jupyter_text>Suggesting mutations Scores are given both for residues that are masked and that are present.  When inputting a non-human antibody sequence, the output scores can be used for humanization.<jupyter_code>scores = sapiens.predict_scores(
    '**QLV*SGVEVKKPGASVKVSCKASGYTFTNYYMYWVRQAPGQGLEWMGGINPSNGGTNFNEKFKNRVTLTTDSSTTTAYMELKSLQFDDTAVYYCARRDYRFDMGFDYWGQGTTVTVSS',
    'H'
)
scores<jupyter_output><empty_output><jupyter_text>Visualizing suggested mutations<jupyter_code>!pip install seaborn
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
def visualize_scores(sequence, chain_type):
    preds = sapiens.predict_scores(sequence, chain_type)
    sns.set(rc={'figure.figsize':(12, 24)})
    labels = [f"{i}: {pos}" for i, pos in enumerate(sequence)]
    p = sns.heatmap(preds, yticklabels=labels, cmap="mako")
    p.xaxis.tick_top()
visualize_scores(
    'QVKLQESGAELARPGASVKLSCKASGYTFTNYWMQWVKQRPGQGLDWIGAIYPGDGNTRYTHKFKGKATLTADKSSSTAYMQLSSLASEDSGVYYCARGEGNYAWFAYWGQGTTVTVSS',
    'H'
)<jupyter_output><empty_output><jupyter_text>Show only the five most probable residues for each position:<jupyter_code>def visualize_top_five(sequence, chain_type, offset=0, length=15):
    preds = sapiens.predict_scores(sequence, chain_type)
    preds.index = list(sequence)
    
    numbers = preds.T.reset_index(drop=True).T
    numbers = numbers.apply(lambda row: pd.Series(row.sort_values(ascending=False).values, row.index), axis=1)
    numbers.columns = numbers.columns + 1
    
    annot = preds.apply(lambda row: (row.sort_values(ascending=False).index + '\n' + row.sort_values(ascending=False).apply('{:.1%}'.format)).reset_index(drop=True), axis=1)
    annot.columns = annot.columns + 1
    
    fig, ax = plt.subplots(figsize=(16, 3))
    sns.heatmap(numbers.iloc[offset:length + offset,:5].T, annot=annot.iloc[offset:length + offset,:5].T, fmt='s', ax=ax, robust=True, vmax=1, vmin=0, cmap='Greens', cbar_kws={'label': 'Probability'});
    for i in range(length):
        ax.axvline(i, color='white', lw=5)
    ax.set_ylabel('Top predictions');
    ax.xaxis.tick_top()
    plt.xticks(rotation=0, fontsize=15);
visualize_top_five(
    'QVKLQESGAELARPGASVKLSCKASGYTFTNYWMQWVKQRPGQGLDWIGAIYPGDGNTRYTHKFKGKATLTADKSSSTAYMQLSSLASEDSGVYYCARGEGNYAWFAYWGQGTTVTVSS', 
    'H',
    offset=0, 
    length=15
)
visualize_top_five(
    '*****ESGAELARPGASVKLSCKASGYTFTNYWMQWVKQRPGQGLDWIGAIYPGDGNTRYTHKFKGKATLTADKSSSTAYMQLSSLASEDSGVYYCARGEGNYAWFAYWGQGTTVTVSS', 
    'H',
    offset=0, 
    length=15
)
def visualize_top_five_vertical(sequence, chain_type):
    preds = sapiens.predict_scores(sequence, chain_type)
    preds.index = list(sequence)
    
    numbers = preds.T.reset_index(drop=True).T
    numbers = numbers.apply(lambda row: pd.Series(row.sort_values(ascending=False).values, row.index), axis=1)
    numbers.columns = numbers.columns + 1
    
    annot = preds.apply(lambda row: (row.sort_values(ascending=False).index + '   ' + row.sort_values(ascending=False).apply('{:.1%}'.format)).reset_index(drop=True), axis=1)
    annot.columns = annot.columns + 1
    
    fig, ax = plt.subplots(figsize=(8, 35))
    sns.heatmap(numbers.iloc[:,:5], annot=annot.iloc[:,:5], fmt='s', ax=ax, robust=True, vmax=0.5, cmap='Greens');
    plt.yticks(rotation=0, fontsize=13);
visualize_top_five_vertical(
    'QVKLQESGAELARPGASVKLSCKASGYTFTNYWMQWVKQRPGQGLDWIGAIYPGDGNTRYTHKFKGKATLTADKSSSTAYMQLSSLASEDSGVYYCARGEGNYAWFAYWGQGTTVTVSS', 
    'H'
)<jupyter_output><empty_output><jupyter_text>Evaluating infill accuracy on TheraSAbDab<jupyter_code>import sys
from os import path
!wget -O TheraSAbDab.csv http://opig.stats.ox.ac.uk/webapps/newsabdab/static/downloads/TheraSAbDab_SeqStruc_OnlineDownload.csv
thera = pd.read_csv("TheraSAbDab.csv")
thera.head()
def infill_sequence_pos(positions, sequence, chain_type):
    """Mask the residues given by positions and perform infilling."""
    list_seq = list(sequence)
    for pos in positions:
        list_seq[pos] = "*"
    query_seq = "".join(list_seq)
    return predict_masked(query_seq, chain_type)
def get_mean_infill_accuracy(sequence, chain_type):
    """Mask each position in the sequence individually and return the accuracy of infilling the original residue."""
    correct = 0
    for i in range(len(sequence)):
        filled = infill_sequence_pos([i], sequence, chain_type)
        if filled == sequence:
            correct += 1
    return correct / len(sequence)
get_mean_infill_accuracy(
    "QVKLQESGAELARPGASVKLSCKASGYTFTNYWMQWVKQRPGQGLDWIGAIYPGDGNTRYTHKFKGKATLTADKSSSTAYMQLSSLASEDSGVYYCARGEGNYAWFAYWGQGTTVTVSS", 
    "H"
)
def suffix_class(name):
    if name.endswith("zumab"):
        return "zumab"
    if name.endswith("umab"):
        return "umab"
    if name.endswith("imab"):
        return "imab"
    return None
thera["suffix"] = thera["Therapeutic"].apply(suffix_class)
%%time

# Apply infilling to each heavy sequence and get average accuracy
thera["Accuracy heavy"] = thera["Heavy Sequence"].apply(lambda x: get_mean_infill_accuracy(x, "H"))
thera.head()
# Accuracies of infilling by antibody suffix
thera.groupby("suffix").mean()["Accuracy heavy"]
%%time

# Repeat for light sequences
thera["Accuracy light"] = thera["Light Sequence"].apply(lambda x: get_mean_infill_accuracy(x, "L"))
thera.groupby("suffix").mean()["Accuracy light"]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1052833078101072, 'fraction_numerical': 0.007784583971413987, 'mean_word_length': 6.8843058350100605, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '114743', 'license': 'MIT', 'n_tokens_mistral': 3407, 'n_tokens_neox': 3180, 'n_words': 461, 'path': 'notebooks/01_sapiens_antibody_infilling.ipynb', 'repo_name': 'Merck/Sapiens', 'size': '7836'}