药品零售作为一个传统行业,正受到新零售方式崛起、医改不断深化、行业监管逐步提升等挑战,零售药店位居医药产业链下游,是医药零售的重要终端。在中国,药店是指面向消费者销售医药产品和各类健康产品的零售门店,近年来也发展出网上药店这类线上终端。而中国药店渠道仅占药品总销售约2成,如将我国药品销售分为医院、药店和基层医疗机构三大终端,药品在药店渠道销售占比约为22.5%,远不及医院终端68.4%的销售占比。
但与医院相比,零售药店无论是购药的经济性还是便利性上都更具优势,但在医药未分家的现状下,目前药店专业性方面仍不如医院终端。在这样的新竞争、新规则环境下,零售药店行业有必要从扩容时代1.0系列的跑马圈地、医保管控时代2.0系列的精耕细作,进入精准定位、精准服务的3.0新纪元。
药店以便利性为核心,具备一定的经济性。零售药店是当前三大医药消费终端之一,上游连接医药制造商或批发商,下游为消费者提供药品、医疗器械、保健品等医药健康产品。和医院相比,零售药店规模相对较小,可灵活布局,具有明显的便利性优势;零售药店竞争激烈,规模化的龙头可借助较强的供应链整合能力为消费者提供更具性价比的产品,因此也具备一定的经济性。
连锁药店药品的概念和特点决定了药品的购买者为成年人。成年人有一定的疾病判断能力,能较为准确的判断病的类别和病情严重程度,有一定的药品使用经验;且在经济上有一定的来源,可以自主支配药品费用;文化程度高的人和医疗保健意识更强的人;工作节奏快的人;如白领阶层在选购药品时,更倾向于知名品牌和声誉好的公司的产品,注重药品质量更愿意去大型的连锁药店买药,药品质量有保证。而消费者到连锁药店购买OTC药品的主要原因为无需去医院就诊的小病痛,方便快捷无需繁琐流程等。
本项目获取到了某家药店年销售数据,通过对相关药品销售数据进行分析,了解该药店在一年里的销售情况,得到以下三个指标:
(3)客单价,即月均消费金额/月消费次数。来判断是否需要提高客单价来提高收益。
同时分析消费趋势、需求量前几位的药品等来增加药店销售收益。
图 1 数据探索具体流程图
能在经过模型评价后已经达到了要求,但在实际生产环境应用过程中,发现模型的性能并不理想,继而对模型进行重构与优化的过程。
2.1.2 数据展示
项目研究数据为6578条药店年销售数据,共7个属性参数。接下来需要对原数据进行清洗、分布等操作来更抽取有效数据。
表 2-1 原始数据展示
由上表可知,原数据集七个特征名称分别:(1)购药时间;(2)社保卡号;(3)商品编号;(4)商品名称;(5)销售数量;(6)应收金额;(7)实收金额。接下来将对其展开分析。
import numpy as np
from pandas import Series,DataFrame
import pandas as pd
#导入数据
file_name = r'C:\某药店年销售数据.xlsx'
# 使用ExcelFile()时需要传入目标excel文件所在路径及文件名称
xls = pd.ExcelFile(file_name)
# 使用parse()可以根据传入的sheet名称来提取对应的表格信息
dataDF = xls.parse('Sheet1',dtype='object')
# 输出前五行数据
dataDF.head()
#查看数据几行几列
print(dataDF.shape)
#查看索引
print(dataDF.index)
#查看每一列的列表头内容
print(dataDF.columns)
#查看每一列数据统计数目
print(dataDF.count())
本案例中数据清洗主要针对的是缺失值处理,查找出原始数据集中的缺失值并对其所在行执行删除操作,通过info()可明显看出操作后行数减少3行,说明操作成功。
代码如下:
dataDF.rename(columns={'购药时间':'销售时间'},inplace=True)
print('删除缺失值前:', dataDF.shape)
# 使用info查看数据信息,
print(dataDF.info())
#删除缺失值
dataDF = dataDF.dropna(subset=['销售时间','社保卡号'], how='any')
print('\n删除缺失值后',dataDF.shape)
print(dataDF.info())
从数据中可以看出,共有6578行,但是没有哪一列是有6578行的,表明存在缺失值。
(1)字符串转换为浮点型
因为初始导入的数据,销售数量、应收金额、实收金额都为字符串格式,无法进行计算,因此需要将格式转换成数值格式,这里取float类型。
代码如下:
dataDF['销售数量'] = dataDF['销售数量'].astype('float')
dataDF['应收金额'] = dataDF['应收金额'].astype('float')
dataDF['实收金额'] = dataDF['实收金额'].astype('float')
print(dataDF.dtypes)
(2)日期列字符串转换为日期数据类型
当前数据日期字段为字符串(object)格式,并且含有星期几,这会给时间统计造成麻烦,因此销售时间的格式需要由object调整为datetime格式,并删除后面的星期几。
代码如下:
def splitSaletime(timeColSer):
??? timeList = []
??? for value in timeColSer:
??????? dateStr = value.split(' ')[0]
??????? timeList.append(dateStr)
? timeSer = pd.Series(timeList)
??? return timeSer
timeSer = dataDF.loc[:,'销售时间']
dateSer = splitSaletime(timeSer)
dateSer[0:3]
dataDF.loc[:,'销售时间'] = pd.to_datetime(dataDF.loc[:,'销售时间'],\????????????????????????????????????? format='%Y-%m-%d',errors='coerce')
dataDF.info()
因转换日期过程中不符合日期格式的数值会被转换为空值None,所以转换完成后需要检验是否有空值存在。
True表明确实有控制存在,此时使用dropna()函数删除空值所在行,如下所示:
可看出行数减少,说明空值所在行删除成功
按照购买时间,对数据进行排序
代码如下:
dataDF = dataDF.dropna(subset=['销售时间','社保卡号'],how='any')
datasDF = dataDF.reset_index(drop = True)
dataDF.head()
2.2.5 异常值处理
使用describe()函数查看数据大致情况
发现最小值存在负数,但是对于销售数量与金额来说,负数是不可能存在的,因此需要对数据进行异常值处理。
删除异常值前: (6549, 7) 删除异常值后: (6506, 7)。至此,数据已完成全部预处理工作。
3.1.1 月消费次数
月均消费次数=总消费次数/总月份数。总消费次数定义为同一天内同一个人发生的所有消费算作一次消费,需要判断销售时间和社保卡号这两列里面有哪些数据是重复的。删除重复值之后的行数即为总消费次数。
代码如下:
#删除重复数据
kpil_Df = dataDF.drop_duplicates(subset=['销售时间','社保卡号'])
totalI = kpil_Df.shape[0]
print('总消费次数=',totalI)
#计算月份数
#按销售时间升序排序
kpil_Df = kpil_Df.sort_values(by='销售时间', ascending=True)
#重命名行名
kpil_Df = kpil_Df.reset_index(drop=True)
#获取时间范围
startTime = kpil_Df.loc[0,'销售时间']
endTime = kpil_Df.loc[totalI-1,'销售时间']
#计算月份
#天数
daysI = (endTime-startTime).days
mounthI = daysI//30
print('月份数=',mounthI)
#月平均消费次数
kpil_I = totalI//mounthI
print('业务指标1:月均消费次数=', kpil_I)
因为销售的过程会打折,这也就是会有应收金额和实收金额两个数据。对于计算销售的收益,以实收金额为准,也就是计算实际收费金额的总和(sum函数)再除以总的月数。
代码如下:
#消费总金额
totalMoneyF = dataDF.loc[:,'实收金额'].sum()
mounthMoney = totalMoneyF // mounthI
print('业务指标2:月均消费金额=', mounthMoney)
客单价即为指标1/指标2
pct = totalMoneyF / totalI
print('业务指标3:客单价=', pct)
3.2.1 每日消费金额
代码如下:
import matplotlib.pyplot as plt
import matplotlib
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
groupDF = dataDF
groupDF.index = groupDF['销售时间']
print(groupDF.head())
gb = groupDF.groupby(groupDF.index)
print(gb)
dayDF = gb.sum()
print(dayDF)
plt.plot(dayDF['实收金额'])
plt.title('按天消费金额')
plt.xlabel('时间')
plt.ylabel('实收金额')
plt.show()
运行结果:
图 2 按天消费金额可视化图表
从图中可以看出,每日消费总额差异较大,除了个别天出现比较大笔的消费,大部分人消费情况维持在1000-2000元以内。
3.2.2 每月消费金额
代码如下:
gb = groupDF.groupby(groupDF.index.month)
print(gb)
monthDF = gb.sum()
print(monthDF)
plt.plot(monthDF['实收金额'])
plt.title('按月消费金额')
plt.xlabel('时间')
plt.ylabel('实收金额')
plt.show()
运行结果:
图 3 按月消费金额可视化图表
图中显示,7月消费金额最少,这是因为7月份的数据不完整,所以不具参考价值。1月、4月、5月和6月的月消费金额差异不大。2月和3月的消费金额迅速降低,这可能是由于春节期间人员普遍外出较少的原因。
3.2.3 药品的销售情况
代码如下:
#聚合统计各种药品数量
medicine = groupDF[['商品名称','销售数量']]
bk = medicine.groupby('商品名称')[['销售数量']]
re_medicine = bk.sum()
#对销售药品数量按降序排序
re_medicine = re_medicine.sort_values(by='销售数量', ascending=False)
top_medicine = re_medicine.iloc[:10,:]
top_medicine
# 数据可视化,用条形图展示前十的药品
top_medicine.plot(kind = 'bar')
plt.title('销售前十的药品')
plt.xlabel('药品')
plt.ylabel('数量')
plt.show()
运行结果:
图中可以看出销售前十的药品,第一和第十相差较大,这与药品的种类及适应性相关。该图标可为药店进货及库存量提供参考建议。
3.2.4 每日消费金额分布情况
代码如下:
temp1=list(dataDF['销售时间'])
temp2=list(dataDF['实收金额'])
plt.scatter(temp1,temp2)
plt.title('每天销售金额')
plt.xlabel('时间')
plt.ylabel('实收金额')
plt.show()
运行结果:
从图中可以看出,每天消费金额在500以下的占绝大多数,存在极个别个离群点,有消费金额很大的情况。
(1)该药店1-7月的月均消费次数为89次,月均消费金额为50668元,客单价为56.91元。
(2)单名顾客的消费数量主要是个位数为主,顾客购买最多的产品是对苯磺酸氨氯地平片(安内真),其次是开博通。可以适当增加这两种药品的进货量。
(3)销售趋势整体呈下降趋势,4月达到峰值后,下降趋势明显,7月达到最低值。经营者应当着重观察4月份以后周边经营环境是否发生变化, 药店内部是否出现经营纰漏,找出销量下跌原因,改善销售情况。
通过这次对药店销售数据的分析让我将python数据分析的知识点得到了一个综合运用,对于知识点有了更深刻的理解。对于python数据分析的流程,不外乎就是获取数据、处理数据、分析数据、数据可视化,能运用python数据分析解决具体的问题,再根据分析结果得到一些趋势的判断,这就是数据分析的意义。
参 考 文 献
、
药品零售作为一个传统行业,正受到新零售方式崛起、医改不断深化、行业监管逐步提升等挑战,零售药店位居医药产业链下游,是医药零售的重要终端。在中国,药店是指面向消费者销售医药产品和各类健康产品的零售门店,近年来也发展出网上药店这类线上终端。而中国药店渠道仅占药品总销售约2成,如将我国药品销售分为医院、药店和基层医疗机构三大终端,药品在药店渠道销售占比约为22.5%,远不及医院终端68.4%的销售占比。
但与医院相比,零售药店无论是购药的经济性还是便利性上都更具优势,但在医药未分家的现状下,目前药店专业性方面仍不如医院终端。在这样的新竞争、新规则环境下,零售药店行业有必要从扩容时代1.0系列的跑马圈地、医保管控时代2.0系列的精耕细作,进入精准定位、精准服务的3.0新纪元。
药店以便利性为核心,具备一定的经济性。零售药店是当前三大医药消费终端之一,上游连接医药制造商或批发商,下游为消费者提供药品、医疗器械、保健品等医药健康产品。和医院相比,零售药店规模相对较小,可灵活布局,具有明显的便利性优势;零售药店竞争激烈,规模化的龙头可借助较强的供应链整合能力为消费者提供更具性价比的产品,因此也具备一定的经济性。
连锁药店药品的概念和特点决定了药品的购买者为成年人。成年人有一定的疾病判断能力,能较为准确的判断病的类别和病情严重程度,有一定的药品使用经验;且在经济上有一定的来源,可以自主支配药品费用;文化程度高的人和医疗保健意识更强的人;工作节奏快的人;如白领阶层在选购药品时,更倾向于知名品牌和声誉好的公司的产品,注重药品质量更愿意去大型的连锁药店买药,药品质量有保证。而消费者到连锁药店购买OTC药品的主要原因为无需去医院就诊的小病痛,方便快捷无需繁琐流程等。
本项目获取到了某家药店年销售数据,通过对相关药品销售数据进行分析,了解该药店在一年里的销售情况,得到以下三个指标:
(3)客单价,即月均消费金额/月消费次数。来判断是否需要提高客单价来提高收益。
同时分析消费趋势、需求量前几位的药品等来增加药店销售收益。
图 1 数据探索具体流程图
能在经过模型评价后已经达到了要求,但在实际生产环境应用过程中,发现模型的性能并不理想,继而对模型进行重构与优化的过程。
2.1.2 数据展示
项目研究数据为6578条药店年销售数据,共7个属性参数。接下来需要对原数据进行清洗、分布等操作来更抽取有效数据。
表 2-1 原始数据展示
由上表可知,原数据集七个特征名称分别:(1)购药时间;(2)社保卡号;(3)商品编号;(4)商品名称;(5)销售数量;(6)应收金额;(7)实收金额。接下来将对其展开分析。
import numpy as np
from pandas import Series,DataFrame
import pandas as pd
#导入数据
file_name = r'C:\某药店年销售数据.xlsx'
# 使用ExcelFile()时需要传入目标excel文件所在路径及文件名称
xls = pd.ExcelFile(file_name)
# 使用parse()可以根据传入的sheet名称来提取对应的表格信息
dataDF = xls.parse('Sheet1',dtype='object')
# 输出前五行数据
dataDF.head()
#查看数据几行几列
print(dataDF.shape)
#查看索引
print(dataDF.index)
#查看每一列的列表头内容
print(dataDF.columns)
#查看每一列数据统计数目
print(dataDF.count())
本案例中数据清洗主要针对的是缺失值处理,查找出原始数据集中的缺失值并对其所在行执行删除操作,通过info()可明显看出操作后行数减少3行,说明操作成功。
代码如下:
dataDF.rename(columns={'购药时间':'销售时间'},inplace=True)
print('删除缺失值前:', dataDF.shape)
# 使用info查看数据信息,
print(dataDF.info())
#删除缺失值
dataDF = dataDF.dropna(subset=['销售时间','社保卡号'], how='any')
print('\n删除缺失值后',dataDF.shape)
print(dataDF.info())
从数据中可以看出,共有6578行,但是没有哪一列是有6578行的,表明存在缺失值。
(1)字符串转换为浮点型
因为初始导入的数据,销售数量、应收金额、实收金额都为字符串格式,无法进行计算,因此需要将格式转换成数值格式,这里取float类型。
代码如下:
dataDF['销售数量'] = dataDF['销售数量'].astype('float')
dataDF['应收金额'] = dataDF['应收金额'].astype('float')
dataDF['实收金额'] = dataDF['实收金额'].astype('float')
print(dataDF.dtypes)
(2)日期列字符串转换为日期数据类型
当前数据日期字段为字符串(object)格式,并且含有星期几,这会给时间统计造成麻烦,因此销售时间的格式需要由object调整为datetime格式,并删除后面的星期几。
代码如下:
def splitSaletime(timeColSer):
??? timeList = []
??? for value in timeColSer:
??????? dateStr = value.split(' ')[0]
??????? timeList.append(dateStr)
? timeSer = pd.Series(timeList)
??? return timeSer
timeSer = dataDF.loc[:,'销售时间']
dateSer = splitSaletime(timeSer)
dateSer[0:3]
dataDF.loc[:,'销售时间'] = pd.to_datetime(dataDF.loc[:,'销售时间'],\????????????????????????????????????? format='%Y-%m-%d',errors='coerce')
dataDF.info()
因转换日期过程中不符合日期格式的数值会被转换为空值None,所以转换完成后需要检验是否有空值存在。
True表明确实有控制存在,此时使用dropna()函数删除空值所在行,如下所示:
可看出行数减少,说明空值所在行删除成功
按照购买时间,对数据进行排序
代码如下:
dataDF = dataDF.dropna(subset=['销售时间','社保卡号'],how='any')
datasDF = dataDF.reset_index(drop = True)
dataDF.head()
2.2.5 异常值处理
使用describe()函数查看数据大致情况
发现最小值存在负数,但是对于销售数量与金额来说,负数是不可能存在的,因此需要对数据进行异常值处理。
删除异常值前: (6549, 7) 删除异常值后: (6506, 7)。至此,数据已完成全部预处理工作。
3.1.1 月消费次数
月均消费次数=总消费次数/总月份数。总消费次数定义为同一天内同一个人发生的所有消费算作一次消费,需要判断销售时间和社保卡号这两列里面有哪些数据是重复的。删除重复值之后的行数即为总消费次数。
代码如下:
#删除重复数据
kpil_Df = dataDF.drop_duplicates(subset=['销售时间','社保卡号'])
totalI = kpil_Df.shape[0]
print('总消费次数=',totalI)
#计算月份数
#按销售时间升序排序
kpil_Df = kpil_Df.sort_values(by='销售时间', ascending=True)
#重命名行名
kpil_Df = kpil_Df.reset_index(drop=True)
#获取时间范围
startTime = kpil_Df.loc[0,'销售时间']
endTime = kpil_Df.loc[totalI-1,'销售时间']
#计算月份
#天数
daysI = (endTime-startTime).days
mounthI = daysI//30
print('月份数=',mounthI)
#月平均消费次数
kpil_I = totalI//mounthI
print('业务指标1:月均消费次数=', kpil_I)
因为销售的过程会打折,这也就是会有应收金额和实收金额两个数据。对于计算销售的收益,以实收金额为准,也就是计算实际收费金额的总和(sum函数)再除以总的月数。
代码如下:
#消费总金额
totalMoneyF = dataDF.loc[:,'实收金额'].sum()
mounthMoney = totalMoneyF // mounthI
print('业务指标2:月均消费金额=', mounthMoney)
客单价即为指标1/指标2
pct = totalMoneyF / totalI
print('业务指标3:客单价=', pct)
3.2.1 每日消费金额
代码如下:
import matplotlib.pyplot as plt
import matplotlib
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
groupDF = dataDF
groupDF.index = groupDF['销售时间']
print(groupDF.head())
gb = groupDF.groupby(groupDF.index)
print(gb)
dayDF = gb.sum()
print(dayDF)
plt.plot(dayDF['实收金额'])
plt.title('按天消费金额')
plt.xlabel('时间')
plt.ylabel('实收金额')
plt.show()
运行结果:
图 2 按天消费金额可视化图表
从图中可以看出,每日消费总额差异较大,除了个别天出现比较大笔的消费,大部分人消费情况维持在1000-2000元以内。
3.2.2 每月消费金额
代码如下:
gb = groupDF.groupby(groupDF.index.month)
print(gb)
monthDF = gb.sum()
print(monthDF)
plt.plot(monthDF['实收金额'])
plt.title('按月消费金额')
plt.xlabel('时间')
plt.ylabel('实收金额')
plt.show()
运行结果:
图 3 按月消费金额可视化图表
图中显示,7月消费金额最少,这是因为7月份的数据不完整,所以不具参考价值。1月、4月、5月和6月的月消费金额差异不大。2月和3月的消费金额迅速降低,这可能是由于春节期间人员普遍外出较少的原因。
3.2.3 药品的销售情况
代码如下:
#聚合统计各种药品数量
medicine = groupDF[['商品名称','销售数量']]
bk = medicine.groupby('商品名称')[['销售数量']]
re_medicine = bk.sum()
#对销售药品数量按降序排序
re_medicine = re_medicine.sort_values(by='销售数量', ascending=False)
top_medicine = re_medicine.iloc[:10,:]
top_medicine
# 数据可视化,用条形图展示前十的药品
top_medicine.plot(kind = 'bar')
plt.title('销售前十的药品')
plt.xlabel('药品')
plt.ylabel('数量')
plt.show()
运行结果:
图中可以看出销售前十的药品,第一和第十相差较大,这与药品的种类及适应性相关。该图标可为药店进货及库存量提供参考建议。
3.2.4 每日消费金额分布情况
代码如下:
temp1=list(dataDF['销售时间'])
temp2=list(dataDF['实收金额'])
plt.scatter(temp1,temp2)
plt.title('每天销售金额')
plt.xlabel('时间')
plt.ylabel('实收金额')
plt.show()
运行结果:
从图中可以看出,每天消费金额在500以下的占绝大多数,存在极个别个离群点,有消费金额很大的情况。
(1)该药店1-7月的月均消费次数为89次,月均消费金额为50668元,客单价为56.91元。
(2)单名顾客的消费数量主要是个位数为主,顾客购买最多的产品是对苯磺酸氨氯地平片(安内真),其次是开博通。可以适当增加这两种药品的进货量。
(3)销售趋势整体呈下降趋势,4月达到峰值后,下降趋势明显,7月达到最低值。经营者应当着重观察4月份以后周边经营环境是否发生变化, 药店内部是否出现经营纰漏,找出销量下跌原因,改善销售情况。
通过这次对药店销售数据的分析让我将python数据分析的知识点得到了一个综合运用,对于知识点有了更深刻的理解。对于python数据分析的流程,不外乎就是获取数据、处理数据、分析数据、数据可视化,能运用python数据分析解决具体的问题,再根据分析结果得到一些趋势的判断,这就是数据分析的意义。
参 考 文 献
、