以分析数据集未前往就诊的预约挂号
为例,总结一下探索性数据分析的基本流程。
目录
简介
数据集的简介
数据来源kaggle,未前往就诊的挂号预约
指一个人预约了医生,收到了所有的指示却没有按约去医院就诊。该数据集包含11万条巴西病人预约挂号的求诊信息,每行数据包含有关患者特点的14个变量,具体有:
- PatientId:病人ID,
- AppointmentID:预约流水号ID,
- Gender:预约者的性别,
- ScheduledDay:作出预约的具体时间,
- AppointmentDay:预约的就诊日期,
- Age:病人年龄,
- Neighbourhood:医院所在位置,
- Scholarship:是否参加巴西福利项目 Bolsa Família
- Hipertension: 是否是高血压,
- Diabetes:是否是糖尿病,
- Alcoholism:是否是酗酒,
- Handcap:是否是残障,
- SMS_received:病人是否收到短信通知,
- No-show:
no
表示病人如约就诊,yes
表示病人没有前往就诊。
提出问题
数据分析的目标就是解决问题,提出感兴趣的或是以业务为导向的问题。一般是两种情况:要么获取一批数据,然后根据它提问;要么先提问,然后根据问题收集数据。
这里提的问题如下:
- 病人的年龄是如何分布的?哪个年龄段的病人更多?
- 未按约去就诊的病人有多少?占多大的比例?
- 人们一般会预约在一周的哪一天就诊?
- 一天中的哪个时段,预约的人最多?
- 一般会提前几天预约挂号,即病人等待就诊的时长是多久?
- 哪些重要因素会影响病人是否如约去就诊?例如,年龄,性别,是否参加福利项目,一周中的哪天就诊等。
理解数据
理解数据背景,每个字段的实际含义。主要包括:导入数据和查看数据基本信息,比如,一些描述统计信息。
导入必要的包和数据:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
df = pd.read_csv('noshowappointments-kagglev2-may-2016.csv')
查看基本信息
查看数据集中有哪些列
df.head()
查看数据集是否有缺失值、重复值以及每列的数据类型。共14列,每列都是110527个值,不存在缺失值,也不存在重复值。
df.info()
df.duplicated().sum()
检查异常值,发现年龄的最小值有误。
df.describe()
df.Age.describe()
输出如下:
count 110527.000000
mean 37.088874
std 23.110205
min -1.000000
25% 18.000000
50% 37.000000
75% 55.000000
max 115.000000
Name: Age, dtype: float64
数据清洗
这一步主要是评估数据来识别数据质量或结构中的问题,并通过修改、替换或删除数据来清理数据,以确保数据集具有最高质量和尽可能结构化。
1、将所有列名统一修改成小写并以下划线连接,便于操作。
df.rename(columns=lambda x: x.strip().lower().replace('-','_'), inplace=True )
df.head(2)
2、删除与问题无关的列
要删除的列包括:
-
PatientId:病人ID,
-
neighbourhood: 医院位置
-
Hipertension: 是否是高血压,
-
Diabetes:是否是糖尿病,
-
Alcoholism:是否是酗酒,
-
Handcap:是否是残障,
-
SMS_received:病人是否收到短信通知,
df.drop([‘patientid’,‘hipertension’,‘diabetes’,‘alcoholism’,‘handcap’,‘sms_received’,‘neighbourhood’], axis=1, inplace= True)
df.tail(2)
3、处理异常值。
通过上面的观察发现age
列的最小值为-1,最大值为115。最小值明显是错误值,而最大值不确定是否有误,但我觉得它可能是真实的,所以保留。
df = df[df['age'] > 0]
df.age.describe()
4、对age
列进行分段,并创建新的列 age_group
bin_labels = ['Teenager','Young','Middle','Old']
bin_edges = [0,19,38,56,116]
df['age_group'] = pd.cut(df['age'], bin_edges, labels=bin_labels)
df['age_group'].isnull().sum()
探索性数据分析(建立指标模型与可视化)
执行EDA,可以探索并扩充数据,以最大限度地发挥自己数据分析、可视化和模型构建的潜力。
探索数据涉及在数据中查找模式,可视化数据中的关系,并对正在使用的数据建立直觉。
经过探索后,可以删除异常值,并从数据中创建更好的特征。
问题 1:病人的年龄是如何分布的?哪个年龄段的病人更多?
plt.subplots(figsize=(6,4))
df['age_group'].value_counts().plot(kind='bar')
plt.title('Age Distribution')
plt.xlabel('Age')
plt.ylabel('Frequence');
可以看到发出预约挂号的病人中,青年人最多,其次是中年人和青少年,老年人最少。
问题2:未按约去就诊的病人有多少?占多大的比例?
x_vaule = df['no_show'].value_counts()
print(x_vaule)
labels = 'Show up','No show'
explode = [0, 0.1]
plt.subplots(figsize=(5,5))
plt.axes(aspect=1)
# 设置饼图样式,标签,突出显示,圆上文本格式,显示阴影,文本位置离圆心距离,起始角度,百分比的文本离圆心距离
plt.pie(x=x_vaule, labels=labels,explode=explode,autopct='%.1f %%', shadow=True, labeldistance=1.1, startangle = 90,pctdistance = 0.6 )
plt.title('The proportion of patients who did not go to the hospital as appointment ');
通过统计函数可以得出未按约定到医院就诊的病人有21680人,观察饼图可以知道,该人数占据总预约人数的20.3%。
问题3:人们一般会预约在一周的哪一天就诊?
# 先将预约日期转换成日期类型,然后换成一周表示
df['appointmentday'] = pd.to_datetime(df['appointmentday'], errors='coerce')
df['appointment_weekday'] =df['appointmentday'].dt.weekday_name
plt.subplots(figsize=(8,5))
plt.plot(df['appointment_weekday'].value_counts().keys(),df['appointment_weekday'].value_counts().values)
plt.title('Appointment Day Distribution')
plt.xlabel('Appointment Day')
plt.ylabel('Frequence');
可以看到,人们更喜欢在星期三就诊,其次是星期二,星期四和星期五预约就诊的人相对少些,星期六的人最少。这里没有出现星期天,也许是因为星期天是休息日。
问题4:一天中的哪个时段,预约的人最多?
#先将scheduleday处理成日期类型,然后提取小时
df['scheduledday'] = pd.to_datetime(df['scheduledday'], errors='coerce')
df['scheduledhour'] = df['scheduledday'].dt.hour
# df['scheduledhour'].describe()
#将一天的时间分成三个时段,上午,下午,晚上
bin_labels = ['Morning','Afternoon','Night']
bin_edges = [5,12,18,22]
df['scheduledhour_cut'] = pd.cut(df['scheduledhour'], bin_edges, labels=bin_labels)
# df['scheduledhour_cut'].isnull().sum()
df['scheduledhour_cut'].value_counts()
plt.subplots(figsize=(6,4))
df['scheduledhour_cut'].value_counts().plot(kind='bar')
plt.title('Scheduled Time Distribution')
plt.xlabel('Scheduled Time')
plt.ylabel('Frequence');
可以看到,绝大多数的病人选择在上午预约,一部分病人选择在下午预约,极少病人选择在晚上预约。一般下午6点以后,医院的医生可能已经下班了,预约成功的几率会下降。
问题5:一般会提前几天预约挂号,即病人等待就诊的时长是多久?
df['waittime'] = df['appointmentday'].dt.date - df['scheduledday'].dt.date
df['waittime'].describe()
等待时长
的统计信息如下:
count 106987
mean 10 days 04:00:04.710852
std 15 days 06:19:27.050399
min -6 days +00:00:00
25% 0 days 00:00:00
50% 4 days 00:00:00
75% 14 days 00:00:00
max 179 days 00:00:00
Name: waittime, dtype: object
得到的等待时长
出现了负数值,这些值应该是错误的。至于等待时长
很大的数据,不确定是否是错误的。这里留下那些等待时长大于等于0天
的数据。
df = df[df['appointmentday'].dt.date >= df['scheduledday'].dt.date]
df['waittime'] = (df['appointmentday'].dt.date - df['scheduledday'].dt.date)
df['waittime'].describe()
处理后的结果:
count 106982
mean 10 days 04:00:53.840833
std 15 days 06:19:37.756884
min 0 days 00:00:00
25% 0 days 00:00:00
50% 4 days 00:00:00
75% 14 days 00:00:00
max 179 days 00:00:00
Name: waittime, dtype: object
人们平均提前10天预约,大多数人提前一星期预约,等待时长最短是0天,即当天预约当天就诊,等待时长最久的是179天。
问题6:哪些重要因素会影响病人是否如约去就诊?
例如,年龄,性别,是否参加福利项目,一周中的就诊时间等
计算去就诊和未去就诊的人数的比例函数
def cal_proportion(col_name):
"""按传入的列与no-show分组,统计不同类的数量,并计算各类的比例
"""
counts = df.groupby(['no_show',col_name]).count()['appointmentid']
total = df.groupby(['no_show']).count()['appointmentid']
no_show_proportions = counts['Yes'] / total['Yes']
show_proportions = counts['No']/ total['No']
return no_show_proportions,show_proportions
绘制图形函数
def double_bar(bar_data1,bar_data2,xlabels,xtick_label):
# 绘制条柱,先设置每个等级组的x坐标位置和每个条柱的宽度
ind = np.arange(len(bar_data1))
width = 0.35
red_bar = plt.bar(ind,bar_data1, width, color='r', alpha=0.7, label='No show')
blue_bar = plt.bar(ind+width, bar_data2, width, color='b',alpha=0.7, label='Show up')
# 标题和标签
plt.ylabel('Proportion')
plt.xlabel(xlabels)
plt.title('Proportion by '+ xlabels + ' and No-Show')
locations = ind + width / 2 # x 坐标刻度位置
plt.xticks(locations, xtick_label)
# 图例
plt.legend()
6.1 不同年龄段的病人,如约去就诊的情况怎么样?
计算各个年龄段,按约去就诊的比例和未按约去就诊的比例
no_show = cal_proportion('age_group')[0]
show_up = cal_proportion('age_group')[1]
xtick_label = ['Teenager', 'Young', 'Middle', 'Old']
double_bar(no_show,show_up,'Age Group',xtick_label)
通过上图可以看到,不同年龄段的病人如约去就诊的比例是不同的。年龄越高,去就诊的比例比未去就诊的比例就越高,而青少年和年轻人则是未去就诊的比例高于按约去就诊的比例。
6.2 不同性别的病人,如约去就诊的情况怎么样?
no_show = cal_proportion('gender')[0]
show_up = cal_proportion('gender')[1]
xtick_label = ['Female', 'Male']
double_bar(no_show,show_up,'Gender',xtick_label)
看起来,男女患者去医院就诊的概率相似。但在所有患者里,女性预约患者更多。
6.3 是否参加福利项目,影响患者如约去就诊吗?
no_show = cal_proportion('scholarship')[0]
show_up = cal_proportion('scholarship')[1]
xtick_label = ['False', 'True']
double_bar(no_show,show_up,'Scholarship',xtick_label)
可以看到,未参加巴西福利项目的病人更倾向于按约就诊;而参加了巴西福利项目的病人按约就诊的比例低于未按约就诊的比例。还可以清楚地看到,绝大多数的病人并没有参加福利项目。
6.4 在一周的哪天就诊,是否会影响病人如约去就诊?
no_show = cal_proportion('appointment_weekday')[0]
show_up = cal_proportion('appointment_weekday')[1]
xtick_label = ['Friday', 'Monday', 'Saturday', 'Thursday', 'Tuesday', 'Wednesday']
double_bar(no_show,show_up,'Appointment Weekday',xtick_label)
除开星期六外,病人更喜欢在星期四、星期二和星期三如约去就诊;而约在星期五和星期一的病人,未去就诊的比例更高。同时也可以知道,更多的病人选择在星期二和星期三就诊。
结论/交流
得出结论这一步通常使用机器学习或推理性统计来完成,本文的重点是使用描述性统计得出结论。
与他人交流结果,要证明发现的见解。如果最终目标是构建系统,那么需要分享构建的结果,解释得出设计结论的方式,并报告该系统的性能。
- 交流结果的方法有多种:报告、幻灯片、博客帖子、电子邮件、演示文稿,甚至对话。
- 数据可视化在这个过程中可以发挥很大的价值。
实验结果:在所有这些病人中,女性病人和中老年病人更关注身体健康,年龄越大,更愿意按约去就诊。人们一般倾向于在星期二和星期三的上午去就诊,有的病人会提前很久就预约医生,而大多数的病人会在预约当天去就诊。病人的年龄、病人是否参加福利项目以及在一周的哪天去就医这几个因素会影响病人是否按约去医院就诊。
数据本身的局限性:数据中还提供了病人患的几种病,例如,高血压,酗酒,糖尿病等,还提供了医院的地理位置,病人是否患这些病和医院的位置可能也会影响他们能否按约去医院就诊。此外,病人等待的时间长短,可能也会影响他是否按约去就诊。目前,暂时未作这三方面的探究,只对目前感兴趣的问题做了分析。
探索方式局限性:此次探索分析使用的只是一些简单的统计计算和可视化分析。这样得出的结论只能是暂时的,还有进一步验证的需要(使用统计检验或者机器学习建模等)。