import%20marimo%0A%0A__generated_with%20%3D%20%220.23.2%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%20Imports%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%20logging%0A%20%20%20%20import%20joblib%0A%20%20%20%20import%20gc%0A%20%20%20%20import%20math%0A%20%20%20%20import%20warnings%0A%20%20%20%20from%20abc%20import%20ABC%2C%20abstractmethod%0A%20%20%20%20from%20pathlib%20import%20Path%0A%20%20%20%20from%20typing%20import%20Iterator%2C%20Literal%2C%20Optional%0A%20%20%20%20from%20urllib.request%20import%20urlretrieve%0A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20numpy%20as%20np%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%20pingouin%20as%20pg%0A%20%20%20%20import%20seaborn%20as%20sns%0A%0A%20%20%20%20from%20scipy%20import%20stats%0A%20%20%20%20from%20scipy.stats%20import%20spearmanr%0A%20%20%20%20from%20sklearn.metrics%20import%20(%0A%20%20%20%20%20%20%20%20accuracy_score%2C%0A%20%20%20%20%20%20%20%20r2_score%2C%0A%20%20%20%20%20%20%20%20balanced_accuracy_score%2C%0A%20%20%20%20%20%20%20%20f1_score%2C%0A%20%20%20%20%20%20%20%20matthews_corrcoef%2C%0A%20%20%20%20%20%20%20%20mean_absolute_error%2C%0A%20%20%20%20%20%20%20%20mean_squared_error%2C%0A%20%20%20%20%20%20%20%20precision_score%2C%0A%20%20%20%20%20%20%20%20recall_score%2C%0A%20%20%20%20%20%20%20%20roc_auc_score%2C%0A%20%20%20%20)%0A%20%20%20%20from%20sklearn.model_selection._split%20import%20_BaseKFold%20as%20BaseKFold%0A%20%20%20%20from%20statsmodels.stats.anova%20import%20AnovaRM%0A%20%20%20%20from%20statsmodels.stats.libqsturng%20import%20psturng%2C%20qsturng%0A%0A%20%20%20%20from%20sklearn.ensemble%20import%20RandomForestClassifier%2C%20RandomForestRegressor%0A%20%20%20%20import%20xgboost%20as%20xgb%0A%0A%20%20%20%20import%20torch%0A%20%20%20%20from%20torch%20import%20nn%2C%20optim%0A%20%20%20%20from%20torch.functional%20import%20F%0A%20%20%20%20from%20torch.utils.data%20import%20DataLoader%0A%0A%20%20%20%20import%20lightning%20as%20L%0A%20%20%20%20from%20lightning%20import%20pytorch%20as%20pyl%0A%20%20%20%20from%20lightning.pytorch.callbacks.early_stopping%20import%20EarlyStopping%0A%0A%20%20%20%20from%20chemprop%20import%20data%2C%20featurizers%2C%20models%0A%20%20%20%20from%20chemprop%20import%20nn%20as%20chemnn%0A%20%20%20%20from%20chemprop.data%20import%20BatchMolGraph%0A%20%20%20%20from%20chemprop.models%20import%20MPNN%0A%20%20%20%20from%20chemprop.nn%20import%20RegressionFFN%0A%20%20%20%20from%20rdkit.Chem%20import%20MolFromSmiles%0A%0A%20%20%20%20from%20rdkit%20import%20Chem%0A%20%20%20%20from%20rdkit.Chem.Scaffolds%20import%20MurckoScaffold%0A%0A%20%20%20%20from%20skfp.preprocessing%20import%20ConformerGenerator%2C%20MolFromSmilesTransformer%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%20E3FPFingerprint%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%0A%20%20%20%20import%20gzip%0A%20%20%20%20import%20shutil%0A%20%20%20%20import%20subprocess%0A%20%20%20%20import%20sys%0A%20%20%20%20import%20tempfile%0A%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20matplotlib.patches%20as%20mpatches%0A%20%20%20%20from%20tqdm.auto%20import%20tqdm%0A%20%20%20%20from%20typing%20import%20Iterable%0A%0A%20%20%20%20from%20rdkit%20import%20DataStructs%0A%20%20%20%20from%20rdkit.DataStructs%20import%20ExplicitBitVect%0A%0A%20%20%20%20import%20optuna%0A%20%20%20%20optuna.logging.set_verbosity(optuna.logging.WARNING)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20AnovaRM%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%20BaseKFold%2C%0A%20%20%20%20%20%20%20%20BatchMolGraph%2C%0A%20%20%20%20%20%20%20%20ConformerGenerator%2C%0A%20%20%20%20%20%20%20%20E3FPFingerprint%2C%0A%20%20%20%20%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20Iterable%2C%0A%20%20%20%20%20%20%20%20Iterator%2C%0A%20%20%20%20%20%20%20%20MACCSFingerprint%2C%0A%20%20%20%20%20%20%20%20MPNN%2C%0A%20%20%20%20%20%20%20%20MQNsFingerprint%2C%0A%20%20%20%20%20%20%20%20MolFromSmiles%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%20RandomForestClassifier%2C%0A%20%20%20%20%20%20%20%20RandomForestRegressor%2C%0A%20%20%20%20%20%20%20%20RegressionFFN%2C%0A%20%20%20%20%20%20%20%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20%20%20%20%20accuracy_score%2C%0A%20%20%20%20%20%20%20%20balanced_accuracy_score%2C%0A%20%20%20%20%20%20%20%20chemnn%2C%0A%20%20%20%20%20%20%20%20f1_score%2C%0A%20%20%20%20%20%20%20%20featurizers%2C%0A%20%20%20%20%20%20%20%20gc%2C%0A%20%20%20%20%20%20%20%20gzip%2C%0A%20%20%20%20%20%20%20%20math%2C%0A%20%20%20%20%20%20%20%20matthews_corrcoef%2C%0A%20%20%20%20%20%20%20%20mean_absolute_error%2C%0A%20%20%20%20%20%20%20%20mean_squared_error%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%20pd%2C%0A%20%20%20%20%20%20%20%20pg%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%20precision_score%2C%0A%20%20%20%20%20%20%20%20psturng%2C%0A%20%20%20%20%20%20%20%20qsturng%2C%0A%20%20%20%20%20%20%20%20r2_score%2C%0A%20%20%20%20%20%20%20%20recall_score%2C%0A%20%20%20%20%20%20%20%20roc_auc_score%2C%0A%20%20%20%20%20%20%20%20shutil%2C%0A%20%20%20%20%20%20%20%20sns%2C%0A%20%20%20%20%20%20%20%20spearmanr%2C%0A%20%20%20%20%20%20%20%20stats%2C%0A%20%20%20%20%20%20%20%20subprocess%2C%0A%20%20%20%20%20%20%20%20sys%2C%0A%20%20%20%20%20%20%20%20tempfile%2C%0A%20%20%20%20%20%20%20%20torch%2C%0A%20%20%20%20%20%20%20%20tqdm%2C%0A%20%20%20%20%20%20%20%20urlretrieve%2C%0A%20%20%20%20%20%20%20%20warnings%2C%0A%20%20%20%20%20%20%20%20xgb%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20BatchMolGraph%2C%0A%20%20%20%20MPNN%2C%0A%20%20%20%20MolFromSmiles%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20RegressionFFN%2C%0A%20%20%20%20chemnn%2C%0A%20%20%20%20featurizers%2C%0A%20%20%20%20torch%2C%0A%20%20%20%20urlretrieve%2C%0A)%3A%0A%20%20%20%20class%20CheMeleonFingerprint%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Learned%20molecular%20fingerprints%20from%20the%20CheMeleon%20pretrained%20D-MPNN%20backbone.%0A%0A%20%20%20%20%20%20%20%20Wraps%20the%20message-passing%20encoder%20of%20CheMeleon%20and%20extracts%20the%20aggregated%0A%20%20%20%20%20%20%20%20graph-level%20embedding%20(2048-d)%20for%20each%20molecule%20without%20running%20the%0A%20%20%20%20%20%20%20%20task-specific%20FFN%20head.%20Weights%20are%20downloaded%20once%20from%20Zenodo%20and%20cached%0A%20%20%20%20%20%20%20%20at%20~%2F.chemprop%2Fchemeleon_mp.pt.%0A%0A%20%20%20%20%20%20%20%20Usage%3A%3A%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20fp_gen%20%3D%20CheMeleonFingerprint()%0A%20%20%20%20%20%20%20%20%20%20%20%20embeddings%20%3D%20fp_gen(%5B%22CCO%22%2C%20%22c1ccccc1%22%5D)%20%20%23%20np.ndarray%20(2%2C%202048)%0A%0A%20%20%20%20%20%20%20%20The%20instance%20can%20also%20be%20passed%20SMILES%20strings%20directly%2C%20matching%20the%0A%20%20%20%20%20%20%20%20calling%20convention%20of%20scikit-fingerprints%20transformers.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(self%2C%20device%3A%20str%20%7C%20torch.device%20%7C%20None%20%3D%20None)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20device%3A%20PyTorch%20device%20to%20run%20inference%20on.%20If%20None%2C%20uses%20CPU.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Pass%20%22mps%22%20or%20%22cuda%22%20to%20accelerate%20on%20Apple%20Silicon%20%2F%20NVIDIA.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.featurizer%20%3D%20featurizers.SimpleMoleculeMolGraphFeaturizer()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20ckpt_dir%20%3D%20Path.home()%20%2F%20%22.chemprop%22%0A%20%20%20%20%20%20%20%20%20%20%20%20ckpt_dir.mkdir(exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20mp_path%20%3D%20ckpt_dir%20%2F%20%22chemeleon_mp.pt%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Download%20the%20backbone%20weights%20on%20first%20use%20(~30%20MB%2C%20cached%20afterwards)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20mp_path.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(%22Downloading%20CheMeleon%20backbone%20weights%20(~30%20MB)%E2%80%A6%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20urlretrieve(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A%2F%2Fzenodo.org%2Frecords%2F15460715%2Ffiles%2Fchemeleon_mp.pt%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mp_path%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20chemeleon_mp%20%3D%20torch.load(mp_path%2C%20weights_only%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20mp%20%3D%20chemnn.BondMessagePassing(**chemeleon_mp%5B%22hyper_parameters%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20mp.load_state_dict(chemeleon_mp%5B%22state_dict%22%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20MPNN(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20message_passing%3Dmp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20agg%3Dchemnn.MeanAggregation()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20predictor%3DRegressionFFN(input_dim%3Dmp.output_dim)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model.eval()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20device%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model.to(device%3Ddevice)%0A%0A%20%20%20%20%20%20%20%20def%20transform(self%2C%20smiles%3A%20list%5Bstr%5D)%20-%3E%20%22np.ndarray%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Generate%20CheMeleon%20embeddings%20for%20a%20list%20of%20SMILES%20strings.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Mirrors%20the%20scikit-fingerprints%20%60.transform()%60%20API%20so%20this%20class%0A%20%20%20%20%20%20%20%20%20%20%20%20can%20be%20used%20as%20a%20drop-in%20inside%20%60generate_fingerprint%60.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20smiles%3A%20List%20of%20SMILES%20strings.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Float32%20array%20of%20shape%20(n_molecules%2C%202048).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20bmg%20%3D%20BatchMolGraph(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Bself.featurizer(MolFromSmiles(s))%20for%20s%20in%20smiles%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20bmg.to(device%3Dself.model.device)%0A%20%20%20%20%20%20%20%20%20%20%20%20with%20torch.no_grad()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.fingerprint(bmg).numpy(force%3DTrue)%0A%0A%20%20%20%20%20%20%20%20%23%20Expose%20requires_conformers%20so%20generate_fingerprint%20treats%20it%20like%20skfp%20classes%0A%20%20%20%20%20%20%20%20requires_conformers%3A%20bool%20%3D%20False%0A%0A%20%20%20%20return%20(CheMeleonFingerprint%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%20Model%20classes%0A%0A%20%20%20%20Four%20model%20types%20share%20a%20stable%20API%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20model.train(train_df%2C%20target_col%2C%20task%2C%20**kwargs)%0A%20%20%20%20model.predict(df)%20%20%20%20%20%20%20%20%20%20-%3E%20np.ndarray%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%7C%20Class%20%7C%20Backend%20%7C%20Input%20features%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60RandomForestModel%60%20%7C%20sklearn%20RF%20%7C%20fingerprint%20column%20%7C%0A%20%20%20%20%7C%20%60BoostedTreesModel%60%20%7C%20XGBoost%20%7C%20fingerprint%20column%20%7C%0A%20%20%20%20%7C%20%60ChempropModel%60%20%7C%20Chemprop%20v2%20MPNN%20from%20scratch%20%7C%20SMILES%20column%20%7C%0A%20%20%20%20%7C%20%60ChempropChemeleonModel%60%20%7C%20Chemprop%20v2%20fine-tuned%20from%20%5BCheMeleon%5D(https%3A%2F%2Fgithub.com%2FJacksonBurns%2Fchemeleon)%20backbone%20%7C%20SMILES%20column%20%7C%0A%0A%0A%20%20%20%20%60task%60%20is%20either%20%60%22regression%22%60%20or%20%60%22classification%22%60.%0A%20%20%20%20Classification%20%60predict()%60%20returns%20the%20probability%20of%20the%20positive%20class.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20pl)%3A%0A%20%20%20%20def%20extract_fp_matrix(df%3A%20pl.DataFrame%2C%20fp_col%3A%20str)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Extract%20a%202-D%20float32%20feature%20matrix%20from%20a%20fingerprint%20column.%0A%0A%20%20%20%20%20%20%20%20The%20column%20is%20expected%20to%20hold%20numpy%20arrays%20of%20equal%20length%20as%20produced%0A%20%20%20%20%20%20%20%20by%20generate_fingerprint.%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%20a%20fingerprint%20column.%0A%20%20%20%20%20%20%20%20%20%20%20%20fp_col%3A%20Column%20name.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%202-D%20array%20of%20shape%20(n_compounds%2C%20fp_size).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20return%20np.stack(df%5Bfp_col%5D.to_list()).astype(np.float32)%0A%0A%20%20%20%20return%20(extract_fp_matrix%2C)%0A%0A%0A%40app.cell%0Adef%20_(RandomForestClassifier%2C%20RandomForestRegressor%2C%20np)%3A%0A%20%20%20%20class%20RandomForestModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22Scikit-learn%20Random%20Forest%20model%20with%20a%20unified%20fit%2Fpredict%20interface.%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22classification%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20int%20%3D%20100%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3A%20int%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20min_samples_split%3A%20int%20%3D%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20min_samples_leaf%3A%20int%20%3D%201%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_features%3A%20str%20%7C%20float%20%3D%20%22sqrt%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_samples%3A%20float%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20class_weight%3A%20str%20%7C%20dict%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20int%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_jobs%3A%20int%20%3D%20-1%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22classification%22%20(RandomForestClassifier)%20or%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22regression%22%20(RandomForestRegressor).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20Number%20of%20trees%20in%20the%20forest.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3A%20Maximum%20depth%20of%20each%20tree.%20None%20grows%20trees%20until%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20leaves%20are%20pure%20or%20contain%20fewer%20than%20min_samples_split%20samples.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples_split%3A%20Minimum%20number%20of%20samples%20required%20to%20split%20an%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20internal%20node.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples_leaf%3A%20Minimum%20number%20of%20samples%20required%20to%20be%20at%20a%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20leaf%20node.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_features%3A%20Number%20of%20features%20to%20consider%20at%20each%20split.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22sqrt%22%20(default)%2C%20%22log2%22%2C%20a%20float%20fraction%2C%20or%20an%20int%20count.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_samples%3A%20Fraction%20of%20training%20samples%20drawn%20for%20each%20tree%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(bootstrap).%20None%20uses%20all%20samples.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20class_weight%3A%20Only%20meaningful%20for%20classification.%20%22balanced%22%20adjusts%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20weights%20inversely%20proportional%20to%20class%20frequencies.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20Random%20seed%20for%20reproducibility.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_jobs%3A%20Number%20of%20parallel%20jobs%20for%20fitting%20trees.%20-1%20uses%20all%20CPUs.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValueError%3A%20If%20pred_type%20is%20not%20%22classification%22%20or%20%22regression%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20common_kwargs%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3Dn_estimators%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3Dmax_depth%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples_split%3Dmin_samples_split%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples_leaf%3Dmin_samples_leaf%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_features%3Dmax_features%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_samples%3Dmax_samples%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3Drandom_state%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_jobs%3Dn_jobs%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20RandomForestClassifier(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20**common_kwargs%2C%20class_weight%3Dclass_weight%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%20elif%20pred_type%20%3D%3D%20%22regression%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20RandomForestRegressor(**common_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pred_type%20must%20be%20either%20'classification'%20or%20'regression'%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20def%20train(self%2C%20X_train%3A%20np.ndarray%2C%20y_train%3A%20np.ndarray)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fit%20the%20model%20on%20the%20training%20data.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20Training%20feature%20matrix.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Training%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model.fit(X_train%2C%20y_train)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20np.ndarray)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Generate%20predictions%20for%20the%20test%20set.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20Test%20feature%20matrix.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Predicted%20probabilities%20(classification)%20or%20values%20(regression).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict_proba(X_test)%5B%3A%2C%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict(X_test)%0A%0A%20%20%20%20return%20(RandomForestModel%2C)%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20xgb)%3A%0A%20%20%20%20class%20BoostedTreesModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22XGBoost%20gradient-boosted%20tree%20model%20with%20a%20unified%20fit%2Fpredict%20interface.%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22classification%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20int%20%3D%20100%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3A%20int%20%3D%206%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20learning_rate%3A%20float%20%3D%200.3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20subsample%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20colsample_bytree%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20min_child_weight%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20gamma%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_alpha%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_lambda%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20early_stopping_rounds%3A%20int%20%3D%2010%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20scale_pos_weight%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22classification%22%20(XGBClassifier)%20or%20%22regression%22%20(XGBRegressor).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20Maximum%20number%20of%20boosting%20rounds.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3A%20Maximum%20depth%20of%20each%20tree.%20Deeper%20trees%20capture%20more%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20interactions%20but%20increase%20overfitting%20risk.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20learning_rate%3A%20Step-size%20shrinkage%20(%CE%B7).%20Lower%20values%20require%20more%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20trees%20but%20generalise%20better.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20subsample%3A%20Fraction%20of%20training%20rows%20sampled%20per%20tree%20(0%20%3C%20subsample%20%E2%89%A4%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20colsample_bytree%3A%20Fraction%20of%20features%20sampled%20per%20tree.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_child_weight%3A%20Minimum%20sum%20of%20instance%20weights%20in%20a%20child%20node.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Higher%20values%20prevent%20learning%20on%20rare%20samples.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gamma%3A%20Minimum%20loss%20reduction%20required%20to%20make%20a%20further%20split.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Acts%20as%20a%20regularisation%20threshold.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reg_alpha%3A%20L1%20regularisation%20on%20leaf%20weights%20(increases%20sparsity).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reg_lambda%3A%20L2%20regularisation%20on%20leaf%20weights%20(shrinks%20weights).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20early_stopping_rounds%3A%20Stop%20training%20if%20validation%20metric%20does%20not%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20improve%20for%20this%20many%20consecutive%20rounds.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale_pos_weight%3A%20Ratio%20of%20negative%20to%20positive%20class%20counts.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Only%20meaningful%20for%20classification%3B%20set%20to%20n_neg%2Fn_pos%20for%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20imbalanced%20datasets.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValueError%3A%20If%20pred_type%20is%20not%20%22classification%22%20or%20%22regression%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20common_kwargs%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3Dn_estimators%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3Dmax_depth%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20learning_rate%3Dlearning_rate%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20subsample%3Dsubsample%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20colsample_bytree%3Dcolsample_bytree%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_child_weight%3Dmin_child_weight%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gamma%3Dgamma%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reg_alpha%3Dreg_alpha%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reg_lambda%3Dreg_lambda%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20early_stopping_rounds%3Dearly_stopping_rounds%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tree_method%3D%22hist%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_jobs%3D-1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20xgb.XGBClassifier(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20**common_kwargs%2C%20scale_pos_weight%3Dscale_pos_weight%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%20elif%20pred_type%20%3D%3D%20%22regression%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20xgb.XGBRegressor(**common_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pred_type%20must%20be%20either%20'classification'%20or%20'regression'%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20def%20train(self%2C%20X_train%3A%20np.ndarray%2C%20y_train%3A%20np.ndarray%2C%20X_val%3A%20np.ndarray%2C%20y_val%3A%20np.ndarray)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fit%20the%20model%20using%20training%20data%20with%20an%20evaluation%20set%20for%20early%20stopping.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20Training%20feature%20matrix.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Training%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20Validation%20feature%20matrix%20used%20for%20early%20stopping.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20Validation%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model.fit(X_train%2C%20y_train%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%20eval_set%3D%5B(X_val%2C%20y_val)%5D%2C%20verbose%3DFalse)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20np.ndarray)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Generate%20predictions%20for%20the%20test%20set.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20Test%20feature%20matrix.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Predicted%20probabilities%20(classification)%20or%20values%20(regression).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict_proba(X_test)%5B%3A%2C%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict(X_test)%0A%0A%0A%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Optional%2C%20Path%2C%20np%2C%20pl%2C%20shutil%2C%20subprocess%2C%20sys%2C%20tempfile%2C%20torch)%3A%0A%20%20%20%20%23%20Resolve%20the%20chemprop%20CLI%20from%20the%20same%20venv%20as%20the%20running%20interpreter%0A%20%20%20%20_CHEMPROP_BIN%20%3D%20Path(sys.executable).parent%20%2F%20%22chemprop%22%0A%0A%20%20%20%20%23%20Persistent%20temp%20directories%20%E2%80%94%20one%20per%20model%20type%2C%20reused%20across%20CV%20folds.%0A%20%20%20%20%23%20Using%20a%20fixed%20path%20(not%20TemporaryDirectory)%20so%20the%20folder%20survives%20between%0A%20%20%20%20%23%20train()%20and%20predict()%20calls%20within%20the%20same%20session.%0A%20%20%20%20_CHEMPROP_MODEL_DIR%20%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_scratch_model%22%0A%20%20%20%20_CHEMELEON_MODEL_DIR%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_chemeleon_model%22%0A%0A%20%20%20%20def%20_get_device()%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Detect%20and%20return%20the%20best%20available%20compute%20device%20for%20PyTorch.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22cuda%22%20if%20an%20NVIDIA%2FAMD%20GPU%20is%20available%2C%20%22mps%22%20if%20running%20on%20Apple%0A%20%20%20%20%20%20%20%20%20%20%20%20Silicon%20with%20Metal%20Performance%20Shaders%2C%20otherwise%20%22cpu%22.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22cuda%22%20%23%20Device%20for%20NVIDIA%20or%20AMD%20GPUs%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20torch.cuda.is_available()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20%22mps%22%20%23%20Device%20for%20Apple%20Silicon%20(Metal%20Performance%20Shaders)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20torch.backends.mps.is_available()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20%22cpu%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20def%20_write_smiles_csv(%0A%20%20%20%20%20%20%20%20smiles%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20targets%3A%20Optional%5Bnp.ndarray%5D%2C%0A%20%20%20%20%20%20%20%20path%3A%20Path%2C%0A%20%20%20%20%20%20%20%20target_col%3A%20str%2C%0A%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Write%20a%20CSV%20file%20with%20a%20smiles%20column%20and%20an%20optional%20target%20column.%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%20List%20of%20SMILES%20strings.%0A%20%20%20%20%20%20%20%20%20%20%20%20targets%3A%201-D%20array%20of%20target%20values%2C%20or%20None%20for%20inference-only%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20path%3A%20Destination%20file%20path.%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20Name%20of%20the%20target%20column.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20targets%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20pl.DataFrame(%7B%22smiles%22%3A%20smiles%2C%20target_col%3A%20targets.flatten().tolist()%7D)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20pl.DataFrame(%7B%22smiles%22%3A%20smiles%7D)%0A%20%20%20%20%20%20%20%20df.write_csv(path)%0A%0A%20%20%20%20%23%20Single%20log%20file%20for%20all%20chemprop%20CLI%20calls%20%E2%80%94%20appended%20across%20folds.%0A%20%20%20%20_CHEMPROP_LOG%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_cli.log%22%0A%0A%20%20%20%20def%20_run_chemprop_cli(args%3A%20list%5Bstr%5D)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Run%20the%20chemprop%20CLI%20as%20a%20subprocess%2C%20redirecting%20all%20output%20to%20a%20log%20file.%0A%0A%20%20%20%20%20%20%20%20stdout%20and%20stderr%20are%20appended%20to%20_CHEMPROP_LOG%20so%20the%20notebook%20stays%0A%20%20%20%20%20%20%20%20quiet.%20On%20failure%20the%20tail%20of%20the%20log%20is%20printed%20to%20help%20diagnose%20the%20error.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20args%3A%20Argument%20list%20passed%20after%20the%20%60chemprop%60%20binary.%0A%0A%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20RuntimeError%3A%20If%20the%20process%20exits%20with%20a%20non-zero%20return%20code.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20cmd%20%3D%20%5Bstr(_CHEMPROP_BIN)%5D%20%2B%20args%0A%20%20%20%20%20%20%20%20with%20open(_CHEMPROP_LOG%2C%20%22a%22)%20as%20_log%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_log.write(f%22%5Cn%7B'%3D'*60%7D%5CnCMD%3A%20%7B'%20'.join(cmd)%7D%5Cn%7B'%3D'*60%7D%5Cn%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20result%20%3D%20subprocess.run(cmd%2C%20stdout%3D_log%2C%20stderr%3D_log%2C%20text%3DTrue)%0A%20%20%20%20%20%20%20%20if%20result.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Print%20only%20the%20last%2030%20lines%20of%20the%20log%20to%20surface%20the%20error%0A%20%20%20%20%20%20%20%20%20%20%20%20lines%20%3D%20_CHEMPROP_LOG.read_text().splitlines()%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22%5Cn%22.join(lines%5B-30%3A%5D))%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22chemprop%20CLI%20failed%20(exit%20%7Bresult.returncode%7D).%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Full%20log%3A%20%7B_CHEMPROP_LOG%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20class%20ChempropModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Chemprop%20D-MPNN%20trained%20from%20scratch%20via%20the%20chemprop%20CLI.%0A%0A%20%20%20%20%20%20%20%20train()%20and%20predict()%20shell%20out%20to%20%60chemprop%20train%60%20%2F%20%60chemprop%20predict%60%0A%20%20%20%20%20%20%20%20rather%20than%20using%20the%20Python%20API%2C%20avoiding%20MPS%20memory%20issues%20when%20running%0A%20%20%20%20%20%20%20%20many%20CV%20folds%20inside%20a%20notebook%20kernel.%0A%0A%20%20%20%20%20%20%20%20The%20trained%20model%20is%20written%20to%20a%20fixed%20temporary%20directory%0A%20%20%20%20%20%20%20%20(%2Ftmp%2Fchemprop_scratch_model)%20which%20is%20overwritten%20on%20each%20train()%20call%0A%20%20%20%20%20%20%20%20so%20no%20disk%20space%20accumulates%20across%20folds.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Path%20%3D%20_CHEMPROP_MODEL_DIR%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20int%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20message_hidden_dim%3A%20int%20%3D%20300%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20depth%3A%20int%20%3D%203%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20int%20%3D%20300%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20int%20%3D%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20int%20%3D%2064%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20float%20%3D%201e-3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22regression%22%20or%20%22classification%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Directory%20where%20the%20CLI%20writes%20model%20checkpoints.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Reused%20(overwritten)%20on%20every%20train()%20call.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20Maximum%20number%20of%20training%20epochs.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20message_hidden_dim%3A%20Hidden%20dimension%20of%20the%20MPNN%20message-passing%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20layers%20(--message-hidden-dim).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20depth%3A%20Number%20of%20message-passing%20steps%2C%20i.e.%20the%20radius%20of%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20molecular%20graph%20neighbourhood%20considered%20(--depth).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20Dropout%20probability%20applied%20after%20each%20message-passing%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20and%20FFN%20layer%20(--dropout).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20Hidden%20dimension%20of%20the%20feed-forward%20network%20on%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20top%20of%20the%20MPNN%20(--ffn-hidden-dim).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20Number%20of%20layers%20in%20the%20feed-forward%20network%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--ffn-num-layers).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20Mini-batch%20size%20for%20training%20(--batch-size).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20Initial%20learning%20rate%20for%20the%20one-cycle%20scheduler%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--init-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20Peak%20learning%20rate%20for%20the%20one-cycle%20scheduler%20(--max-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20Final%20learning%20rate%20for%20the%20one-cycle%20scheduler%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--final-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20not%20in%20(%22regression%22%2C%20%22classification%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22pred_type%20must%20be%20'regression'%20or%20'classification'%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%20%20%20%20%20%20%20%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model_dir%20%20%20%20%20%20%20%20%20%3D%20model_dir%0A%20%20%20%20%20%20%20%20%20%20%20%20self.epochs%20%20%20%20%20%20%20%20%20%20%20%20%3D%20epochs%0A%20%20%20%20%20%20%20%20%20%20%20%20self.message_hidden_dim%20%3D%20message_hidden_dim%0A%20%20%20%20%20%20%20%20%20%20%20%20self.depth%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20depth%0A%20%20%20%20%20%20%20%20%20%20%20%20self.dropout%20%20%20%20%20%20%20%20%20%20%20%3D%20dropout%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_hidden_dim%20%20%20%20%3D%20ffn_hidden_dim%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_num_layers%20%20%20%20%3D%20ffn_num_layers%0A%20%20%20%20%20%20%20%20%20%20%20%20self.batch_size%20%20%20%20%20%20%20%20%3D%20batch_size%0A%20%20%20%20%20%20%20%20%20%20%20%20self.init_lr%20%20%20%20%20%20%20%20%20%20%20%3D%20init_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.max_lr%20%20%20%20%20%20%20%20%20%20%20%20%3D%20max_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.final_lr%20%20%20%20%20%20%20%20%20%20%3D%20final_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%3A%20Optional%5Bstr%5D%20%3D%20None%20%20%23%20set%20during%20train()%0A%0A%20%20%20%20%20%20%20%20def%20train(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20str%20%3D%20%22target%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Train%20the%20model%20by%20calling%20%60chemprop%20train%60%20via%20subprocess.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Writes%20temporary%20CSV%20files%20for%20train%20and%20val%20sets%2C%20runs%20the%20CLI%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20then%20removes%20the%20CSVs.%20The%20model%20directory%20is%20cleared%20before%20each%0A%20%20%20%20%20%20%20%20%20%20%20%20run%20so%20old%20checkpoints%20do%20not%20accumulate.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20SMILES%20strings%20for%20training.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Training%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20SMILES%20strings%20for%20validation%20(early%20stopping).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20Validation%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20Column%20name%20used%20in%20the%20temporary%20CSV%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%20%3D%20target_col%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv%20%3D%20tmp%20%2F%20%22chemprop_train.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv%20%20%20%3D%20tmp%20%2F%20%22chemprop_val.csv%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_train%2C%20y_train%2C%20train_csv%2C%20target_col)%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_val%2C%20%20%20y_val%2C%20%20%20val_csv%2C%20%20%20target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Remove%20stale%20checkpoints%20so%20the%20CLI%20starts%20fresh%20each%20fold%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.model_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(self.model_dir)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20task_type%20%3D%20%22regression%22%20if%20self.pred_type%20%3D%3D%20%22regression%22%20else%20%22binary%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Pass%20val_csv%20twice%20(as%20val%20and%20as%20dummy%20test)%20so%20the%20CLI%20tracks%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20val_loss%20for%20early%20stopping.%20Two-file%20mode%20triggers%20a%20validation%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20error%20unless%20--split-sizes%20is%20also%20set.%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--data-path%22%2C%20str(train_csv)%2C%20str(val_csv)%2C%20str(val_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--smiles-columns%22%2C%20%22smiles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--target-columns%22%2C%20target_col%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--task-type%22%2C%20task_type%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--accelerator%22%2C%20_get_device()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--epochs%22%2C%20str(self.epochs)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--message-hidden-dim%22%2C%20str(self.message_hidden_dim)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--depth%22%2C%20str(self.depth)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--dropout%22%2C%20str(self.dropout)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--ffn-hidden-dim%22%2C%20str(self.ffn_hidden_dim)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--ffn-num-layers%22%2C%20str(self.ffn_num_layers)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--batch-size%22%2C%20str(self.batch_size)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--init-lr%22%2C%20str(self.init_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--max-lr%22%2C%20str(self.max_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--final-lr%22%2C%20str(self.final_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--save-dir%22%2C%20str(self.model_dir)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20list%5Bstr%5D)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Run%20inference%20by%20calling%20%60chemprop%20predict%60%20via%20subprocess.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Writes%20a%20temporary%20SMILES%20CSV%2C%20runs%20the%20CLI%2C%20reads%20the%20output%20CSV%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20then%20removes%20both%20temporary%20files.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20SMILES%20strings%20to%20predict.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201-D%20numpy%20array%20of%20predicted%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv%20%3D%20tmp%20%20%2F%20%22chemprop_test.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv%20%3D%20tmp%20%20%2F%20%22chemprop_preds.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20The%20best.pt%20written%20by%20%60chemprop%20train%60%20into%20model_dir%2Fmodel_0%2F%0A%20%20%20%20%20%20%20%20%20%20%20%20model_pt%20%3D%20self.model_dir%20%2F%20%22model_0%22%20%2F%20%22best.pt%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_test%2C%20None%2C%20test_csv%2C%20self.target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22predict%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--test-path%22%2C%20%20str(test_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--model-path%22%2C%20str(model_pt)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--preds-path%22%2C%20str(pred_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20pl.read_csv(pred_csv)%5Bself.target_col%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20preds.flatten()%0A%0A%20%20%20%20class%20ChempropChemeleonModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Chemprop%20D-MPNN%20fine-tuned%20from%20the%20CheMeleon%20pretrained%20backbone%20via%20the%20CLI.%0A%0A%20%20%20%20%20%20%20%20Identical%20interface%20to%20ChempropModel%20but%20passes%20%60--from-foundation%20CHEMELEON%60%0A%20%20%20%20%20%20%20%20to%20%60chemprop%20train%60.%20%20The%20CLI%20downloads%20and%20caches%20the%20CheMeleon%20weights%0A%20%20%20%20%20%20%20%20automatically%20at%20~%2F.chemprop%2Fchemeleon_mp.pt%20on%20the%20first%20call.%0A%0A%20%20%20%20%20%20%20%20Reference%3A%20https%3A%2F%2Fgithub.com%2FJacksonBurns%2Fchemeleon%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Path%20%3D%20_CHEMELEON_MODEL_DIR%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20int%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20int%20%3D%20900%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20int%20%3D%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20int%20%3D%2064%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20float%20%3D%201e-3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22regression%22%20or%20%22classification%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Directory%20where%20the%20CLI%20writes%20model%20checkpoints.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Distinct%20from%20ChempropModel's%20default%20to%20avoid%20collisions.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20Maximum%20number%20of%20training%20epochs.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20Dropout%20probability%20applied%20after%20each%20FFN%20layer.%20Note%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20that%20message-passing%20architecture%20is%20fixed%20by%20CheMeleon%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(2048%20d_h%2C%20depth%206)%20and%20cannot%20be%20changed%20during%20fine-tuning.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20Hidden%20dimension%20of%20the%20task-specific%20feed-forward%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20head%20added%20on%20top%20of%20the%20frozen%20backbone%20(--ffn-hidden-dim).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20Number%20of%20layers%20in%20the%20feed-forward%20head%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--ffn-num-layers).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20Mini-batch%20size%20for%20fine-tuning%20(--batch-size).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20Initial%20learning%20rate%20for%20the%20one-cycle%20scheduler%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--init-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20Peak%20learning%20rate%20for%20the%20one-cycle%20scheduler%20(--max-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20Final%20learning%20rate%20for%20the%20one-cycle%20scheduler%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--final-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20not%20in%20(%22regression%22%2C%20%22classification%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22pred_type%20must%20be%20'regression'%20or%20'classification'%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%20%20%20%20%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model_dir%20%20%20%20%20%20%3D%20model_dir%0A%20%20%20%20%20%20%20%20%20%20%20%20self.epochs%20%20%20%20%20%20%20%20%20%3D%20epochs%0A%20%20%20%20%20%20%20%20%20%20%20%20self.dropout%20%20%20%20%20%20%20%20%3D%20dropout%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_hidden_dim%20%3D%20ffn_hidden_dim%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_num_layers%20%3D%20ffn_num_layers%0A%20%20%20%20%20%20%20%20%20%20%20%20self.batch_size%20%20%20%20%20%3D%20batch_size%0A%20%20%20%20%20%20%20%20%20%20%20%20self.init_lr%20%20%20%20%20%20%20%20%3D%20init_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.max_lr%20%20%20%20%20%20%20%20%20%3D%20max_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.final_lr%20%20%20%20%20%20%20%3D%20final_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%3A%20Optional%5Bstr%5D%20%3D%20None%0A%0A%20%20%20%20%20%20%20%20def%20train(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20str%20%3D%20%22target%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fine-tune%20from%20CheMeleon%20by%20calling%20%60chemprop%20train%20--from-foundation%20CHEMELEON%60.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20SMILES%20strings%20for%20training.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Training%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20SMILES%20strings%20for%20validation%20(early%20stopping).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20Validation%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20Column%20name%20used%20in%20the%20temporary%20CSV%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%20%3D%20target_col%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv%20%3D%20tmp%20%2F%20%22chemeleon_train.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv%20%20%20%3D%20tmp%20%2F%20%22chemeleon_val.csv%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_train%2C%20y_train%2C%20train_csv%2C%20target_col)%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_val%2C%20%20%20y_val%2C%20%20%20val_csv%2C%20%20%20target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.model_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(self.model_dir)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20task_type%20%3D%20%22regression%22%20if%20self.pred_type%20%3D%3D%20%22regression%22%20else%20%22binary%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Pass%20val_csv%20twice%20(as%20val%20and%20as%20dummy%20test)%20%E2%80%94%20same%20reason%20as%20ChempropModel.%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--data-path%22%2C%20str(train_csv)%2C%20str(val_csv)%2C%20str(val_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--smiles-columns%22%2C%20%22smiles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--target-columns%22%2C%20target_col%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--task-type%22%2C%20task_type%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--accelerator%22%2C%20_get_device()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--epochs%22%2C%20str(self.epochs)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--dropout%22%2C%20str(self.dropout)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--ffn-hidden-dim%22%2C%20str(self.ffn_hidden_dim)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--ffn-num-layers%22%2C%20str(self.ffn_num_layers)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--batch-size%22%2C%20str(self.batch_size)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--init-lr%22%2C%20str(self.init_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--max-lr%22%2C%20str(self.max_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--final-lr%22%2C%20str(self.final_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--from-foundation%22%2C%20%22CHEMELEON%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--save-dir%22%2C%20str(self.model_dir)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20list%5Bstr%5D)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Run%20inference%20by%20calling%20%60chemprop%20predict%60%20via%20subprocess.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20SMILES%20strings%20to%20predict.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201-D%20numpy%20array%20of%20predicted%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv%20%3D%20tmp%20%20%2F%20%22chemeleon_test.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv%20%3D%20tmp%20%20%2F%20%22chemeleon_preds.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20model_pt%20%3D%20self.model_dir%20%2F%20%22model_0%22%20%2F%20%22best.pt%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_test%2C%20None%2C%20test_csv%2C%20self.target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22predict%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--test-path%22%2C%20%20str(test_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--model-path%22%2C%20str(model_pt)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--preds-path%22%2C%20str(pred_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20pl.read_csv(pred_csv)%5Bself.target_col%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20preds.flatten()%0A%0A%20%20%20%20return%20(ChempropModel%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20accuracy_score%2C%0A%20%20%20%20balanced_accuracy_score%2C%0A%20%20%20%20f1_score%2C%0A%20%20%20%20matthews_corrcoef%2C%0A%20%20%20%20mean_absolute_error%2C%0A%20%20%20%20mean_squared_error%2C%0A%20%20%20%20np%2C%0A%20%20%20%20precision_score%2C%0A%20%20%20%20r2_score%2C%0A%20%20%20%20recall_score%2C%0A%20%20%20%20roc_auc_score%2C%0A%20%20%20%20spearmanr%2C%0A%20%20%20%20warnings%2C%0A)%3A%0A%20%20%20%20_classification_metrics%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22accuracy%22%3A%20accuracy_score%2C%0A%20%20%20%20%20%20%20%20%22balanced_accuracy%22%3A%20balanced_accuracy_score%2C%0A%20%20%20%20%20%20%20%20%22precision%22%3A%20precision_score%2C%0A%20%20%20%20%20%20%20%20%22recall%22%3A%20recall_score%2C%0A%20%20%20%20%20%20%20%20%22f1%22%3A%20f1_score%2C%0A%20%20%20%20%20%20%20%20%22mcc%22%3A%20matthews_corrcoef%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20_safe_spearmanr(y_true%2C%20y_pred)%3A%0A%20%20%20%20%20%20%20%20with%20warnings.catch_warnings()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20warnings.simplefilter(%22ignore%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20spearmanr(y_true%2C%20y_pred).correlation%0A%0A%20%20%20%20_regression_metrics%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22r2%22%3A%20r2_score%2C%0A%20%20%20%20%20%20%20%20%22rho%22%3A%20lambda%20y_true%2C%20y_pred%3A%20_safe_spearmanr(y_true%2C%20y_pred)%2C%0A%20%20%20%20%20%20%20%20%22mse%22%3A%20mean_squared_error%2C%0A%20%20%20%20%20%20%20%20%22rmse%22%3A%20lambda%20y_true%2C%20y_pred%3A%20np.sqrt(mean_squared_error(y_true%2C%20y_pred))%2C%0A%20%20%20%20%20%20%20%20%22mae%22%3A%20mean_absolute_error%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20evaluate_predictions(%0A%20%20%20%20%20%20%20%20y_pred%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20y_test%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20pred_type%3A%20str%2C%0A%20%20%20%20%20%20%20%20thr%3A%20float%20%3D%200.5%2C%0A%20%20%20%20)%20-%3E%20dict%5Bstr%2C%20float%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Compute%20a%20standard%20set%20of%20metrics%20for%20either%20classification%20or%20regression.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20y_pred%3A%20Model%20predictions.%20For%20classification%2C%20these%20should%20be%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20probability%20scores%20(0%E2%80%931)%3B%20for%20regression%2C%20continuous%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20y_test%3A%20Ground-truth%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22classification%22%20or%20%22regression%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20thr%3A%20Decision%20threshold%20applied%20to%20y_pred%20for%20binary%20classification%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20metrics.%20Ignored%20for%20regression.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Dictionary%20mapping%20metric%20names%20to%20their%20computed%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20Classification%20metrics%3A%20accuracy%2C%20balanced_accuracy%2C%20precision%2C%20recall%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20f1%2C%20mcc%2C%20roc_auc.%20Regression%20metrics%3A%20r2%2C%20rho%2C%20mse%2C%20rmse%2C%20mae.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20out%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20metric%3A%20_classification_metrics%5Bmetric%5D(y_test%2C%20y_pred%20%3E%20thr)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20metric%20in%20_classification_metrics%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20out%5B%22roc_auc%22%5D%20%3D%20roc_auc_score(y_test%2C%20y_pred)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20out%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20metric%3A%20_regression_metrics%5Bmetric%5D(y_test%2C%20y_pred)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20metric%20in%20_regression_metrics%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20AtomPairFingerprint%2C%0A%20%20%20%20AvalonFingerprint%2C%0A%20%20%20%20CheMeleonFingerprint%2C%0A%20%20%20%20ConformerGenerator%2C%0A%20%20%20%20E3FPFingerprint%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%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20%22morgan%22%3A%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20%22maccs%22%3A%20MACCSFingerprint%2C%0A%20%20%20%20%20%20%20%20%22torsion%22%3A%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20%20%20%20%20%22rdkit%22%3A%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%20AvalonFingerprint%2C%0A%20%20%20%20%20%20%20%20%22e3fp%22%3A%20E3FPFingerprint%2C%0A%20%20%20%20%20%20%20%20%22mordred%22%3A%20MordredFingerprint%2C%0A%20%20%20%20%20%20%20%20%22mqn%22%3A%20MQNsFingerprint%2C%0A%20%20%20%20%20%20%20%20%22pubchem%22%3A%20PubChemFingerprint%2C%0A%20%20%20%20%20%20%20%20%23%20CheMeleon%20is%20handled%20separately%20below%20%E2%80%94%20it%20does%20not%20follow%20the%20skfp%0A%20%20%20%20%20%20%20%20%23%20constructor%20%2F%20transform%20pattern%20for%20conformers%2C%20so%20we%20mark%20it%20as%20a%0A%20%20%20%20%20%20%20%20%23%20sentinel%20value%20and%20branch%20on%20it%20inside%20generate_fingerprint.%0A%20%20%20%20%20%20%20%20%22chemeleon%22%3A%20CheMeleonFingerprint%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%20new%20column%20to%20the%20DataFrame.%0A%0A%20%20%20%20%20%20%20%20Dispatches%20to%20the%20appropriate%20fingerprint%20class%20based%20on%20fingerprint_type.%0A%20%20%20%20%20%20%20%20All%20skfp%20types%20use%20their%20standard%20constructor%20%2F%20transform%20pipeline.%0A%20%20%20%20%20%20%20%20The%20special%20%22chemeleon%22%20type%20uses%20the%20CheMeleonFingerprint%20class%2C%20which%0A%20%20%20%20%20%20%20%20runs%20the%20pretrained%20CheMeleon%20D-MPNN%20backbone%20and%20returns%202048-d%20embeddings.%0A%20%20%20%20%20%20%20%20For%20fingerprint%20types%20that%20require%203D%20conformers%20(e.g.%2C%20E3FP)%2C%20conformers%20are%0A%20%20%20%20%20%20%20%20generated%20automatically%20via%20RDKit%20ETKDGv3.%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%20types%3A%20%22ecfp%22%2F%22morgan%22%2C%20%22maccs%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22torsion%22%2C%20%22rdkit%22%2C%20%22atompair%22%2C%20%22avalon%22%2C%20%22e3fp%22%2C%20%22mordred%22%2C%20%22mqn%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pubchem%22%2C%20%22chemeleon%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20**kwargs%3A%20Additional%20keyword%20arguments%20forwarded%20to%20the%20fingerprint%20class%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20constructor%20(e.g.%2C%20radius%3D3%2C%20n_bits%3D1024%20for%20ECFP%3B%20device%3D%22mps%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20chemeleon).%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%20containing%0A%20%20%20%20%20%20%20%20%20%20%20%20the%20computed%20fingerprint%20arrays.%0A%0A%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ValueError%3A%20If%20fingerprint_type%20is%20not%20a%20recognized%20key.%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%20recognized%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%0A%20%20%20%20%20%20%20%20smiles_list%20%3D%20df.get_column(%22smiles%22).to_list()%0A%0A%20%20%20%20%20%20%20%20%23%20CheMeleon%20has%20a%20different%20API%3A%20instantiate%20once%2C%20call%20.transform(smiles)%0A%20%20%20%20%20%20%20%20if%20fingerprint_type%20%3D%3D%20%22chemeleon%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fp_func%20%3D%20CheMeleonFingerprint(**kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20fps%20%3D%20fp_func.transform(smiles_list)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20df.with_columns(pl.Series(values%3Dfps%2C%20name%3Dfingerprint_type))%0A%0A%20%20%20%20%20%20%20%20%23%20All%20other%20fingerprint%20types%20follow%20the%20skfp%20constructor%20%2F%20transform%20pattern.%0A%20%20%20%20%20%20%20%20fp_func%20%3D%20_fp_dict%5Bfingerprint_type%5D(**kwargs)%0A%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(smiles_list)%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%20smiles_list%0A%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_(BaseKFold%2C%20Iterator%2C%20Optional%2C%20np%2C%20pl)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%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%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20def%20split_dataset_random(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20p_test%3A%20float%20%3D%200.2%2C%0A%20%20%20%20%20%20%20%20seed%3A%20int%20%3D%2042%2C%0A%20%20%20%20)%20-%3E%20tuple%5Bpl.DataFrame%2C%20pl.DataFrame%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Randomly%20split%20a%20DataFrame%20into%20train%20and%20test%20subsets.%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%20Input%20DataFrame.%0A%20%20%20%20%20%20%20%20%20%20%20%20p_test%3A%20Fraction%20of%20rows%20allocated%20to%20the%20test%20set.%0A%20%20%20%20%20%20%20%20%20%20%20%20seed%3A%20Random%20seed%20for%20reproducibility.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Tuple%20of%20(train_df%2C%20test_df).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20rng%20%3D%20np.random.default_rng(seed)%0A%20%20%20%20%20%20%20%20idx%20%3D%20rng.permutation(df.shape%5B0%5D)%0A%20%20%20%20%20%20%20%20n_test%20%3D%20int(len(idx)%20*%20p_test)%0A%20%20%20%20%20%20%20%20test_idx%2C%20train_idx%20%3D%20idx%5B%3An_test%5D%2C%20idx%5Bn_test%3A%5D%0A%20%20%20%20%20%20%20%20return%20df%5Btrain_idx%5D.clone()%2C%20df%5Btest_idx%5D.clone()%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20GroupKFoldShuffle%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%0A%0A%20%20%20%20class%20GroupKFoldShuffle(BaseKFold)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20K-fold%20cross-validator%20that%20respects%20group%20boundaries%20and%20supports%20shuffling.%0A%0A%20%20%20%20%20%20%20%20An%20extension%20of%20scikit-learn's%20GroupKFold%20that%20adds%20optional%20shuffling%20of%0A%20%20%20%20%20%20%20%20groups%20before%20splitting.%20Useful%20for%20scaffold-aware%20cross-validation%20where%0A%20%20%20%20%20%20%20%20you%20want%20reproducible%20but%20shuffled%20group%20assignments.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20n_splits%3A%20Number%20of%20folds.%0A%20%20%20%20%20%20%20%20%20%20%20%20shuffle%3A%20Whether%20to%20shuffle%20groups%20before%20splitting.%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20Random%20seed%20used%20when%20shuffle%3DTrue.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_splits%3A%20int%20%3D%205%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%20shuffle%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20Optional%5Bint%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20super().__init__(n_splits%3Dn_splits%2C%20shuffle%3Dshuffle%2C%20random_state%3Drandom_state)%0A%0A%20%20%20%20%20%20%20%20def%20split(self%2C%20X%2C%20y%3DNone%2C%20groups%3DNone)%20-%3E%20Iterator%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Collect%20unique%20groups%2C%20then%20optionally%20shuffle%20them%20so%20that%20fold%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20assignment%20is%20randomised%20while%20still%20keeping%20each%20group%20intact.%0A%20%20%20%20%20%20%20%20%20%20%20%20unique_groups%20%3D%20np.unique(groups)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.shuffle%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rng%20%3D%20np.random.default_rng(self.random_state)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20unique_groups%20%3D%20rng.permutation(unique_groups)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Distribute%20groups%20as%20evenly%20as%20possible%20across%20folds.%0A%20%20%20%20%20%20%20%20%20%20%20%20split_groups%20%3D%20np.array_split(unique_groups%2C%20self.n_splits)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20test_group_ids%20in%20split_groups%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20test_mask%20%3D%20np.isin(groups%2C%20test_group_ids)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20train_mask%20%3D%20~test_mask%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20yield%20np.where(train_mask)%5B0%5D%2C%20np.where(test_mask)%5B0%5D%0A%0A%20%20%20%20return%20GroupKFoldShuffle%2C%20split_dataset_random%0A%0A%0A%40app.cell%0Adef%20_(GroupKFoldShuffle%2C%20Iterator%2C%20pl%2C%20split_dataset_random)%3A%0A%20%20%20%20def%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20n_outer%3A%20int%20%3D%205%2C%0A%20%20%20%20%20%20%20%20n_inner%3A%20int%20%3D%205%2C%0A%20%20%20%20%20%20%20%20seed%3A%20int%20%3D%2042%2C%0A%20%20%20%20%20%20%20%20p_val%3A%20float%20%3D%200%2C%0A%20%20%20%20)%20-%3E%20Iterator%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Generate%20nested%205%C3%975%20CV%20splits%20using%20a%20**random**%20molecule%20assignment.%0A%0A%20%20%20%20%20%20%20%20Each%20molecule%20is%20treated%20as%20its%20own%20group%2C%20so%20folds%20are%20purely%20random.%0A%20%20%20%20%20%20%20%20This%20is%20the%20baseline%20split%20strategy%3A%20it%20gives%20optimistic%20estimates%20of%0A%20%20%20%20%20%20%20%20generalisation%20because%20train%20and%20test%20scaffolds%20can%20overlap.%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%20to%20split.%0A%20%20%20%20%20%20%20%20%20%20%20%20n_outer%3A%20Number%20of%20outer%20CV%20folds.%0A%20%20%20%20%20%20%20%20%20%20%20%20n_inner%3A%20Number%20of%20inner%20CV%20folds%20per%20outer%20iteration.%0A%20%20%20%20%20%20%20%20%20%20%20%20seed%3A%20Random%20seed%20for%20GroupKFoldShuffle.%0A%20%20%20%20%20%20%20%20%20%20%20%20p_val%3A%20Fraction%20of%20the%20training%20set%20reserved%20as%20a%20validation%20split.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%200%20disables%20the%20validation%20split%20(val_df%20is%20yielded%20as%20None).%0A%0A%20%20%20%20%20%20%20%20Yields%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Tuples%20of%20(fold_index%2C%20outer_index%2C%20inner_index%2C%20train_df%2C%20val_df%2C%20test_df).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(n_outer)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20kf%20%3D%20GroupKFoldShuffle(n_splits%3Dn_inner%2C%20random_state%3Dseed%20%2B%20i%2C%20shuffle%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20groups%20%3D%20list(range(df.shape%5B0%5D))%20%20%23%20each%20molecule%20is%20its%20own%20group%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20j%2C%20(train_idx%2C%20test_idx)%20in%20enumerate(kf.split(df%2C%20groups%3Dgroups))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fold%20%3D%20i%20*%20n_inner%20%2B%20j%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20train%20%3D%20df%5Btrain_idx%5D.clone()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20test%20%3D%20df%5Btest_idx%5D.clone()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20val%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20p_val%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20train%2C%20val%20%3D%20split_dataset_random(train%2C%20p_test%3Dp_val%2C%20seed%3Dseed%20%2B%20fold)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20yield%20fold%2C%20i%2C%20j%2C%20train%2C%20val%2C%20test%0A%0A%20%20%20%20return%20(generate_cv_splits_random%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%20ML%20comparison%20code%0A%0A%20%20%20%20Adapted%20from%20https%3A%2F%2Fgithub.com%2Fpolaris-hub%2Fpolaris-method-comparison%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20mean_absolute_error%2C%0A%20%20%20%20mean_squared_error%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20precision_score%2C%0A%20%20%20%20r2_score%2C%0A%20%20%20%20recall_score%2C%0A%20%20%20%20spearmanr%2C%0A%20%20%20%20warnings%2C%0A)%3A%0A%20%20%20%20def%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20cycle_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20val_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20pred_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20thresh%3A%20float%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%20Calculate%20regression%20metrics%20(MAE%2C%20MSE%2C%20R2%2C%20rho%2C%20prec%2C%20recall)%20for%20each%20method%20and%20split.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20columns%20%5Bmethod%2C%20split%5D%20plus%20the%20columns%20named%20in%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20remaining%20arguments.%0A%20%20%20%20%20%20%20%20%20%20%20%20cycle_col%3A%20Column%20indicating%20the%20cross-validation%20fold.%0A%20%20%20%20%20%20%20%20%20%20%20%20val_col%3A%20Column%20with%20the%20ground%20truth%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3A%20Column%20with%20the%20model%20predictions.%0A%20%20%20%20%20%20%20%20%20%20%20%20thresh%3A%20Decision%20threshold%20used%20to%20binarise%20continuous%20values%20for%20precision%2Frecall.%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%20%5Bcv_cycle%2C%20method%2C%20split%2C%20mae%2C%20mse%2C%20r2%2C%20rho%2C%20prec%2C%20recall%5D.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20Derive%20binary%20class%20columns%20from%20the%20continuous%20threshold%0A%20%20%20%20%20%20%20%20df_in%20%3D%20df.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(val_col)%20%3E%20thresh).alias(%22true_class%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(pred_col)%20%3E%20thresh).alias(%22pred_class%22)%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%23%20Ensure%20the%20threshold%20actually%20produces%20two%20distinct%20classes%0A%20%20%20%20%20%20%20%20assert%20df_in%5B%22true_class%22%5D.n_unique()%20%3D%3D%202%2C%20%22Binary%20classification%20requires%20two%20classes%22%0A%0A%20%20%20%20%20%20%20%20metric_list%3A%20list%5Bdict%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20%23%20Iterate%20over%20each%20(cycle%2C%20method%2C%20split)%20group%20and%20compute%20metrics%0A%20%20%20%20%20%20%20%20for%20group_keys%2C%20group_df%20in%20df_in.group_by(%5Bcycle_col%2C%20%22method%22%2C%20%22split%22%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20cycle%2C%20method%2C%20split%20%3D%20group_keys%0A%20%20%20%20%20%20%20%20%20%20%20%20y_true%20%3D%20group_df%5Bval_col%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20y_pred%20%3D%20group_df%5Bpred_col%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20y_true_cls%20%3D%20group_df%5B%22true_class%22%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20y_pred_cls%20%3D%20group_df%5B%22pred_class%22%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20with%20warnings.catch_warnings()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20warnings.simplefilter(%22ignore%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rho%2C%20_%20%3D%20spearmanr(y_true%2C%20y_pred)%0A%20%20%20%20%20%20%20%20%20%20%20%20metric_list.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22cv_cycle%22%3A%20cycle%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22method%22%3A%20method%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22split%22%3A%20split%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mae%22%3A%20mean_absolute_error(y_true%2C%20y_pred)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mse%22%3A%20mean_squared_error(y_true%2C%20y_pred)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22r2%22%3A%20r2_score(y_true%2C%20y_pred)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22rho%22%3A%20float(rho)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22prec%22%3A%20precision_score(y_true_cls%2C%20y_pred_cls)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22recall%22%3A%20recall_score(y_true_cls%2C%20y_pred_cls)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20return%20pl.DataFrame(metric_list)%0A%0A%20%20%20%20return%20(calc_regression_metrics%2C)%0A%0A%0A%40app.cell%0Adef%20_(Optional%2C%20np%2C%20pd%2C%20pg%2C%20pl%2C%20psturng%2C%20qsturng%2C%20warnings)%3A%0A%20%20%20%20def%20rm_tukey_hsd(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20metric%3A%20str%2C%0A%20%20%20%20%20%20%20%20group_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20alpha%3A%20float%20%3D%200.05%2C%0A%20%20%20%20%20%20%20%20sort%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20direction_dict%3A%20Optional%5Bdict%5D%20%3D%20None%2C%0A%20%20%20%20)%20-%3E%20tuple%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Perform%20repeated%20measures%20Tukey%20HSD%20test%20on%20the%20given%20Polars%20DataFrame.%0A%0A%20%20%20%20%20%20%20%20Internally%20converts%20to%20pandas%20for%20pingouin%2Fstatsmodels%20compatibility.%0A%20%20%20%20%20%20%20%20All%20returned%20DataFrames%20are%20pandas%20objects%20for%20downstream%20seaborn%20plotting.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20columns%20%5Bcv_cycle%2C%20group_col%2C%20metric%5D.%0A%20%20%20%20%20%20%20%20%20%20%20%20metric%3A%20Column%20name%20of%20the%20metric%20to%20test.%0A%20%20%20%20%20%20%20%20%20%20%20%20group_col%3A%20Column%20name%20indicating%20the%20comparison%20groups.%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3A%20Significance%20level%20for%20the%20test.%0A%20%20%20%20%20%20%20%20%20%20%20%20sort%3A%20Whether%20to%20sort%20groups%20by%20their%20mean%20metric%20value.%0A%20%20%20%20%20%20%20%20%20%20%20%20direction_dict%3A%20Maps%20metric%20names%20to%20%22maximize%22%20or%20%22minimize%22%20for%20sort%20direction.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Tuple%20of%20(result_tab%2C%20df_means%2C%20df_means_diff%2C%20pc)%20%E2%80%94%20all%20pandas%20DataFrames.%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20result_tab%3A%20Pairwise%20comparisons%20with%20adjusted%20p-values.%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20df_means%3A%20Mean%20values%20per%20group.%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20df_means_diff%3A%20Matrix%20of%20pairwise%20mean%20differences.%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20pc%3A%20Matrix%20of%20adjusted%20p-values.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20Convert%20to%20pandas%20%E2%80%94%20pingouin%20and%20statsmodels%20require%20it%0A%20%20%20%20%20%20%20%20df_pd%20%3D%20df.to_pandas()%0A%0A%20%20%20%20%20%20%20%20if%20sort%20and%20direction_dict%20and%20metric%20in%20direction_dict%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20direction_dict%5Bmetric%5D%20%3D%3D%20'maximize'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_means%20%3D%20df_pd.groupby(group_col).mean(numeric_only%3DTrue).sort_values(metric%2C%20ascending%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20direction_dict%5Bmetric%5D%20%3D%3D%20'minimize'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_means%20%3D%20df_pd.groupby(group_col).mean(numeric_only%3DTrue).sort_values(metric%2C%20ascending%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22Invalid%20direction.%20Expected%20'maximize'%20or%20'minimize'.%22)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df_means%20%3D%20df_pd.groupby(group_col).mean(numeric_only%3DTrue)%0A%0A%20%20%20%20%20%20%20%20with%20warnings.catch_warnings()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20warnings.filterwarnings('ignore'%2C%20category%3DRuntimeWarning%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20message%3D'divide%20by%20zero%20encountered%20in%20scalar%20divide')%0A%20%20%20%20%20%20%20%20%20%20%20%20aov%20%3D%20pg.rm_anova(dv%3Dmetric%2C%20within%3Dgroup_col%2C%20subject%3D'cv_cycle'%2C%20data%3Ddf_pd%2C%20detailed%3DTrue)%0A%20%20%20%20%20%20%20%20mse%20%3D%20aov.loc%5B1%2C%20'MS'%5D%0A%20%20%20%20%20%20%20%20df_resid%20%3D%20aov.loc%5B1%2C%20'DF'%5D%0A%0A%20%20%20%20%20%20%20%20methods%20%3D%20df_means.index%0A%20%20%20%20%20%20%20%20n_groups%20%3D%20len(methods)%0A%20%20%20%20%20%20%20%20n_per_group%20%3D%20df_pd%5Bgroup_col%5D.value_counts().mean()%0A%0A%20%20%20%20%20%20%20%20tukey_se%20%3D%20np.sqrt(2%20*%20mse%20%2F%20n_per_group)%0A%20%20%20%20%20%20%20%20q%20%3D%20qsturng(1%20-%20alpha%2C%20n_groups%2C%20df_resid)%0A%0A%20%20%20%20%20%20%20%20num_comparisons%20%3D%20len(methods)%20*%20(len(methods)%20-%201)%20%2F%2F%202%0A%20%20%20%20%20%20%20%20result_tab%20%3D%20pd.DataFrame(index%3Drange(num_comparisons)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20columns%3D%5B%22group1%22%2C%20%22group2%22%2C%20%22meandiff%22%2C%20%22lower%22%2C%20%22upper%22%2C%20%22p-adj%22%5D)%0A%0A%20%20%20%20%20%20%20%20df_means_diff%20%3D%20pd.DataFrame(index%3Dmethods%2C%20columns%3Dmethods%2C%20data%3D0.0)%0A%20%20%20%20%20%20%20%20pc%20%3D%20pd.DataFrame(index%3Dmethods%2C%20columns%3Dmethods%2C%20data%3D1.0)%0A%0A%20%20%20%20%20%20%20%20%23%20Calculate%20pairwise%20mean%20differences%20and%20adjusted%20p-values%0A%20%20%20%20%20%20%20%20row_idx%20%3D%200%0A%20%20%20%20%20%20%20%20for%20i%2C%20method1%20in%20enumerate(methods)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20j%2C%20method2%20in%20enumerate(methods)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20group1%20%3D%20df_pd%5Bdf_pd%5Bgroup_col%5D%20%3D%3D%20method1%5D%5Bmetric%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20group2%20%3D%20df_pd%5Bdf_pd%5Bgroup_col%5D%20%3D%3D%20method2%5D%5Bmetric%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mean_diff%20%3D%20group1.mean()%20-%20group2.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20studentized_range%20%3D%20np.abs(mean_diff)%20%2F%20tukey_se%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20adjusted_p%20%3D%20psturng(studentized_range%20*%20np.sqrt(2)%2C%20n_groups%2C%20df_resid)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance(adjusted_p%2C%20np.ndarray)%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%20adjusted_p%20%3D%20adjusted_p%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lower%20%3D%20mean_diff%20-%20(q%20%2F%20np.sqrt(2)%20*%20tukey_se)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20upper%20%3D%20mean_diff%20%2B%20(q%20%2F%20np.sqrt(2)%20*%20tukey_se)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result_tab.loc%5Brow_idx%5D%20%3D%20%5Bmethod1%2C%20method2%2C%20mean_diff%2C%20lower%2C%20upper%2C%20adjusted_p%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pc.loc%5Bmethod1%2C%20method2%5D%20%3D%20adjusted_p%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pc.loc%5Bmethod2%2C%20method1%5D%20%3D%20adjusted_p%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_means_diff.loc%5Bmethod1%2C%20method2%5D%20%3D%20mean_diff%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_means_diff.loc%5Bmethod2%2C%20method1%5D%20%3D%20-mean_diff%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20row_idx%20%2B%3D%201%0A%0A%20%20%20%20%20%20%20%20df_means_diff%20%3D%20df_means_diff.astype(float)%0A%0A%20%20%20%20%20%20%20%20result_tab%5B%22group1_mean%22%5D%20%3D%20result_tab%5B%22group1%22%5D.map(df_means%5Bmetric%5D)%0A%20%20%20%20%20%20%20%20result_tab%5B%22group2_mean%22%5D%20%3D%20result_tab%5B%22group2%22%5D.map(df_means%5Bmetric%5D)%0A%0A%20%20%20%20%20%20%20%20result_tab.index%20%3D%20result_tab%5B'group1'%5D%20%2B%20'%20-%20'%20%2B%20result_tab%5B'group2'%5D%0A%0A%20%20%20%20%20%20%20%20return%20result_tab%2C%20df_means%2C%20df_means_diff%2C%20pc%0A%0A%20%20%20%20return%20(rm_tukey_hsd%2C)%0A%0A%0A%40app.cell%0Adef%20_(AnovaRM%2C%20Optional%2C%20Path%2C%20pg%2C%20pl%2C%20plt%2C%20sns)%3A%0A%20%20%20%20def%20make_boxplots_parametric(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20metric_ls%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20save_path%3A%20Optional%5BPath%5D%20%3D%20None%2C%0A%20%20%20%20)%20-%3E%20plt.Figure%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Create%20boxplots%20for%20each%20metric%20using%20repeated%20measures%20ANOVA.%0A%0A%20%20%20%20%20%20%20%20Converts%20to%20pandas%20internally%20because%20statsmodels%20AnovaRM%20and%20seaborn%0A%20%20%20%20%20%20%20%20require%20pandas%20DataFrames.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20columns%20%5Bcv_cycle%2C%20method%5D%20plus%20metric%20columns.%0A%20%20%20%20%20%20%20%20%20%20%20%20metric_ls%3A%20List%20of%20metric%20column%20names%20to%20create%20boxplots%20for.%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20If%20provided%2C%20the%20figure%20is%20saved%20to%20this%20path%20before%20returning.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Matplotlib%20Figure.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20AnovaRM%20and%20seaborn%20both%20require%20pandas%0A%20%20%20%20%20%20%20%20df_pd%20%3D%20df.to_pandas()%0A%0A%20%20%20%20%20%20%20%20sns.set_context('notebook')%0A%20%20%20%20%20%20%20%20sns.set(rc%3D%7B'figure.figsize'%3A%20(4%2C%203)%7D%2C%20font_scale%3D1.5)%0A%20%20%20%20%20%20%20%20sns.set_style('whitegrid')%0A%20%20%20%20%20%20%20%20figure%2C%20axes%20%3D%20plt.subplots(2%2C%202%2C%20sharex%3DFalse%2C%20sharey%3DFalse%2C%20figsize%3D(14%2C%2010))%0A%0A%20%20%20%20%20%20%20%20for%20i%2C%20stat%20in%20enumerate(metric_ls)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20model%20%3D%20AnovaRM(data%3Ddf_pd%2C%20depvar%3Dstat%2C%20subject%3D'cv_cycle'%2C%20within%3D%5B'method'%5D).fit()%0A%20%20%20%20%20%20%20%20%20%20%20%20p_value%20%3D%20model.anova_table%5B'Pr%20%3E%20F'%5D.iloc%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%20%3D%20sns.boxplot(y%3Dstat%2C%20x%3D%22method%22%2C%20hue%3D%22method%22%2C%20ax%3Daxes%5Bi%20%2F%2F%202%2C%20i%20%25%202%5D%2C%20data%3Ddf_pd%2C%20palette%3D%22Set2%22%2C%20legend%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20title%20%3D%20stat.upper()%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(f%22p%3D%7Bp_value%3A.1e%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlabel(%22%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_ylabel(title)%0A%20%20%20%20%20%20%20%20%20%20%20%20x_tick_labels%20%3D%20ax.get_xticklabels()%0A%20%20%20%20%20%20%20%20%20%20%20%20label_text_list%20%3D%20%5Bx.get_text()%20for%20x%20in%20x_tick_labels%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20new_xtick_labels%20%3D%20%5B%22%5Cn%22.join(x.split(%22_%22))%20for%20x%20in%20label_text_list%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xticks(list(range(0%2C%20len(x_tick_labels))))%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xticklabels(new_xtick_labels)%0A%20%20%20%20%20%20%20%20figure.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%20figure.savefig(save_path%2C%20dpi%3D300%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20%20%20%20%20return%20figure%0A%0A%20%20%20%20def%20make_boxplots_nonparametric(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20metric_ls%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20save_path%3A%20Optional%5BPath%5D%20%3D%20None%2C%0A%20%20%20%20)%20-%3E%20plt.Figure%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Create%20boxplots%20for%20each%20metric%20using%20the%20Friedman%20non-parametric%20test.%0A%0A%20%20%20%20%20%20%20%20Converts%20to%20pandas%20internally%20because%20pingouin%20and%20seaborn%20require%20pandas.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20columns%20%5Bcv_cycle%2C%20method%5D%20plus%20metric%20columns.%0A%20%20%20%20%20%20%20%20%20%20%20%20metric_ls%3A%20List%20of%20metric%20column%20names%20to%20create%20boxplots%20for.%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20If%20provided%2C%20the%20figure%20is%20saved%20to%20this%20path%20before%20returning.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Matplotlib%20Figure.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20pingouin%20and%20seaborn%20both%20require%20pandas%0A%20%20%20%20%20%20%20%20df_pd%20%3D%20df.to_pandas()%0A%0A%20%20%20%20%20%20%20%20sns.set_context('notebook')%0A%20%20%20%20%20%20%20%20sns.set(rc%3D%7B'figure.figsize'%3A%20(4%2C%203)%7D%2C%20font_scale%3D1.5)%0A%20%20%20%20%20%20%20%20sns.set_style('whitegrid')%0A%20%20%20%20%20%20%20%20figure%2C%20axes%20%3D%20plt.subplots(2%2C%202%2C%20sharex%3DFalse%2C%20sharey%3DFalse%2C%20figsize%3D(14%2C%2010))%0A%0A%20%20%20%20%20%20%20%20for%20i%2C%20stat%20in%20enumerate(metric_ls)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20friedman%20%3D%20pg.friedman(df_pd%2C%20dv%3Dstat%2C%20within%3D%22method%22%2C%20subject%3D%22cv_cycle%22)%5B'p_unc'%5D.values%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%20%3D%20sns.boxplot(y%3Dstat%2C%20x%3D%22method%22%2C%20hue%3D%22method%22%2C%20ax%3Daxes%5Bi%20%2F%2F%202%2C%20i%20%25%202%5D%2C%20data%3Ddf_pd%2C%20palette%3D%22Set2%22%2C%20legend%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20title%20%3D%20stat.replace(%22_%22%2C%20%22%20%22).upper()%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(f%22p%3D%7Bfriedman%3A.1e%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlabel(%22%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_ylabel(title)%0A%20%20%20%20%20%20%20%20%20%20%20%20x_tick_labels%20%3D%20ax.get_xticklabels()%0A%20%20%20%20%20%20%20%20%20%20%20%20label_text_list%20%3D%20%5Bx.get_text()%20for%20x%20in%20x_tick_labels%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20new_xtick_labels%20%3D%20%5B%22%5Cn%22.join(x.split(%22_%22))%20for%20x%20in%20label_text_list%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xticks(list(range(0%2C%20len(x_tick_labels))))%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xticklabels(new_xtick_labels)%0A%20%20%20%20%20%20%20%20figure.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%20figure.savefig(save_path%2C%20dpi%3D300%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20%20%20%20%20return%20figure%0A%0A%20%20%20%20return%20(make_boxplots_nonparametric%2C)%0A%0A%0A%40app.cell%0Adef%20_(Optional%2C%20Path%2C%20math%2C%20np%2C%20pl%2C%20plt%2C%20rm_tukey_hsd%2C%20sns%2C%20stats)%3A%0A%20%20%20%20def%20make_normality_diagnostic(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20metric_ls%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20save_path%3A%20Optional%5BPath%5D%20%3D%20None%2C%0A%20%20%20%20)%20-%3E%20plt.Figure%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Create%20a%20normality%20diagnostic%20plot%20grid%20with%20histograms%20and%20QQ%20plots%20for%20the%20given%20metrics.%0A%0A%20%20%20%20%20%20%20%20Residuals%20are%20computed%20by%20subtracting%20each%20group's%20mean%20(per%20method)%20so%20that%0A%20%20%20%20%20%20%20%20the%20normality%20assumption%20of%20the%20repeated-measures%20ANOVA%20can%20be%20assessed.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20columns%20%5Bcv_cycle%2C%20method%2C%20split%5D%20plus%20metric%20columns.%0A%20%20%20%20%20%20%20%20%20%20%20%20metric_ls%3A%20List%20of%20metric%20column%20names%20to%20assess%20for%20normality.%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20If%20provided%2C%20the%20figure%20is%20saved%20to%20this%20path%20before%20returning.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Matplotlib%20Figure.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20Subtract%20per-method%20group%20mean%20from%20each%20metric%20(mean-centre%20within%20method)%0A%20%20%20%20%20%20%20%20group_means%20%3D%20df.group_by(%22method%22).agg(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(m).mean().alias(f%22_mean_%7Bm%7D%22)%20for%20m%20in%20metric_ls%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20df_norm%20%3D%20df.join(group_means%2C%20on%3D%22method%22%2C%20how%3D%22left%22)%0A%20%20%20%20%20%20%20%20df_norm%20%3D%20df_norm.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(m)%20-%20pl.col(f%22_mean_%7Bm%7D%22)).alias(m)%20for%20m%20in%20metric_ls%0A%20%20%20%20%20%20%20%20%5D).drop(%5Bf%22_mean_%7Bm%7D%22%20for%20m%20in%20metric_ls%5D)%0A%0A%20%20%20%20%20%20%20%20%23%20Unpivot%20(melt)%20to%20long%20format%20for%20easy%20per-metric%20iteration%0A%20%20%20%20%20%20%20%20df_long%20%3D%20df_norm.unpivot(%0A%20%20%20%20%20%20%20%20%20%20%20%20on%3Dmetric_ls%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20index%3D%5B%22cv_cycle%22%2C%20%22method%22%2C%20%22split%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20variable_name%3D%22metric%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20value_name%3D%22value%22%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Convert%20to%20pandas%20for%20seaborn%20and%20scipy.stats.probplot%0A%20%20%20%20%20%20%20%20df_long_pd%20%3D%20df_long.to_pandas()%0A%0A%20%20%20%20%20%20%20%20sns.set_context('notebook'%2C%20font_scale%3D1.5)%0A%20%20%20%20%20%20%20%20sns.set_style('whitegrid')%0A%0A%20%20%20%20%20%20%20%20metrics%20%3D%20df_long_pd%5B'metric'%5D.unique()%0A%20%20%20%20%20%20%20%20n_metrics%20%3D%20len(metrics)%0A%0A%20%20%20%20%20%20%20%20fig%2C%20axes%20%3D%20plt.subplots(2%2C%20n_metrics%2C%20figsize%3D(20%2C%2010))%0A%0A%20%20%20%20%20%20%20%20for%20i%2C%20metric%20in%20enumerate(metrics)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%20%3D%20axes%5B0%2C%20i%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20sns.histplot(df_long_pd%5Bdf_long_pd%5B'metric'%5D%20%3D%3D%20metric%5D%5B'value'%5D%2C%20kde%3DTrue%2C%20ax%3Dax)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(f'%7Bmetric%7D'%2C%20fontsize%3D16)%0A%0A%20%20%20%20%20%20%20%20for%20i%2C%20metric%20in%20enumerate(metrics)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%20%3D%20axes%5B1%2C%20i%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20metric_data%20%3D%20df_long_pd%5Bdf_long_pd%5B'metric'%5D%20%3D%3D%20metric%5D%5B'value'%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20stats.probplot(metric_data%2C%20dist%3D%22norm%22%2C%20plot%3Dax)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(%22%22)%0A%0A%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(save_path%2C%20dpi%3D300%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20%20%20%20%20return%20fig%0A%0A%0A%20%20%20%20def%20mcs_plot(pc%2C%20effect_size%2C%20means%2C%20labels%3DTrue%2C%20cmap%3DNone%2C%20cbar_ax_bbox%3DNone%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax%3DNone%2C%20show_diff%3DTrue%2C%20cell_text_size%3D16%2C%20axis_text_size%3D12%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20show_cbar%3DTrue%2C%20reverse_cmap%3DFalse%2C%20vlim%3DNone%2C%20**kwargs)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Create%20a%20multiple%20comparison%20of%20means%20plot%20using%20a%20heatmap.%0A%0A%20%20%20%20%20%20%20%20Parameters%3A%0A%20%20%20%20%20%20%20%20pc%20(pd.DataFrame)%3A%20DataFrame%20containing%20p-values%20for%20pairwise%20comparisons.%0A%20%20%20%20%20%20%20%20effect_size%20(pd.DataFrame)%3A%20DataFrame%20containing%20effect%20sizes%20for%20pairwise%20comparisons.%0A%20%20%20%20%20%20%20%20means%20(pd.Series)%3A%20Series%20containing%20mean%20values%20for%20each%20group.%0A%20%20%20%20%20%20%20%20labels%20(bool)%3A%20Whether%20to%20show%20labels%20on%20the%20axes.%20Default%20is%20True.%0A%20%20%20%20%20%20%20%20cmap%20(str)%3A%20Colormap%20to%20use%20for%20the%20heatmap.%20Default%20is%20None.%0A%20%20%20%20%20%20%20%20cbar_ax_bbox%20(tuple)%3A%20Bounding%20box%20for%20the%20colorbar%20axis.%20Default%20is%20None.%0A%20%20%20%20%20%20%20%20ax%20(matplotlib.axes.Axes)%3A%20The%20axes%20on%20which%20to%20plot%20the%20heatmap.%20Default%20is%20None.%0A%20%20%20%20%20%20%20%20show_diff%20(bool)%3A%20Whether%20to%20show%20the%20mean%20differences%20in%20the%20plot.%20Default%20is%20True.%0A%20%20%20%20%20%20%20%20cell_text_size%20(int)%3A%20Font%20size%20for%20the%20cell%20text.%20Default%20is%2016.%0A%20%20%20%20%20%20%20%20axis_text_size%20(int)%3A%20Font%20size%20for%20the%20axis%20text.%20Default%20is%2012.%0A%20%20%20%20%20%20%20%20show_cbar%20(bool)%3A%20Whether%20to%20show%20the%20colorbar.%20Default%20is%20True.%0A%20%20%20%20%20%20%20%20reverse_cmap%20(bool)%3A%20Whether%20to%20reverse%20the%20colormap.%20Default%20is%20False.%0A%20%20%20%20%20%20%20%20vlim%20(float)%3A%20Limit%20for%20the%20colormap.%20Default%20is%20None.%0A%20%20%20%20%20%20%20%20**kwargs%3A%20Additional%20keyword%20arguments%20for%20the%20heatmap.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20matplotlib.axes.Axes%3A%20The%20axes%20with%20the%20heatmap.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20for%20key%20in%20%5B'cbar'%2C%20'vmin'%2C%20'vmax'%2C%20'center'%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20key%20in%20kwargs%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20kwargs%5Bkey%5D%0A%0A%20%20%20%20%20%20%20%20if%20not%20cmap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20cmap%20%3D%20%22coolwarm%22%0A%20%20%20%20%20%20%20%20if%20reverse_cmap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20cmap%20%3D%20cmap%20%2B%20%22_r%22%0A%0A%20%20%20%20%20%20%20%20significance%20%3D%20pc.copy().astype(object)%0A%20%20%20%20%20%20%20%20significance%5B(pc%20%3C%200.001)%20%26%20(pc%20%3E%3D%200)%5D%20%3D%20'***'%0A%20%20%20%20%20%20%20%20significance%5B(pc%20%3C%200.01)%20%26%20(pc%20%3E%3D%200.001)%5D%20%3D%20'**'%0A%20%20%20%20%20%20%20%20significance%5B(pc%20%3C%200.05)%20%26%20(pc%20%3E%3D%200.01)%5D%20%3D%20'*'%0A%20%20%20%20%20%20%20%20significance%5B(pc%20%3E%3D%200.05)%5D%20%3D%20''%0A%0A%20%20%20%20%20%20%20%20np.fill_diagonal(significance.values%2C%20'')%0A%0A%20%20%20%20%20%20%20%20%23%20Create%20a%20DataFrame%20for%20the%20annotations%0A%20%20%20%20%20%20%20%20if%20show_diff%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20annotations%20%3D%20effect_size.round(3).astype(str)%20%2B%20significance%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20annotations%20%3D%20significance%0A%0A%20%20%20%20%20%20%20%20hax%20%3D%20sns.heatmap(effect_size%2C%20cmap%3Dcmap%2C%20annot%3Dannotations%2C%20fmt%3D''%2C%20cbar%3Dshow_cbar%2C%20ax%3Dax%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%20annot_kws%3D%7B%22size%22%3A%20cell_text_size%7D%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%20vmin%3D-2*vlim%20if%20vlim%20else%20None%2C%20vmax%3D2*vlim%20if%20vlim%20else%20None%2C%20**kwargs)%0A%0A%20%20%20%20%20%20%20%20if%20labels%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20label_list%20%3D%20list(means.index)%0A%20%20%20%20%20%20%20%20%20%20%20%20x_label_list%20%3D%20%5Bx%20%2B%20f'%5Cn%7Bmeans.loc%5Bx%5D.round(2)%7D'%20for%20x%20in%20label_list%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20y_label_list%20%3D%20%5Bx%20%2B%20f'%5Cn%7Bmeans.loc%5Bx%5D.round(2)%7D%5Cn'%20for%20x%20in%20label_list%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20hax.set_xticklabels(x_label_list%2C%20size%3Daxis_text_size%2C%20ha%3D'center'%2C%20va%3D'top'%2C%20rotation%3D0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rotation_mode%3D'anchor')%0A%20%20%20%20%20%20%20%20%20%20%20%20hax.set_yticklabels(y_label_list%2C%20size%3Daxis_text_size%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20rotation%3D90%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rotation_mode%3D'anchor')%0A%0A%20%20%20%20%20%20%20%20hax.set_xlabel('')%0A%20%20%20%20%20%20%20%20hax.set_ylabel('')%0A%0A%20%20%20%20%20%20%20%20return%20hax%0A%0A%0A%20%20%20%20def%20make_mcs_plot_grid(df%2C%20stats%2C%20group_col%2C%20alpha%3D.05%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%20figsize%3D(20%2C%2010)%2C%20direction_dict%3D%7B%7D%2C%20effect_dict%3D%7B%7D%2C%20show_diff%3DTrue%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%20cell_text_size%3D16%2C%20axis_text_size%3D12%2C%20title_text_size%3D16%2C%20sort_axes%3DFalse%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%20save_path%3A%20Optional%5BPath%5D%20%3D%20None)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Create%20a%20grid%20of%20multiple%20comparison%20of%20means%20plots%20using%20Tukey%20HSD%20test%20results.%0A%0A%20%20%20%20%20%20%20%20Parameters%3A%0A%20%20%20%20%20%20%20%20df%20(pd.DataFrame)%3A%20Input%20dataframe%20containing%20the%20data.%0A%20%20%20%20%20%20%20%20stats%20(list%20of%20str)%3A%20List%20of%20statistical%20metrics%20to%20create%20plots%20for.%0A%20%20%20%20%20%20%20%20group_col%20(str)%3A%20The%20column%20name%20indicating%20the%20groups.%0A%20%20%20%20%20%20%20%20alpha%20(float)%3A%20Significance%20level%20for%20the%20Tukey%20HSD%20test.%20Default%20is%200.05.%0A%20%20%20%20%20%20%20%20figsize%20(tuple)%3A%20Size%20of%20the%20figure.%20Default%20is%20(20%2C%2010).%0A%20%20%20%20%20%20%20%20direction_dict%20(dict)%3A%20Dictionary%20indicating%20whether%20to%20minimize%20or%20maximize%20each%20metric.%0A%20%20%20%20%20%20%20%20effect_dict%20(dict)%3A%20Dictionary%20with%20effect%20size%20limits%20for%20each%20metric.%0A%20%20%20%20%20%20%20%20show_diff%20(bool)%3A%20Whether%20to%20show%20the%20mean%20differences%20in%20the%20plot.%20Default%20is%20True.%0A%20%20%20%20%20%20%20%20cell_text_size%20(int)%3A%20Font%20size%20for%20the%20cell%20text.%20Default%20is%2016.%0A%20%20%20%20%20%20%20%20axis_text_size%20(int)%3A%20Font%20size%20for%20the%20axis%20text.%20Default%20is%2012.%0A%20%20%20%20%20%20%20%20title_text_size%20(int)%3A%20Font%20size%20for%20the%20title%20text.%20Default%20is%2016.%0A%20%20%20%20%20%20%20%20sort%20(bool)%3A%20Whether%20to%20sort%20the%20axes.%20Default%20is%20False.%0A%20%20%20%20%20%20%20%20save_path%20(Path%20%7C%20None)%3A%20If%20provided%2C%20the%20figure%20is%20saved%20to%20this%20path%20before%20returning.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20plt.Figure%3A%20The%20figure%20with%20the%20grid%20of%20heatmaps.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20Use%20a%201-column%20grid%20for%20a%20single%20stat%2C%202%20columns%20for%204%20stats%2C%20else%203.%0A%20%20%20%20%20%20%20%20ncol%20%3D%201%20if%20len(stats)%20%3D%3D%201%20else%20(2%20if%20len(stats)%20%3D%3D%204%20else%203)%0A%20%20%20%20%20%20%20%20nrow%20%3D%20math.ceil(len(stats)%20%2F%20ncol)%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(nrow%2C%20ncol%2C%20figsize%3Dfigsize%2C%20squeeze%3DFalse)%0A%0A%20%20%20%20%20%20%20%20%23%20Set%20defaults%0A%20%20%20%20%20%20%20%20for%20key%20in%20%5B'r2'%2C%20'rho'%2C%20'prec'%2C%20'recall'%2C%20'mae'%2C%20'mse'%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20direction_dict.setdefault(key%2C%20'maximize'%20if%20key%20in%20%5B'r2'%2C%20'rho'%2C%20'prec'%2C%20'recall'%5D%20else%20'minimize')%0A%0A%20%20%20%20%20%20%20%20for%20key%20in%20%5B'r2'%2C%20'rho'%2C%20'prec'%2C%20'recall'%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20effect_dict.setdefault(key%2C%200.1)%0A%20%20%20%20%20%20%20%20effect_dict.setdefault('mae'%2C%200.5)%0A%20%20%20%20%20%20%20%20effect_dict.setdefault('mse'%2C%201.0)%0A%0A%20%20%20%20%20%20%20%20direction_dict%20%3D%20%7Bk.lower()%3A%20v%20for%20k%2C%20v%20in%20direction_dict.items()%7D%0A%20%20%20%20%20%20%20%20effect_dict%20%3D%20%7Bk.lower()%3A%20v%20for%20k%2C%20v%20in%20effect_dict.items()%7D%0A%0A%20%20%20%20%20%20%20%20for%20i%2C%20stat%20in%20enumerate(stats)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20stat%20%3D%20stat.lower()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20row%20%3D%20i%20%2F%2F%20ncol%0A%20%20%20%20%20%20%20%20%20%20%20%20col%20%3D%20i%20%25%20ncol%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20stat%20not%20in%20direction_dict%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22Stat%20'%7Bstat%7D'%20is%20missing%20in%20direction_dict.%20Please%20set%20its%20value.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20stat%20not%20in%20effect_dict%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(f%22Stat%20'%7Bstat%7D'%20is%20missing%20in%20effect_dict.%20Please%20set%20its%20value.%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20reverse_cmap%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20direction_dict%5Bstat%5D%20%3D%3D%20'minimize'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reverse_cmap%20%3D%20True%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_%2C%20df_means%2C%20df_means_diff%2C%20pc%20%3D%20rm_tukey_hsd(df%2C%20stat%2C%20group_col%2C%20alpha%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sort_axes%2C%20direction_dict)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20hax%20%3D%20mcs_plot(pc%2C%20effect_size%3Ddf_means_diff%2C%20means%3Ddf_means%5Bstat%5D%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%20show_diff%3Dshow_diff%2C%20ax%3Dax%5Brow%2C%20col%5D%2C%20cbar%3DTrue%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%20cell_text_size%3Dcell_text_size%2C%20axis_text_size%3Daxis_text_size%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%20reverse_cmap%3Dreverse_cmap%2C%20vlim%3Deffect_dict%5Bstat%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20hax.set_title(stat.upper()%2C%20fontsize%3Dtitle_text_size)%0A%0A%20%20%20%20%20%20%20%20%23%20If%20there%20are%20less%20plots%20than%20cells%20in%20the%20grid%2C%20hide%20the%20remaining%20cells%0A%20%20%20%20%20%20%20%20if%20(len(stats)%20%25%20ncol)%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20i%20in%20range(len(stats)%2C%20nrow%20*%20ncol)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20row%20%3D%20i%20%2F%2F%20ncol%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20col%20%3D%20i%20%25%20ncol%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax%5Brow%2C%20col%5D.set_visible(False)%0A%0A%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(save_path%2C%20dpi%3D300%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20%20%20%20%20return%20fig%0A%0A%20%20%20%20return%20(make_mcs_plot_grid%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Optional%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20precision_score%2C%0A%20%20%20%20recall_score%2C%0A%20%20%20%20rm_tukey_hsd%2C%0A%20%20%20%20sns%2C%0A)%3A%0A%20%20%20%20def%20make_scatterplot(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20val_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20pred_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20thresh%3A%20float%2C%0A%20%20%20%20%20%20%20%20cycle_col%3A%20str%20%3D%20%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20group_col%3A%20str%20%3D%20%22method%22%2C%0A%20%20%20%20%20%20%20%20save_path%3A%20Optional%5BPath%5D%20%3D%20None%2C%0A%20%20%20%20)%20-%3E%20plt.Figure%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Create%20scatter%20plots%20for%20each%20method%20showing%20the%20relationship%20between%20predicted%20and%20measured%20values.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20columns%20%5Bgroup_col%2C%20cycle_col%2C%20val_col%2C%20pred_col%5D.%0A%20%20%20%20%20%20%20%20%20%20%20%20val_col%3A%20Column%20name%20for%20the%20ground%20truth%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3A%20Column%20name%20for%20the%20model%20predictions.%0A%20%20%20%20%20%20%20%20%20%20%20%20thresh%3A%20Decision%20threshold%20for%20binary%20precision%2Frecall%20computation.%0A%20%20%20%20%20%20%20%20%20%20%20%20cycle_col%3A%20Column%20indicating%20the%20cross-validation%20fold.%20Default%20is%20%22cv_cycle%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20group_col%3A%20Column%20indicating%20the%20comparison%20groups%2Fmethods.%20Default%20is%20%22method%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20If%20provided%2C%20the%20figure%20is%20saved%20to%20this%20path%20before%20returning.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Matplotlib%20Figure.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20df_split_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20%20%20%20%20df%2C%20cycle_col%3Dcycle_col%2C%20val_col%3Dval_col%2C%20pred_col%3Dpred_col%2C%20thresh%3Dthresh%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20methods%20%3D%20df%5Bgroup_col%5D.unique().to_list()%0A%0A%20%20%20%20%20%20%20%20fig%2C%20axs%20%3D%20plt.subplots(nrows%3D3%2C%20ncols%3D2%2C%20figsize%3D(14%2C%2018))%0A%20%20%20%20%20%20%20%20axs_flat%20%3D%20axs.flatten()%0A%0A%20%20%20%20%20%20%20%20for%20ax%2C%20method%20in%20zip(axs_flat%2C%20methods)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Filter%20using%20Polars%20expressions%0A%20%20%20%20%20%20%20%20%20%20%20%20df_method%20%3D%20df.filter(pl.col(group_col)%20%3D%3D%20method)%0A%20%20%20%20%20%20%20%20%20%20%20%20df_metrics%20%3D%20df_split_metrics.filter(pl.col(group_col)%20%3D%3D%20method)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20y_true_vals%20%3D%20df_method%5Bval_col%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20y_pred_vals%20%3D%20df_method%5Bpred_col%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.scatter(y_pred_vals%2C%20y_true_vals%2C%20alpha%3D0.3)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.plot(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5By_true_vals.min()%2C%20y_true_vals.max()%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5By_true_vals.min()%2C%20y_true_vals.max()%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20'k--'%2C%20lw%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.axhline(y%3Dthresh%2C%20color%3D'r'%2C%20linestyle%3D'--')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.axvline(x%3Dthresh%2C%20color%3D'r'%2C%20linestyle%3D'--')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(method)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20precision%20%3D%20precision_score(y_true_vals%20%3E%20thresh%2C%20y_pred_vals%20%3E%20thresh)%0A%20%20%20%20%20%20%20%20%20%20%20%20recall%20%3D%20recall_score(y_true_vals%20%3E%20thresh%2C%20y_pred_vals%20%3E%20thresh)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Aggregate%20mean%20metrics%20across%20CV%20folds%20for%20the%20annotation%0A%20%20%20%20%20%20%20%20%20%20%20%20mae_mean%20%20%3D%20df_metrics%5B%22mae%22%5D.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20mse_mean%20%20%3D%20df_metrics%5B%22mse%22%5D.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20r2_mean%20%20%20%3D%20df_metrics%5B%22r2%22%5D.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20rho_mean%20%20%3D%20df_metrics%5B%22rho%22%5D.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20metrics_text%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22MAE%3A%20%7Bmae_mean%3A.2f%7D%5CnMSE%3A%20%7Bmse_mean%3A.2f%7D%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22R2%3A%20%7Br2_mean%3A.2f%7D%5Cnrho%3A%20%7Brho_mean%3A.2f%7D%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Precision%3A%20%7Bprecision%3A.2f%7D%5CnRecall%3A%20%7Brecall%3A.2f%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.text(0.05%2C%20.5%2C%20metrics_text%2C%20transform%3Dax.transAxes%2C%20verticalalignment%3D'top')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlabel('Predicted')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_ylabel('Measured')%0A%0A%20%20%20%20%20%20%20%20for%20ax%20in%20axs_flat%5Blen(methods)%3A%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_visible(False)%0A%0A%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(save_path%2C%20dpi%3D300%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20%20%20%20%20return%20fig%0A%0A%0A%20%20%20%20def%20ci_plot(%0A%20%20%20%20%20%20%20%20result_tab%2C%0A%20%20%20%20%20%20%20%20ax_in%2C%0A%20%20%20%20%20%20%20%20name%3A%20str%2C%0A%20%20%20%20%20%20%20%20show_ylabel%3A%20bool%20%3D%20True%2C%0A%20%20%20%20%20%20%20%20xlim%3A%20tuple%5Bfloat%2C%20float%5D%20%3D%20(-0.2%2C%200.2)%2C%0A%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Create%20a%20confidence%20interval%20plot%20for%20the%20given%20result%20table.%0A%0A%20%20%20%20%20%20%20%20result_tab%20is%20a%20pandas%20DataFrame%20produced%20by%20rm_tukey_hsd%20%E2%80%94%20seaborn's%0A%20%20%20%20%20%20%20%20pointplot%20and%20errorbar%20require%20pandas%20Series%20for%20its%20index%20labels.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result_tab%3A%20pandas%20DataFrame%20with%20columns%20%5B'meandiff'%2C%20'lower'%2C%20'upper'%5D.%0A%20%20%20%20%20%20%20%20%20%20%20%20ax_in%3A%20Matplotlib%20Axes%20on%20which%20to%20draw%20the%20plot.%0A%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20Title%20string%20for%20the%20subplot.%0A%20%20%20%20%20%20%20%20%20%20%20%20show_ylabel%3A%20Whether%20to%20show%20y-axis%20tick%20labels.%20Set%20False%20for%20right-column%20axes.%0A%20%20%20%20%20%20%20%20%20%20%20%20xlim%3A%20X-axis%20limits%20as%20(min%2C%20max).%20Default%20is%20(-0.2%2C%200.2).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20result_err%20%3D%20np.array(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20result_tab%5B'meandiff'%5D%20-%20result_tab%5B'lower'%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20result_tab%5B'upper'%5D%20-%20result_tab%5B'meandiff'%5D%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20sns.set(rc%3D%7B'figure.figsize'%3A%20(6%2C%202)%7D)%0A%20%20%20%20%20%20%20%20sns.set_context('notebook')%0A%20%20%20%20%20%20%20%20sns.set_style('whitegrid')%0A%20%20%20%20%20%20%20%20ax%20%3D%20sns.pointplot(x%3Dresult_tab.meandiff%2C%20y%3Dresult_tab.index%2C%20marker%3D'o'%2C%20linestyle%3D''%2C%20ax%3Dax_in)%0A%20%20%20%20%20%20%20%20ax.errorbar(y%3Dresult_tab.index%2C%20x%3Dresult_tab%5B'meandiff'%5D%2C%20xerr%3Dresult_err%2C%20fmt%3D'o'%2C%20capsize%3D5)%0A%20%20%20%20%20%20%20%20ax.axvline(0%2C%20ls%3D%22--%22%2C%20lw%3D3)%0A%20%20%20%20%20%20%20%20ax.set_xlabel(%22Mean%20Difference%22)%0A%20%20%20%20%20%20%20%20ax.set_ylabel(%22%22)%0A%20%20%20%20%20%20%20%20ax.set_title(name)%0A%20%20%20%20%20%20%20%20ax.set_xlim(*xlim)%0A%20%20%20%20%20%20%20%20if%20not%20show_ylabel%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_yticklabels(%5B%5D)%0A%0A%0A%20%20%20%20def%20make_ci_plot_grid(%0A%20%20%20%20%20%20%20%20df_in%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20metric_list%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20group_col%3A%20str%20%3D%20%22method%22%2C%0A%20%20%20%20%20%20%20%20figsize%3A%20tuple%5Bint%2C%20int%5D%20%3D%20(14%2C%2012)%2C%0A%20%20%20%20%20%20%20%20xlim%3A%20tuple%5Bfloat%2C%20float%5D%20%3D%20(-0.2%2C%200.2)%2C%0A%20%20%20%20%20%20%20%20save_path%3A%20Optional%5BPath%5D%20%3D%20None%2C%0A%20%20%20%20)%20-%3E%20plt.Figure%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Create%20a%20grid%20of%20confidence%20interval%20plots%20for%20multiple%20metrics%20using%20Tukey%20HSD%20test%20results.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df_in%3A%20Polars%20DataFrame%20passed%20through%20to%20rm_tukey_hsd%20(converted%20internally).%0A%20%20%20%20%20%20%20%20%20%20%20%20metric_list%3A%20List%20of%20metric%20column%20names%20to%20create%20confidence%20interval%20plots%20for.%0A%20%20%20%20%20%20%20%20%20%20%20%20group_col%3A%20Column%20indicating%20the%20comparison%20groups.%20Default%20is%20%22method%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20figsize%3A%20Figure%20size%20as%20(width%2C%20height).%20Default%20is%20(14%2C%2012).%0A%20%20%20%20%20%20%20%20%20%20%20%20xlim%3A%20X-axis%20limits%20passed%20to%20each%20ci_plot%20as%20(min%2C%20max).%20Default%20is%20(-0.2%2C%200.2).%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20If%20provided%2C%20the%20figure%20is%20saved%20to%20this%20path%20before%20returning.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Matplotlib%20Figure.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20figure%2C%20axes%20%3D%20plt.subplots(2%2C%202%2C%20figsize%3Dfigsize%2C%20sharex%3DFalse)%0A%20%20%20%20%20%20%20%20for%20i%2C%20metric%20in%20enumerate(metric_list)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row%2C%20col%20%3D%20i%20%2F%2F%202%2C%20i%20%25%202%0A%20%20%20%20%20%20%20%20%20%20%20%20df_tukey%2C%20_%2C%20_%2C%20_%20%3D%20rm_tukey_hsd(df_in%2C%20metric%2C%20group_col%3Dgroup_col)%0A%20%20%20%20%20%20%20%20%20%20%20%20ci_plot(df_tukey%2C%20ax_in%3Daxes%5Brow%2C%20col%5D%2C%20name%3Dmetric%2C%20show_ylabel%3D(col%20%3D%3D%200)%2C%20xlim%3Dxlim)%0A%20%20%20%20%20%20%20%20for%20ax%20in%20axes.flatten()%5Blen(metric_list)%3A%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_visible(False)%0A%20%20%20%20%20%20%20%20figure.suptitle(%22Multiple%20Comparison%20of%20Means%5CnTukey%20HSD%2C%20FWER%3D0.05%22)%0A%20%20%20%20%20%20%20%20figure.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%20figure.savefig(save_path%2C%20dpi%3D300%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20%20%20%20%20return%20figure%0A%0A%20%20%20%20return%20(make_ci_plot_grid%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%20Read%20train%20dataset%20and%20test%20different%20data%20splits%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(gc%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%20single_task_train%20%3D%20all_compounds.filter(pl.col(%22pEC50_dr%22).is_not_null()).%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20select(%5B%22smiles%22%2C%22inchikey%22%2C%20%22molecule_names%22%2C%20%22pEC50_dr%22%5D)%0A%20%20%20%20single_task_train%0A%0A%20%20%20%20del%20all_compounds%0A%20%20%20%20gc.collect()%0A%20%20%20%20return%20(single_task_train%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Compare%20larger%20Chemprop%20to%20CheMeleon%0A%0A%20%20%20%20Before%20we%20saw%20Chemeleon%20being%20significantly%20better%20than%20Chemprop.%0A%20%20%20%20But%20Chemeleon%2C%20in%20addition%20to%20its%20pretraining%2C%20is%20a%20larger%20model%20than%20Chemprop%20(2048%20vs%20300%20d_h%20and%203%20vs%206%20depth).%0A%20%20%20%20I%20want%20to%20check%20if%20chemprop%20as%20a%20larger%20model%20but%20no%20pretraining%20comes%20close%20to%20chemeleon%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ChempropModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20single_task_train%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20constants%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%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_TARGET_COL%20%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_PRED_PATH_GZ%20%3D%20Path(%22..%2Fpredictions%2F3_larger_chemprop_size_test.csv.gz%22)%0A%20%20%20%20_N_OUTER%20%20%20%20%3D%205%0A%20%20%20%20_N_INNER%20%20%20%20%3D%205%0A%20%20%20%20_SEED%20%20%20%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%20%20%20%3D%200.1%20%20%20%20%20%20%20%20%20%20%23%20fraction%20of%20train%20kept%20as%20validation%20(XGBoost%20%2F%20Chemprop%20early%20stopping)%0A%0A%20%20%20%20_MODEL_NAMES%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22chemprop_base%22%3A%20%7B%7D%2C%0A%20%20%20%20%20%20%20%20%22chemprop_depth6%22%3A%20%7B%22depth%22%3A%206%7D%2C%0A%20%20%20%20%20%20%20%20%22chemprop_1kwidth%22%3A%20%7B%22message_hidden_dim%22%3A%201024%7D%2C%0A%20%20%20%20%20%20%20%20%22chemprop_2kwidth%22%3A%20%7B%22message_hidden_dim%22%3A%202048%7D%2C%0A%20%20%20%20%20%20%20%20%22chemprop_2kwidth_depth6%22%3A%20%7B%22depth%22%3A%206%2C%20%22message_hidden_dim%22%3A%202048%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20_PRED_PATH_GZ.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22Predictions%20already%20exist%20at%20%7B_PRED_PATH_GZ%7D%20%E2%80%94%20skipping%20training.%22)%0A%20%20%20%20%20%20%20%20_pred_df%20%3D%20pl.read_csv(_PRED_PATH_GZ)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20run%20all%2025%20folds%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%0A%20%20%20%20%20%20%20%20%23_debug_df%20%3D%20whole_train.sample(n%3D100%2C%20seed%3D_SEED)%20%20%23%20TODO%3A%20remove%20for%20full%20run%0A%20%20%20%20%20%20%20%20_all_records%3A%20list%5Bdict%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20_n_folds%20%3D%20_N_OUTER%20*%20_N_INNER%0A%0A%20%20%20%20%20%20%20%20_pbar%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20single_task_train%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%20seed%3D_SEED%2C%20p_val%3D_P_VAL%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%20total%3D_n_folds%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20desc%3D%22CV%20folds%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_val_raw%2C%20_test_raw%20in%20_pbar%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Extract%20SMILES%20lists%20used%20by%20Chemprop-based%20models%0A%20%20%20%20%20%20%20%20%20%20%20%20_smi_train%20%3D%20_train_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20_smi_val%20%20%20%3D%20_val_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20_smi_test%20%20%3D%20_test_raw%5B%22smiles%22%5D.to_list()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_train%20%3D%20_train_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_val%20%20%20%3D%20_val_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_true%20%20%3D%20_test_raw%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20train%20%26%20predict%20each%20model%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%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_model_name%20in%20_MODEL_NAMES.keys()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_params%20%3D%20_MODEL_NAMES%5B_model_name%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pbar.set_postfix(%7B%22fold%22%3A%20_fold%2C%20%22o%22%3A%20_outer%2C%20%22i%22%3A%20_inner%2C%20%22model%22%3A%20_model_name%7D%2C%20refresh%3DFalse)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model%20%3D%20ChempropModel(pred_type%3D%22regression%22%2C%20**_params)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model.train(_smi_train%2C%20_y_train%2C%20_smi_val%2C%20_y_val%2C%20target_col%3D_TARGET_COL)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred%20%3D%20_model.predict(_smi_test)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Free%20model%20memory%20before%20accumulating%20results%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_model%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Accumulate%20one%20row%20per%20test%20compound%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%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%22inchikey%22%3A%20%20%20%20%20%20%20_ik%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%22molecule_names%22%3A%20_mn%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%22smiles%22%3A%20%20%20%20%20%20%20%20%20_smi%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%22fold%22%3A%20%20%20%20%20%20%20%20%20%20%20_fold%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%22outer_fold%22%3A%20%20%20%20%20_outer%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%22inner_fold%22%3A%20%20%20%20%20_inner%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%22model%22%3A%20%20%20%20%20%20%20%20%20%20_model_name%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%22y_true%22%3A%20%20%20%20%20%20%20%20%20_yt%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%22y_pred%22%3A%20%20%20%20%20%20%20%20%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20write%20predictions%20(gzip-compressed%20directly)%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%0A%20%20%20%20%20%20%20%20_pred_df%20%3D%20pl.DataFrame(_all_records)%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_PRED_PATH_GZ%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_pred_df.write_csv(_f)%0A%20%20%20%20%20%20%20%20print(f%22%5CnSaved%20%7Blen(_pred_df)%3A%2C%7D%20prediction%20rows%20%E2%86%92%20%7B_PRED_PATH_GZ%7D%22)%0A%0A%20%20%20%20def%20_summarise(df)%3A%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20thresh%3D4.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20%20%20%20%20.sort(%22rho%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_summarise(_pred_df)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20calc_regression_metrics%2C%20mo%2C%20pl%2C%20rm_tukey_hsd)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Control%20check%3A%20verify%20that%20chemprop_base%20in%20the%20size-test%20run%20reproduces%20the%0A%20%20%20%20chemprop%20model%20from%20the%20baseline%20run.%20Both%20were%20trained%20with%20the%20same%0A%20%20%20%20architecture%20and%20the%20same%205%C3%975%20random%20CV%20splits%2C%20so%20they%20should%20be%0A%20%20%20%20statistically%20indistinguishable.%20A%20non-significant%20Tukey%20HSD%20result%0A%20%20%20%20confirms%20the%20two%20runs%20are%20consistent%20before%20we%20compare%20model%20sizes.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_BASELINE%20%20%20%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_SIZE_TEST%20%20%20%3D%20Path(%22..%2Fpredictions%2F3_larger_chemprop_size_test.csv.gz%22)%0A%20%20%20%20_METRIC_LIST%20%3D%20%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D%0A%0A%20%20%20%20_ctrl_df%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20pl.read_csv(_BASELINE)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemprop%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20pl.read_csv(_SIZE_TEST)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemprop_base%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%5D%2C%20how%3D%22diagonal%22)%0A%0A%20%20%20%20_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_ctrl_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_tukey_tables%20%3D%20%5B%5D%0A%20%20%20%20for%20_metric%20in%20_METRIC_LIST%3A%0A%20%20%20%20%20%20%20%20_result_tab%2C%20_df_means%2C%20_%2C%20_%20%3D%20rm_tukey_hsd(_metrics%2C%20_metric%2C%20group_col%3D%22method%22)%0A%20%20%20%20%20%20%20%20_tukey_tables.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22**%7B_metric.upper()%7D**%20%E2%80%94%20mean%20chemprop%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%60%7B_df_means.loc%5B'chemprop'%2C%20_metric%5D%3A.4f%7D%60%2C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22mean%20chemprop_base%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%60%7B_df_means.loc%5B'chemprop_base'%2C%20_metric%5D%3A.4f%7D%60%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.plain_text(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_result_tab%5B%5B%22meandiff%22%2C%20%22lower%22%2C%20%22upper%22%2C%20%22p-adj%22%5D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.to_string()%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%5D)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Control%3A%20%60chemprop%60%20(baseline)%20vs%20%60chemprop_base%60%20(size%20test)%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(%22Expecting%20**no%20significant%20difference**%20(p-adj%20%3E%200.05)%20for%20all%20metrics.%22)%2C%0A%20%20%20%20%20%20%20%20*_tukey_tables%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20mo%2C%20pl)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Fold%20membership%20check%3A%20verify%20that%20the%20exact%20same%20set%20of%20compounds%0A%20%20%20%20appears%20in%20each%20fold%20across%20both%20prediction%20files.%20A%20mismatch%20would%0A%20%20%20%20mean%20the%20CV%20splits%20differ%20between%20the%20two%20runs%20and%20all%20metric%0A%20%20%20%20comparisons%20would%20be%20invalid.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_BASELINE%20%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_SIZE_TEST%20%3D%20Path(%22..%2Fpredictions%2F3_larger_chemprop_size_test.csv.gz%22)%0A%0A%20%20%20%20_base%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(_BASELINE)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemprop%22)%0A%20%20%20%20%20%20%20%20.select(%5B%22fold%22%2C%20%22inchikey%22%5D)%0A%20%20%20%20%20%20%20%20.unique()%0A%20%20%20%20)%0A%20%20%20%20_size%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(_SIZE_TEST)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemprop_base%22)%0A%20%20%20%20%20%20%20%20.select(%5B%22fold%22%2C%20%22inchikey%22%5D)%0A%20%20%20%20%20%20%20%20.unique()%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Anti-join%3A%20compounds%20in%20baseline%20not%20in%20size-test%20for%20the%20same%20fold%0A%20%20%20%20_only_in_base%20%3D%20_base.join(_size%2C%20on%3D%5B%22fold%22%2C%20%22inchikey%22%5D%2C%20how%3D%22anti%22)%0A%20%20%20%20%23%20And%20the%20reverse%0A%20%20%20%20_only_in_size%20%3D%20_size.join(_base%2C%20on%3D%5B%22fold%22%2C%20%22inchikey%22%5D%2C%20how%3D%22anti%22)%0A%0A%20%20%20%20_n_folds_base%20%3D%20_base%5B%22fold%22%5D.n_unique()%0A%20%20%20%20_n_folds_size%20%3D%20_size%5B%22fold%22%5D.n_unique()%0A%20%20%20%20_n_compounds_base%20%3D%20_base.group_by(%22fold%22).len().sort(%22fold%22)%0A%20%20%20%20_n_compounds_size%20%3D%20_size.group_by(%22fold%22).len().sort(%22fold%22)%0A%0A%20%20%20%20if%20len(_only_in_base)%20%3D%3D%200%20and%20len(_only_in_size)%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20_status%20%3D%20mo.md(%22%E2%9C%85%20**All%20folds%20match%20exactly**%20%E2%80%94%20same%20compounds%20in%20every%20fold%20across%20both%20files.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_status%20%3D%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22%E2%9D%8C%20**Mismatch%20detected!**%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22Compounds%20in%20baseline%20but%20not%20size-test%3A%20%7Blen(_only_in_base)%7D%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.plain_text(str(_only_in_base))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22Compounds%20in%20size-test%20but%20not%20baseline%3A%20%7Blen(_only_in_size)%7D%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.plain_text(str(_only_in_size))%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Fold%20membership%20check%3A%20baseline%20vs%20size-test%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(f%22Baseline%20folds%3A%20%7B_n_folds_base%7D%20%7C%20Size-test%20folds%3A%20%7B_n_folds_size%7D%22)%2C%0A%20%20%20%20%20%20%20%20_status%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20make_boxplots_nonparametric%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A)%3A%0A%20%20%20%20_BASELINE%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_METRIC_LIST%20%3D%20%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D%0A%20%20%20%20_PLOT_DIR%20%3D%20Path(%22..%2Fplots%2F3_ml_optimization%22)%0A%20%20%20%20_PLOT_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_chemeleon_ref%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(_BASELINE)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%20%20%20%20_chemprop_size_df%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(Path(%22..%2Fpredictions%2F3_larger_chemprop_size_test.csv.gz%22))%0A%20%20%20%20%20%20%20%20.with_columns(pl.col(%22model%22).str.replace(%22chemprop%22%2C%20%22ch%22))%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%20%20%20%20_cmp_df%20%3D%20pl.concat(%5B_chemprop_size_df%2C%20_chemeleon_ref%5D)%0A%20%20%20%20_metrics%20%3D%20calc_regression_metrics(_cmp_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.as_html(make_boxplots_nonparametric(_metrics%2C%20_METRIC_LIST%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3D_PLOT_DIR%20%2F%20%22chemprop_size_boxplots.png%22))%2C%0A%20%20%20%20%20%20%20%20mo.as_html(make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20%20%20%20%20_metrics%2C%20stats%3D_METRIC_LIST%2C%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20figsize%3D(13%2C%2012)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20show_diff%3DTrue%2C%20sort_axes%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3D_PLOT_DIR%20%2F%20%22chemprop_size_mcs.png%22%2C%0A%20%20%20%20%20%20%20%20))%2C%0A%20%20%20%20%5D)%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%20took%20around%2012%20hours%20in%20a%20M4%20mac%20mini%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%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%20%23%20Compare%20tree%20models%20with%20all%20different%20fingerprints%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20RandomForestModel%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20extract_fp_matrix%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20generate_fingerprint%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20single_task_train%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20output%20path%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%E2%94%80%0A%20%20%20%20_TARGET_COL%20%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_PRED_PATH_GZ%20%3D%20Path(%22..%2Fpredictions%2F3_rf_fingerprint_comparison.csv.gz%22)%0A%20%20%20%20_N_OUTER%20%3D%205%0A%20%20%20%20_N_INNER%20%3D%205%0A%20%20%20%20_SEED%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%3D%200.1%20%20%20%20%20%20%20%20%20%20%23%20fraction%20of%20train%20kept%20as%20validation%20%E2%80%94%20unused%20by%20RF%20but%20ensures%20identical%20splits%20to%20other%20cells%0A%0A%20%20%20%20%23%20Allow%20multiple%20OpenMP%20runtimes%20(PyTorch%20libkmp%20%2B%20sklearn%20libkmp)%20to%20coexist%0A%20%20%20%20%23%20in%20the%20same%20process.%20Without%20this%2C%20the%20two%20runtimes%20collide%20on%20kmp%20barrier%0A%20%20%20%20%23%20synchronisation%20and%20segfault%20(EXC_BAD_ACCESS%20in%20__kmp_fork_barrier).%0A%20%20%20%20%23%20This%20has%20no%20effect%20on%20RF%20parallelism%20%E2%80%94%20trees%20are%20parallelised%20by%20joblib's%0A%20%20%20%20%23%20loky%20process%20pool%2C%20which%20is%20completely%20independent%20of%20OpenMP.%0A%20%20%20%20import%20os%20as%20_os%0A%20%20%20%20_os.environ%5B%22KMP_DUPLICATE_LIB_OK%22%5D%20%3D%20%22TRUE%22%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20fingerprint%20variants%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%0A%20%20%20%20%23%20Each%20entry%3A%20(fingerprint_type%20passed%20to%20generate_fingerprint%2C%20kwargs%20dict%2C%0A%20%20%20%20%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20column%20name%20used%20to%20retrieve%20the%20feature%20matrix)%0A%20%20%20%20%23%20The%20column%20name%20equals%20the%20fingerprint_type%20string%20(set%20by%20generate_fingerprint).%0A%20%20%20%20%23%20For%20CheMeleon%20we%20bypass%20generate_fingerprint%20and%20call%20the%20class%20directly.%0A%20%20%20%20%23%0A%20%20%20%20%23%20Design%20rationale%20per%20fingerprint%20family%3A%0A%20%20%20%20%23%20%20%20ECFP%20%20%E2%80%94%20vary%20radius%20(2%3DECFP4%2C%203%3DECFP6)%20%C3%97%20bit%20length%20(1k%2F2k%2F4k)%0A%20%20%20%20%23%20%20%20%20%20%20%20%20%20%20%20%20%2B%20count%20variant%20captures%20repetitive%20substructures%20better%0A%20%20%20%20%23%20%20%20%20%20%20%20%20%20%20%20%20%2B%20chirality%20and%20FCFP%20pharmacophoric%20invariants%20as%20alternatives%0A%20%20%20%20%23%20%20%20MACCS%20%E2%80%94%20fixed%20167%20structural%20keys%3B%20test%20bits%20vs%20counts%0A%20%20%20%20%23%20%20%20TopologicalTorsion%20%E2%80%94%20vary%20bit%20length%3B%20counts%20encode%20frequency%0A%20%20%20%20%23%20%20%20RDKit%20path%20FP%20%E2%80%94%20vary%20path%20length%20and%20bit%20length%3B%20counts%0A%20%20%20%20%23%20%20%20AtomPair%20%E2%80%94%20vary%20bit%20length%3B%20counts%20capture%20pair%20frequencies%0A%20%20%20%20%23%20%20%20Avalon%20%20%20%20%E2%80%94%20vary%20bit%20length%20(native%20default%20is%20512%2C%20much%20smaller%20than%20others)%0A%20%20%20%20%23%20%20%20Mordred%20%20%20%E2%80%94%202D%20(1613%20descriptors)%20and%203D%20(1826%20descriptors).%20The%203D%20variant%0A%20%20%20%20%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sets%20requires_conformers%3DTrue%20internally%20but%20generates%20its%20own%0A%20%20%20%20%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20conformers%20via%20RDKit%20ETKDGv3%20%E2%80%94%20no%20pre-computed%20conformers%20needed.%0A%20%20%20%20%23%20%20%20MQNs%20%20%20%20%20%20%E2%80%94%2042%20integer%20counts%2C%20no%20size%20parameter%3B%20single%20variant%0A%20%20%20%20%23%20%20%20PubChem%20%20%20%E2%80%94%20881-bit%20CACTVS%20structural%20keys%3B%20test%20bits%20vs%20counts%0A%20%20%20%20%23%20%20%20CheMeleon%20%E2%80%94%202048-d%20learned%20embeddings%3B%20single%20variant%20(no%20tunable%20params)%0A%20%20%20%20%23%0A%20%20%20%20%23%20Each%20family%20always%20includes%20a%20base%20entry%20with%20empty%20kwargs%20(skfp%20defaults).%0A%20%20%20%20%23%20count%2Bchirality%20combinations%20test%20whether%20frequency%20information%20and%20stereo%0A%20%20%20%20%23%20awareness%20interact%20constructively%20across%20different%20bit-vector%20sizes.%0A%0A%20%20%20%20_FP_VARIANTS%3A%20list%5Btuple%5Bstr%2C%20dict%2C%20str%5D%5D%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20ECFP%20%2F%20FCFP%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%0A%20%20%20%20%20%20%20%20%23%20Base%20(skfp%20defaults%3A%20radius%3D2%2C%20fp_size%3D2048%2C%20bits)%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_base%22)%2C%0A%20%20%20%20%20%20%20%20%23%20Radius%20%C3%97%20size%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%201024%7D%2C%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%22ecfp_r2_1k%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%202048%7D%2C%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%22ecfp_r2_2k%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%204096%7D%2C%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%22ecfp_r2_4k%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%201024%7D%2C%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%22ecfp_r3_1k%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%202048%7D%2C%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%22ecfp_r3_2k%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%204096%7D%2C%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%22ecfp_r3_4k%22)%2C%0A%20%20%20%20%20%20%20%20%23%20Count%20only%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%201024%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r2_1k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r2_2k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%204096%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r2_4k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%201024%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r3_1k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r3_2k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%204096%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r3_4k_count%22)%2C%0A%20%20%20%20%20%20%20%20%23%20Chirality%20only%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%201024%2C%20%22include_chirality%22%3A%20True%7D%2C%20%20%20%22ecfp_r2_1k_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%202048%2C%20%22include_chirality%22%3A%20True%7D%2C%20%20%20%22ecfp_r2_2k_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%204096%2C%20%22include_chirality%22%3A%20True%7D%2C%20%20%20%22ecfp_r2_4k_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%202048%2C%20%22include_chirality%22%3A%20True%7D%2C%20%20%20%22ecfp_r3_2k_chiral%22)%2C%0A%20%20%20%20%20%20%20%20%23%20Count%20%2B%20chirality%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%201024%2C%20%22count%22%3A%20True%2C%20%22include_chirality%22%3A%20True%7D%2C%20%22ecfp_r2_1k_count_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%2C%20%22include_chirality%22%3A%20True%7D%2C%20%22ecfp_r2_2k_count_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%204096%2C%20%22count%22%3A%20True%2C%20%22include_chirality%22%3A%20True%7D%2C%20%22ecfp_r2_4k_count_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%2C%20%22include_chirality%22%3A%20True%7D%2C%20%22ecfp_r3_2k_count_chiral%22)%2C%0A%20%20%20%20%20%20%20%20%23%20Pharmacophoric%20invariants%20(FCFP)%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%202048%2C%20%22use_fcfp%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%22fcfp_r2_2k%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%203%2C%20%22fp_size%22%3A%202048%2C%20%22use_fcfp%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%22fcfp_r3_2k%22)%2C%0A%20%20%20%20%20%20%20%20(%22ecfp%22%2C%20%7B%22radius%22%3A%202%2C%20%22fp_size%22%3A%202048%2C%20%22use_fcfp%22%3A%20True%2C%20%22count%22%3A%20True%7D%2C%20%22fcfp_r2_2k_count%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20MACCS%20structural%20keys%20(fixed%20167%20bits)%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%20%20%20%20(%22maccs%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22maccs_base%22)%2C%0A%20%20%20%20%20%20%20%20(%22maccs%22%2C%20%7B%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22maccs_count%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20Topological%20Torsion%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%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_base%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%201024%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_1k%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%202048%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_2k%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%204096%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_4k%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22count%22%3A%20True%7D%2C%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%22torsion_1k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%7D%2C%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%22torsion_2k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%204096%2C%20%22count%22%3A%20True%7D%2C%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%22torsion_4k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22include_chirality%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_1k_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22include_chirality%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_2k_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22count%22%3A%20True%2C%20%22include_chirality%22%3A%20True%7D%2C%20%22torsion_1k_count_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22torsion%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%2C%20%22include_chirality%22%3A%20True%7D%2C%20%22torsion_2k_count_chiral%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20RDKit%20path%20fingerprint%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%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22rdkit_base%22)%2C%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22max_path%22%3A%207%7D%2C%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%22rdkit_1k_p7%22)%2C%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22max_path%22%3A%205%7D%2C%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%22rdkit_2k_p5%22)%2C%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22max_path%22%3A%207%7D%2C%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%22rdkit_2k_p7%22)%2C%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22max_path%22%3A%2010%7D%2C%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%22rdkit_2k_p10%22)%2C%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%22fp_size%22%3A%204096%2C%20%22max_path%22%3A%207%7D%2C%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%22rdkit_4k_p7%22)%2C%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22max_path%22%3A%207%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%22rdkit_1k_p7_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22max_path%22%3A%207%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%22rdkit_2k_p7_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22rdkit%22%2C%20%7B%22fp_size%22%3A%204096%2C%20%22max_path%22%3A%207%2C%20%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%22rdkit_4k_p7_count%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20Atom%20Pair%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%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_base%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%201024%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_1k%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%202048%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_2k%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%204096%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_4k%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22count%22%3A%20True%7D%2C%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%22atompair_1k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%7D%2C%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%22atompair_2k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%204096%2C%20%22count%22%3A%20True%7D%2C%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%22atompair_4k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22include_chirality%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_1k_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22include_chirality%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_2k_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22count%22%3A%20True%2C%20%22include_chirality%22%3A%20True%7D%2C%20%22atompair_1k_count_chiral%22)%2C%0A%20%20%20%20%20%20%20%20(%22atompair%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%2C%20%22include_chirality%22%3A%20True%7D%2C%20%22atompair_2k_count_chiral%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20Avalon%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%E2%94%80%0A%20%20%20%20%20%20%20%20(%22avalon%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22avalon_base%22)%2C%0A%20%20%20%20%20%20%20%20(%22avalon%22%2C%20%7B%22fp_size%22%3A%20512%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22avalon_512%22)%2C%0A%20%20%20%20%20%20%20%20(%22avalon%22%2C%20%7B%22fp_size%22%3A%201024%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22avalon_1k%22)%2C%0A%20%20%20%20%20%20%20%20(%22avalon%22%2C%20%7B%22fp_size%22%3A%202048%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22avalon_2k%22)%2C%0A%20%20%20%20%20%20%20%20(%22avalon%22%2C%20%7B%22fp_size%22%3A%20512%2C%20%20%22count%22%3A%20True%7D%2C%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%22avalon_512_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22avalon%22%2C%20%7B%22fp_size%22%3A%201024%2C%20%22count%22%3A%20True%7D%2C%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%22avalon_1k_count%22)%2C%0A%20%20%20%20%20%20%20%20(%22avalon%22%2C%20%7B%22fp_size%22%3A%202048%2C%20%22count%22%3A%20True%7D%2C%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%22avalon_2k_count%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20Mordred%20descriptors%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%0A%20%20%20%20%20%20%20%20%23%202D%3A%201613%20descriptors%2C%20no%20conformers%20needed%0A%20%20%20%20%20%20%20%20%23%203D%3A%201826%20descriptors%2C%20conformers%20generated%20internally%20by%20skfp%0A%20%20%20%20%20%20%20%20(%22mordred%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mordred_base%22)%2C%0A%20%20%20%20%20%20%20%20(%22mordred%22%2C%20%7B%22use_3D%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mordred_3d%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20MQNs%20(42%20integer%20counts%2C%20no%20tunable%20params)%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%0A%20%20%20%20%20%20%20%20(%22mqn%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%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%22mqn%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20PubChem%20CACTVS%20structural%20keys%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%0A%20%20%20%20%20%20%20%20(%22pubchem%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pubchem_base%22)%2C%0A%20%20%20%20%20%20%20%20(%22pubchem%22%2C%20%7B%22count%22%3A%20True%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pubchem_count%22)%2C%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20CheMeleon%20learned%20embedding%20(2048-d%2C%20single%20variant)%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%0A%20%20%20%20%20%20%20%20(%22chemeleon%22%2C%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22chemeleon%22)%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20if%20_PRED_PATH_GZ.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22Predictions%20already%20exist%20at%20%7B_PRED_PATH_GZ%7D%20%E2%80%94%20skipping%20training.%22)%0A%20%20%20%20%20%20%20%20_pred_df%20%3D%20pl.read_csv(_PRED_PATH_GZ)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_all_records%3A%20list%5Bdict%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20_n_folds%20%3D%20_N_OUTER%20*%20_N_INNER%0A%0A%20%20%20%20%20%20%20%20_VARIANTS_SKL%20%20%20%20%20%20%20%3D%20%5B(t%2C%20k%2C%20n)%20for%20t%2C%20k%2C%20n%20in%20_FP_VARIANTS%20if%20t%20!%3D%20%22chemeleon%22%5D%0A%20%20%20%20%20%20%20%20_VARIANTS_CHEMELEON%20%3D%20%5B(t%2C%20k%2C%20n)%20for%20t%2C%20k%2C%20n%20in%20_FP_VARIANTS%20if%20t%20%3D%3D%20%22chemeleon%22%5D%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20pass%201%3A%20chemeleon%20embeddings%20in%20an%20isolated%20subprocess%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%0A%20%20%20%20%20%20%20%20%23%20PyTorch's%20libkmp%20and%20sklearn's%20libkmp%20corrupt%20each%20other's%20global%0A%20%20%20%20%20%20%20%20%23%20scheduler%20state%20when%20both%20are%20loaded%20in%20the%20same%20process%2C%20even%20with%0A%20%20%20%20%20%20%20%20%23%20KMP_DUPLICATE_LIB_OK%3DTRUE.%20multiprocessing.Process%20can't%20be%20used%20from%0A%20%20%20%20%20%20%20%20%23%20a%20marimo%20cell%20because%20cell-local%20functions%20aren't%20picklable%20by%20spawn.%0A%20%20%20%20%20%20%20%20%23%20Solution%3A%20subprocess.run%20with%20a%20self-contained%20Python%20script%20string.%0A%20%20%20%20%20%20%20%20%23%20SMILES%20are%20written%20to%20a%20temp%20JSON%20file%3B%20embeddings%20are%20written%20back%20as%0A%20%20%20%20%20%20%20%20%23%20.npy%20files%20by%20the%20child%2C%20read%20by%20the%20parent%2C%20then%20temp%20files%20deleted.%0A%20%20%20%20%20%20%20%20import%20json%20as%20_json%0A%20%20%20%20%20%20%20%20import%20subprocess%20as%20_subprocess%0A%20%20%20%20%20%20%20%20import%20sys%20as%20_sys%0A%20%20%20%20%20%20%20%20import%20tempfile%20as%20_tempfile%0A%20%20%20%20%20%20%20%20import%20numpy%20as%20_np_sub%0A%0A%20%20%20%20%20%20%20%20%23%20Self-contained%20script%20written%20to%20a%20temp%20file%20and%20executed%20as%20a%20child%0A%20%20%20%20%20%20%20%20%23%20process%20%E2%80%94%20no%20imports%20from%20the%20parent's%20namespace%2C%20kmp%20never%20shared.%0A%20%20%20%20%20%20%20%20%23%20Written%20to%20a%20.py%20file%20(not%20-c)%20so%20indentation%20in%20the%20source%20is%20exact.%0A%20%20%20%20%20%20%20%20_SCRIPT_LINES%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22import%20os%2C%20json%2C%20sys%2C%20numpy%20as%20np%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22os.environ%5B'KMP_DUPLICATE_LIB_OK'%5D%20%3D%20'TRUE'%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20pathlib%20import%20Path%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22import%20torch%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20chemprop%20import%20featurizers%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20chemprop%20import%20nn%20as%20chemnn%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20chemprop.models%20import%20MPNN%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20chemprop.nn%20import%20RegressionFFN%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20chemprop.data%20import%20BatchMolGraph%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20rdkit.Chem%20import%20MolFromSmiles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22smiles_file%2C%20out_train%2C%20out_test%20%3D%20sys.argv%5B1%5D%2C%20sys.argv%5B2%5D%2C%20sys.argv%5B3%5D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22with%20open(smiles_file)%20as%20f%3A%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%20%20%20%20data%20%3D%20json.load(f)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mp_path%20%3D%20Path.home()%20%2F%20'.chemprop'%20%2F%20'chemeleon_mp.pt'%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ckpt%20%3D%20torch.load(mp_path%2C%20weights_only%3DTrue)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mp%20%3D%20chemnn.BondMessagePassing(**ckpt%5B'hyper_parameters'%5D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mp.load_state_dict(ckpt%5B'state_dict'%5D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22model%20%3D%20MPNN(mp%2C%20chemnn.MeanAggregation()%2C%20RegressionFFN(input_dim%3Dmp.output_dim))%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22model.eval()%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22feat%20%3D%20featurizers.SimpleMoleculeMolGraphFeaturizer()%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22def%20embed(smiles)%3A%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%20%20%20%20bmg%20%3D%20BatchMolGraph(%5Bfeat(MolFromSmiles(s))%20for%20s%20in%20smiles%5D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%20%20%20%20with%20torch.no_grad()%3A%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%20%20%20%20%20%20%20%20return%20model.fingerprint(bmg).numpy(force%3DTrue)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22np.save(out_train%2C%20embed(data%5B'train'%5D))%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22np.save(out_test%2C%20%20embed(data%5B'test'%5D))%22%2C%0A%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20%20%20%20%20_SCRIPT_PATH%20%3D%20Path(_tempfile.gettempdir())%20%2F%20%22chemeleon_embed.py%22%0A%20%20%20%20%20%20%20%20_SCRIPT_PATH.write_text(%22%5Cn%22.join(_SCRIPT_LINES))%0A%0A%20%20%20%20%20%20%20%20def%20_chemeleon_embed_subprocess(smiles_train%2C%20smiles_test)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22Run%20CheMeleon%20inference%20in%20an%20isolated%20subprocess.%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(_tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20smi_file%20%20%20%3D%20tmp%20%2F%20%22ch_smiles.json%22%0A%20%20%20%20%20%20%20%20%20%20%20%20train_file%20%3D%20tmp%20%2F%20%22ch_train%22%0A%20%20%20%20%20%20%20%20%20%20%20%20test_file%20%20%3D%20tmp%20%2F%20%22ch_test%22%0A%20%20%20%20%20%20%20%20%20%20%20%20smi_file.write_text(_json.dumps(%7B%22train%22%3A%20smiles_train%2C%20%22test%22%3A%20smiles_test%7D))%0A%20%20%20%20%20%20%20%20%20%20%20%20result%20%3D%20_subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B_sys.executable%2C%20str(_SCRIPT_PATH)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20str(smi_file)%2C%20str(train_file)%2C%20str(test_file)%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%20text%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20result.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(f%22CheMeleon%20subprocess%20failed%3A%5Cn%7Bresult.stderr%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%20%3D%20_np_sub.load(str(train_file)%20%2B%20%22.npy%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20X_test%20%20%3D%20_np_sub.load(str(test_file)%20%20%2B%20%22.npy%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20smi_file.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20Path(str(train_file)%20%2B%20%22.npy%22).unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20Path(str(test_file)%20%20%2B%20%22.npy%22).unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20X_train%2C%20X_test%0A%0A%20%20%20%20%20%20%20%20_pbar1%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20single_task_train%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%20seed%3D_SEED%2C%20p_val%3D_P_VAL%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%20total%3D_n_folds%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20desc%3D%22CV%20folds%20(CheMeleon)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_%2C%20_test_raw%20in%20_pbar1%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_train%20%3D%20_train_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_true%20%20%3D%20_test_raw%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_fp_type%2C%20_fp_kwargs%2C%20_fp_col%20in%20_VARIANTS_CHEMELEON%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pbar1.set_postfix(%7B%22fold%22%3A%20_fold%2C%20%22fp%22%3A%20_fp_col%7D%2C%20refresh%3DFalse)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_train%2C%20_X_test%20%3D%20_chemeleon_embed_subprocess(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model%20%3D%20RandomForestModel(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3D%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3D500%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3D_SEED%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model.train(_X_train%2C%20_y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred%20%3D%20_model.predict(_X_test)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_model%2C%20_X_train%2C%20_X_test%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%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%22inchikey%22%3A%20%20%20%20%20%20%20_ik%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%22molecule_names%22%3A%20_mn%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%22smiles%22%3A%20%20%20%20%20%20%20%20%20_smi%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%22fold%22%3A%20%20%20%20%20%20%20%20%20%20%20_fold%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%22outer_fold%22%3A%20%20%20%20%20_outer%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%22inner_fold%22%3A%20%20%20%20%20_inner%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%22model%22%3A%20%20%20%20%20%20%20%20%20%20_fp_col%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%22y_true%22%3A%20%20%20%20%20%20%20%20%20_yt%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%22y_pred%22%3A%20%20%20%20%20%20%20%20%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20pass%202%3A%20all%20sklearn%20fingerprints%20(RF%20with%20n_jobs%3D-1)%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%0A%20%20%20%20%20%20%20%20_pbar2%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20single_task_train%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%20seed%3D_SEED%2C%20p_val%3D_P_VAL%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%20total%3D_n_folds%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20desc%3D%22CV%20folds%20(sklearn%20FPs)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20from%20concurrent.futures%20import%20ThreadPoolExecutor%20as%20_TPE%0A%0A%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_val_raw%2C%20_test_raw%20in%20_pbar2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_train%20%3D%20_train_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_true%20%20%3D%20_test_raw%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_fp_type%2C%20_fp_kwargs%2C%20_fp_col%20in%20_VARIANTS_SKL%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pbar2.set_postfix(%7B%22fold%22%3A%20_fold%2C%20%22fp%22%3A%20_fp_col%7D%2C%20refresh%3DFalse)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Compute%20train%20and%20test%20fingerprints%20concurrently%20(2%20threads).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20skfp's%20RDKit%20C%2B%2B%20code%20releases%20the%20GIL%20so%20this%20gives%20real%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20parallelism%20without%20holding%20more%20than%20one%20variant%20in%20memory.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20with%20_TPE(max_workers%3D2)%20as%20_pool%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ft%20%3D%20_pool.submit(generate_fingerprint%2C%20_train_raw%2C%20_fp_type%2C%20**_fp_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_fs%20%3D%20_pool.submit(generate_fingerprint%2C%20_test_raw%2C%20%20_fp_type%2C%20**_fp_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_fp%2C%20_test_fp%20%3D%20_ft.result()%2C%20_fs.result()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_train%20%3D%20extract_fp_matrix(_train_fp%2C%20_fp_type)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_test%20%20%3D%20extract_fp_matrix(_test_fp%2C%20%20_fp_type)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_train_fp%2C%20_test_fp%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model%20%3D%20RandomForestModel(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3D%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3D500%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3D_SEED%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model.train(_X_train%2C%20_y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred%20%3D%20_model.predict(_X_test)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_model%2C%20_X_train%2C%20_X_test%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%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%22inchikey%22%3A%20%20%20%20%20%20%20_ik%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%22molecule_names%22%3A%20_mn%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%22smiles%22%3A%20%20%20%20%20%20%20%20%20_smi%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%22fold%22%3A%20%20%20%20%20%20%20%20%20%20%20_fold%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%22outer_fold%22%3A%20%20%20%20%20_outer%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%22inner_fold%22%3A%20%20%20%20%20_inner%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%22model%22%3A%20%20%20%20%20%20%20%20%20%20_fp_col%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%22y_true%22%3A%20%20%20%20%20%20%20%20%20_yt%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%22y_pred%22%3A%20%20%20%20%20%20%20%20%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20write%20compressed%20predictions%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%20%20%20%20_pred_df%20%3D%20pl.DataFrame(_all_records)%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_PRED_PATH_GZ%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_pred_df.write_csv(_f)%0A%20%20%20%20%20%20%20%20print(f%22%5CnSaved%20%7Blen(_pred_df)%3A%2C%7D%20prediction%20rows%20%E2%86%92%20%7B_PRED_PATH_GZ%7D%22)%0A%0A%20%20%20%20def%20_summarise(df)%3A%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20thresh%3D4.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20%20%20%20%20.sort(%22rho%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_summarise(_pred_df)%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%20takes%20very%20long%20(~8hours)%20probably%20could%20have%20optimized%20and%20paralelized%20the%20code%20better%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20calc_regression_metrics%2C%20mo%2C%20pl%2C%20rm_tukey_hsd)%3A%0A%20%20%20%20_BASELINE%20%20%20%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_RF_FP_PATH%20%20%3D%20Path(%22..%2Fpredictions%2F3_rf_fingerprint_comparison.csv.gz%22)%0A%20%20%20%20_METRIC_LIST%20%3D%20%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D%0A%20%20%20%20_ALPHA%20%20%20%20%20%20%20%3D%200.05%20%20%23%20Tukey%20HSD%20significance%20threshold%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20load%20and%20reshape%20predictions%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%0A%20%20%20%20chemeleon_ref_df%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(_BASELINE)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%20%20%20%20rf_fp_df%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(_RF_FP_PATH)%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(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.then(pl.lit(%22chemeleon_fp%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.otherwise(pl.col(%22model%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.alias(%22model%22)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20fingerprint%20family%20membership%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%0A%20%20%20%20%23%20Multi-variant%20families%20get%20an%20intra-family%20boxplot%20%2B%20best-model%20selection.%0A%20%20%20%20%23%20Small%20families%20(%E2%89%A42%20variants)%20skip%20the%20boxplot%20and%20select%20best%20by%20mean%20rho.%0A%20%20%20%20_MULTI_FAMILIES%3A%20dict%5Bstr%2C%20list%5Bstr%5D%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22ECFP%2FFCFP%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_base%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r2_1k%22%2C%20%22ecfp_r2_2k%22%2C%20%22ecfp_r2_4k%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r3_1k%22%2C%20%22ecfp_r3_2k%22%2C%20%22ecfp_r3_4k%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r2_1k_count%22%2C%20%22ecfp_r2_2k_count%22%2C%20%22ecfp_r2_4k_count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r3_1k_count%22%2C%20%22ecfp_r3_2k_count%22%2C%20%22ecfp_r3_4k_count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r2_1k_chiral%22%2C%20%22ecfp_r2_2k_chiral%22%2C%20%22ecfp_r2_4k_chiral%22%2C%20%22ecfp_r3_2k_chiral%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r2_1k_count_chiral%22%2C%20%22ecfp_r2_2k_count_chiral%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22ecfp_r2_4k_count_chiral%22%2C%20%22ecfp_r3_2k_count_chiral%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22fcfp_r2_2k%22%2C%20%22fcfp_r3_2k%22%2C%20%22fcfp_r2_2k_count%22%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%22Torsion%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_base%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_1k%22%2C%20%22torsion_2k%22%2C%20%22torsion_4k%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_1k_count%22%2C%20%22torsion_2k_count%22%2C%20%22torsion_4k_count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_1k_chiral%22%2C%20%22torsion_2k_chiral%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22torsion_1k_count_chiral%22%2C%20%22torsion_2k_count_chiral%22%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%22RDKit%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22rdkit_base%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22rdkit_1k_p7%22%2C%20%22rdkit_2k_p5%22%2C%20%22rdkit_2k_p7%22%2C%20%22rdkit_2k_p10%22%2C%20%22rdkit_4k_p7%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22rdkit_1k_p7_count%22%2C%20%22rdkit_2k_p7_count%22%2C%20%22rdkit_4k_p7_count%22%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%22AtomPair%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_base%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_1k%22%2C%20%22atompair_2k%22%2C%20%22atompair_4k%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_1k_count%22%2C%20%22atompair_2k_count%22%2C%20%22atompair_4k_count%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_1k_chiral%22%2C%20%22atompair_2k_chiral%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22atompair_1k_count_chiral%22%2C%20%22atompair_2k_count_chiral%22%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%22Avalon%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22avalon_base%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22avalon_512%22%2C%20%22avalon_1k%22%2C%20%22avalon_2k%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22avalon_512_count%22%2C%20%22avalon_1k_count%22%2C%20%22avalon_2k_count%22%2C%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%7D%0A%20%20%20%20_SMALL_FAMILIES%3A%20dict%5Bstr%2C%20list%5Bstr%5D%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22MACCS%22%3A%20%20%20%5B%22maccs_base%22%2C%20%22maccs_count%22%5D%2C%0A%20%20%20%20%20%20%20%20%22Mordred%22%3A%20%5B%22mordred_base%22%2C%20%22mordred_3d%22%5D%2C%0A%20%20%20%20%20%20%20%20%22MQN%22%3A%20%20%20%20%20%5B%22mqn%22%5D%2C%0A%20%20%20%20%20%20%20%20%22PubChem%22%3A%20%5B%22pubchem_base%22%2C%20%22pubchem_count%22%5D%2C%0A%20%20%20%20%20%20%20%20%22Chemeleon_fp%22%3A%20%5B%22chemeleon_fp%22%5D%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20_best_by_mae(metrics_df%3A%20pl.DataFrame)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20the%20method%20with%20the%20lowest%20mean%20MAE%20across%20folds.%22%22%22%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20metrics_df.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.agg(pl.col(%22mae%22).mean())%0A%20%20%20%20%20%20%20%20%20%20%20%20.sort(%22mae%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.row(0)%5B0%5D%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20def%20_non_sig_diff_from_best(metrics_df%3A%20pl.DataFrame%2C%20best%3A%20str)%20-%3E%20set%3A%0A%20%20%20%20%20%20%20%20%22%22%22Return%20methods%20not%20significantly%20different%20from%20best%20on%20MAE%20(Tukey%20HSD).%0A%0A%20%20%20%20%20%20%20%20Returns%20an%20empty%20set%20if%20the%20test%20cannot%20be%20computed%20(e.g.%20all%20models%0A%20%20%20%20%20%20%20%20produce%20identical%20predictions%2C%20causing%20zero%20MSresidual%20in%20the%20RM-ANOVA).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20result_tab%2C%20_%2C%20_%2C%20_%20%3D%20rm_tukey_hsd(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20metrics_df%2C%20%22mae%22%2C%20group_col%3D%22method%22%2C%20alpha%3D_ALPHA%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20set()%0A%20%20%20%20%20%20%20%20mask%20%3D%20(result_tab%5B%22group1%22%5D%20%3D%3D%20best)%20%7C%20(result_tab%5B%22group2%22%5D%20%3D%3D%20best)%0A%20%20%20%20%20%20%20%20not_sig%20%3D%20result_tab%5Bmask%20%26%20(result_tab%5B%22p-adj%22%5D%20%3E%20_ALPHA)%5D%0A%20%20%20%20%20%20%20%20others%20%3D%20set()%0A%20%20%20%20%20%20%20%20for%20_%2C%20row%20in%20not_sig.iterrows()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20others.add(row%5B%22group2%22%5D%20if%20row%5B%22group1%22%5D%20%3D%3D%20best%20else%20row%5B%22group1%22%5D)%0A%20%20%20%20%20%20%20%20return%20others%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20stage%201%3A%20intra-family%20comparison%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%20best_models_rf_fp%3A%20dict%5Bstr%2C%20str%5D%20%3D%20%7B%7D%0A%20%20%20%20_family_plots%3A%20list%20%3D%20%5B%5D%0A%0A%20%20%20%20for%20_family%2C%20_variants%20in%20_MULTI_FAMILIES.items()%3A%0A%20%20%20%20%20%20%20%20_family_df%20%3D%20rf_fp_df.filter(pl.col(%22method%22).is_in(_variants))%0A%20%20%20%20%20%20%20%20_family_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20%20%20%20%20_family_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_best%20%3D%20_best_by_mae(_family_metrics)%0A%20%20%20%20%20%20%20%20best_models_rf_fp%5B_family%5D%20%3D%20_best%0A%0A%20%20%20%20%20%20%20%20%23%20Keep%20only%20the%20best%20%2B%20variants%20not%20significantly%20different%20from%20it%20on%20MAE%0A%20%20%20%20%20%20%20%20_show%20%3D%20%7B_best%7D%20%7C%20_non_sig_diff_from_best(_family_metrics%2C%20_best)%0A%20%20%20%20%20%20%20%20_n_total%20%3D%20_family_metrics%5B%22method%22%5D.n_unique()%0A%20%20%20%20%20%20%20%20_n_show%20%20%3D%20len(_show)%0A%20%20%20%20%20%20%20%20_display_metrics%20%3D%20_family_metrics.filter(pl.col(%22method%22).is_in(_show))%0A%0A%20%20%20%20%20%20%20%20_equiv%20%3D%20sorted(_show%20-%20%7B_best%7D)%0A%20%20%20%20%20%20%20%20_equiv_str%20%3D%20%22%2C%20%22.join(f%22%60%7Bm%7D%60%22%20for%20m%20in%20_equiv)%20if%20_equiv%20else%20%22none%22%0A%20%20%20%20%20%20%20%20_plots%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%23%23%23%20%7B_family%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22**Best%20(lowest%20MAE)%3A**%20%60%7B_best%7D%60%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22**Not%20significantly%20different%20(p%20%3E%20%7B_ALPHA%7D)%3A**%20%7B_equiv_str%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22*%7B_n_total%20-%20_n_show%7D%20of%20%7B_n_total%7D%20variants%20excluded%20as%20significantly%20worse.*%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20_family_plots.append(mo.vstack(_plots))%0A%0A%20%20%20%20%23%20For%20small%20families%20pick%20best%20by%20MAE%20directly%20(too%20few%20variants%20for%20Tukey)%0A%20%20%20%20for%20_family%2C%20_variants%20in%20_SMALL_FAMILIES.items()%3A%0A%20%20%20%20%20%20%20%20_fam_df%20%3D%20rf_fp_df.filter(pl.col(%22method%22).is_in(_variants))%0A%20%20%20%20%20%20%20%20_fam_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20%20%20%20%20_fam_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20best_models_rf_fp%5B_family%5D%20%3D%20_best_by_mae(_fam_metrics)%0A%0A%20%20%20%20mo.vstack(_family_plots)%0A%20%20%20%20return%20best_models_rf_fp%2C%20chemeleon_ref_df%2C%20rf_fp_df%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20best_models_rf_fp%3A%20dict%5Bstr%2C%20str%5D%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20chemeleon_ref_df%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20rf_fp_df%2C%0A)%3A%0A%20%20%20%20_METRIC_LIST%20%3D%20%5B%22mae%22%5D%0A%20%20%20%20_PLOT_DIR%20%3D%20Path(%22..%2Fplots%2F3_ml_optimization%22)%0A%20%20%20%20_PLOT_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20stage%202%3A%20cross-family%20comparison%20(best%20per%20family%20%2B%20chemeleon)%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%0A%20%20%20%20_best_names%20%3D%20list(best_models_rf_fp.values())%0A%20%20%20%20_cross_df%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20rf_fp_df.filter(pl.col(%22method%22).is_in(_best_names))%2C%0A%20%20%20%20%20%20%20%20chemeleon_ref_df%2C%0A%20%20%20%20%5D)%0A%20%20%20%20_cross_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_cross_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Cross-family%20comparison%20%E2%80%94%20best%20per%20FP%20type%20%2B%20CheMeleon%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(%22Best%20variants%20selected%3A%20%22%20%2B%20%22%2C%20%22.join(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22**%7Bfam%7D**%20%E2%86%92%20%60%7Bmod%7D%60%22%20for%20fam%2C%20mod%20in%20best_models_rf_fp.items()%0A%20%20%20%20%20%20%20%20))%2C%0A%20%20%20%20%20%20%20%20mo.as_html(make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20%20%20%20%20_cross_metrics%2C%20stats%3D_METRIC_LIST%2C%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20figsize%3D(18%2C%2016)%2C%20show_diff%3DTrue%2C%20sort_axes%3DTrue%2C%20effect_dict%3D%7B%22mae%22%3A%200.1%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3D_PLOT_DIR%20%2F%20%22rf_fp_cross_family_mcs.png%22%2C%0A%20%20%20%20%20%20%20%20))%2C%0A%20%20%20%20%5D)%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%20%23%20Compare%20scoring%20ensembles%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20calc_regression_metrics%2C%20gzip%2C%20np%2C%20pl)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20paths%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%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_SRC_PATH%20%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_OUT_PATH%20%20%3D%20Path(%22..%2Fpredictions%2F3_prediction_ensemble_test.csv.gz%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20the%20four%20models%20we%20want%20to%20ensemble%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%0A%20%20%20%20_ENSEMBLE_MODELS%20%3D%20%5B%22rf%22%2C%20%22gbm%22%2C%20%22chemprop%22%2C%20%22chemeleon%22%5D%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20enumerate%20valid%20weight%20combinations%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%0A%20%20%20%20%23%20Each%20weight%20%E2%88%88%20%7B0%2C%201%2C%202%7D.%20Two%20filters%20are%20applied%3A%0A%20%20%20%20%23%20%20%20%E2%80%A2%20not%20all%20weights%20%3D%3D%202%20%20%20(degenerate%3A%20equal%20to%20plain%20mean%2C%20name%20collision)%0A%20%20%20%20%23%20%20%20%E2%80%A2%20fewer%20than%203%20weights%20%3D%3D%200%20%20(requires%20at%20least%202%20active%20models)%0A%20%20%20%20%23%20This%20yields%2071%20distinct%20ensembles%20out%20of%203%5E4%20%3D%2081%20raw%20combinations.%0A%20%20%20%20%23%20The%20chemeleon-only%20control%20(0%2C0%2C0%2C1)%20is%20appended%20explicitly%20%E2%80%94%20it%20is%0A%20%20%20%20%23%20excluded%20by%20the%20filters%20above%20but%20useful%20as%20a%20single-model%20reference.%0A%20%20%20%20_all_weights%3A%20list%5Btuple%5Bint%2C%20...%5D%5D%20%3D%20%5B%0A%20%20%20%20%20%20%20%20w%0A%20%20%20%20%20%20%20%20for%20w%20in%20np.ndindex(3%2C%203%2C%203%2C%203)%20%20%20%20%20%20%20%23%20iterates%20(0..2)%5E4%0A%20%20%20%20%20%20%20%20if%20not%20all(x%20%3D%3D%202%20for%20x%20in%20w)%0A%20%20%20%20%20%20%20%20and%20sum(x%20%3D%3D%200%20for%20x%20in%20w)%20%3C%203%0A%20%20%20%20%5D%20%2B%20%5B(0%2C%200%2C%200%2C%201)%5D%20%23added%20chemeleon%20only%20as%20control%0A%0A%20%20%20%20def%20_weight_label(weights%3A%20tuple%5Bint%2C%20...%5D)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22Encode%20weights%20as%20a%20compact%20model%20string%2C%20e.g.%20'ens_rf1_gbm2_cp0_ch1'.%22%22%22%0A%20%20%20%20%20%20%20%20tags%20%3D%20zip(%5B%22rf%22%2C%20%22gbm%22%2C%20%22cp%22%2C%20%22ch%22%5D%2C%20weights)%0A%20%20%20%20%20%20%20%20return%20%22ens_%22%20%2B%20%22_%22.join(f%22%7Btag%7D%7Bw%7D%22%20for%20tag%2C%20w%20in%20tags)%0A%0A%20%20%20%20if%20_OUT_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22Ensemble%20predictions%20already%20exist%20at%20%7B_OUT_PATH%7D%20%E2%80%94%20skipping.%22)%0A%20%20%20%20%20%20%20%20_ens_df%20%3D%20pl.read_csv(_OUT_PATH)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20load%20source%20predictions%20and%20pivot%20to%20wide%20format%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%0A%20%20%20%20%20%20%20%20%23%20Keep%20only%20the%20four%20target%20models%3B%20the%20id%20columns%20identify%20each%0A%20%20%20%20%20%20%20%20%23%20(compound%2C%20fold)%20pair%20uniquely.%0A%20%20%20%20%20%20%20%20_id_cols%20%3D%20%5B%22inchikey%22%2C%20%22molecule_names%22%2C%20%22smiles%22%2C%20%22fold%22%2C%20%22outer_fold%22%2C%20%22inner_fold%22%5D%0A%0A%20%20%20%20%20%20%20%20_src%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.read_csv(_SRC_PATH)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22).is_in(_ENSEMBLE_MODELS))%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Wide%20table%3A%20one%20row%20per%20(compound%20%C3%97%20fold)%2C%20one%20column%20per%20model's%20y_pred%0A%20%20%20%20%20%20%20%20_wide%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20_src%0A%20%20%20%20%20%20%20%20%20%20%20%20.pivot(on%3D%22model%22%2C%20index%3D_id_cols%20%2B%20%5B%22y_true%22%5D%2C%20values%3D%22y_pred%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Rename%20model%20columns%20to%20avoid%20collisions%20when%20building%20weighted%20sums%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7Bm%3A%20f%22_p_%7Bm%7D%22%20for%20m%20in%20_ENSEMBLE_MODELS%7D)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20build%20all%20ensemble%20predictions%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%0A%20%20%20%20%20%20%20%20_records%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20_w%20in%20_all_weights%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_w_rf%2C%20_w_gbm%2C%20_w_cp%2C%20_w_ch%20%3D%20_w%0A%20%20%20%20%20%20%20%20%20%20%20%20_total%20%3D%20float(_w_rf%20%2B%20_w_gbm%20%2B%20_w_cp%20%2B%20_w_ch)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Weighted%20mean%20as%20a%20Polars%20expression%20%E2%80%94%20avoids%20Python%20loops%20over%20rows%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_pred_expr%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22_p_rf%22)%20%20%20%20%20%20%20*%20_w_rf%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2B%20pl.col(%22_p_gbm%22)%20%20%20%20*%20_w_gbm%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2B%20pl.col(%22_p_chemprop%22)%20*%20_w_cp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2B%20pl.col(%22_p_chemeleon%22)%20*%20_w_ch%0A%20%20%20%20%20%20%20%20%20%20%20%20)%20%2F%20_total%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_records.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_wide.select(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*_id_cols%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22y_true%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred_expr.alias(%22y_pred%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(_weight_label(_w)).alias(%22model%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20_ens_df%20%3D%20pl.concat(_records)%0A%0A%20%20%20%20%20%20%20%20_OUT_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_OUT_PATH%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ens_df.write_csv(_f)%0A%20%20%20%20%20%20%20%20print(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22Saved%20%7Blen(_ens_df)%3A%2C%7D%20rows%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22(%7Blen(_all_weights)%7D%20ensembles%20%C3%97%20%7Blen(_wide)%3A%2C%7D%20compound-folds)%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22%E2%86%92%20%7B_OUT_PATH%7D%22%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20def%20_summarise(df)%3A%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20thresh%3D4.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20%20%20%20%20.sort(%22rho%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_summarise(_ens_df)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20calc_regression_metrics%2C%20mo%2C%20pl%2C%20rm_tukey_hsd)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Control%20check%3A%20verify%20that%20the%20chemeleon-only%20ensemble%20weight%20(0%2C0%2C0%2C1)%2C%0A%20%20%20%20labelled%20ens_rf0_gbm0_cp0_ch1%2C%20is%20statistically%20identical%20to%20the%20chemeleon%0A%20%20%20%20model%20from%20the%20baseline%20run.%20Both%20are%20pure%20chemeleon%20predictions%20on%20the%20same%0A%20%20%20%20CV%20folds%2C%20so%20any%20difference%20would%20indicate%20a%20data-pipeline%20bug.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_BASELINE%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_ENS_PATH%20%3D%20Path(%22..%2Fpredictions%2F3_prediction_ensemble_test.csv.gz%22)%0A%20%20%20%20_METRIC_LIST%20%3D%20%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D%0A%0A%20%20%20%20_ctrl_df%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20pl.read_csv(_BASELINE)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20pl.read_csv(_ENS_PATH)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22ens_rf0_gbm0_cp0_ch1%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%5D%2C%20how%3D%22diagonal%22)%0A%0A%20%20%20%20_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_ctrl_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_tukey_tables%20%3D%20%5B%5D%0A%20%20%20%20for%20_metric%20in%20_METRIC_LIST%3A%0A%20%20%20%20%20%20%20%20_result_tab%2C%20_df_means%2C%20_%2C%20_%20%3D%20rm_tukey_hsd(_metrics%2C%20_metric%2C%20group_col%3D%22method%22)%0A%20%20%20%20%20%20%20%20_tukey_tables.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22**%7B_metric.upper()%7D**%20%E2%80%94%20mean%20chemeleon%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%60%7B_df_means.loc%5B'chemeleon'%2C%20_metric%5D%3A.4f%7D%60%2C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22mean%20ens_rf0_gbm0_cp0_ch1%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%60%7B_df_means.loc%5B'ens_rf0_gbm0_cp0_ch1'%2C%20_metric%5D%3A.4f%7D%60%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.plain_text(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_result_tab%5B%5B%22meandiff%22%2C%20%22lower%22%2C%20%22upper%22%2C%20%22p-adj%22%5D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.to_string()%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%5D)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Control%3A%20%60chemeleon%60%20(baseline)%20vs%20%60ens_rf0_gbm0_cp0_ch1%60%20(ensemble%20test)%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(%22Expecting%20**no%20significant%20difference**%20(p-adj%20%3E%200.05)%20for%20all%20metrics.%22)%2C%0A%20%20%20%20%20%20%20%20*_tukey_tables%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20calc_regression_metrics%2C%20mo%2C%20pl)%3A%0A%20%20%20%20_BASELINE%20%20%20%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_ENS_PATH%20%20%20%20%3D%20Path(%22..%2Fpredictions%2F3_prediction_ensemble_test.csv.gz%22)%0A%20%20%20%20_METRIC_LIST%20%3D%20%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D%0A%20%20%20%20_TOP_N%20%20%20%20%20%20%20%3D%204%20%20%20%23%20models%20to%20show%20in%20plots%20alongside%20chemeleon%0A%0A%20%20%20%20_chemeleon_ref%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(_BASELINE)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%20%20%20%20_ens_df%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(_ENS_PATH)%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Compute%20metrics%20for%20all%20models%20%2B%20chemeleon%20via%20calc_regression_metrics%0A%20%20%20%20_all_df%20%3D%20pl.concat(%5B_ens_df%2C%20_chemeleon_ref%5D%2C%20how%3D%22diagonal%22)%0A%20%20%20%20_all_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_all_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Mean%20MAE%20per%20model%20from%20the%20Tukey-consistent%20per-fold%20metrics%0A%20%20%20%20_mean_mae%20%3D%20(%0A%20%20%20%20%20%20%20%20_all_metrics%0A%20%20%20%20%20%20%20%20.group_by(%22method%22).agg(pl.col(%22mae%22).mean())%0A%20%20%20%20%20%20%20%20.sort(%22mae%22)%0A%20%20%20%20)%0A%20%20%20%20_chemeleon_mae%20%3D%20_mean_mae.filter(pl.col(%22method%22)%20%3D%3D%20%22chemeleon%22)%5B%22mae%22%5D%5B0%5D%0A%0A%20%20%20%20%23%20All%20ensemble%20models%20that%20beat%20chemeleon%20on%20mean%20MAE%2C%20ranked%20best%20first%0A%20%20%20%20_better_df%20%3D%20_mean_mae.filter(%0A%20%20%20%20%20%20%20%20(pl.col(%22method%22)%20!%3D%20%22chemeleon%22)%20%26%20(pl.col(%22mae%22)%20%3C%20_chemeleon_mae)%0A%20%20%20%20)%0A%20%20%20%20_n_better%20%3D%20len(_better_df)%0A%20%20%20%20_better_list%20%3D%20_better_df%5B%22method%22%5D.to_list()%20%20%20%23%20already%20sorted%20by%20mae%20asc%0A%0A%20%20%20%20%23%20Select%20top%20N%20for%20plots%0A%20%20%20%20_plot_models%20%3D%20_better_list%5B%3A_TOP_N%5D%0A%20%20%20%20_plot_df%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20_ens_df.filter(pl.col(%22method%22).is_in(_plot_models))%2C%0A%20%20%20%20%20%20%20%20_chemeleon_ref%2C%0A%20%20%20%20%5D%2C%20how%3D%22diagonal%22)%0A%20%20%20%20_plot_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_plot_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Build%20the%20text%20summary%20of%20all%20improving%20models%0A%20%20%20%20_better_rows%20%3D%20%22%5Cn%22.join(%0A%20%20%20%20%20%20%20%20f%22-%20%60%7Brow%5B0%5D%7D%60%20%E2%80%94%20MAE%20%7Brow%5B1%5D%3A.4f%7D%22%0A%20%20%20%20%20%20%20%20for%20row%20in%20_better_df.iter_rows()%0A%20%20%20%20)%0A%20%20%20%20_summary%20%3D%20(%0A%20%20%20%20%20%20%20%20f%22**CheMeleon%20baseline%20MAE%3A**%20%7B_chemeleon_mae%3A.4f%7D%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20f%22**%7B_n_better%7D%20ensemble(s)%20with%20lower%20MAE**%20(ranked%20best%20first)%3A%5Cn%5Cn%22%0A%20%20%20%20%20%20%20%20%2B%20(_better_rows%20if%20_n_better%20else%20%22*none*%22)%0A%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Ensemble%20vs%20CheMeleon%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(_summary)%2C%0A%0A%20%20%20%20%5D)%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%20%23%20Multitask%20test%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20shutil%2C%0A%20%20%20%20single_task_train%2C%0A%20%20%20%20subprocess%2C%0A%20%20%20%20sys%2C%0A%20%20%20%20tempfile%2C%0A%20%20%20%20torch%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20multitask%20chemprop%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%0A%20%20%20%20%23%20We%20re-use%20_get_device%20and%20_run_chemprop_cli%20patterns%20from%20ChempropModel%20but%0A%20%20%20%20%23%20inline%20them%20here%20to%20keep%20the%20cell%20self-contained%20(those%20are%20cell-private).%0A%0A%20%20%20%20_MT_CHEMPROP_BIN%20%3D%20Path(sys.executable).parent%20%2F%20%22chemprop%22%0A%20%20%20%20_MT_MODEL_DIR%20%20%20%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_multitask_model%22%0A%20%20%20%20_MT_LOG%20%20%20%20%20%20%20%20%20%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_multitask_cli.log%22%0A%0A%20%20%20%20def%20_mt_device()%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22cuda%22%20if%20torch.cuda.is_available()%0A%20%20%20%20%20%20%20%20%20%20%20%20else%20%22mps%22%20if%20torch.backends.mps.is_available()%0A%20%20%20%20%20%20%20%20%20%20%20%20else%20%22cpu%22%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20def%20_mt_run_cli(args%3A%20list%5Bstr%5D)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20cmd%20%3D%20%5Bstr(_MT_CHEMPROP_BIN)%5D%20%2B%20args%0A%20%20%20%20%20%20%20%20with%20open(_MT_LOG%2C%20%22a%22)%20as%20_log%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_log.write(f%22%5Cn%7B'%3D'*60%7D%5CnCMD%3A%20%7B'%20'.join(cmd)%7D%5Cn%7B'%3D'*60%7D%5Cn%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20result%20%3D%20subprocess.run(cmd%2C%20stdout%3D_log%2C%20stderr%3D_log%2C%20text%3DTrue)%0A%20%20%20%20%20%20%20%20if%20result.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20lines%20%3D%20_MT_LOG.read_text().splitlines()%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22%5Cn%22.join(lines%5B-30%3A%5D))%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22chemprop%20CLI%20failed%20(exit%20%7Bresult.returncode%7D).%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Full%20log%3A%20%7B_MT_LOG%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20def%20_write_mt_csv(df%2C%20path%2C%20target_cols)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Write%20a%20multitask%20CSV%20with%20a%20smiles%20column%20and%20one%20column%20per%20target.%0A%0A%20%20%20%20%20%20%20%20Missing%20target%20values%20are%20written%20as%20empty%20cells%2C%20which%20chemprop%20reads%0A%20%20%20%20%20%20%20%20as%20NaN%20and%20automatically%20masks%20during%20loss%20computation.%20Boolean%20is_hit%0A%20%20%20%20%20%20%20%20columns%20are%20cast%20to%200%2F1%20float%20so%20chemprop%20treats%20them%20as%20regression.%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%20and%20target%20columns.%0A%20%20%20%20%20%20%20%20%20%20%20%20path%3A%20Output%20CSV%20path.%0A%20%20%20%20%20%20%20%20%20%20%20%20target_cols%3A%20Names%20of%20the%20target%20columns%20to%20include.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20out%20%3D%20df.select(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22smiles%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20*%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Cast%20boolean%20hit%20columns%20to%200%2F1%20float%3B%20pass%20others%20through%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.col(c).cast(pl.Float64).alias(c)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20df%5Bc%5D.dtype%20%3D%3D%20pl.Boolean%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20pl.col(c).cast(pl.Float64).alias(c)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20c%20in%20target_cols%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20out.write_csv(path%2C%20null_value%3D%22%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20multitask%20scenarios%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%0A%20%20%20%20%23%20Each%20scenario%20is%20a%20tuple%20of%3A%0A%20%20%20%20%23%20%20%20(name%2C%20list_of_target_columns)%0A%20%20%20%20%23%20pEC50_dr%20is%20always%20the%20first%20target%20%E2%80%94%20predict()%20reads%20only%20that%20column.%0A%20%20%20%20%23%20Additional%20targets%20are%20auxiliary%3B%20NaN%20rows%20are%20masked%20automatically.%0A%20%20%20%20%23%20is_hit%20columns%20are%20treated%20as%200%2F1%20regression%20targets.%0A%20%20%20%20%23%0A%20%20%20%20%23%20Training%20pool%3A%20all%20non-test%20rows%20that%20have%20at%20least%20one%20of%20the%20scenario's%0A%20%20%20%20%23%20targets%20present.%20This%20expands%20the%20training%20set%20for%20scenarios%20with%20hit%20data%0A%20%20%20%20%23%20(single-dose%20compounds%20without%20dose-response%20measurements).%0A%20%20%20%20_SCENARIOS%3A%20list%5Btuple%5Bstr%2C%20list%5Bstr%5D%5D%5D%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(%22st_pec50%22%2C%20%20%20%20%20%20%5B%22pEC50_dr%22%5D)%2C%0A%20%20%20%20%20%20%20%20(%22mt_emax_dr%22%2C%20%20%20%20%5B%22pEC50_dr%22%2C%20%22Emax_dr%22%5D)%2C%0A%20%20%20%20%20%20%20%20(%22mt_counter%22%2C%20%20%20%20%5B%22pEC50_dr%22%2C%20%22pEC50_counter%22%2C%20%22Emax_counter%22%5D)%2C%0A%20%20%20%20%20%20%20%20(%22mt_hit10%22%2C%20%20%20%20%20%20%5B%22pEC50_dr%22%2C%20%2210.0_is_hit%22%5D)%2C%0A%20%20%20%20%20%20%20%20(%22mt_hit30%22%2C%20%20%20%20%20%20%5B%22pEC50_dr%22%2C%20%2230.0_is_hit%22%5D)%2C%0A%20%20%20%20%20%20%20%20(%22mt_hits%22%2C%20%20%20%20%20%20%20%5B%22pEC50_dr%22%2C%20%2210.0_is_hit%22%2C%20%2230.0_is_hit%22%5D)%2C%0A%20%20%20%20%20%20%20%20(%22mt_all_counter%22%2C%5B%22pEC50_dr%22%2C%20%22pEC50_counter%22%2C%20%22Emax_dr%22%2C%20%22Emax_counter%22%5D)%2C%0A%20%20%20%20%20%20%20%20(%22mt_all%22%2C%20%20%20%20%20%20%20%20%5B%22pEC50_dr%22%2C%20%22pEC50_counter%22%2C%20%22Emax_dr%22%2C%20%22Emax_counter%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2210.0_is_hit%22%2C%20%2230.0_is_hit%22%5D)%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20constants%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%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_TARGET_COL%20%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_PRED_PATH_GZ%20%3D%20Path(%22..%2Fpredictions%2F3_multitask_test.csv.gz%22)%0A%20%20%20%20_N_OUTER%20%3D%205%0A%20%20%20%20_N_INNER%20%3D%205%0A%20%20%20%20_SEED%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%3D%200.1%20%20%20%20%20%20%23%20kept%20identical%20to%20other%20cells%3B%20val%20split%20used%20for%20early%20stopping%0A%0A%20%20%20%20%23%20CV%20splits%20are%20driven%20by%20the%20same%20single_task_train%20as%20all%20other%20cells.%0A%20%20%20%20%23%20Aux%20columns%20(Emax_dr%2C%20pEC50_counter%2C%20etc.)%20are%20loaded%20separately%20and%0A%20%20%20%20%23%20joined%20onto%20each%20fold%20by%20inchikey%20%E2%80%94%20single_task_train%20only%20carries%20the%0A%20%20%20%20%23%20four%20columns%20needed%20for%20CV%20splitting%2C%20not%20the%20full%20feature%20set.%0A%20%20%20%20_cv_base%20%3D%20single_task_train%0A%0A%20%20%20%20%23%20All%20aux%20target%20columns%20that%20any%20scenario%20may%20need%0A%20%20%20%20_AUX_COLS%20%3D%20%5B%22Emax_dr%22%2C%20%22pEC50_counter%22%2C%20%22Emax_counter%22%2C%20%2210.0_is_hit%22%2C%20%2230.0_is_hit%22%5D%0A%20%20%20%20_aux_data%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22in_test%22).not_()%20%26%20pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%5D%20%2B%20_AUX_COLS)%0A%20%20%20%20)%0A%0A%20%20%20%20if%20_PRED_PATH_GZ.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22Predictions%20already%20exist%20at%20%7B_PRED_PATH_GZ%7D%20%E2%80%94%20skipping%20training.%22)%0A%20%20%20%20%20%20%20%20_mt_pred_df%20%3D%20pl.read_csv(_PRED_PATH_GZ)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_all_records%3A%20list%5Bdict%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20_n_folds%20%3D%20_N_OUTER%20*%20_N_INNER%0A%0A%20%20%20%20%20%20%20%20_pbar%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_cv_base%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%20seed%3D_SEED%2C%20p_val%3D_P_VAL%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%20total%3D_n_folds%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20desc%3D%22MT%20folds%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_val_raw%2C%20_test_raw%20in%20_pbar%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Attach%20aux%20columns%20%E2%80%94%20join%20is%20by%20inchikey%2C%20nulls%20stay%20null%20for%20masking%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_raw%20%3D%20_train_raw.join(_aux_data%2C%20on%3D%22inchikey%22%2C%20how%3D%22left%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20_val_raw%20%20%20%3D%20_val_raw.join(_aux_data%2C%20on%3D%22inchikey%22%2C%20how%3D%22left%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_smi_test%20%3D%20_test_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_true%20%20%20%3D%20_test_raw%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv%20%3D%20tmp%20%2F%20%22mt_train.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv%20%20%20%3D%20tmp%20%2F%20%22mt_val.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv%20%20%3D%20tmp%20%2F%20%22mt_test.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv%20%20%3D%20tmp%20%2F%20%22mt_preds.csv%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_scenario_name%2C%20_target_cols%20in%20_SCENARIOS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pbar.set_postfix(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22fold%22%3A%20_fold%2C%20%22scenario%22%3A%20_scenario_name%7D%2C%20refresh%3DFalse%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Training%20and%20validation%20pools%20contain%20only%20compounds%20with%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20pEC50_dr%2C%20sliced%20to%20the%20same%20fold%20as%20all%20other%20CV%20cells.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Aux%20target%20columns%20are%20included%20as%20extra%20columns%3B%20rows%20where%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20those%20targets%20are%20null%20get%20an%20empty%20cell%20and%20are%20masked%20by%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20chemprop%20during%20loss%20computation.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_pool%20%3D%20_train_raw%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_val_pool%20%20%20%3D%20_val_raw%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_write_mt_csv(_train_pool%2C%20train_csv%2C%20_target_cols)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_write_mt_csv(_val_pool%2C%20%20%20val_csv%2C%20%20%20_target_cols)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20test%20CSV%20needs%20only%20smiles%20(no%20targets%20needed%20for%20prediction)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(%7B%22smiles%22%3A%20_smi_test%7D).write_csv(test_csv)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_MT_MODEL_DIR.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(_MT_MODEL_DIR)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20All%20targets%20treated%20as%20regression%3B%20is_hit%20encoded%20as%200%2F1%20float%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_mt_run_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--data-path%22%2C%20str(train_csv)%2C%20str(val_csv)%2C%20str(val_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--smiles-columns%22%2C%20%22smiles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--target-columns%22%2C%20*_target_cols%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--task-type%22%2C%20%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--accelerator%22%2C%20_mt_device()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--epochs%22%2C%20%2250%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--save-dir%22%2C%20str(_MT_MODEL_DIR)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20model_pt%20%3D%20_MT_MODEL_DIR%20%2F%20%22model_0%22%20%2F%20%22best.pt%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_mt_run_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22predict%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--test-path%22%2C%20%20str(test_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--model-path%22%2C%20str(model_pt)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--preds-path%22%2C%20str(pred_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Read%20only%20pEC50_dr%20column%20from%20the%20predictions%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred%20%3D%20pl.read_csv(pred_csv)%5B_TARGET_COL%5D.to_numpy().flatten()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20train_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20val_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20test_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%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%22inchikey%22%3A%20%20%20%20%20%20%20_ik%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%22molecule_names%22%3A%20_mn%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%22smiles%22%3A%20%20%20%20%20%20%20%20%20_smi%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%22fold%22%3A%20%20%20%20%20%20%20%20%20%20%20_fold%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%22outer_fold%22%3A%20%20%20%20%20_outer%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%22inner_fold%22%3A%20%20%20%20%20_inner%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%22model%22%3A%20%20%20%20%20%20%20%20%20%20_scenario_name%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%22y_true%22%3A%20%20%20%20%20%20%20%20%20_yt%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%22y_pred%22%3A%20%20%20%20%20%20%20%20%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20write%20compressed%20output%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%0A%20%20%20%20%20%20%20%20_mt_pred_df%20%3D%20pl.DataFrame(_all_records)%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_PRED_PATH_GZ%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_mt_pred_df.write_csv(_f)%0A%20%20%20%20%20%20%20%20print(f%22%5CnSaved%20%7Blen(_mt_pred_df)%3A%2C%7D%20prediction%20rows%20%E2%86%92%20%7B_PRED_PATH_GZ%7D%22)%0A%0A%20%20%20%20def%20_summarise(df)%3A%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20thresh%3D4.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20%20%20%20%20.sort(%22rho%22%2C%20descending%3DTrue)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_summarise(_mt_pred_df)%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%20cell%20took%205h%20to%20run%20on%20a%20Mac%20Mini%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20calc_regression_metrics%2C%20mo%2C%20pl%2C%20rm_tukey_hsd)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Control%20check%3A%20verify%20that%20st_pec50%20(single-task%20chemprop%20trained%20in%20the%0A%20%20%20%20multitask%20cell)%20is%20statistically%20identical%20to%20chemprop%20from%20the%20baseline%20run.%0A%20%20%20%20Both%20use%20the%20same%20architecture%2C%20the%20same%20CV%20splits%2C%20and%20the%20same%20single%20target%2C%0A%20%20%20%20so%20any%20difference%20would%20indicate%20a%20data-pipeline%20inconsistency.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_BASELINE%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_MT_PATH%20%20%3D%20Path(%22..%2Fpredictions%2F3_multitask_test.csv.gz%22)%0A%20%20%20%20_METRIC_LIST%20%3D%20%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D%0A%0A%20%20%20%20_ctrl_df%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20pl.read_csv(_BASELINE)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemprop%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20pl.read_csv(_MT_PATH)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22st_pec50%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%5D%2C%20how%3D%22diagonal%22)%0A%0A%20%20%20%20_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_ctrl_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_tukey_tables%20%3D%20%5B%5D%0A%20%20%20%20for%20_metric%20in%20_METRIC_LIST%3A%0A%20%20%20%20%20%20%20%20_result_tab%2C%20_df_means%2C%20_%2C%20_%20%3D%20rm_tukey_hsd(_metrics%2C%20_metric%2C%20group_col%3D%22method%22)%0A%20%20%20%20%20%20%20%20_tukey_tables.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22**%7B_metric.upper()%7D**%20%E2%80%94%20mean%20chemprop%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%60%7B_df_means.loc%5B'chemprop'%2C%20_metric%5D%3A.4f%7D%60%2C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22mean%20st_pec50%3A%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%60%7B_df_means.loc%5B'st_pec50'%2C%20_metric%5D%3A.4f%7D%60%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.plain_text(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_result_tab%5B%5B%22meandiff%22%2C%20%22lower%22%2C%20%22upper%22%2C%20%22p-adj%22%5D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.to_string()%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%5D)%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Control%3A%20%60chemprop%60%20(baseline)%20vs%20%60st_pec50%60%20(multitask%20test)%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(%22Expecting%20**no%20significant%20difference**%20(p-adj%20%3E%200.05)%20for%20all%20metrics.%22)%2C%0A%20%20%20%20%20%20%20%20*_tukey_tables%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20make_boxplots_nonparametric%2C%0A%20%20%20%20make_ci_plot_grid%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A)%3A%0A%20%20%20%20_BASELINE%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20_METRIC_LIST%20%3D%20%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D%0A%20%20%20%20_PLOT_DIR%20%3D%20Path(%22..%2Fplots%2F3_ml_optimization%22)%0A%20%20%20%20_PLOT_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_chemeleon_ref%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(_BASELINE)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22chemel%22).alias(%22model%22))%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%20%20%20%20_mt_cmp_df%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(Path(%22..%2Fpredictions%2F3_multitask_test.csv.gz%22))%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%20%20%20%20_cmp_df%20%3D%20pl.concat(%5B_mt_cmp_df%2C%20_chemeleon_ref%5D)%0A%20%20%20%20_metrics%20%3D%20calc_regression_metrics(_cmp_df%2C%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.as_html(make_boxplots_nonparametric(_metrics%2C%20_METRIC_LIST%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3D_PLOT_DIR%20%2F%20%22multitask_boxplots.png%22))%2C%0A%20%20%20%20%20%20%20%20mo.as_html(make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20%20%20%20%20_metrics%2C%20stats%3D_METRIC_LIST%2C%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20figsize%3D(13%2C%2012)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20show_diff%3DTrue%2C%20sort_axes%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3D_PLOT_DIR%20%2F%20%22multitask_mcs.png%22%2C%0A%20%20%20%20%20%20%20%20))%2C%0A%20%20%20%20%20%20%20%20mo.as_html(make_ci_plot_grid(%0A%20%20%20%20%20%20%20%20%20%20%20%20_metrics%2C%20metric_list%3D%5B%22mae%22%5D%2C%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20figsize%3D(6%2C%2014)%2C%20xlim%3D(-0.05%2C%200.05)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3D_PLOT_DIR%20%2F%20%22multitask_ci_mae.png%22%2C%0A%20%20%20%20%20%20%20%20))%2C%0A%20%20%20%20%5D)%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%20Train%20final%20models%20on%20the%20full%20training%20set%20and%20generate%20test-set%20submissions%0A%0A%20%20%20%20We%20train%20two%20new%20models%20on%20the%20full%20%60single_task_train%60%20dataset%20(all%20compounds%0A%20%20%20%20with%20a%20measured%20pEC50%5C_dr)%20and%20generate%20predictions%20for%20the%20513%20held-out%20test%0A%20%20%20%20compounds%3A%0A%0A%20%20%20%201.%20**%60chemprop_depth6%60**%20%E2%80%94%20Chemprop%20D-MPNN%20from%20scratch%20with%20%60depth%3D6%60%20and%0A%20%20%20%20%20%20%20default%20%60message_hidden_dim%3D300%60%20(matching%20the%20CV%20study%20configuration).%0A%20%20%20%202.%20**%60rf_mordred3d%60**%20%E2%80%94%20Random%20Forest%20trained%20on%20Mordred%203-D%20descriptors%0A%20%20%20%20%20%20%20(1826%20descriptors%20computed%20from%20RDKit%20ETKDGv3%20conformers).%0A%0A%20%20%20%20These%20predictions%20are%20then%20combined%20with%20the%20existing%20CheMeleon%20baseline%0A%20%20%20%20(%60predictions%2F2_ml_baseline_chemeleon_test_submission.csv%60)%20to%20build%20four%0A%20%20%20%20weighted-mean%20ensemble%20submission%20files.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ChempropModel%2C%20Path%2C%20np%2C%20pl%2C%20single_task_train)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Train%20a%20Chemprop%20depth-6%20model%20(depth%3D6%2C%20default%20message_hidden_dim%3D300)%20on%20the%0A%20%20%20%20entire%20training%20set%20and%20generate%20predictions%20for%20the%20513%20held-out%20test%0A%20%20%20%20compounds.%0A%0A%20%20%20%20A%2010%20%25%20internal%20validation%20split%20is%20drawn%20for%20early%20stopping%20%E2%80%94%20this%20is%20NOT%0A%20%20%20%20the%20competition%20test%20set.%0A%0A%20%20%20%20Output%20CSV%20columns%3A%20SMILES%20%7C%20Molecule%20Name%20%7C%20pEC50%0A%20%20%20%20%22%22%22%0A%0A%20%20%20%20_TARGET_COL%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_SEED%20%20%20%20%20%20%20%3D%2042%0A%20%20%20%20_PRED_OUT%20%20%20%3D%20Path(%22..%2Fpredictions%2F3_chemprop_depth6_test_submission.csv%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20load%20test%20set%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%20%20%20%20_test_df%20%3D%20pl.read_csv(%22..%2Fdata%2Fraw%2F20260409%2Fdose_response_test.csv%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%2010%20%25%20validation%20split%20for%20early%20stopping%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%0A%20%20%20%20_rng%20%20%20%20%20%20%3D%20np.random.default_rng(_SEED)%0A%20%20%20%20_n%20%20%20%20%20%20%20%20%3D%20single_task_train.shape%5B0%5D%0A%20%20%20%20_val_idx%20%20%3D%20_rng.choice(_n%2C%20size%3Dint(_n%20*%200.1)%2C%20replace%3DFalse)%0A%20%20%20%20_train_idx%20%3D%20np.setdiff1d(np.arange(_n)%2C%20_val_idx)%0A%0A%20%20%20%20_train_sub%20%3D%20single_task_train%5B_train_idx%5D%0A%20%20%20%20_val_sub%20%20%20%3D%20single_task_train%5B_val_idx%5D%0A%0A%20%20%20%20_X_train%20%3D%20_train_sub%5B%22smiles%22%5D.to_list()%0A%20%20%20%20_y_train%20%3D%20_train_sub%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20_X_val%20%20%20%3D%20_val_sub%5B%22smiles%22%5D.to_list()%0A%20%20%20%20_y_val%20%20%20%3D%20_val_sub%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20_X_test%20%20%3D%20_test_df%5B%22SMILES%22%5D.to_list()%0A%0A%20%20%20%20if%20_PRED_OUT.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22Submission%20file%20already%20exists%20at%20%7B_PRED_OUT%7D%20%E2%80%94%20skipping%20training.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20depth%3D6%20matches%20the%20chemprop_depth6%20CV%20study%20configuration%20(message_hidden_dim%3D300%20default)%0A%20%20%20%20%20%20%20%20_model%20%3D%20ChempropModel(%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3D%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20epochs%3D50%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20depth%3D6%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_model.train(_X_train%2C%20_y_train%2C%20_X_val%2C%20_y_val%2C%20target_col%3D_TARGET_COL)%0A%0A%20%20%20%20%20%20%20%20_y_pred%20%3D%20_model.predict(_X_test)%0A%0A%20%20%20%20%20%20%20%20_submission%20%3D%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_test_df%5B%22SMILES%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Molecule%20Name%22%3A%20_test_df%5B%22Molecule%20Name%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22pEC50%22%3A%20%20%20%20%20%20%20%20%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20_PRED_OUT.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20_submission.write_csv(_PRED_OUT)%0A%20%20%20%20%20%20%20%20print(f%22Saved%20%7Blen(_submission)%7D%20predictions%20%E2%86%92%20%7B_PRED_OUT%7D%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20RandomForestModel%2C%0A%20%20%20%20extract_fp_matrix%2C%0A%20%20%20%20generate_fingerprint%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20single_task_train%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Train%20a%20Random%20Forest%20on%20Mordred%203-D%20descriptors%20on%20the%20entire%20training%20set%0A%20%20%20%20and%20generate%20predictions%20for%20the%20513%20held-out%20test%20compounds.%0A%0A%20%20%20%20Mordred%203-D%20uses%201826%20molecular%20descriptors%3B%20conformers%20are%20generated%0A%20%20%20%20internally%20by%20scikit-fingerprints%20via%20RDKit%20ETKDGv3%20(no%20pre-computed%0A%20%20%20%20conformers%20needed).%0A%0A%20%20%20%20Output%20CSV%20columns%3A%20SMILES%20%7C%20Molecule%20Name%20%7C%20pEC50%0A%20%20%20%20%22%22%22%0A%20%20%20%20%23import%20os%20as%20_os_rf%0A%20%20%20%20%23%20Allow%20multiple%20OpenMP%20runtimes%20(PyTorch%20%2B%20sklearn)%20to%20coexist%20without%20crash%0A%20%20%20%20%23_os_rf.environ%5B%22KMP_DUPLICATE_LIB_OK%22%5D%20%3D%20%22TRUE%22%0A%0A%20%20%20%20_TARGET_COL%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_SEED%20%20%20%20%20%20%20%3D%2042%0A%20%20%20%20_PRED_OUT%20%20%20%3D%20Path(%22..%2Fpredictions%2F3_rf_mordred3d_test_submission.csv%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20load%20test%20set%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%20%20%20%20_test_df%20%3D%20pl.read_csv(%22..%2Fdata%2Fraw%2F20260409%2Fdose_response_test.csv%22)%0A%20%20%20%20%23%20generate_fingerprint%20expects%20a%20%22smiles%22%20column%20(lowercase)%0A%20%20%20%20_test_for_fp%20%3D%20_test_df.rename(%7B%22SMILES%22%3A%20%22smiles%22%7D)%0A%0A%20%20%20%20if%20_PRED_OUT.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22Submission%20file%20already%20exists%20at%20%7B_PRED_OUT%7D%20%E2%80%94%20skipping%20training.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20generate%20Mordred%203-D%20descriptors%20for%20train%20and%20test%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%0A%20%20%20%20%20%20%20%20_train_fp%20%3D%20generate_fingerprint(single_task_train%2C%20%22mordred%22%2C%20use_3D%3DTrue)%0A%20%20%20%20%20%20%20%20_test_fp%20%20%3D%20generate_fingerprint(_test_for_fp%2C%20%22mordred%22%2C%20use_3D%3DTrue)%0A%0A%20%20%20%20%20%20%20%20_X_train%20%3D%20extract_fp_matrix(_train_fp%2C%20%22mordred%22)%0A%20%20%20%20%20%20%20%20_y_train%20%3D%20single_task_train%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20_X_test%20%20%3D%20extract_fp_matrix(_test_fp%2C%20%22mordred%22)%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20train%20Random%20Forest%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%0A%20%20%20%20%20%20%20%20_model%20%3D%20RandomForestModel(pred_type%3D%22regression%22%2C%20random_state%3D_SEED)%0A%20%20%20%20%20%20%20%20_model.train(_X_train%2C%20_y_train)%0A%0A%20%20%20%20%20%20%20%20_y_pred%20%3D%20_model.predict(_X_test)%0A%0A%20%20%20%20%20%20%20%20_submission%20%3D%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_test_df%5B%22SMILES%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Molecule%20Name%22%3A%20_test_df%5B%22Molecule%20Name%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22pEC50%22%3A%20%20%20%20%20%20%20%20%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20_PRED_OUT.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20_submission.write_csv(_PRED_OUT)%0A%20%20%20%20%20%20%20%20print(f%22Saved%20%7Blen(_submission)%7D%20predictions%20%E2%86%92%20%7B_PRED_OUT%7D%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20np%2C%20pl)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Build%20four%20weighted-mean%20ensemble%20submission%20files%20by%20combining%3A%0A%20%20%20%20%20%20%20%20%E2%80%A2%20cp%20%20%E2%80%94%20chemprop_depth6%20%20(predictions%2F3_chemprop_depth6_test_submission.csv)%0A%20%20%20%20%20%20%20%20%E2%80%A2%20rf%20%20%E2%80%94%20RF%20%2B%20Mordred%203-D%20(predictions%2F3_rf_mordred3d_test_submission.csv)%0A%20%20%20%20%20%20%20%20%E2%80%A2%20ch%20%20%E2%80%94%20CheMeleon%20%20%20%20%20%20%20%20(predictions%2F2_ml_baseline_chemeleon_test_submission.csv)%0A%0A%20%20%20%20The%20four%20ensembles%20requested%2C%20expressed%20as%20(rf%2C%20gbm%2C%20cp%2C%20ch)%20integer%20weights%3A%0A%20%20%20%20%20%20%20%20ens_rf0_gbm0_cp1_ch2%20%20%E2%86%92%20%20(0%2C%200%2C%201%2C%202)%0A%20%20%20%20%20%20%20%20ens_rf0_gbm0_cp1_ch1%20%20%E2%86%92%20%20(0%2C%200%2C%201%2C%201)%0A%20%20%20%20%20%20%20%20ens_rf1_gbm0_cp2_ch2%20%20%E2%86%92%20%20(1%2C%200%2C%202%2C%202)%0A%20%20%20%20%20%20%20%20ens_rf1_gbm0_cp1_ch2%20%20%E2%86%92%20%20(1%2C%200%2C%201%2C%202)%0A%0A%20%20%20%20Output%20files%3A%0A%20%20%20%20%20%20%20%20predictions%2F3_ens_rf0_gbm0_cp1_ch2_submission.csv%0A%20%20%20%20%20%20%20%20predictions%2F3_ens_rf0_gbm0_cp1_ch1_submission.csv%0A%20%20%20%20%20%20%20%20predictions%2F3_ens_rf1_gbm0_cp2_ch2_submission.csv%0A%20%20%20%20%20%20%20%20predictions%2F3_ens_rf1_gbm0_cp1_ch2_submission.csv%0A%20%20%20%20%22%22%22%0A%0A%20%20%20%20_CP_PATH%20%20%3D%20Path(%22..%2Fpredictions%2F3_chemprop_depth6_test_submission.csv%22)%0A%20%20%20%20_RF_PATH%20%20%3D%20Path(%22..%2Fpredictions%2F3_rf_mordred3d_test_submission.csv%22)%0A%20%20%20%20_CH_PATH%20%20%3D%20Path(%22..%2Fpredictions%2F2_ml_baseline_chemeleon_test_submission.csv%22)%0A%20%20%20%20_OUT_DIR%20%20%3D%20Path(%22..%2Fpredictions%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20load%20predictions%20from%20the%20three%20component%20models%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%0A%20%20%20%20_cp%20%3D%20pl.read_csv(_CP_PATH).rename(%7B%22pEC50%22%3A%20%22pEC50_cp%22%7D)%0A%20%20%20%20_rf%20%3D%20pl.read_csv(_RF_PATH).rename(%7B%22pEC50%22%3A%20%22pEC50_rf%22%7D)%0A%20%20%20%20_ch%20%3D%20pl.read_csv(_CH_PATH).rename(%7B%22pEC50%22%3A%20%22pEC50_ch%22%7D)%0A%0A%20%20%20%20%23%20Join%20on%20Molecule%20Name%20to%20align%20rows%20(test%20set%20order%20may%20differ%20across%20files)%0A%20%20%20%20_merged%20%3D%20(%0A%20%20%20%20%20%20%20%20_cp%0A%20%20%20%20%20%20%20%20.join(_rf.select(%5B%22Molecule%20Name%22%2C%20%22pEC50_rf%22%5D)%2C%20on%3D%22Molecule%20Name%22%2C%20how%3D%22left%22)%0A%20%20%20%20%20%20%20%20.join(_ch.select(%5B%22Molecule%20Name%22%2C%20%22pEC50_ch%22%5D)%2C%20on%3D%22Molecule%20Name%22%2C%20how%3D%22left%22)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20Weights%3A%20(w_rf%2C%20w_gbm%2C%20w_cp%2C%20w_ch)%20%E2%80%94%20gbm%20is%20absent%2C%20so%20w_gbm%3D0%20always%0A%20%20%20%20_ensembles%3A%20list%5Btuple%5Bstr%2C%20int%2C%20int%2C%20int%2C%20int%5D%5D%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(%22ens_rf0_gbm0_cp1_ch2%22%2C%200%2C%200%2C%201%2C%202)%2C%0A%20%20%20%20%20%20%20%20(%22ens_rf0_gbm0_cp1_ch1%22%2C%200%2C%200%2C%201%2C%201)%2C%0A%20%20%20%20%20%20%20%20(%22ens_rf1_gbm0_cp2_ch2%22%2C%201%2C%200%2C%202%2C%202)%2C%0A%20%20%20%20%20%20%20%20(%22ens_rf1_gbm0_cp1_ch2%22%2C%201%2C%200%2C%201%2C%202)%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20for%20_name%2C%20_w_rf%2C%20_w_gbm%2C%20_w_cp%2C%20_w_ch%20in%20_ensembles%3A%0A%20%20%20%20%20%20%20%20_out_path%20%3D%20_OUT_DIR%20%2F%20f%223_%7B_name%7D_submission.csv%22%0A%20%20%20%20%20%20%20%20if%20_out_path.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Already%20exists%20%E2%80%94%20skipping%20%7B_out_path.name%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%0A%20%20%20%20%20%20%20%20_total%20%3D%20float(_w_rf%20%2B%20_w_gbm%20%2B%20_w_cp%20%2B%20_w_ch)%0A%20%20%20%20%20%20%20%20_pec50_ens%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20_merged%5B%22pEC50_rf%22%5D.to_numpy()%20*%20_w_rf%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%20np.zeros(len(_merged))%20*%20_w_gbm%20%20%20%23%20gbm%20weight%20is%200%3B%20placeholder%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%20_merged%5B%22pEC50_cp%22%5D.to_numpy()%20*%20_w_cp%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%20_merged%5B%22pEC50_ch%22%5D.to_numpy()%20*%20_w_ch%0A%20%20%20%20%20%20%20%20)%20%2F%20_total%0A%0A%20%20%20%20%20%20%20%20_sub%20%3D%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_merged%5B%22SMILES%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Molecule%20Name%22%3A%20_merged%5B%22Molecule%20Name%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22pEC50%22%3A%20%20%20%20%20%20%20%20%20_pec50_ens.tolist()%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20_sub.write_csv(_out_path)%0A%20%20%20%20%20%20%20%20print(f%22Saved%20%7Blen(_sub)%7D%20predictions%20%E2%86%92%20%7B_out_path.name%7D%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Iterable%2C%20Optional%2C%20Path%2C%20mo%2C%20np%2C%20pd)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Validate%20all%20five%20submission%20files%20produced%20in%20this%20section%3A%0A%20%20%20%20%20%20%20%20%E2%80%A2%203_chemprop_depth6_test_submission.csv%0A%20%20%20%20%20%20%20%20%E2%80%A2%203_rf_mordred3d_test_submission.csv%0A%20%20%20%20%20%20%20%20%E2%80%A2%203_ens_rf0_gbm0_cp1_ch2_submission.csv%0A%20%20%20%20%20%20%20%20%E2%80%A2%203_ens_rf0_gbm0_cp1_ch1_submission.csv%0A%20%20%20%20%20%20%20%20%E2%80%A2%203_ens_rf1_gbm0_cp2_ch2_submission.csv%0A%20%20%20%20%20%20%20%20%E2%80%A2%203_ens_rf1_gbm0_cp1_ch2_submission.csv%0A%0A%20%20%20%20Rules%20applied%20(matching%20the%20OpenADMET%20activity_validation.py%20spec)%3A%0A%20%20%20%20%20%20%20%20-%20Required%20columns%3A%20SMILES%2C%20Molecule%20Name%2C%20pEC50%0A%20%20%20%20%20%20%20%20-%20No%20missing%20identifiers%20or%20duplicate%20Molecule%20Names%0A%20%20%20%20%20%20%20%20-%20pEC50%20must%20be%20numeric%20and%20finite%0A%20%20%20%20%20%20%20%20-%20Exactly%20513%20rows%0A%20%20%20%20%22%22%22%0A%0A%20%20%20%20_ACTIVITY_DATASET_SIZE%20%3D%20513%0A%0A%20%20%20%20_SUBMISSION_FILES%20%3D%20%5B%0A%20%20%20%20%20%20%20%20Path(%22..%2Fpredictions%2F3_chemprop_depth6_test_submission.csv%22)%2C%0A%20%20%20%20%20%20%20%20Path(%22..%2Fpredictions%2F3_rf_mordred3d_test_submission.csv%22)%2C%0A%20%20%20%20%20%20%20%20Path(%22..%2Fpredictions%2F3_ens_rf0_gbm0_cp1_ch2_submission.csv%22)%2C%0A%20%20%20%20%20%20%20%20Path(%22..%2Fpredictions%2F3_ens_rf0_gbm0_cp1_ch1_submission.csv%22)%2C%0A%20%20%20%20%20%20%20%20Path(%22..%2Fpredictions%2F3_ens_rf1_gbm0_cp2_ch2_submission.csv%22)%2C%0A%20%20%20%20%20%20%20%20Path(%22..%2Fpredictions%2F3_ens_rf1_gbm0_cp1_ch2_submission.csv%22)%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20def%20_as_set(values%3A%20Iterable%5Bstr%5D)%20-%3E%20set%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20return%20%7Bstr(v)%20for%20v%20in%20values%7D%0A%0A%20%20%20%20def%20validate_activity_submission_3(%0A%20%20%20%20%20%20%20%20activity_predictions_file%3A%20Path%2C%0A%20%20%20%20%20%20%20%20expected_ids%3A%20Optional%5Bset%5Bstr%5D%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20required_id_columns%3A%20tuple%5Bstr%2C%20...%5D%20%3D%20(%22SMILES%22%2C%20%22Molecule%20Name%22)%2C%0A%20%20%20%20%20%20%20%20required_value_columns%3A%20tuple%5Bstr%2C%20...%5D%20%3D%20(%22pEC50%22%2C)%2C%0A%20%20%20%20)%20-%3E%20tuple%5Bbool%2C%20list%5Bstr%5D%5D%3A%0A%20%20%20%20%20%20%20%20errors%3A%20list%5Bstr%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20path%20%3D%20Path(activity_predictions_file)%0A%20%20%20%20%20%20%20%20if%20not%20path.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20False%2C%20%5Bf%22File%20does%20not%20exist%3A%20%7Bpath%7D%22%5D%0A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20activity_predictions%20%3D%20pd.read_csv(path)%0A%20%20%20%20%20%20%20%20except%20Exception%20as%20exc%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20False%2C%20%5Bf%22Error%20reading%20CSV%20file%3A%20%7Bexc%7D%22%5D%0A%0A%20%20%20%20%20%20%20%20required_columns%20%3D%20(*required_id_columns%2C%20*required_value_columns)%0A%20%20%20%20%20%20%20%20missing_columns%20%3D%20%5Bcol%20for%20col%20in%20required_columns%20if%20col%20not%20in%20activity_predictions.columns%5D%0A%20%20%20%20%20%20%20%20if%20missing_columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22Missing%20required%20column(s)%3A%20%7Bmissing_columns%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20False%2C%20errors%0A%0A%20%20%20%20%20%20%20%20if%20activity_predictions.empty%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(%22Submission%20is%20empty.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20False%2C%20errors%0A%0A%20%20%20%20%20%20%20%20null_id_rows%20%3D%20activity_predictions%5Blist(required_id_columns)%5D.isna().any(axis%3D1).sum()%0A%20%20%20%20%20%20%20%20if%20null_id_rows%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22Found%20%7Bnull_id_rows%7D%20row(s)%20with%20missing%20identifier%20values.%22)%0A%0A%20%20%20%20%20%20%20%20if%20%22Molecule%20Name%22%20in%20activity_predictions.columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicate_ids%20%3D%20activity_predictions%5B%22Molecule%20Name%22%5D.duplicated().sum()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20duplicate_ids%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22Found%20%7Bduplicate_ids%7D%20duplicated%20'Molecule%20Name'%20value(s).%22)%0A%0A%20%20%20%20%20%20%20%20for%20col%20in%20required_value_columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20numeric_col%20%3D%20pd.to_numeric(activity_predictions%5Bcol%5D%2C%20errors%3D%22coerce%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20invalid_numeric%20%3D%20numeric_col.isna().sum()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20invalid_numeric%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22Column%20'%7Bcol%7D'%20contains%20%7Binvalid_numeric%7D%20non-numeric%20or%20missing%20value(s).%22)%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%20non_finite%20%3D%20(~np.isfinite(numeric_col.to_numpy())).sum()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20non_finite%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22Column%20'%7Bcol%7D'%20contains%20%7Bnon_finite%7D%20non-finite%20value(s)%20(inf%20or%20-inf).%22)%0A%0A%20%20%20%20%20%20%20%20submitted_ids%20%3D%20_as_set(activity_predictions%5B%22Molecule%20Name%22%5D)%0A%20%20%20%20%20%20%20%20if%20expected_ids%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20expected_ids%20%3D%20_as_set(expected_ids)%0A%20%20%20%20%20%20%20%20%20%20%20%20missing%20%3D%20sorted(expected_ids%20-%20submitted_ids)%0A%20%20%20%20%20%20%20%20%20%20%20%20extra%20%3D%20sorted(submitted_ids%20-%20expected_ids)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20missing%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22Missing%20%7Blen(missing)%7D%20expected%20molecule(s)%3A%20%7Bmissing%5B%3A20%5D%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20extra%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22Found%20%7Blen(extra)%7D%20unexpected%20molecule(s)%3A%20%7Bextra%5B%3A20%5D%7D%22)%0A%20%20%20%20%20%20%20%20elif%20len(activity_predictions)%20!%3D%20_ACTIVITY_DATASET_SIZE%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Submission%20contains%20%7Blen(activity_predictions)%7D%20rows%2C%20expected%20%7B_ACTIVITY_DATASET_SIZE%7D.%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20return%20len(errors)%20%3D%3D%200%2C%20errors%0A%0A%20%20%20%20_results%20%3D%20%5B%5D%0A%20%20%20%20for%20_path%20in%20_SUBMISSION_FILES%3A%0A%20%20%20%20%20%20%20%20_ok%2C%20_errs%20%3D%20validate_activity_submission_3(_path)%0A%20%20%20%20%20%20%20%20if%20_ok%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_results.append(mo.md(f%22%E2%9C%93%20**%7B_path.name%7D**%20%E2%80%94%20validation%20passed.%22))%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_results.append(mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%E2%9C%97%20**%7B_path.name%7D**%20%E2%80%94%20validation%20FAILED%3A%5Cn%22%20%2B%20%22%5Cn%22.join(f%22-%20%7Be%7D%22%20for%20e%20in%20_errs)%0A%20%20%20%20%20%20%20%20%20%20%20%20))%0A%0A%20%20%20%20mo.vstack(_results)%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
f001f922c261446dda9919d0cb70f824