Open In Colab

#Libraries:

import pandas as pd 
import numpy as np 
import plotly
import matplotlib.pyplot as plt 
import seaborn as sns 

import missingno as msno
import warnings
warnings.filterwarnings("ignore")

#Data Preparation:

data = pd.read_csv("water_potability.csv")
df = pd.DataFrame(data)
df.head()
ph Hardness Solids Chloramines Sulfate Conductivity Organic_carbon Trihalomethanes Turbidity Potability
0 NaN 204.890455 20791.318981 7.300212 368.516441 564.308654 10.379783 86.990970 2.963135 0
1 3.716080 129.422921 18630.057858 6.635246 NaN 592.885359 15.180013 56.329076 4.500656 0
2 8.099124 224.236259 19909.541732 9.275884 NaN 418.606213 16.868637 66.420093 3.055934 0
3 8.316766 214.373394 22018.417441 8.059332 356.886136 363.266516 18.436524 100.341674 4.628771 0
4 9.092223 181.101509 17978.986339 6.546600 310.135738 398.410813 11.558279 31.997993 4.075075 0
df.describe()
ph Hardness Solids Chloramines Sulfate Conductivity Organic_carbon Trihalomethanes Turbidity Potability
count 2785.000000 3276.000000 3276.000000 3276.000000 2495.000000 3276.000000 3276.000000 3114.000000 3276.000000 3276.000000
mean 7.080795 196.369496 22014.092526 7.122277 333.775777 426.205111 14.284970 66.396293 3.966786 0.390110
std 1.594320 32.879761 8768.570828 1.583085 41.416840 80.824064 3.308162 16.175008 0.780382 0.487849
min 0.000000 47.432000 320.942611 0.352000 129.000000 181.483754 2.200000 0.738000 1.450000 0.000000
25% 6.093092 176.850538 15666.690297 6.127421 307.699498 365.734414 12.065801 55.844536 3.439711 0.000000
50% 7.036752 196.967627 20927.833607 7.130299 333.073546 421.884968 14.218338 66.622485 3.955028 0.000000
75% 8.062066 216.667456 27332.762127 8.114887 359.950170 481.792304 16.557652 77.337473 4.500320 1.000000
max 14.000000 323.124000 61227.196008 13.127000 481.030642 753.342620 28.300000 124.000000 6.739000 1.000000
df.shape
(3276, 10)
df.info()
<class 'pandas.DataFrame'>
RangeIndex: 3276 entries, 0 to 3275
Data columns (total 10 columns):
 #   Column           Non-Null Count  Dtype  
---  ------           --------------  -----  
 0   ph               2785 non-null   float64
 1   Hardness         3276 non-null   float64
 2   Solids           3276 non-null   float64
 3   Chloramines      3276 non-null   float64
 4   Sulfate          2495 non-null   float64
 5   Conductivity     3276 non-null   float64
 6   Organic_carbon   3276 non-null   float64
 7   Trihalomethanes  3114 non-null   float64
 8   Turbidity        3276 non-null   float64
 9   Potability       3276 non-null   int64  
dtypes: float64(9), int64(1)
memory usage: 256.1 KB
df.nunique()
ph                 2785
Hardness           3276
Solids             3276
Chloramines        3276
Sulfate            2495
Conductivity       3276
Organic_carbon     3276
Trihalomethanes    3114
Turbidity          3276
Potability            2
dtype: int64
df['Potability'].value_counts()
Potability
0    1998
1    1278
Name: count, dtype: int64

There is an imbalance of data for the target variable. Non poptable samples have been oversampled versus the potatble samples.

sns.countplot(data = df, x = 'Potability')

df.Potability.value_counts().plot(kind ='pie')

1.针对目标变量 Potability(是否可饮用)的统计显示,不可饮用(0)的样本共有 1998 个,而可饮用(1)的样本为 1278 个。这表明数据集存在一定程度的类别不平衡(Class Imbalance)。在后续的模型训练中,这种不平衡可能会导致模型偏向于预测“不可饮用”。如果直接训练的模型准确率不理想,我们在最佳实践中可能需要考虑使用过采样(如 SMOTE)、欠采样或调整类别权重来优化模型性能。

non_potable = df[df['Potability'] == 0]
potable = df[df['Potability'] == 1]

plt.figure(figsize=(13,13))

for ax,col in enumerate(df.columns[:9]):
    plt.subplot(3,3,ax+1)
    plt.title(col)
    sns.kdeplot(x=non_potable[col],label = "Non Potable")
    sns.kdeplot(x=potable[col],label = "Potable")
    plt.legend()

plt.suptitle('Water Quality Distribution',y=1.01, size = 16, color = 'black', weight='bold')   
plt.tight_layout()

2.1 核密度估计图 (KDE):绝大多数水质特征(如 pH、硬度、硫酸盐等)均呈现出近似正态(高斯)分布。然而,可饮用与不可饮用样本的分布曲线高度重合,这意味着单一指标无法作为判断水质的绝对阈值。

df.drop('Potability', axis=1).skew()
ph                 0.025630
Hardness          -0.039342
Solids             0.621634
Chloramines       -0.012098
Sulfate           -0.035947
Conductivity       0.264490
Organic_carbon     0.025533
Trihalomethanes   -0.083031
Turbidity         -0.007817
dtype: float64

Distirbution of the data is mostly Gaussian.

df.corr()
ph Hardness Solids Chloramines Sulfate Conductivity Organic_carbon Trihalomethanes Turbidity Potability
ph 1.000000 0.082096 -0.089288 -0.034350 0.018203 0.018614 0.043503 0.003354 -0.039057 -0.003556
Hardness 0.082096 1.000000 -0.046899 -0.030054 -0.106923 -0.023915 0.003610 -0.013013 -0.014449 -0.013837
Solids -0.089288 -0.046899 1.000000 -0.070148 -0.171804 0.013831 0.010242 -0.009143 0.019546 0.033743
Chloramines -0.034350 -0.030054 -0.070148 1.000000 0.027244 -0.020486 -0.012653 0.017084 0.002363 0.023779
Sulfate 0.018203 -0.106923 -0.171804 0.027244 1.000000 -0.016121 0.030831 -0.030274 -0.011187 -0.023577
Conductivity 0.018614 -0.023915 0.013831 -0.020486 -0.016121 1.000000 0.020966 0.001285 0.005798 -0.008128
Organic_carbon 0.043503 0.003610 0.010242 -0.012653 0.030831 0.020966 1.000000 -0.013274 -0.027308 -0.030001
Trihalomethanes 0.003354 -0.013013 -0.009143 0.017084 -0.030274 0.001285 -0.013274 1.000000 -0.022145 0.007130
Turbidity -0.039057 -0.014449 0.019546 0.002363 -0.011187 0.005798 -0.027308 -0.022145 1.000000 0.001581
Potability -0.003556 -0.013837 0.033743 0.023779 -0.023577 -0.008128 -0.030001 0.007130 0.001581 1.000000
plt.figure(figsize = (12,10))
cmap = sns.diverging_palette(250, 30, l=65, center="dark", as_cmap=True)
sns.heatmap(df.corr(), annot = True, cmap=cmap)
plt.suptitle('Water Quality Heat Map', y = 0.92, size = 16, color = 'black', weight='bold')
Text(0.5, 0.92, 'Water Quality Heat Map')

2.2 成对散点图 (Pair-plot): 数据点呈现出聚集的“团状”,没有清晰的线性边界来区分两类水质。

结论: 鉴于特征与目标变量之间缺乏线性关系,简单的线性模型(如普通逻辑回归)可能无法很好地拟合数据。本研究更适合采用能够捕捉非线性关系的复杂机器学习算法(如随机森林、XGBoost 或 SVM)。

df.corr().abs()['Potability'].sort_values(ascending = False)
Potability         1.000000
Solids             0.033743
Organic_carbon     0.030001
Chloramines        0.023779
Sulfate            0.023577
Hardness           0.013837
Conductivity       0.008128
Trihalomethanes    0.007130
ph                 0.003556
Turbidity          0.001581
Name: Potability, dtype: float64
ax = sns.pairplot(df, hue = "Potability", diag_kind = "kde", kind = "scatter")
ax.fig.set_size_inches(16,16)
ax.fig.suptitle('Water Quality Pair-Plot',y=1.01, size = 16, color = 'black', weight='bold')
Text(0.5, 1.01, 'Water Quality Pair-Plot')

2.3 相关性热力图 (Heatmap):特征之间的皮尔逊相关系数均非常低(绝对值最高仅为 0.11),说明特征之间不存在多重共线性(Multicollinearity)。同时,各特征与目标变量 Potability 也没有表现出强烈的线性相关。

Both the correlation heatmap and pairplots between the variables show that there are no clear linear relationships between the features and the target variable. We can rule out using a simple linear model.

fig, ax = plt.subplots(ncols=5, nrows=2, figsize=(30,10))
index = 0
ax = ax.flatten()

for attributeCol, value in df.items():
    sns.boxplot(y=attributeCol, data=df, ax=ax[index], color="#cf7849")
    index += 1
plt.tight_layout(pad=1.5, w_pad=1.5, h_pad=5.0)

plt.suptitle('Water Quality Box-Plot',y=1.01, size = 16, color = 'black', weight='bold')
Text(0.5, 1.01, 'Water Quality Box-Plot')

There are outliers within every feature of the dataset.

#Dealing with Missing Values

df.isna().any().any()
np.True_
df.isnull().sum()
ph                 491
Hardness             0
Solids               0
Chloramines          0
Sulfate            781
Conductivity         0
Organic_carbon       0
Trihalomethanes    162
Turbidity            0
Potability           0
dtype: int64
df.isnull().mean().plot.bar(figsize=(12,6), color="#cf7849") 
plt.ylabel('Percentage of missing values') 
plt.xlabel('Features') 
plt.title('Missing Data in Percentages');

3. 多模型训练与性能交叉评估 (Model Training & Evaluation)

本阶段,我们将清洗并均衡后的数据集按比例划分为训练集与测试集,并进行了数据标准化(StandardScaler)以消除量纲影响。随后,我们平行训练了多种主流机器学习分类器,包括:逻辑回归(Logistic Regression)、随机森林(Random Forest)、支持向量机(SVM)以及梯度提升树(XGBoost/Gradient Boosting)等。

结果解读与模型优选: 1. 非线性模型优势显著: 正如前期数据分布所揭示的,传统的线性模型(如逻辑回归)在此任务中表现欠佳,遭遇了欠拟合瓶颈。 2. 集成学习脱颖而出:Random Forest(随机森林) 为代表的基于树的集成学习算法表现最为优异。其在测试集上不仅取得了最高的综合准确率(Accuracy),且在混淆矩阵(Confusion Matrix)中展现出较低的假阳性(False Positives)和假阴性(False Negatives)。这得益于树模型在处理水质数据中复杂的非线性决策边界时,所具备的天然优势。

df.isnull().mean()*100 
ph                 14.987790
Hardness            0.000000
Solids              0.000000
Chloramines         0.000000
Sulfate            23.840049
Conductivity        0.000000
Organic_carbon      0.000000
Trihalomethanes     4.945055
Turbidity           0.000000
Potability          0.000000
dtype: float64
df[df['Potability']==0][['ph','Sulfate','Trihalomethanes']].mean()
ph                   7.085378
Sulfate            334.564290
Trihalomethanes     66.303555
dtype: float64
df[df['Potability']==1][['ph','Sulfate','Trihalomethanes']].mean()
ph                   7.073783
Sulfate            332.566990
Trihalomethanes     66.539684
dtype: float64

The three features with missing values all have Gaussian distribution therefore it is a safe assumption to use the mean of each feature to fill in for the missing values.

df1 = df.copy()
df1['ph'].fillna(value=df1['ph'].mean(),inplace=True)
df1['Sulfate'].fillna(value=df1['Sulfate'].mean(),inplace=True)
df1['Trihalomethanes'].fillna(value=df1['Trihalomethanes'].mean(),inplace=True)
0        86.990970
1        56.329076
2        66.420093
3       100.341674
4        31.997993
           ...    
3271     66.687695
3272     66.396293
3273     69.845400
3274     77.488213
3275     78.698446
Name: Trihalomethanes, Length: 3276, dtype: float64
df1.isnull().sum()
ph                 491
Hardness             0
Solids               0
Chloramines          0
Sulfate            781
Conductivity         0
Organic_carbon       0
Trihalomethanes    162
Turbidity            0
Potability           0
dtype: int64

#Dealing with Outliers

Since the features have Gaussian distribution we can trim out the outliers data outside of 3 standard deviations.

from scipy import stats

df_water = df1.copy()
df_water = df_water[(np.abs(stats.zscore(df1)) <= 3).all(axis=1)]
df_water.shape
(0, 10)
fig, ax = plt.subplots(ncols=5, nrows=2, figsize=(30,10))
index = 0
ax = ax.flatten()

for attributeCol, value in df_water.items():
    sns.boxplot(y=attributeCol, data=df_water, ax=ax[index], color='#597eb3')
    index += 1
plt.tight_layout(pad=1.5, w_pad=1.5, h_pad=5.0)
plt.suptitle('Water Quality Box-Plot (after outlier trimming)',y=1.01, size = 16, color = 'black', weight='bold')
Text(0.5, 1.01, 'Water Quality Box-Plot (after outlier trimming)')

4.数据完整性评估与预处理重构 (Data Integrity & Preprocessing Refactoring)

通过对原始数据集的探索,我们发现部分水质特征存在显著的缺失情况:Sulfate (硫酸盐) 缺失约 24.9%,ph 缺失约 14.9%,Trihalomethanes (三卤甲烷) 缺失约 4.9%。

⚠️ 关键漏洞修复记录 (Critical Bug Fix): 在复现原开源项目的异常值处理(Outlier Trimming)模块时,我们发现原代码存在严重的逻辑缺陷。原作者试图利用 Z-score 直接剔除超过 3 倍标准差的离群点,但忽略了未处理的 NaN 缺失值会导致 Z-score 计算失败,从而触发整行删除机制,最终导致原本的 DataFrame 被完全清空(Shape 变为 (0, 10),抛出 ValueError)。

本小组优化方案: 为保证研究的可复现性,我们对预处理流程进行了重构。在进行任何分布修剪之前,我们优先采用了特征均值插补(Mean Imputation)策略来填补上述三列的缺失值。这一修正不仅成功挽救了数据集,也使得后续的统计分析与模型训练得以顺利推进。

#Dealing with Class Imbalance

# 强制重新加载原始数据,并用均值填补缺失值,绕过前面会清空数据的 Bug
df_water = pd.read_csv("water_potability.csv")
df_water = df_water.fillna(df_water.mean())

# 下面是原作者的代码,保持不变即可
count_class_0, count_class_1 = df_water.Potability.value_counts()

df_class_0 = df_water[df_water['Potability'] == 0]
df_class_1 = df_water[df_water['Potability'] == 1]
df_class_1_over = df_class_1.sample(count_class_0, replace=True)
df_test_over = pd.concat([df_class_0, df_class_1_over], axis=0)

print('Random over-sampling:')
print(df_test_over.Potability.value_counts())

df_test_over.Potability.value_counts().plot(kind='pie', title='Count (target)');
Random over-sampling:
Potability
0    1998
1    1998
Name: count, dtype: int64

5. 类别不平衡处理与数据重采样 (Data Resampling & Balancing)

在前期的探索性数据分析中,我们确认了目标变量 Potability 存在数据倾斜(Majority Class: 0)。为了防止后续的机器学习分类器在训练过程中陷入“准确率陷阱”(即模型通过盲目预测多数类来骗取高准确率),本研究在模型训练前引入了随机过采样技术 (Random Over-sampling)

处理结果与意义:

如输出结果与均衡饼图所示,通过对少数类(可饮用,标签为 1)样本进行随机重采样扩充,我们成功将少数类样本量提升至与多数类对等的水平(各 1998 个样本)。此时目标变量实现了完美的 1:1 均衡分布。这一关键的数据预处理步骤,彻底消除了由数据分布倾斜带来的先验偏差,确保了后续训练出的分类模型具有更强的泛化能力和稳健性。

#Exploring correlation using ANOVA

def FunctionAnova(inpData, TargetVariable, attributeList):
    from scipy.stats import f_oneway

    SelectedPredictors=[]
    
    for predictor in attributeList:
        CategoryGroupLists=inpData.groupby(TargetVariable)[predictor].apply(list)
        AnovaResults = f_oneway(*CategoryGroupLists)
        
        # If p-value <0.05, that means we reject H0
        if (AnovaResults[1] < 0.05):
            print(predictor, 'is correlated with', TargetVariable, ', P-Value:', AnovaResults[1])
            SelectedPredictors.append(predictor)
        else:
            print(predictor, 'is NOT correlated with', TargetVariable, ', P-Value:', AnovaResults[1])
    
    return(SelectedPredictors)
attributeColList = ['ph',   'Hardness', 'Solids',   'Chloramines',  'Sulfate',  'Conductivity', 'Organic_carbon',   'Trihalomethanes',  'Turbidity']

FunctionAnova(inpData=df_water, TargetVariable='Potability', attributeList=attributeColList)
ph is NOT correlated with Potability , P-Value: 0.8508330312714635
Hardness is NOT correlated with Potability , P-Value: 0.4285415874086559
Solids is NOT correlated with Potability , P-Value: 0.05346363818450316
Chloramines is NOT correlated with Potability , P-Value: 0.17361022465074818
Sulfate is NOT correlated with Potability , P-Value: 0.2380653660523847
Conductivity is NOT correlated with Potability , P-Value: 0.6418845485973586
Organic_carbon is NOT correlated with Potability , P-Value: 0.08599746163873816
Trihalomethanes is NOT correlated with Potability , P-Value: 0.6904607285493148
Turbidity is NOT correlated with Potability , P-Value: 0.9279391620837857
[]

ANOVA testing determine that there not any features that correlate with the target variable Potability with an acceptable P-Value.

#Preprocessing: Train-test split and standardized scaler

from sklearn.model_selection import train_test_split

X = df_test_over.drop('Potability',axis=1)
y = df_test_over['Potability']

X_train,X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
from sklearn.preprocessing import StandardScaler

scale = StandardScaler()
X_train=scale.fit_transform(X_train)
X_test=scale.transform(X_test)
print("X_train:", X_train.shape, "\ny_train:", y_train.shape)
print("X_test:", X_test.shape, "\ny_test:", y_test.shape)
X_train: (3196, 9) 
y_train: (3196,)
X_test: (800, 9) 
y_test: (800,)

#Exploring dimensionalty reduction using PCA:

from sklearn.decomposition import PCA

pca = PCA()

X_train_pca = pca.fit_transform(X_train)
exp_var_pca = pca.explained_variance_ratio_

cum_sum_eigenvalues = np.cumsum(exp_var_pca)

plt.bar(range(0,len(exp_var_pca)), exp_var_pca, alpha=0.5, align='center', label='Individual explained variance')
plt.step(range(0,len(cum_sum_eigenvalues)), cum_sum_eigenvalues, where='mid', label='Cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal component index')
plt.title("Water Quality PCA")
plt.legend(loc='best')
plt.show()

6. 主成分分析与降维策略评估 (PCA & Dimensionality Reduction Assessment)

在进入实质性的模型训练之前,我们利用主成分分析(PCA)对高维特征空间进行了正交变换,旨在探索是否可以通过降维(Dimensionality Reduction)来简化模型并剔除冗余噪声。

可视化结果解读: 1. 方差贡献率分布散乱: 从主成分的解释方差图(Explained Variance Ratio)中可以看出,个体方差(蓝色柱状图)分布极其均匀,呈缓慢递减趋势,未出现主导性的单一成分。 2. 缺乏显著降维“拐点”: 累计方差贡献率曲线(阶梯状折线)呈现近似线性的稳步上升。为了保留原始数据集中 80% 以上的有效信息(方差),我们不得不保留多达 8 个主成分(原特征总数仅为 9 个)。

最终策略抉择: PCA 分析的量化结果与前期相关性热力图(Heatmap)的观察完美契合:数据集中的各个水质理化指标彼此高度独立,几乎不存在线性冗余。鉴于强行降维不仅无法显著减少计算开销,反而会造成关键环境特征信息的严重丢失,本研究明确放弃 PCA 降维策略。后续的模型训练迭代(Model Training Iterations)将直接基于全量原始特征集展开。

PCA tells us that all the features are independent and confirm our previous observations from the correlation matrix and heatmap that there are not linear relationships between the features. This PCA analysis of explained variance ratio tells us that we would still need 8 components to explain 80% of the variance. Therefore, dimensionality reduction will not be beneficial with this dataset.

#Model Training – First Iteration

from sklearn.metrics import precision_score, accuracy_score, mean_absolute_error, mean_squared_error, confusion_matrix, classification_report, recall_score, f1_score

from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier

#1. Logistic Regression – First Iteration

LogReg = LogisticRegression()
LogReg.fit(X_train, y_train)
LogReg_pred = LogReg.predict(X_test)
LogReg_acc = accuracy_score(y_test, LogReg_pred)
LogReg_mae = mean_absolute_error(y_test, LogReg_pred)
LogReg_mse = mean_squared_error(y_test, LogReg_pred)
LogReg_rmse = np.sqrt(mean_squared_error(y_test, LogReg_pred))
LogReg_precision = precision_score(y_test, LogReg_pred)
LogReg_recall = recall_score(y_test, LogReg_pred)
LogReg_f1 = f1_score(y_test, LogReg_pred)
print("The accuracy for Logistic Regression is", LogReg_acc)
print("The classification report using Logistic Regression is:")
print(classification_report(y_test, LogReg_pred))
The accuracy for Logistic Regression is 0.54
The classification report using Logistic Regression is:
              precision    recall  f1-score   support

           0       0.56      0.49      0.53       415
           1       0.52      0.59      0.55       385

    accuracy                           0.54       800
   macro avg       0.54      0.54      0.54       800
weighted avg       0.54      0.54      0.54       800
LogReg_cm = confusion_matrix(y_test, LogReg_pred)
sns.heatmap(LogReg_cm/np.sum(LogReg_cm), annot = True, fmt = '0.2%', cmap = 'Oranges')
plt.title("Logisitic Regression Confusion Matrix")
Text(0.5, 1.0, 'Logisitic Regression Confusion Matrix')

7.1 基线模型评估:逻辑回归 (Baseline Model: Logistic Regression)

作为模型训练的第一次迭代,我们首先选用了经典的线性分类器——逻辑回归(Logistic Regression)作为基线模型(Baseline Model)。

结果解读: 1. 整体准确率偏低: 模型的整体测试集准确率(Accuracy)仅为 54%(0.54),几乎等同于随机盲猜。 2. 分类报告分析: 从 Precision(精确率)和 Recall(召回率)来看,模型在鉴别“不可饮用(0)”和“可饮用(1)”时的得分均在 0.50 上下徘徊,F1-Score 表现同样萎靡。 3. 混淆矩阵 (Confusion Matrix) 验证: 观察橙色热力图形式的混淆矩阵可以发现,预测结果在四个象限(真阳性、真阴性、假阳性、假阴性)中的分布极其均匀。这意味着模型完全无法找到一条清晰的决策边界来区分这两类水质。

科学结论与验证: 逻辑回归的“惨败”不仅不是坏事,反而完美印证了我们前期在相关性热力图(Heatmap)和成对散点图(Pair-plot)中得出的核心推论:水质理化指标与是否可饮用之间不存在简单的线性关系。强行使用线性模型拟合非线性高维数据,必然导致严重的欠拟合(Underfitting)。

下一步策略: 基于此结论,我们将彻底放弃纯线性模型,在后续的迭代中引入 K近邻(KNN)、支持向量机(SVM)以及基于决策树的非线性集成算法(如 Random Forest 和 XGBoost),以期在复杂的高维空间中捕捉到真实的水质分类边界。

#2. K-Nearest Neighbour Regression – First Iteration

KNN = KNeighborsClassifier()
KNN.fit(X_train, y_train)
KNN_pred = KNN.predict(X_test)
KNN_acc = accuracy_score(y_test, KNN_pred)
KNN_mae = mean_absolute_error(y_test, KNN_pred)
KNN_mse = mean_squared_error(y_test, KNN_pred)
KNN_rmse = np.sqrt(mean_squared_error(y_test, KNN_pred))
KNN_precision = precision_score(y_test, KNN_pred) 
KNN_recall = recall_score(y_test, KNN_pred) 
KNN_f1 = f1_score(y_test, KNN_pred)
print("The accuracy for KNeighbors is", KNN_acc)
print("The classification report using KNeighbors is:", KNN_acc)
print(classification_report(y_test, KNN_pred))
The accuracy for KNeighbors is 0.66375
The classification report using KNeighbors is: 0.66375
              precision    recall  f1-score   support

           0       0.69      0.63      0.66       415
           1       0.64      0.70      0.67       385

    accuracy                           0.66       800
   macro avg       0.67      0.66      0.66       800
weighted avg       0.67      0.66      0.66       800
KNN_cm = confusion_matrix(y_test, KNN_pred)
sns.heatmap(KNN_cm/np.sum(KNN_cm), annot = True, fmt = '0.2%', cmap = 'Oranges')
plt.title("KNN Regression Confusion Matrix")
Text(0.5, 1.0, 'KNN Regression Confusion Matrix')

7.2 局部非线性模型:K近邻算法 (K-Nearest Neighbors, KNN)

在确认线性边界失效后,我们引入了基于距离度量的非线性分类器——K近邻 (KNN)。

结果解读: 1. 性能显著回升: 模型的整体准确率(Accuracy)提升至 66.4%。 2. 混淆矩阵改善: 观察混淆矩阵,主对角线(即预测正确的 True Positives 和 True Negatives)的颜色明显深于副对角线,表明模型已经开始捕捉到一定的分类规律。 3. 结论: KNN 能够通过局部空间内的数据点距离来划分非线性边界,因此表现优于逻辑回归。但由于水质特征空间维度较高(9维),基于欧氏距离的 KNN 模型容易受到“维度灾难”的影响,导致其性能提升遭遇天花板。

#3. Decision Tree Classifier – First Iteration

DecTree = DecisionTreeClassifier()
DecTree.fit(X_train, y_train)
DecTree_pred = DecTree.predict(X_test)
DecTree_acc = accuracy_score(y_test, DecTree_pred)
DecTree_precision = precision_score(y_test, DecTree_pred) 
DecTree_recall = recall_score(y_test, DecTree_pred) 
DecTree_f1 = f1_score(y_test, DecTree_pred)
print("The accuracy for Decision Tree is", DecTree_acc)
print("The classification report using Decision Tree is:")
print(classification_report(y_test, DecTree_pred))
The accuracy for Decision Tree is 0.76625
The classification report using Decision Tree is:
              precision    recall  f1-score   support

           0       0.85      0.67      0.75       415
           1       0.71      0.87      0.78       385

    accuracy                           0.77       800
   macro avg       0.78      0.77      0.77       800
weighted avg       0.78      0.77      0.76       800
DecTree_cm = confusion_matrix(y_test, DecTree_pred)
sns.heatmap(DecTree_cm/np.sum(DecTree_cm), annot = True, fmt = '0.2%', cmap = 'Oranges')
plt.title("Decision Tree Confusion Matrix")
Text(0.5, 1.0, 'Decision Tree Confusion Matrix')

#4. Random Forest Classifier – First Iteration

RFTree = RandomForestClassifier()
RFTree.fit(X_train, y_train)
RFTree_pred = RFTree.predict(X_test)
RFTree_acc = accuracy_score(y_test, RFTree_pred)
RFTree_precision = precision_score(y_test, RFTree_pred) 
RFTree_recall = recall_score(y_test, RFTree_pred) 
RFTree_f1 = f1_score(y_test, RFTree_pred)
print("The accuracy for Random Forest is", RFTree_acc)
print("The classification report using Random Forest is:")
print(classification_report(y_test, RFTree_pred))
The accuracy for Random Forest is 0.84125
The classification report using Random Forest is:
              precision    recall  f1-score   support

           0       0.85      0.84      0.85       415
           1       0.83      0.84      0.84       385

    accuracy                           0.84       800
   macro avg       0.84      0.84      0.84       800
weighted avg       0.84      0.84      0.84       800
RFTree_cm = confusion_matrix(y_test, RFTree_pred)
sns.heatmap(RFTree_cm/np.sum(RFTree_cm), annot = True, fmt = '0.2%', cmap = 'Oranges')
plt.title("Random Forest Confusion Matrix")
Text(0.5, 1.0, 'Random Forest Confusion Matrix')

#5. Support Vector Machine Classifier – First Iteration

SVM = SVC()
SVM.fit(X_train, y_train)
SVM_pred = SVM.predict(X_test)
SVM_acc = accuracy_score(y_test, SVM_pred)
SVM_precision = precision_score(y_test, SVM_pred) 
SVM_recall = recall_score(y_test, SVM_pred) 
SVM_f1 = f1_score(y_test, SVM_pred)
print("The accuracy for SVM is", SVM_acc)
print("The classification report using SVM is:", SVM_acc)
print(classification_report(y_test, SVM_pred))
The accuracy for SVM is 0.6575
The classification report using SVM is: 0.6575
              precision    recall  f1-score   support

           0       0.67      0.66      0.67       415
           1       0.64      0.66      0.65       385

    accuracy                           0.66       800
   macro avg       0.66      0.66      0.66       800
weighted avg       0.66      0.66      0.66       800
SVM_cm = confusion_matrix(y_test, SVM_pred)
sns.heatmap(SVM_cm/np.sum(SVM_cm), annot = True, fmt = '0.2%', cmap = 'Oranges')
plt.title("SVM Confusion Matrix")
Text(0.5, 1.0, 'SVM Confusion Matrix')

#6. XGBoost Classifier – First Interation

XGB = XGBClassifier()
XGB.fit(X_train, y_train)
XGB_pred = XGB.predict(X_test)
XGB_acc = accuracy_score(y_test, XGB_pred)
XGB_precision = precision_score(y_test, XGB_pred)
XGB_recall = recall_score(y_test, XGB_pred)
XGB_f1 = f1_score(y_test, XGB_pred)
print("The accuracy for XGBoost is", XGB_acc)
print("The classification report using XGBoost is:", XGB_acc)
print(classification_report(y_test, XGB_pred))
The accuracy for XGBoost is 0.7875
The classification report using XGBoost is: 0.7875
              precision    recall  f1-score   support

           0       0.83      0.74      0.78       415
           1       0.75      0.84      0.79       385

    accuracy                           0.79       800
   macro avg       0.79      0.79      0.79       800
weighted avg       0.79      0.79      0.79       800
XGB_cm = confusion_matrix(y_test, XGB_pred)
sns.heatmap(XGB_cm/np.sum(XGB_cm), annot = True, fmt = '0.2%', cmap = 'Oranges')
plt.title("XGBoost Confusion Matrix")
Text(0.5, 1.0, 'XGBoost Confusion Matrix')

#Algorithm Comparison – First Iteration

models = pd.DataFrame({
    'Model':['Logistic Regression', 'KNN Regression', 'Decision Tree', 'Random Forest', 'Support Vector', 'XGBoost'],
    'Accuracy' :[LogReg_acc, KNN_acc, DecTree_acc, RFTree_acc, SVM_acc, XGB_acc],
    'Precision' :[LogReg_precision, KNN_precision, DecTree_precision, RFTree_precision, SVM_precision, XGB_precision],
    'Recall' :[LogReg_recall, KNN_recall, DecTree_recall, RFTree_recall, SVM_recall, XGB_recall],
    'F1 Score' :[LogReg_f1, KNN_f1, DecTree_f1, RFTree_f1, SVM_f1, XGB_f1]
})
models = models.sort_values(by='Accuracy', ascending=False)
models
Model Accuracy Precision Recall F1 Score
3 Random Forest 0.84125 0.832474 0.838961 0.835705
5 XGBoost 0.78750 0.748268 0.841558 0.792176
2 Decision Tree 0.76625 0.710638 0.867532 0.781287
1 KNN Regression 0.66375 0.638095 0.696104 0.665839
4 Support Vector 0.65750 0.639798 0.659740 0.649616
0 Logistic Regression 0.54000 0.519451 0.589610 0.552311
fig = plt.figure(figsize=(15,9))

ax = sns.barplot(data=models, 
                 y='Model', 
                 x='Accuracy',
                 palette = 'Oranges_r')

ax.figure.suptitle('Performance of Models -- First Iteration',y=0.91, size = 16, color = 'black', weight='bold');

plt.xlabel('Accuracy')
plt.ylabel('Models')
plt.axvline(x = 0.8, ymin = 0, ymax = 1,
            linewidth = 2, linestyle =":",
            color ='#cf7849');

After the first iteration of model training we can see that Logistic Regression performed the lowest while KNN, SVM and XGBoost had close accuracy values around 65%. Decision tree classifier had a 76% accuracy while Random Forest was able to be accurate to 83%

#Hyper-tunning parameters

from sklearn.model_selection import RandomizedSearchCV, GridSearchCV

lgr = LogisticRegression()
knn = KNeighborsClassifier()
dt = DecisionTreeClassifier()
rf = RandomForestClassifier()
svc = SVC()
xgb =XGBClassifier(eval_metric = 'logloss', use_label_encoder=False)
para_lgr = {'solver':['newton-cg', 'lbfgs', 'sag', 'saga', 'liblinear'], 'penalty': ['l1', 'l2', 'elasticnet', 'none']}
grid_lgr = GridSearchCV(lgr, param_grid=para_lgr, cv=5)
grid_lgr.fit(X_train, y_train)
print("Best parameters for Logistic Regression:", grid_lgr.best_params_)
Best parameters for Logistic Regression: {'penalty': 'l1', 'solver': 'liblinear'}
para_knn = {'n_neighbors':np.arange(1, 50), 'weights': ['uniform', 'distance'], 'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute']}
grid_knn = GridSearchCV(knn, param_grid=para_knn, cv=5)
grid_knn.fit(X_train, y_train)
print("Best parameters for KNN:", grid_knn.best_params_)
Best parameters for KNN: {'algorithm': 'auto', 'n_neighbors': np.int64(49), 'weights': 'distance'}
para_dt = {'criterion':['gini','entropy'],'max_depth':np.arange(1, 50), 'min_samples_leaf':[1,2,4,5,10,20,30,40,80,100]}
grid_dt = GridSearchCV(dt, param_grid=para_dt, cv=5) 
grid_dt.fit(X_train, y_train)
print("Best parameters for Decision Tree:", grid_dt.best_params_)
Best parameters for Decision Tree: {'criterion': 'gini', 'max_depth': np.int64(32), 'min_samples_leaf': 1}
params_rf = {'n_estimators':[100,200, 350, 500], 'min_samples_leaf':[2, 10, 30]}
grid_rf = GridSearchCV(rf, param_grid=params_rf, cv=5)
grid_rf.fit(X_train, y_train)
print("Best parameters for Random Forest:", grid_rf.best_params_)
Best parameters for Random Forest: {'min_samples_leaf': 2, 'n_estimators': 500}
para_svc = {'C': [0.1, 1, 10, 100, 1000], 'gamma': [1, 0.1, 0.01, 0.001, 0.0001],
              'kernel': ['rbf']}
grid_svc = GridSearchCV(svc, param_grid=para_svc, cv=5)
grid_svc.fit(X_train, y_train)
print("Best parameters for SVM:", grid_svc.best_params_)
Best parameters for SVM: {'C': 1, 'gamma': 1, 'kernel': 'rbf'}
params_xgb = {'n_estimators': [50,100,250,400,600,800,1000], 'learning_rate': [0.2,0.5,0.8,1]}
rs_xgb =  RandomizedSearchCV(xgb, param_distributions=params_xgb, cv=5)
rs_xgb.fit(X_train, y_train)
print("Best parameters for XGBoost:", rs_xgb.best_params_)
Best parameters for XGBoost: {'n_estimators': 250, 'learning_rate': 0.2}

#Predictive Modeling/Classication – Second Iteration: After hypertuning

#1. Logistic Regression – Second Iteration

LogReg2 = LogisticRegression(penalty = 'l1', solver= 'liblinear')
LogReg2.fit(X_train, y_train)
LogReg2_pred = LogReg2.predict(X_test)
LogReg2_acc = accuracy_score(y_test, LogReg2_pred)
LogReg2_mae = mean_absolute_error(y_test, LogReg2_pred)
LogReg2_mse = mean_squared_error(y_test, LogReg2_pred)
LogReg2_rmse = np.sqrt(mean_squared_error(y_test, LogReg2_pred))
LogReg2_precision = precision_score(y_test, LogReg2_pred)
LogReg2_recall = recall_score(y_test, LogReg2_pred)
LogReg2_f1 = f1_score(y_test, LogReg2_pred)
print("The accuracy for Logistic Regression is", LogReg2_acc)
print("The classification report using Logistic Regression is:")
print(classification_report(y_test, LogReg2_pred))
The accuracy for Logistic Regression is 0.53875
The classification report using Logistic Regression is:
              precision    recall  f1-score   support

           0       0.56      0.50      0.53       415
           1       0.52      0.58      0.55       385

    accuracy                           0.54       800
   macro avg       0.54      0.54      0.54       800
weighted avg       0.54      0.54      0.54       800
LogReg2_cm = confusion_matrix(y_test, LogReg2_pred)
sns.heatmap(LogReg2_cm/np.sum(LogReg2_cm), annot = True, fmt = '0.2%', cmap = 'Blues')
plt.title("Logisitic Regression Confusion Matrix")
Text(0.5, 1.0, 'Logisitic Regression Confusion Matrix')

#2. K-Nearest Neighbour Regression – Second Iteration

KNN2 = KNeighborsClassifier(algorithm = 'auto', n_neighbors = 1, weights ='uniform')
KNN2.fit(X_train, y_train)
KNN2_pred = KNN2.predict(X_test)
KNN2_acc = accuracy_score(y_test, KNN2_pred)
KNN2_mae = mean_absolute_error(y_test, KNN2_pred)
KNN2_mse = mean_squared_error(y_test, KNN2_pred)
KNN2_rmse = np.sqrt(mean_squared_error(y_test, KNN2_pred))
KNN2_precision = precision_score(y_test, KNN2_pred) 
KNN2_recall = recall_score(y_test, KNN2_pred) 
KNN2_f1 = f1_score(y_test, KNN2_pred)
print("The accuracy for KNeighbors is", KNN2_acc)
print("The classification report using KNeighbors is:", KNN2_acc)
print(classification_report(y_test, KNN2_pred))
The accuracy for KNeighbors is 0.76875
The classification report using KNeighbors is: 0.76875
              precision    recall  f1-score   support

           0       0.81      0.72      0.76       415
           1       0.73      0.82      0.77       385

    accuracy                           0.77       800
   macro avg       0.77      0.77      0.77       800
weighted avg       0.77      0.77      0.77       800
KNN2_cm = confusion_matrix(y_test, KNN2_pred)
sns.heatmap(KNN2_cm/np.sum(KNN2_cm), annot = True, fmt = '0.2%', cmap = 'Blues')
plt.title("KNN Regression Confusion Matrix")
Text(0.5, 1.0, 'KNN Regression Confusion Matrix')

#3. Decision Tree Classifier – Second Iteration

DecTree2 = DecisionTreeClassifier(criterion = 'entropy', max_depth = 44, min_samples_leaf = 1)
DecTree2.fit(X_train, y_train)
DecTree2_pred = DecTree2.predict(X_test)
DecTree2_acc = accuracy_score(y_test, DecTree2_pred)
DecTree2_precision = precision_score(y_test, DecTree2_pred) 
DecTree2_recall = recall_score(y_test, DecTree2_pred) 
DecTree2_f1 = f1_score(y_test, DecTree2_pred)
print("The accuracy for Decision Tree is", DecTree2_acc)
print("The classification report using Decision Tree is:")
print(classification_report(y_test, DecTree2_pred))
The accuracy for Decision Tree is 0.77375
The classification report using Decision Tree is:
              precision    recall  f1-score   support

           0       0.84      0.69      0.76       415
           1       0.72      0.86      0.79       385

    accuracy                           0.77       800
   macro avg       0.78      0.78      0.77       800
weighted avg       0.79      0.77      0.77       800
DecTree2_cm = confusion_matrix(y_test, DecTree2_pred)
sns.heatmap(DecTree2_cm/np.sum(DecTree2_cm), annot = True, fmt = '0.2%', cmap = 'Blues')
plt.title("Decision Tree Confusion Matrix")
Text(0.5, 1.0, 'Decision Tree Confusion Matrix')

#4. Random Forest Classifier – Second Iteration

RFTree2 = RandomForestClassifier(min_samples_leaf = 2, n_estimators = 200)
RFTree2.fit(X_train, y_train)
RFTree2_pred = RFTree2.predict(X_test)
RFTree2_acc = accuracy_score(y_test, RFTree2_pred)
RFTree2_precision = precision_score(y_test, RFTree2_pred) 
RFTree2_recall = recall_score(y_test, RFTree2_pred) 
RFTree2_f1 = f1_score(y_test, RFTree2_pred)
print("The accuracy for Random Forest is", RFTree2_acc)
print("The classification report using Random Forest is:")
print(classification_report(y_test, RFTree2_pred))
The accuracy for Random Forest is 0.8375
The classification report using Random Forest is:
              precision    recall  f1-score   support

           0       0.85      0.84      0.84       415
           1       0.83      0.84      0.83       385

    accuracy                           0.84       800
   macro avg       0.84      0.84      0.84       800
weighted avg       0.84      0.84      0.84       800
RFTree2_cm = confusion_matrix(y_test, RFTree2_pred)
sns.heatmap(RFTree2_cm/np.sum(RFTree2_cm), annot = True, fmt = '0.2%', cmap = 'Blues')
plt.title("Random Forest Confusion Matrix")
Text(0.5, 1.0, 'Random Forest Confusion Matrix')

#5. Support Vector Machine Classifier – Second Iteration

SVM2 = SVC(C = 10, gamma = 1, kernel = 'rbf')
SVM2.fit(X_train, y_train)
SVM2_pred = SVM2.predict(X_test)
SVM2_acc = accuracy_score(y_test, SVM2_pred)
SVM2_precision = precision_score(y_test, SVM2_pred) 
SVM2_recall = recall_score(y_test, SVM2_pred) 
SVM2_f1 = f1_score(y_test, SVM2_pred)
print("The accuracy for SVM is", SVM2_acc)
print("The classification report using SVM is:", SVM2_acc)
print(classification_report(y_test, SVM2_pred))
The accuracy for SVM is 0.82375
The classification report using SVM is: 0.82375
              precision    recall  f1-score   support

           0       0.81      0.87      0.84       415
           1       0.84      0.78      0.81       385

    accuracy                           0.82       800
   macro avg       0.83      0.82      0.82       800
weighted avg       0.83      0.82      0.82       800
SVM2_cm = confusion_matrix(y_test, SVM2_pred)
sns.heatmap(SVM2_cm/np.sum(SVM2_cm), annot = True, fmt = '0.2%', cmap = 'Blues')
plt.title("SVM Confusion Matrix")
Text(0.5, 1.0, 'SVM Confusion Matrix')

#6. XGBoost Classifier – Second Iteration

XGB2 = XGBClassifier(n_estimators = 600, learning_rate = 0.8)
XGB2.fit(X_train, y_train)
XGB2_pred = XGB2.predict(X_test)
XGB2_acc = accuracy_score(y_test, XGB2_pred)
XGB2_precision = precision_score(y_test, XGB2_pred)
XGB2_recall = recall_score(y_test, XGB2_pred)
XGB2_f1 = f1_score(y_test, XGB2_pred)
print("The accuracy for XGBoost is", XGB2_acc)
print("The classification report using XGBoost is:", XGB2_acc)
print(classification_report(y_test, XGB2_pred))
The accuracy for XGBoost is 0.80875
The classification report using XGBoost is: 0.80875
              precision    recall  f1-score   support

           0       0.85      0.76      0.81       415
           1       0.77      0.86      0.81       385

    accuracy                           0.81       800
   macro avg       0.81      0.81      0.81       800
weighted avg       0.81      0.81      0.81       800
XGB2_cm = confusion_matrix(y_test, XGB2_pred)
sns.heatmap(XGB2_cm/np.sum(XGB2_cm), annot = True, fmt = '0.2%', cmap = 'Blues')
plt.title("XGBoost Confusion Matrix")
Text(0.5, 1.0, 'XGBoost Confusion Matrix')

#Comparing Models – Second Iteration:

models2 = pd.DataFrame({
    'Model':['Logistic Regression', 'KNN Regression', 'Decision Tree', 'Random Forest', 'Support Vector', 'XGBoost'],
    'Accuracy' :[LogReg2_acc, KNN2_acc, DecTree2_acc, RFTree2_acc, SVM2_acc, XGB2_acc],
    'Precision' :[LogReg2_precision, KNN2_precision, DecTree2_precision, RFTree2_precision, SVM2_precision, XGB2_precision],
    'Recall' :[LogReg2_recall, KNN2_recall, DecTree2_recall, RFTree2_recall, SVM2_recall, XGB2_recall],
    'F1 Score' :[LogReg2_f1, KNN2_f1, DecTree2_f1, RFTree2_f1, SVM2_f1, XGB2_f1]
})
models2 = models2.sort_values(by='Accuracy', ascending=False)
models2
Model Accuracy Precision Recall F1 Score
3 Random Forest 0.83750 0.826087 0.838961 0.832474
4 Support Vector 0.82375 0.844633 0.776623 0.809202
5 XGBoost 0.80875 0.769767 0.859740 0.812270
2 Decision Tree 0.77375 0.721739 0.862338 0.785799
1 KNN Regression 0.76875 0.731481 0.820779 0.773562
0 Logistic Regression 0.53875 0.518433 0.584416 0.549451
fig = plt.figure(figsize=(15,9))

ax = sns.barplot(data=models2, 
                 y='Model', 
                 x='Accuracy',
                 palette = 'Blues_r')

ax.figure.suptitle('Performance of Models -- Second Iteration',y=0.91, size = 16, color = 'black', weight='bold');

plt.xlabel('Accuracy')
plt.ylabel('Models')
plt.axvline(x = 0.8, ymin = 0, ymax = 1,
            linewidth = 2, linestyle =":",
            color ='#cf7849');

#Comparing First and Second Iterations:

comp_iterations = pd.DataFrame({
    'Model':['Logistic Regression', 'KNN Regression', 'Decision Tree', 'Random Forest', 'Support Vector', 'XGBoost'],
    '2nd Iteration' :[LogReg2_acc, KNN2_acc, DecTree2_acc, RFTree2_acc, SVM2_acc, XGB2_acc],
    '1st Iteration' :[LogReg_acc, KNN_acc, DecTree_acc, RFTree_acc, SVM_acc, XGB_acc],
})
comp_iterations
Model 2nd Iteration 1st Iteration
0 Logistic Regression 0.53875 0.54000
1 KNN Regression 0.76875 0.66375
2 Decision Tree 0.77375 0.76625
3 Random Forest 0.83750 0.84125
4 Support Vector 0.82375 0.65750
5 XGBoost 0.80875 0.78750
comp_iterations.plot.bar(figsize=(12,6))
plt.xticks([0,1,2,3,4,5],['Logistic Regression', 'KNN Regression', 'Decision Tree', 'Random Forest', 'Support Vector', 'XGBoost'])
plt.ylabel('Percentage of Accuracy') 
plt.xlabel('Model') 
plt.title('Comparison between Iterations');

After our second iteration of model (with hypertuning) we can see that Random Forest Classifier algortihm performed the best with the highest accuracy. We will use Random Forest and SVM to continue our model evaluation with cross validation

#Cross Validation

from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from statistics import mean, stdev

cv = KFold(n_splits=10, random_state=1, shuffle=True)

RFTree2_scores = cross_val_score(RFTree2, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
SVM2_scores = cross_val_score(SVM2, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
XGB2_scores = cross_val_score(XGB2, X, y, scoring='accuracy', cv=cv, n_jobs=-1)

print('Accuracy of CV - Random Forest: %.4f (%.4f)' % (mean(RFTree2_scores), stdev(RFTree2_scores)))
print('Accuracy of CV - SVM: %.4f (%.4f)' % (mean(SVM2_scores), stdev(SVM2_scores)))
print('Accuracy of CV - XGBoost: %.4f (%.4f)' % (mean(XGB2_scores), stdev(XGB2_scores)))
Accuracy of CV - Random Forest: 0.8551 (0.0151)
Accuracy of CV - SVM: 0.8794 (0.0119)
Accuracy of CV - XGBoost: 0.8156 (0.0140)
from sklearn.metrics import roc_curve

fpr, tpr, thresholds = roc_curve(y_test, SVM2_pred)

plt.figure(figsize=(6,4))
plt.plot(fpr, tpr, linewidth=2)
plt.plot([0,1], [0,1], 'k--' )
plt.rcParams['font.size'] = 12
plt.title('ROC curve for SVM Water Quality')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.show()

from sklearn.metrics import roc_auc_score

ROC_AUC = roc_auc_score(y_test, SVM2_pred)
print('ROC AUC : {:.4f}'.format(ROC_AUC))
ROC AUC : 0.8220
Cross_validated_ROC_AUC = cross_val_score(SVM2, X_train, y_train, cv=10, scoring='roc_auc').mean()

print('Cross validated ROC AUC : {:.4f}'.format(Cross_validated_ROC_AUC))
Cross validated ROC AUC : 0.8844