爱数课实验 | 第九期-利用机器学习方法进行健康智能诊断

2022-06-27 18:23:43 浏览数 (1)

爱数课:idatacourse.cn

领域:医疗

简介:慢性肝病近年来对印度负担很高, 2017年由于肝硬化导致近22万人死亡。慢性肝病也会带来严重疾病的重叠感染,急性慢性肝功能衰竭,增加暴发性肝功能衰竭和死亡率。在本次案例中,我们对影响印度肝病发生的指标进行了探索性分析,并建立机器学习分类模型,对肝病进行自动智能诊断。

数据:

./dataset/Indian Liver Patient Dataset (ILPD).csv

目录

1. 数据读取与预处理

1.1 数据集简介

印度肝病患者数据集(Indian Liver Patient Dataset)包含416名肝病患者记录和167名非肝病患者记录。数据集是从印度安德拉·普拉德什东北部收集的。标签列是用于分为组(患肝病或不患肝病)的类标签label。此数据集包含441名男性患者记录和142名女性患者记录。数据集链接:http://www.idatascience.cn/dataset-detail?table_id=88

以下为数据集各列所代表的具体含义:

列名

数据类型

含义说明

Age

Integer

患者的年龄

Gender

String

患者的性别

TB

Float

总胆红素

DB

Float

直接胆红素

Alkphos

Integer

碱性磷酸酶

Sgpt

Integer

谷丙转氨酶

Sgot

Integer

天冬氨酸氨基转移酶

TP

Float

总蛋白

ALB

Float

白蛋白

A/G Ratio

Float

白蛋白和球蛋白比率

label

Integer

是否患病(1为患病,2为健康)

1.2 导入数据

首先导入需要的Python包

代码语言:javascript复制
import pandas as pd
import numpy as np

# 不显示警告
import warnings
warnings.filterwarnings('ignore')

读取数据集,并查看数据集各个列相关的属性信息:

代码语言:javascript复制
data = pd.read_csv("./dataset/Indian Liver Patient Dataset (ILPD).csv")
data.head()
代码语言:javascript复制
# 查看数据信息
data.info()

从这里可以看到,A/G Ratio字段为一浮点数字段,存在4个缺失值,我们需要对其进行填补处理。并且Gender为唯一的object特征,我们需要提前处理为数值型,方便建立模型。

使用.describe()对总体数据进行统计,查看常见的数据的统计学特征。

代码语言:javascript复制
data.describe(include='all')

从这里我们可以看出,Gender列为字符型变量,其余列为数值型变量。SgptSgotTBDB的最大值都是均值的数十倍,这说明这几个特征的分布较为分散

1.3 数字编码

Gender列为字符串,为了在后续模型中方便引入进行处理,我们对Gender进行数字编码。

代码语言:javascript复制
from sklearn.preprocessing import LabelEncoder
# 数字编码
le = LabelEncoder()

# 数字编码
data["Gender"] = le.fit_transform(data["Gender"][:])

data

编码后,女性(Female)被编码为0,男性(Male)被编码为1。

1.4 缺失值填补

在导入数据并对数据信息查看之后可以发现,A/G Ratio存在缺失值,我们需要对其进行填补。在这里我们采用均值填补的方法进行填充。

代码语言:javascript复制
from sklearn.impute import SimpleImputer

# 采用均值填补法
imp = SimpleImputer(strategy="mean")
data["A/G Ratio"] = imp.fit_transform(data["A/G Ratio"].to_frame())

data.describe()

填充后,A/G Ratio列的mean值未变,与填充缺失值之前一样均为0.947064,但是count已经由579变成了583。

1.5 数据列数值转化

因为在进行处理时,LogisticRegression的标签列需要为或者为,但是本数据集的标签列label的值为,所以我们需要先进行转化。

代码语言:javascript复制
# 定义label列
calculate_col = "label"
calculate_value = 2

# 将label值从{1,2},转化为{1,0}并更名为label_cal
data[calculate_col   '_cal'] = calculate_value - data[calculate_col]
# 删除label列
data = data.drop(labels='label', axis=1)

#查看转化后的数据集
data

转化后,患病标签为1,为正类标签,不患病为0,为负类标签,方便我们后续建模时使用。

2. 数据相关性探索

2.1 年龄分布直方图、性别数量柱状图

首先对年龄性别等标签进行统计,画出年龄分布的直方图以及性别数量的柱状图。

代码语言:javascript复制
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 定义genders列
genders = ['女性', '男性']
fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 5))

# 画出年龄分布直方图
sns.histplot(data['Age'], kde=False, palette="Set2", ax=ax1)
# 画出性别分布柱状图
sns.countplot(x='Gender', data=data, palette="Set2", ax=ax2)

ax1.set(xlabel='年龄', ylabel='数量')
ax2.set(xlabel='性别', ylabel='数量')
ax2.set_xticklabels(genders)

从上图可以看出,我们的数据集中,中年人和男性的样本数较多

2.2 年龄、性别与患病的分布情况

年龄性别与是否患有肝病进行大致描述,使用的方法为箱线图和柱状图。

代码语言:javascript复制
# 定义两个图像
fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(12, 5))

# 绘制年龄箱线图
sns.boxplot(x='label_cal', y='Age', hue='label_cal', data=data, palette="Set2", ax=ax1)
# 绘制性别柱状图
sns.countplot(x='Gender', hue='label_cal', data=data, palette="Set2", ax=ax2)

# 给图像加标题
ax1.set_title('年龄与患病情况的分布', fontsize=13)
ax2.set_title('性别与患病情况的分布', fontsize=13)

# 给图像添加标签
labels = ['不患病', '患病']
ax1.set(xlabel='是否患病', ylabel='年龄')
ax1.set_xticklabels(labels)
ax1.legend_.remove()
ax2.set(xlabel='性别', ylabel='数量')
ax2.set_xticklabels(genders)
ax2.legend(['不患病', '患病'])

plt.show()

从这两张图可以看出,肝病在样本中更加偏向于年龄较大的人群,且在男性中患病的比例更高。

2.3 各指标间相关性

接下来使用热力图(heatmap)对连续型对象之间计算线性相关系数,对其相关性进行可视化呈现。

代码语言:javascript复制
def correlation_heatmap(df):
    hm, ax = plt.subplots(figsize=(12, 8))

# 绘制热力图
    hm = sns.heatmap(
        df.corr(),
        cmap='Blues',
        square=True,
        cbar_kws={'shrink': .9},
        ax=ax,
        annot=True,
        linewidths=0.1, vmax=1.0, linecolor='white',
        annot_kws={'fontsize': 12}
    )

    plt.title('连续型对象间的皮尔森相关系数', y=1.05, size=15)

# 删掉Gender与label_cal列
df = data.drop(['Gender', 'label_cal'], axis=1)
correlation_heatmap(df)

从特征之间的相关系数图可以看出,颜色越深,特征之间的正相关性越强;颜色越浅,特征之间的负相关性越强。其中,总胆红素(TB)直接胆红素(DB)的相关系数为0.87,它们均是衡量血液中的胆红素情况的指标;谷丙转氨酶(sgpt)天冬氨酸氨基转移酶(sgot)的相关系数为0.79,它们衡量的是血液中血清酶的含量;白蛋白(ALB)总蛋白(TP)白蛋白(ALB)和球蛋白比率(A/G Ratio)的相关系数均在0.6以上,它们衡量的是血清蛋白的情况。整体来看,特征之间有着较强的相关性,在后续进行建模时,需要重点考虑模型的特征选择问题。

针对两个离散型变量Genderlabel_cal,我们使用卡方检验来观测其相关性,使用的方法是sklearn.feature_selection中的chi2方法:

代码语言:javascript复制
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

# 定义两个新的数据框
gender = pd.DataFrame(data['Gender'])
label_cal = pd.DataFrame(data['label_cal'])

# 使用卡方检验
model1 = SelectKBest(chi2, k=1) # 选择k个最佳特征
model1.fit_transform(gender, label_cal)

# 打印得分
print('性别变量与是否得病之间的得分为:%.4f' % model1.scores_)
print('性别变量与是否得病之间的pvalue为:%.4f' % model1.pvalues_)

可以看出,p值为0.3261,远大于0.05,说明性别与是否得病之间无明显的统计学差异,即性别与是否得病是相关的。

计算label_cal与其他连续型变量之间的关系,使用的方法为sklearn.feature_selection中的f_classif方法:

代码语言:javascript复制
from sklearn.feature_selection import f_classif


fdata = pd.DataFrame(data.drop(['Gender', 'label_cal'], axis=1))# 删除gender列与label_cal列
label_cal = pd.DataFrame(data['label_cal'])

# 使用f_classif计算label_cal与其他连续型变量间的关系
F, p_val = f_classif(fdata, label_cal)
# f分布的0.05分位数
print('各连续型变量的名称:')
print(fdata.columns.tolist())
print('各连续型变量与是否得病之间的F值为:')
print(F)
print('各连续型变量与是否得病之间的pvalue为:')
print(p_val)

从计算得出的P值能看出,除了TP的值为0.398,大于0.05以外,各个连续型变量与label_cal之间具有很高的的相关性。

3. 构建分类模型

患者是否患病是一个二分类问题,我们将使用逻辑回归、决策树以及随机森林方法对数据进行建模。

3.1 训练集测试集划分

我们根据计算出的label_cal来对数据集进行划分。划分比例设置为测试集:训练集 = 20%:80%

代码语言:javascript复制
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
x = data.drop(['label_cal'], axis=1) # x为删除label_cal的数据集
y = data['label_cal'] # y为label_cal列
代码语言:javascript复制
# 划分数据集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=2, stratify=data['label_cal'])

3.2 逻辑回归

逻辑回归是一种广义线性回归模型,在回归的基础上,使用Logistic函数将连续型的输出映射到(0,1)之间,用来解决分类问题。

在Python中,使用sklearn_model的LogisticRegression进行分类建模,使用的主要参数有:

  • penalty ——可设为l1或者l2,代表L1和L2正则化,默认为l2
  • class_weight ——class-weight用于指定样本各个类别的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。
  • random_state ——随机种子,设定为一个常数,保证每次运行的结果都是一样的。

我们使用这几个参数进行模型的构建,其中,class_weight为我们自己赋予的权重:

代码语言:javascript复制
from sklearn.linear_model import LogisticRegression

# 为权重赋值
weights = {0: 1, 1: 1.3}

# 进行logistic回归
lr = LogisticRegression(penalty='l2', random_state=8, class_weight=weights)
lr.fit(x_train, y_train)

# 对y进行预测
y_predprb = lr.predict_proba(x_test)[:, 1]
y_pred = lr.predict(x_test)
代码语言:javascript复制
from sklearn import metrics
from sklearn.metrics import auc

# 计算fpr,tpr及thresholds的值
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_predprb)
# 计算gmean的值
gmean = np.sqrt(tpr*(1-fpr))

# 计算最大的gmean值对应的thresholds值
dictionary = dict(zip(thresholds,gmean))
max_thresholds = max(dictionary, key=dictionary.get)

print("最大的GMean值为:%.4f"%(max(gmean)))
print("最大的GMean对应的thresholds为:%.4f"%(max_thresholds))

计算AUC值,打印分类预测报告并画出混淆矩阵:

代码语言:javascript复制
from sklearn.metrics import roc_auc_score
# 计算AUC值
test_roc_auc = roc_auc_score(y_test, y_predprb)
print(test_roc_auc)
代码语言:javascript复制
# 打印模型分类预测报告
print(classification_report(y_test, y_pred))
代码语言:javascript复制
# 画出混淆矩阵热力图
cm1 = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm1, annot=True, linewidths=.5, square=True, cmap='Blues')
plt.ylabel('Actual label')
plt.xlabel('Predicted label')
all_sample_title = 'ROC AUC Score: {0}'.format(round(test_roc_auc,2))
plt.title(all_sample_title, size=15)

可以看到,我们训练后的逻辑回归模型能够实现测试集上患病类别(label_cal=1)召回率(Recall)达到0.93,且精确度(Precision)达到0.71,总体的平均F1_score达到0.45,是一个分类水平一般的模型。

3.3 决策树

使用sklearn中的DecisionTreeClassifier算法来训练决策树模型。使用的主要参数有:

  • max_depth:设置决策树的最大深度。为多次试验后设置的较好值。
  • class_weight:用于指定样本各个类别的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。设置为balanced时,能够保证样本量少的类别权重较高。
  • random_state ——随机种子,设定为一个常数,保证每次运行的结果都是一样的。
代码语言:javascript复制
from sklearn.tree import DecisionTreeClassifier
from sklearn import tree

# 建立决策树模型
model = DecisionTreeClassifier(random_state=5, class_weight=weights)
model = model.fit(x_train, y_train)

# 对y进行预测
y_predict = model.predict(x_test)
y_predprb = model.predict_proba(x_test)[:, 1]

我们使用二分类模型的评估指数来对决策树模型进行评估,打印分类报告并画出混淆矩阵:

代码语言:javascript复制
# 计算AUC值
test_roc_auc = roc_auc_score(y_test, y_predprb)
print(test_roc_auc)
代码语言:javascript复制
# 打印模型分类预测报告
print(classification_report(y_test, y_predict))
代码语言:javascript复制
# 绘制混淆矩阵热力图
cm2 = confusion_matrix(y_test, y_predict)
plt.figure(figsize=(9, 9))
sns.heatmap(cm2, annot=True, linewidths=.5, square=True, cmap='Blues')
plt.ylabel('Actual label')
plt.xlabel('Predicted label')
all_sample_title = 'ROC AUC score: {0}'.format(round(test_roc_auc,2))
plt.title(all_sample_title, size=15)

可以看到,和逻辑回归模型接近,我们训练后的决策树模型能够实现测试集上患病类别(label_cal=1)召回率(Recall)达到0.71,且精确度(Precision)达到0.77,总体的平均F1_score达到0.58,分类水平一般。

3.4 随机森林

随机森林是一种集成模型,通过使用随机的方式从数据中抽取样本和特征,训练多个不同的决策树,形成“森林”。每个树都给出自己的分类意见,称“投票”。

在Python中,使用sklearn.ensemble的RandomForestClassifier进行分类建模,使用的主要参数有:

  • n_estimator:训练分类器的数量,默认值为100。
  • max_depth:每棵树的最大深度,默认为3。
  • random_state:随机种子,设定为一个常数,保证每次运行的结果都是一样的。
  • class_weight:用于指定样本各个类别的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。
代码语言:javascript复制
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

# 建立随机森林模型
ran_for = RandomForestClassifier(n_estimators=80, random_state=0, class_weight=weights)
ran_for.fit(x_train, y_train)

# 对y进行预测
y_pred_ran = ran_for.predict(x_test)
y_predprb = ran_for.predict_proba(x_test)[:, 1]
代码语言:javascript复制
# 计算AUC值
test_roc_auc = roc_auc_score(y_test, y_predprb)
print(test_roc_auc)
代码语言:javascript复制
# 打印模型分类预测报告
print(classification_report(y_test, y_pred_ran, digits=2))
代码语言:javascript复制
# 绘制混淆矩阵热力图
cm3 = confusion_matrix(y_test, y_pred_ran)
plt.figure(figsize=(9, 9))
sns.heatmap(cm3, annot=True, linewidths=.5, square=True, cmap='Blues')
plt.ylabel('Actual label')
plt.xlabel('Predicted label')
all_sample_title = 'ROC AUC score: {0}'.format(round(test_roc_auc,2))
plt.title(all_sample_title, size=15)

集成模型对我们分类准确度有一定的提升,但是效果有限,我们训练后的随机森林模型能够实现测试集上患病类别(label_cal=1)召回率(Recall)达到0.80,且精确度(Precision)达到0.77,总体的平均F1_score达到0.61。

可以得到随机森林模型的分类效果高于逻辑回归与决策树。

3.5 主成分分析

PCA降维是一种常见的数据降维方法,其目的是在“信息”损失较小的前提下,将高维的数据转换到低维,从而减小计算量。PCA通常用于高维数据集的探索与可视化,还可以用于数据压缩,数据预处理等。

在我们构建以上模型之外,由于数据的特征较多,我们首先使用PCA主成分分析法对数据进行降维。

主成分分析必须从相同量纲的变量表格开始。由于需要将变量总方差分配给特征根,因此变量必须有相同的物理单位,方差才有意义(方差的单位是变量单位的平方)。主成分分析的变量也可以是无量纲的数据,例如标准化或对数转化后的数据。因此在构建模型之前,我们需要进行数据标准化。常用的标准化方法有 min-max 标准化 z-score 标准化等。在本例中,我们直接采用 z-score 标准化方法。

对数据进行标准化处理:

代码语言:javascript复制
from sklearn import preprocessing
X = data.iloc[:, :-1] # 除了label_cal列
y = data['label_cal']

np.random.seed(123)
perm = np.random.permutation(len(X)) # 将数组随机生成一个新的序列
X = X.loc[perm]
y = y[perm]
X = preprocessing.scale(X)# 进行标准化处理

然后对标准化处理之后的数据使用PCA降维,选择保留6个维度:

代码语言:javascript复制
from sklearn.decomposition import PCA
# 使用PCA进行降维
pca = PCA(copy=True, n_components=6, whiten=False, random_state=1)
X_new = pca.fit_transform(X)

print(u'所保留的6个主成分的方差贡献率为:')
print(pca.explained_variance_ratio_)
print(u'排名前2的主成分特征向量为:')
print(pca.components_[0:1])
print(u'累计方差贡献率为:')
print(sum(pca.explained_variance_ratio_))

前六个主成分的累计方差贡献率达到89.52%,能实现较好的降维效果。

降维后需要重新对数据集进行划分:

代码语言:javascript复制
# 对数据集进行划分
x_train, x_test, y_train, y_test = train_test_split(X_new, y, test_size=0.2, random_state=2, stratify=data['label_cal'])

降维之后,我们再次使用随机森林构建分类模型,并对分类结果进行评估。

代码语言:javascript复制
# 建立随机森林模型
ran_for = RandomForestClassifier(n_estimators=80, random_state=0, class_weight=weights)
# 训练模型
ran_for.fit(x_train, y_train)

# 对y进行预测
y_pred_ran = ran_for.predict(x_test)
y_predprb = ran_for.predict_proba(x_test)[:, 1]

计算AUC值,打印分类报告并画出混淆矩阵:

代码语言:javascript复制
# 计算AUC值
test_roc_auc = roc_auc_score(y_test, y_predprb)
print(test_roc_auc)
代码语言:javascript复制
# 打印模型分类预测报告
print(classification_report(y_test, y_pred_ran, digits=2))
代码语言:javascript复制
# 绘制混淆矩阵热力图
cm4 = confusion_matrix(y_test, y_pred_ran)
plt.figure(figsize=(9, 9))
sns.heatmap(cm4, annot=True, linewidths=.5, square=True, cmap='Blues')
plt.ylabel('Actual label')
plt.xlabel('Predicted label')
all_sample_title = 'ROC AUC score: {0}'.format(round(test_roc_auc,2))
plt.title(all_sample_title, size=15)

降维之后的随机森林模型实现了较为显著的提升,能够实现测试集上患病类别(label_cal=1)召回率(Recall)达到0.85,且精确度(Precision)达到0.83,总体的平均F1_score达到0.65。

4. 总结

本案例基与机器学习模型对印度肝病患者的诊断数据进行了分析和预测,通过数据预处理、探索性分析和分类建模三个阶段进行了深入挖掘。在数据预处理中,通过查看数据描述信息发现数据存在缺失值并对其进行填补;在数据探索性分析中,通过分组对比了不同年龄、性别的人群中的患病占比;在分类建模过程中,分别使用了逻辑回归,决策树,随机森林三种不同方法进行预测,通过对比分类模型的Recall、Precision和F1值对模型进行评估,结果发现随机森林模型的预测效果最好,为了进一步提高模型准确率以及提高模型效率,我们对数据做主成分分析进行降维,并将降维后的数据进一步使用随机森林模型进行分类,并且模型效果在一定程度上得到提高。

爱数课(iDataCourse)是一个面向院校的大数据和人工智能课程和资源平台。平台提供权威的课程资源、数据资源、案例实验资源,助力院校大数据和人工智能专业建设,课程建设和师资能力建设。

0 人点赞