以下文章来源于数据STUDIO ,作者云朵君
在本文中,云朵君将和大家一起探索慢性肾脏疾病数据集,并对其进行完整的分析,的主要目标是根据所提供的数据预测一个人是否会患有慢性肾脏疾病。
图片来源:https://www.kidney.org
- 准备
- 导入必要的库
- 数据读取
- 数据预处理
- 探索性数据分析 (EDA)
- 模型构建
- 逻辑回归
- 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%的准确性,但它使用的资源最少,因为它甚至不需要模型调优。
为了简单演示,本文使用了两个非常简单的模型,在实际工作中,需要根据实际情况选用不同的模型,可以选用更加复杂的集成模型,甚至有时候需要用神经网络模型。继续关注我,和云朵君一起学习数据分析和挖掘!
- 还没有人评论,欢迎说说您的想法!