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%201d%20%E2%80%94%20Train%20%2F%20test%20set%20exploration%0A%0A%20%20%20%20Compares%20the%20chemical%20space%20of%20the%20dose-response%20training%20set%20and%20the%20blinded%0A%20%20%20%20test%20set%20to%20understand%3A%0A%0A%20%20%20%20-%20How%20structurally%20similar%20the%20two%20sets%20are%20(pairwise%20Tanimoto%20KDE%20curves).%0A%20%20%20%20-%20How%20much%20of%20the%20test%20set%20is%20covered%20by%20the%20training%20data%20(nearest-neighbour%0A%20%20%20%20%20%20maximum%20similarity%20per%20test%20compound).%0A%20%20%20%20-%20Where%20test%20compounds%20fall%20in%20UMAP%20space%20relative%20to%20their%20closest%20training%0A%20%20%20%20%20%20analogue%20(interactive%20scatter%20with%20on-click%20structure%20panel).%0A%0A%20%20%20%20**Input%3A**%20%60data%2Fprocessed%2Fall_compounds_activity_data.csv%60%20(produced%20by%20**1a**).%0A%20%20%20%20The%20raw%20test%20CSV%20is%20also%20read%20to%20access%20the%20blinded%20test%20molecules.%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%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%20scipy.stats%20import%20gaussian_kde%0A%0A%20%20%20%20from%20umap%20import%20UMAP%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%20from%20rdkit%20import%20Chem%2C%20DataStructs%2C%20RDLogger%0A%20%20%20%20from%20rdkit.Chem%20import%20rdDepictor%0A%20%20%20%20from%20rdkit.Chem.Draw%20import%20rdMolDraw2D%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%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%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%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%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%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)%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%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%20all%20unique%20pairwise%20similarities%20within%20a%20single%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%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%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%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%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%20DataFrame%20with%20%22similarity%22%20and%20group_col%20columns.%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_(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_(Chem%2C%20pl)%3A%0A%20%20%20%20all_compounds%20%3D%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%20%20%20%20%23%20Also%20read%20the%20raw%20test%20file%20to%20access%20the%20Molecule%20Name%20column%0A%20%20%20%20test%20%3D%20pl.read_csv(%22..%2Fdata%2Fraw%2F20260409%2Fdose_response_test.csv%22).rename(%7B%22SMILES%22%3A%20%22smiles%22%7D)%0A%0A%20%20%20%20def%20_smi_to_inchikey(smi%3A%20str)%3A%0A%20%20%20%20%20%20%20%20mol%20%3D%20Chem.MolFromSmiles(smi)%0A%20%20%20%20%20%20%20%20return%20Chem.MolToInchiKey(mol)%20if%20mol%20else%20None%0A%0A%20%20%20%20test%20%3D%20test.with_columns(%0A%20%20%20%20%20%20%20%20pl.col(%22smiles%22).map_elements(_smi_to_inchikey%2C%20return_dtype%3Dpl.Utf8).alias(%22inchikey%22)%0A%20%20%20%20)%0A%0A%20%20%20%20all_compounds%0A%20%20%20%20return%20all_compounds%2C%20test%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%20Train%20%2F%20test%20similarity%20comparison%0A%0A%20%20%20%20Three%20ECFP4%20Tanimoto%20similarity%20distributions%20are%20overlaid%3A%0A%0A%20%20%20%20-%20**Within%20train**%20%E2%80%94%20all%20unique%20pairs%20among%20dose-response%20training%20compounds%0A%20%20%20%20-%20**Within%20test**%20%20%E2%80%94%20all%20unique%20pairs%20among%20test-set%20compounds%0A%20%20%20%20-%20**Train%20vs%20test**%20%E2%80%94%20every%20(train%2C%20test)%20cross-set%20pair%0A%0A%20%20%20%20A%20large%20gap%20between%20%22within%20train%22%20and%20%22train%20vs%20test%22%20would%20indicate%20that%0A%20%20%20%20the%20test%20set%20probes%20chemical%20space%20not%20well%20represented%20by%20the%20training%20data.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20DataStructs%2C%0A%20%20%20%20all_compounds%2C%0A%20%20%20%20compute_pairwise_similarities%2C%0A%20%20%20%20generate_fingerprint%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20plot_similarity_distributions%2C%0A%20%20%20%20test%2C%0A)%3A%0A%20%20%20%20_train_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(%22in_dose_response%22))%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%20%20%20%20_test_df%20%3D%20(%0A%20%20%20%20%20%20%20%20test%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_FP_KWARGS%20%3D%20%7B%22fp_size%22%3A%201024%2C%20%22radius%22%3A%202%2C%20%22include_chirality%22%3A%20True%7D%0A%0A%20%20%20%20_sim_train%20%3D%20(%0A%20%20%20%20%20%20%20%20compute_pairwise_similarities(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_df%2C%20id_col%3D%22inchikey%22%2C%20fingerprint%3D%22ecfp%22%2C%20metric%3D%22tanimoto%22%2C%20**_FP_KWARGS%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22within_train%22).alias(%22comparison%22))%0A%20%20%20%20)%0A%20%20%20%20_sim_test%20%3D%20(%0A%20%20%20%20%20%20%20%20compute_pairwise_similarities(%0A%20%20%20%20%20%20%20%20%20%20%20%20_test_df%2C%20id_col%3D%22inchikey%22%2C%20fingerprint%3D%22ecfp%22%2C%20metric%3D%22tanimoto%22%2C%20**_FP_KWARGS%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22within_test%22).alias(%22comparison%22))%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Cross-set%20similarity%20matrix%3A%20every%20(train%2C%20test)%20pair%0A%20%20%20%20def%20_to_bv(row%3A%20np.ndarray)%20-%3E%20DataStructs.ExplicitBitVect%3A%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%20_train_fp%20%3D%20generate_fingerprint(_train_df%2C%20%22ecfp%22%2C%20**_FP_KWARGS)%0A%20%20%20%20_test_fp%20%20%3D%20generate_fingerprint(_test_df%2C%20%20%22ecfp%22%2C%20**_FP_KWARGS)%0A%0A%20%20%20%20_train_arr%20%3D%20np.vstack(_train_fp%5B%22ecfp%22%5D.to_list())%0A%20%20%20%20_test_arr%20%20%3D%20np.vstack(_test_fp%5B%22ecfp%22%5D.to_list())%0A%0A%20%20%20%20_train_bvs%20%3D%20%5B_to_bv(_train_arr%5Bi%5D)%20for%20i%20in%20range(len(_train_arr))%5D%0A%20%20%20%20_test_bvs%20%20%3D%20%5B_to_bv(_test_arr%5Bi%5D)%20%20for%20i%20in%20range(len(_test_arr))%5D%0A%0A%20%20%20%20_cross_sims%3A%20list%5Bfloat%5D%20%3D%20%5B%5D%0A%20%20%20%20for%20_bv%20in%20_train_bvs%3A%0A%20%20%20%20%20%20%20%20_cross_sims.extend(DataStructs.BulkTanimotoSimilarity(_bv%2C%20_test_bvs))%0A%0A%20%20%20%20_sim_cross%20%3D%20pl.DataFrame(%7B%0A%20%20%20%20%20%20%20%20%22similarity%22%3A%20pl.Series(_cross_sims%2C%20dtype%3Dpl.Float32)%2C%0A%20%20%20%20%20%20%20%20%22comparison%22%3A%20pl.Series(%5B%22train_vs_test%22%5D%20*%20len(_cross_sims)%2C%20dtype%3Dpl.Utf8)%2C%0A%20%20%20%20%7D)%0A%0A%20%20%20%20_sim_combined%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20_sim_train.select(%5B%22similarity%22%2C%20%22comparison%22%5D)%2C%0A%20%20%20%20%20%20%20%20_sim_test.select(%5B%22similarity%22%2C%20%20%22comparison%22%5D)%2C%0A%20%20%20%20%20%20%20%20_sim_cross.select(%5B%22similarity%22%2C%20%22comparison%22%5D)%2C%0A%20%20%20%20%5D)%0A%0A%20%20%20%20plot_similarity_distributions(%0A%20%20%20%20%20%20%20%20_sim_combined%2C%0A%20%20%20%20%20%20%20%20group_col%3D%22comparison%22%2C%0A%20%20%20%20%20%20%20%20group_order%3D%5B%22within_train%22%2C%20%22within_test%22%2C%20%22train_vs_test%22%5D%2C%0A%20%20%20%20%20%20%20%20colors%3D%5B%22%234e79a7%22%2C%20%22%2359a14f%22%2C%20%22%23e15759%22%5D%2C%0A%20%20%20%20%20%20%20%20title%3D%22ECFP4%20Tanimoto%20similarity%20%E2%80%94%20train%20%2F%20test%20comparison%22%2C%0A%20%20%20%20%20%20%20%20save_path%3D%22..%2Fplots%2F1_sar_exploration%2Fdensity_sim_train_test.png%22%2C%0A%20%20%20%20)%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%20Test%20set%20coverage%20by%20training%20compounds%0A%0A%20%20%20%20For%20each%20test%20compound%20we%20compute%20the%20**maximum%20ECFP4%20Tanimoto%20similarity**%0A%20%20%20%20to%20any%20compound%20in%20the%20dose-response%20training%20set.%20This%20gives%20us%20the%0A%20%20%20%20nearest%20neighbor%20(NN)%20of%20the%20test%20compound%20within%20the%20training%20set.%0A%0A%20%20%20%20A%20high%20value%20means%20the%20test%20compound%20is%20well-represented%20in%20the%20training%20data%3B%0A%20%20%20%20a%20low%20value%20flags%20a%20potential%20extrapolation%20challenge%20for%20any%20model.%0A%0A%20%20%20%20The%20scatter%20shows%20test%20compounds%20projected%20into%202D%20via%20**UMAP**%20(jaccard%0A%20%20%20%20metric%20on%20ECFP4)%2C%20coloured%20by%20nearest-neighbour%20similarity%20to%20the%20training%0A%20%20%20%20set.%20%20Hover%20over%20a%20point%20to%20see%20the%20test%20compound%20and%20its%20closest%20training%0A%20%20%20%20analogue%20side%20by%20side.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DataStructs%2C%20UMAP%2C%20all_compounds%2C%20generate_fingerprint%2C%20np%2C%20pl%2C%20test)%3A%0A%20%20%20%20_ECFP4%20%3D%20%7B%22fp_size%22%3A%201024%2C%20%22radius%22%3A%202%2C%20%22include_chirality%22%3A%20True%7D%0A%0A%20%20%20%20_train_meta%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(%22in_dose_response%22))%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%2C%20%22molecule_names%22%5D)%0A%20%20%20%20)%0A%20%20%20%20_test_meta%20%3D%20(%0A%20%20%20%20%20%20%20%20test%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%2C%20%22Molecule%20Name%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20_train_fp%20%3D%20generate_fingerprint(_train_meta%2C%20%22ecfp%22%2C%20**_ECFP4)%0A%20%20%20%20_test_fp%20%20%3D%20generate_fingerprint(_test_meta%2C%20%20%22ecfp%22%2C%20**_ECFP4)%0A%0A%20%20%20%20_train_arr%20%3D%20np.vstack(_train_fp%5B%22ecfp%22%5D.to_list())%0A%20%20%20%20_test_arr%20%20%3D%20np.vstack(_test_fp%5B%22ecfp%22%5D.to_list())%0A%0A%20%20%20%20def%20_to_bv(row%3A%20np.ndarray)%3A%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%20_train_bvs%20%3D%20%5B_to_bv(_train_arr%5Bi%5D)%20for%20i%20in%20range(len(_train_arr))%5D%0A%20%20%20%20_test_bvs%20%20%3D%20%5B_to_bv(_test_arr%5Bi%5D)%20%20for%20i%20in%20range(len(_test_arr))%5D%0A%0A%20%20%20%20_max_sims%3A%20list%5Bfloat%5D%20%3D%20%5B%5D%0A%20%20%20%20_nn_idx%3A%20%20%20list%5Bint%5D%20%20%20%3D%20%5B%5D%0A%20%20%20%20for%20_bv%20in%20_test_bvs%3A%0A%20%20%20%20%20%20%20%20_sims%20%3D%20DataStructs.BulkTanimotoSimilarity(_bv%2C%20_train_bvs)%0A%20%20%20%20%20%20%20%20_best%20%3D%20int(np.argmax(_sims))%0A%20%20%20%20%20%20%20%20_max_sims.append(float(_sims%5B_best%5D))%0A%20%20%20%20%20%20%20%20_nn_idx.append(_best)%0A%0A%20%20%20%20_train_ids%20%20%20%20%3D%20_train_meta%5B%22inchikey%22%5D.to_list()%0A%20%20%20%20_train_smiles%20%3D%20_train_meta%5B%22smiles%22%5D.to_list()%0A%20%20%20%20_train_names%20%20%3D%20_train_meta%5B%22molecule_names%22%5D.to_list()%0A%0A%20%20%20%20_umap%20%3D%20UMAP(%0A%20%20%20%20%20%20%20%20n_components%3D2%2C%0A%20%20%20%20%20%20%20%20n_neighbors%3Dmin(15%2C%20len(_test_arr)%20-%201)%2C%0A%20%20%20%20%20%20%20%20min_dist%3D0.1%2C%0A%20%20%20%20%20%20%20%20metric%3D%22jaccard%22%2C%0A%20%20%20%20%20%20%20%20random_state%3D42%2C%0A%20%20%20%20)%0A%20%20%20%20_umap_coords%20%3D%20_umap.fit_transform(_test_arr)%0A%0A%20%20%20%20test_coverage_df%20%3D%20pl.DataFrame(%7B%0A%20%20%20%20%20%20%20%20%22inchikey%22%3A%20%20%20%20%20%20%20%20%20_test_meta%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%22smiles%22%3A%20%20%20%20%20%20%20%20%20%20%20_test_meta%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%22molecule_names%22%3A%20%20%20_test_meta%5B%22Molecule%20Name%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%22UMAP_x%22%3A%20%20%20%20%20%20%20%20%20%20%20_umap_coords%5B%3A%2C%200%5D.tolist()%2C%0A%20%20%20%20%20%20%20%20%22UMAP_y%22%3A%20%20%20%20%20%20%20%20%20%20%20_umap_coords%5B%3A%2C%201%5D.tolist()%2C%0A%20%20%20%20%20%20%20%20%22max_sim_to_train%22%3A%20pl.Series(_max_sims%2C%20dtype%3Dpl.Float32)%2C%0A%20%20%20%20%20%20%20%20%22nn_inchikey%22%3A%20%20%20%20%20%20%5B_train_ids%5Bi%5D%20%20%20%20for%20i%20in%20_nn_idx%5D%2C%0A%20%20%20%20%20%20%20%20%22nn_smiles%22%3A%20%20%20%20%20%20%20%20%5B_train_smiles%5Bi%5D%20for%20i%20in%20_nn_idx%5D%2C%0A%20%20%20%20%20%20%20%20%22nn_name%22%3A%20%20%20%20%20%20%20%20%20%20%5B_train_names%5Bi%5D%20%20for%20i%20in%20_nn_idx%5D%2C%0A%20%20%20%20%7D)%0A%0A%20%20%20%20test_coverage_df%0A%20%20%20%20return%20(test_coverage_df%2C)%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20mo%2C%20test_coverage_df)%3A%0A%20%20%20%20_sel%20%3D%20alt.selection_point(fields%3D%5B%22inchikey%22%5D%2C%20name%3D%22test_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(test_coverage_df)%0A%20%20%20%20%20%20%20%20.mark_circle(opacity%3D0.85)%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(%22UMAP_x%3AQ%22%2C%20title%3D%22UMAP%201%22%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(%22UMAP_y%3AQ%22%2C%20title%3D%22UMAP%202%22%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%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%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_sim_to_train%3AQ%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%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%20similarity%5Cnto%20train%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(%22molecule_names%3AN%22%2C%20%20%20title%3D%22Name%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22inchikey%3AN%22%2C%20%20%20%20%20%20%20%20%20%20title%3D%22InChIKey%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22max_sim_to_train%3AQ%22%2C%20%20title%3D%22Max%20sim%20to%20train%22%2C%20format%3D%22.3f%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22nn_name%3AN%22%2C%20%20%20%20%20%20%20%20%20%20%20title%3D%22Nearest%20train%20compound%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(%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Test%20compounds%20%E2%80%94%20ECFP4%20UMAP%20coloured%20by%20nearest-train%20similarity%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D500%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3D400%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.configure_title(fontSize%3D12)%0A%20%20%20%20)%0A%0A%20%20%20%20test_coverage_chart%20%3D%20mo.ui.altair_chart(_scatter)%0A%20%20%20%20return%20(test_coverage_chart%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Chem%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%20%20%20%20test_coverage_chart%2C%0A%20%20%20%20test_coverage_df%2C%0A)%3A%0A%20%20%20%20_PANEL_W%20%3D%20300%0A%20%20%20%20_PANEL_H%20%3D%20200%0A%0A%20%20%20%20def%20_smi_to_svg(smi%3A%20str%2C%20width%3A%20int%20%3D%20_PANEL_W%2C%20height%3A%20int%20%3D%20_PANEL_H)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22Render%20a%20single%20SMILES%20as%20an%20SVG%20string%20via%20RDKit%20MolDraw2DSVG.%22%22%22%0A%20%20%20%20%20%20%20%20mol%20%3D%20Chem.MolFromSmiles(smi)%0A%20%20%20%20%20%20%20%20if%20mol%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%20rdDepictor.Compute2DCoords(mol)%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(mol)%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%20def%20_strip_xml(svg%3A%20str)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20return%20svg.split(%22%3F%3E%22%2C%201)%5B-1%5D.strip()%20if%20%22%3F%3E%22%20in%20svg%20else%20svg%0A%0A%20%20%20%20_sel_rows%20%3D%20test_coverage_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%3A%7B(_PANEL_H%20%2B%2040)%20*%202%7Dpx%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%20test%20compound%20and%20its%20nearest%20training%20neighbour%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_key%20%3D%20_sel_rows.row(0%2C%20named%3DTrue)%5B%22inchikey%22%5D%0A%20%20%20%20%20%20%20%20_r%20%3D%20test_coverage_df.filter(pl.col(%22inchikey%22)%20%3D%3D%20_key).row(0%2C%20named%3DTrue)%0A%0A%20%20%20%20%20%20%20%20_svg_test%20%3D%20_smi_to_svg(_r%5B%22smiles%22%5D)%0A%20%20%20%20%20%20%20%20_svg_nn%20%20%20%3D%20_smi_to_svg(_r%5B%22nn_smiles%22%5D)%0A%0A%20%20%20%20%20%20%20%20_name_test%20%3D%20_r%5B%22molecule_names%22%5D%20or%20_r%5B%22inchikey%22%5D%0A%20%20%20%20%20%20%20%20_name_nn%20%20%20%3D%20_r%5B%22nn_name%22%5D%20%20%20%20%20%20%20%20or%20_r%5B%22nn_inchikey%22%5D%0A%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%20font-family%3Amonospace%3B%20font-size%3A11px'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D'padding%3A5px%3B%20background%3A%23eef4fb%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%20border-radius%3A4px%3B%20text-align%3Acenter%3B%20margin-bottom%3A2px'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cb%3ETest%20compound%3C%2Fb%3E%3Cbr%3E%7B_name_test%7D%3Cbr%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Max%20sim%20to%20train%3A%20%3Cb%3E%7B_r%5B'max_sim_to_train'%5D%3A.3f%7D%3C%2Fb%3E%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_strip_xml(_svg_test)%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D'padding%3A5px%3B%20background%3A%23f0faf0%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%20border-radius%3A4px%3B%20text-align%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%20%20%20%20%20margin-top%3A6px%3B%20margin-bottom%3A2px'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cb%3ENearest%20train%20compound%3C%2Fb%3E%3Cbr%3E%7B_name_nn%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_strip_xml(_svg_nn)%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(%5Btest_coverage_chart%2C%20_panel%5D%2C%20align%3D%22start%22)%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%20Based%20on%20our%20discussion%20in%20notebook%201c%20of%20how%20to%20define%20similar%20compounds%2C%0A%20%20%20%20almost%20all%20test%20compounds%20have%20a%20NN%20in%20the%20training%20set%20that%20would%20be%20considered%0A%20%20%20%20similar%20(ECFP4%20Tanimoto%20over%200.4)%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%20Dose-response%20vs%20counter%20screen%20selectivity%0A%0A%20%20%20%20Compounds%20that%20have%20both%20a%20dose-response%20pEC50%20and%20a%20counter%20screen%20pEC50%20are%0A%20%20%20%20plotted%20against%20each%20other%20to%20identify%20selective%20hits.%0A%0A%20%20%20%20-%20**Selective**%3A%20dose-response%20pEC50%20is%20at%20least%20**1.5%20units%20higher**%20than%0A%20%20%20%20%20%20counter%20screen%20pEC50%20(compound%20is%20active%20in%20the%20primary%20assay%20but%20not%20in%20the%0A%20%20%20%20%20%20counter%20screen).%0A%20%20%20%20-%20**Hit**%3A%20selective%20**and**%20dose-response%20pEC50%20%E2%89%A5%20**6**%20(potent%20enough%20to%0A%20%20%20%20%20%20matter).%0A%0A%20%20%20%20Three%20reference%20lines%20are%20drawn%3A%0A%20%20%20%20-%20Diagonal%20%E2%80%94%20identity%20line%20(pEC50_dr%20%3D%20pEC50_counter).%0A%20%20%20%20-%20Diagonal%20shifted%20down%201.5%20units%20%E2%80%94%20the%20selectivity%20threshold.%0A%20%20%20%20-%20Vertical%20line%20at%20pEC50_dr%20%3D%206%20%E2%80%94%20the%20potency%20threshold.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(all_compounds%2C%20pl)%3A%0A%20%20%20%20%23%23%20Build%20a%20dataset%20restricted%20to%20compounds%20with%20both%20pEC50%20values%20available.%0A%20%20%20%20%23%20Filter%20to%20rows%20that%20have%20both%20dose-response%20and%20counter%20screen%20pEC50%20values.%0A%20%20%20%20dr_counter_df%20%3D%20(%0A%20%20%20%20%20%20%20%20all_compounds%0A%20%20%20%20%20%20%20%20.filter(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22pEC50_dr%22).is_not_null()%0A%20%20%20%20%20%20%20%20%20%20%20%20%26%20pl.col(%22pEC50_counter%22).is_not_null()%0A%20%20%20%20%20%20%20%20)%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%2C%20%22molecule_names%22%2C%20%22pEC50_dr%22%2C%20%22pEC50_counter%22%5D)%0A%20%20%20%20%20%20%20%20%23%20Mark%20a%20compound%20as%20selective%20if%20its%20dose-response%20pEC50%20is%20%3E1.5%20units%0A%20%20%20%20%20%20%20%20%23%20above%20the%20counter%20screen%20pEC50%20%E2%80%94%20i.e.%20it%20is%20potent%20in%20the%20primary%20assay%0A%20%20%20%20%20%20%20%20%23%20but%20not%20in%20the%20counter%20assay.%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_dr%22)%20-%20pl.col(%22pEC50_counter%22)%20%3E%201.5).alias(%22selective%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%23%20A%20hit%20must%20also%20clear%20the%20absolute%20potency%20threshold%20of%20pEC50_dr%20%3E%3D%206.%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(%22selective%22)%20%26%20(pl.col(%22pEC50_dr%22)%20%3E%3D%206.0)).alias(%22hit%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%23%20A%20readable%20category%20label%20for%20tooltip%20%2F%20legend%20use.%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.when(pl.col(%22hit%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22Hit%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.when(pl.col(%22selective%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22Selective%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.otherwise(pl.lit(%22Non-selective%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22category%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20dr_counter_df%0A%20%20%20%20return%20(dr_counter_df%2C)%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20dr_counter_df%2C%20mo%2C%20np)%3A%0A%20%20%20%20%23%23%20Build%20reference%20line%20data%20for%20the%20three%20guide%20lines.%0A%20%20%20%20%23%20Determine%20axis%20range%20with%20a%20small%20margin%20so%20the%20lines%20extend%20past%20all%20points.%0A%20%20%20%20_x_min%20%3D%20float(dr_counter_df%5B%22pEC50_dr%22%5D.min())%20-%200.3%0A%20%20%20%20_x_max%20%3D%20float(dr_counter_df%5B%22pEC50_dr%22%5D.max())%20%2B%200.3%0A%20%20%20%20_y_min%20%3D%20float(dr_counter_df%5B%22pEC50_counter%22%5D.min())%20-%200.3%0A%20%20%20%20_y_max%20%3D%20float(dr_counter_df%5B%22pEC50_counter%22%5D.max())%20%2B%200.3%0A%0A%20%20%20%20%23%20Both%20axes%20share%20the%20same%20global%20range%20so%20diagonal%20lines%20are%20meaningful.%0A%20%20%20%20_global_min%20%3D%20min(_x_min%2C%20_y_min)%0A%20%20%20%20_global_max%20%3D%20max(_x_max%2C%20_y_max)%0A%0A%20%20%20%20_line_pts%20%3D%20np.linspace(_global_min%2C%20_global_max%2C%20200).tolist()%0A%0A%20%20%20%20%23%20Identity%20diagonal%3A%20counter%20%3D%20dr%0A%20%20%20%20_diag_data%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22x%22%3A%20_line_pts%2C%0A%20%20%20%20%20%20%20%20%22y%22%3A%20_line_pts%2C%0A%20%20%20%20%20%20%20%20%22line%22%3A%20%5B%22Identity%20(counter%20%3D%20DR)%22%5D%20*%20len(_line_pts)%2C%0A%20%20%20%20%7D%0A%20%20%20%20%23%20Selectivity%20threshold%20diagonal%3A%20counter%20%3D%20dr%20-%201.5%0A%20%20%20%20_thresh_data%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22x%22%3A%20_line_pts%2C%0A%20%20%20%20%20%20%20%20%22y%22%3A%20%5Bv%20-%201.5%20for%20v%20in%20_line_pts%5D%2C%0A%20%20%20%20%20%20%20%20%22line%22%3A%20%5B%22Selectivity%20threshold%20(DR%20%E2%88%92%201.5)%22%5D%20*%20len(_line_pts)%2C%0A%20%20%20%20%7D%0A%20%20%20%20%23%20Potency%20threshold%3A%20vertical%20line%20at%20pEC50_dr%20%3D%206%0A%20%20%20%20_potency_pts%20%3D%20np.linspace(_global_min%2C%20_global_max%2C%20200).tolist()%0A%20%20%20%20_potency_data%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22x%22%3A%20%5B6.0%5D%20*%20len(_potency_pts)%2C%0A%20%20%20%20%20%20%20%20%22y%22%3A%20_potency_pts%2C%0A%20%20%20%20%20%20%20%20%22line%22%3A%20%5B%22Potency%20threshold%20(DR%20%3D%206)%22%5D%20*%20len(_potency_pts)%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20%23%20Colour%20palette%3A%20Non-selective%20%3D%20grey%2C%20Selective%20%3D%20orange%2C%20Hit%20%3D%20red.%0A%20%20%20%20_color_scale%20%3D%20alt.Scale(%0A%20%20%20%20%20%20%20%20domain%3D%5B%22Non-selective%22%2C%20%22Selective%22%2C%20%22Hit%22%5D%2C%0A%20%20%20%20%20%20%20%20range%3D%5B%22%23b0b0b0%22%2C%20%22%23f28e2b%22%2C%20%22%23e15759%22%5D%2C%0A%20%20%20%20)%0A%20%20%20%20%23%20Dash%20pattern%20for%20each%20reference%20line.%0A%20%20%20%20_dash_scale%20%3D%20alt.Scale(%0A%20%20%20%20%20%20%20%20domain%3D%5B%22Identity%20(counter%20%3D%20DR)%22%2C%20%22Selectivity%20threshold%20(DR%20%E2%88%92%201.5)%22%2C%20%22Potency%20threshold%20(DR%20%3D%206)%22%5D%2C%0A%20%20%20%20%20%20%20%20range%3D%5B%5B4%2C%204%5D%2C%20%5B6%2C%203%5D%2C%20%5B3%2C%203%5D%5D%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Click%20selection%20%E2%80%94%20one%20point%20at%20a%20time%2C%20keyed%20on%20inchikey.%0A%20%20%20%20_sel%20%3D%20alt.selection_point(fields%3D%5B%22inchikey%22%5D%2C%20name%3D%22dr_sel%22%2C%20empty%3DFalse%2C%20on%3D%22mouseover%22%2C%20nearest%3DTrue%2C%20clear%3D%22mouseout%22)%0A%0A%20%20%20%20%23%23%20Scatter%20layer%20%E2%80%94%20one%20point%20per%20compound.%0A%20%20%20%20_scatter_layer%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(dr_counter_df)%0A%20%20%20%20%20%20%20%20.mark_circle(opacity%3D0.75%2C%20size%3D60)%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(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pEC50_dr%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Dose-response%20pEC50%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(domain%3D%5B_global_min%2C%20_global_max%5D)%2C%0A%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%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3Dalt.Y(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pEC50_counter%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Counter%20screen%20pEC50%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(domain%3D%5B_global_min%2C%20_global_max%5D)%2C%0A%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%20)%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%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.value(%22%23f5c518%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Color(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22category%3AN%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3D_color_scale%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20legend%3Dalt.Legend(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3DNone%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%20orient%3D%22bottom%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%20direction%3D%22horizontal%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%20titleFontSize%3D12%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%20labelFontSize%3D11%2C%0A%20%20%20%20%20%20%20%20%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%20%20%20%20%20)%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(%22molecule_names%3AN%22%2C%20title%3D%22Name%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22inchikey%3AN%22%2C%20%20%20%20%20%20%20%20title%3D%22InChIKey%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22pEC50_dr%3AQ%22%2C%20%20%20%20%20%20%20%20%20title%3D%22DR%20pEC50%22%2C%20%20%20%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(%22pEC50_counter%3AQ%22%2C%20%20%20%20title%3D%22Counter%20pEC50%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(%22category%3AN%22%2C%20%20%20%20%20%20%20%20%20title%3D%22Category%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)%0A%0A%20%20%20%20%23%23%20Reference%20line%20layers%20%E2%80%94%20diagonal%2C%20threshold%2C%20potency.%0A%20%20%20%20_ref_diag%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(alt.Data(values%3D%5B%7B%22x%22%3A%20x%2C%20%22y%22%3A%20y%2C%20%22line%22%3A%20l%7D%20for%20x%2C%20y%2C%20l%20in%20zip(_diag_data%5B%22x%22%5D%2C%20_diag_data%5B%22y%22%5D%2C%20_diag_data%5B%22line%22%5D)%5D))%0A%20%20%20%20%20%20%20%20.mark_line(strokeWidth%3D1.5)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3D%22x%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3D%22y%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20strokeDash%3Dalt.StrokeDash(%22line%3AN%22%2C%20scale%3D_dash_scale%2C%20legend%3DNone)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dalt.value(%22%23555555%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20_ref_thresh%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(alt.Data(values%3D%5B%7B%22x%22%3A%20x%2C%20%22y%22%3A%20y%2C%20%22line%22%3A%20l%7D%20for%20x%2C%20y%2C%20l%20in%20zip(_thresh_data%5B%22x%22%5D%2C%20_thresh_data%5B%22y%22%5D%2C%20_thresh_data%5B%22line%22%5D)%5D))%0A%20%20%20%20%20%20%20%20.mark_line(strokeWidth%3D1.5)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3D%22x%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3D%22y%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20strokeDash%3Dalt.StrokeDash(%22line%3AN%22%2C%20scale%3D_dash_scale%2C%20legend%3DNone)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dalt.value(%22%23555555%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20_ref_potency%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.Chart(alt.Data(values%3D%5B%7B%22x%22%3A%20x%2C%20%22y%22%3A%20y%2C%20%22line%22%3A%20l%7D%20for%20x%2C%20y%2C%20l%20in%20zip(_potency_data%5B%22x%22%5D%2C%20_potency_data%5B%22y%22%5D%2C%20_potency_data%5B%22line%22%5D)%5D))%0A%20%20%20%20%20%20%20%20.mark_line(strokeWidth%3D1.5)%0A%20%20%20%20%20%20%20%20.encode(%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3D%22x%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3D%22y%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20strokeDash%3Dalt.StrokeDash(%22line%3AN%22%2C%20scale%3D_dash_scale%2C%20legend%3DNone)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dalt.value(%22%23555555%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20_chart%20%3D%20(%0A%20%20%20%20%20%20%20%20alt.layer(_ref_diag%2C%20_ref_thresh%2C%20_ref_potency%2C%20_scatter_layer)%0A%20%20%20%20%20%20%20%20.properties(%0A%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Dose-response%20vs%20counter%20screen%20pEC50%20%E2%80%94%20selectivity%20%26%20hits%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3D520%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3D480%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.configure_title(fontSize%3D13)%0A%20%20%20%20%20%20%20%20.configure_legend(orient%3D%22bottom%22%2C%20direction%3D%22horizontal%22%2C%20titleAnchor%3D%22middle%22)%0A%20%20%20%20%20%20%20%20.configure_view(stroke%3D%22transparent%22)%0A%20%20%20%20)%0A%0A%20%20%20%20dr_counter_chart%20%3D%20mo.ui.altair_chart(_chart)%0A%20%20%20%20return%20(dr_counter_chart%2C)%0A%0A%0A%40app.cell%0Adef%20_(Chem%2C%20dr_counter_chart%2C%20dr_counter_df%2C%20mo%2C%20rdDepictor%2C%20rdMolDraw2D)%3A%0A%20%20%20%20_PANEL_W%20%3D%20280%0A%20%20%20%20_PANEL_H%20%3D%20220%0A%0A%20%20%20%20def%20_smi_to_svg(smi%3A%20str%2C%20width%3A%20int%20%3D%20_PANEL_W%2C%20height%3A%20int%20%3D%20_PANEL_H)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22Render%20a%20SMILES%20as%20an%20SVG%20string%20via%20RDKit%20MolDraw2DSVG.%22%22%22%0A%20%20%20%20%20%20%20%20mol%20%3D%20Chem.MolFromSmiles(smi)%0A%20%20%20%20%20%20%20%20if%20mol%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%20rdDepictor.Compute2DCoords(mol)%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(mol)%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%20def%20_strip_xml(svg%3A%20str)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20return%20svg.split(%22%3F%3E%22%2C%201)%5B-1%5D.strip()%20if%20%22%3F%3E%22%20in%20svg%20else%20svg%0A%0A%20%20%20%20%23%20apply_selection%20filters%20dr_counter_df%20to%20the%20clicked%20point(s).%0A%20%20%20%20%23%20This%20is%20required%20for%20layered%20charts%20where%20.value%20returns%20UndefinedType.%0A%20%20%20%20_sel_rows%20%3D%20dr_counter_chart.apply_selection(dr_counter_df)%0A%0A%20%20%20%20if%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%3A%7B_PANEL_H%20%2B%2080%7Dpx%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%3B%20text-align%3Acenter%3B%20padding%3A12px'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Click%20a%20point%20to%20see%20the%20compound%20structure%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_r%20%3D%20_sel_rows.row(0%2C%20named%3DTrue)%0A%20%20%20%20%20%20%20%20_svg%20%3D%20_smi_to_svg(_r%5B%22smiles%22%5D)%0A%20%20%20%20%20%20%20%20%23%20Colour%20the%20info%20box%20header%20by%20category.%0A%20%20%20%20%20%20%20%20_bg%20%3D%20%7B%22Hit%22%3A%20%22%23fde8e8%22%2C%20%22Selective%22%3A%20%22%23fef3e2%22%2C%20%22Non-selective%22%3A%20%22%23f0f0f0%22%7D%5B_r%5B%22category%22%5D%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%3B%20font-family%3Amonospace%3B%20font-size%3A11px'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20style%3D'padding%3A6px%3B%20background%3A%7B_bg%7D%3B%20border-radius%3A4px%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%20margin-bottom%3A4px%3B%20line-height%3A1.8'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cb%3E%7B_r%5B'molecule_names'%5D%20or%20_r%5B'inchikey'%5D%7D%3C%2Fb%3E%3Cbr%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cb%3ECategory%3A%3C%2Fb%3E%20%7B_r%5B'category'%5D%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%3EDR%20pEC50%3A%3C%2Fb%3E%20%7B_r%5B'pEC50_dr'%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%3ECounter%20pEC50%3A%3C%2Fb%3E%20%7B_r%5B'pEC50_counter'%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%3E%CE%94pEC50%3A%3C%2Fb%3E%20%7B_r%5B'pEC50_dr'%5D%20-%20_r%5B'pEC50_counter'%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_strip_xml(_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(%5Bdr_counter_chart%2C%20_panel%5D%2C%20align%3D%22start%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%20Counter-screen%20status%20of%20nearest%20training%20neighbours%0A%0A%20%20%20%20For%20each%20test%20compound%20we%20found%20its%20nearest%20neighbour%20(NN)%20in%20the%20dose-response%0A%20%20%20%20training%20set.%20%20Here%20we%20ask%3A%20**what%20do%20we%20know%20about%20that%20NN%20compound%20in%20the%0A%20%20%20%20counter%20screen%3F**%0A%0A%20%20%20%20Four%20mutually%20exclusive%20categories%20are%20assigned%20to%20each%20NN%3A%0A%0A%20%20%20%20%7C%20Category%20%7C%20Meaning%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20**Not%20tested**%20%7C%20NN%20was%20never%20run%20in%20the%20counter%20assay%20%7C%0A%20%20%20%20%7C%20**Non-selective**%20%7C%20NN%20has%20a%20counter%20pEC50%20within%201.5%20units%20of%20its%20DR%20pEC50%20%7C%0A%20%20%20%20%7C%20**Selective**%20%7C%20NN%20counter%20pEC50%20is%20%3E1.5%20units%20below%20DR%20pEC50%2C%20but%20DR%20pEC50%20%3C%206%20%7C%0A%20%20%20%20%7C%20**Hit**%20%7C%20Selective%20*and*%20DR%20pEC50%20%E2%89%A5%206%20%7C%0A%0A%20%20%20%20This%20tells%20us%20how%20much%20selectivity%20context%20exists%20in%20the%20training%20data%20for%20the%0A%20%20%20%20regions%20of%20chemical%20space%20where%20the%20test%20compounds%20live.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(all_compounds%2C%20pl%2C%20test_coverage_df)%3A%0A%20%20%20%20%23%20Build%20a%20lookup%20of%20counter-screen%20status%20for%20every%20dose-response%20train%20compound.%0A%20%20%20%20%23%20We%20use%20all_compounds%20which%20carries%20both%20in_counter%20and%20pEC50_dr%20%2F%20pEC50_counter.%0A%20%20%20%20_train_status%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(%22in_dose_response%22))%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%22in_counter%22%2C%20%22pEC50_dr%22%2C%20%22pEC50_counter%22%5D)%0A%20%20%20%20%20%20%20%20%23%20Derive%20the%20same%20selectivity%20%2F%20hit%20logic%20used%20in%20dr_counter_df.%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.when(~pl.col(%22in_counter%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22Not%20tested%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.when(pl.col(%22pEC50_counter%22).is_null())%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22Not%20tested%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.when(pl.col(%22pEC50_dr%22).is_null())%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22Not%20tested%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.when((pl.col(%22pEC50_dr%22)%20-%20pl.col(%22pEC50_counter%22)%20%3E%201.5)%20%26%20(pl.col(%22pEC50_dr%22)%20%3E%3D%206.0))%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22Hit%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.when(pl.col(%22pEC50_dr%22)%20-%20pl.col(%22pEC50_counter%22)%20%3E%201.5)%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22Selective%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.otherwise(pl.lit(%22Non-selective%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22nn_counter_status%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%2C%20%22nn_counter_status%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Join%20the%20NN%20inchikey%20in%20test_coverage_df%20to%20the%20status%20lookup.%0A%20%20%20%20nn_status_df%20%3D%20(%0A%20%20%20%20%20%20%20%20test_coverage_df%0A%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%2C%20%22molecule_names%22%2C%20%22max_sim_to_train%22%2C%20%22nn_inchikey%22%2C%20%22nn_name%22%5D)%0A%20%20%20%20%20%20%20%20.join(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_status.rename(%7B%22inchikey%22%3A%20%22nn_inchikey%22%7D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20on%3D%22nn_inchikey%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20how%3D%22left%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%23%20NNs%20not%20present%20in%20the%20lookup%20at%20all%20(shouldn't%20happen%2C%20but%20guard%20against%20it).%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22nn_counter_status%22).fill_null(%22Not%20tested%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Summary%20counts%20per%20category.%0A%20%20%20%20_order%20%3D%20%5B%22Hit%22%2C%20%22Selective%22%2C%20%22Non-selective%22%2C%20%22Not%20tested%22%5D%0A%20%20%20%20nn_status_summary%20%3D%20(%0A%20%20%20%20%20%20%20%20nn_status_df%0A%20%20%20%20%20%20%20%20.group_by(%22nn_counter_status%22)%0A%20%20%20%20%20%20%20%20.agg(pl.len().alias(%22n_test_compounds%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(%22n_test_compounds%22)%20%2F%20pl.col(%22n_test_compounds%22).sum()%20*%20100)%0A%20%20%20%20%20%20%20%20%20%20%20%20.round(1)%0A%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22pct%22)%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%20pl.col(%22nn_counter_status%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.cast(pl.Enum(_order))%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.sort(%22nn_counter_status%22)%0A%20%20%20%20)%0A%0A%20%20%20%20nn_status_summary%0A%20%20%20%20return%20(nn_status_summary%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20nn_status_summary%2C%20plt)%3A%0A%20%20%20%20_order%20%3D%20%5B%22Hit%22%2C%20%22Selective%22%2C%20%22Non-selective%22%2C%20%22Not%20tested%22%5D%0A%20%20%20%20_colors%20%3D%20%5B%22%23e15759%22%2C%20%22%23f28e2b%22%2C%20%22%23b0b0b0%22%2C%20%22%23d3d3f5%22%5D%0A%0A%20%20%20%20%23%20Reorder%20rows%20to%20match%20the%20desired%20category%20order.%0A%20%20%20%20_rows%20%3D%20%7Br%5B%22nn_counter_status%22%5D%3A%20r%20for%20r%20in%20nn_status_summary.iter_rows(named%3DTrue)%7D%0A%20%20%20%20_labels%20%3D%20%5Bcat%20for%20cat%20in%20_order%20if%20cat%20in%20_rows%5D%0A%20%20%20%20_counts%20%3D%20%5B_rows%5Bcat%5D%5B%22n_test_compounds%22%5D%20for%20cat%20in%20_labels%5D%0A%20%20%20%20_pcts%20%20%20%3D%20%5B_rows%5Bcat%5D%5B%22pct%22%5D%20for%20cat%20in%20_labels%5D%0A%20%20%20%20_bar_colors%20%3D%20%5B_colors%5B_order.index(cat)%5D%20for%20cat%20in%20_labels%5D%0A%0A%20%20%20%20with%20plt.style.context(%22seaborn-v0_8-whitegrid%22)%3A%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(5%2C%204)%2C%20dpi%3D150)%0A%20%20%20%20%20%20%20%20_bars%20%3D%20_ax.bar(_labels%2C%20_counts%2C%20color%3D_bar_colors%2C%20edgecolor%3D%22white%22%2C%20linewidth%3D0.8)%0A%0A%20%20%20%20%20%20%20%20%23%20Annotate%20each%20bar%20with%20count%20and%20percentage.%0A%20%20%20%20%20%20%20%20for%20_bar%2C%20_n%2C%20_pct%20in%20zip(_bars%2C%20_counts%2C%20_pcts)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_bar.get_x()%20%2B%20_bar.get_width()%20%2F%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_bar.get_height()%20%2B%200.3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7B_n%7D%5Cn(%7B_pct%7D%25)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ha%3D%22center%22%2C%20va%3D%22bottom%22%2C%20fontsize%3D9%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20_ax.set_xlabel(%22Counter-screen%20status%20of%20nearest%20train%20neighbour%22%2C%20fontsize%3D11)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel(%22Number%20of%20test%20compounds%22%2C%20fontsize%3D11)%0A%20%20%20%20%20%20%20%20_ax.set_title(%22Counter-screen%20status%20of%20nearest%20training%20neighbour%5Cnper%20test%20compound%22%2C%20fontsize%3D11)%0A%20%20%20%20%20%20%20%20_ax.tick_params(axis%3D%22x%22%2C%20labelsize%3D10)%0A%20%20%20%20%20%20%20%20_fig.tight_layout()%0A%0A%20%20%20%20_fig.savefig(%22..%2Fplots%2F1_sar_exploration%2Fbarchart_test_nn_train.png%22%2C%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dpi%3D300%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20mo.center(mo.as_html(_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%20While%20the%20majority%20of%20the%20test%20compounds%20seem%20to%20derive%20from%20selective%20compounds%20(%3E%2090%25)%20it%20is%20surprising%0A%20%20%20%20to%20see%20that%20the%20hit%20category%20is%20not%20higher.%20This%20could%20be%20an%20artifact%2C%20it%20could%20happen%20that%20an%20analog%20of%20a%20hit%0A%20%20%20%20compound%20is%20identified%20within%20a%20commercially%20available%20compound%20set%20has%20a%20NN%20that%20is%20different%20within%20our%20dataset.%0A%20%20%20%20%22%22%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
6434c9f0ee335e2add3cdc4f90dbe6a5