import%20marimo%0A%0A__generated_with%20%3D%20%220.22.5%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%201c%20%E2%80%94%20Activity%20cliff%20analysis%0A%0A%20%20%20%20Activity%20cliffs%20(ACs)%20are%20pairs%20of%20molecules%20that%20are%20structurally%20very%20similar%0A%20%20%20%20but%20have%20large%20differences%20in%20biological%20activity.%20%20They%20are%20challenging%20for%0A%20%20%20%20machine-learning%20models%20because%20they%20violate%20the%20%22similar%20molecules%20%E2%86%94%20similar%0A%20%20%20%20activity%22%20assumption%20that%20most%20models%20rely%20on.%0A%0A%20%20%20%20This%20notebook%3A%0A%20%20%20%201.%20Identifies%20ACs%20from%20the%20**MMP**%20table%20(transformation-based%20cliffs).%0A%20%20%20%202.%20Identifies%20ACs%20from%20**pairwise%20fingerprint%20similarity**%20(threshold-based).%0A%20%20%20%203.%20Displays%20an%20interactive%20scatter%20of%20all%20ECFP4%20cliffs%20with%20side-by-side%0A%20%20%20%20%20%20%20structure%20rendering.%0A%20%20%20%204.%20Shows%20a%20UMAP%20of%20the%20dose-response%20subset%20coloured%20by%20pEC50.%0A%0A%20%20%20%20**Input%3A**%20%60data%2Fprocessed%2Fall_compounds_activity_data.csv%60%20and%0A%20%20%20%20%60data%2Fprocessed%2Fall_compounds_mmp.mmp.csv.gz%60%20(both%20produced%20by%20notebook%20**1a**).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20polars%20as%20pl%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20altair%20as%20alt%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20base64%0A%20%20%20%20from%20pathlib%20import%20Path%0A%20%20%20%20from%20typing%20import%20Optional%2C%20Callable%0A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20matplotlib.figure%0A%20%20%20%20from%20matplotlib.colors%20import%20LinearSegmentedColormap%0A%20%20%20%20from%20scipy.stats%20import%20gaussian_kde%0A%20%20%20%20from%20matplotlib_venn%20import%20venn2%0A%0A%20%20%20%20from%20sklearn.decomposition%20import%20PCA%0A%20%20%20%20from%20umap%20import%20UMAP%0A%20%20%20%20from%20sklearn.manifold%20import%20TSNE%0A%0A%20%20%20%20from%20rdkit%20import%20Chem%2C%20DataStructs%2C%20RDLogger%0A%20%20%20%20from%20rdkit.Chem%20import%20rdDepictor%2C%20CombineMols%0A%20%20%20%20from%20rdkit.Chem.Draw%20import%20rdMolDraw2D%0A%0A%20%20%20%20from%20skfp.fingerprints%20import%20(%0A%20%20%20%20%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20MACCSFingerprint%2C%0A%20%20%20%20%20%20%20%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20%20%20%20%20RDKitFingerprint%2C%0A%20%20%20%20%20%20%20%20AtomPairFingerprint%2C%0A%20%20%20%20%20%20%20%20AvalonFingerprint%2C%0A%20%20%20%20%20%20%20%20MordredFingerprint%2C%0A%20%20%20%20%20%20%20%20MQNsFingerprint%2C%0A%20%20%20%20%20%20%20%20PubChemFingerprint%2C%0A%20%20%20%20)%0A%20%20%20%20from%20skfp.preprocessing%20import%20ConformerGenerator%2C%20MolFromSmilesTransformer%0A%0A%20%20%20%20RDLogger.DisableLog(%22rdApp.*%22)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20AtomPairFingerprint%2C%0A%20%20%20%20%20%20%20%20AvalonFingerprint%2C%0A%20%20%20%20%20%20%20%20Callable%2C%0A%20%20%20%20%20%20%20%20Chem%2C%0A%20%20%20%20%20%20%20%20CombineMols%2C%0A%20%20%20%20%20%20%20%20ConformerGenerator%2C%0A%20%20%20%20%20%20%20%20DataStructs%2C%0A%20%20%20%20%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20LinearSegmentedColormap%2C%0A%20%20%20%20%20%20%20%20MACCSFingerprint%2C%0A%20%20%20%20%20%20%20%20MQNsFingerprint%2C%0A%20%20%20%20%20%20%20%20MolFromSmilesTransformer%2C%0A%20%20%20%20%20%20%20%20MordredFingerprint%2C%0A%20%20%20%20%20%20%20%20Optional%2C%0A%20%20%20%20%20%20%20%20PCA%2C%0A%20%20%20%20%20%20%20%20Path%2C%0A%20%20%20%20%20%20%20%20PubChemFingerprint%2C%0A%20%20%20%20%20%20%20%20RDKitFingerprint%2C%0A%20%20%20%20%20%20%20%20TSNE%2C%0A%20%20%20%20%20%20%20%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20%20%20%20%20UMAP%2C%0A%20%20%20%20%20%20%20%20alt%2C%0A%20%20%20%20%20%20%20%20base64%2C%0A%20%20%20%20%20%20%20%20gaussian_kde%2C%0A%20%20%20%20%20%20%20%20mo%2C%0A%20%20%20%20%20%20%20%20np%2C%0A%20%20%20%20%20%20%20%20pl%2C%0A%20%20%20%20%20%20%20%20plt%2C%0A%20%20%20%20%20%20%20%20rdDepictor%2C%0A%20%20%20%20%20%20%20%20rdMolDraw2D%2C%0A%20%20%20%20%20%20%20%20venn2%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20AtomPairFingerprint%2C%0A%20%20%20%20AvalonFingerprint%2C%0A%20%20%20%20ConformerGenerator%2C%0A%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20MACCSFingerprint%2C%0A%20%20%20%20MQNsFingerprint%2C%0A%20%20%20%20MolFromSmilesTransformer%2C%0A%20%20%20%20MordredFingerprint%2C%0A%20%20%20%20PubChemFingerprint%2C%0A%20%20%20%20RDKitFingerprint%2C%0A%20%20%20%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20pl%2C%0A)%3A%0A%20%20%20%20_fp_dict%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22ecfp%22%3A%20%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20%22morgan%22%3A%20%20%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20%22maccs%22%3A%20%20%20%20MACCSFingerprint%2C%0A%20%20%20%20%20%20%20%20%22torsion%22%3A%20%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20%20%20%20%20%22rdkit%22%3A%20%20%20%20RDKitFingerprint%2C%0A%20%20%20%20%20%20%20%20%22atompair%22%3A%20AtomPairFingerprint%2C%0A%20%20%20%20%20%20%20%20%22avalon%22%3A%20%20%20AvalonFingerprint%2C%0A%20%20%20%20%20%20%20%20%22mordred%22%3A%20%20MordredFingerprint%2C%0A%20%20%20%20%20%20%20%20%22mqn%22%3A%20%20%20%20%20%20MQNsFingerprint%2C%0A%20%20%20%20%20%20%20%20%22pubchem%22%3A%20%20PubChemFingerprint%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20generate_fingerprint(df%3A%20pl.DataFrame%2C%20fingerprint_type%3A%20str%2C%20**kwargs)%20-%3E%20pl.DataFrame%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Generate%20molecular%20fingerprints%20and%20add%20them%20as%20a%20column%20to%20the%20DataFrame.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20containing%20a%20%22smiles%22%20column.%0A%20%20%20%20%20%20%20%20%20%20%20%20fingerprint_type%3A%20One%20of%20the%20supported%20fingerprint%20keys.%0A%20%20%20%20%20%20%20%20%20%20%20%20**kwargs%3A%20Extra%20arguments%20forwarded%20to%20the%20skfp%20fingerprint%20class.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20DataFrame%20with%20an%20added%20column%20named%20after%20fingerprint_type.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20fingerprint_type%20not%20in%20_fp_dict%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Fingerprint%20type%20not%20recognised%3A%20%7Bfingerprint_type!r%7D.%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Valid%20values%3A%20%7Blist(_fp_dict.keys())%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20fp_func%20%3D%20_fp_dict%5Bfingerprint_type%5D(**kwargs)%20if%20kwargs%20else%20_fp_dict%5Bfingerprint_type%5D()%0A%20%20%20%20%20%20%20%20if%20fp_func.requires_conformers%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mol_from_smiles%20%3D%20MolFromSmilesTransformer()%0A%20%20%20%20%20%20%20%20%20%20%20%20conf_gen%20%3D%20ConformerGenerator()%0A%20%20%20%20%20%20%20%20%20%20%20%20mols_list%20%3D%20mol_from_smiles.transform(df.get_column(%22smiles%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20mols_list%20%3D%20conf_gen.transform(mols_list)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mols_list%20%3D%20df.get_column(%22smiles%22)%0A%20%20%20%20%20%20%20%20fps%20%3D%20fp_func.transform(mols_list)%0A%20%20%20%20%20%20%20%20return%20df.with_columns(pl.Series(values%3Dfps%2C%20name%3Dfingerprint_type))%0A%0A%20%20%20%20return%20(generate_fingerprint%2C)%0A%0A%0A%40app.cell%0Adef%20_(Callable%2C%20DataStructs%2C%20generate_fingerprint%2C%20np%2C%20pl)%3A%0A%20%20%20%20_METRIC_FNS%3A%20dict%5Bstr%2C%20Callable%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22tanimoto%22%3A%20DataStructs.BulkTanimotoSimilarity%2C%0A%20%20%20%20%20%20%20%20%22dice%22%3A%20%20%20%20%20DataStructs.BulkDiceSimilarity%2C%0A%20%20%20%20%20%20%20%20%22cosine%22%3A%20%20%20DataStructs.BulkCosineSimilarity%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20_row_to_bitvect(row%3A%20np.ndarray)%20-%3E%20DataStructs.ExplicitBitVect%3A%0A%20%20%20%20%20%20%20%20%22%22%22Convert%20a%20binary%20uint8%20numpy%20fingerprint%20row%20to%20an%20RDKit%20ExplicitBitVect.%22%22%22%0A%20%20%20%20%20%20%20%20bv%20%3D%20DataStructs.ExplicitBitVect(int(row.shape%5B0%5D))%0A%20%20%20%20%20%20%20%20for%20bit%20in%20np.where(row%20%3E%200)%5B0%5D.tolist()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20bv.SetBit(bit)%0A%20%20%20%20%20%20%20%20return%20bv%0A%0A%20%20%20%20def%20compute_pairwise_similarities(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20id_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20fingerprint%3A%20str%2C%0A%20%20%20%20%20%20%20%20metric%3A%20str%2C%0A%20%20%20%20%20%20%20%20**fp_kwargs%2C%0A%20%20%20%20)%20-%3E%20pl.DataFrame%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Compute%20pairwise%20molecular%20similarities%20for%20all%20unique%20compound%20pairs.%0A%0A%20%20%20%20%20%20%20%20Each%20unique%20(i%2C%20j)%20pair%20with%20i%20%3C%20j%20is%20evaluated%20once%20using%20RDKit%20bulk%20ops.%0A%20%20%20%20%20%20%20%20Fingerprints%20are%20generated%20on%20demand%20if%20absent.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20molecule%20data%20and%20a%20%22smiles%22%20column.%0A%20%20%20%20%20%20%20%20%20%20%20%20id_col%3A%20Column%20name%20with%20unique%20molecule%20identifiers.%0A%20%20%20%20%20%20%20%20%20%20%20%20fingerprint%3A%20Fingerprint%20type%20accepted%20by%20generate_fingerprint().%0A%20%20%20%20%20%20%20%20%20%20%20%20metric%3A%20One%20of%20%22tanimoto%22%2C%20%22dice%22%2C%20or%20%22cosine%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20**fp_kwargs%3A%20Extra%20arguments%20forwarded%20to%20the%20fingerprint%20constructor.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Polars%20DataFrame%20with%20columns%20ID1%2C%20ID2%2C%20fingerprint%2C%20metric%2C%20similarity.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20metric%20not%20in%20_METRIC_FNS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22metric%20must%20be%20one%20of%20%7Blist(_METRIC_FNS.keys())!r%7D%2C%20got%20%7Bmetric!r%7D%22)%0A%0A%20%20%20%20%20%20%20%20if%20fingerprint%20not%20in%20df.columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20generate_fingerprint(df%2C%20fingerprint%2C%20**fp_kwargs)%0A%0A%20%20%20%20%20%20%20%20fp_array%20%3D%20np.vstack(df%5Bfingerprint%5D.to_list())%0A%20%20%20%20%20%20%20%20ids%20%3D%20df%5Bid_col%5D.cast(pl.Utf8).to_list()%0A%20%20%20%20%20%20%20%20n%20%3D%20len(ids)%0A%20%20%20%20%20%20%20%20bitvects%20%3D%20%5B_row_to_bitvect(fp_array%5Bi%5D)%20for%20i%20in%20range(n)%5D%0A%20%20%20%20%20%20%20%20bulk_fn%20%3D%20_METRIC_FNS%5Bmetric%5D%0A%0A%20%20%20%20%20%20%20%20id1_list%2C%20id2_list%2C%20sim_list%20%3D%20%5B%5D%2C%20%5B%5D%2C%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(n%20-%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20sims%20%3D%20bulk_fn(bitvects%5Bi%5D%2C%20bitvects%5Bi%20%2B%201%3A%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20id1_list.extend(%5Bids%5Bi%5D%5D%20*%20len(sims))%0A%20%20%20%20%20%20%20%20%20%20%20%20id2_list.extend(ids%5Bi%20%2B%201%3A%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20sim_list.extend(sims)%0A%0A%20%20%20%20%20%20%20%20return%20pl.DataFrame(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ID1%22%3A%20%20%20%20%20%20%20%20%20id1_list%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ID2%22%3A%20%20%20%20%20%20%20%20%20id2_list%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22fingerprint%22%3A%20pl.Series(%5Bfingerprint%5D%20*%20len(sim_list)%2C%20dtype%3Dpl.Utf8)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22metric%22%3A%20%20%20%20%20%20pl.Series(%5Bmetric%5D%20%20%20%20%20%20*%20len(sim_list)%2C%20dtype%3Dpl.Utf8)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22similarity%22%3A%20%20pl.Series(sim_list%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dtype%3Dpl.Float32)%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20return%20(compute_pairwise_similarities%2C)%0A%0A%0A%40app.cell%0Adef%20_(Optional%2C%20Path%2C%20gaussian_kde%2C%20np%2C%20pl%2C%20plt)%3A%0A%20%20%20%20def%20plot_similarity_distributions(%0A%20%20%20%20%20%20%20%20sim_df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20group_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20group_order%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20colors%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20title%3A%20str%2C%0A%20%20%20%20%20%20%20%20x_label%3A%20str%20%3D%20%22Tanimoto%20similarity%22%2C%0A%20%20%20%20%20%20%20%20save_path%3A%20Optional%5Bstr%20%7C%20Path%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20figsize%3A%20tuple%5Bfloat%2C%20float%5D%20%3D%20(6%2C%205)%2C%0A%20%20%20%20%20%20%20%20dpi%3A%20int%20%3D%20300%2C%0A%20%20%20%20)%20-%3E%20%22matplotlib.figure.Figure%22%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Plot%20overlapping%20KDE%20curves%20for%20pairwise%20Tanimoto%20similarity%20distributions.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20sim_df%3A%20Polars%20DataFrame%20with%20a%20%22similarity%22%20column%20and%20group_col.%0A%20%20%20%20%20%20%20%20%20%20%20%20group_col%3A%20Column%20used%20to%20split%20into%20separate%20curves.%0A%20%20%20%20%20%20%20%20%20%20%20%20group_order%3A%20Ordered%20list%20of%20group%20labels.%0A%20%20%20%20%20%20%20%20%20%20%20%20colors%3A%20Hex%2Fnamed%20colours%2C%20one%20per%20group.%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3A%20Plot%20title.%0A%20%20%20%20%20%20%20%20%20%20%20%20x_label%3A%20x-axis%20label.%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20If%20given%2C%20saves%20the%20figure%20here.%0A%20%20%20%20%20%20%20%20%20%20%20%20figsize%3A%20Figure%20size%20in%20inches.%0A%20%20%20%20%20%20%20%20%20%20%20%20dpi%3A%20Resolution%20for%20raster%20output.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20matplotlib.figure.Figure.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20_x%20%3D%20np.linspace(0%2C%201%2C%20500)%0A%20%20%20%20%20%20%20%20with%20plt.style.context(%22seaborn-v0_8-whitegrid%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(figsize%3Dfigsize%2C%20dpi%3Ddpi)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20group%2C%20color%20in%20zip(group_order%2C%20colors)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20vals%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sim_df.filter(pl.col(group_col)%20%3D%3D%20group)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.get_column(%22similarity%22).to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20len(vals)%20%3C%202%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kde%20%3D%20gaussian_kde(vals%2C%20bw_method%3D%22scott%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.plot(_x%2C%20kde(_x)%2C%20color%3Dcolor%2C%20linewidth%3D2%2C%20label%3Dgroup)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.fill_between(_x%2C%20kde(_x)%2C%20alpha%3D0.15%2C%20color%3Dcolor)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlabel(x_label%2C%20fontsize%3D12)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_ylabel(%22Density%22%2C%20fontsize%3D12)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlim(0%2C%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.legend(fontsize%3D11%2C%20frameon%3DTrue%2C%20framealpha%3D0.9%2C%20edgecolor%3D%220.8%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(title%2C%20fontsize%3D13)%0A%20%20%20%20%20%20%20%20%20%20%20%20fig.tight_layout()%0A%20%20%20%20%20%20%20%20if%20save_path%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fig.savefig(Path(save_path)%2C%20dpi%3Ddpi%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20%20%20%20%20return%20fig%0A%0A%20%20%20%20return%20(plot_similarity_distributions%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20LinearSegmentedColormap%2C%0A%20%20%20%20Optional%2C%0A%20%20%20%20PCA%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20TSNE%2C%0A%20%20%20%20UMAP%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20plt%2C%0A)%3A%0A%20%20%20%20def%20generate_embedding_plot_continuous(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20x_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20y_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20color_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20fp_column%3A%20str%20%3D%20%22ecfp%22%2C%0A%20%20%20%20%20%20%20%20method%3A%20str%20%3D%20%22umap%22%2C%0A%20%20%20%20%20%20%20%20umap_metric%3A%20str%20%3D%20%22euclidean%22%2C%0A%20%20%20%20%20%20%20%20title%3A%20Optional%5Bstr%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20save_path%3A%20Optional%5Bstr%20%7C%20Path%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20dpi%3A%20int%20%3D%20300%2C%0A%20%20%20%20%20%20%20%20figsize%3A%20tuple%5Bfloat%2C%20float%5D%20%3D%20(8.0%2C%207.0)%2C%0A%20%20%20%20%20%20%20%20point_size%3A%20float%20%3D%2018.0%2C%0A%20%20%20%20%20%20%20%20alpha%3A%20float%20%3D%200.8%2C%0A%20%20%20%20%20%20%20%20cmap%3A%20str%20%3D%20%22viridis%22%2C%0A%20%20%20%20)%20-%3E%20%22matplotlib.figure.Figure%22%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Matplotlib%20scatter%20for%20a%20continuous%20colour%20column%20%E2%80%94%20recomputes%20embedding%20if%20absent.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20DataFrame%20with%20molecule%20data.%0A%20%20%20%20%20%20%20%20%20%20%20%20x_col%20%2F%20y_col%3A%20Embedding%20coordinate%20columns%20(computed%20if%20absent).%0A%20%20%20%20%20%20%20%20%20%20%20%20color_col%3A%20Numeric%20column%20for%20colour%20encoding.%0A%20%20%20%20%20%20%20%20%20%20%20%20fp_column%3A%20Fingerprint%20column%20(ECFP%20by%20default).%0A%20%20%20%20%20%20%20%20%20%20%20%20method%3A%20%22umap%22%20or%20%22tsne%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20umap_metric%3A%20Distance%20metric%20passed%20to%20UMAP.%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3A%20Optional%20plot%20title.%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20If%20given%2C%20saves%20the%20figure%20here.%0A%20%20%20%20%20%20%20%20%20%20%20%20dpi%3A%20Resolution%20for%20raster%20output.%0A%20%20%20%20%20%20%20%20%20%20%20%20figsize%3A%20Figure%20size%20in%20inches.%0A%20%20%20%20%20%20%20%20%20%20%20%20point_size%3A%20Marker%20area%20in%20points%C2%B2.%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3A%20Marker%20opacity.%0A%20%20%20%20%20%20%20%20%20%20%20%20cmap%3A%20Matplotlib%20colourmap%20name%20or%20%22ryg%22.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20matplotlib.figure.Figure.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20if%20fp_column%20not%20in%20df.columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fp_func%20%3D%20ECFPFingerprint(fp_size%3D4096%2C%20radius%3D3%2C%20include_chirality%3DTrue%2C%20count%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20fps%20%3D%20fp_func.transform(df.get_column(%22smiles%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20df.with_columns(pl.Series(values%3Dfps%2C%20name%3Dfp_column))%0A%0A%20%20%20%20%20%20%20%20fp_array%20%3D%20np.vstack(df%5Bfp_column%5D.to_list())%0A%20%20%20%20%20%20%20%20n_samples%20%3D%20fp_array.shape%5B0%5D%0A%0A%20%20%20%20%20%20%20%20if%20x_col%20not%20in%20df.columns%20or%20y_col%20not%20in%20df.columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20method%20%3D%3D%20%22umap%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20umap%20%3D%20UMAP(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_components%3D2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_neighbors%3Dmin(15%2C%20n_samples%20-%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_dist%3D0.1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20metric%3Dumap_metric%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3D42%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20coords%20%3D%20umap.fit_transform(fp_array)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_pca%20%3D%20min(50%2C%20n_samples%20-%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pca%20%3D%20PCA(n_components%3Dn_pca%2C%20random_state%3D42)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20coords_pca%20%3D%20pca.fit_transform(fp_array)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tsne%20%3D%20TSNE(n_components%3D2%2C%20random_state%3D42%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20perplexity%3Dmin(30.0%2C%20float(n_samples%20-%201))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20init%3D%22pca%22%2C%20learning_rate%3D%22auto%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20coords%20%3D%20tsne.fit_transform(coords_pca)%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20df.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.Series(name%3Dx_col%2C%20values%3Dcoords%5B%3A%2C%200%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.Series(name%3Dy_col%2C%20values%3Dcoords%5B%3A%2C%201%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20x%20%3D%20df%5Bx_col%5D.to_numpy()%0A%20%20%20%20%20%20%20%20y%20%3D%20df%5By_col%5D.to_numpy()%0A%20%20%20%20%20%20%20%20values%20%3D%20df%5Bcolor_col%5D.to_numpy().astype(float)%0A%0A%20%20%20%20%20%20%20%20_cmap%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20LinearSegmentedColormap.from_list(%22ryg%22%2C%20%5B%22%23d73027%22%2C%20%22%23fee08b%22%2C%20%22%231a9850%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cmap%20%3D%3D%20%22ryg%22%20else%20cmap%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20with%20plt.style.context(%22seaborn-v0_8-whitegrid%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(figsize%3Dfigsize%2C%20dpi%3Ddpi)%0A%20%20%20%20%20%20%20%20%20%20%20%20sc%20%3D%20ax.scatter(x%2C%20y%2C%20c%3Dvalues%2C%20cmap%3D_cmap%2C%20s%3Dpoint_size%2C%20alpha%3Dalpha%2C%20linewidths%3D0)%0A%20%20%20%20%20%20%20%20%20%20%20%20cbar%20%3D%20fig.colorbar(sc%2C%20ax%3Dax%2C%20pad%3D0.02)%0A%20%20%20%20%20%20%20%20%20%20%20%20cbar.set_label(color_col%2C%20fontsize%3D12)%0A%20%20%20%20%20%20%20%20%20%20%20%20cbar.ax.tick_params(labelsize%3D10)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlabel(%22UMAP%201%22%20if%20method%20%3D%3D%20%22umap%22%20else%20%22t-SNE%201%22%2C%20fontsize%3D14%2C%20labelpad%3D8)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_ylabel(%22UMAP%202%22%20if%20method%20%3D%3D%20%22umap%22%20else%20%22t-SNE%202%22%2C%20fontsize%3D14%2C%20labelpad%3D8)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.tick_params(axis%3D%22both%22%2C%20labelsize%3D11)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20title%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(title%2C%20fontsize%3D16%2C%20pad%3D12)%0A%20%20%20%20%20%20%20%20%20%20%20%20fig.tight_layout()%0A%0A%20%20%20%20%20%20%20%20if%20save_path%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fig.savefig(Path(save_path)%2C%20dpi%3Ddpi%2C%20bbox_inches%3D%22tight%22)%0A%0A%20%20%20%20%20%20%20%20return%20fig%0A%0A%20%20%20%20return%20(generate_embedding_plot_continuous%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Load%20data%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl)%3A%0A%20%20%20%20all_compounds%20%3D%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%0A%20%20%20%20mmp_raw%20%3D%20pl.read_csv(%0A%20%20%20%20%20%20%20%20%22..%2Fdata%2Fprocessed%2Fall_compounds_mmp.mmp.csv.gz%22%2C%0A%20%20%20%20%20%20%20%20separator%3D%22%5Ct%22%2C%0A%20%20%20%20%20%20%20%20has_header%3DFalse%2C%0A%20%20%20%20%20%20%20%20new_columns%3D%5B%22smiles1%22%2C%20%22smiles2%22%2C%20%22ID1%22%2C%20%22ID2%22%2C%20%22transform%22%2C%20%22core%22%5D%2C%0A%20%20%20%20)%0A%20%20%20%20all_compounds%0A%20%20%20%20return%20all_compounds%2C%20mmp_raw%0A%0A%0A%40app.cell%0Adef%20_(Chem%2C%20mmp_raw%2C%20pl)%3A%0A%20%20%20%20_n_ha1%2C%20_n_ha2%2C%20_n_ha_core%2C%20_n_ha_frag1%2C%20_n_ha_frag2%20%3D%20%5B%5D%2C%20%5B%5D%2C%20%5B%5D%2C%20%5B%5D%2C%20%5B%5D%0A%20%20%20%20for%20_s1%2C%20_s2%2C%20_core%2C%20_transform%20in%20mmp_raw.select(%0A%20%20%20%20%20%20%20%20%5B%22smiles1%22%2C%20%22smiles2%22%2C%20%22core%22%2C%20%22transform%22%5D%0A%20%20%20%20).iter_rows()%3A%0A%20%20%20%20%20%20%20%20_lhs%2C%20_rhs%20%3D%20_transform.split(%22%3E%3E%22)%0A%20%20%20%20%20%20%20%20_m_lhs%20%3D%20Chem.MolFromSmiles(_lhs)%0A%20%20%20%20%20%20%20%20_m_rhs%20%3D%20Chem.MolFromSmiles(_rhs)%0A%20%20%20%20%20%20%20%20_n_ha1.append(Chem.MolFromSmiles(_s1).GetNumHeavyAtoms())%0A%20%20%20%20%20%20%20%20_n_ha2.append(Chem.MolFromSmiles(_s2).GetNumHeavyAtoms())%0A%20%20%20%20%20%20%20%20_n_ha_core.append(Chem.MolFromSmiles(_core).GetNumHeavyAtoms())%0A%20%20%20%20%20%20%20%20_n_ha_frag1.append(_m_lhs.GetNumHeavyAtoms()%20if%20_m_lhs%20else%20None)%0A%20%20%20%20%20%20%20%20_n_ha_frag2.append(_m_rhs.GetNumHeavyAtoms()%20if%20_m_rhs%20else%20None)%0A%0A%20%20%20%20mmp_df_filtered%20%3D%20(%0A%20%20%20%20%20%20%20%20mmp_raw%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.Series(%22n_ha1%22%2C%20%20%20%20%20_n_ha1%2C%20%20%20%20%20dtype%3Dpl.Int32)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.Series(%22n_ha2%22%2C%20%20%20%20%20_n_ha2%2C%20%20%20%20%20dtype%3Dpl.Int32)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.Series(%22n_ha_core%22%2C%20_n_ha_core%2C%20dtype%3Dpl.Int32)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.Series(%22n_ha_frag1%22%2C_n_ha_frag1%2Cdtype%3Dpl.Int32)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.Series(%22n_ha_frag2%22%2C_n_ha_frag2%2Cdtype%3Dpl.Int32)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20(abs(pl.col(%22n_ha_frag1%22)%20-%20pl.col(%22n_ha_frag2%22))).alias(%22size_diff_transform%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.max_horizontal(%22n_ha_frag1%22%2C%20%22n_ha_frag2%22)%20%2F%20pl.col(%22n_ha_core%22)).alias(%22core_transform_ratio%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22core_transform_ratio%22)%20%3C%201.0)%0A%20%20%20%20)%0A%20%20%20%20mmp_df_filtered%0A%20%20%20%20return%20(mmp_df_filtered%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20UMAP%20of%20dose-response%20compounds%20coloured%20by%20pEC50%0A%0A%20%20%20%20This%20plot%20uses%20only%20the%20compounds%20tested%20in%20the%20dose-response%20assay.%0A%20%20%20%20The%20embedding%20is%20recomputed%20on%20this%20subset%20alone%20(different%20from%20the%20full-%0A%20%20%20%20dataset%20embedding%20in%20notebook%201b)%20to%20maximise%20structural%20resolution%20within%0A%20%20%20%20the%20active%20chemical%20space.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(all_compounds%2C%20generate_embedding_plot_continuous%2C%20pl)%3A%0A%20%20%20%20_dr_df%20%3D%20(%0A%20%20%20%20%20%20%20%20all_compounds%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.drop(%5B%22UMAP_x%22%2C%20%22UMAP_y%22%2C%20%22TSNE_x%22%2C%20%22TSNE_y%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20strict%3DFalse)%20%20%20%23%20drop%20pre-computed%20coords%20if%20present%0A%20%20%20%20)%0A%0A%20%20%20%20generate_embedding_plot_continuous(%0A%20%20%20%20%20%20%20%20_dr_df%2C%0A%20%20%20%20%20%20%20%20x_col%3D%22UMAP_x%22%2C%0A%20%20%20%20%20%20%20%20y_col%3D%22UMAP_y%22%2C%0A%20%20%20%20%20%20%20%20color_col%3D%22pEC50_dr%22%2C%0A%20%20%20%20%20%20%20%20method%3D%22umap%22%2C%0A%20%20%20%20%20%20%20%20umap_metric%3D%22jaccard%22%2C%0A%20%20%20%20%20%20%20%20title%3D%22UMAP%20Chemical%20Space%20%E2%80%94%20Training%20pEC50%20(dose-response)%22%2C%0A%20%20%20%20%20%20%20%20point_size%3D20%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.85%2C%0A%20%20%20%20%20%20%20%20cmap%3D%22viridis%22%2C%0A%20%20%20%20%20%20%20%20save_path%3D%22..%2Fplots%2F1_sar_exploration%2Fumap_pec50_chemical_space.png%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Similar%20to%20the%20plot%20on%20the%20whole%20chemical%20space%2C%20most%20low%20active%20compounds%20tend%20to%0A%20%20%20%20cluster%20together.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20MMP-based%20activity%20cliffs%0A%0A%20%20%20%20One%20approach%20to%20identifying%20ACs%20is%20to%20use%20MMPs%3A%20we%20keep%20pairs%20from%20the%0A%20%20%20%20filtered%20MMP%20table%20where%20both%20compounds%20were%20tested%20in%20the%20dose-response%20assay%0A%20%20%20%20and%20their%20pEC50%20values%20differ%20by%20**%E2%89%A5%202%20log%20units**.%0A%0A%20%20%20%20This%20dataset%20has%20low%20MMP%20propensity%20(high%20chemical%20diversity)%2C%20so%20the%20number%0A%20%20%20%20of%20MMP%20cliffs%20is%20small.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(all_compounds%2C%20mmp_df_filtered%2C%20pl)%3A%0A%20%20%20%20_pec50%20%3D%20(%0A%20%20%20%20%20%20%20%20all_compounds%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%2C%20%22pEC50_dr%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20mmp_activity_cliffs%20%3D%20(%0A%20%20%20%20%20%20%20%20mmp_df_filtered%0A%20%20%20%20%20%20%20%20.join(_pec50.rename(%7B%22inchikey%22%3A%20%22ID1%22%2C%20%22pEC50_dr%22%3A%20%22pEC50_1%22%7D)%2C%20on%3D%22ID1%22%2C%20how%3D%22inner%22)%0A%20%20%20%20%20%20%20%20.join(_pec50.rename(%7B%22inchikey%22%3A%20%22ID2%22%2C%20%22pEC50_dr%22%3A%20%22pEC50_2%22%7D)%2C%20on%3D%22ID2%22%2C%20how%3D%22inner%22)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22pEC50_1%22)%20-%20pl.col(%22pEC50_2%22)).abs().alias(%22delta_pEC50%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22delta_pEC50%22)%20%3E%3D%202.0)%0A%20%20%20%20%20%20%20%20.sort(%22delta_pEC50%22%2C%20descending%3DTrue)%0A%20%20%20%20)%0A%0A%20%20%20%20mmp_activity_cliffs%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Fingerprint-based%20activity%20cliffs%0A%0A%20%20%20%20An%20alternative%20approach%20defines%20a%20similarity%20threshold%20to%20consider%20a%20pair%20of%0A%20%20%20%20compounds%20%22similar%22.%20%20The%20threshold%20depends%20on%20the%20fingerprint%20and%20metric%2C%20so%20whenever%0A%20%20%20%20someone%20says%20a%20Tanimoto%20similarity%20value%20without%20saying%20the%20fingerprint%20is%20providing%20useless%0A%20%20%20%20information.%20Based%20on%20rule%20of%20thumb%20and%20previous%20experience%2C%20I%20generally%20use%20the%20following%0A%20%20%20%20thresholds%20to%20define%20similar%20compounds%3A%0A%0A%20%20%20%20-%20MACCS%20%2B%20Tanimoto%20%E2%86%92%20threshold%20%E2%89%88%200.8%0A%20%20%20%20-%20ECFP4%20%2B%20Tanimoto%20%E2%86%92%20threshold%20%E2%89%88%200.4%0A%0A%20%20%20%20First%2C%20we%20compare%20the%20pairwise%20similarity%20distributions%20of%20three%20fingerprints%0A%20%20%20%20across%20all%20dose-response%20training%20compounds.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20all_compounds%2C%0A%20%20%20%20compute_pairwise_similarities%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20plot_similarity_distributions%2C%0A)%3A%0A%20%20%20%20_dr_df%20%3D%20(%0A%20%20%20%20%20%20%20%20all_compounds%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.unique(subset%3D%5B%22inchikey%22%5D)%0A%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%2C%20%22smiles%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20_sim_maccs%20%3D%20compute_pairwise_similarities(%0A%20%20%20%20%20%20%20%20_dr_df%2C%20id_col%3D%22inchikey%22%2C%20fingerprint%3D%22maccs%22%2C%20metric%3D%22tanimoto%22%2C%0A%20%20%20%20)%0A%20%20%20%20_sim_ecfp4_1k%20%3D%20compute_pairwise_similarities(%0A%20%20%20%20%20%20%20%20_dr_df%2C%20id_col%3D%22inchikey%22%2C%20fingerprint%3D%22ecfp%22%2C%20metric%3D%22tanimoto%22%2C%0A%20%20%20%20%20%20%20%20fp_size%3D1024%2C%20radius%3D2%2C%20include_chirality%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20_sim_ecfp4_4k%20%3D%20compute_pairwise_similarities(%0A%20%20%20%20%20%20%20%20_dr_df%2C%20id_col%3D%22inchikey%22%2C%20fingerprint%3D%22ecfp%22%2C%20metric%3D%22tanimoto%22%2C%0A%20%20%20%20%20%20%20%20fp_size%3D4096%2C%20radius%3D2%2C%20include_chirality%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20_sim_ecfp6%20%3D%20compute_pairwise_similarities(%0A%20%20%20%20%20%20%20%20_dr_df%2C%20id_col%3D%22inchikey%22%2C%20fingerprint%3D%22ecfp%22%2C%20metric%3D%22tanimoto%22%2C%0A%20%20%20%20%20%20%20%20fp_size%3D4096%2C%20radius%3D3%2C%20include_chirality%3DTrue%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Relabel%20ECFP%20variants%20by%20their%20common%20names%0A%20%20%20%20_sim_ecfp4_1k%20%3D%20_sim_ecfp4_1k.with_columns(pl.lit(%22ecfp4_1k%22).alias(%22fingerprint%22))%0A%20%20%20%20_sim_ecfp4_4k%20%3D%20_sim_ecfp4_4k.with_columns(pl.lit(%22ecfp4_4k%22).alias(%22fingerprint%22))%0A%20%20%20%20_sim_ecfp6%20%3D%20_sim_ecfp6.with_columns(pl.lit(%22ecfp6%22).alias(%22fingerprint%22))%0A%0A%20%20%20%20pairwise_similarities%20%3D%20pl.concat(%5B_sim_maccs%2C%20_sim_ecfp4_1k%2C%20_sim_ecfp4_4k%2C%20_sim_ecfp6%5D)%0A%0A%20%20%20%20plot_similarity_distributions(%0A%20%20%20%20%20%20%20%20pairwise_similarities%2C%0A%20%20%20%20%20%20%20%20group_col%3D%22fingerprint%22%2C%0A%20%20%20%20%20%20%20%20group_order%3D%5B%22maccs%22%2C%20%22ecfp4_1k%22%2C%20%22ecfp4_4k%22%2C%20%22ecfp6%22%5D%2C%0A%20%20%20%20%20%20%20%20colors%3D%5B%22%234e79a7%22%2C%20%22%23f28e2b%22%2C%20%22%23e15759%22%2C%20%22%2359a14f%22%5D%2C%0A%20%20%20%20%20%20%20%20title%3D%22Pairwise%20similarity%20distributions%20%E2%80%94%20dose-response%20compounds%22%2C%0A%20%20%20%20%20%20%20%20save_path%3D%22..%2Fplots%2F1_sar_exploration%2Fdensity_sim_fingerprints.png%22%2C%0A%20%20%20%20)%0A%20%20%20%20return%20(pairwise_similarities%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20This%20plot%20demonstrates%20how%20different%20the%20similarity%20value%20distributions%20can%20be%20for%20different%0A%20%20%20%20fingerprints.%0A%20%20%20%20ECFP6%20generally%20has%20lower%20similarity%20values.%0A%20%20%20%20Even%20small%20changes%20such%20as%20the%20bit%20size%20for%20ECFP4%20(1024%20vs%204096)%20makes%20a%20difference%2C%20although%20a%20small%20one.%0A%20%20%20%20It%20is%20always%20useful%20to%20check%20the%20similarity%20distribution%20within%20a%20dataset%20and%20decide%20if%0A%20%20%20%20you%20need%20to%20adapt%20those%20rule%20of%20thumb%20thresholds.%0A%20%20%20%20For%20the%20analysis%20below%20I%20decided%20to%20keep%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20pairwise_similarities%2C%20pl)%3A%0A%20%20%20%20%22%22%22Summary%20statistics%20for%20each%20fingerprint%20distribution.%22%22%22%0A%20%20%20%20_fp_order%20%3D%20%5B%22maccs%22%2C%20%22ecfp4_1k%22%2C%20%22ecfp4_4k%22%2C%20%22ecfp6%22%5D%0A%20%20%20%20_rows%20%3D%20%5B%5D%0A%20%20%20%20for%20_fp%20in%20_fp_order%3A%0A%20%20%20%20%20%20%20%20_vals%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20pairwise_similarities.filter(pl.col(%22fingerprint%22)%20%3D%3D%20_fp)%0A%20%20%20%20%20%20%20%20%20%20%20%20.get_column(%22similarity%22).to_numpy()%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_rows.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22fingerprint%22%3A%20_fp.upper()%2C%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Q25%22%3A%20%20%20%20round(float(np.percentile(_vals%2C%2025))%2C%20%20%203)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22median%22%3A%20round(float(np.median(_vals))%2C%20%20%20%20%20%20%20%20%20%20%203)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Q75%22%3A%20%20%20%20round(float(np.percentile(_vals%2C%2075))%2C%20%20%203)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22p95%22%3A%20%20%20%20round(float(np.percentile(_vals%2C%2095))%2C%20%20%203)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22p99%22%3A%20%20%20%20round(float(np.percentile(_vals%2C%2099))%2C%20%20%203)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22p99.9%22%3A%20%20%20round(float(np.percentile(_vals%2C%2099.9))%2C%20%20%203)%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20pl.DataFrame(_rows)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Cliff%20count%20summary%20(MACCS%20%E2%89%A5%200.8%20and%20ECFP4%20%E2%89%A5%200.4%2C%20%7C%CE%94pEC50%7C%20%E2%89%A5%202)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(all_compounds%2C%20pairwise_similarities%2C%20pl)%3A%0A%20%20%20%20_pec50%20%3D%20(%0A%20%20%20%20%20%20%20%20all_compounds.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%2C%20%22pEC50_dr%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20_sim_with_delta%20%3D%20(%0A%20%20%20%20%20%20%20%20pairwise_similarities%0A%20%20%20%20%20%20%20%20.join(_pec50.rename(%7B%22inchikey%22%3A%20%22ID1%22%2C%20%22pEC50_dr%22%3A%20%22pEC50_1%22%7D)%2C%20on%3D%22ID1%22%2C%20how%3D%22inner%22)%0A%20%20%20%20%20%20%20%20.join(_pec50.rename(%7B%22inchikey%22%3A%20%22ID2%22%2C%20%22pEC50_dr%22%3A%20%22pEC50_2%22%7D)%2C%20on%3D%22ID2%22%2C%20how%3D%22inner%22)%0A%20%20%20%20%20%20%20%20.with_columns((pl.col(%22pEC50_1%22)%20-%20pl.col(%22pEC50_2%22)).abs().alias(%22delta_pEC50%22))%0A%20%20%20%20)%0A%0A%20%20%20%20_thresholds%20%3D%20%7B%22maccs%22%3A%200.8%2C%20%22ecfp4_1k%22%3A%200.4%2C%20%22ecfp4_4k%22%3A%200.4%7D%0A%20%20%20%20_rows%20%3D%20%5B%5D%0A%20%20%20%20for%20_fp%2C%20_sim_thresh%20in%20_thresholds.items()%3A%0A%20%20%20%20%20%20%20%20_total_similar%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20_sim_with_delta%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((pl.col(%22fingerprint%22)%20%3D%3D%20_fp)%20%26%20(pl.col(%22similarity%22)%20%3E%3D%20_sim_thresh))%0A%20%20%20%20%20%20%20%20%20%20%20%20.height%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_n_cliffs%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20_sim_with_delta%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22fingerprint%22)%20%3D%3D%20_fp)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20(pl.col(%22similarity%22)%20%3E%3D%20_sim_thresh)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20(pl.col(%22delta_pEC50%22)%20%3E%3D%202.0)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.height%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_rows.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22fingerprint%22%3A%20%20%20%20%20%20%20%20%20%20_fp.upper()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22similarity%20threshold%22%3A%20_sim_thresh%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22similar%20pairs%22%3A%20%20%20%20%20%20%20%20_total_similar%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22activity%20cliffs%22%3A%20%20%20%20%20%20_n_cliffs%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22cliff%20fraction%20(%25)%22%3A%20%20%20round(100%20*%20_n_cliffs%20%2F%20_total_similar%2C%201)%20if%20_total_similar%20%3E%200%20else%200.0%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20pl.DataFrame(_rows)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20all_compounds%2C%20pairwise_similarities%2C%20pl%2C%20plt%2C%20venn2)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Venn%20diagram%20comparing%20which%20compound%20pairs%20are%20flagged%20as%20activity%20cliffs%0A%20%20%20%20by%20MACCS%20(Tanimoto%20%E2%89%A5%200.8)%20vs%20ECFP4_1k%20(Tanimoto%20%E2%89%A5%200.4)%2C%20both%20requiring%0A%20%20%20%20%7C%CE%94pEC50%7C%20%E2%89%A5%202.%20%20Each%20pair%20is%20represented%20as%20a%20frozenset%20of%20its%20two%20InChIKeys%0A%20%20%20%20so%20that%20(A%2C%20B)%20and%20(B%2C%20A)%20collapse%20to%20the%20same%20element.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_DELTA_THRESH%20%3D%202.0%0A%0A%20%20%20%20_pec50%20%3D%20(%0A%20%20%20%20%20%20%20%20all_compounds.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%2C%20%22pEC50_dr%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20_sim_with_delta%20%3D%20(%0A%20%20%20%20%20%20%20%20pairwise_similarities%0A%20%20%20%20%20%20%20%20.join(_pec50.rename(%7B%22inchikey%22%3A%20%22ID1%22%2C%20%22pEC50_dr%22%3A%20%22pEC50_1%22%7D)%2C%20on%3D%22ID1%22%2C%20how%3D%22inner%22)%0A%20%20%20%20%20%20%20%20.join(_pec50.rename(%7B%22inchikey%22%3A%20%22ID2%22%2C%20%22pEC50_dr%22%3A%20%22pEC50_2%22%7D)%2C%20on%3D%22ID2%22%2C%20how%3D%22inner%22)%0A%20%20%20%20%20%20%20%20.with_columns((pl.col(%22pEC50_1%22)%20-%20pl.col(%22pEC50_2%22)).abs().alias(%22delta_pEC50%22))%0A%20%20%20%20)%0A%0A%20%20%20%20def%20_cliff_pairs(fp_name%3A%20str%2C%20sim_thresh%3A%20float)%20-%3E%20set%5Bfrozenset%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20the%20set%20of%20cliff%20pairs%20(as%20frozensets)%20for%20a%20given%20fingerprint.%22%22%22%0A%20%20%20%20%20%20%20%20rows%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20_sim_with_delta%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22fingerprint%22)%20%3D%3D%20fp_name)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20(pl.col(%22similarity%22)%20%3E%3D%20sim_thresh)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%26%20(pl.col(%22delta_pEC50%22)%20%3E%3D%20_DELTA_THRESH)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.select(%5B%22ID1%22%2C%20%22ID2%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rows()%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20return%20%7Bfrozenset(pair)%20for%20pair%20in%20rows%7D%0A%0A%20%20%20%20_maccs_cliffs%20%20%20%3D%20_cliff_pairs(%22maccs%22%2C%20%20%20%20%200.8)%0A%20%20%20%20_ecfp4_1k_cliffs%20%3D%20_cliff_pairs(%22ecfp4_1k%22%2C%200.4)%0A%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(5%2C%204))%0A%20%20%20%20venn2(%0A%20%20%20%20%20%20%20%20subsets%3D(_maccs_cliffs%2C%20_ecfp4_1k_cliffs)%2C%0A%20%20%20%20%20%20%20%20set_labels%3D(%22MACCS%20%E2%89%A5%200.8%22%2C%20%22ECFP4_1k%20%E2%89%A5%200.4%22)%2C%0A%20%20%20%20%20%20%20%20set_colors%3D(%22%234e79a7%22%2C%20%22%23f28e2b%22)%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.6%2C%0A%20%20%20%20%20%20%20%20ax%3D_ax%2C%0A%20%20%20%20)%0A%20%20%20%20_ax.set_title(%22Activity%20cliff%20overlap%20%E2%80%94%20MACCS%20vs%20ECFP4_1k%5Cn(%7C%CE%94pEC50%7C%20%E2%89%A5%202)%22)%0A%20%20%20%20_fig.tight_layout()%0A%20%20%20%20_fig.savefig(%0A%20%20%20%20%20%20%20%20Path(%22..%2Fplots%2F1_sar_exploration%2Fvenn_cliffs_maccs_ecfp4.png%22)%2C%0A%20%20%20%20%20%20%20%20dpi%3D150%2C%20bbox_inches%3D%22tight%22%2C%0A%20%20%20%20)%0A%20%20%20%20_fig%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20This%20also%20shows%20another%20complexity%20of%20activity%20cliff%20analysis%2C%20different%20fingerprints%0A%20%20%20%20will%20identify%20different%20ACs%2C%20as%20their%20definition%20of%20similarity%20can%20be%20very%20different.%0A%20%20%20%20You%20can%20look%20at%20consensus%20cliffs%2C%20ACs%20defined%20by%20more%20than%20one%20similarity%20threshold%2C%0A%20%20%20%20in%20our%20case%20that%20would%20be%2010%20out%20of%20cumulatively%20more%20than%20500%20ACs.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Activity%20cliff%20scatter%20plot%20(ECFP4%2C%20Tanimoto%20%E2%89%A5%200.4)%0A%0A%20%20%20%20Each%20point%20is%20an%20activity%20cliff%3A%20Tanimoto%20similarity%20%E2%89%A5%200.4%20(ECFP4)%20and%0A%20%20%20%20%7C%CE%94pEC50%7C%20%E2%89%A5%202%20log%20units.%20Hover%20over%20a%20point%20to%20see%20both%20molecules%20drawn%20side%0A%20%20%20%20by%20side.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Chem%2C%0A%20%20%20%20CombineMols%2C%0A%20%20%20%20all_compounds%2C%0A%20%20%20%20alt%2C%0A%20%20%20%20base64%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pairwise_similarities%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20rdDepictor%2C%0A%20%20%20%20rdMolDraw2D%2C%0A)%3A%0A%20%20%20%20def%20_pair_smiles_to_base64_png(smi1%3A%20str%2C%20smi2%3A%20str%2C%20width%3A%20int%20%3D%20300%2C%20height%3A%20int%20%3D%20150)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22Render%20two%20molecules%20side%20by%20side%20as%20a%20base64-encoded%20PNG%20data%20URI.%22%22%22%0A%20%20%20%20%20%20%20%20mol1%20%3D%20Chem.MolFromSmiles(smi1)%0A%20%20%20%20%20%20%20%20mol2%20%3D%20Chem.MolFromSmiles(smi2)%0A%20%20%20%20%20%20%20%20if%20mol1%20is%20None%20or%20mol2%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%22%22%0A%20%20%20%20%20%20%20%20combined%20%3D%20CombineMols(mol1%2C%20mol2)%0A%20%20%20%20%20%20%20%20rdDepictor.Compute2DCoords(combined)%0A%20%20%20%20%20%20%20%20drawer%20%3D%20rdMolDraw2D.MolDraw2DCairo(width%2C%20height)%0A%20%20%20%20%20%20%20%20drawer.DrawMolecule(combined)%0A%20%20%20%20%20%20%20%20drawer.FinishDrawing()%0A%20%20%20%20%20%20%20%20return%20f%22data%3Aimage%2Fpng%3Bbase64%2C%7Bbase64.b64encode(drawer.GetDrawingText()).decode('ascii')%7D%22%0A%0A%20%20%20%20_CLIFF_THRESHOLD%20%3D%202.0%0A%20%20%20%20_SIM_THRESHOLD%20%20%20%3D%200.4%0A%0A%20%20%20%20_pec50_smiles%20%3D%20(%0A%20%20%20%20%20%20%20%20all_compounds%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%2C%20%22smiles%22%2C%20%22pEC50_dr%22%2C%20%22molecule_names%22%5D)%0A%20%20%20%20%20%20%20%20.unique(subset%3D%5B%22inchikey%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20_ecfp4_cliffs%20%3D%20(%0A%20%20%20%20%20%20%20%20pairwise_similarities%0A%20%20%20%20%20%20%20%20.filter(%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22fingerprint%22)%20%3D%3D%20%22ecfp4_1k%22)%20%26%20(pl.col(%22similarity%22)%20%3E%3D%20_SIM_THRESHOLD)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.join(%0A%20%20%20%20%20%20%20%20%20%20%20%20_pec50_smiles.rename(%7B%22inchikey%22%3A%20%22ID1%22%2C%20%22pEC50_dr%22%3A%20%22pEC50_1%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smiles%22%3A%20%22smiles1%22%2C%20%22molecule_names%22%3A%20%22name1%22%7D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20on%3D%22ID1%22%2C%20how%3D%22inner%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.join(%0A%20%20%20%20%20%20%20%20%20%20%20%20_pec50_smiles.rename(%7B%22inchikey%22%3A%20%22ID2%22%2C%20%22pEC50_dr%22%3A%20%22pEC50_2%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smiles%22%3A%20%22smiles2%22%2C%20%22molecule_names%22%3A%20%22name2%22%7D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20on%3D%22ID2%22%2C%20how%3D%22inner%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns((pl.col(%22pEC50_1%22)%20-%20pl.col(%22pEC50_2%22)).abs().alias(%22delta_pEC50%22))%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22delta_pEC50%22)%20%3E%3D%20_CLIFF_THRESHOLD)%0A%20%20%20%20)%0A%0A%20%20%20%20cliff_plot_df%20%3D%20_ecfp4_cliffs.drop(%5B%22fingerprint%22%2C%20%22metric%22%5D).with_columns(%0A%20%20%20%20%20%20%20%20pl.max_horizontal(%22pEC50_1%22%2C%20%22pEC50_2%22).alias(%22max_pEC50%22)%0A%20%20%20%20)%0A%0A%20%20%20%20_sel%20%3D%20alt.selection_point(fields%3D%5B%22ID1%22%2C%20%22ID2%22%5D%2C%20name%3D%22cliff_sel%22%2C%20empty%3DFalse%2C%20on%3D%22mouseover%22%2C%20nearest%3DTrue%2C%20clear%3D%22mouseout%22)%0A%0A%20%20%20%20_scatter%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(cliff_plot_df)%0A%20%20%20%20%20%20%20%20.mark_circle(size%3D60%2C%20opacity%3D0.8)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3Dalt.X(%22similarity%3AQ%22%2C%20title%3D%22Tanimoto%20similarity%20(ECFP4)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(domain%3D%5B0.39%2C%201.0%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20axis%3Dalt.Axis(titleFontSize%3D13%2C%20labelFontSize%3D11))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3Dalt.Y(%22delta_pEC50%3AQ%22%2C%20title%3D%22%7C%CE%94pEC50%7C%20(dose-response)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(domain%3D%5B1.9%2C%204.0%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20axis%3Dalt.Axis(titleFontSize%3D13%2C%20labelFontSize%3D11))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dalt.condition(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_sel%2C%20alt.value(%22%23f5c518%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Color(%22max_pEC50%3AQ%22%2C%20scale%3Dalt.Scale(scheme%3D%22viridis%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(title%3D%22max%20pEC50%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20size%3Dalt.condition(_sel%2C%20alt.value(120)%2C%20alt.value(60))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20tooltip%3D%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22name1%3AN%22%2C%20%20%20%20%20%20%20title%3D%22Molecule%201%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22name2%3AN%22%2C%20%20%20%20%20%20%20title%3D%22Molecule%202%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22similarity%3AQ%22%2C%20%20title%3D%22Tanimoto%22%2C%20%20%20format%3D%22.3f%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22pEC50_1%3AQ%22%2C%20%20%20%20%20title%3D%22pEC50%20mol1%22%2C%20format%3D%22.2f%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22pEC50_2%3AQ%22%2C%20%20%20%20%20title%3D%22pEC50%20mol2%22%2C%20format%3D%22.2f%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22delta_pEC50%3AQ%22%2C%20title%3D%22%7C%CE%94pEC50%7C%22%2C%20%20%20format%3D%22.2f%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22max_pEC50%3AQ%22%2C%20%20%20title%3D%22max%20pEC50%22%2C%20%20format%3D%22.2f%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.add_params(_sel)%0A%20%20%20%20%20%20%20%20.properties(title%3D%22Activity%20cliffs%20%E2%80%94%20ECFP4%20Tanimoto%20%E2%89%A5%200.4%2C%20%7C%CE%94pEC50%7C%20%E2%89%A5%202%22%2C%20width%3D500%2C%20height%3D400)%0A%20%20%20%20%20%20%20%20.configure_title(fontSize%3D12)%0A%20%20%20%20)%0A%0A%20%20%20%20cliff_chart%20%3D%20mo.ui.altair_chart(_scatter)%0A%20%20%20%20return%20cliff_chart%2C%20cliff_plot_df%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Chem%2C%0A%20%20%20%20CombineMols%2C%0A%20%20%20%20cliff_chart%2C%0A%20%20%20%20cliff_plot_df%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20rdDepictor%2C%0A%20%20%20%20rdMolDraw2D%2C%0A)%3A%0A%20%20%20%20_PANEL_W%20%3D%20350%0A%0A%20%20%20%20def%20_pair_to_svg(smi1%3A%20str%2C%20smi2%3A%20str%2C%20width%3A%20int%20%3D%20_PANEL_W%2C%20height%3A%20int%20%3D%20210)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22Render%20two%20molecules%20side%20by%20side%20as%20an%20SVG%20string.%22%22%22%0A%20%20%20%20%20%20%20%20mol1%20%3D%20Chem.MolFromSmiles(smi1)%0A%20%20%20%20%20%20%20%20mol2%20%3D%20Chem.MolFromSmiles(smi2)%0A%20%20%20%20%20%20%20%20if%20mol1%20is%20None%20or%20mol2%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%22%22%0A%20%20%20%20%20%20%20%20combined%20%3D%20CombineMols(mol1%2C%20mol2)%0A%20%20%20%20%20%20%20%20rdDepictor.Compute2DCoords(combined)%0A%20%20%20%20%20%20%20%20drawer%20%3D%20rdMolDraw2D.MolDraw2DSVG(width%2C%20height)%0A%20%20%20%20%20%20%20%20drawer.DrawMolecule(combined)%0A%20%20%20%20%20%20%20%20drawer.FinishDrawing()%0A%20%20%20%20%20%20%20%20return%20drawer.GetDrawingText()%0A%0A%20%20%20%20_sel_rows%20%3D%20cliff_chart.value%0A%0A%20%20%20%20if%20_sel_rows%20is%20None%20or%20len(_sel_rows)%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20_panel%20%3D%20mo.Html(f%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D'width%3A%7B_PANEL_W%7Dpx%3B%20height%3A400px%3B%20display%3Aflex%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20align-items%3Acenter%3B%20justify-content%3Acenter%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3Agrey%3B%20font-size%3A14px%3B%20border%3A1px%20dashed%20%23ccc%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20border-radius%3A6px'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Hover%20over%20a%20point%20to%20see%20the%20pair%20of%20structures%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%22%22%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_row%20%3D%20_sel_rows.row(0%2C%20named%3DTrue)%0A%20%20%20%20%20%20%20%20_match%20%3D%20cliff_plot_df.filter(%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22ID1%22)%20%3D%3D%20_row%5B%22ID1%22%5D)%20%26%20(pl.col(%22ID2%22)%20%3D%3D%20_row%5B%22ID2%22%5D)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_r%20%3D%20_match.row(0%2C%20named%3DTrue)%0A%20%20%20%20%20%20%20%20_svg%20%3D%20_pair_to_svg(_r%5B%22smiles1%22%5D%2C%20_r%5B%22smiles2%22%5D)%0A%20%20%20%20%20%20%20%20_panel%20%3D%20mo.Html(f%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D'width%3A%7B_PANEL_W%7Dpx'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D'font-size%3A11px%3B%20font-family%3Amonospace%3B%20margin-bottom%3A6px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20padding%3A6px%3B%20background%3A%23f8f8f8%3B%20border-radius%3A4px'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cb%3EMol%201%3A%3C%2Fb%3E%20%7B_r%5B'name1'%5D%7D%20%26nbsp%3B%20pEC50%20%3D%20%7B_r%5B'pEC50_1'%5D%3A.2f%7D%3Cbr%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cb%3EMol%202%3A%3C%2Fb%3E%20%7B_r%5B'name2'%5D%7D%20%26nbsp%3B%20pEC50%20%3D%20%7B_r%5B'pEC50_2'%5D%3A.2f%7D%3Cbr%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Tanimoto%20%7B_r%5B'similarity'%5D%3A.3f%7D%20%26nbsp%3B%7C%26nbsp%3B%20%7C%CE%94pEC50%7C%20%7B_r%5B'delta_pEC50'%5D%3A.2f%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B_svg%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%22%22%22)%0A%0A%20%20%20%20mo.hstack(%5Bcliff_chart%2C%20_panel%5D%2C%20align%3D%22start%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
046cf84c91bc793cfb1341b0de9c7392