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%201e%20%E2%80%94%20Scaffold%20analysis%0A%0A%20%20%20%20Decomposes%20every%20unique%20molecule%20into%20a%20hierarchy%20of%20ring-based%20scaffolds%20and%0A%20%20%20%20analyses%20how%20those%20scaffolds%20are%20distributed%20across%20the%20four%20datasets.%0A%0A%20%20%20%20**Scaffold%20types%20produced%3A**%0A%0A%20%20%20%20%7C%20Type%20%7C%20Description%20%7C%0A%20%20%20%20%7C------%7C-------------%7C%0A%20%20%20%20%7C%20%60ring_system%60%20%7C%20Individual%20fused%2Fbridged%20polycyclic%20ring%20system%20or%20isolated%20ring%20%7C%0A%20%20%20%20%7C%20%60linked_ring_systems%60%20%7C%20Two%20ring%20systems%20joined%20by%20a%20linker%20(zero-atom%20for%20direct%20bonds)%20%7C%0A%20%20%20%20%7C%20%60full_scaffold%60%20%7C%20For%20the%20most%20part%20equivalent%20to%20a%20Bemis-Murcko%20scaffold%20%7C%0A%0A%20%20%20%20The%20interactive%20scatter%20at%20the%20end%20lets%20you%20see%20which%20scaffolds%20are%20shared%0A%20%20%20%20between%20the%20training%20and%20test%20sets%2C%20with%20on-hover%20structure%20rendering.%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%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%20itertools%0A%20%20%20%20from%20pathlib%20import%20Path%0A%20%20%20%20from%20typing%20import%20Optional%0A%20%20%20%20from%20collections%20import%20defaultdict%2C%20deque%0A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20matplotlib.figure%0A%0A%20%20%20%20from%20rdkit%20import%20Chem%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%20Chem%2C%0A%20%20%20%20%20%20%20%20Optional%2C%0A%20%20%20%20%20%20%20%20alt%2C%0A%20%20%20%20%20%20%20%20defaultdict%2C%0A%20%20%20%20%20%20%20%20deque%2C%0A%20%20%20%20%20%20%20%20itertools%2C%0A%20%20%20%20%20%20%20%20mo%2C%0A%20%20%20%20%20%20%20%20pl%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_(Chem%2C%20Optional%2C%20defaultdict%2C%20deque%2C%20itertools%2C%20pl)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Internal%20helpers%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20def%20_build_ring_systems(mol%3A%20Chem.Mol)%20-%3E%20list%5Bfrozenset%5Bint%5D%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Return%20one%20frozenset%20of%20atom%20indices%20per%20ring%20system.%0A%0A%20%20%20%20%20%20%20%20A%20ring%20system%20is%20the%20union%20of%20all%20SSSR%20rings%20that%20share%20at%20least%20one%0A%20%20%20%20%20%20%20%20atom%20(fused%2C%20bridged%2C%20or%20spiro%20rings%20merge%20into%20one%20system).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20atom_rings%20%3D%20mol.GetRingInfo().AtomRings()%0A%20%20%20%20%20%20%20%20if%20not%20atom_rings%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20n%20%3D%20len(atom_rings)%0A%20%20%20%20%20%20%20%20adj%3A%20dict%5Bint%2C%20set%5Bint%5D%5D%20%3D%20defaultdict(set)%0A%20%20%20%20%20%20%20%20for%20i%2C%20j%20in%20itertools.combinations(range(n)%2C%202)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20set(atom_rings%5Bi%5D)%20%26%20set(atom_rings%5Bj%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20adj%5Bi%5D.add(j)%3B%20adj%5Bj%5D.add(i)%0A%0A%20%20%20%20%20%20%20%20visited%20%3D%20%5BFalse%5D%20*%20n%0A%20%20%20%20%20%20%20%20systems%3A%20list%5Bfrozenset%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20start%20in%20range(n)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20visited%5Bstart%5D%3A%0A%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%20component%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20queue%20%3D%20deque(%5Bstart%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20queue.popleft()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20visited%5Bnode%5D%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%20visited%5Bnode%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20component.append(node)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20queue.extend(adj%5Bnode%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20systems.append(frozenset(a%20for%20ri%20in%20component%20for%20a%20in%20atom_rings%5Bri%5D))%0A%20%20%20%20%20%20%20%20return%20systems%0A%0A%20%20%20%20def%20_linker_atoms(%0A%20%20%20%20%20%20%20%20mol_adj%3A%20dict%5Bint%2C%20set%5Bint%5D%5D%2C%0A%20%20%20%20%20%20%20%20rs1%3A%20frozenset%5Bint%5D%2C%0A%20%20%20%20%20%20%20%20rs2%3A%20frozenset%5Bint%5D%2C%0A%20%20%20%20%20%20%20%20non_ring%3A%20set%5Bint%5D%2C%0A%20%20%20%20)%20-%3E%20Optional%5Bfrozenset%5Bint%5D%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Find%20the%20set%20of%20non-ring%20atoms%20forming%20the%20shortest%20path%20between%20two%20ring%20systems.%0A%0A%20%20%20%20%20%20%20%20Returns%20an%20empty%20frozenset%20for%20a%20direct%20ring%E2%80%93ring%20bond%2C%20or%20None%20when%20the%0A%20%20%20%20%20%20%20%20two%20systems%20are%20not%20connected.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20for%20a%20in%20rs1%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20any(nb%20in%20rs2%20for%20nb%20in%20mol_adj%5Ba%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20frozenset()%0A%0A%20%20%20%20%20%20%20%20prev%3A%20dict%5Bint%2C%20int%5D%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20queue%20%3D%20deque()%0A%20%20%20%20%20%20%20%20for%20a%20in%20rs1%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20nb%20in%20mol_adj%5Ba%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20nb%20in%20non_ring%20and%20nb%20not%20in%20prev%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prev%5Bnb%5D%20%3D%20a%3B%20queue.append(nb)%0A%0A%20%20%20%20%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20queue.popleft()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20in%20rs2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20path%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20while%20node%20not%20in%20rs1%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20node%20in%20non_ring%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20path.append(node)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20prev%5Bnode%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20frozenset(path)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20nb%20in%20mol_adj%5Bcur%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20nb%20not%20in%20prev%20and%20(nb%20in%20non_ring%20or%20nb%20in%20rs2)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prev%5Bnb%5D%20%3D%20cur%3B%20queue.append(nb)%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20_to_canonical(mol%3A%20Chem.Mol%2C%20atom_set%3A%20frozenset%5Bint%5D)%20-%3E%20Optional%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Canonical%20SMILES%20for%20an%20atom%20subset%3B%20None%20on%20failure%20or%20empty%20set.%0A%0A%20%20%20%20%20%20%20%20Kekulizes%20the%20parent%20molecule%20first%20so%20extracted%20aromatic%20fragments%20can%0A%20%20%20%20%20%20%20%20be%20re-parsed%20correctly%20by%20%60%60MolFromSmiles%60%60.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20not%20atom_set%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mol_kek%20%3D%20Chem.RWMol(mol)%0A%20%20%20%20%20%20%20%20%20%20%20%20Chem.Kekulize(mol_kek%2C%20clearAromaticFlags%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20smi%20%3D%20Chem.MolFragmentToSmiles(mol_kek%2C%20sorted(atom_set)%2C%20kekuleSmiles%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20smi%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20m%20%3D%20Chem.MolFromSmiles(smi)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20Chem.MolToSmiles(m)%20if%20m%20is%20not%20None%20else%20None%0A%20%20%20%20%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Public%20function%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20def%20decompose_scaffold_network(smiles%3A%20list%5Bstr%5D)%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%20Decompose%20each%20molecule%20into%20three%20exhaustive%20levels%20of%20ring-based%20scaffolds.%0A%0A%20%20%20%20%20%20%20%20**Scaffold%20types%3A**%0A%0A%20%20%20%20%20%20%20%20-%20%60%60ring_system%60%60%3A%20A%20single%20fused%2Fbridged%20ring%20system%20with%20all%20substituents%20removed.%0A%20%20%20%20%20%20%20%20-%20%60%60linked_ring_systems%60%60%3A%20A%20pair%20of%20ring%20systems%20connected%20by%20a%20linker.%0A%20%20%20%20%20%20%20%20-%20%60%60full_scaffold%60%60%3A%20All%20ring%20systems%20%2B%20linkers%20(%E2%89%A5%203%20ring%20systems%2C%20all%20connected).%0A%0A%20%20%20%20%20%20%20%20Molecules%20with%20no%20rings%20produce%20zero%20rows.%20Invalid%20SMILES%20are%20recorded%20with%20a%0A%20%20%20%20%20%20%20%20%60%60parse_error%60%60%20message%20and%20null%20scaffold%20columns.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20smiles%3A%20Input%20SMILES%20strings.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Long-format%20Polars%20DataFrame%20with%20columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20%60%60smiles%60%60%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3A%20original%20input%20SMILES%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20%60%60scaffold_smiles%60%60%20%20%20%20%20%3A%20canonical%20scaffold%20SMILES%20(null%20on%20failure)%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20%60%60scaffold_type%60%60%20%20%20%20%20%20%20%3A%20scaffold%20type%20string%20(null%20on%20failure)%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20%60%60scaffold_heavy_atoms%60%60%3A%20heavy-atom%20count%20of%20the%20scaffold%20(null%20on%20failure)%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20%60%60parse_error%60%60%20%20%20%20%20%20%20%20%20%3A%20error%20message%20or%20null%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20in_smi_col%3A%20%20list%5Bstr%5D%20%20%20%20%20%20%20%20%20%20%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20sc_smi_col%3A%20%20list%5BOptional%5Bstr%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20sc_type_col%3A%20list%5BOptional%5Bstr%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20sc_ha_col%3A%20%20%20list%5BOptional%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20err_col%3A%20%20%20%20%20list%5BOptional%5Bstr%5D%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20smi%20in%20smiles%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mol%20%3D%20Chem.MolFromSmiles(smi)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20mol%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20in_smi_col.append(smi)%3B%20sc_smi_col.append(None)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sc_type_col.append(None)%3B%20sc_ha_col.append(None)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20err_col.append(f%22Invalid%20SMILES%3A%20%7Bsmi!r%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20ring_systems%20%3D%20_build_ring_systems(mol)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20ring_systems%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20acyclic%20%E2%80%94%20no%20scaffold%20rows%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20ring_atom_set%3A%20set%5Bint%5D%20%3D%20set(a%20for%20rs%20in%20ring_systems%20for%20a%20in%20rs)%0A%20%20%20%20%20%20%20%20%20%20%20%20non_ring%3A%20set%5Bint%5D%20%3D%20set(range(mol.GetNumAtoms()))%20-%20ring_atom_set%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20mol_adj%3A%20dict%5Bint%2C%20set%5Bint%5D%5D%20%3D%20defaultdict(set)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20bond%20in%20mol.GetBonds()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20u%2C%20v%20%3D%20bond.GetBeginAtomIdx()%2C%20bond.GetEndAtomIdx()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mol_adj%5Bu%5D.add(v)%3B%20mol_adj%5Bv%5D.add(u)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20seen%3A%20dict%5Bstr%2C%20str%5D%20%3D%20%7B%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20def%20_emit(atom_set%3A%20frozenset%5Bint%5D%2C%20stype%3A%20str)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20csmi%20%3D%20_to_canonical(mol%2C%20atom_set)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20csmi%20and%20csmi%20not%20in%20seen%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seen%5Bcsmi%5D%20%3D%20stype%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Level%201%3A%20individual%20ring%20systems%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20rs%20in%20ring_systems%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_emit(rs%2C%20%22ring_system%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Level%202%3A%20pairs%20of%20ring%20systems%20%2B%20linker%0A%20%20%20%20%20%20%20%20%20%20%20%20linker_map%3A%20dict%5Btuple%5Bint%2C%20int%5D%2C%20Optional%5Bfrozenset%5Bint%5D%5D%5D%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20rs_connected%3A%20dict%5Bint%2C%20set%5Bint%5D%5D%20%3D%20defaultdict(set)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20i%2C%20rs1%20in%20enumerate(ring_systems)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20j%2C%20rs2%20in%20enumerate(ring_systems)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20j%20%3C%3D%20i%3A%0A%20%20%20%20%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%20%20%20%20%20linker%20%3D%20_linker_atoms(mol_adj%2C%20rs1%2C%20rs2%2C%20non_ring)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20linker_map%5B(i%2C%20j)%5D%20%3D%20linker%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20linker%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rs_connected%5Bi%5D.add(j)%3B%20rs_connected%5Bj%5D.add(i)%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_emit(rs1%20%7C%20rs2%20%7C%20linker%2C%20%22linked_ring_systems%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Level%203%3A%20full%20scaffold%20(%E2%89%A5%203%20ring%20systems%2C%20all%20connected)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20len(ring_systems)%20%3E%3D%203%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rs_visited%3A%20list%5Bbool%5D%20%3D%20%5BFalse%5D%20*%20len(ring_systems)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20rs_start%20in%20range(len(ring_systems))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20rs_visited%5Brs_start%5D%3A%0A%20%20%20%20%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%20%20%20%20%20comp%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20q%20%3D%20deque(%5Brs_start%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20while%20q%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20q.popleft()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20rs_visited%5Bnode%5D%3A%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%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rs_visited%5Bnode%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20comp.append(node)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20q.extend(rs_connected%5Bnode%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20len(comp)%20%3E%3D%203%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20all_atoms%3A%20frozenset%5Bint%5D%20%3D%20frozenset(%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%20a%20for%20rs_idx%20in%20comp%20for%20a%20in%20ring_systems%5Brs_idx%5D%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)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20a%2C%20b%20in%20itertools.combinations(comp%2C%202)%3A%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%20lk%20%3D%20linker_map.get((min(a%2C%20b)%2C%20max(a%2C%20b)))%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%20if%20lk%3A%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%20all_atoms%20%3D%20all_atoms%20%7C%20lk%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_emit(all_atoms%2C%20%22full_scaffold%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20csmi%2C%20stype%20in%20seen.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sc_mol%20%3D%20Chem.MolFromSmiles(csmi)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ha%20%3D%20sc_mol.GetNumHeavyAtoms()%20if%20sc_mol%20is%20not%20None%20else%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20in_smi_col.append(smi)%3B%20sc_smi_col.append(csmi)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sc_type_col.append(stype)%3B%20sc_ha_col.append(ha)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20err_col.append(None)%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%22smiles%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.Series(in_smi_col%2C%20%20dtype%3Dpl.Utf8)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22scaffold_smiles%22%3A%20%20%20%20%20%20pl.Series(sc_smi_col%2C%20%20dtype%3Dpl.Utf8)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22scaffold_type%22%3A%20%20%20%20%20%20%20%20pl.Series(sc_type_col%2C%20dtype%3Dpl.Utf8)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22scaffold_heavy_atoms%22%3A%20pl.Series(sc_ha_col%2C%20%20%20dtype%3Dpl.Int32)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22parse_error%22%3A%20%20%20%20%20%20%20%20%20%20pl.Series(err_col%2C%20%20%20%20%20dtype%3Dpl.Utf8)%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20return%20(decompose_scaffold_network%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%20%20%20%20all_compounds%0A%20%20%20%20return%20(all_compounds%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%20Decompose%20scaffolds%20for%20all%20unique%20molecules%0A%0A%20%20%20%20Each%20unique%20molecule%20(by%20InChIKey)%20is%20decomposed%20into%20ring-based%20scaffolds.%0A%20%20%20%20Deduplication%20before%20the%20call%20avoids%20running%20the%20expensive%20ring-perception%0A%20%20%20%20step%20multiple%20times%20for%20the%20same%20structure.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(all_compounds%2C%20decompose_scaffold_network%2C%20pl)%3A%0A%20%20%20%20_unique_mols%20%3D%20(%0A%20%20%20%20%20%20%20%20all_compounds%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%22in_single_dose%22%2C%20%22in_dose_response%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22in_counter%22%2C%20%22in_test%22%5D)%0A%20%20%20%20%20%20%20%20.drop_nulls(subset%3D%5B%22smiles%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20_scaffold_long%20%3D%20decompose_scaffold_network(%0A%20%20%20%20%20%20%20%20_unique_mols.get_column(%22smiles%22).to_list()%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Join%20dataset%20membership%20flags%20back%20onto%20the%20long-format%20scaffold%20table%0A%20%20%20%20_membership%20%3D%20_unique_mols.select(%0A%20%20%20%20%20%20%20%20%5B%22smiles%22%2C%20%22in_single_dose%22%2C%20%22in_dose_response%22%2C%20%22in_counter%22%2C%20%22in_test%22%5D%0A%20%20%20%20)%0A%0A%20%20%20%20scaffold_hits%20%3D%20(%0A%20%20%20%20%20%20%20%20_scaffold_long%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22parse_error%22).is_null())%0A%20%20%20%20%20%20%20%20.join(_membership%2C%20on%3D%22smiles%22%2C%20how%3D%22left%22)%0A%20%20%20%20)%0A%0A%20%20%20%20scaffold_hits%0A%20%20%20%20return%20(scaffold_hits%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%20Scaffold%20counts%20per%20dataset%0A%0A%20%20%20%20For%20each%20unique%20scaffold%2C%20count%20how%20many%20distinct%20molecules%20(by%20SMILES)%0A%20%20%20%20contain%20it%2C%20broken%20down%20by%20dataset%20membership.%0A%0A%20%20%20%20Because%20a%20molecule%20can%20belong%20to%20multiple%20datasets%2C%20per-dataset%20counts%20can%0A%20%20%20%20sum%20to%20more%20than%20%60n_total%60%20%E2%80%94%20this%20is%20intentional%20and%20informative.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20scaffold_hits)%3A%0A%20%20%20%20scaffold_counts%20%3D%20(%0A%20%20%20%20%20%20%20%20scaffold_hits%0A%20%20%20%20%20%20%20%20%23%20Deduplicate%20within%20molecule%20(safety%20guard)%0A%20%20%20%20%20%20%20%20.unique(subset%3D%5B%22scaffold_smiles%22%2C%20%22smiles%22%5D)%0A%20%20%20%20%20%20%20%20.group_by(%5B%22scaffold_smiles%22%2C%20%22scaffold_type%22%5D)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22scaffold_heavy_atoms%22).first().alias(%22scaffold_heavy_atoms%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22smiles%22).n_unique().alias(%22n_total%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22in_single_dose%22)%20.filter(pl.col(%22in_single_dose%22))%20.len().alias(%22n_single_dose%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22in_dose_response%22).filter(pl.col(%22in_dose_response%22)).len().alias(%22n_dose_response%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22in_counter%22)%20%20%20%20%20.filter(pl.col(%22in_counter%22))%20%20%20%20%20%20.len().alias(%22n_counter%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22in_test%22)%20%20%20%20%20%20%20%20.filter(pl.col(%22in_test%22))%20%20%20%20%20%20%20%20%20.len().alias(%22n_test%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.select(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22scaffold_smiles%22%2C%20%22scaffold_type%22%2C%20%22scaffold_heavy_atoms%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22n_total%22%2C%20%22n_single_dose%22%2C%20%22n_dose_response%22%2C%20%22n_counter%22%2C%20%22n_test%22%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20.sort(%5B%22scaffold_type%22%2C%20%22n_total%22%5D%2C%20descending%3D%5BFalse%2C%20True%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20scaffold_counts%0A%20%20%20%20return%20(scaffold_counts%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%20Scaffold%20coverage%20of%20the%20test%20set%20by%20dose-response%20training%20compounds%0A%0A%20%20%20%20Each%20point%20represents%20a%20scaffold%20present%20in%20**at%20least%20one%20test%20compound**%20and%0A%20%20%20%20**at%20least%20one%20dose-response%20training%20compound**.%0A%0A%20%20%20%20-%20**x-axis**%20%E2%80%94%20number%20of%20dose-response%20training%20molecules%20containing%20this%20scaffold%0A%20%20%20%20-%20**y-axis**%20%E2%80%94%20number%20of%20test%20molecules%20containing%20this%20scaffold%0A%20%20%20%20-%20**colour**%20%E2%80%94%20scaffold%20heavy-atom%20count%0A%0A%20%20%20%20Scaffolds%20sitting%20high%20on%20the%20y-axis%20but%20far%20left%20are%20test-set%20enriched%20(low%0A%20%20%20%20training%20coverage)%3B%20scaffolds%20far%20right%20and%20low%20are%20training-enriched.%0A%20%20%20%20Hover%20over%20a%20point%20to%20see%20the%20scaffold%20structure%20drawn%20on%20the%20right.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(alt%2C%20mo%2C%20pl%2C%20scaffold_counts)%3A%0A%20%20%20%20_plot_df%20%3D%20scaffold_counts.filter(%0A%20%20%20%20%20%20%20%20(pl.col(%22n_test%22)%20%3E%3D%201)%20%26%20(pl.col(%22n_dose_response%22)%20%3E%3D%201)%0A%20%20%20%20)%0A%0A%20%20%20%20_sel%20%3D%20alt.selection_point(fields%3D%5B%22scaffold_smiles%22%5D%2C%20name%3D%22scaffold_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(_plot_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(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22n_dose_response%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Dose-response%20training%20molecules%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(type%3D%22log%22%2C%20base%3D10%2C%20domain%3D%5B0.9%2C%205000%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%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20values%3D%5B1%2C%2010%2C%20100%2C%201000%2C%205000%5D%2C%20format%3D%22~s%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%20y%3Dalt.Y(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22n_test%3AQ%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Test-set%20molecules%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale%3Dalt.Scale(type%3D%22log%22%2C%20base%3D10%2C%20domain%3D%5B0.9%2C%20500%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%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20values%3D%5B1%2C%2010%2C%20100%2C%20500%5D%2C%20format%3D%22~s%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%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(%22scaffold_heavy_atoms%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%22Heavy%20atoms%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(150)%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(%22scaffold_smiles%3AN%22%2C%20%20%20%20%20%20title%3D%22Scaffold%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22scaffold_type%3AN%22%2C%20%20%20%20%20%20%20%20title%3D%22Type%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22scaffold_heavy_atoms%3AQ%22%2C%20title%3D%22Heavy%20atoms%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22n_dose_response%3AQ%22%2C%20%20%20%20%20%20title%3D%22%23%20dose-response%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22n_test%3AQ%22%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22%23%20test%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt.Tooltip(%22n_total%3AQ%22%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22%23%20total%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%22Shared%20scaffolds%20%E2%80%94%20dose-response%20training%20vs.%20test%20set%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%20scaffold_coverage_chart%20%3D%20mo.ui.altair_chart(_scatter)%0A%20%20%20%20return%20(scaffold_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%20scaffold_counts%2C%0A%20%20%20%20scaffold_coverage_chart%2C%0A)%3A%0A%20%20%20%20_PANEL_W%20%3D%20300%0A%20%20%20%20_PANEL_H%20%3D%20260%0A%0A%20%20%20%20def%20_scaffold_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%20scaffold%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%20scaffold_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%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'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Hover%20over%20a%20point%20to%20see%20the%20scaffold%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_scaffold_smi%20%3D%20_sel_rows.row(0%2C%20named%3DTrue)%5B%22scaffold_smiles%22%5D%0A%20%20%20%20%20%20%20%20_r%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20scaffold_counts%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(%22scaffold_smiles%22)%20%3D%3D%20_scaffold_smi)%0A%20%20%20%20%20%20%20%20%20%20%20%20.row(0%2C%20named%3DTrue)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_svg%20%3D%20_scaffold_to_svg(_r%5B%22scaffold_smiles%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%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%23f5f5f5%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.6'%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cb%3EType%3A%3C%2Fb%3E%20%7B_r%5B'scaffold_type'%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%3EHeavy%20atoms%3A%3C%2Fb%3E%20%7B_r%5B'scaffold_heavy_atoms'%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%3E%23%20dose-response%3A%3C%2Fb%3E%20%7B_r%5B'n_dose_response'%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%3E%23%20test%3A%3C%2Fb%3E%20%7B_r%5B'n_test'%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%3E%23%20total%3A%3C%2Fb%3E%20%7B_r%5B'n_total'%5D%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(%5Bscaffold_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%20This%20plot%20finally%20killed%20a%20misconception%20I%20had%20about%20the%20test%20dataset.%20As%20I%20had%20seen%20them%20called%0A%20%20%20%20analog%20sets%2C%20my%20mind%20went%20directly%20to%20analog%20series.%20So%20I%20was%20expecting%20the%20test%20set%20to%20be%20based%20on%0A%20%20%20%20one%20or%20a%20small%20number%20of%20unique%20scaffolds%2C%20using%20a%20small%20set%20of%20hit%20compounds%20as%20reference.%0A%20%20%20%20That%20is%20not%20the%20case.%20Although%2C%20the%20test%20set%20molecules%20are%20highly%20similar%20to%20at%20least%20one%20compound%20in%0A%20%20%20%20the%20training%20set%20as%20we%20saw%20in%20notebook%201d%2C%20they%20are%20themselves%20fairly%20diverse%20as%20we%20saw%20in%20notebook%201b.%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
3d84dfce8a096555c0bab96c0aed5ad6