以下文章来源于数据STUDIO ,作者云朵君

在本文中,云朵君将和大家一起探索慢性肾脏疾病数据集,并对其进行完整的分析,的主要目标是根据所提供的数据预测一个人是否会患有慢性肾脏疾病。


图片来源:https://www.kidney.org

  • 准备
  1. 导入必要的库
  2. 数据读取
  • 数据预处理
  • 探索性数据分析 (EDA)
  • 模型构建
  1. 逻辑回归
  2. K-近邻分类器
  • 保存模型

准备
导入必要的库

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import GridSearchCV, train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import r2_score
import scipy.stats as stats
import seaborn as sns

%matplotlib inline

数据读取
使用pandas的样式方法高亮显示制定值,此处显示缺失值。

df = pd.read_csv('kidney.csv')
data = df
col = data.select_dtypes(exclude=['object']).columns
format_dict = dict(zip(col, len(col) * ["{:.2f}"]))
format_dict["sg"] = "{:.3f}"

data.head(10).style.highlight_null()\
                   .format(format_dict)

# 数据集的形状
>>> df.shape
(400, 25)

数据预处理
 

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 400 entries, 0 to 399
Data columns (total 25 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   age     391 non-null    float64
 1   bp      388 non-null    float64
 2   sg      353 non-null    float64
 3   al      354 non-null    float64
 4   su      351 non-null    float64
 5   rbc     248 non-null    object 
 6   pc      335 non-null    object 
 7   pcc     396 non-null    object 
 8   ba      396 non-null    object 
 9   bgr     356 non-null    float64
 10  bu      381 non-null    float64
 11  sc      383 non-null    float64
 12  sod     313 non-null    float64
 13  pot     312 non-null    float64
 14  hemo    348 non-null    float64
 15  pcv     329 non-null    float64
 16  wbcc    294 non-null    float64
 17  rbcc    269 non-null    float64
 18  htn     398 non-null    object 
 19  dm      398 non-null    object 
 20  cad     398 non-null    object 
 21  appet   399 non-null    object 
 22  pe      399 non-null    object 
 23  ane     399 non-null    object 
 24  class   400 non-null    object 
dtypes: float64(14), object(11)
memory usage: 78.2+ KB


统计数据

df.describe().T

每个特征的缺失值总数
 

df.isna().sum()
age        9
bp        12
sg        47
al        46
su        49
rbc      152
pc        65
pcc        4
ba         4
bgr       44
bu        19
sc        17
sod       87
pot       88
hemo      52
pcv       71
wbcc     106
rbcc     131
htn        2
dm         2
cad        2
appet      1
pe         1
ane        1
class      0
dtype: int64

相关矩阵和矩阵可视化
 

col = df.select_dtypes(exclude=['object']).columns
format_dict = dict(zip(col, len(col) * ["{0:.2%}"]))
df.corr().style.format(format_dict)\
               .background_gradient(cmap='summer')

找出每个类有多少
 

df['class'].value_counts()
ckd       250
notckd    150
Name: class, dtype: int64


推断: 从下面的输出中,可以推断出该数据集属于 不平衡数据集。

以百分比表示目标变量

countNoDisease = len(df[df['class'] == 0])
countHaveDisease = len(df[df['class'] == 1])
print("没有心脏病的患者百分比:{:.2f}%".format((countNoDisease / (len(df['class']))*100)))
print("患有心脏病的患者百分比:{:.2f}%".format((countHaveDisease / (len(df['class']))*100)))


没有心脏病的患者百分比:0.00%
患有心脏病的患者百分比:0.00%
直观地理解数据的平衡

df['class'].value_counts().plot(kind='bar',
              color=['salmon', 'lightblue'],
              title="肾病诊断计数")

检查年龄列的分布

df['age'].plot(kind='hist')

推论: 在直方图的帮助下,可以看到 50-60 岁年龄段 的人在这个数据集中分布广泛。

绘制图表以查看数据集中的缺失值

p = msno.bar(data)

推论: 上面这张图中的任何条状图未触及顶部 400 的特征都具有空值。

fig = plt.figure(figsize=(15,6), dpi=200)
plt.subplot(121), sns.distplot(data['bp'])
plt.subplot(122), data['bp'].plot.box(figsize=(16,5))
plt.show()

推论: 在上图中,可以看到血压的分布,并且在子图中,可以看到 bp 列中有一些异常值。

现在将分类值(对象)转换为分类值(int)
 

data['class'] = data['class'].map({'ckd':1,'notckd':0})
data['htn'] = data['htn'].map({'yes':1,'no':0})
data['dm'] = data['dm'].map({'yes':1,'no':0})
data['cad'] = data['cad'].map({'yes':1,'no':0})
data['appet'] = data['appet'].map({'good':1,'poor':0})
data['ane'] = data['ane'].map({'yes':1,'no':0})
data['pe'] = data['pe'].map({'yes':1,'no':0})
data['ba'] = data['ba'].map({'present':1,'notpresent':0})
data['pcc'] = data['pcc'].map({'present':1,'notpresent':0})
data['pc'] = data['pc'].map({'abnormal':1,'normal':0})
data['rbc'] = data['rbc'].map({'abnormal':1,'normal':0})

对class进行统计

data['class'].value_counts()
1 250
0 150
Name: class, dtype: int64

找到图之间的相关性

plt.figure(figsize = (19,19))
sns.heatmap(data.corr(), annot = True, cmap = 'coolwarm') # 寻找与“class”行的强相关性


在这里我要提到一些会对你的肾脏造成严重影响的原因。

  • 糖尿病——血糖或糖尿病
  • 高血压(BP)
  • 心脏和血管(心血管)疾病
  • 吸烟
  • 肥胖
  • 非裔美国人,印第安人或亚裔美国人
  • 肾脏疾病家族史
  • 不正常的肾脏结构
  • 老年-年龄

探索性数据分析 (EDA)
数据集中列名

data.columns
Index(['age', 'bp', 'sg', 'al', 'su', 'rbc',
       'pc', 'pcc', 'ba', 'bgr', 'bu',
       'sc', 'sod', 'pot', 'hemo', 'pcv', 
       'wbcc', 'rbcc', 'htn', 'dm', 'cad',
       'appet', 'pe', 'ane', 'class'],
      dtype='object')

删除空值

data.shape[0],data.dropna().shape[0]
(400, 158)


从上面的输出中,可以看到数据集中有 158 个空值。现在只剩下两个选择,可以放弃所有空值或保留他们。我们的数据集不是那么大,如果删除这些空值,那么它就会变得会更小。

data.dropna(inplace=True)
data.shape
(158, 25)


在这种情况下,如果为机器学习模型提供的数据非常少,那么性能会非常低,而且我们还不知道这些空值与数据集中的其他一些特征相关。

所以这次我会保留这些值,看看模型在这个数据集中的表现如何。

此外,当我们从事一些医疗保健项目时,将预测该人是否患有该疾病,那么应该记住的一件事是模型评估应该具有最小的误报率。

模型构建
逻辑回归

from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression()
X = data.iloc[:,:-1]
y = data['class']
X_train, X_test, y_train, y_test = train_test_split(X,y, stratify = y, shuffle = True)
logreg.fit(X_train,y_train)

训练得分

logreg.score(X_train,y_train)
1.0

测试精度

logreg.score(X_test,y_test)
0.975

打印训练和测试的准确性

from sklearn.metrics import accuracy_score, confusion_matrix

print('Train Accuracy: ', accuracy_score(y_train, train_pred))
print('Test Accuracy: ', accuracy_score(y_test, test_pred))
Train Accuracy:  1.0
Test Accuracy:  1.0


每个变量的系数
(从逻辑回归中读取系数的示例:年龄增加一单位使个体患 CKD 的可能性约为 e^0.14 倍,而血压增加一单位使个体约 e^-0.07 倍可能患有 CKD。

pd.DataFrame(logreg.coef_, columns=X.columns)

混淆矩阵
 

sns.set(font_scale=1.5)
def plot_conf_mat(y_test,y_preds):
    """
    该函数将通过使用 seaborn 绘制混淆矩阵
    """
    
    fig,ax=plt.subplots(figsize=(3,3))
    ax=sns.heatmap(confusion_matrix(y_test,y_preds),annot=True,cbar=False)
    plt.xlabel("真实标签")
    plt.ylabel("预测标签")
log_pred = logreg.predict(X_test)
plot_conf_mat(y_test, log_pred)

 

tn, fp, fn, tp = confusion_matrix(y_test, test_pred).ravel()

print(f'True Neg: {tn}')
print(f'False Pos: {fp}')
print(f'False Neg: {fn}')
print(f'True Pos: {tp}')
True Neg: 29
False Pos: 0
False Neg: 0
True Pos: 11

K-近邻分类器
在使用 KNN 之前先很好地平衡类是一个很好的做法,因为我们知道在不平衡类的情况下,KNN 表现不佳。

df["class"].value_counts()
0    115
1     43
Name: class, dtype: int64


现在将类变量连接在一起

balance_df = pd.concat([df[df["class"] == 0], 
                        df[df["class"] == 1].sample(n = 115, 
                                                    replace = True)],
                       axis = 0)
balance_df.reset_index(drop=True, inplace=True)
balance_df["class"].value_counts()
1    115
0    115
Name: class, dtype: int64

数据归一化
 

ss = StandardScaler()
ss.fit(X_train)
X_train = ss.transform(X_train)
X_test = ss.transform(X_test)


模型调参数
为了获得更好的准确性,现在调整 KNN 模型参数。

knn = KNeighborsClassifier()
params = {
"n_neighbors":[3,5,7,9],
"weights":["uniform","distance"],
"algorithm":["ball_tree","kd_tree","brute"],
"leaf_size":[25,30,35],
"p":[1,2]
}

gs = GridSearchCV(knn, param_grid=params)
model = gs.fit(X_train,y_train)
preds = model.predict(X_test)
accuracy_score(y_test, preds)
1.0


KNN模型的混淆矩阵

knn_pred = model.predict(X_test)
plot_conf_mat(y_test, knn_pred)


 

tn, fp, fn, tp = confusion_matrix(y_test, preds).ravel()

print(f'True Neg: {tn}')
print(f'False Pos: {fp}')
print(f'False Neg: {fn}')
print(f'True Pos: {tp}')
True Neg: 26
False Pos: 0
False Neg: 0
True Pos: 32


特征重要性

feature_dict=dict(zip(df.columns,list(logreg.coef_[0])))
feature_dict

在这里,将从特征中获取系数,该系数表明每个特征的权重。

{'age': 0.2858203378727209,
 'bp': -0.13211767022170745,
 'sg': 0.0026714534270341444,
 'al': 0.3099528545093234,
 'su': 0.010788785962584712,
 'rbc': 0.01985635073828642,
 'pc': 0.09106895589320387,
 'pcc': 0.003106393240262293,
 'ba': 0.006828878616469952,
 'bgr': 0.4140451203997997,
 'bu': 0.47262371944289844,
 'sc': 0.12893875993072498,
 'sod': -0.4419699201228987,
 'pot': 0.05909714695858163,
 'hemo': -0.28286805186344094,
 'pcv': -0.6211104727832718,
 'wbcc': 0.001157338688486265,
 'rbcc': -0.1417833283935927,
 'htn': 0.08881269207443204,
 'dm': 0.08689401433102413,
 'cad': 0.0018059681932075433,
 'appet': -0.004481530609769657,
 'pe': 0.0051126943850270425,
 'ane': 0.00349950552414094}

可视化特征重要性
 

feature_df=pd.DataFrame(feature_dict,index=[0])
feature_df.T.plot(kind="hist",
            legend=False,
            title="特征重要性")

可视化特征重要性——条形图

feature_df=pd.DataFrame(feature_dict,index=[0])
feature_df.T.plot(kind="bar",
            legend=False,
            title="特征重要性")


保存模型
 

import pickle

# 现在在pickle模型的帮助下,我们将保存训练好的模型
saved_model = pickle.dumps(logreg)
# 加载pickle模型
logreg_from_pickle = pickle.loads(saved_model)
# 用将加载对模型预测结果
logreg_from_pickle.predict(X_test)
array([1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0,
       1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0,
       1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0,
       1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
       1, 0, 0, 0, 1, 1], dtype=int64)


另一种方法--使用上下文管理器
 

# 保存模型
with open('kidney_disease_prediction.pkl', 'wb') as files:
    pickle.dump(logreg, files)   
# 导入模型
with open('kidney_disease_prediction.pkl' , 'rb') as f:
    model = pickle.load(f)
# 预测结果
model.predict(X_test)


写在最后
在这篇文章中,使用了各种机器学习模型来根据患者的数据来分类患者是否会有慢性肾脏问题。

KNN需要类平衡,数据归一化和模型调优得到100%的准确性,而Logistic回归在没有调优的情况下是100%准确的。

在本文演示的情况下,逻辑回归似乎是一个更好的模型,虽然两者均得到了100%的准确性,但它使用的资源最少,因为它甚至不需要模型调优。

为了简单演示,本文使用了两个非常简单的模型,在实际工作中,需要根据实际情况选用不同的模型,可以选用更加复杂的集成模型,甚至有时候需要用神经网络模型。继续关注我,和云朵君一起学习数据分析和挖掘!