- 1. 内容描述
- 1.1 字段描述
- 1.2 Exploratory Data Analysis探索性数据分析
- 1.2.1数据整体信息以及统计特征
- 1.2.2 id
- 1.2.3 gender性别
- 1.2.4 age年龄
- 1.2.5 Hypertension高血压
- 1.2.6 heart_disease心脏病
- 1.2.7 ever_married已婚与否
- 1.2.8 work_type工作类型
- 1.2.9 Residence_type居住类型
- 1.2.10 avg_glucose_level患者体内的平均血糖水平
- 1.2.11 bmi
- 1.2.12 smoking_status吸烟状况
- 1.3 特征工程
- 1.3.1 标签编码
- 1.3.2 特征相关性检查
- 1.3.2.1 热图
- 1.3.2.2 SelectKBest and F_Classif
- 1.3.3 连续型数据处理
- 1.4 贝叶斯模型描述
- 1.5 数据集拆分
- 1.6 模型创建
- 1.6.1 先验条件计算
- 1.6.2 后验条件计算(单组)
- 1.6.3 后验条件计算(测试集)
- 1.7 结果评估
- 1.7.1准确率计算
- 1.7.2 召回率计算
- *★,°*:.☆( ̄▽ ̄)/$:*.°★* 。撒花撒花,完美搞定定
中风预测:根据世界卫生组织(WHO)的数据,中风是全球第二大死亡原因,约占总死亡人数的11%。该数据集用于根据输入参数(例如性别,年龄,各种疾病和吸烟状况)预测患者是否可能中风。数据中的每一行都提供有关患者的相关信息。 https://mp.weixin.qq.com/s/QobTa9eN0snb9u2lXxX_iQ 70%训练贝叶斯模型,30%预测
数据集
链接:https://pan.baidu.com/s/1ZVKdnMfiq5TwP0-Z8lrDcQ
提取码:gk71
import pandas as pd
data = pd.read_csv('strokePredictionData.csv')
## 打印数据基本信息
print(data.info())
## 打印数据的统计特征
print(data.describe())
结果
统计值变量说明:
count:数量统计,此列共有多少有效值
unipue:不同的值有多少个
std:标准差
min:最小值
25%:四分之一分位数
50%:二分之一分位数
75%:四分之三分位数
max:最大值
mean:均值
id属性是用于分配给每个患者的唯一编号进行跟踪使用,对此于模型使用过程中无用,可进行删除操作
代码
# 删除id列
data.drop("id", inplace=True, axis=1)
drop函数(删除操作)说明:
drop函数的使用:删除行、删除列。drop函数默认删除行,列需要加axis = 1【注意:凡是会对原数组作出修改并返回一个新数组的,往往都有一个 inplace可选参数。如果手动设定为True(默认为False),那么原数组直接就被替换。也就是说,采用inplace=True之后,原数组名对应的内存值直接改变】
# 查询表头看是否还有id,仅是验证查看
print(data.head(0))
结果(已无id列)
性别属性说明患者的性别,对此进行中风率和性别对比。由于性别属性有三种值(Male、Female gender、Other),对此采用计数柱状图来进行比较。
代码
# 为方便对比,创建一个1行2列的画布,figsize设置画布大小
fig, axes = plt.subplots(1, 2, figsize=(10, 5),)
# 提供关于它的唯一值以及每个值的计数的信息
print('计数 n', data['gender'].value_counts())
#设置画板颜色风格,I am Purple lover
sns.set_palette("magma")
# 计数柱状图绘制
sns.countplot(data=data, x='gender',ax=axes[0])
sns.countplot(data=data, x='gender', hue='stroke',ax=axes[1])
plt.show()
结果
结果分析
虽然男女性别数据集并不完全平衡。但由此可见,不同性别之间的中风率没有太大的区别
针对年龄属性进行分布图以及分箱图绘制
代码
data['age'].nunique() sns.displot(data['age']) plt.figure(figsize=(15, 7)) sns.boxplot(data=data, x='stroke', y='age')
结果
结果分析
60岁以上的人更容易患中风
高血压在老年人中对比年轻人很常见,高血压会可能导致中风
代码
data['hypertension'].nunique() sns.countplot(data=data, x='hypertension', hue='stroke',ax=axes[1]) plt.show()
结果
结果分析
高血压人群更容易患中风
代码
data['ever_married'].nunique() sns.countplot(data=data, x='ever_married', hue='stroke') plt.show()
结果
结果分析
已婚人士的中风率更高
代码
sns.countplot(data=data, x='work_type') sns.countplot(data=data, x='work_type', hue='stroke') plt.show()
结果
结果分析
在私营部门工作的人2号患中风的风险更高。从未工作过的人4号中风率非常低
代码
sns.countplot(data=data,x='Residence_type') plt.show()
结果
结果分析
对中风患者影响差异不大
代码
data['avg_glucose_level'].nunique() sns.displot(data['avg_glucose_level']) sns.boxplot(data=data, x='stroke', y='avg_glucose_level') plt.show()
结果
结果分析
中风患者的平均血糖水平偏高
代码
sns.countplot(data=data,x='bmi') sns.boxplot(data=data,x='stroke',y='bmi') plt.show()
结果
结果分析
BMI针对中风几率影响不大
根据计数图,吸烟状况人数分布以及中风情况
代码
sns.countplot(data=data,x='smoking_status',hue='stroke') plt.show()
结果
结果分析
无论吸烟状况如何,中风的几率都没有太大差异
由于数据集由分类数据和数值数据组成,对此使用标签编码器(将分类数据转换为数字数据0——(n-1))将分类数据编码为数值数据。
代码
# 获取数据类型为object的列 cols = data.select_dtypes(include=['object']).columns # 打印出object的列检查 print(cols) # 标签编码初始化 le = LabelEncoder() # 将分类数据转换为数字 data[cols] = data[cols].apply(le.fit_transform) # 随机找个object的列进行检查,看是否已将分类数据编码为数值数据 print(data.head(10).work_type)
结果
通过1.3.1EDA进行初步数据分析,对此采用热图以及 SelectKBest 和 F_Classif 进一步检查特征
1.3.2.1 热图代码
# 创建15*10的画布 plt.figure(figsize=(15,10)) print(data.corr()) # data.corr()函数说明 # data.corr()表示了data中的两个变量之间的相关性,取值范围为[-1,1],取值接近-1,表示反相关,类似反比例函数,取值接近1,表正相关 sns.heatmap(data.corr(),annot=True,fmt='.2') # 参数说明 # data:数据data中的两个变量之间的相关性 # annot: # annotate的缩写,annot默认为False,当annot为True时,在heatmap中每个方格写入数据 # annot_kws,当annot为True时,可设置各个参数,包括大小,颜色,加粗,斜体字等 # fmt: 格式设置 plt.show()
结果
使用sklearn中的feature_selection库中SelectKBest函数进行特征选择,参数中的score_func选择来进行特征选择F检验(f_classif)【计算样本的方差分析f值】
代码以及函数解析
#处理数据空值,用0代替
data = data.replace(np.nan, 0)
# 特征选择F检验(f_classif)
#参数说明:score_func[得分方法]
classifiers = SelectKBest(score_func=f_classif, k=5)
# 用于计算训练数据的均值和方差
fits = classifiers.fit(data.drop('stroke', axis=1), data['stroke'])
# Dataframe的单元格可以存放数值、字符串等,这和excel表很像,同时Dataframe可以设置列名columns与行名index
x = pd.Dataframe(fits.scores_)
print(x)
columns = pd.Dataframe(data.drop('stroke', axis=1).columns)
# concat函数是pandas底下的方法,可以把数据根据不同的轴进行简单的融合
# pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False,
# keys=None, levels=None, names=None, verify_integrity=False)
# 参数说明:
# objs:series,dataframe,或者panel构成的序列list
# axis:0 行,1列
fscores = pd.concat([columns, x], axis=1)
fscores.columns = ['属性特征', '得分']
# sort_values()是pandas中比较常用的排序方法,其主要涉及以下三个参数:
# by : str or list of str(字符或者字符列表)
# Name or list of names to sort by.
# 当需要按照多个列排序时,可使用列表
# ascending : bool or list of bool, default True
# (是否升序排序,默认为true,降序则为false。如果是列表,则需和by指定的列表数量相同,指明每一列的排序方式)
fscores.sort_values(by='得分', ascending=False)
plt.show()
print(fscores)
结果
经过EDA,热图以及SelectKBest 和 F_Classif 特征检查,最终特征筛选为age(年龄)、hypertension(高血压)、heart_disease(心脏病)、ever_married(是否已婚)、avg_glucose_level(平均血糖水平)
注意:筛选特征和特征降维不同
本文仅是筛选特征,特征降维大伙可以自己试试,特征降维会考虑特征之间的相互关系,从而产生新特征来成为训练集指标。
代码
#针对葡萄糖水平进行分箱处理 data.avg_glucose_level = pd.cut(data.avg_glucose_level,4,labels=[0,1,2,3]) # 实现等距分箱,分为4个箱,并用0,1,2,3替代原数据 print(data.avg_glucose_level) #针对年龄进行分箱处理 data.age = pd.cut(data.age,4,labels=[0,1,2,3]) # 实现等距分箱,分为4个箱 print(data.age)
结果
分箱区间
贝叶斯公式
设实验E为样本空间,A为E的事件,B1,B2,…,Bn为Ω的一 个分割,且P(Bi)>0,i=1,2,…,n,则由:
上式被称为贝叶斯公式
1.5 数据集拆分根据题目要求70%训练贝叶斯模型,30%预测 (即训练集3577,测试集1533)
代码
# 分割数据 train_x, test_x, train_y, test_y = train_test_split(data,data['stroke'], random_state=1, test_size=0.3) # train_test_split 函数【从 sklearn.model_selection 中调用】参数说明 # train_data:所要划分的样本特征集 # train_target:所要划分的样本结果 # test_size:样本占比,如果是整数的话就是样本的数量 # random_state:是随机数的种子。 # 随机数种子:是该组随机数的编号,在需要重复试验的时候,保证得到一组一样的随机数。 # 数据形式 print(train_x.shape, train_y.shape, test_y.shape,test_x.shape)
结果
将筛选后的特征部分经过标签编码处理后的数据进行数组化处理,并将筛选特征age(年龄)、hypertension(高血压)、heart_disease(心脏病)、ever_married(是否已婚)、avg_glucose_level(平均血糖水平)以及中风与否构成need_data数据集
代码
#数组化处理 data_age=Series.tolist(train_x.age) data_hypertension=Series.tolist(train_x.hypertension) data_heart_disease=Series.tolist(train_x.heart_disease) data_ever_married=Series.tolist(train_x.ever_married) data_avg_glucose_level=Series.tolist(train_x.avg_glucose_level) data_stroke= Series.tolist(train_y) #np.vstack拼接数组 need_data=np.vstack((data_age,data_hypertension,data_heart_disease,data_ever_married,data_avg_glucose_level,data_stroke)).tolist() #检验查看处理结果 print(need_data)
结果
根据EDA、热图、SelectKBest 和 F_Classif综合分析降维后的age、hypertension、heart_disease、ever_married、avg_glucose_level五种特征,高年龄、已婚、 高血压、有心脏病、 平均血糖水平高者,中风概率高。对此根据贝叶斯原理进行题目贝叶斯公式推得
P(中风|高年龄已婚高血压有心脏病平均血糖高)
=P(高年龄已婚高血压有心脏病平均血糖高|中风)P(中风)
/P(高年龄已婚高血压有心脏病*平均血糖高)
先验概率P(Bi)(i=1,2,…)表示各种原因发生的可能性大小
代码
def train_1(self):
# 统计data_stroke的种类及数量,用于后续计算
count_y = Counter(self.t_data[5])
print(count_y)
# 先统计y的种类,并计算P(Y=c)的先验概率,再切分训练数据
# 计算先验概率并对应y值存入字典,然后根据不同的y切分数据,各自存入一个列表,这些列表存于字典ys
# 统计y的种类,并计算概率,再切分训练数据
ys = {}
for y in count_y.keys():
# print(count_y.keys())
# dict_keys([0.0, 1.0])
ys[y] = []
# 计算先验概率并对应y值存入字典
self.p_y[y] = count_y[y] / len(self.t_data[0])
# print(count_y[y])# 结果为3411,166
# print(self.p_y[y] )#先验概率结果0.9535923958624546 以及 0.04640760413754543
# 遍历数据,根据其y存入对应列表
for i in range(len(self.t_data[0])):
# 将数据切分后分别存入字典中的列表,key是对应的y值
# print(self.t_data[:, i]) #eg;[47. 0. 0. 1. 72.2 0. ]每个个体数据
# print(self.t_data[5][i])#中风与否
ys[self.t_data[5][i]].append(self.t_data[:, i])#将对应的中风数组与0.0,未中风数组与1.0形成字典
print('完成数据处理,我要开始学习了')
for item in ys.items():
# print(ys.items())# items() 以列表返回可遍历的(键, 值) 元组数组
# print(item)
# print('hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh/n')
self.train_2(item)
print('学习完毕!可以开始预测')
def train_2(self, _y):
# 先把数据转化为矩阵,便于接下来切片统计运算
print(_y)
data = np.array(_y[1])
# print(_y[1])
# 计算P(Xi=k | Y = 中风与否)的先验概率,统计每个特征的值的种类
count_x1 = Counter(data[:, 0])
count_x2 = Counter(data[:, 1])
count_x3 = Counter(data[:, 2])
count_x4 = Counter(data[:, 3])
count_x5 = Counter(data[:, 4])
# 检查结果
#正是出现两部分count_x[0-5]中风与否的两种先验概率
# print('count_x1',count_x1)
# print('count_x2',count_x2)
# print('count_x3',count_x3)
# print('count_x4', count_x4)
# print('count_x5', count_x5)
# 计算相应的概率,存入字典
for x1 in count_x1.keys():
self.p_x1_y['{}_{}'.format(x1, _y[0])] = count_x1[x1] / len(data)
for x2 in count_x2.keys():
self.p_x2_y['{}_{}'.format(x2, _y[0])] = count_x2[x2] / len(data)
for x3 in count_x3.keys():
self.p_x3_y['{}_{}'.format(x3, _y[0])] = count_x3[x3] / len(data)
for x4 in count_x4.keys():
self.p_x4_y['{}_{}'.format(x4, _y[0])] = count_x4[x4] / len(data)
for x5 in count_x5.keys():
self.p_x5_y['{}_{}'.format(x5, _y[0])] = count_x5[x5] / len(data)
1.6.2 后验条件计算(单组)
后验概率: P(Bi|A)(i=1,2…)则反映当产生了中风结果A之后,再对各种原因概率的新认识,故称,在此采用输入方式进行检验查看数据预测状况
代码
def analyse_input(self): # 计算后验概率并比较
in_datas = input('输入x1,x2,x3,x4,x5(空格隔开):').split(' ')
p_p = 0
result = []
#将输入类型str转换至与x1,x2,3,x4,5
in_data=[1,2,3,4,5]
in_data[0]=int(in_datas[0])
in_data[1] = int(in_datas[1])
in_data[2] = int(in_datas[2])
in_data[3] = int(in_datas[3])
in_data[4] = int(in_datas[4])
# print(type(in_data[4]))
for j in self.p_y.keys():
# try:
pp = self.p_y[j] * self.p_x1_y['{}_{}'.format(in_data[0], j)] *self.p_x2_y['{}_{}'.format(in_data[1], j)]* self.p_x3_y['{}_{}'.format(in_data[2], j)]*self.p_x4_y['{}_{}'.format(in_data[3], j)] *self.p_x5_y['{}_{}'.format(in_data[4], j)]
# print(self.p_y[j])
if self.p_y[j]>0.5:
print('未中风概率为',pp)
else:
print('中风概率为',pp)
if pp >= p_p: # 观察到,对于相同的输入,可能出现两种不同预测结果(对于本次数据来说,只有两种结果),要对此做处理
if pp > p_p: # 若有出现更大的概率,需要把先前已有的所有结果全部替换
if not result: # 开始的时候列表是空的,如果直写循环替换,其实那个循环根本不会开始。如果在循环后添加,那将会导致接下来有的结果会重复进入列表(被替换的和被添加的)
result.append(j)
else:
for r in range(len(result)):
result[r] = j
elif p_p == pp:
result.append(j)
p_p = pp
# except:
# print(result)
result = list(set(result))
if len(result) == 1:
print('预测结果为:{}'.format(result[0]))
else:
print('可能结果如下:', end='')
for e in result:
print(e)
结果
代码
def analyse_input2(self): # 计算测试集数据后验概率并比较
p_p = 0
result = []
in_data=[1,2,3,4,5]
# print(self.c_data.shape[1])
for m in range(0,self.c_data.shape[1]):
#将输入类型str转换至与x1,x2,x3,x4,x5
in_data[0]=int(self.c_data[0,m])
in_data[1] = int(self.c_data[1,m])
in_data[2] = int(self.c_data[2,m])
in_data[3] = int(self.c_data[3,m])
in_data[4] = int(self.c_data[4,m])
# print(type(in_data[4]))
for j in self.p_y.keys():
# try:
pp = self.p_y[j] * self.p_x1_y['{}_{}'.format(in_data[0], j)] *self.p_x2_y['{}_{}'.format(in_data[1], j)]* self.p_x3_y['{}_{}'.format(in_data[2], j)]*self.p_x4_y['{}_{}'.format(in_data[3], j)] *self.p_x5_y['{}_{}'.format(in_data[4], j)]
# print(self.p_y[j])
if self.p_y[j]>0.5:
print('未中风概率为',pp)
else:
print('中风概率为',pp)
if pp >= p_p: # 观察到,对于相同的输入,可能出现两种不同预测结果(对于本次数据来说,只有两种结果),要对此做处理
if pp > p_p: # 若有出现更大的概率,需要把先前已有的所有结果全部替换
if not result: # 开始的时候列表是空的,如果直写循环替换,其实那个循环根本不会开始。如果在循环后添加,那将会导致接下来有的结果会重复进入列表(被替换的和被添加的)
result.append(j)
else:
for r in range(len(result)):
result[r] = j
elif p_p == pp:
result.append(j)
p_p = pp
# except:
# print(result)
result = list(set(result))
# print('hhhhhresult',result)
if len(result) == 1:
print('预测结果为:{}'.format(result[0]))
self.predict[m]=result[0]
# print(m)#0-1532
else:
print('可能结果如下:', end='')
for e in result:
print(e)
结果
准确率(Precision) = 系统检索到的相关事件 / 系统所有检索到的事件总数
#计算得分
def score(self, test_target):
count=0
# print(np.array(test_target))
# print(np.array(self.predict.values()))
# 数据格式转换
T=str(list(self.predict.values()))
s=str(list(np.array(test_target)))
# print(type(n),type(s))
for i in range(0, test_target.shape[0]):
print(i)
print(s[i],T[i])
if s[i] == T[i]:
count += 1# 累计正确数
score = count / (test_target.shape[0])
print('the accuracy is:', score)
结果
1.7.2 召回率计算召回率(Recall) = 系统检索到的相关事件 / 系统所有相关的事件总数
代码
#计算得分
def score(self, test_target):
count=0
number=0
# print(np.array(test_target))
# print(np.array(self.predict.values()))
# 数据格式转换
T=str(list(self.predict.values()))
s=str(list(np.array(test_target)))
# print(type(n),type(s))
for i in range(0, test_target.shape[0]):
print(i)
print(s[i],T[i])
if s[i] == T[i]:
count += 1
if s[i] !=T[i]:
number+=1
score = count / (test_target.shape[0])
score2= count/(count+number)
print('准确率:', score)
print('召回率:', score2)
结果



