###################### # Import libraries ###################### import numpy as np import pandas as pd import streamlit as st import pickle from PIL import Image from rdkit import Chem from rdkit.Chem import Descriptors, PandasTools import mols2grid import streamlit.components.v1 as components import plotly.express as px import datetime from pandas.api.types import is_numeric_dtype from scipy.stats import pearsonr, friedmanchisquare, wilcoxon import scikit_posthocs as sp import sys,os import base64 from io import BytesIO from rdkit.Chem.Draw import rdMolDraw2D import matplotlib.pyplot as plt import seaborn as sns cwd = os.getcwd() # Insert functions path into working dir if they are not in the same working dir sys.path.insert(1, cwd) from BackendAI import benchmark_model, get_desc,eval_predictions #from custom_classes import RemoveZeroVarianceFeatures, RemoveAutocorrelatedFeatures #from rdkit.Chem import Draw import plotly.express as px import plotly.io as pio import plotly.graph_objects as go pio.templates.default = "simple_white" font_style = dict(family="Verdana",size=23,color="black") if 'input_df' not in st.session_state: st.session_state.input_df = pd.DataFrame() if 'train_df' not in st.session_state: st.session_state.train_df = pd.DataFrame() if 'benchmark_obj' not in st.session_state: st.session_state.benchmark_obj = object() if 'desc_dict' not in st.session_state: st.session_state.desc_dict = {} if 'data_obj' not in st.session_state: st.session_state.data_obj = {} if 'cv_df' not in st.session_state: st.session_state.cv_df = pd.DataFrame() if 'X_desc_test' not in st.session_state: st.session_state.X_desc_test = np.array([]) if 'test_file' not in st.session_state: st.session_state.test_file = pd.DataFrame() def restart_session_state(): st.session_state.hyperopt_df = pd.DataFrame() st.session_state.train_df = pd.DataFrame() st.session_state.desc_dict = {} st.session_state.X_desc_test = np.array([]) st.session_state.test_file = pd.DataFrame() #logo = Image.open('novalix_logo_small.png') #st.image(logo, use_column_width=False) st.write(""" ## Nova-ML: Machine Learning for Molecular Data Build ML models or perform inference using your own csv data """) st.write("""#### What do you want to do?""") class mod_builder: #initialize bools objective = None column_name_check = False valid_csv = False filter_csv=False plot_scatter = False get_benchmark = False get_unwanted_smarts = False get_physchem_limits = False get_output_file = False visualize = False #plot_benchmark = False improve_model = False proceed_step2 = False proceed_step3 = False proceed_step4 = False proceed_step4 = False proceed_step5 = False run_bayesian_opt = False plot_histogram = False task = None split_type = None binary_threshold = None show_advanced_viz = False mod_builder.objective = st.radio( "", ["Train ML models", "Predict properties"], captions = ["Train your own ML model (Random Forest, LightGBM, XGBoost)", "Get predictions for your compounds using a pre-built model"],on_change=restart_session_state) st.markdown("""**Note:** This is a simplified user-friendly machine learning workflow with no coding/programming required. To professionally build your ML models or explore more advanced deep learning models (e.g. chemprop D-MPNN, CNN, Transformers), contact our cheminformatics team.""") def plot_scatter_regr(df,x='observed',y='predicted',color="set",color_continuous_scale=None): fig = px.scatter(df, x=x, y=y,color=color,color_continuous_scale=color_continuous_scale) #fig_scatter.add_traces(list(px.line(df_scatter,x='observed',y='observed').select_traces())) parity_line = go.Scatter( x=[df[x].min()-np.abs(0.1*df[x].min()), df[x].max()+np.abs(0.15*df[x].max())], y=[df[y].min()-np.abs(0.1*df[y].min()), df[y].max()+np.abs(0.15*df[y].max())], mode='lines', name='Parity Line', line=dict(color='green', width=2)) fig.add_trace(parity_line) fig.update_layout(xaxis=dict(scaleanchor="x", titlefont=dict(size=18,color='black'), scaleratio=1,constrain="domain"), # Lock aspect ratio yaxis=dict(scaleanchor="x",titlefont=dict(size=18,color='black'), scaleratio=1,constrain="domain"), # Lock aspect ratio height=500) return fig def plot_scatter_clf(df,x='observed',y='predicted',color="set"): df[x] = df[x].astype(bool) fig = px.strip(df, x=x, y=y,color=color) fig.update_traces(opacity=.8) fig.update_xaxes(categoryorder='array', categoryarray= [False,True]) fig.update_layout( title="", xaxis_title="Experimental Classification (positive class)", yaxis_title="Probability for Positive Class", legend_title="Set", font=dict(family="Arial",size=18,color="RebeccaPurple")) fig.add_hrect(y0=0.490,y1=0.501) fig.add_vrect(x0=0.490,x1=0.501) annot_good = dict(color="#388E3C") annot_bad = dict(color="#B71C1C") fig.add_annotation(x=0, y=-0.1,text="True Negative",showarrow=False,arrowhead=1,font=annot_good) fig.add_annotation(x=1, y=-0.1,text="False Negative",showarrow=False,arrowhead=1,font=annot_bad) fig.add_annotation(x=1, y=1.1,text="True Positive",showarrow=False,arrowhead=1,font=annot_good) fig.add_annotation(x=0, y=1.1,text="False Positive",showarrow=False,arrowhead=1,font=annot_bad) return fig if mod_builder.objective == "Train ML models": st.markdown('---') st.write("""#### Step 1: Upload your CSV file containing SMILES and the target variable """) csv_file = st.file_uploader('Select File',type='.csv',on_change=restart_session_state) df = pd.DataFrame() if csv_file: if len(df) <= 10000: df = pd.read_csv(csv_file,engine='python') else: df = pd.read_csv(csv_file).sample(n=10000).reset_index(drop=True) col_1_1, col_1_2 = st.columns([3,3],gap='medium') with col_1_1: smi_col = st.selectbox("""**Select SMILES column** Text representation of your molecules""",tuple(df.columns)) with col_1_2: y_col = st.selectbox("""**Select Y column** The target variable to predict, must be a number""", tuple([i for i in df.columns if i not in smi_col])) mod_builder.column_name_check = st.checkbox("I confirm, the column names are correct.",on_change=restart_session_state) if mod_builder.column_name_check : st.session_state.input_df = df #Check SMILES validity if st.session_state.input_df[smi_col].dtype == 'object': #st.write("Checking for invalid SMILES") canon_smi = [] for i,smi in enumerate(st.session_state.input_df[smi_col]): try: canon = Chem.MolToSmiles(Chem.MolFromSmiles(smi,sanitize=True)) canon_smi.append(canon) except: canon_smi.append(np.nan) st.error(f"Invalid SMILES {smi} with index {i} invalid and was removed") # st.session_state.input_df[smi_col] = canon_smi st.session_state.input_df = st.session_state.input_df.dropna(subset=smi_col).reset_index(drop=True) else: st.error("**❗Error:** Please check that the smiles column contain only SMILES") #Check target column validity if is_numeric_dtype(st.session_state.input_df[y_col]): df[y_col] = df[y_col].astype(float) st.session_state.input_df[y_col] = df[y_col] else: st.markdown('######') st.error("**❗Error:** Target variable **must be a number**, if it is categorical, modify the CSV file and convert it to 0 and 1") if st.session_state.input_df[smi_col].dtype == 'object' and is_numeric_dtype(st.session_state.input_df[y_col]): st.success("✅ Input CSV file read successfully!") tab_1_1, tab_1_2= st.tabs(["Molecule View", "Table"]) with tab_1_1: #st.session_state.input_df["SMILES"] = st.session_state.input_df[smi_col] raw_html = mols2grid.display(st.session_state.input_df, subset=[y_col], smiles_col=smi_col, tooltip=[i for i in st.session_state.input_df.columns.to_list() if i not in [smi_col,"smiles","SMILES","canon_smi"] ], size=(208,104))._repr_html_() components.html(raw_html, height=104*7, scrolling=True) with tab_1_2: st.dataframe(st.session_state.input_df,height=104*7) if mod_builder.column_name_check and is_numeric_dtype(st.session_state.input_df[y_col]) : col_1_4, col_1_5 = st.columns(2,gap='medium') with col_1_4: task = st.radio("**Select a Task**", ["Regression", "Binary Classification"], captions = ["**Predict numerical values (when your data is quantitative / low noise)**", "**Predict classes/categories (when your data is qualitative / high noise)**"],on_change=restart_session_state) if task == "Binary Classification": mod_builder.binary_threshold = st.slider('Binary Classification threshold (used to define class labels "low" or "high")', df[y_col].min(), df[y_col].max(), df[y_col].median()) mod_builder.plot_histogram = st.checkbox("Validate threshold and plot histogram") if mod_builder.plot_histogram: #nbins = st.slider('Number of Histogram Bins', 2, 50, 20) if task == "Binary Classification": df = st.session_state.input_df df['cat'] = df[y_col] > mod_builder.binary_threshold df['classification'] = df['cat'].apply(lambda x: "high" if x == True else "low" ) frac_positive = len(df[df.cat == 1])/len(df)*100 st.write(f"Number of positive class: {frac_positive:.1f}% of {len(df)}") fig = px.histogram(df, x=y_col, hover_data=df.columns,nbins=20,color='classification') else: fig = px.histogram(df, x=y_col, hover_data=df.columns,nbins=20) st.plotly_chart(fig) if mod_builder.plot_histogram: mod_builder.proceed_step2 = st.checkbox("Proceed to Step 2") else: print("Waiting for CSV file") if mod_builder.proceed_step2 == True: st.markdown('---') st.write("""#### Step 2: Select Descriptors, Algorithms, and Data Splits """) col_2_1, col_2_2 = st.columns(2,gap='medium') with col_2_1: desc_choice = st.multiselect( "Choose your Descriptors", ["ECFP4", "FCFP4","RDKIT"], ["ECFP4","FCFP4","RDKIT"],on_change=restart_session_state) with col_2_2: algo_choice = st.multiselect( "Choose your Algorithms", ["RandomForest", "LightGBM", "XGBoost","ExtraTrees","Consensus"], ["LightGBM","XGBoost"],on_change=restart_session_state) st.write("Parameterize your data splits") col_2_3, col_2_4 = st.columns(2,gap='medium') with col_2_3: test_size = st.slider('Test Size (Default is 20%)', 0, 100, 20) with col_2_4: CV_split = st.slider('Number of Cross-validation Cycles (Default is 10)',5,30,10,step=5) mod_builder.split_type = st.radio( "", ["Random Split", "Scaffold Split"], captions = ["Test/Validation sets will be randomly selected", "Test/Validation sets will not contain the same scaffolds as the training set"],on_change=restart_session_state) mod_builder.proceed_step3 = st.checkbox("Proceed to Step 3") if mod_builder.proceed_step3 == True: st.markdown('---') st.write("""#### Step 3: Evaluate Model Performance """) smi_series = st.session_state.input_df[smi_col] if task == "Binary Classification": #if 'cat' in st.session_state.input_df: target_y = st.session_state.input_df['cat'] else: target_y = st.session_state.input_df[y_col] #@st.cache_data # Train models (this step will run every time changes are made) if len(st.session_state.train_df) == 0: with st.spinner('Training your machine learning models...'): st.session_state.train_df, st.session_state.data_obj, st.session_state.cv_df = benchmark_model(smi_series=smi_series, y=target_y, desc_list=desc_choice, model_name=algo_choice,task=task,test_size=test_size, CV_split=CV_split, split_type=mod_builder.split_type) def plot_barplot(df,x='desc',y='test_roc_auc',xlabel="descriptor",ylabel="AUC",xlim=None,ylim=None,title='Title'): color = "desc" if x=="algo" else "algo" if title=="Cross-validation Scores": fig = px.bar(df,x=x,y=y,error_y=f"{y}_std",color=color,barmode='group',height=500,title=title) else: fig = px.bar(df,x=x,y=y,color=color,barmode='group',height=500,title=title) fig.update_yaxes(range=ylim,linewidth=3,tickwidth=3) fig.update_xaxes(range=xlim,linewidth=3,tickwidth=3) fig.update_layout( title="",xaxis_title=xlabel,yaxis_title=ylabel, font=font_style) fig.update_layout( title={ 'text': title, 'y':0.9, 'x':0.4, 'xanchor': 'center', 'yanchor': 'top'}) return fig def plot_boxplot(df,x='desc',y='test_roc_auc',xlabel="descriptor",ylabel="AUC",xlim=None,ylim=None): color = "desc" if x=="algo" else "algo" fig = px.box(df, x=x, y=y, color=color, notched=False, # used notched shape hover_data=[y] # add day column to hover data ) def get_p_value(df,color,x_i): n_subgroup = len(df[color].unique()) if n_subgroup == 2: subgroups = [df[df[x] == x_i][df[color] == color_i][y].values for color_i in df[color].unique()] p_value = wilcoxon(subgroups[0],subgroups[1]).pvalue elif n_subgroup >= 3: subgroups = [df[df[x] == x_i][df[color] == color_i][y].values for color_i in df[color].unique()] p_value = friedmanchisquare(*subgroups).pvalue else: p_value = np.nan return p_value annotations = [ dict( x=x_i, y=1.1*df[df[x] == x_i][y].max(), text=f"p={get_p_value(df,color,x_i):.3f}", showarrow=False, arrowhead=0 ) for i,x_i in enumerate(df[x].unique()) ] fig.update_layout(annotations=annotations,font=dict(size=13,color="RebeccaPurple")) fig.update_yaxes(range=ylim,linewidth=3,tickwidth=3,title_font=dict(size=12)) fig.update_xaxes(range=xlim,linewidth=3,tickwidth=3,title_font=dict(size=15),tickfont = dict(size=15,color='black')) return fig def get_signplot(df,x='desc',y='test_AUC'): num_subplots = len(np.unique(df[x])) fig, axes = plt.subplots(1, num_subplots, figsize=(9, 3),layout="constrained") # Populate each subplot for i,x_i in enumerate(df[x].unique()): ax = axes[i] group_col = "desc" if x=="algo" else "algo" df_i = df[df[x] == x_i ] n_cv = df_i[df_i[group_col] == np.unique(df_i[group_col])[0]].shape[0] n_groups = len(np.unique(df_i[group_col])) df_i["cv_cycle"] = df_i.groupby([group_col]).cumcount() + 1 sns.set(rc={'figure.figsize': (3, 3)}, font_scale=1) heatmap_args = {'linewidths': 0.25, 'linecolor': '0.5', 'clip_on': True, 'square': True} pc = sp.posthoc_conover_friedman(df_i, y_col=y, group_col=group_col, block_col="cv_cycle", p_adjust="holm",melted=True) sub_ax, sub_c = sp.sign_plot(pc, **heatmap_args, ax=axes[i],cbar_ax_bbox=[1.05, 0.5, 0.02, 0.3]) sub_ax.set_title(x_i) plt.subplots_adjust(wspace=3) return fig tab_3_0, tab_3_1= st.tabs(["Plots","Table"]) with tab_3_0: col_3_4a, col_3_4b = st.columns(2,gap='large') with col_3_4a: metric_options = [i.strip('Test_') for i in st.session_state.train_df.columns if (i.startswith('Test') and not (i.endswith('std')) and not i.endswith('tp)'))] metric_selected = st.selectbox("**Select a metric to plot**",metric_options) with col_3_4b: groupby_selected = st.selectbox("**Group by:**",["Descriptors","Algorithm"]) groupby_dict = {"Descriptors": "desc", "Algorithm":"algo"} ybar_min = 0.6*np.min([st.session_state.train_df[f'Val_{metric_selected}'].min()-st.session_state.train_df[f'Val_{metric_selected}_std'].max(), st.session_state.train_df[f'Test_{metric_selected}'].min()-st.session_state.train_df[f'Val_{metric_selected}_std'].max()]) ybar_max = 1.03*np.max([st.session_state.train_df[f'Val_{metric_selected}'].max()+st.session_state.train_df[f'Val_{metric_selected}_std'].max(), st.session_state.train_df[f'Test_{metric_selected}'].max()+st.session_state.train_df[f'Val_{metric_selected}_std'].max()]) st.write("""##### """) st.write("""##### Evaluate Model Performance """) st.write("If the **Cross-validation Scores** are so much better than the **Test Scores**, it might be a sign of **overfitting.**") col_3_0a, col_3_0b = st.columns(2,gap='large') with col_3_0a: st.plotly_chart(plot_barplot(st.session_state.train_df,x=groupby_dict[groupby_selected],y=f'Val_{metric_selected}',xlabel="descriptor",ylabel=f'{metric_selected}',ylim=[ybar_min,ybar_max],title="Cross-validation Scores"),theme='streamlit',use_container_width=True) with col_3_0b: st.plotly_chart(plot_barplot(st.session_state.train_df,x=groupby_dict[groupby_selected],y=f'Test_{metric_selected}',xlabel="descriptor",ylabel=f'{metric_selected}',ylim=[ybar_min,ybar_max],title="Test Scores"),theme='streamlit',use_container_width=True) mod_builder.show_advanced_viz = st.checkbox("Show Advanced **Statistical Analysis & Visualization** (Box plot, Sign plot and Scatter plots)") if mod_builder.show_advanced_viz: #ybar_min,ybar_max=0,1 st.write("""##### """) st.write("""##### Box Plot of Validation Scores""") st.write("If p < 0.05, a **statistically significant** difference exists **within** the group. Otherwise, the observed variations are not significant." ) st.write("It uses Friedman’s test for k > 2 or Wilcoxon's test for k = 2." ) st.plotly_chart(plot_boxplot(st.session_state.cv_df,x=groupby_dict[groupby_selected],y=f'test_{metric_selected}',xlabel="descriptor",ylabel=f'{metric_selected}',ylim=[None,None]),theme='streamlit',use_container_width=True) show_signplot = st.checkbox("Show Pairwise Conover-Friedman Signplot",value=False) if show_signplot: st.write("""##### """) st.write("""##### Pairwise Conover-Friedman test""") st.write("This will tell us which pairs have a significant difference (NS = non-significant)." ) try: fig = get_signplot(st.session_state.cv_df,x=groupby_dict[groupby_selected],y=f'test_{metric_selected}') st.pyplot(fig) except Exception as e: print(e) print("Cannot generate Conover-Friedman plot") #with tab_3_2: def prepare_scatter_regr(train_df,algo_select,desc_select,data_obj): X_train = data_obj[desc_select].X_train X_test = data_obj[desc_select].X_test model = train_df[train_df.desc == desc_select][train_df.algo == algo_select ].model_obj.values[0] y_train_pred = model.predict(X_train) y_test_pred = model.predict(X_test) y_train = data_obj[desc_select].y_train y_test = data_obj[desc_select].y_test df_scatter = pd.DataFrame({"observed": list(y_train) + list(y_test), "predicted": list(y_train_pred) + list(y_test_pred), "set": ["train" for i in range(len(y_train))] + ["test" for i in range(len(y_test))] }) train_perf = eval_predictions(y_train,y_train_pred,task="Regressor",thresh=None) test_perf = eval_predictions(y_test,y_test_pred,task="Regressor",thresh=None) return df_scatter, train_perf,test_perf def prepare_scatter_clf(train_df,algo_select,desc_select,data_obj): X_train = data_obj[desc_select].X_train X_test = data_obj[desc_select].X_test model = train_df[train_df.desc == desc_select][train_df.algo == algo_select ].model_obj.values[0] y_train_pred = model.predict_proba(X_train)[:,1] y_test_pred = model.predict_proba(X_test)[:,1] y_train = data_obj[desc_select].y_train y_test = data_obj[desc_select].y_test df_scatter = pd.DataFrame({"observed": list(y_train) + list(y_test), "predicted": list(y_train_pred) + list(y_test_pred), "set": ["train" for i in range(len(y_train))] + ["test" for i in range(len(y_test))] }) train_perf = eval_predictions(y_train,y_train_pred,task="Classifier",thresh=mod_builder.binary_threshold,y_transformed_binary=True) test_perf = eval_predictions(y_test,y_test_pred,task="Classifier",thresh=mod_builder.binary_threshold,y_transformed_binary=True) return df_scatter, train_perf,test_perf show_scatter= st.checkbox("Show Scatter Plot",value=False) if show_scatter: col_3_3, col_3_4 = st.columns(2,gap='large') with col_3_3: algo_to_plot = st.selectbox("**Select the algorithm to plot**",np.flip(np.unique(st.session_state.train_df.algo))) with col_3_4: desc_to_plot = st.selectbox("**Select the descriptor to plot**",np.flip(np.unique(st.session_state.train_df.desc))) col_3_5, col_3_6 = st.columns([1,1],gap='large') with col_3_5: if task == "Regression": df_scatter,train_perf, test_perf = prepare_scatter_regr(st.session_state.train_df,algo_to_plot,desc_to_plot,st.session_state.data_obj) st.plotly_chart(plot_scatter_regr(df_scatter),key="smiles",on_select="rerun",theme="streamlit", use_container_width=True,height=600) else: df_scatter,train_perf, test_perf = prepare_scatter_clf(st.session_state.train_df,algo_to_plot,desc_to_plot,st.session_state.data_obj) #st.dataframe(df_scatter) st.plotly_chart(plot_scatter_clf(df_scatter),key="smiles",on_select="rerun",theme="streamlit", use_container_width=True,height=600) with col_3_6: st.write("""###""") st.write("**Train Performance**") st.dataframe(train_perf,use_container_width=False) st.write("**Test Performance**") st.dataframe(test_perf,use_container_width=False) with tab_3_1: st.write("""##### Summary """) st.dataframe(st.session_state.train_df[[col for col in st.session_state.train_df.columns if col != 'model_obj']],use_container_width=True) st.write("""##### Cross-validation Performance""") st.dataframe(st.session_state.cv_df) mod_builder.proceed_step4 = st.checkbox("Proceed to Save the models") if mod_builder.proceed_step4: import joblib import pickle import base64 st.markdown('---') st.write("""#### Step 4: Save the models for future use """) st.write("Select the best combination of algorithm and descriptors based on the analysis above.") def save_models(df,desc=desc_choice,algo=algo_choice): #df containing the result of benchmark model function mod = df[df['algo'] == algo][df['desc'] == desc].model_obj.values[0] return mod def get_date(): now = datetime.datetime.now() return now.strftime('%d-%m-%y') col_4_1, col_4_2 = st.columns(2,gap='large') with col_4_1: algo_to_save = st.selectbox("**Select the algorithm to save**",np.flip(np.unique(st.session_state.train_df.algo))) with col_4_2: desc_to_save = st.selectbox("**Select the descriptor to save**",np.flip(np.unique(st.session_state.train_df.desc))) output_name = st.text_input("Give a useful filename", "My_Model") def download_model(model,output_name): #output_model = pickle.dumps(model) #b64 = base64.b64encode(output_model).decode() #href = f'Download Trained Model .pkl File' #st.markdown(href, unsafe_allow_html=True) joblib.dump(model,"./temp_model_download.joblib") confirm_model_choice = st.checkbox("I confirm my selected model to download.",value=False) if confirm_model_choice: try: download_model(model=save_models(st.session_state.train_df,desc_to_save,algo_to_save),output_name=output_name) with open("./temp_model_download.joblib", "rb") as fp: btn = st.download_button( label="Download as **JOBLIB**", data=fp, file_name=f'./{output_name}_{algo_to_save.replace(" ", "")}_{desc_to_save}_{get_date()}.joblib', mime="application/joblib") except Exception as e: st.error(e) st.error("Failed to save the model. Try to restart training") st.button("Restart Training",on_click=restart_session_state) #restart_session_state() #st.rerun() #try: # #except Exception as e: # st.write("Model saving failed, trying to restart") # #restart_session_state() # st.rerun() # st.write(e) ################################################################ # Predictor ################################################################ import joblib class mod_predicter: proceed_step2 = False proceed_step3 = False desc_choice = "ECFP4" test_results = pd.DataFrame() model_file = None if mod_builder.objective == "Predict properties": st.markdown('---') st.write("""#### Step 1: Upload your model (.joblib)""") mod_predicter.model_file = st.file_uploader('Select Model File',type=['.pkl','.joblib'],on_change=restart_session_state) if mod_predicter.model_file: col_5_1, col_5_2 = st.columns(2,gap='medium') with col_5_1: model = joblib.load(mod_predicter.model_file) from sklearn.base import is_classifier, is_regressor task_type = "Classifier" if is_classifier(model.named_steps["estimator"]) else "Regressor" desc_list = ["ECFP4", "FCFP4", "RDK5","RDKIT"] mod_predicter.desc_choice = [desc for desc in desc_list if desc in mod_predicter.model_file.name.split('_')][0] if mod_predicter.desc_choice: st.write(f"The model is a **{task_type}** using the **{mod_predicter.desc_choice}** descriptor") else: st.write("Could not automatically identify the molecular features used. Please specify") mod_predicter.desc_choice = st.selectbox("Select descriptor used", tuple(["ECFP4", "FCFP4", "RDK5","RDKIT"])) var_pred = st.text_input("Give the name of the variable to be predicted", "My_Property") mod_predicter.proceed_step2 = st.checkbox("I confirm that the model file and the target variable name is correct.") if mod_predicter.model_file and mod_predicter.proceed_step2: st.markdown('---') st.write("""#### Step 2: Upload your CSV file containing SMILES (.csv)""") test_file = st.file_uploader('Select CSV File with SMILES',type='.csv',on_change=restart_session_state) if test_file: try: df_test = pd.read_csv(test_file) #,engine='python',encoding='unicode_escape' except Exception as e: st.write(e) encoding_list = ['utf_8','ascii', 'big5', 'big5hkscs', 'cp037', 'cp273', 'cp424', 'cp437', 'cp500', 'cp720', 'cp737' , 'cp775', 'cp850', 'cp852', 'cp855', 'cp856', 'cp857', 'cp858', 'cp860', 'cp861', 'cp862' , 'cp863', 'cp864', 'cp865', 'cp866', 'cp869', 'cp874', 'cp875', 'cp932', 'cp949', 'cp950' , 'cp1006', 'cp1026', 'cp1125', 'cp1140', 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254' , 'cp1255', 'cp1256', 'cp1257', 'cp1258', 'euc_jp', 'euc_jis_2004', 'euc_jisx0213', 'euc_kr' , 'gb2312', 'gbk', 'gb18030', 'hz', 'iso2022_jp', 'iso2022_jp_1', 'iso2022_jp_2' , 'iso2022_jp_2004', 'iso2022_jp_3', 'iso2022_jp_ext', 'iso2022_kr', 'latin_1', 'iso8859_2' , 'iso8859_3', 'iso8859_4', 'iso8859_5', 'iso8859_6', 'iso8859_7', 'iso8859_8', 'iso8859_9' , 'iso8859_10', 'iso8859_11', 'iso8859_13', 'iso8859_14', 'iso8859_15', 'iso8859_16', 'johab' , 'koi8_r', 'koi8_t', 'koi8_u', 'kz1048', 'mac_cyrillic', 'mac_greek', 'mac_iceland', 'mac_latin2' , 'mac_roman', 'mac_turkish', 'ptcp154', 'shift_jis', 'shift_jis_2004', 'shift_jisx0213', 'utf_32' , 'utf_32_be', 'utf_32_le', 'utf_16', 'utf_16_be', 'utf_16_le', 'utf_7', 'utf_8_sig'] for encoding in encoding_list: worked = False while worked == False: for encoding in encoding_list: try: df_test = pd.read_csv(test_file, encoding=encoding, nrows=5,engine='python') if df_test is not None: worked = True df_test = pd.read_csv(test_file, encoding=encoding,engine='python') except: worked = False df_test = pd.DataFrame() col_6_1, col_6_2 = st.columns(2,gap='medium') with col_6_1: if len(df_test) > 0: smi_col_test = st.selectbox("Select SMILES column of the CSV file",tuple(df_test.columns)) st.session_state.test_file = df_test if var_pred in df_test.columns: var_pred = f"{var_pred}_pred" if st.session_state.test_file[smi_col_test].dtype == 'object': #st.write("Checking for invalid SMILES") canon_smi = [] for i,smi in enumerate(st.session_state.test_file[smi_col_test]): try: canon = Chem.MolToSmiles(Chem.MolFromSmiles(smi,sanitize=True)) canon_smi.append(canon) except: canon_smi.append(np.nan) st.write(f"Invalid SMILES {smi} with index {i} was removed") # st.session_state.test_file[smi_col_test] = canon_smi st.session_state.test_file = st.session_state.test_file.dropna(subset=smi_col_test).reset_index(drop=True) error_in_smiles = False else: st.write("**Error:** Please check that the smiles column contain only SMILES") error_in_smiles = True test_exp = st.checkbox("I have the true experimental values to compare with the model predictions") error_in_y_exp = False if test_exp: y_exp = st.selectbox("Select the column with the experimental target value",tuple([i for i in st.session_state.test_file.columns if i != smi_col_test])) if is_numeric_dtype(st.session_state.test_file[y_exp]): st.session_state.test_file[y_exp] = st.session_state.test_file[y_exp].astype(float) else: st.markdown('######') st.write("**Error:** Target variable **must be a number**, if it is categorical, modify the CSV file and convert it to 0 and 1") error_in_y_exp = True if not error_in_smiles and not error_in_y_exp: mod_predicter.proceed_step3 = st.checkbox("Run Predictions!") if mod_predicter.proceed_step3: model = joblib.load(mod_predicter.model_file) with st.spinner('Calculating Descriptors...'): if len(st.session_state.X_desc_test) == 0: st.session_state.X_desc_test = get_desc(smi_series=st.session_state.test_file[smi_col_test],desc_name=mod_predicter.desc_choice) with st.spinner('Performing Inference...'): if var_pred not in st.session_state.test_file.columns: try: if task_type == "Classifier": st.session_state.test_file[var_pred] = model.predict_proba(st.session_state.X_desc_test)[:,1] else: st.session_state.test_file[var_pred] = model.predict(st.session_state.X_desc_test) if test_exp: st.session_state.test_file["label"] = [f"exp: {exp:.2f} | pred: {pred:.2f}" for exp,pred in zip(st.session_state.test_file[y_exp],st.session_state.test_file[var_pred]) ] else: st.session_state.test_file["label"] = [f"pred: {pred:.2f}" for pred in st.session_state.test_file[var_pred] ] except Exception as e: st.write(e) st.write("Error. Please check for INVALID SMILES or if the descriptor used was the correct one") st.markdown('---') st.write("""#### Step 3: View and Analyze Results""") st.session_state.test_file["SMILES"] = st.session_state.test_file[smi_col_test] if task_type == "Classifier": st.write("For binary classification, the model predicts the probabilities for the positive class.") #st.write("To save the results, click on the **[⋮]** at the upper right") tab_6_1, tab_6_2= st.tabs(["Molecule View", "Table"]) with tab_6_1: raw_html = mols2grid.display(st.session_state.test_file, subset=["label"] if test_exp else [var_pred], tooltip=[i for i in st.session_state.test_file.columns.to_list() if i not in ["smiles","SMILES","canon_smi"] ], smiles_col=smi_col_test,size=(208,104))._repr_html_() components.html(raw_html, height=700, scrolling=True) with tab_6_2: st.dataframe(st.session_state.test_file) @st.cache_data def convert_df(df): return df.to_csv(index=False).encode('utf-8') if len(st.session_state.test_file) > 0: st.download_button( "**Download CSV**",convert_df(st.session_state.test_file),"file.csv","text/csv",key='download-csv') if test_exp: st.write("""#### Model Performance Statistics""") col_7_1, col_7_2 = st.columns(2,gap='large') with col_7_1: if task_type == "Classifier": thresh_clf = st.slider('Threshold for Positive Class that you have used in model training (Default is median)', st.session_state.test_file[y_exp].min(), st.session_state.test_file[y_exp].max(), st.session_state.test_file[y_exp].median()) y_transformed_binary = True if len(np.unique(st.session_state.test_file[y_exp])) == 2 else False perf_stats = eval_predictions(st.session_state.test_file[y_exp],st.session_state.test_file[var_pred],task=task_type,thresh=thresh_clf,y_transformed_binary=y_transformed_binary) st.session_state.test_file["exp_class"] = st.session_state.test_file[y_exp] > thresh_clf st.plotly_chart(plot_scatter_clf(st.session_state.test_file,x="exp_class",y=var_pred,color="exp_class")) if task_type == "Regressor": st.session_state.test_file["|error|"] = np.abs(st.session_state.test_file[y_exp].values - st.session_state.test_file[var_pred].values) st.plotly_chart(plot_scatter_regr(st.session_state.test_file,x=y_exp,y=var_pred,color="|error|",color_continuous_scale="Portland")) perf_stats = eval_predictions(st.session_state.test_file[y_exp],st.session_state.test_file[var_pred],task=task_type,thresh=None) with col_7_2: st.write("""##""") st.write("""##""") st.dataframe(perf_stats) #st.write("""#### Step 4: View Results""") #model.predict()