以朝阳医院2018年销售数据为例,目的是了解朝阳医院在2018年里的销售情况,这就需要知道几个业务指标,本次的分析目标是从销售数据中分析出以下业务指标:
(1)业务指标1:月均消费次数
月均消费次数 = 总消费次数 / 月份数(同一天内,同一个人所有消费算作一次消费)
(2)业务指标2:月均消费金额
月均消费金额 = 总消费金额 / 月份数
(3)客单价
客单价 = 总消费金额 / 总消费次数
(4)消费趋势(可视化展示,并根据可视化结果给出下属问题分析得出的结论)
a、分析每天的消费金额
b、分析每月的消费金额
c、分析药品销售情况(截取销售数量最多的前十种药品,并用条形图展示结果)
数据分析基本过程 数据分析基本过程包括:获取数据、数据清洗、构建模型、数据可视化以及消费趋势分析。
(一)数据获取
1. 读取数据,并且返回表格的前几行数据
import pandas as pd # 读取数据(最好使用 object 类型读取) data = pd.read_excel("朝阳医院2018年销售数据.xlsx", dtype="object") # 通过panda模块内置的read_excel方法读取格式为.xlsx的excel表格内容,以object类型读取,还有一个为read_csv方法读取的是格式为.csv的Excel表格 data.head()# 返回data的前几行数据,默认为前五行,括号内部可填入数字表示显示几行数据
图1:读取数据,并且返回表格的前几行数据
2. 修改为 DataFrame 格式,并且查看数据的形状
# 修改为 DataFrame 格式 dataDF = pd.DataFrame(data)# 定义一个dataDF来存储修改为DataFrame格式的数据 # DataFrame是一种表格型的数据结构。它的每一列可以是不同的值类型(例如布尔型、数值型、字符串等), # 此外它既有行索引index,又有列索引columns。我们可以将它看成是由Series组成的字典(将每一列看成是一个Series)。 # 查看数据的形状,即几行几列 print(dataDF.shape)# 形状(shape) 打印出数据的形状 print("dataDF.index:{}".format(dataDF.index))# 行索引(index) 打印出数据的参数
图2:修改为 DataFrame 格式,并且查看数据的形状
3. 查看每一列的列表头内容
# 查看每一列的列表头内容 print("dataDF.columns:{}".format(dataDF.columns))# 打印出数据的每个列表头的说明,以及数据的类型 print(dataDF.columns)# 列索引(columns)
图3:查看每一列的列表头内容
4. 查看每一列数据统计数目
# 查看每一列数据统计数目 # count()返回每一列中的非空值的个数。 print("dataDF计数:{}".format(dataDF.count()))
图4:查看每一列数据统计数目
(二)数据分析
数据清洗过程包括:选择子集、列名重命名、缺失数据处理、数据类型转换、数据排序及异常值处理。
1. 列名重命名
# 使用 rename 函数,把"购药时间" 改为 "销售时间" dataDF.rename(columns={"购药时间": "销售时间"}, inplace=True) # 通过rename函数对于数据的列名(columns)里面的购药时间改成销售时间,inplace:是否替换,默认为False。 # inplace为False时返回修改后结果,变量自身不修改。inplace为True时返回None,变量自身被修改。 print("dataDF.columns:{}".format(dataDF.columns)) # 打印出修改后的数据结果,以及数据类型
图5:列名重命名
2. 缺失值处理
# 删除缺失值之前 # 打印出数据缺失值之前的类型(几行几列) print("删除缺失值之前dataDF.shape:{}".format(dataDF.shape)) # 使用dropna函数删除缺失值 # 因为Excel中的空的cell读入pandas中是空值(NaN),这个NaN是个浮点类型,一般当作空值处理,所以要先去除NaN再进行分隔字符串。 dataDF = dataDF.dropna()# 通过内置的dropna函数删除缺失的数据 # 删除缺失值之后 # 打印出缺失的数据被删除后的数据类型(几行几列) print("删除缺失值之后dataDF.shape:{}".format(dataDF.shape)) # 将字符串转为浮点型数据 # 将销售数量,应收金额,实收金额的数据类型由字符串转变为浮点数类型(float) # 数据类型转换:astype函数:字符串转换成数值(浮点型) dataDF["销售数量"] = dataDF["销售数量"].astype("f8") dataDF["应收金额"] = dataDF["应收金额"].astype("f8") dataDF["实收金额"] = dataDF["实收金额"].astype("f8") print("dataDF.dtypes:{}".format(dataDF.dtypes))
图6:缺失值处理
3. 数据类型转换之自定义函数
# 将日期进行分割 # 自定义函数::分隔销售口期,获取销售日期 # 输入:timeColSer销售时间这一列,是个Series数据类型 # 输出:分割后的时间,返回的也是Series数据类型 def splitsaletime(timeColser): timelist = [] for t in timeColser:# [0]表示选取的分片,这里表示切割完后选取第一个分片 timelist.append(t.split(" ")[0])# split(" ")分割 timeser = pd.Series(timelist)# 将列表转行为一维数据Series类型 return timeser
图7:数据类型转换之自定义函数
4. 数据类型转换之调用自定义函数
# 获取"销售时间"这一列数据 t = dataDF.loc[:, "销售时间"]# 获取销售时间这一列数据的数据存储到t里面 # 调用函数去除星期,获取日期 timeser = splitsaletime(t)# 对字符串进行分割,获取销售日期 # 修改"销售时间"这一列日期 dataDF.loc[:, "销售时间"] = timeser print(dataDF.head())
图8:数据类型转换之调用自定义函数
5. 数据类型转换
# 字符串转日期 # errors='coerce'如果原始数据不符合日期的格式,转换后的值为NaT dataDF.loc[:, "销售时间"] = pd.to_datetime(dataDF.loc[:, "销售时间"], errors='coerce') # 将原本是字符串的日期类型转变成日期datatime类型的,pandas提供了一个可选的参数errors, # 传入errors=‘coerce’,pandas遇到不能转换的数据就会赋值为NaN(Not a Number) print("dataDF.dtypes:{}".format(dataDF.dtypes))
图9:数据类型转换
6. 删除空值
# 转换日期过程中不符合日期格式的数值会被转换为空值None, # 这里删除为空的行 dataDF = dataDF.dropna() print("dataDF.shape:{}".format(dataDF.shape)) # 按销售时间进行升序排序 dataDF = dataDF.sort_values(by='销售时间', # by :按几列排序 ascending=True)# ascending=True为升序,ascending=False为降序 print("dataDF.head():{}".format(dataDF.head())) # 重置索引(index) dataDF = dataDF.reset_index(drop=True) # 通过reset进行索引的重置,drop=True表示删除用作新索引的列,也就是删除被用作索引的第一列
图10:删除空值
7. 删除异常值
# 查看描述统计信息 # 描述指标:“销售数量”值不能小于0 print(dataDF.describe()) # 删除异常值:通过条件判断筛选出数据 # 将"销售数量"这一列中小于0的数排除掉 pop = dataDF.loc[:, "销售数量"] > 0 dataDF = dataDF.loc[pop, :]
图11:删除异常值
8. 删除重复数据
# 排除异常值后再次查看描述统计信息 print(dataDF.describe()) # 计算总消费次数 # 删除重复数据 kpi1_Df = dataDF.drop_duplicates(subset=['销售时间', '社保卡号']) # drop_duplicates是pandas内的一个删除函数,subset:表示要进去重的列名,默认为 None。
图12:删除重复数据
(三)构建模型及数据可视化
数据清洗完成后,需要利用数据构建模型(就是计算相应的业务指标),并用可视化的方式呈现结果。
1. 计算总消费次数
# 计算总消费次数 # 总消费次数:同一天内,同一个人发生的所有消费算作一次消费 # 有多少行 totall = kpi1_Df.shape[0]# 统计有多少行的数据(总消费次数) print('总消费次数:', totall) # 按销售时间升序排序 kpi1_Df = kpi1_Df.sort_values(by='销售时间', ascending=True) # 对销售时间这一列进行升序排序,ascending=True为升序,ascending=False为降序 # 重命名行名(index) kpi1_Df = kpi1_Df.reset_index(drop=True)
图13:计算总消费次数
2. 业务指标1-3(月均消费次数、月均消费金额、客单价)
# 获取时间范围 # 最小时间值 startTime = kpi1_Df.loc[0, '销售时间'] # 最大时间值 endTime = kpi1_Df.loc[totall - 1, '销售时间'] # 计算天数 daysI = (endTime - startTime).days # 月份数:运算符"//"表示取整除,返回商的整数部分 monthsI = daysI // 30 print('月份数:', monthsI) # 计算月均消费次数 # 业务指标1:月均消费次数=总消费次数 / 月份数 kpi1_I = totall // monthsI print('业务指标1:月均消费次数=', kpi1_I) # 总消费金额 totalMoneyF = dataDF.loc[:, '实收金额'].sum() # 月均消费金额 # 业务指标2:月均消费金额 = 总消费金额 / 月份数 monthMoneyF = totalMoneyF / monthsI print('业务指标2:月均消费金额=', monthMoneyF) # 业务指标3:客单价 = 总消费金额 / 总消费次数 # 客单价(per customer transaction)是指商场(超市)每一个顾客平均购买商品的金额,客单价也即是平均交易金额。 pct = totalMoneyF / totall print('业务指标3:客单价=', pct)
图14:月均消费次数、月均消费金额、客单价
3. 业务指标:消费趋势(可视化展示)
# 业务指标:消费趋势,画图-折线图 import matplotlib.pyplot as plt from pylab import mpl # 画图时用于显示中文字符 mpl.rcParams['font.sans-serif'] = ['SimHei'] # SimHei是黑体的意思 # 在操作之前先复制一份数据,防止影响清洗后的数据 groupDf = dataDF # 重命名行(index)为销售时间所在列的值 groupDf.index = groupDf['销售时间'] groupDf.head()
图15:重命名行(index)为销售时间所在列的值
4. a、分析每天的消费金额
# a、分析每天的消费金额 plt.figure(figsize=(20,10))# 设置画布大小 plt.plot(groupDf['实收金额']) # plt.plot(x, y, format_string, **kwargs)可以绘制点和线, 并且对其样式进行控制 # x X轴数据,列表或数组,可选 # y Y轴数据,列表或数组 # format_string 控制曲线的格式字符串,可选 # kwargs 第二组或更多(x,y,format_string),可画多条曲线 plt.title('按天消费金额(ZShiJ)')# 设置图像标题 plt.xlabel('时间') # 设置x轴的标签文本 plt.ylabel('实收金额')# 设置y轴的标签文本 plt.savefig("day.png")# 保存图片 plt.show() # 把图像显示出来。
图16:a、分析每天的消费金额
图17:按天消费金额
分析:由图17按天消费金额,可以看出,每天的消费金额有所不同,但是除了极个别天会出现消费的金额较大,大部分人消费情况基本都在500元以内。
5. b、分析每月的消费金额
# b、分析每月的消费金额 # 将销售时间聚合按月分组 gb = groupDf.groupby(groupDf.index.month) print(gb)
图18:b、分析每月的消费金额
6. 描绘按月消费金额图
# 描绘按月消费金额图 # 对进行按月份分组好数据进行求和,从而看到每个月份的销售数量,应收金额,实收金额的数据和 monthDF = gb.sum() print(monthDF) # plt.figure(figsize=(8,7))# 设置画布大小 plt.plot(monthDF['实收金额'])# 根据实收金额绘制按月消费金额图的线图 plt.title('按月消费金额(ZShiJ)')# 设置图像标题 plt.xlabel('时间') # 设置x轴的标签文本 plt.ylabel('实收金额') # 设置y轴的标签文本 plt.savefig("month.png")# 保存图片 plt.show() # 把图像显示出来。
图19:描绘按月消费金额图
图20:按月消费金额
分析:由图20按月消费金额的结果显示,我们可以看出7月消费金额最少,我认为这是因为7月份的数据不完整,所以不具参考价值。
1月、4月、5月和6月的月消费金额差异不大,2月和3月的消费金额迅速降低,这可能是2月和3月处于春节期间,大部分人都回家过年的原因。
7. c、分析药品销售情况(截取销售数量最多的前十种药品,并用条形图展示结果)
# c、分析药品销售情况(截取销售数量最多的前十种药品,并用条形图展示结果) # 聚合统计各种药品的销售数量 # 对“商品名称”和“销售数量”这两列数据进行聚合为Series形式 medicine = groupDf[['商品名称','销售数量']] # groupby按照商品名称进行分组,算出商品名称对应相应的销售数量 bk = medicine.groupby('商品名称')[['销售数量']] # 按照商品名称对药品的销售数量进行求和 re_medicine = bk.sum()
图21:c、分析药品销售情况
8. 降序排序截取销售数量最多的十种药品
# 对药品销售数量按降序排序 re_medicine = re_medicine.sort_values(by='销售数量', ascending=False)# 降序排序 re_medicine.head() # 截取销售数量最多的十种药品 top_medicine = re_medicine.iloc[:10,:] print(top_medicine)
图22:降序排序截取销售数量最多的十种药品
9. 条形图展示销售数量前十的药品
# 用条形图展示销售数量前十的药品 # 对数据的top_medicine进行条形图的可视化 top_medicine.plot(kind = 'bar',color = 'pink') plt.title('销售前十的药品(ZShiJ)')# 设置图像标题 plt.xlabel('药品') # 设置x轴的标签文本 plt.ylabel('数量') # 设置y轴的标签文本 plt.savefig("top_medicine.png")# 保存图片 plt.show() # 把图像显示出来。
图23:条形图展示销售数量前十的药品
图24:销售前十的药品
分析:由图24销售前十的药品可以得到销售数量最多的前十种药品信息,这些信息将会有助于加强医院对药房的管理,比如多进一些销售数量多的药品。
异常问题与解决方案
问题1:去重复值用错函数
图25:没有成功去重复值
解决方案:Dropna是删除空值和缺失值,删除重复值需要使用drop_duplicates,drop_duplicates是pandas内的一个删除函数,subset:表示要进去重的列名,默认为 None。
图26:计算总消费次数
问题2:需要注意数据类型转换
图27:数据类型转换
解决方法:一定要记得将原始数据字符串格式日期转换成正常的数据日期,否则会影响后面建模。
参考资料
[2] Pandas常用函数大合集
[3] plt.plot()函数解析
[4] Pandas数据排序