| from __future__ import annotations |
|
|
| from typing import Dict, Tuple |
| import pandas as pd |
| from rdkit import Chem |
| from rdkit import RDLogger |
|
|
| RDLogger.DisableLog("rdApp.*") |
|
|
| def canonicalize_smiles(smiles: str): |
| """ |
| convert smiles to canonical smiles |
| :param smiles: smiles string |
| :return: returns canonical smiles string, or None if invalid |
| """ |
|
|
| if not isinstance(smiles, str): |
| print('not a valid smiles string') |
| return None |
|
|
| try: |
| mol = Chem.MolFromSmiles(smiles, sanitize=True) |
|
|
| if mol is None: |
| return None |
|
|
| return Chem.MolToSmiles(mol, canonical=True) |
|
|
| except Exception: |
| print('exception occurred during canonicalization') |
| return None |
|
|
| def canonicalize_smiles_df(df, smiles_column_name, canonical_column_name: str = "canonical_smiles", drop_bad_smiles: bool = True): |
| """ |
| canonicalize a dataframe column of smiles strings |
| :param df: pandas dataframe |
| :param smiles_column_name: column name to look for smiles strings |
| :param canonical_column_name: name of new column to store canonical smiles |
| :param drop_bad_smiles: remove the rows with invalid smiles |
| :return: original dataframe with new column of canonical smiles and invalid smiles removed (if specified) |
| """ |
|
|
| df2 = df.copy() |
|
|
| df2[canonical_column_name] = df2[smiles_column_name].apply(canonicalize_smiles) |
|
|
| |
| df2['changed'] = df2[canonical_column_name] != df2[smiles_column_name] |
| n_changed = df2['changed'].sum() |
| print(len(df2) - n_changed, 'smiles were already canonical,', n_changed, 'smiles were changed during canonicalization') |
|
|
| if drop_bad_smiles: |
|
|
| n_before = len(df2) |
| df2 = df2.dropna(subset=[canonical_column_name]).reset_index(drop=True) |
| n_after = len(df2) |
|
|
| print(f"dropped {n_before - n_after} invalid smiles") |
|
|
| return df2 |
|
|
| def count_stereocenters_from_smiles(smiles: str) -> Dict[str, int]: |
|
|
| mol = Chem.MolFromSmiles(smiles) |
| if mol is None: |
| raise ValueError(f"Invalid SMILES: {smiles}") |
|
|
| |
| Chem.FindPotentialStereoBonds(mol) |
|
|
| stereocenters = Chem.FindMolChiralCenters(mol, includeUnassigned=True) |
| stereobonds = [ |
| bond |
| for bond in mol.GetBonds() |
| if bond.GetStereo() is not Chem.rdchem.BondStereo.STEREONONE |
| ] |
|
|
| atom_assigned = sum(1 for _, tag in stereocenters if tag != "?") |
| atom_unassigned = sum(1 for _, tag in stereocenters if tag == "?") |
|
|
| bond_assigned = sum( |
| 1 |
| for bond in stereobonds |
| if bond.GetStereo() is not Chem.rdchem.BondStereo.STEREOANY |
| ) |
| bond_unassigned = sum( |
| 1 for bond in stereobonds if bond.GetStereo() is Chem.rdchem.BondStereo.STEREOANY |
| ) |
|
|
| return { |
| "atom_assigned": atom_assigned, |
| "atom_unassigned": atom_unassigned, |
| "bond_assigned": bond_assigned, |
| "bond_unassigned": bond_unassigned, |
| } |
|
|
|
|
| def filter_unassigned_stereo_rows(df: pd.DataFrame, smiles_col: str = "original_smiles", *, drop_invalid_smiles: bool = True, keep_stereo_counts: bool = False) -> pd.DataFrame: |
|
|
| counts = [] |
| bad_smiles_idx = [] |
|
|
| for idx, smi in df[smiles_col].items(): |
| if not isinstance(smi, str) or not smi.strip(): |
| bad_smiles_idx.append(idx) |
| counts.append({"atom_assigned": 0, "atom_unassigned": 0, "bond_assigned": 0, "bond_unassigned": 0}) |
| continue |
|
|
| try: |
| c = count_stereocenters_from_smiles(smi) |
| counts.append(c) |
| except Exception: |
| if drop_invalid_smiles: |
| bad_smiles_idx.append(idx) |
| counts.append({"atom_assigned": 0, "atom_unassigned": 0, "bond_assigned": 0, "bond_unassigned": 0}) |
| else: |
| raise |
|
|
| counts_df = pd.DataFrame(counts, index=df.index) |
|
|
| if keep_stereo_counts: |
| df2 = df.join(counts_df) |
| else: |
| df2 = df.copy() |
|
|
| |
| ok = (counts_df["atom_unassigned"] == 0) & (counts_df["bond_unassigned"] == 0) |
|
|
| if drop_invalid_smiles and bad_smiles_idx: |
| ok = ok & (~df2.index.isin(bad_smiles_idx)) |
|
|
| return df2.loc[ok].reset_index(drop=True) |
|
|
|
|
| if __name__ == '__main__': |
|
|
| df = pd.read_csv('/Users/mimis_stuff/PycharmProjects/PythonProject/philicity_prediction/data/all_philicities.csv') |
|
|
| df = canonicalize_smiles_df(df, 'radical_smiles', 'canonical_radical_smiles', True) |
|
|
| df.to_csv('/Users/mimis_stuff/PycharmProjects/PythonProject/philicity_prediction/data/all_philicities_canonicalized.csv', index=False) |
|
|
|
|