贝叶斯中风预测详解--python
1. 内容描述
中风预测:根据世界卫生组织(WHO)的数据,中风是全球第二大死亡原因,约占总死亡人数的11%。该数据集用于根据输入参数(例如性别,年龄,各种疾病和吸烟状况)预测患者是否可能中风。数据中的每一行都提供有关患者的相关信息。 https://mp.weixin.qq.com/s/QobTa9eN0snb9u2lXxX_iQ 70%训练贝叶斯模型,30%预测
数据集👇
链接:https://pan.baidu.com/s/1e47HLe-icM56cTmlWHppQA
提取码:wfey
1.1 字段描述
1.2 Exploratory Data Analysis探索性数据分析
1.2.1数据整体信息以及统计特征
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:均值
1.2.2 id
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列)
1.2.3 gender性别
性别属性说明患者的性别,对此进行中风率和性别对比。由于性别属性有三种值(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()
结果👇
结果分析👇
虽然男女性别数据集并不完全平衡。但由此可见,不同性别之间的中风率没有太大的区别
1.2.4 age年龄
针对年龄属性进行分布图以及分箱图绘制
代码👇
data['age'].nunique()
sns.displot(data['age'])
plt.figure(figsize=(15, 7))
sns.boxplot(data=data, x='stroke', y='age')
结果👇
结果分析👇
60岁以上的人更容易患中风
1.2.5 Hypertension高血压
高血压在老年人中对比年轻人很常见,高血压会可能导致中风
代码👇
data['hypertension'].nunique()
sns.countplot(data=data, x='hypertension', hue='stroke',ax=axes[1])
plt.show()
结果👇
结果分析👇
高血压人群更容易患中风
1.2.6 heart_disease心脏病
1.2.7 ever_married已婚与否
代码👇
data['ever_married'].nunique()
sns.countplot(data=data, x='ever_married', hue='stroke')
plt.show()
结果👇
结果分析👇
已婚人士的中风率更高
1.2.8 work_type工作类型
代码👇
sns.countplot(data=data, x='work_type')
sns.countplot(data=data, x='work_type', hue='stroke')
plt.show()
结果👇
结果分析👇
在私营部门工作的人2号患中风的风险更高。从未工作过的人4号中风率非常低
1.2.9 Residence_type居住类型
代码👇
sns.countplot(data=data,x='Residence_type')
plt.show()
结果👇
结果分析👇
对中风患者影响差异不大
1.2.10 avg_glucose_level患者体内的平均血糖水平
代码👇
data['avg_glucose_level'].nunique()
sns.displot(data['avg_glucose_level'])
sns.boxplot(data=data, x='stroke', y='avg_glucose_level')
plt.show()
结果👇
结果分析👇
中风患者的平均血糖水平偏高
1.2.11 bmi
代码👇
sns.countplot(data=data,x='bmi')
sns.boxplot(data=data,x='stroke',y='bmi')
plt.show()
结果👇
结果分析👇
BMI针对中风几率影响不大
1.2.12 smoking_status吸烟状况
根据计数图,吸烟状况人数分布以及中风情况
代码👇
sns.countplot(data=data,x='smoking_status',hue='stroke')
plt.show()
结果👇
结果分析👇
无论吸烟状况如何,中风的几率都没有太大差异
1.3 特征工程
1.3.1 标签编码
由于数据集由分类数据和数值数据组成,对此使用标签编码器(将分类数据转换为数字数据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.2 特征相关性检查
通过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()
结果👇
1.3.2.2 SelectKBest and F_Classif
使用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(平均血糖水平)
注意:筛选特征和特征降维不同
本文仅是筛选特征,特征降维大伙可以自己试试,特征降维会考虑特征之间的相互关系,从而产生新特征来成为训练集指标。
1.3.3 连续型数据处理
代码👇
#针对葡萄糖水平进行分箱处理
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)
结果👇
分箱区间👇
1.4 贝叶斯模型描述
贝叶斯公式
设实验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)
结果👇
1.6 模型创建
根据EDA、热图、SelectKBest 和 F_Classif综合分析降维后的age、hypertension、heart_disease、ever_married、avg_glucose_level五种特征,高年龄、已婚、 高血压、有心脏病、 平均血糖水平高者,中风概率高。对此根据贝叶斯原理进行题目贝叶斯公式推得
P(中风|高年龄已婚高血压有心脏病平均血糖高)
=P(高年龄已婚高血压有心脏病平均血糖高|中风)P(中风)
/P(高年龄已婚高血压有心脏病*平均血糖高)
1.6.1 先验条件计算
先验概率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)
结果👇
1.6.3 后验条件计算(测试集)
代码👇
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)
结果👇
1.7 结果评估
1.7.1准确率计算
准确率(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)
结果👇
★,°:.☆( ̄▽ ̄)/$:.°★ 。撒花撒花,完美搞定定
完整代码👇
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from pandas import Series
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
import numpy as np
from collections import Counter
data = pd.read_csv('strokePredictionData.csv')
# print(data.info())
# ## 打印数据基本信息
# print(data.describe())
## 打印数据的统计特征
# 删除id列
# data.drop("id", inplace=True, axis=1)
# # 查询表头看是否还有id,仅是验证查看
# print(data.head(0))
# # 为方便对比,创建一个1行2列的画布,figsize设置画布大小
fig, axes = plt.subplots(1, 2, figsize=(10, 5),)
# # 提供关于它的唯一值以及每个值的计数的信息
# print('计数\n', data['age'].value_counts())
# #设置画板颜色风格,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()
# 获取数据类型为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)
# 创建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()
# data['avg_glucose_level'].nunique()
# sns.displot(data['avg_glucose_level'])
# sns.boxplot(data=data, x='stroke', y='avg_glucose_level')
# plt.show()
# data['age'].nunique()
# sns.displot(data['age'])
# plt.figure(figsize=(15, 7))
# sns.boxplot(data=data, x='stroke', y='age')
# 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')
# sns.countplot(data=data, x='work_type')
# sns.countplot(data=data, x='work_type', hue='stroke')
# sns.countplot(data=data,x='bmi')
# sns.boxplot(data=data,x='stroke',y='bmi')
# sns.countplot(data=data,x='smoking_status')
# sns.countplot(data=data,x='smoking_status',hue='stroke')
# plt.show()
# #处理数据空值,用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)
# print(type(data.age))
#针对葡萄糖水平进行分箱处理
data.avg_glucose_level = pd.cut(data.avg_glucose_level,4,labels=[0,1,2,3]) # 实现等距分箱,分为4个箱
# print(data.avg_glucose_level)
#针对年龄进行分箱处理
data.age = pd.cut(data.age,4,labels=[0,1,2,3]) # 实现等距分箱,分为4个箱
# print(data.age)
# 分割数据
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:样本占比,如果是整数的话就是样本的数量
# train_y:训练集中风情况【0/1】
# random_state:是随机数的种子。
# 随机数种子:是该组随机数的编号,在需要重复试验的时候,保证得到一组一样的随机数。
# 数据形式👇
# print(train_x)
#训练集数组化处理
train_x_ages=Series.tolist(train_x.age)
train_x_hypertensions=Series.tolist(train_x.hypertension)
train_x_heart_diseases=Series.tolist(train_x.heart_disease)
train_x_ever_marrieds=Series.tolist(train_x.ever_married)
train_x_avg_glucose_levels=Series.tolist(train_x.avg_glucose_level)
train_ys= Series.tolist(train_y)
#测试集数组化处理
test_x_ages=Series.tolist(test_x.age)
test_x_hypertensions=Series.tolist(test_x.hypertension)
test_x_heart_diseases=Series.tolist(test_x.heart_disease)
test_x_ever_married=Series.tolist(test_x.ever_married)
test_x_avg_glucose_levels=Series.tolist(test_x.avg_glucose_level)
test_ys= Series.tolist(test_y)
#np.vstack拼接数组
need_data=np.vstack((train_x_ages,train_x_hypertensions,train_x_heart_diseases,train_x_ever_marrieds,train_x_avg_glucose_levels,train_ys)).tolist()
test_data=np.vstack((test_x_ages,test_x_hypertensions,test_x_heart_diseases,test_x_ever_married,test_x_avg_glucose_levels,test_y)).tolist()
#检验查看处理结果
# print(need_data)
# print(test_data)
class Bayes:
def __init__(self):
#将数据转化为矩阵
self.t_data = np.array(need_data)
self.c_data=np.array(test_data)
# print(self.c_data)
# 使用字典方便计算时调用
# 存储P(Y=c)的先验概率👇
self.p_y = {}
# 存储P(Xi=k | Y = 中风与否)的先验概率👇
self.p_x1_y = {}
self.p_x2_y = {}
self.p_x3_y = {}
self.p_x4_y = {}
self.p_x5_y = {}
self.predict ={}
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)
# print(self.p_x5_y)
# print(type(x5))
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)
#计算得分
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)
answer = Bayes()
answer.train_1()
answer.analyse_input2()
answer.score(test_y)