<jupyter_start><jupyter_text>Read the activity data from the CSV file<jupyter_code>data_df = pd.read_csv("activity_data.csv")<jupyter_output><empty_output><jupyter_text>Look for the duplicated records in the dataframe<jupyter_code>for k,v in data_df.groupby("SMILES"):
    if len(v) > 1:
        print(k,v['IC50 (uM)'].values)<jupyter_output>CC(=O)N1CCN(CC(=O)Nc2cccc(S(N)(=O)=O)c2)CC1 [nan nan]
CC(C(=O)O)c1ccc2c(c1)[nH]c1ccc(Cl)cc12 [nan nan nan nan]
CC(C)N(C)C(=O)C1CCN(C(=O)CCl)CC1 [nan nan]
CNS(=O)(=O)Cc1ccc2[nH]cc(CCN(C)C)c2c1 [nan nan nan]
COc1ccc2[nH]cc(CCNC(C)=O)c2c1 [nan nan]
CS(=O)(=O)Nc1cccc(C(=O)Nc2cccc(N3CCCC3=O)c2)c1 [nan nan]
Cc1ccc(C)c(S(=O)(=O)N2CCN(C(=O)CCl)CC2)c1 [nan nan]
Cc1ccncc1NC(=O)Nc1cccc(Cl)c1 [68. 68.]
Cc1ccncc1NC(=O)Nc1ccccc1 [nan nan]
Cc1ccncc1NC(=O)c1cccc(NC(=O)Nc2cccnc2)c1 [nan nan]
N#Cc1ccc(NC(=O)Nc2cccnc2)cc1 [nan nan]
N#Cc1cccc(NC(=O)Nc2cccnc2)c1 [nan nan nan nan]
O=C(CCl)N1CCC(C(=O)N2CCCCC2)CC1 [nan nan]
O=C(CCl)N1CCN(Cc2cccc(Cl)c2)CC1 [nan nan nan]
O=C(CCl)N1CCN(Cc2cccc3ccccc23)CC1 [nan nan]
O=C(CCl)N1CCN(Cc2cccs2)CC1 [5.66 5.66]
O=C(CCl)N1CCN(Cc2ccsc2)CC1 [3.27 3.27 3.27]
O=C(CCl)N1CCN(S(=O)(=O)c2c(F)cccc2F)CC1 [3.43 3.43]
O=C(CCl)N1CCN(S(=O)(=O)c2cccc(F)c2)CC1 [4.1 4.1]
O=C(CCl)N1CCN(S(=O)(=O)c2cccs2)CC1 [2.07 2.07]
O=C(CCl)Nc1cccc(N2CCCC2=O)c1 [72. 72. 72.]
O=C(c1cc(=O)[nH]c2ccccc12)N1[...]<jupyter_text>Remove the duplicated rows in the dataframe<jupyter_code>before_rows = data_df.shape[0]
data_df.drop_duplicates("SMILES",inplace=True)
after_rows = data_df.shape[0]
print(f"{before_rows} rows reduced to {after_rows} rows")<jupyter_output>310 rows reduced to 280 rows<jupyter_text>Add a RDKit molecule column to the dataframe<jupyter_code>data_df['mol'] = [Chem.MolFromSmiles(x) for x in data_df.SMILES]<jupyter_output><empty_output><jupyter_text>Change the missing IC50 values to 999 and add a pIC50 column<jupyter_code>data_df.fillna({'IC50 (uM)': 999},inplace=True)
data_df['pIC50'] = -np.log10([x * 1e-6 for x in data_df['IC50 (uM)'].values])<jupyter_output><empty_output><jupyter_text>Add a compound to identify the covalent compounds<jupyter_code>chloromethylketone = Chem.MolFromSmarts('C(=O)[CH2]Cl')
acrylamide = Chem.MolFromSmarts('C(=O)[CH]=[CH2]')
data_df['covalent'] = [x.HasSubstructMatch(acrylamide) or x.HasSubstructMatch(chloromethylketone) for x in data_df.mol]<jupyter_output><empty_output><jupyter_text>Add a fingerprint column<jupyter_code>data_df['fp'] = [Chem.RDKFingerprint(x) for x in data_df.mol]<jupyter_output><empty_output><jupyter_text>Select covalent or noncovalent compounds.  To select all compounds just set "df = data_df"<jupyter_code>df = data_df.query("covalent == False")
df.shape<jupyter_output><empty_output><jupyter_text>Calculate the [Structure Activity Landscape Index (SALI)](https://pubs.acs.org/doi/abs/10.1021/ci7004093) as $\dfrac{\Delta pIC_{50}}{1-Tanimoto}$.  Note that we add 0.001 to the denominator to avoid divide by zero errors.<jupyter_code>pIC50_list = df.pIC50.values
smiles_list = df.SMILES.values
fp_list = df.fp.values
sal_list = []
for i,fp in enumerate(df.fp):
    sim_list = DataStructs.BulkTanimotoSimilarity(fp,fp_list)
    for j in range(0,i):
        ii,jj = i,j
        if pIC50_list[i] >= pIC50_list[j]:
            ii,jj = i,j
        else:
            jj,ii = i,j
        delta = abs(pIC50_list[i]-pIC50_list[j])
        sim = sim_list[j]
        sal_list.append([ii,jj,sim,delta/(1-sim+0.001),smiles_list[ii],smiles_list[jj],pIC50_list[ii],pIC50_list[jj]])<jupyter_output><empty_output><jupyter_text>Create a dataframe with the SALI data<jupyter_code>sal_df = pd.DataFrame(sal_list,columns=["i","j","Sim","SAL","SMILES_i","SMILES_j","pIC50_i","pIC50_j"])
sal_df.sort_values("SAL",ascending=False,inplace=True)
sal_df = sal_df.head(100)<jupyter_output><empty_output><jupyter_text>Add molecule columns to the SALI dataframe<jupyter_code>PandasTools.AddMoleculeColumnToFrame(sal_df,"SMILES_i","Mol_i")
PandasTools.AddMoleculeColumnToFrame(sal_df,"SMILES_j","Mol_j")
sal_df.head()<jupyter_output><empty_output><jupyter_text>Align pairs of molecules based on the Maximum Common Subgraph (MCS)<jupyter_code>def align_mols(df_in):
    mcs_list = []
    for idx, [mol_i,mol_j] in tqdm(df_in[["Mol_i","Mol_j"]].iterrows(),total=df_in.shape[0]):
        mcs = rdFMCS.FindMCS([mol_i, mol_j],completeRingsOnly=True)
        mcs_query = Chem.MolFromSmarts(mcs.smartsString)
        AllChem.Compute2DCoords(mcs_query)
        AllChem.GenerateDepictionMatching2DStructure(mol_i,mcs_query)
        AllChem.GenerateDepictionMatching2DStructure(mol_j,mcs_query)
        
align_mols(sal_df)<jupyter_output><empty_output><jupyter_text>View the pairs of molecules. The more active molecule is always on the left.<jupyter_code>#@interact(row_num=range(0,100)) #control with a menu
@interact(row_num=(0,99,1)) #control with a slider
def show_pair(row_num=0):
    row = sal_df.iloc[row_num]
    mol_i = row.Mol_i
    mol_j = row.Mol_j
    i = row.i
    j = row.j
    legends = [f"{df.iloc[i].CID} | {df.iloc[i]['IC50 (uM)']:.2f}",f"{df.iloc[j].CID} | {df.iloc[j]['IC50 (uM)']:.2f}"]
    print(df.iloc[i].CID,i)
    print(df.iloc[j].CID,j)
    print(f"SAL = {row.SAL:.2f}")
    print(f"Tanimoto = {row.Sim:.2f}")
    delta = 10**(abs(row.pIC50_i-row.pIC50_j))
    print(f"Delta = {delta:.1f}")
    return MolsToGridImage([mol_i,mol_j],molsPerRow=2,subImgSize=(300, 300),legends=legends)<jupyter_output><empty_output><jupyter_text>Find the most similar crystal structure.  We'll begin by reading the SMILES and molecule names for the crystallized compounds.<jupyter_code>lig_buff = """Mpro-x0072	CS(=O)(=O)NCCC=1C=CC=CC1
Mpro-x0104	CC(=O)NCCC1=CNC=2C=CC(F)=CC12
Mpro-x0107	CC(=O)NC=1C=NC=CC1C
Mpro-x0161	COC(=O)C=1C=CC(=CC1)S(=O)(=O)N
Mpro-x0195	CN1CCCC=2C=CC(=CC12)S(=O)(=O)N
Mpro-x0305	CCNC=1C=CC(C#N)=CN1
Mpro-x0354	CN1CCN(CC1)C(=O)COC=2C=CC(C)=CC2
Mpro-x0387	OC1CCN(CC=2C=CSC2)CC1
Mpro-x0395	CC1=NN=C(CN2CCC=C(F)C2)S1
Mpro-x0397	CN(CC=1C=C(C)ON1)C(=O)NC2CC2
Mpro-x0426	FC=1C=CC=CC1C(=O)NCCC=2C=CN=CC2
Mpro-x0434	O=C(NC=1C=CC=CC1)NC=2C=CC=NC2
Mpro-x0540	O=C(NCCC=1C=CN=CC1)NC2CCCCC2
Mpro-x0678	O=C(CC1CCCCC1)NC=2C=CC=NC2
Mpro-x0874	O=C([C@@H]1[C@H](C2=CSC=C2)CCC1)N
Mpro-x0946	BrC1=CC=C(S(N)(=O)=O)C=C1
Mpro-x0967	CC(=O)NC(Cc1ccc(cc1)O)C(=O)NCC#CBr
Mpro-x0991	Cl.CCC(=N)N
Mpro-x0995	NC=1C=NC=NC1
Mpro-x1077	N#CC=1C=CC(=CN1)N2CCCOCC2
Mpro-x1093	CN1CCN(CC1)C(=O)CC1=CNC2=NC=CC=C12
Mpro-x1249	O=C(NCC=1C=CC(C#N)=CC1)N2CCOCC2"""<jupyter_output><empty_output><jupyter_text>Create a dataframe with the data above<jupyter_code>lig_df = pd.DataFrame([x.split() for x in lig_buff.split("\n")],columns=["Name","SMILES"])<jupyter_output><empty_output><jupyter_text>Add molecule and fingerprint columns to the dataframe<jupyter_code>lig_df['mol'] = [Chem.MolFromSmiles(x) for x in lig_df.SMILES]
lig_df['fp'] = [Chem.RDKFingerprint(x) for x in lig_df.mol]<jupyter_output><empty_output><jupyter_text>Define the query fingerprint<jupyter_code>query_fp = df.iloc[41].fp<jupyter_output><empty_output><jupyter_text>Calculate the Tanimoto similarity of each of the crystallized molecules to the query<jupyter_code>lig_df['sim'] = [DataStructs.TanimotoSimilarity(query_fp,x) for x in lig_df.fp]
lig_df.sort_values("sim",inplace=True,ascending=False)
lig_df.head()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16911461908030198, 'fraction_numerical': 0.05696636925188744, 'mean_word_length': 7.472093023255814, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 73, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '659975', 'license': 'MIT', 'n_tokens_mistral': 3731, 'n_tokens_neox': 3491, 'n_words': 455, 'path': 'sali.ipynb', 'repo_name': 'PatWalters/sali', 'size': '7285'}