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%201a%20%E2%80%94%20Data%20ingestion%20and%20preprocessing%0A%0A%20%20%20%20Reads%20the%20four%20raw%20CSV%20datasets%2C%20computes%20InChIKeys%20and%20InChIs%2C%20pivots%20and%0A%20%20%20%20enriches%20the%20single-dose%20screen%2C%20and%20produces%20the%20combined%20%60all_compounds%60%0A%20%20%20%20DataFrame%20that%20is%20written%20to%20%60data%2Fprocessed%2Fall_compounds_activity_data.csv%60.%0A%0A%20%20%20%20All%20downstream%20notebooks%20(1b%E2%80%931e)%20read%20that%20CSV%20as%20their%20starting%20point.%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%20subprocess%0A%20%20%20%20import%20sys%0A%20%20%20%20import%20base64%0A%20%20%20%20from%20pathlib%20import%20Path%0A%20%20%20%20from%20typing%20import%20Optional%0A%0A%20%20%20%20from%20rdkit%20import%20Chem%2C%20RDLogger%0A%0A%20%20%20%20%23%20Suppress%20RDKit%20InChI%20warnings%0A%20%20%20%20RDLogger.DisableLog(%22rdApp.*%22)%0A%20%20%20%20return%20Chem%2C%20Optional%2C%20Path%2C%20mo%2C%20pl%2C%20subprocess%2C%20sys%0A%0A%0A%40app.cell%0Adef%20_(Chem%2C%20Optional)%3A%0A%20%20%20%20def%20smi_to_inchikey(smi%3A%20str)%20-%3E%20Optional%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20InChIKey%20for%20*smi*%2C%20or%20None%20on%20parse%20failure.%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%20return%20Chem.MolToInchiKey(mol)%20if%20mol%20else%20None%0A%0A%20%20%20%20def%20smi_to_inchi(smi%3A%20str)%20-%3E%20Optional%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20InChI%20for%20*smi*%2C%20or%20None%20on%20parse%20failure.%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%20return%20Chem.MolToInchi(mol)%20if%20mol%20else%20None%0A%0A%20%20%20%20return%20smi_to_inchi%2C%20smi_to_inchikey%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20smi_to_inchi%2C%20smi_to_inchikey)%3A%0A%20%20%20%20def%20process_dataset(df%3A%20pl.DataFrame)%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%20Standardise%20a%20raw%20dataset%20CSV%20into%20a%20common%20schema.%0A%0A%20%20%20%20%20%20%20%20-%20Renames%20the%20%60%60SMILES%60%60%20column%20to%20lowercase%20%60%60smiles%60%60.%0A%20%20%20%20%20%20%20%20-%20Appends%20%60%60inchikey%60%60%20and%20%60%60inchi%60%60%20columns%20computed%20via%20RDKit.%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%20Raw%20Polars%20DataFrame%20loaded%20from%20one%20of%20the%20challenge%20CSVs.%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%20added%20%60%60smiles%60%60%2C%20%60%60inchikey%60%60%2C%20and%20%60%60inchi%60%60%20columns.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20return%20df.rename(%7B%22SMILES%22%3A%20%22smiles%22%7D).with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22smiles%22).map_elements(smi_to_inchikey%2C%20return_dtype%3Dpl.Utf8).alias(%22inchikey%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22smiles%22).map_elements(smi_to_inchi%2C%20%20%20%20return_dtype%3Dpl.Utf8).alias(%22inchi%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20return%20(process_dataset%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%20Read%20raw%20datasets%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20process_dataset)%3A%0A%20%20%20%20train%20%20%20%20%20%20%20%20%20%3D%20process_dataset(pl.read_csv(%22..%2Fdata%2Fraw%2F20260409%2Fdose_response_train.csv%22))%0A%20%20%20%20test%20%20%20%20%20%20%20%20%20%20%3D%20process_dataset(pl.read_csv(%22..%2Fdata%2Fraw%2F20260409%2Fdose_response_test.csv%22))%0A%20%20%20%20train_counter%20%3D%20process_dataset(pl.read_csv(%22..%2Fdata%2Fraw%2F20260409%2Fcounter_screen_train.csv%22))%0A%20%20%20%20train_single%20%20%3D%20process_dataset(pl.read_csv(%22..%2Fdata%2Fraw%2F20260409%2Fsingle_dose_train.csv%22))%0A%20%20%20%20return%20test%2C%20train%2C%20train_counter%2C%20train_single%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%20Pivot%20single-dose%20dataset%20by%20concentration%0A%0A%20%20%20%20Each%20molecule%20(identified%20by%20%60inchikey%60)%20may%20have%20been%20tested%20at%20multiple%20concentrations.%0A%20%20%20%20Here%20we%3A%0A%20%20%20%201.%20Aggregate%20molecule%20identifiers%3A%20keep%20%60smiles%60%2C%20%60inchi%60%2C%20%60inchikey%60%20as%20unique%20entities%2C%0A%20%20%20%20%20%20%20concatenate%20all%20%60Molecule%20Name%60%20and%20%60OCNT%20Batch%60%20values%20seen%20for%20that%20molecule.%0A%20%20%20%202.%20Add%20a%20boolean%20%60is_hit%60%20flag%20per%20row%3A%20%60median_log2_fc%20%3E%201%60%20AND%20%60fdr_bh%20%3C%200.05%60.%0A%20%20%20%203.%20Pivot%20so%20that%20each%20%60concentration_uM%60%20becomes%20its%20own%20set%20of%20columns%2C%20keeping%0A%20%20%20%20%20%20%20the%20**median**%20of%20%60median_log2_fc%60%20and%20the%20%60is_hit%60%20flag%20(true%20if%20any%20replicate%0A%20%20%20%20%20%20%20is%20a%20hit%20at%20that%20concentration).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20train_single)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Step%201%3A%20build%20the%20identifier%20lookup%20(smiles%20%2F%20inchi%20%2F%20inchikey)%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%0A%20%20%20%20%23%20train_single%20already%20has%20inchi%20and%20inchikey%20added%20by%20process_dataset()%0A%0A%20%20%20%20%23%20Aggregate%20molecule-level%20metadata%3A%20one%20row%20per%20inchikey%0A%20%20%20%20_mol_meta%20%3D%20(%0A%20%20%20%20%20%20%20%20train_single%0A%20%20%20%20%20%20%20%20.group_by(%22inchikey%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22smiles%22).first()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22inchi%22).first()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Molecule%20Name%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.drop_nulls()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.unique()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.sort()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.str.join(%22%7C%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22molecule_names%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22OCNT%20Batch%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.drop_nulls()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.unique()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.sort()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.str.join(%22%7C%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22ocnt_batches%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Nominal%20concentration%20labels%20(%C2%B5M)%3A%20map%20messy%20float%20values%20to%20round%20numbers%0A%20%20%20%20_conc_labels%20%3D%20%7B99.01%3A%20100%2C%2033.0%3A%2030%2C%208.251%3A%2010%2C%200.9803%3A%201%7D%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Step%202%3A%20add%20is_hit%20flag%2C%20then%20aggregate%20per%20(inchikey%2C%20concentration_uM)%20%E2%94%80%E2%94%80%0A%20%20%20%20_agg_values%20%3D%20(%0A%20%20%20%20%20%20%20%20train_single%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20(10**6%20*%20pl.col(%22concentration_M%22)).round(4).alias(%22concentration_uM%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20((pl.col(%22median_log2_fc%22)%20%3E%201)%20%26%20(pl.col(%22fdr_bh%22)%20%3C%200.05)).alias(%22is_hit%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.with_columns(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22concentration_uM%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.replace(_conc_labels)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22concentration_uM%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.group_by(%5B%22inchikey%22%2C%20%22concentration_uM%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(%22median_log2_fc%22).median()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22is_hit%22).any()%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Step%203%3A%20pivot%20so%20each%20concentration%20becomes%20its%20own%20column%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%0A%20%20%20%20_pivoted_fc%20%3D%20_agg_values.pivot(%0A%20%20%20%20%20%20%20%20on%3D%22concentration_uM%22%2C%0A%20%20%20%20%20%20%20%20index%3D%22inchikey%22%2C%0A%20%20%20%20%20%20%20%20values%3D%22median_log2_fc%22%2C%0A%20%20%20%20%20%20%20%20aggregate_function%3D%22median%22%2C%0A%20%20%20%20)%0A%20%20%20%20_pivoted_hit%20%3D%20_agg_values.pivot(%0A%20%20%20%20%20%20%20%20on%3D%22concentration_uM%22%2C%0A%20%20%20%20%20%20%20%20index%3D%22inchikey%22%2C%0A%20%20%20%20%20%20%20%20values%3D%22is_hit%22%2C%0A%20%20%20%20%20%20%20%20aggregate_function%3D%22first%22%2C%0A%20%20%20%20)%0A%20%20%20%20_pivoted%20%3D%20_pivoted_fc.join(_pivoted_hit%2C%20on%3D%22inchikey%22%2C%20how%3D%22left%22%2C%20suffix%3D%22_is_hit%22)%0A%20%20%20%20_pivoted%20%3D%20_pivoted.rename(%0A%20%20%20%20%20%20%20%20%7Bc%3A%20f%22%7Bc%7D_log2_fc%22%20for%20c%20in%20_pivoted.columns%20if%20c%20!%3D%20%22inchikey%22%20and%20%22_is_hit%22%20not%20in%20c%7D%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Step%204%3A%20join%20molecule%20metadata%20back%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%0A%20%20%20%20train_single_pivoted%3A%20pl.DataFrame%20%3D%20_mol_meta.join(%0A%20%20%20%20%20%20%20%20_pivoted%2C%20on%3D%22inchikey%22%2C%20how%3D%22left%22%0A%20%20%20%20)%0A%0A%20%20%20%20train_single_pivoted%0A%20%20%20%20return%20(train_single_pivoted%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%20Enrich%20single-dose%20pivot%20with%20dose-response%20and%20counter-screen%20activity%0A%0A%20%20%20%20Join%20%60pEC50%60%20and%20%60Emax_estimate%60%20from%3A%0A%20%20%20%20-%20**train**%20(primary%20dose-response%20assay)%20%E2%86%92%20suffixed%20%60_dr%60%0A%20%20%20%20-%20**train_counter**%20(counter-screen%20assay)%20%E2%86%92%20suffixed%20%60_counter%60%0A%0A%20%20%20%20Both%20datasets%20are%20first%20reduced%20to%20one%20row%20per%20%60inchikey%60%20(median%20of%20the%20two%0A%20%20%20%20activity%20columns)%20before%20joining%2C%20to%20guard%20against%20any%20duplicate%20records.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20train%2C%20train_counter%2C%20train_single_pivoted%3A%20%22pl.DataFrame%22)%3A%0A%20%20%20%20_emax_col%20%3D%20%22Emax_estimate%20(log2FC%20vs.%20baseline)%22%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Dose-response%3A%20one%20row%20per%20inchikey%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%0A%20%20%20%20_dr%20%3D%20(%0A%20%20%20%20%20%20%20%20train%0A%20%20%20%20%20%20%20%20.group_by(%22inchikey%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22smiles%22).first()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22inchi%22).first()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Molecule%20Name%22).drop_nulls().unique().sort().str.join(%22%7C%22).alias(%22molecule_names%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22OCNT%20Batch%22).drop_nulls().unique().sort().str.join(%22%7C%22).alias(%22ocnt_batches%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22pEC50%22).median().alias(%22pEC50_dr%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(_emax_col).median().alias(%22Emax_dr%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Counter-screen%3A%20one%20row%20per%20inchikey%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%0A%20%20%20%20_counter%20%3D%20(%0A%20%20%20%20%20%20%20%20train_counter%0A%20%20%20%20%20%20%20%20.group_by(%22inchikey%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22smiles%22).first()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22inchi%22).first()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Molecule%20Name%22).drop_nulls().unique().sort().str.join(%22%7C%22).alias(%22molecule_names%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22OCNT%20Batch%22).drop_nulls().unique().sort().str.join(%22%7C%22).alias(%22ocnt_batches%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22pEC50%22).median().alias(%22pEC50_counter%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(_emax_col).median().alias(%22Emax_counter%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Join%20both%20onto%20the%20pivoted%20single-dose%20table%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%0A%20%20%20%20_meta_cols%20%3D%20%5B%22smiles%22%2C%20%22inchi%22%2C%20%22molecule_names%22%2C%20%22ocnt_batches%22%5D%0A%0A%20%20%20%20train_single_enriched%20%3D%20(%0A%20%20%20%20%20%20%20%20train_single_pivoted%0A%20%20%20%20%20%20%20%20.join(_dr%2C%20on%3D%22inchikey%22%2C%20how%3D%22full%22%2C%20coalesce%3DTrue%2C%20suffix%3D%22_dr_r%22)%0A%20%20%20%20%20%20%20%20.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.coalesce(%5Bc%2C%20f%22%7Bc%7D_dr_r%22%5D).alias(c)%20for%20c%20in%20_meta_cols%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20.drop(%5Bf%22%7Bc%7D_dr_r%22%20for%20c%20in%20_meta_cols%5D)%0A%20%20%20%20%20%20%20%20.join(_counter%2C%20on%3D%22inchikey%22%2C%20how%3D%22full%22%2C%20coalesce%3DTrue%2C%20suffix%3D%22_ctr_r%22)%0A%20%20%20%20%20%20%20%20.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.coalesce(%5Bc%2C%20f%22%7Bc%7D_ctr_r%22%5D).alias(c)%20for%20c%20in%20_meta_cols%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20.drop(%5Bf%22%7Bc%7D_ctr_r%22%20for%20c%20in%20_meta_cols%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20train_single_enriched%0A%20%20%20%20return%20(train_single_enriched%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%20Full%20chemical%20space%0A%0A%20%20%20%20Combine%20all%20four%20datasets%20into%20one%20table%2C%20adding%20boolean%20membership%20flags%3A%0A%20%20%20%20-%20%60in_single_dose%60%20%E2%80%94%20compound%20was%20in%20the%20single-dose%20screen%0A%20%20%20%20-%20%60in_dose_response%60%20%E2%80%94%20compound%20was%20in%20the%20dose-response%20screen%0A%20%20%20%20-%20%60in_counter%60%20%E2%80%94%20compound%20was%20in%20the%20counter-screen%0A%20%20%20%20-%20%60in_test%60%20%E2%80%94%20compound%20is%20in%20the%20blinded%20test%20set%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pl%2C%20test%2C%20train%2C%20train_counter%2C%20train_single_enriched)%3A%0A%20%20%20%20_meta_cols%20%3D%20%5B%22smiles%22%2C%20%22inchi%22%2C%20%22molecule_names%22%5D%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Collect%20the%20inchikey%20sets%20for%20membership%20flags%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%0A%20%20%20%20_single_keys%20%20%3D%20set(train_single_enriched.get_column(%22inchikey%22).drop_nulls().to_list())%0A%20%20%20%20_dr_keys%20%20%20%20%20%20%3D%20set(train.get_column(%22inchikey%22).drop_nulls().to_list())%0A%20%20%20%20_counter_keys%20%3D%20set(train_counter.get_column(%22inchikey%22).drop_nulls().to_list())%0A%20%20%20%20_test_keys%20%20%20%20%3D%20set(test.get_column(%22inchikey%22).drop_nulls().to_list())%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Slim%20test%20table%3A%20one%20row%20per%20inchikey%20with%20metadata%20only%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%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.group_by(%22inchikey%22)%0A%20%20%20%20%20%20%20%20.agg(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22smiles%22).first()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22inchi%22).first()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22Molecule%20Name%22).drop_nulls().unique().sort().str.join(%22%7C%22).alias(%22molecule_names%22)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Full%20outer%20join%20of%20test%20onto%20the%20enriched%20table%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%0A%20%20%20%20_full%20%3D%20(%0A%20%20%20%20%20%20%20%20train_single_enriched%0A%20%20%20%20%20%20%20%20.join(_test_meta%2C%20on%3D%22inchikey%22%2C%20how%3D%22full%22%2C%20coalesce%3DTrue%2C%20suffix%3D%22_tst_r%22)%0A%20%20%20%20%20%20%20%20.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.coalesce(%5Bc%2C%20f%22%7Bc%7D_tst_r%22%5D).alias(c)%20for%20c%20in%20_meta_cols%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20.drop(%5Bf%22%7Bc%7D_tst_r%22%20for%20c%20in%20_meta_cols%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Add%20boolean%20membership%20flags%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%0A%20%20%20%20all_compounds%20%3D%20_full.with_columns(%0A%20%20%20%20%20%20%20%20pl.col(%22inchikey%22).is_in(_single_keys).alias(%22in_single_dose%22)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22inchikey%22).is_in(_dr_keys).alias(%22in_dose_response%22)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22inchikey%22).is_in(_counter_keys).alias(%22in_counter%22)%2C%0A%20%20%20%20%20%20%20%20pl.col(%22inchikey%22).is_in(_test_keys).alias(%22in_test%22)%2C%0A%20%20%20%20)%0A%0A%20%20%20%20all_compounds%0A%20%20%20%20return%20(all_compounds%2C)%0A%0A%0A%40app.cell%0Adef%20_(all_compounds)%3A%0A%20%20%20%20%22%22%22Write%20the%20combined%20dataset%20to%20disk%20for%20consumption%20by%20notebooks%201b%E2%80%931e.%22%22%22%0A%20%20%20%20all_compounds.write_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%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%20Compute%20MMP%20input%20file%0A%0A%20%20%20%20Canonicalise%20SMILES%20through%20RDKit%20(strips%20CXSMILES%20extensions%20that%20confuse%0A%20%20%20%20%60mmpdb%60)%20then%20write%20the%20two-column%20%60.smi%60%20file%20required%20by%20%60mmpdb%20fragment%60.%0A%20%20%20%20If%20the%20stripping%20of%20CXSMILES%20is%20not%20done%2C%20it%20will%20trip%20up%20mmpdb%20and%20cause%20issues.%0A%20%20%20%20Probably%20another%20way%20to%20solve%20would%20have%20been%20to%20use%20a%20different%20separator%20in%20the%20%60.smi%60%0A%20%20%20%20file%20and%20adapt%20the%20call%20to%20%60mmpdb%20fragment%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Chem%2C%20all_compounds%2C%20pl)%3A%0A%20%20%20%20_canonical%20%3D%20all_compounds.with_columns(%0A%20%20%20%20%20%20%20%20pl.col(%22smiles%22)%0A%20%20%20%20%20%20%20%20%20%20.map_elements(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20lambda%20s%3A%20Chem.MolToSmiles(Chem.MolFromSmiles(s))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20return_dtype%3Dpl.Utf8%2C%0A%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20.alias(%22smiles_canonical%22)%0A%20%20%20%20)%0A%0A%20%20%20%20_canonical.select(%5B%22smiles_canonical%22%2C%20%22inchikey%22%5D).write_csv(%0A%20%20%20%20%20%20%20%20%22..%2Fdata%2Fprocessed%2Fall_compounds_mmp.smi%22%2C%0A%20%20%20%20%20%20%20%20separator%3D%22%20%22%2C%0A%20%20%20%20%20%20%20%20include_header%3DFalse%2C%0A%20%20%20%20%20%20%20%20quote_style%3D%22never%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%20Run%20mmpdb%3A%20fragment%20%2B%20index%0A%0A%20%20%20%20Two%20steps%20using%20%5Bmmpdb%5D(https%3A%2F%2Fgithub.com%2Frdkit%2Fmmpdb)%3A%0A%0A%20%20%20%201.%20**fragment**%20%E2%80%94%20breaks%20each%20molecule%20at%20single%20rotatable%20bonds%3B%20writes%20a%20%60.frag%60%20file.%0A%20%20%20%202.%20**index**%20%E2%80%94%20finds%20all%20matched%20molecular%20pairs%20sharing%20the%20same%20core%3B%20writes%20a%20%60.csv.gz%60.%0A%0A%20%20%20%20Both%20steps%20are%20skipped%20automatically%20if%20the%20output%20file%20already%20exists.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20mo%2C%20subprocess%2C%20sys)%3A%0A%20%20%20%20_smi%20%20%3D%20%22..%2Fdata%2Fprocessed%2Fall_compounds_mmp.smi%22%0A%20%20%20%20_frag%20%3D%20%22..%2Fdata%2Fprocessed%2Fall_compounds_mmp.frag%22%0A%0A%20%20%20%20if%20Path(_frag).exists()%3A%0A%20%20%20%20%20%20%20%20mo.md(f%22**fragment**%20skipped%20%E2%80%94%20%60%7B_frag%7D%60%20already%20exists%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_result%20%3D%20subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bsys.executable%2C%20%22-m%22%2C%20%22mmpdblib%22%2C%20%22fragment%22%2C%20_smi%2C%20%22-o%22%2C%20_frag%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20text%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20_result.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(f%22mmpdb%20fragment%20failed%3A%5Cn%7B_result.stderr%7D%22)%0A%20%20%20%20%20%20%20%20mo.md(f%22**fragment**%20finished%20%E2%80%94%20output%3A%20%60%7B_frag%7D%60%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20mo%2C%20subprocess%2C%20sys)%3A%0A%20%20%20%20_frag%20%20%3D%20%22..%2Fdata%2Fprocessed%2Fall_compounds_mmp.frag%22%0A%20%20%20%20_mmpdb%20%3D%20%22..%2Fdata%2Fprocessed%2Fall_compounds_mmp.mmp.csv.gz%22%0A%0A%20%20%20%20if%20Path(_mmpdb).exists()%3A%0A%20%20%20%20%20%20%20%20mo.md(f%22**index**%20skipped%20%E2%80%94%20%60%7B_mmpdb%7D%60%20already%20exists%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_result%20%3D%20subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bsys.executable%2C%20%22-m%22%2C%20%22mmpdblib%22%2C%20%22index%22%2C%20_frag%2C%20%22-out%20csv.gz%22%2C%20%22-o%22%2C%20_mmpdb%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20text%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20_result.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(f%22mmpdb%20index%20failed%3A%5Cn%7B_result.stderr%7D%22)%0A%20%20%20%20%20%20%20%20mo.md(f%22**index**%20finished%20%E2%80%94%20MMP%20database%3A%20%60%7B_mmpdb%7D%60%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
20cab94dad6baa91fa98ab9684876cd0