--- a +++ b/Random Forest.ipynb @@ -0,0 +1,457 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h1 align=\"center\">Machine learning-based prediction of early recurrence in glioblastoma patients: a glance towards precision medicine <br><br>[Random Forest]</h1>" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>[1] Library</h2>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# OS library\n", + "import os\n", + "import sys\n", + "import argparse\n", + "import itertools\n", + "import random\n", + "\n", + "# Analysis\n", + "import numpy as np\n", + "import pandas as pd\n", + "import seaborn as sns\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Sklearn\n", + "from boruta import BorutaPy\n", + "from sklearn.preprocessing import LabelEncoder\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.ensemble import RandomForestClassifier\n", + "from sklearn.metrics import confusion_matrix, f1_score, recall_score, classification_report, accuracy_score, auc, roc_curve\n", + "from sklearn.model_selection import RandomizedSearchCV\n", + "\n", + "import scikitplot as skplt\n", + "from imblearn.over_sampling import RandomOverSampler, SMOTENC, SMOTE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>[2] Exploratory data analysis and Data Preprocessing</h2>" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4>[-] Load the database</h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "file = os.path.join(sys.path[0], \"db.xlsx\")\n", + "db = pd.read_excel(file)\n", + "\n", + "print(\"N° of patients: {}\".format(len(db)))\n", + "print(\"N° of columns: {}\".format(db.shape[1]))\n", + "db.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4>[-] Drop unwanted columns + create <i>'results'</i> column</h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = db.drop(['Name_Surname','...'], axis = 'columns')\n", + "\n", + "print(\"Effective features to consider: {} \".format(len(df.columns)-1))\n", + "print(\"Creating 'result' column...\")\n", + "\n", + "# 0 = No relapse\n", + "df.loc[df['PFS'] > 6, 'result'] = 0\n", + "\n", + "# 1 = Early relapse (within 6 months)\n", + "df.loc[df['PFS'] <= 6, 'result'] = 1\n", + "\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4>[-] Check for class imbalance in the <i>'results'</i> column </h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"PFS Overview\")\n", + "print(df.result.value_counts())\n", + "\n", + "df.result.value_counts().plot(kind='pie', autopct='%1.0f%%', colors=['skyblue', 'orange'], explode=(0.05, 0.05))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4>[-] Label encoding of the categorical variables </h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df['sex'] =df['sex'].astype('category')\n", + "\n", + "#df['Ki67'] =df['Ki67'].astype(int)\n", + "df['MGMT'] =df['MGMT'].astype('category')\n", + "df['IDH1'] =df['IDH1'].astype('category')\n", + "\n", + "df['side'] =df['side'].astype('category')\n", + "df['ependima'] =df['ependima'].astype('category')\n", + "df['cc'] =df['cc'].astype('category')\n", + "df['necrotico_cistico'] =df['necrotico_cistico'].astype('category')\n", + "df['shift'] =df['shift'].astype('category')\n", + "\n", + "## VARIABLE TO ONE-HOT-ENCODE\n", + "df['localization'] =df['localization'].astype(int)\n", + "df['clinica_esordio'] =df['clinica_esordio'].astype(int)\n", + "df['immediate_p_o'] =df['immediate_p_o'].astype(int)\n", + "df['onco_Protocol'] =df['onco_Protocol'].astype(int)\n", + "\n", + "df['result'] =df['result'].astype(int)\n", + "\n", + "dummy_v = ['localization', 'clinica_esordio', 'onco_Protocol', 'immediate_p_o']\n", + "\n", + "df = pd.get_dummies(df, columns = dummy_v, prefix = dummy_v)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4>[-] Evaluate variables' correlation with <u>'PFS'</u> columns </h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "corr = df.corr()\n", + "ax = sns.heatmap(\n", + " corr, \n", + " vmin=-1, vmax=1, center=0,\n", + " cmap=sns.diverging_palette(20, 220, n=200),\n", + " square=True\n", + ")\n", + "ax.set_xticklabels(\n", + " ax.get_xticklabels(),\n", + " rotation=60,\n", + " horizontalalignment='right'\n", + ");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h2>[3] Prediction Models</h2>" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4> [-] Training and testing set splitting</h4>" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'df' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m<ipython-input-5-48cdcc32916c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mtarget\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'result'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0minputs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdrop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'result'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'PFS'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'columns'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'df' is not defined" + ] + } + ], + "source": [ + "target = df['result']\n", + "inputs = df.drop(['result', 'PFS'], axis = 'columns')\n", + "x_train, x_test, y_train, y_test = train_test_split(inputs['...'],target,test_size=0.20, random_state=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4> [-] BORUTA Features Selection</h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x = x_train.values\n", + "y = y_train.values\n", + "y = y.ravel()\n", + "\n", + "rf_boruta = RandomForestClassifier(n_jobs=-1, class_weight={0:1, 1:3}, max_depth=3)\n", + "feat_selector = BorutaPy(rf_boruta, n_estimators='auto', verbose=0, random_state=42, perc='...')\n", + "feat_selector.fit(x, y)\n", + "\n", + "cols = inputs.columns[feat_selector.support_]\n", + "print(\"N° of selected features: {}\".format(len(cols)))\n", + "print(cols) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4> [-] Random Grid Search Hyperparameter tuning</h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The function to measure the quality of a split\n", + "criterion = ['gini', 'entropy']\n", + "\n", + "# Number of trees in random forest\n", + "n_estimators = [int(x) for x in np.linspace(start = 20, stop = 50, num = 5)]\n", + "\n", + "# Number of features to consider at every split\n", + "max_features = ['auto', 'sqrt']\n", + "\n", + "# Maximum number of levels in tree\n", + "max_depth = [int(x) for x in np.linspace(14, 30, num = 2)]\n", + "max_depth.append(None)\n", + "\n", + "# Minimum number of samples required to split a node\n", + "min_samples_split = [ 2, 3, 4, 5, 8]\n", + "\n", + "# Minimum number of samples required at each leaf node\n", + "min_samples_leaf = [1, 2, 3, 4, 5, 6]\n", + "\n", + "max_leaf_nodes = [None, 2, 3, 4, 5, 6]\n", + "# Method of selecting samples for training each tree\n", + "bootstrap = [True, False]\n", + "\n", + "random_grid = {'criterion': criterion,\n", + " 'n_estimators': n_estimators,\n", + " 'max_features': max_features,\n", + " 'max_depth': max_depth,\n", + " 'min_samples_split': min_samples_split,\n", + " 'min_samples_leaf': min_samples_leaf,\n", + " 'max_leaf_nodes': max_leaf_nodes,\n", + " 'bootstrap':bootstrap\n", + " }\n", + "\n", + "# First create the base model to tune\n", + "rf = RandomForestClassifier(random_state=42,\n", + " n_jobs = -1, \n", + " class_weight=class_weight)\n", + "\n", + "# Random search of parameters, using 5 fold cross validation, different combinations, and use all available cores\n", + "rf_random = RandomizedSearchCV(estimator = rf, \n", + " param_distributions = random_grid, \n", + " n_iter = 500, \n", + " cv = 5)\n", + "# Fit the random search model\n", + "rf_random.fit(x_train, y_train)\n", + "rf_random.best_params_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4>[-] SMOTE-NC</h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "smote_nc = SMOTENC(categorical_features=[3,4,10,11], k_neighbors= 3, random_state=42)\n", + "x_smote_train, y_smote_train = smote_nc.fit_resample(x_train, y_train)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4>[-] Random Forest Model</h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "rm_smote = RandomForestClassifier(random_state = 42,\n", + " criterion= '...',\n", + " n_estimators = '...',\n", + " min_samples_split = '...',\n", + " min_samples_leaf = '...',\n", + " max_leaf_nodes = '...',\n", + " max_features = '...',\n", + " max_depth = '...',\n", + " bootstrap = '...')\n", + "\n", + "rm_smote.fit(x_smote_train, y_smote_train)\n", + "print(\"Trained \\n\")\n", + "\n", + "score_rf_smote = rm_smote.score(x_test, y_test)\n", + "print(\"Random Forest accuracy: \", round(score_rf_smote*100,2), \"% \\n\")\n", + "\n", + "y_smote_predicted = rm_smote.predict(x_test)\n", + "cm_rf_smote = confusion_matrix(y_test, y_smote_predicted)\n", + "print(cm_rf_smote, \"\\n\")\n", + "\n", + "false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_smote_predicted)\n", + "roc_auc = auc(false_positive_rate, true_positive_rate)\n", + "\n", + "print('1. The F-1 Score of the model {} \\n '.format(round(f1_score(y_test, y_smote_predicted, average = 'macro'), 2)))\n", + "print('2. The Recall Score of the model {} \\n '.format(round(recall_score(y_test, y_smote_predicted, average = 'macro'), 2)))\n", + "print('3. Classification report \\n {} \\n'.format(classification_report(y_test, y_smote_predicted)))\n", + "print('3. AUC: \\n {} \\n'.format(roc_auc))\n", + "\n", + "tn, fp, fn, tp = cm_rf_smote.ravel()\n", + "\n", + "# Sensitivity, hit rate, Recall, or true positive rate\n", + "tpr = tp/(tp+fn)\n", + "print(\"Sensitivity (TPR): {}\".format(tpr))\n", + "\n", + "# Specificity or true negative rate\n", + "tnr = tn/(tn+fp)\n", + "print(\"Specificity (TNR): {}\".format(tnr))\n", + "\n", + "# Precision or positive predictive value\n", + "ppv = tp/(tp+fp)\n", + "print(\"Precision (PPV): {}\".format(ppv))\n", + "\n", + "# Negative predictive value\n", + "npv = tn/(tn+fn)\n", + "print(\"Negative Predictive Value (NPV): {}\".format(npv))\n", + "\n", + "# False positive rate\n", + "fpr = fp / (fp + tn)\n", + "print(\"False Positive Rate (FPV): {}\".format(fpr))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<h4> [-] Features Importance Plot </h4>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "features = x_train.columns.values\n", + "\n", + "features[0] = 'Age'\n", + "features[6] = 'Tumor volume T1'\n", + "features[7] = 'edema volume'\n", + "features[8] = 'Residual tumor'\n", + "features[9] = 'Pre-operative KPS'\n", + "features[10] = 'Post-operative KPS'\n", + "features[11] = 'Onset neurological symptoms = 1'\n", + "features[12] = 'Oncological protocol = 0'\n", + "features[13] = 'Oncological protocol = 1'\n", + "features[14] = 'Oncological protocol = 2'\n", + "\n", + "indices = np.argsort(importances)\n", + "\n", + "plt.title('Random Forest Classifier Features Importance')\n", + "plt.barh(range(len(indices)), importances[indices], color='g', align='center')\n", + "plt.yticks(range(len(indices)), [features[i] for i in indices])\n", + "plt.xlabel('Relative Importance')\n", + "\n", + "plt.savefig(\"RF Features importance.jpg\", dpi = 400, facecolor='w', edgecolor='w',\n", + " orientation='landscape', papertype=None, format=None,\n", + " transparent=False, bbox_inches='tight', pad_inches=0.3,\n", + " frameon=None)\n", + "\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}