Spaces:
Runtime error
Runtime error
theresaschneider
commited on
Commit
•
4f6fa63
1
Parent(s):
7320efe
feat: ML_final_project files being added to some git
Browse files- app.py +1290 -0
- requirements.txt +7 -0
app.py
ADDED
@@ -0,0 +1,1290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""Copy of finalProjectDaniel
|
3 |
+
|
4 |
+
Automatically generated by Colaboratory.
|
5 |
+
|
6 |
+
Original file is located at
|
7 |
+
https://colab.research.google.com/drive/1Xmu0qEBPBWsUKnRKtCsUn2mmP6R5tkZQ
|
8 |
+
|
9 |
+
# Importing libraries
|
10 |
+
"""
|
11 |
+
|
12 |
+
## Basic imports
|
13 |
+
import matplotlib.pyplot as plt
|
14 |
+
import pandas as pd
|
15 |
+
import numpy as np
|
16 |
+
import tensorflow as tf
|
17 |
+
|
18 |
+
## Specific imports
|
19 |
+
from sklearn.model_selection import train_test_split
|
20 |
+
from sklearn.model_selection import StratifiedShuffleSplit
|
21 |
+
from sklearn.impute import SimpleImputer
|
22 |
+
from sklearn.preprocessing import OneHotEncoder
|
23 |
+
from sklearn import preprocessing
|
24 |
+
from sklearn.base import BaseEstimator, TransformerMixin
|
25 |
+
from sklearn.pipeline import Pipeline
|
26 |
+
from sklearn.preprocessing import StandardScaler
|
27 |
+
from sklearn.compose import ColumnTransformer
|
28 |
+
from sklearn.preprocessing import OrdinalEncoder
|
29 |
+
from sklearn.linear_model import LinearRegression
|
30 |
+
|
31 |
+
"""#Loading the DataSet and Reducing the Features of Interest"""
|
32 |
+
|
33 |
+
# from google.colab import drive
|
34 |
+
# drive.mount('/content/drive/', force_remount=True)
|
35 |
+
# !ls /content/drive/MyDrive/FALL2022/Warfarin_Dose_Prediction_Dataset.xls
|
36 |
+
|
37 |
+
## for theresa to run it
|
38 |
+
from google.colab import drive
|
39 |
+
drive.mount('/content/drive/')
|
40 |
+
!ls /content/drive/MyDrive/Machine Learning/data_final_project.csv
|
41 |
+
|
42 |
+
!pip install --upgrade xlrd
|
43 |
+
|
44 |
+
import pandas as pd
|
45 |
+
# original_df = pd.read_excel('/content/drive/MyDrive/FALL2022/Warfarin_Dose_Prediction_Dataset.xls')
|
46 |
+
|
47 |
+
# for theresa to run it
|
48 |
+
original_df = pd.read_csv('/content/drive/MyDrive/Machine Learning/data_final_project.csv', sep=',')
|
49 |
+
|
50 |
+
original_df.info()
|
51 |
+
|
52 |
+
patients = original_df[['Gender','Race (Reported)', 'Age', 'Height (cm)', 'Weight (kg)', 'Diabetes', 'Simvastatin (Zocor)', 'Amiodarone (Cordarone)',
|
53 |
+
'Target INR', 'INR on Reported Therapeutic Dose of Warfarin', 'Cyp2C9 genotypes',
|
54 |
+
'VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T', 'Therapeutic Dose of Warfarin']].copy()
|
55 |
+
|
56 |
+
patients.head(n=5)
|
57 |
+
|
58 |
+
patients.describe()
|
59 |
+
|
60 |
+
patients.info()
|
61 |
+
|
62 |
+
# patients.to_excel("patients_df_reduced.xlsx")
|
63 |
+
|
64 |
+
"""# Setting aside a validation set right away
|
65 |
+
separates dataset into patients_df (95%) and validation_set (5%)
|
66 |
+
"""
|
67 |
+
|
68 |
+
from sklearn.model_selection import train_test_split
|
69 |
+
|
70 |
+
patients_df, validation_set = train_test_split(patients, test_size=0.05, random_state=42)
|
71 |
+
|
72 |
+
"""# Visualizing Data Features and Correlations on whole dataset (minus validation set)
|
73 |
+
|
74 |
+
###Looking at Numerical Data (note that some of these are numerical catagorical but are entered as 0 or 1)
|
75 |
+
"""
|
76 |
+
|
77 |
+
# Commented out IPython magic to ensure Python compatibility.
|
78 |
+
# %matplotlib inline
|
79 |
+
patients_df.hist(bins=50, figsize=(20,15))
|
80 |
+
plt.show()
|
81 |
+
|
82 |
+
corr_matrix = patients_df.corr()
|
83 |
+
corr_matrix["Therapeutic Dose of Warfarin"].sort_values(ascending=False)
|
84 |
+
|
85 |
+
# note that Target INR and INR on Reported Therapeutic Dose of Warfarin are linearly related. Target INR has so few values that I will remove it as part of pre-processing
|
86 |
+
corr_matrix["Target INR"].sort_values(ascending=False)
|
87 |
+
|
88 |
+
"""### Looking at Catagorical Text Data (Use these catagories for gradio implementation later)"""
|
89 |
+
|
90 |
+
patients_df['Gender'].value_counts()
|
91 |
+
|
92 |
+
patients_df['Age'].value_counts()
|
93 |
+
|
94 |
+
patients_df['Race (Reported)'].value_counts()
|
95 |
+
|
96 |
+
patients_df['Target INR'].value_counts()
|
97 |
+
|
98 |
+
patients_df['Diabetes'].value_counts()
|
99 |
+
|
100 |
+
patients_df['Simvastatin (Zocor)'].value_counts()
|
101 |
+
|
102 |
+
patients_df['Amiodarone (Cordarone)'].value_counts()
|
103 |
+
|
104 |
+
patients_df['Cyp2C9 genotypes'].value_counts()
|
105 |
+
|
106 |
+
patients_df['VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T'].value_counts()
|
107 |
+
|
108 |
+
"""# Dropping any rows that have Nan in the target column ON WHOLE DATASET"""
|
109 |
+
|
110 |
+
# Dropping any rows that have Nan in the target column
|
111 |
+
patients_df.dropna(subset=['Therapeutic Dose of Warfarin'], inplace=True)
|
112 |
+
patients_df.info()
|
113 |
+
|
114 |
+
"""# Dividing Data into Statified Train (80%) and Test Set (20%)
|
115 |
+
This includes minimal pre-processing of gender and weight on the full dataset that was necessary for the statified sampling based on weight
|
116 |
+
Test and Train Sets with features and labels are stored in ;'strat_train_set' and 'strat_test_set'
|
117 |
+
|
118 |
+
patients_df -> strat_train_set, strat_test_set
|
119 |
+
|
120 |
+
### Perform Statified Sampling based on Weight (Chapter 2 Pages 54-55)
|
121 |
+
|
122 |
+
### Dropping Rows with Nan Gender Columns (since there are only 4 of them) -- NEED TO DO BEFORE STAT SAMPLING IN THIS CASE
|
123 |
+
"""
|
124 |
+
|
125 |
+
patients_df.dropna(subset=['Gender'], inplace=True)
|
126 |
+
|
127 |
+
"""#### Replacing Nan values in weight group with median based on Gender as is needed to perform statified sampling for the weight group"""
|
128 |
+
|
129 |
+
## looking at median female weight
|
130 |
+
median_female_weight=patients_df.loc[patients_df['Gender'] == 'female', 'Weight (kg)'].median()
|
131 |
+
median_female_weight
|
132 |
+
|
133 |
+
## looking at median male weight
|
134 |
+
median_male_weight=patients_df.loc[patients_df['Gender'] == 'male', 'Weight (kg)'].median()
|
135 |
+
median_male_weight
|
136 |
+
|
137 |
+
## filling in null weight values on full dataset
|
138 |
+
medians = patients_df.groupby(['Gender'])['Weight (kg)'].median()
|
139 |
+
patients_df = patients_df.set_index(['Gender'])
|
140 |
+
patients_df['Weight (kg)'] = patients_df['Weight (kg)'].fillna(medians)
|
141 |
+
patients_df = patients_df.reset_index()
|
142 |
+
|
143 |
+
patients_df['Weight (kg)'].isna().sum()
|
144 |
+
|
145 |
+
"""#### Creating Weight Catagories from which the test set will sample from"""
|
146 |
+
|
147 |
+
patients_df["weight_cat"] = pd.cut(patients_df["Weight (kg)"], bins=[0, 50, 75, 100, np.inf],
|
148 |
+
labels=[1, 2, 3, 4])
|
149 |
+
patients_df["weight_cat"].hist()
|
150 |
+
|
151 |
+
"""#### Dividing patients_df into strat_train_set (80%) and strat_test_set (20%) distribution"""
|
152 |
+
|
153 |
+
from sklearn.model_selection import StratifiedShuffleSplit
|
154 |
+
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
|
155 |
+
for train_index, test_index in split.split(patients_df, patients_df["weight_cat"]):
|
156 |
+
strat_train_set = patients_df.loc[train_index]
|
157 |
+
strat_test_set = patients_df.loc[test_index]
|
158 |
+
|
159 |
+
"""#### Comparing proportion of samples per weight catagory between test set and original dataset
|
160 |
+
#####(distrbutions are the same, showing that the stratified sampling worked)
|
161 |
+
"""
|
162 |
+
|
163 |
+
strat_test_set["weight_cat"].value_counts() / len(strat_test_set)
|
164 |
+
|
165 |
+
for set_ in (strat_train_set, strat_test_set):
|
166 |
+
set_.drop("weight_cat", axis=1, inplace=True)
|
167 |
+
|
168 |
+
patients_df["weight_cat"].value_counts() / len(patients_df)
|
169 |
+
|
170 |
+
"""## Visualizing Training Set Features and Visualizing Effects of Pre-processing Steps
|
171 |
+
##### (height, weight, and some catagorical variables)--nothing permanent done here--all incorporated into transformer later
|
172 |
+
|
173 |
+
### Visualizing Outliers in Weight Class
|
174 |
+
##### (not getting rid of outliers as they represent natural variation)
|
175 |
+
"""
|
176 |
+
|
177 |
+
# reference: https://statisticsbyjim.com/basics/remove-outliers/
|
178 |
+
|
179 |
+
strat_train_set.boxplot(column='Weight (kg)')
|
180 |
+
|
181 |
+
strat_train_set[['Weight (kg)']].describe()
|
182 |
+
# note that the high of 237.7 kg represents around 522 lbs which is plausible to see in a population
|
183 |
+
|
184 |
+
"""### Visualizing method for replacing Nan values in height group with median based on Gender """
|
185 |
+
|
186 |
+
# souce 1: https://stackoverflow.com/questions/33457191/python-pandas-dataframe-fill-nans-with-a-conditional-mean
|
187 |
+
# source 2: https://www.statology.org/conditional-mean-pandas/
|
188 |
+
|
189 |
+
median_female_height=strat_train_set.loc[strat_train_set['Gender'] == 'female', 'Height (cm)'].median()
|
190 |
+
median_female_height
|
191 |
+
|
192 |
+
median_male_height=strat_train_set.loc[strat_train_set['Gender'] == 'male', 'Height (cm)'].median()
|
193 |
+
median_male_height
|
194 |
+
|
195 |
+
"""##### Copy of Strat_train_set created for testing purposes"""
|
196 |
+
|
197 |
+
strat_train_set_copy = strat_train_set.copy()
|
198 |
+
|
199 |
+
strat_train_set_copy.head(n=3)
|
200 |
+
|
201 |
+
# getting gender specific medians for training set
|
202 |
+
medians = strat_train_set_copy.groupby(['Gender'])['Height (cm)'].median()
|
203 |
+
|
204 |
+
# performing test transformation of null Height values based on gender
|
205 |
+
strat_train_set_copy = strat_train_set_copy.set_index(['Gender'])
|
206 |
+
strat_train_set_copy['Height (cm)'] = strat_train_set_copy['Height (cm)'].fillna(medians)
|
207 |
+
strat_train_set_copy = strat_train_set_copy.reset_index()
|
208 |
+
|
209 |
+
strat_train_set_copy.head(n=3)
|
210 |
+
|
211 |
+
"""### Visualizing Race Distribution
|
212 |
+
#### Includes Visualization of pre-processing steps implemented later
|
213 |
+
"""
|
214 |
+
|
215 |
+
# craete copy for testing purposes
|
216 |
+
strat_train_set_copy = strat_train_set.copy()
|
217 |
+
|
218 |
+
# visualizing original race feature
|
219 |
+
strat_train_set_copy['Race (Reported)'].value_counts().plot(kind='bar')
|
220 |
+
|
221 |
+
strat_train_set_copy['Race (Reported)'] = strat_train_set_copy['Race (Reported)'].fillna("UNSPECIFIED") # full null with UNSPECIFIED
|
222 |
+
strat_train_set_copy['Race (Reported)'] = strat_train_set_copy['Race (Reported)'].str.upper() # uppercase all catagories
|
223 |
+
|
224 |
+
# remove redundancy
|
225 |
+
strat_train_set_copy = strat_train_set_copy.replace({'Race (Reported)': {'AFRICAN-AMERICAN': 'BLACK OR AFRICAN AMERICAN', 'BLACK': 'BLACK OR AFRICAN AMERICAN'}})
|
226 |
+
|
227 |
+
# visualizing the race feature after pre-processing
|
228 |
+
strat_train_set_copy['Race (Reported)'].value_counts().plot(kind='bar')
|
229 |
+
strat_train_set['Race (Reported)'].isna().sum()
|
230 |
+
|
231 |
+
"""### Visualizing Age Distribution
|
232 |
+
#### Replace Age Nan Values with mode from train set in pipeline
|
233 |
+
"""
|
234 |
+
|
235 |
+
# visualizing age dataset before pre-processing
|
236 |
+
strat_train_set['Age'].value_counts().plot(kind='bar')
|
237 |
+
strat_train_set['Age'].isna().sum()
|
238 |
+
|
239 |
+
"""### Visualizing Diabetes Distribution
|
240 |
+
#### Replace Diabetes Nan Values with mode from train set in pipeline
|
241 |
+
"""
|
242 |
+
|
243 |
+
# visualizing diabetes training set before pre-processing
|
244 |
+
strat_train_set['Diabetes'].value_counts().plot(kind='bar')
|
245 |
+
strat_train_set['Diabetes'].isna().sum()
|
246 |
+
|
247 |
+
"""### Visualizing Simvastatin Distribution
|
248 |
+
#### Replace Simvastatin Nan Values with mode from train set in pipeline
|
249 |
+
"""
|
250 |
+
|
251 |
+
strat_train_set['Simvastatin (Zocor)'].value_counts().plot(kind='bar')
|
252 |
+
strat_train_set['Simvastatin (Zocor)'].isna().sum()
|
253 |
+
|
254 |
+
"""### Visualizing Amiodarone Distribution
|
255 |
+
#### Replace Amiodarone Nan Values with mode from train set in pipeline
|
256 |
+
"""
|
257 |
+
|
258 |
+
strat_train_set['Amiodarone (Cordarone)'].value_counts().plot(kind='bar')
|
259 |
+
strat_train_set['Amiodarone (Cordarone)'].isna().sum()
|
260 |
+
|
261 |
+
"""### Visualizing Cyp2C9 Distribution
|
262 |
+
#### Includes Visualization of Pre-processing steps implemented later
|
263 |
+
#### Replace Cyp2C9 Nan Values with mode from train set in pipeline
|
264 |
+
"""
|
265 |
+
|
266 |
+
strat_train_set_copy = strat_train_set.copy()
|
267 |
+
|
268 |
+
strat_train_set_copy['Cyp2C9 genotypes'].value_counts()
|
269 |
+
|
270 |
+
strat_train_set_copy['Cyp2C9 genotypes'].isna().sum()
|
271 |
+
|
272 |
+
strat_train_set_copy['Cyp2C9 genotypes'].value_counts().plot(kind='bar')
|
273 |
+
|
274 |
+
strat_train_set_copy['Cyp2C9 genotypes'] = strat_train_set_copy['Cyp2C9 genotypes'].fillna(strat_train_set_copy['Cyp2C9 genotypes'].mode()[0])
|
275 |
+
|
276 |
+
strat_train_set['Cyp2C9 genotypes'].mode()[0]
|
277 |
+
|
278 |
+
strat_train_set_copy['Cyp2C9 genotypes'].value_counts()
|
279 |
+
|
280 |
+
strat_train_set_copy['Cyp2C9 genotypes'].value_counts().plot(kind='bar')
|
281 |
+
|
282 |
+
"""### Visualizing VKORC1 genotype
|
283 |
+
#### Includes Visualization of Pre-processing Steps Implemented Later
|
284 |
+
#### Replacing VKORC1 genotype Nan Values with 'Unknown' since there is no obvious mode (creates new catagory)
|
285 |
+
"""
|
286 |
+
|
287 |
+
strat_train_set_copy = strat_train_set.copy()
|
288 |
+
|
289 |
+
strat_train_set_copy['VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T'].value_counts().plot(kind='bar')
|
290 |
+
|
291 |
+
strat_train_set_copy['VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T'].isna().sum()
|
292 |
+
|
293 |
+
# filling null values with 'Unknown'
|
294 |
+
strat_train_set_copy['VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T']=strat_train_set_copy['VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T'].fillna("Unknown")
|
295 |
+
|
296 |
+
strat_train_set_copy['VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T'].value_counts().plot(kind='bar')
|
297 |
+
|
298 |
+
"""## Separate the data from the labels in training set:
|
299 |
+
##### strat_train_set -> patients_info and patients_labels
|
300 |
+
"""
|
301 |
+
|
302 |
+
patients_info = strat_train_set.drop("Therapeutic Dose of Warfarin", axis=1) # drop labels for training set
|
303 |
+
patients_labels = strat_train_set["Therapeutic Dose of Warfarin"].copy()
|
304 |
+
|
305 |
+
"""## Custom Transformers for Pre-processing (Important Part)
|
306 |
+
##### reference: Chapter 2 Textbook associated google collab notebook
|
307 |
+
|
308 |
+
##### creating a custom transformer to handle catagorical attributes Nan Values:
|
309 |
+
##### includes Gender, Cyp2C9 genotypes, VKORC1 genotype, Diabetes, Amiodarone, Simvastatin, Race, Age
|
310 |
+
"""
|
311 |
+
|
312 |
+
from sklearn.base import BaseEstimator, TransformerMixin
|
313 |
+
|
314 |
+
class CatTransformer(BaseEstimator, TransformerMixin):
|
315 |
+
"""
|
316 |
+
REPLACEMENT OF NAN FOR ALL CATAGORICAL FEATURES
|
317 |
+
for Gender, fills with mode from training set
|
318 |
+
for Cyp2C9 genotypes, fills with mode from training set as there is a most common class by far
|
319 |
+
for VKORC1 genotype, many more are unknown, and there is not a most common class, so fills with "unknown", thus creating a new catagory
|
320 |
+
for Diabetes phenotype, fills with mode--assumes no diabetes
|
321 |
+
for Amiodarone (Cordarone) drug, fills with mode from training set as there is a most common class by far
|
322 |
+
for Simvastatin (Zocor), fills with mode from training set as there is a most common class by far
|
323 |
+
for Race, fills nan with "unknown" and converts all classes to upper so that the several groups labelled "other" are grouped together
|
324 |
+
for Race, only a few were missing--replacement of nan with Mode
|
325 |
+
for Race, due to there already being a catagory for "Black or African American", the catagories "Black" and "African American" were grouped together under "Black or African American"
|
326 |
+
for Age, fills nan with mode from training set--not many Age values are missing. Even though there is not a most common class by a lot, I think this is best
|
327 |
+
"""
|
328 |
+
def __init__(self): # no *args or **kwargs
|
329 |
+
pass
|
330 |
+
def fit(self, X, y=None):
|
331 |
+
self.mode_Gen = X['Gender'].mode()[0]
|
332 |
+
self.mode_Cyp = X['Cyp2C9 genotypes'].mode()[0]
|
333 |
+
self.mode_Amio = X['Amiodarone (Cordarone)'].mode()[0]
|
334 |
+
self.mode_Simv = X['Simvastatin (Zocor)'].mode()[0]
|
335 |
+
self.mode_Diab = X['Diabetes'].mode()[0]
|
336 |
+
self.mode_Age = X['Age'].mode()[0]
|
337 |
+
return self
|
338 |
+
def transform(self, X):
|
339 |
+
X['Cyp2C9 genotypes']=X['Cyp2C9 genotypes'].fillna(self.mode_Cyp)
|
340 |
+
X['VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T']=X['VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T'].fillna("Unknown")
|
341 |
+
X['Amiodarone (Cordarone)']=X['Amiodarone (Cordarone)'].fillna(self.mode_Amio)
|
342 |
+
X['Simvastatin (Zocor)']=X['Simvastatin (Zocor)'].fillna(self.mode_Simv)
|
343 |
+
X['Diabetes']=X['Diabetes'].fillna(self.mode_Diab)
|
344 |
+
X['Race (Reported)'] = X['Race (Reported)'].fillna("UNSPECIFIED")
|
345 |
+
X['Race (Reported)'] = X['Race (Reported)'].str.upper()
|
346 |
+
X=X.replace({'Race (Reported)': {'AFRICAN-AMERICAN': 'BLACK OR AFRICAN AMERICAN', 'BLACK': 'BLACK OR AFRICAN AMERICAN'}})
|
347 |
+
X['Age']=X['Age'].fillna(self.mode_Age)
|
348 |
+
X['Gender']=X['Gender'].fillna(self.mode_Gen)
|
349 |
+
return X
|
350 |
+
|
351 |
+
"""##### creating a custom transformer to handle the transformation of height nan variables based on gender-depenedent median"""
|
352 |
+
|
353 |
+
from sklearn.base import BaseEstimator, TransformerMixin
|
354 |
+
|
355 |
+
class GenderTransformer(BaseEstimator, TransformerMixin):
|
356 |
+
"""
|
357 |
+
replaces missing Height variables by median for the associated gender
|
358 |
+
replaces missing Weight variables by median for the associated gender
|
359 |
+
"""
|
360 |
+
def __init__(self): # no *args or **kwargs
|
361 |
+
pass
|
362 |
+
def fit(self, X, y=None):
|
363 |
+
self.medians_height = X.groupby(['Gender'])["Height (cm)"].median()
|
364 |
+
self.medians_weight = X.groupby(['Gender'])["Weight (kg)"].median()
|
365 |
+
return self
|
366 |
+
def transform(self, X):
|
367 |
+
X = X.set_index(['Gender'])
|
368 |
+
X["Height (cm)"] = X["Height (cm)"].fillna(self.medians_height)
|
369 |
+
X["Weight (kg)"] = X["Weight (kg)"].fillna(self.medians_weight)
|
370 |
+
X = X.reset_index()
|
371 |
+
return X
|
372 |
+
|
373 |
+
"""##### creating a custom transformer to add extra attributes (BMI, BSA):"""
|
374 |
+
|
375 |
+
from sklearn.base import BaseEstimator, TransformerMixin
|
376 |
+
|
377 |
+
# column index
|
378 |
+
col_names = ["Height (cm)", "Weight (kg)"]
|
379 |
+
weight_ix, height_ix = [0, 1] # get the column indices; they are 0 and 1
|
380 |
+
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
|
381 |
+
"""
|
382 |
+
adds the variables for BSA (body surface area) to the data
|
383 |
+
def transform returns numpy array
|
384 |
+
Body Surface Area (as calculated from the DuBois and DuBois formula)
|
385 |
+
reference: https://www.uptodate.com/contents/image?imageKey=ONC%2F96451&topicKey=ONC%2F83810&search=Pharmacogenomics&rank=3~18&source=see_link
|
386 |
+
"""
|
387 |
+
def __init__(self): # no *args or **kwargs
|
388 |
+
pass
|
389 |
+
def fit(self, X, y=None):
|
390 |
+
return self # nothing else to do
|
391 |
+
def transform(self, X):
|
392 |
+
# BMI = X[:, weight_ix] / ((X[:, height_ix]/100)**2)
|
393 |
+
BSA = ((0.007184*(X[:, weight_ix])**0.425)) * ((X[:, height_ix])**0.725)
|
394 |
+
return np.c_[X, BSA]
|
395 |
+
|
396 |
+
"""#### Working Transformer Pipelines
|
397 |
+
|
398 |
+
##### pipeline for dealing with missing height and weight values
|
399 |
+
"""
|
400 |
+
|
401 |
+
from sklearn.pipeline import Pipeline
|
402 |
+
from sklearn.preprocessing import StandardScaler
|
403 |
+
|
404 |
+
gender_pipeline = Pipeline([
|
405 |
+
('gender_transformer', GenderTransformer()),
|
406 |
+
])
|
407 |
+
|
408 |
+
"""##### pipeline for dealing with catagorical data nan values"""
|
409 |
+
|
410 |
+
from sklearn.pipeline import Pipeline
|
411 |
+
from sklearn.preprocessing import StandardScaler
|
412 |
+
|
413 |
+
cat_pipeline = Pipeline([
|
414 |
+
('catagorical_transformer', CatTransformer()),
|
415 |
+
])
|
416 |
+
|
417 |
+
"""##### pipeline for dealing with numerical data: height, weight, INR
|
418 |
+
##### uses CombinedAttributeAdder class for the addition of BSA (or BMI)
|
419 |
+
##### uses SimpleImputer to replace any remaining Nan values with the median for that feature
|
420 |
+
##### uses StandardScaler for scaling
|
421 |
+
"""
|
422 |
+
|
423 |
+
from sklearn.pipeline import Pipeline
|
424 |
+
from sklearn.preprocessing import StandardScaler
|
425 |
+
|
426 |
+
num_pipeline = Pipeline([
|
427 |
+
('imputer', SimpleImputer(strategy="median")),
|
428 |
+
('attribs_adder', CombinedAttributesAdder()),
|
429 |
+
('std_scaler', StandardScaler()),
|
430 |
+
])
|
431 |
+
|
432 |
+
"""##### full pipuline using ColumnTransformer
|
433 |
+
##### Adds Attributes (from num_pipeline), Scales and imputes numerical data (from num_pipeline), Uses ordinal encoder for Ordinal Catagorical Data (Age), Uses 1Hot Encoder for non-ordinal Catagorical Data
|
434 |
+
"""
|
435 |
+
|
436 |
+
from sklearn.compose import ColumnTransformer
|
437 |
+
from sklearn.preprocessing import OrdinalEncoder
|
438 |
+
|
439 |
+
num_attribs = ['Height (cm)', 'Weight (kg)', 'INR on Reported Therapeutic Dose of Warfarin']
|
440 |
+
cat_attribs_ordinal = ['Age', 'Gender', 'Diabetes', 'Simvastatin (Zocor)', 'Amiodarone (Cordarone)']
|
441 |
+
cat_attribs_1hot = ["Race (Reported)",
|
442 |
+
'Cyp2C9 genotypes', 'VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T']
|
443 |
+
"""
|
444 |
+
Pipeline using column transformer
|
445 |
+
Adds BSA attribute (from num_pipeline)
|
446 |
+
imputes remaining nan numerical data using median (from num_pipeline)
|
447 |
+
scales numerical data using StandardScaler (from num_pipeline)
|
448 |
+
Uses ordinal encoder for Ordinal Catagorical Data (Age) and Binary Catagorical Data (gender, diabetes, simvastatin, amiodorone)--see cat_attrib_ordinal
|
449 |
+
Uses 1Hot Encoder for non-ordinal Catagorical Data--see cat_attribs_1hot
|
450 |
+
"""
|
451 |
+
scale_encode_pipeline = ColumnTransformer([
|
452 |
+
("num", num_pipeline, num_attribs),
|
453 |
+
('cat_ord', OrdinalEncoder(), cat_attribs_ordinal),
|
454 |
+
("cat_1hot", OneHotEncoder(sparse=False, handle_unknown='ignore'), cat_attribs_1hot),
|
455 |
+
]) #input list of (name, transformer, columns) tuples specifying the transformer objects to be applied to subsets of the data.
|
456 |
+
|
457 |
+
"""## Full PreProcess Function to incorporate all pipelines
|
458 |
+
##### contains "full_preprocess_function()"
|
459 |
+
"""
|
460 |
+
|
461 |
+
def series_to_df(data_series):
|
462 |
+
"""
|
463 |
+
function to help with processing new data (potentially useful for Gradio implementation)
|
464 |
+
input: Series with dimensions (12,)
|
465 |
+
output: pandas dataframe with features as column names; can now be sent through full_preprocess_function
|
466 |
+
"""
|
467 |
+
data_df = data_series.to_frame()
|
468 |
+
data_df = data_df.transpose()
|
469 |
+
return data_df
|
470 |
+
|
471 |
+
def full_preprocess_function(data_df, train=False):
|
472 |
+
"""
|
473 |
+
INPUT: program expects the equivalent of an instance (or multiple instances) from the non pre-processed dataset (without the label) in the form of a pandas_df
|
474 |
+
|
475 |
+
--input should have the following 12 features as column names: Gender, Race (Reported), Age, Height (cm), Weight (kg), Diabetes, Simvastatin (Zocor), Amiodarone (Cordarone),
|
476 |
+
Target INR, INR on Reported Therapeutic Dose of Warfarin,
|
477 |
+
Cyp2C9 genotypes, VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T
|
478 |
+
--input should either contain a value for each feature of Nan
|
479 |
+
|
480 |
+
program will remove the Target INR column from dataset as there were too few values and it was multicollinearly related to INR Reported
|
481 |
+
if train==True, function will send training data to pre-processing be fit and transformed
|
482 |
+
|
483 |
+
else, function will send new data to pre-processing to be transformed (not fit)
|
484 |
+
OUTPUT: function returns pandas df of features, including feature names as column names
|
485 |
+
Note for encoded variables:
|
486 |
+
Gender: 0=female, 1=male;
|
487 |
+
Diabetes: 0=no, 1=yes;
|
488 |
+
Simvastatin: 0=no, 1=yes;
|
489 |
+
Amiodorone: 0=no, 1=yes;
|
490 |
+
Age: {0: '10 - 19', 1:'20 - 29', 2:'30 - 39', 3:'40 - 49', 4:'50 - 59', 5:'60 - 69', 6:'70 - 79', 7:'80 - 89', 8:'90+'}
|
491 |
+
"""
|
492 |
+
if isinstance(data_df, pd.Series) and data_df.shape == (12,):
|
493 |
+
raise TypeError("Expects pd.DataFrame; Send your data through the series_to_df() function for conversion to proper format")
|
494 |
+
if not isinstance(data_df, pd.DataFrame):
|
495 |
+
raise TypeError("Expects pd.DataFrame; See full_preprocess function documentation for input expectations")
|
496 |
+
# prepared_feature_names = ['Height (cm)', 'Weight (kg)', 'INR (Reported)', 'BSA (m**2)', 'Age', 'Gender', 'Diabetes', 'Simvastatin', 'Amiodorone',
|
497 |
+
# 'ASIAN', 'BLACK OR AFRICAN AMERICAN', 'CAUCASIAN', 'CHINESE', 'HAN CHINESE', 'HISPANIC', 'INDIAN', 'INTERMEDIATE', 'JAPANESE', 'KOREAN', 'MALAY', 'OTHER','OTHER MIXED RACE', 'UNSPECIFIED', 'WHITE',
|
498 |
+
# '*1/*1', '*1/*11', '*1/*13', '*1/*14', '*1/*2', '*1/*3', '*1/*5', '*1/*6', '*2/*2', '*2/*3', '*3/*3',
|
499 |
+
# 'A/A', 'A/G', 'G/G', 'Unknown']
|
500 |
+
data_df.drop(['Target INR'], axis=1, inplace=True) # remove Target INR due to too few values and collinearity with INR Reported
|
501 |
+
if train==True:
|
502 |
+
data_cat_tr = cat_pipeline.fit_transform(data_df)
|
503 |
+
data_height_tr = gender_pipeline.fit_transform(data_cat_tr)
|
504 |
+
data_prepared = scale_encode_pipeline.fit_transform(data_height_tr)
|
505 |
+
else:
|
506 |
+
data_cat_tr = cat_pipeline.transform(data_df)
|
507 |
+
data_height_tr = gender_pipeline.transform(data_cat_tr )
|
508 |
+
data_prepared = scale_encode_pipeline.transform(data_height_tr)
|
509 |
+
data_prepared_df = pd.DataFrame(data_prepared)
|
510 |
+
# data_prepared_df.drop(['Weight (kg)'], axis=1, inplace=True) # removing weight to address multicollinearity
|
511 |
+
return data_prepared_df
|
512 |
+
|
513 |
+
"""##### Example test input for full_preprocess_function()
|
514 |
+
![example_input.PNG](data:image/png;base64,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/j1ZZrN09pb+3kXJxzgdFvsHcR5xZb7UiY/yIxN3OHkb2gXbR4rza8N3Ou+47UBzv932TSvSUcYflH/EbitlPWlWP6T7C/V9jEhf/1CnzJCla2bamquPN+raHEREnCHr2nT/t0J9/5HmuwzXGgxERKzUVnRxIKU4iEp2hm2lefUONiIiIiIiIiIiIiLivGggGIiIN6IN9CXvaBDdnQ3lpwArntJARERERERERERERMSCBoKBiHgD5oG+4tIp4ZOC9cFAlgpFRERERERERERERKzXQDAQEW/A/B0tYVDPvY8hDwhWLxNKMBARERERERERERERsUkDwUBEvAF9MLC0TGj01J9/gWoe+JOXohIMRERERERERERERERs0kAwEBFvwKqlPn2wLwwS+oCg82t18IZlQhERERERERERERER22ggGIiIN2DV+wATwcBYv0zoj/9O/A0RERERERERERERETMNBAMR8UZ8+6+vy+8DjJ4YTL0z0H4veK8gIiIiIiIiIiIiIiKmNRAMRMQbMl7uU94HGAT6/BKh2VOA/qlAlghFRERERERERERERGzWQDAQEW9QCQB6S08KagvvDGR5UERERERERERERETEthoIBiIiIiIiIiIiIiIiIiJ2UAPBQERERERERERERERERMQOaiAYiIiIiIiIiIiIiIiIiNhBDQQDERERERERERERERERETuogWAgIiIiIiIiIiIiIiIiYgc1EAxERERERERERERERERE7KAGgoGIiIiIiIiIiIiIiIiIHdRAMBARERERERERERERERGxgxoIBiIiIiIiIiIiIiIiIiJ2UAPBQERERERERERERERERMQOaiAYiIiIiIiIiIiIiIiIiNhBDQQDERERERERERERERERETuogWAgIiIiIiIiIiIiIiIiYgc1jBUMBAAAAAAAAAAAAAAAAIDZgWAgAAAAAAAAAAAAAAAAQEchGAgAAAAAAAAAAAAAAADQUQgGAgAAAAAAAAAAAAAAAHQUgoEAAAAAAAAAAAAAAAAAHYVgIAAAAAAAAAAAAAAAAEBHIRgIAAAAAAAAAAAAAAAA0FGqg4FK/f/iav+e06yChQAAAABJRU5ErkJggg==)
|
515 |
+
|
516 |
+
#### Getting OneHotEncoder Catagory Info and Testing Ordinal Encoding for Age (can be ignored)
|
517 |
+
"""
|
518 |
+
|
519 |
+
# data_cat_tr = cat_pipeline.fit_transform(patients_info)
|
520 |
+
# data_height_tr = gender_pipeline.fit_transform(data_cat_tr)
|
521 |
+
|
522 |
+
# data_height_tr
|
523 |
+
|
524 |
+
# data_height_tr_age = data_height_tr[['Age']]
|
525 |
+
|
526 |
+
# cat_encoder = OrdinalEncoder()
|
527 |
+
|
528 |
+
# patients_age_ord = cat_encoder.fit_transform(data_height_tr_age)
|
529 |
+
|
530 |
+
# patients_age_ord
|
531 |
+
|
532 |
+
# cat_encoder.categories_
|
533 |
+
# array([[7.],
|
534 |
+
# [6.],
|
535 |
+
# [6.],
|
536 |
+
# ...,
|
537 |
+
# [4.],
|
538 |
+
# [7.],
|
539 |
+
# [5.]])
|
540 |
+
# [array(['10 - 19', '20 - 29', '30 - 39', '40 - 49', '50 - 59', '60 - 69',
|
541 |
+
# '70 - 79', '80 - 89', '90+'], dtype=object)]
|
542 |
+
|
543 |
+
# cat_encoder.categories_
|
544 |
+
# [array(['10 - 19', '20 - 29', '30 - 39', '40 - 49', '50 - 59', '60 - 69',
|
545 |
+
# '70 - 79', '80 - 89', '90+'], dtype=object),
|
546 |
+
# array(['female', 'male'], dtype=object),
|
547 |
+
# array(['ASIAN', 'BLACK OR AFRICAN AMERICAN',
|
548 |
+
# 'CAUCASIAN', 'CHINESE', 'HAN CHINESE', 'HISPANIC', 'INDIAN',
|
549 |
+
# 'INTERMEDIATE', 'JAPANESE', 'KOREAN', 'MALAY', 'OTHER',
|
550 |
+
# 'OTHER MIXED RACE', 'UNSPECIFIED', 'WHITE'], dtype=object),
|
551 |
+
# array([0., 1.]),
|
552 |
+
# array([0., 1.]),
|
553 |
+
# array([0., 1.]),
|
554 |
+
# array(['*1/*1', '*1/*11', '*1/*13', '*1/*14', '*1/*2', '*1/*3', '*1/*5',
|
555 |
+
# '*1/*6', '*2/*2', '*2/*3', '*3/*3'], dtype=object),
|
556 |
+
# array(['A/A', 'A/G', 'G/G', 'Unknown'], dtype=object)]
|
557 |
+
|
558 |
+
|
559 |
+
## Sending training features data through pre-processing pipeline
|
560 |
+
##### patients_info -> 'X_train_prepared'
|
561 |
+
##### y_train stored in 'patients_labels'
|
562 |
+
"""
|
563 |
+
|
564 |
+
## showing un-pre-processed dataset
|
565 |
+
patients_info.head()
|
566 |
+
|
567 |
+
|
568 |
+
|
569 |
+
X_train_prepared = full_preprocess_function(patients_info, train=True)
|
570 |
+
|
571 |
+
# showing pre-processed training dataset
|
572 |
+
X_train_prepared.head()
|
573 |
+
|
574 |
+
X_train_prepared.info()
|
575 |
+
|
576 |
+
"""##### Send pre-processed train_data to excel (labels too)"""
|
577 |
+
|
578 |
+
# X_train_prepared.to_excel("X_patients_train.xlsx")
|
579 |
+
# patients_labels.to_excel('y_patients_train.xlsx')
|
580 |
+
|
581 |
+
"""## Making Sure Pre-processed training set works with basic model"""
|
582 |
+
|
583 |
+
from sklearn.linear_model import LinearRegression
|
584 |
+
|
585 |
+
lin_reg = LinearRegression()
|
586 |
+
lin_reg.fit(X_train_prepared, patients_labels)
|
587 |
+
|
588 |
+
patients_labels
|
589 |
+
|
590 |
+
from sklearn.metrics import mean_squared_error
|
591 |
+
|
592 |
+
patients_predictions = lin_reg.predict(X_train_prepared)
|
593 |
+
lin_mse = mean_squared_error(patients_labels, patients_predictions)
|
594 |
+
lin_rmse = np.sqrt(lin_mse)
|
595 |
+
lin_rmse
|
596 |
+
|
597 |
+
"""## Pre-processing on Test Set (currently stored in strat_test_set)
|
598 |
+
##### note: strat_test_set contains features and labels
|
599 |
+
##### produces X_test_prepared and y_test
|
600 |
+
|
601 |
+
#### Separate strat_test_set features from labels
|
602 |
+
##### stored in X_test and y_test
|
603 |
+
"""
|
604 |
+
|
605 |
+
X_test = strat_test_set.drop("Therapeutic Dose of Warfarin", axis=1)
|
606 |
+
y_test = strat_test_set["Therapeutic Dose of Warfarin"].copy()
|
607 |
+
|
608 |
+
"""#### Send X_test to pre-processing function/pipeline
|
609 |
+
##### stored in X_test_prepared
|
610 |
+
"""
|
611 |
+
|
612 |
+
X_test_prepared = full_preprocess_function(X_test)
|
613 |
+
|
614 |
+
"""##### Send pre-processed test_data to excel (labels too)"""
|
615 |
+
|
616 |
+
# X_test_prepared.to_excel("X_patients_test.xlsx")
|
617 |
+
# y_test.to_excel("y_patients_test.xlsx")
|
618 |
+
|
619 |
+
"""## Making sure Pre-processed testing set works with simple regression model"""
|
620 |
+
|
621 |
+
test_predictions = lin_reg.predict(X_test_prepared)
|
622 |
+
|
623 |
+
"""#### Evaluate mse and rmse"""
|
624 |
+
|
625 |
+
test_mse = mean_squared_error(y_test, test_predictions)
|
626 |
+
test_rmse = np.sqrt(test_mse)
|
627 |
+
|
628 |
+
test_rmse
|
629 |
+
|
630 |
+
"""## Pre-processing on Validation Set
|
631 |
+
##### produces X_val_prepared and y_val
|
632 |
+
|
633 |
+
#### Dropping nan labels and Separating validation_set features from labels
|
634 |
+
##### oridinally stored in 'X_val' and 'y_val'
|
635 |
+
"""
|
636 |
+
|
637 |
+
validation_set.dropna(subset=['Therapeutic Dose of Warfarin'], inplace=True)
|
638 |
+
X_val = validation_set.drop("Therapeutic Dose of Warfarin", axis=1)
|
639 |
+
y_val = validation_set["Therapeutic Dose of Warfarin"].copy()
|
640 |
+
|
641 |
+
"""## Sending a single instance from X_val through pre-processing pipeline and making sure it works with simple regression model"""
|
642 |
+
|
643 |
+
trial = X_val.iloc[3]
|
644 |
+
trial
|
645 |
+
|
646 |
+
trial.shape
|
647 |
+
|
648 |
+
trial_df = series_to_df(trial)
|
649 |
+
|
650 |
+
# example of input for full_preprocessing_function()
|
651 |
+
trial_df
|
652 |
+
|
653 |
+
X_val_trial = full_preprocess_function(trial_df)
|
654 |
+
|
655 |
+
# example of pre-processed single test input
|
656 |
+
X_val_trial
|
657 |
+
|
658 |
+
trial_val_prediction = lin_reg.predict(X_val_trial)
|
659 |
+
|
660 |
+
trial_val_prediction
|
661 |
+
|
662 |
+
y_trial = y_val.iloc[3]
|
663 |
+
y_trial
|
664 |
+
|
665 |
+
"""#### Sending X_val through pre-processing pipeline"""
|
666 |
+
|
667 |
+
X_val_prepared = full_preprocess_function(X_val)
|
668 |
+
|
669 |
+
"""## Making sure pre-processed validation set works with simple regression model"""
|
670 |
+
|
671 |
+
val_predictions = lin_reg.predict(X_val_prepared)
|
672 |
+
|
673 |
+
val_mse = mean_squared_error(y_val, val_predictions)
|
674 |
+
val_rmse = np.sqrt(val_mse)
|
675 |
+
|
676 |
+
val_rmse
|
677 |
+
|
678 |
+
"""##### Send pre-processed validation_data to excel (labels too)"""
|
679 |
+
|
680 |
+
# X_val_prepared.to_excel("X_patients_val.xlsx")
|
681 |
+
# y_val.to_excel("y_patients_val.xlsx")
|
682 |
+
|
683 |
+
"""#**PART II ----> ML MODELS FOR BINARY CLASSIFICATION**
|
684 |
+
|
685 |
+
**First let's create a binary classification dataset by cutting the target values into two categories (<30 mg , >=30 mg)**
|
686 |
+
"""
|
687 |
+
|
688 |
+
import numpy as np
|
689 |
+
|
690 |
+
y_train = patients_labels
|
691 |
+
|
692 |
+
#Preparing training/testing/validation data for binary classifier
|
693 |
+
train_label_binary = (y_train >= 30)
|
694 |
+
print("binary train labels:", train_label_binary)
|
695 |
+
|
696 |
+
# print("original test labels:", y_test)
|
697 |
+
test_label_binary = (y_test >= 30)
|
698 |
+
print("binary test labels:", test_label_binary)
|
699 |
+
|
700 |
+
validation_label_binary = (y_val >= 30)
|
701 |
+
print("binary validation labels:", validation_label_binary)
|
702 |
+
|
703 |
+
"""## 1.LOGISTIC REGRESSION MODEL
|
704 |
+
|
705 |
+
Logistic regression can be used for binary classification because it estimates the probability that one instance belogns to a class or not. So by using a probability threshold e.g 50%, it classifies the instances in positive class (1) if the probability is greater than 50 %. otherwise the instances will be classified in negative class (0). So, this model works in the same way as the Linear Regression but instead of outputing the result, it outputs the logistic of the result.
|
706 |
+
"""
|
707 |
+
|
708 |
+
from sklearn.linear_model import LogisticRegression
|
709 |
+
log_regression = LogisticRegression(penalty = 'l2', C = 1, random_state = 0 )
|
710 |
+
log_regression.fit(X_train_prepared, train_label_binary.values.ravel())
|
711 |
+
log_prediction = log_regression.predict(X_train_prepared)
|
712 |
+
log_prediction
|
713 |
+
|
714 |
+
"""## 2.SUPPORT VECTOR MACHINE
|
715 |
+
The main goal of Support Vector Machines is to fit the widest possible “street” between the classes. So, we need to have a large margin between the decision boundary which separates the classes and the training instances. the objective of SVM to find the optimal classifier is bacause the other linear classifiers might separate linear dataset in the correct way but the decision boundary is so close the training instances so that these models will probably not perform as well on new instances. Tha's why SVM tries to find the widest possible "street" between the classes.
|
716 |
+
|
717 |
+
"""
|
718 |
+
|
719 |
+
from sklearn.svm import SVC
|
720 |
+
# # define linear kernel,
|
721 |
+
# svm_model_linear = SVC(kernel = "linear",C = 1 )
|
722 |
+
# svm_model_linear.fit(X_train_prepared, train_label_binary.values.ravel())
|
723 |
+
# svm_linear_prediction= svm_model_linear.predict(X_train_prepared)
|
724 |
+
# svm_linear_prediction
|
725 |
+
|
726 |
+
# define polynomial kernel, P158
|
727 |
+
svm_model_polynomial = SVC(kernel = "poly", degree = 7, C = 7 )
|
728 |
+
svm_model_polynomial.fit(X_train_prepared, train_label_binary.values.ravel())
|
729 |
+
svm_polynomial_prediction = svm_model_polynomial.predict(X_train_prepared)
|
730 |
+
|
731 |
+
svm_polynomial_prediction
|
732 |
+
|
733 |
+
"""## 3.DECISION TREE MODEL"""
|
734 |
+
|
735 |
+
from sklearn.tree import DecisionTreeClassifier
|
736 |
+
# define tree model
|
737 |
+
decision_tree_model = DecisionTreeClassifier(max_depth = 5)
|
738 |
+
decision_tree_model.fit(X_train_prepared, train_label_binary.values.ravel())
|
739 |
+
decision_tree_prediction = decision_tree_model.predict(X_train_prepared)
|
740 |
+
decision_tree_prediction
|
741 |
+
|
742 |
+
"""## 4.RANDOM FOREST MODEL"""
|
743 |
+
|
744 |
+
from sklearn.ensemble import RandomForestClassifier
|
745 |
+
random_forest_model = RandomForestClassifier(n_estimators = 500, max_depth= 10, max_leaf_nodes = -1)
|
746 |
+
random_forest_model.fit(X_train_prepared, train_label_binary.values.ravel())
|
747 |
+
random_forest_prediction = random_forest_model.predict(X_train_prepared)
|
748 |
+
random_forest_prediction
|
749 |
+
|
750 |
+
"""## 5.NEURAL NETWORK"""
|
751 |
+
|
752 |
+
import tensorflow as tf
|
753 |
+
from tensorflow.keras.models import Sequential
|
754 |
+
from tensorflow.keras.layers import Dense
|
755 |
+
from tensorflow.keras.layers import Flatten
|
756 |
+
from tensorflow.keras.layers import Dropout
|
757 |
+
|
758 |
+
# Define decision threshold
|
759 |
+
NN_threshold = 0.5;
|
760 |
+
|
761 |
+
def build_NN(n_layers = 3, n_neurons = 1000, dropout = 0):
|
762 |
+
model = Sequential() # create Sequential model
|
763 |
+
for i in range(n_layers-1):
|
764 |
+
model.add(Dense(n_neurons, activation = 'relu'))
|
765 |
+
model.add(Dropout(dropout))
|
766 |
+
model.add(Dense(1, activation = 'sigmoid')) # 2 output neurons for binary classification
|
767 |
+
model.compile(loss = "binary_crossentropy", optimizer = "adam", metrics = ['accuracy']) # binary cross-entropy because it's binary classification!
|
768 |
+
return model
|
769 |
+
|
770 |
+
# Build random NN
|
771 |
+
NN_model = build_NN(n_layers = 3, n_neurons = 10)
|
772 |
+
|
773 |
+
train_history = NN_model.fit(X_train_prepared, train_label_binary.values.ravel(), validation_data=(X_val_prepared,validation_label_binary.values.ravel()), batch_size=128, epochs = 20)
|
774 |
+
NN_prediction = NN_model.predict(X_train_prepared)
|
775 |
+
|
776 |
+
# Prepare prediction to be comparable
|
777 |
+
NN_prediction = (NN_prediction >= NN_threshold)
|
778 |
+
|
779 |
+
"""## **Calculating the performance of each model in the train dataset**"""
|
780 |
+
|
781 |
+
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score, f1_score
|
782 |
+
methods = [decision_tree_prediction, random_forest_prediction,svm_polynomial_prediction,log_prediction, NN_prediction]
|
783 |
+
names = ["decision_tree_model", "random_forest_model","svm_polynomial_model","log_model", 'neural_net']
|
784 |
+
accuracy = []
|
785 |
+
precision =[]
|
786 |
+
recall = []
|
787 |
+
ROC = []
|
788 |
+
F1= []
|
789 |
+
for method in methods:
|
790 |
+
accuracyy = accuracy_score(train_label_binary, method)
|
791 |
+
accuracy.append(accuracyy)
|
792 |
+
precision1 = precision_score(train_label_binary, method)
|
793 |
+
precision.append(precision1)
|
794 |
+
recall1 = recall_score(train_label_binary, method)
|
795 |
+
recall.append(recall1)
|
796 |
+
ROC1 = roc_auc_score(train_label_binary, method)
|
797 |
+
ROC.append(ROC1)
|
798 |
+
F11 = f1_score(train_label_binary, method)
|
799 |
+
F1.append(F11)
|
800 |
+
|
801 |
+
data = {'Method': names,
|
802 |
+
'Accuracy': accuracy,
|
803 |
+
'Precision': precision,
|
804 |
+
'Recall': recall,
|
805 |
+
'ROC': ROC,
|
806 |
+
'F1 score': F1,
|
807 |
+
}
|
808 |
+
evaluation = pd.DataFrame(data, columns=['Method', "Accuracy", "Precision","Recall", "ROC", "F1 score"])
|
809 |
+
evaluation
|
810 |
+
|
811 |
+
"""## **Let's do a better Evaluation Using Cross-Validation**
|
812 |
+
|
813 |
+
**Logistic Regression cross validation**
|
814 |
+
"""
|
815 |
+
|
816 |
+
from sklearn.model_selection import cross_val_score, GridSearchCV
|
817 |
+
from sklearn.linear_model import LogisticRegression
|
818 |
+
log_regression= LogisticRegression(solver ='liblinear')
|
819 |
+
penalty = ['l1', 'l2']
|
820 |
+
C = [1,0.1,0.01,0.001]
|
821 |
+
hyperparameters = dict(C=C, penalty=penalty)
|
822 |
+
classifier = GridSearchCV(log_regression, hyperparameters, cv=10, verbose =0)
|
823 |
+
best_model = classifier.fit(X_train_prepared, train_label_binary )
|
824 |
+
|
825 |
+
#printing out the best parameters for Logistic Regression model
|
826 |
+
print('Best penalty:', best_model.best_estimator_.get_params()['penalty'])
|
827 |
+
print('Best C:', best_model.best_estimator_.get_params()['C'])
|
828 |
+
|
829 |
+
model = LogisticRegression(solver ='liblinear', **best_model.best_params_)
|
830 |
+
model.fit(X_train_prepared, train_label_binary )
|
831 |
+
logistic_prediction= model.predict(X_train_prepared)
|
832 |
+
logistic_prediction
|
833 |
+
|
834 |
+
#calculating the accuracy of the model
|
835 |
+
scores = cross_val_score(model, X_train_prepared, train_label_binary )
|
836 |
+
scores
|
837 |
+
print("%0.2f accuracy with a standard deviation of %0.2f" % (scores.mean(), scores.std()))
|
838 |
+
|
839 |
+
from sklearn.model_selection import cross_val_predict
|
840 |
+
from sklearn.metrics import roc_curve
|
841 |
+
|
842 |
+
y_scores = cross_val_predict(model, X_train_prepared, train_label_binary, cv= 10, method = "decision_function") #decision_function
|
843 |
+
fpr, tpr, thresholds = roc_curve (train_label_binary, y_scores)
|
844 |
+
|
845 |
+
def plot_roc_curve(fpr, tpr, label =None):
|
846 |
+
plt.plot(fpr, tpr, linewidth=2, label = label)
|
847 |
+
plt.plot([0,1], [0,1], "k--")
|
848 |
+
plot_roc_curve(fpr, tpr)
|
849 |
+
plt.title('ROC curve for Logistic Regression')
|
850 |
+
plt.xlabel('False Positive Rate (1- specifity')
|
851 |
+
plt.ylabel('True Positive Rate (Recall)')
|
852 |
+
plt.legend(['Logistic Regression'],loc ="lower right")
|
853 |
+
plt.grid()
|
854 |
+
plt.show()
|
855 |
+
|
856 |
+
"""**Support Vector Machine Cross validation**"""
|
857 |
+
|
858 |
+
from sklearn.svm import SVC
|
859 |
+
|
860 |
+
# hyperparameter_set = {'C': [0.001, 0.01, 0.1, 1, 10], 'kernel': ['linear', 'rbf'], 'gamma': [0.001, 0.01, 0.1, 1]}
|
861 |
+
# svm = SVC()
|
862 |
+
# classifier2 = GridSearchCV(svm, hyperparameter_set, cv=10, verbose =0)
|
863 |
+
# best_SV = classifier2.fit(X_train_prepared, train_label_binary )
|
864 |
+
|
865 |
+
# #printing out the best parameters for SVM model
|
866 |
+
# print('Best kernel:', best_SV.best_params_['kernel'])
|
867 |
+
# print('Best C:', best_SV.best_params_['C'])
|
868 |
+
# print('Best gamma:', best_SV.best_params_['gamma'])
|
869 |
+
|
870 |
+
SVM_final_model = SVC(C=1, kernel= 'rbf', gamma = 0.1, probability=True)
|
871 |
+
SVM_final_model.fit(X_train_prepared, train_label_binary)
|
872 |
+
svm_prediction= SVM_final_model.predict(X_train_prepared)
|
873 |
+
svm_prediction
|
874 |
+
|
875 |
+
#calculating the accuracy of the model
|
876 |
+
scores = cross_val_score(SVM_final_model, X_train_prepared, train_label_binary )
|
877 |
+
scores
|
878 |
+
print("%0.2f accuracy with a standard deviation of %0.2f" % (scores.mean(), scores.std()))
|
879 |
+
|
880 |
+
#Drawing the ROC curve for SVM
|
881 |
+
from sklearn.model_selection import cross_val_predict
|
882 |
+
from sklearn.metrics import roc_curve
|
883 |
+
|
884 |
+
y_scores = cross_val_predict(model, X_train_prepared, train_label_binary, cv= 10, method = "decision_function")
|
885 |
+
fpr, tpr, thresholds = roc_curve (train_label_binary, y_scores)
|
886 |
+
|
887 |
+
def plot_roc_curve(fpr, tpr, label =None):
|
888 |
+
plt.plot(fpr, tpr, linewidth=2, label = label)
|
889 |
+
plt.plot([0,1], [0,1], "k--")
|
890 |
+
plot_roc_curve(fpr, tpr)
|
891 |
+
plt.title('ROC curve for Support Vector Machine')
|
892 |
+
plt.xlabel('False Positive Rate (1- specifity')
|
893 |
+
plt.ylabel('True Positive Rate (Recall)')
|
894 |
+
plt.legend(['Support Vector Machine '],loc ="lower right")
|
895 |
+
plt.grid()
|
896 |
+
plt.show()
|
897 |
+
|
898 |
+
"""**Random Forest Cross Validation**"""
|
899 |
+
|
900 |
+
# hyperparameter_set = {'n_estimators': [100, 200, 300, 400], 'max_features': ['auto', 'sqrt']}
|
901 |
+
# random_forest = RandomForestClassifier()
|
902 |
+
|
903 |
+
# classifier3 = GridSearchCV(random_forest, hyperparameter_set, cv=10, verbose =0)
|
904 |
+
# best_model3 = classifier3.fit(X_train_prepared, train_label_binary )
|
905 |
+
|
906 |
+
# print('Best n_estimators:', best_model3.best_params_['n_estimators'])
|
907 |
+
# print('Best max_features:', best_model3.best_params_['max_features'])
|
908 |
+
|
909 |
+
model3 = RandomForestClassifier(n_estimators = 200, max_features= 'sqrt')
|
910 |
+
model3.fit(X_train_prepared, train_label_binary)
|
911 |
+
random_forest_prediction= model3.predict(X_train_prepared)
|
912 |
+
random_forest_prediction
|
913 |
+
|
914 |
+
#calculating the accuracy of the model
|
915 |
+
scores = cross_val_score(model3, X_train_prepared, train_label_binary )
|
916 |
+
scores
|
917 |
+
print("%0.2f accuracy with a standard deviation of %0.2f" % (scores.mean(), scores.std()))
|
918 |
+
|
919 |
+
#Drawing the ROC curve for SVM
|
920 |
+
from sklearn.model_selection import cross_val_predict
|
921 |
+
from sklearn.metrics import roc_curve
|
922 |
+
|
923 |
+
y_scores = cross_val_predict(model, X_train_prepared, train_label_binary, cv= 10, method = "decision_function") #decision_function
|
924 |
+
fpr, tpr, thresholds = roc_curve (train_label_binary, y_scores)
|
925 |
+
|
926 |
+
def plot_roc_curve(fpr, tpr, label =None):
|
927 |
+
plt.plot(fpr, tpr, linewidth=2, label = label)
|
928 |
+
plt.plot([0,1], [0,1], "k--")
|
929 |
+
plot_roc_curve(fpr, tpr)
|
930 |
+
plt.title('ROC curve for Random Forest')
|
931 |
+
plt.xlabel('False Positive Rate (1- specifity')
|
932 |
+
plt.ylabel('True Positive Rate (Recall)')
|
933 |
+
plt.legend(['Random Forest '],loc ="lower right")
|
934 |
+
plt.grid()
|
935 |
+
plt.show()
|
936 |
+
|
937 |
+
"""**Showing the feature importance analysis in random forest.**"""
|
938 |
+
|
939 |
+
from pandas import DataFrame
|
940 |
+
random_forest = RandomForestClassifier(n_estimators = 300, random_state=60)
|
941 |
+
random_forest.fit(X_train_prepared,train_label_binary)
|
942 |
+
random_forest_importance = random_forest.feature_importances_
|
943 |
+
print(random_forest_importance)
|
944 |
+
|
945 |
+
features = original_df.columns
|
946 |
+
|
947 |
+
importances = random_forest_importance
|
948 |
+
indices = np.argsort(importances)
|
949 |
+
|
950 |
+
|
951 |
+
|
952 |
+
**Calculating the evaluation metrics for each model and then adding the data in pandas DataFrame**
|
953 |
+
"""
|
954 |
+
|
955 |
+
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score, f1_score
|
956 |
+
predictions = [logistic_prediction,svm_prediction, random_forest_prediction]
|
957 |
+
names = ["Logistic_regression model","Support Vector Machine model", "Random_forest_model"]
|
958 |
+
accuracy = []
|
959 |
+
precision =[]
|
960 |
+
recall = []
|
961 |
+
ROC = []
|
962 |
+
F1= []
|
963 |
+
for i in predictions:
|
964 |
+
accuracyy = accuracy_score(train_label_binary, i)
|
965 |
+
accuracy.append(accuracyy)
|
966 |
+
precision1 = precision_score(train_label_binary, i)
|
967 |
+
precision.append(precision1)
|
968 |
+
recall1 = recall_score(train_label_binary, i)
|
969 |
+
recall.append(recall1)
|
970 |
+
ROC1 = roc_auc_score(train_label_binary, i)
|
971 |
+
ROC.append(ROC1)
|
972 |
+
F11 = f1_score(train_label_binary, i)
|
973 |
+
F1.append(F11)
|
974 |
+
|
975 |
+
data2 = {'Method': names,
|
976 |
+
'Accuracy': accuracy,
|
977 |
+
'Precision': precision,
|
978 |
+
'Recall': recall,
|
979 |
+
'ROC': ROC,
|
980 |
+
'F1 score': F1,
|
981 |
+
}
|
982 |
+
evaluation = pd.DataFrame(data2, columns=['Method', "Accuracy", "Precision","Recall", "ROC", "F1 score"])
|
983 |
+
evaluation
|
984 |
+
|
985 |
+
"""**Drawing the ROC curve of all models on the train dataset**"""
|
986 |
+
|
987 |
+
from sklearn.model_selection import cross_val_predict
|
988 |
+
from sklearn.metrics import roc_curve
|
989 |
+
roc_curve_rates = []
|
990 |
+
for model in [model3, SVM_final_model, model]: #models are 'Logistic Regression', 'RandomForestClassifier', 'SVC'
|
991 |
+
#finds the predicted probability for the sets and model
|
992 |
+
predict_probability = cross_val_predict(model, X_train_prepared, train_label_binary, cv= 10, method = "predict_proba")
|
993 |
+
#gets the probs for pos class
|
994 |
+
y_scorse = predict_probability[:,1]
|
995 |
+
#calculates the fpr and tpr with te scores
|
996 |
+
fpr, tpr, threshold = roc_curve(train_label_binary, y_scorse)
|
997 |
+
roc_curve_rates.append({'fpr': fpr, 'tpr': tpr})
|
998 |
+
|
999 |
+
|
1000 |
+
#Takes the dics array and plots each line on the same graph
|
1001 |
+
line_names = ['Logistic Regression', 'RandomForestClassifier', 'SVC']
|
1002 |
+
plt.plot(fpr, tpr, linewidth=2)
|
1003 |
+
for i in range(len(roc_curve_rates)):
|
1004 |
+
plt.plot(roc_curve_rates[i]['fpr'], roc_curve_rates[i]['tpr'], linewidth=2, label=line_names[i])
|
1005 |
+
plt.xlim([0,1])
|
1006 |
+
plt.ylim([0,1])
|
1007 |
+
plt.plot([0,1], [0,1], "k--")
|
1008 |
+
plt.title('ROC curve')
|
1009 |
+
plt.xlabel('False Positive Rate (1 - specifity)')
|
1010 |
+
plt.ylabel('True Positive Rate (Recall)')
|
1011 |
+
plt.legend(loc ="lower right")
|
1012 |
+
plt.grid()
|
1013 |
+
plt.show()
|
1014 |
+
|
1015 |
+
"""**Optimizing the Neural Network**"""
|
1016 |
+
|
1017 |
+
# Parameters to check
|
1018 |
+
number_of_layers = [3, 4, 5, 6, 7]
|
1019 |
+
number_of_neurons = [10, 100, 100, 5000]
|
1020 |
+
|
1021 |
+
# Variables for saving data
|
1022 |
+
best_epoch = [[]];
|
1023 |
+
best_accuracy = [[]];
|
1024 |
+
i = 0;
|
1025 |
+
|
1026 |
+
# Add early stopping into model training
|
1027 |
+
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
|
1028 |
+
keras_callbacks = [
|
1029 |
+
EarlyStopping(monitor='val_loss', patience=5, mode='min', min_delta=0.0001),
|
1030 |
+
]
|
1031 |
+
|
1032 |
+
# Loop through all parameters
|
1033 |
+
for layers in number_of_layers:
|
1034 |
+
for neurons in number_of_neurons:
|
1035 |
+
print("Testing NN - Layers: "+ str(layers) + "; Neurons per layer:" + str(neurons))
|
1036 |
+
NN_model = build_NN(layers, neurons)
|
1037 |
+
train_history = NN_model.fit(X_train_prepared, train_label_binary.values.ravel(), validation_data=(X_val_prepared,validation_label_binary.values.ravel()), batch_size=128, epochs = 30, callbacks=keras_callbacks)
|
1038 |
+
# Using validation accuracy as performance metric
|
1039 |
+
accuracy = train_history.history['val_accuracy']
|
1040 |
+
best_accuracy[i].append(max(accuracy))
|
1041 |
+
best_epoch[i].append(accuracy.index(max(accuracy)))
|
1042 |
+
i = i + 1;
|
1043 |
+
best_epoch.append([])
|
1044 |
+
best_accuracy.append([])
|
1045 |
+
|
1046 |
+
# Remove last element
|
1047 |
+
best_epoch.pop(i)
|
1048 |
+
best_accuracy.pop(i)
|
1049 |
+
|
1050 |
+
# Build model with best parameters
|
1051 |
+
ideal_layers_index = best_accuracy.index(max(best_accuracy))
|
1052 |
+
ideal_layers = number_of_layers[ideal_layers_index]
|
1053 |
+
ideal_neurons = number_of_neurons[best_accuracy[ideal_layers_index].index(max(best_accuracy[ideal_layers_index]))]
|
1054 |
+
|
1055 |
+
# Print Results
|
1056 |
+
print("Best number of layers:", str(ideal_layers))
|
1057 |
+
print("Best number of neurons:", str(ideal_neurons))
|
1058 |
+
|
1059 |
+
"""## **Evaluate all the models on the Test Set**
|
1060 |
+
|
1061 |
+
|
1062 |
+
"""
|
1063 |
+
|
1064 |
+
#Logistic Regression
|
1065 |
+
logistic_regression_final_model = LogisticRegression(solver ='liblinear', **best_model.best_params_)
|
1066 |
+
logistic_regression_final_model.fit(X_train_prepared, train_label_binary )
|
1067 |
+
logistic_prediction_test= logistic_regression_final_model.predict(X_test_prepared)
|
1068 |
+
logistic_prediction_test
|
1069 |
+
|
1070 |
+
#Support Vector Machine
|
1071 |
+
SVM_final_model = SVC(C=0.1, kernel= 'linear', gamma = 'scale', probability=True)
|
1072 |
+
SVM_final_model.fit(X_train_prepared, train_label_binary)
|
1073 |
+
svm_prediction_test= SVM_final_model.predict(X_test_prepared)
|
1074 |
+
svm_prediction_test
|
1075 |
+
|
1076 |
+
# Random Forest Classifier
|
1077 |
+
random_forest_final_model = RandomForestClassifier(n_estimators = 400, max_features= 'sqrt')
|
1078 |
+
random_forest_final_model.fit(X_train_prepared, train_label_binary)
|
1079 |
+
random_forest_prediction_test= random_forest_final_model.predict(X_test_prepared)
|
1080 |
+
random_forest_prediction_test
|
1081 |
+
|
1082 |
+
# Neural Network
|
1083 |
+
keras_callbacks = [
|
1084 |
+
EarlyStopping(monitor='val_loss', patience=10, mode='min', min_delta=0.0001),
|
1085 |
+
ModelCheckpoint('./checkmodel.h5', monitor='val_loss', save_best_only=True, mode='min')
|
1086 |
+
]
|
1087 |
+
NN_final_model = build_NN(ideal_layers, ideal_neurons, dropout=0.15)
|
1088 |
+
NN_final_model.fit(X_train_prepared, train_label_binary, validation_data=(X_val_prepared,validation_label_binary.values.ravel()), batch_size=128, epochs = 30, callbacks=keras_callbacks)
|
1089 |
+
NN_prediction_test= NN_final_model.predict(X_test_prepared)
|
1090 |
+
|
1091 |
+
# Prepare prediction to be comparable
|
1092 |
+
NN_prediction_test = (NN_prediction_test >= NN_threshold)
|
1093 |
+
|
1094 |
+
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score, f1_score
|
1095 |
+
predictions = [logistic_prediction_test,svm_prediction_test, random_forest_prediction_test, NN_prediction_test]
|
1096 |
+
names = ["Logistic_regression_test","Support_vector_machine_test", "Random_forest_test", "Neural_net_test"]
|
1097 |
+
accuracy = []
|
1098 |
+
precision =[]
|
1099 |
+
recall = []
|
1100 |
+
ROC = []
|
1101 |
+
F1= []
|
1102 |
+
for i in predictions:
|
1103 |
+
accuracyy = accuracy_score(test_label_binary, i)
|
1104 |
+
accuracy.append(accuracyy)
|
1105 |
+
precision1 = precision_score(test_label_binary, i)
|
1106 |
+
precision.append(precision1)
|
1107 |
+
recall1 = recall_score(test_label_binary, i)
|
1108 |
+
recall.append(recall1)
|
1109 |
+
ROC1 = roc_auc_score(test_label_binary, i)
|
1110 |
+
ROC.append(ROC1)
|
1111 |
+
F11 = f1_score(test_label_binary, i)
|
1112 |
+
F1.append(F11)
|
1113 |
+
|
1114 |
+
data3 = {'Method': names,
|
1115 |
+
'Accuracy': accuracy,
|
1116 |
+
'Precision': precision,
|
1117 |
+
'Recall': recall,
|
1118 |
+
'ROC': ROC,
|
1119 |
+
'F1 score': F1,
|
1120 |
+
}
|
1121 |
+
evaluation = pd.DataFrame(data3, columns=['Method', "Accuracy", "Precision","Recall", "ROC", "F1 score"])
|
1122 |
+
evaluation
|
1123 |
+
|
1124 |
+
"""**Trade-off between precision and recall** **for** :
|
1125 |
+
|
1126 |
+
1. Logistic Regression
|
1127 |
+
2. Support Vector Machine
|
1128 |
+
1. Random Forest
|
1129 |
+
|
1130 |
+
|
1131 |
+
|
1132 |
+
|
1133 |
+
|
1134 |
+
"""
|
1135 |
+
|
1136 |
+
from sklearn.metrics import precision_recall_curve
|
1137 |
+
import matplotlib.pyplot as plt
|
1138 |
+
|
1139 |
+
y_score = logistic_regression_final_model.predict_proba(X_test_prepared)[:, 1]
|
1140 |
+
|
1141 |
+
#calculate precision and recall
|
1142 |
+
precision, recall, thresholds = precision_recall_curve(test_label_binary, y_score)
|
1143 |
+
#create precision recall curve
|
1144 |
+
fig, ax = plt.subplots()
|
1145 |
+
ax.plot(recall, precision, color='red')
|
1146 |
+
#add axis labels to plot
|
1147 |
+
ax.set_title('Precision-Recall Curve for Logistic Regression')
|
1148 |
+
ax.set_ylabel('Precision')
|
1149 |
+
ax.set_xlabel('Recall')
|
1150 |
+
|
1151 |
+
#display plot
|
1152 |
+
plt.grid(True)
|
1153 |
+
plt.show()
|
1154 |
+
|
1155 |
+
from sklearn.metrics import precision_recall_curve
|
1156 |
+
import matplotlib.pyplot as plt
|
1157 |
+
|
1158 |
+
y_score = random_forest_final_model.predict_proba(X_test_prepared)[:, 1]
|
1159 |
+
|
1160 |
+
#calculate precision and recall
|
1161 |
+
precision, recall, thresholds = precision_recall_curve(test_label_binary, y_score)
|
1162 |
+
#create precision recall curve
|
1163 |
+
fig, ax = plt.subplots()
|
1164 |
+
ax.plot(recall, precision, color='blue')
|
1165 |
+
#add axis labels to plot
|
1166 |
+
ax.set_title('Precision-Recall Curve for Support Vector Machine')
|
1167 |
+
ax.set_ylabel('Precision')
|
1168 |
+
ax.set_xlabel('Recall')
|
1169 |
+
|
1170 |
+
#display plot
|
1171 |
+
plt.grid(True)
|
1172 |
+
plt.show()
|
1173 |
+
|
1174 |
+
from sklearn.metrics import precision_recall_curve
|
1175 |
+
import matplotlib.pyplot as plt
|
1176 |
+
|
1177 |
+
y_score = SVM_final_model.predict_proba(X_test_prepared)[:, 1]
|
1178 |
+
|
1179 |
+
#calculate precision and recall
|
1180 |
+
precision, recall, thresholds = precision_recall_curve(test_label_binary, y_score)
|
1181 |
+
#create precision recall curve
|
1182 |
+
fig, ax = plt.subplots()
|
1183 |
+
ax.plot(recall, precision, color='purple')
|
1184 |
+
#add axis labels to plot
|
1185 |
+
ax.set_title('Precision-Recall Curve for Random Forest Model')
|
1186 |
+
ax.set_ylabel('Precision')
|
1187 |
+
ax.set_xlabel('Recall')
|
1188 |
+
|
1189 |
+
#display plot
|
1190 |
+
plt.grid(True)
|
1191 |
+
plt.show()
|
1192 |
+
|
1193 |
+
"""**Drawing the ROC curve of all models on the test dataset**"""
|
1194 |
+
|
1195 |
+
from sklearn.model_selection import cross_val_predict
|
1196 |
+
from sklearn.metrics import roc_curve
|
1197 |
+
roc_curve_rates = []
|
1198 |
+
for model in [logistic_regression_final_model, random_forest_final_model,SVM_final_model]: #models are 'Logistic Regression', 'RandomForestClassifier', 'SVC'
|
1199 |
+
#finds the predicted probability for the sets and model
|
1200 |
+
predict_probability = cross_val_predict(logistic_regression_final_model, X_test_prepared, test_label_binary, cv= 10, method = "predict_proba")
|
1201 |
+
#gets the probs for pos class
|
1202 |
+
y_scorse = predict_probability[:,1]
|
1203 |
+
#calculates the fpr and tpr with te scores
|
1204 |
+
fpr, tpr, threshold = roc_curve(test_label_binary, y_scorse)
|
1205 |
+
roc_curve_rates.append({'fpr': fpr, 'tpr': tpr})
|
1206 |
+
|
1207 |
+
#Takes the dics array and plots each line on the same graph
|
1208 |
+
line_names = ['Logistic Regression', 'RandomForestClassifier', 'SVC']
|
1209 |
+
plt.plot(fpr, tpr, linewidth=2)
|
1210 |
+
for i in range(len(roc_curve_rates)):
|
1211 |
+
plt.plot(roc_curve_rates[i]['fpr'], roc_curve_rates[i]['tpr'], linewidth=2, label=line_names[i])
|
1212 |
+
plt.xlim([0,1])
|
1213 |
+
plt.ylim([0,1])
|
1214 |
+
plt.plot([0,1], [0,1], "k--")
|
1215 |
+
plt.title('ROC curve')
|
1216 |
+
plt.xlabel('False Positive Rate (1 - specifity)')
|
1217 |
+
plt.ylabel('True Positive Rate (Recall)')
|
1218 |
+
plt.legend(loc ="lower right")
|
1219 |
+
plt.grid()
|
1220 |
+
plt.show()
|
1221 |
+
|
1222 |
+
"""#**PART III ----> Gradio Implementation**
|
1223 |
+
|
1224 |
+
|
1225 |
+
|
1226 |
+
|
1227 |
+
"""
|
1228 |
+
|
1229 |
+
# Install Gradio
|
1230 |
+
!pip install --quiet gradio
|
1231 |
+
|
1232 |
+
# Import Gradio Library
|
1233 |
+
import gradio as gr
|
1234 |
+
|
1235 |
+
# Define callback function
|
1236 |
+
def warfarin_callback(age, height, weight, gender, race, diabetes, medication, Cyp2C9, VKORC1, INR, model):
|
1237 |
+
# Input validation
|
1238 |
+
if not gender:
|
1239 |
+
return "Please select the patient's gender"
|
1240 |
+
if not race:
|
1241 |
+
return "Please select the patient's race"
|
1242 |
+
|
1243 |
+
# Extract medication
|
1244 |
+
simvastatin = 0.0
|
1245 |
+
amiodarone = 0.0
|
1246 |
+
if 'Simvastatin (Zocor)' in medication: simvastatin = 1.0
|
1247 |
+
if 'Amiodarone (Cordarone)' in medication: amiodarone = 1.0
|
1248 |
+
# Categorize age
|
1249 |
+
age_categories = ['10 - 19', '20 - 29', '30 - 39', '40 - 49', '50 - 59', '60 - 69', '70 - 79', '80 - 89', '90+']
|
1250 |
+
age_category = age_categories[int(np.floor(age/10)) - 1]
|
1251 |
+
|
1252 |
+
# Gender, Race (Reported), Age, Height (cm), Weight (kg), Diabetes, Simvastatin (Zocor), Amiodarone (Cordarone), Target INR, INR on Reported Therapeutic Dose of Warfarin, Cyp2C9 genotypes, VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T
|
1253 |
+
input_df = pd.DataFrame([[gender.lower(), race, age_category, height, weight, float(diabetes), simvastatin, amiodarone, 0.0, INR, Cyp2C9, VKORC1]], columns=["Gender", "Race (Reported)", "Age", "Height (cm)", "Weight (kg)", "Diabetes", "Simvastatin (Zocor)", "Amiodarone (Cordarone)", "Target INR", "INR on Reported Therapeutic Dose of Warfarin", "Cyp2C9 genotypes", "VKORC1 genotype: -1639 G>A (3673); chr16:31015190; rs9923231; C/T"])
|
1254 |
+
preprocessed_input_df = full_preprocess_function(input_df)
|
1255 |
+
|
1256 |
+
# Model Selection
|
1257 |
+
if model == "Logistic Regression":
|
1258 |
+
prediction = (logistic_regression_final_model.predict(preprocessed_input_df))
|
1259 |
+
elif model == "Support Vector Machine":
|
1260 |
+
prediction = (SVM_final_model.predict(preprocessed_input_df))
|
1261 |
+
elif model == "Random Forest":
|
1262 |
+
prediction = (random_forest_final_model.predict(preprocessed_input_df))
|
1263 |
+
elif model == "Neural Network":
|
1264 |
+
prediction = (NN_final_model.predict(preprocessed_input_df))
|
1265 |
+
prediction = prediction > NN_threshold
|
1266 |
+
else:
|
1267 |
+
return "Please select a Machine Learning Model"
|
1268 |
+
|
1269 |
+
if prediction:
|
1270 |
+
return "The recommended Warfarin Dose is >30mg"
|
1271 |
+
else:
|
1272 |
+
return "The recommended Warfarin Dose is <=30mg"
|
1273 |
+
|
1274 |
+
# Define output module as Warfarin dose
|
1275 |
+
output_dose = gr.Textbox(label = "Warfarin Dose")
|
1276 |
+
|
1277 |
+
# Define all input modules
|
1278 |
+
input_age = gr.Slider(10, 100, step=1, label = "Age", default=30)
|
1279 |
+
input_height = gr.Number(label = "Height (cm)")
|
1280 |
+
input_weight = gr.Number(label = "Weight (kg)")
|
1281 |
+
input_gender = gr.Radio(choices=["Male", "Female"], label = "Gender")
|
1282 |
+
input_race = gr.Dropdown(choices=['Asian', 'Black or African American', 'Caucasian', 'Chinese', 'Han Chinese', 'Hispanic', 'Indian', 'Intermediate', 'Japanese', 'Korean', 'Malay', 'Other','Other Mixed Race', 'Unspecified', 'White'], label = "Race")
|
1283 |
+
input_diabetes = gr.Checkbox(label = "Is the patient Diabetic?")
|
1284 |
+
input_medication = gr.CheckboxGroup(["Simvastatin (Zocor)", "Amiodarone (Cordarone)"], label = "Is the patient taking any of the following medication?")
|
1285 |
+
input_Cyp269 = gr.Dropdown(['*1/*1', '*1/*11', '*1/*13', '*1/*14', '*1/*2', '*1/*3', '*1/*5', '*1/*6', '*2/*2', '*2/*3', '*3/*3'], label = "Cyp2C9 genotype")
|
1286 |
+
input_VKORC1 = gr.Dropdown(['A/A', 'A/G', 'G/G', 'Unknown'], label = "VKORC1 genotype")
|
1287 |
+
input_INR = gr.Slider(1, 5, step=0.01, label = "INR on Reported Therapeutic Dose of Warfarin", default=2.45)
|
1288 |
+
input_model = gr.Dropdown(choices=["Logistic Regression", "Support Vector Machine", "Random Forest", "Neural Network" ], label = "Machine Learning Model")
|
1289 |
+
|
1290 |
+
gr.Interface(fn=warfarin_callback, inputs=[input_age, input_height, input_weight,input_gender, input_race, input_diabetes, input_medication, input_Cyp269, input_VKORC1, input_INR, input_model], outputs=output_dose).launch(debug=False)
|
requirements.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
tensorflow
|
2 |
+
numpy
|
3 |
+
matplotlib
|
4 |
+
scikit-learn
|
5 |
+
pandas
|
6 |
+
joblib
|
7 |
+
opencv-python
|