药房销售情况分析(python篇)

运用python中的numpy、pandas等包,可以帮助我们很方便的进行数据统计、数据分析。今天,通过朝阳医院2018的销售数据这个案例来简单做一下展示。

数据分析的基本过程一般分为以下几个部分:
提出问题、理解数据、数据清洗、构建模型、数据可视化。

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

现在从第一步开始着手。

1.提出问题

在数据分析之前,我们先要明确分析目标是什么,这样可以避免我们像无头苍蝇一样拿着数据无从下手,也可以帮助我们更高效的选取数据,进行分析研究。

本次的分析目标是从销售数据中分析出以下业务指标:

1)月均消费次数

2)月均消费金额

3)客单价

4)消费趋势

有了分析目标,我们再来关注一下数据情况。

2.理解数据

1)导入数据包,提取数据文件

在提取数据中,为了保证数据文件中的数据读取正常,通常会将函数的dtype参数设置成’object’。 object的意思是统一按照字符串的格式来读取数据。

#导入numpy、pandas包
import numpy as np
import pandas as pd
#导入数据
xls = pd.ExcelFile(r'D:\dataFile\朝阳医院2018年销售数据.xlsx', dtype='object')
salesDf = xls.parse('数据1',dtype='object')
#当excel中只有一个数据工作表时,用下面的函数直接读取数据也可以。
#salesDf = pd.read_excel(r'D:\dataFile\朝阳医院2018年销售数据.xlsx', dtype='object')

2)查看导入数据的基本状况

#查看导入数据的类型
type(salesDf)

pandas.core.frame.DataFrame

#查看导入数据的每个项目的类型
salesDf.dtypes

购药时间 object
社保卡号 object
商品编码 object
商品名称 object
销售数量 object
应收金额 object
实收金额 object
dtype: object

#查看数据的基本大小
salesDf.shape

(6578, 7)

#查看开头几行数据,看看数据是否读取正确
salesDf.head()

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

#用描述函数describ来查看一下整个数据的基本状况
salesDf.describe()

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

从这些函数中,我们可以基本了解到数据由购药时间、社保卡号、商品编码、商品名称、销售数量、应收金额、实收金额这七个基本项目组成,数据条数为6578条。

3.数据清洗

取得了数据,并不能马上就开始进行数据分析。我们得到的数据通常并不是完全符合我们分析要求的,而且可能存在缺失值、异常值,这些数据都会使我们的分析结果产生偏差。所以在分析之前,需要进行子集选择、缺失数据补充、异常值处理、数据类型转换等多个步骤。这些都属于数据清理的范畴。
在数据分析中,通常有多达60%的时间是花在数据清洗中的。通常的清洗步骤有以下几步:
• 选择子集

• 列名重命名

• 缺失数据处理

• 数据类型转换

• 数据排序

• 异常值处理

这些步骤有些不是一步就能完成的,可能需要重复操作。

现在开始对药店销售数据进行数据清洗。

1)选择子集

药店销售数据中,项目较少,选择子集可以忽略,我们从列名重命名开始。

2)列名重命名

销售数据集,购药时间显示为销售时间更为合理,我们先把这个项目名称做一下变更。

#购药时间->销售时间
nameChangeDict = {'购药时间':'销售时间'}
#参数inplace=True表示覆盖元数据集
salesDf.rename(columns = nameChangeDict,inplace=True)

现在再来显示一下数据,可以发现购药时间已经变成了销售时间。

salesDf.head()

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

3)缺失数据处理

对于缺失数据,我们可以有几种处理方法:

▪ 删除

当缺失数据占总数据量的比例很小的时候,我们通常采用删除的处理方法。

▪ 合理值填充

在某些不适合删除的场合,我们有时候也会对缺失数据进行合理值填充,如平均值,中位数,相邻数据等等。

#首先查看一下哪些项目存在缺失值
salesDf.isnull().any()

销售时间 True
社保卡号 True
商品编码 True
商品名称 True
销售数量 True
应收金额 True
实收金额 True
dtype: bool

好吧,每个项目都存在缺失值。在这个销售数据中,销售时间和社保卡号是必须项目,不可或缺。所以我们在这里只把销售时间和社保卡号有缺失的数据做删除处理。我们来查看一下销售时间和社保卡缺失的数据大小,然后做删除处理。

#查看一下缺失值的数量
#通常可以用isnull函数来查找缺失值
salesDf[salesDf[['销售时间','社保卡号']].isnull().values == True]

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

#序号6574因为销售时间和社保卡号都缺失,所以会出现两次。所以我们要去掉一下重复数据。
naDf = salesDf[salesDf[['销售时间','社保卡号']].isnull().values == True].drop_duplicates()
naDf

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

从上面可以清楚看出销售时间和社保卡号缺失的数据一共有三条,当数据量大的时候我们可以只显示条数,不显示数据内容

#缺失数据行数
naDf.shape[0]

3

现在把这些缺失数据进行删除

#删除前数据集规模显示
salesDf.shape

(6578, 7)

#含有销售时间和社保卡号的缺失数据删除
salesDf = salesDf.dropna(subset=['销售时间','社保卡号'],how = 'any')
#删除后数据集规模显示
salesDf.shape

(6575, 7)

在数据删除后要及时更新一下最新的序号,不然可能会产生问题。我开始做的时候,在这里没有更新数据序号,导致后续销售时间数据类型用函数转换后与元数据合并时发生了错位,数据发生了新的缺失,后来发现导致问题产生的原因在这里。

#重命名行名(index):排序后的列索引值是之前的行号,需要修改成从0到N按顺序的索引值
salesDf=salesDf.reset_index(drop=True)

4)数据类型转换

▪ 数量、金额项目:从字符串类型转换为数值(浮点型)类型

salesDf['销售数量'] = salesDf['销售数量'].astype('float')
salesDf['应收金额'] = salesDf['应收金额'].astype('float')
salesDf['实收金额'] = salesDf['实收金额'].astype('float')
print('转换后的数据类型:\n',salesDf.dtypes)

转换后的数据类型:
销售时间 object
社保卡号 object
商品编码 object
商品名称 object
销售数量 float64
应收金额 float64
实收金额 float64
dtype: object

▪ 日期项目:从字符串类型转换为日期类型
销售日期中包含了日期和星期,我们只要保留日期内容即可。这里用一个自定义的函数dateChange来实现这个功能。

#日期转换
def dateChange(dateSer):
    dateList = []
    for i in dateSer:
        #例如2018-01-01 星期五,分割后为:2018-01-01
        str = i.split(' ')[0]
        dateList.append(str)
    dateChangeSer = pd.Series(dateList)
    return dateChangeSer
dateChangeSer = dateChange(salesDf['销售时间'])
dateChangeSer

0 2018-01-01
1 2018-01-02
2 2018-01-06
3 2018-01-11
4 2018-01-15
5 2018-01-20
6 2018-01-31
7 2018-02-17
8 2018-02-22
9 2018-02-24
10 2018-03-05
11 2018-03-05
12 2018-03-05
13 2018-03-07
14 2018-03-09
15 2018-03-15
16 2018-03-15
17 2018-03-15
18 2018-03-20
19 2018-03-22
20 2018-03-23
21 2018-03-24
22 2018-03-24
23 2018-03-28
24 2018-03-29
25 2018-04-05
26 2018-04-07
27 2018-04-13
28 2018-04-22
29 2018-05-01

6545 2018-04-05
6546 2018-04-05
6547 2018-04-09
6548 2018-04-10
6549 2018-04-10
6550 2018-04-10
6551 2018-04-12
6552 2018-04-13
6553 2018-04-13
6554 2018-04-14
6555 2018-04-15
6556 2018-04-15
6557 2018-04-15
6558 2018-04-15
6559 2018-04-16
6560 2018-04-17
6561 2018-04-18
6562 2018-04-21
6563 2018-04-22
6564 2018-04-24
6565 2018-04-25
6566 2018-04-25
6567 2018-04-25
6568 2018-04-26
6569 2018-04-26
6570 2018-04-27
6571 2018-04-27
6572 2018-04-27
6573 2018-04-27
6574 2018-04-28
Length: 6575, dtype: object

salesDf['销售时间'] = dateChangeSer
salesDf.head()

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

在做完转化后再观察一下有没有产生新的缺失值

salesDf['销售时间'].isnull().any()

False

salesDf.dtypes

销售时间 object
社保卡号 object
商品编码 object
商品名称 object
销售数量 float64
应收金额 float64
实收金额 float64
dtype: object

数据没有产生新的缺失,我们继续向下,把销售时间的数据类型转为日期型。

dateSer=pd.to_datetime(salesDf['销售时间'], format = '%Y-%m-%d', errors='coerce')
dateSer

0 2018-01-01
1 2018-01-02
2 2018-01-06
3 2018-01-11
4 2018-01-15
5 2018-01-20
6 2018-01-31
7 2018-02-17
8 2018-02-22
9 2018-02-24
10 2018-03-05
11 2018-03-05
12 2018-03-05
13 2018-03-07
14 2018-03-09
15 2018-03-15
16 2018-03-15
17 2018-03-15
18 2018-03-20
19 2018-03-22
20 2018-03-23
21 2018-03-24
22 2018-03-24
23 2018-03-28
24 2018-03-29
25 2018-04-05
26 2018-04-07
27 2018-04-13
28 2018-04-22
29 2018-05-01

6545 2018-04-05
6546 2018-04-05
6547 2018-04-09
6548 2018-04-10
6549 2018-04-10
6550 2018-04-10
6551 2018-04-12
6552 2018-04-13
6553 2018-04-13
6554 2018-04-14
6555 2018-04-15
6556 2018-04-15
6557 2018-04-15
6558 2018-04-15
6559 2018-04-16
6560 2018-04-17
6561 2018-04-18
6562 2018-04-21
6563 2018-04-22
6564 2018-04-24
6565 2018-04-25
6566 2018-04-25
6567 2018-04-25
6568 2018-04-26
6569 2018-04-26
6570 2018-04-27
6571 2018-04-27
6572 2018-04-27
6573 2018-04-27
6574 2018-04-28
Name: 销售时间, Length: 6575, dtype: datetime64[ns]

dateSer.isnull().any()

True

compareDf = pd.DataFrame(dateSer[dateSer.isnull()],salesDf[dateSer.isnull()]['销售时间'])
compareDf

《药房销售情况分析(python篇)》

《药房销售情况分析(python篇)》

查看了下数据,产生空值的原因是因为数据中出现了’2018-02-29’这样实际不存在的日期。在实际应用中,最好能向业务部门询问一下产生的原因,看下是不是因为日期推算不正确导致了这样原因的产生,需不需要将这样的数据进行一下必要的修正。这里就简单的把数据进行删除。

salesDf['销售时间'] = dateSer
salesDf.dtypes

销售时间 datetime64[ns]
社保卡号 object
商品编码 object
商品名称 object
销售数量 float64
应收金额 float64
实收金额 float64
dtype: object

salesDf.shape

(6575, 7)

salesDf=salesDf.dropna(subset=['销售时间','社保卡号'],how='any')
salesDf.shape

(6552, 7)

salesDf=salesDf.reset_index(drop=True)

5)数据排序
销售记录一般是以销售时间为顺序排列的,所以我们对数据进行一下排序

#按销售时间排序
salesDf = salesDf.sort_values(by='销售时间')
#再次更新一下序号
salesDf = salesDf.reset_index(drop = True)

6)异常值处理

在下面数据集的描述指标中可以看出,存在销售数量为负的数据,这明显是不合理的,我们把这部分数据也进行删除

salesDf.describe()

《药房销售情况分析(python篇)》

#删除异常值:通过条件判断筛选出数据
#查询条件
querySer=salesDf.loc[:,'销售数量']>0
#应用查询条件
print('删除异常值前:',salesDf.shape)
salesDf=salesDf.loc[querySer,:]
print('删除异常值后:',salesDf.shape)

删除异常值前: (6552, 7)
删除异常值后: (6509, 7)

数据清洗完了之后,我们终于可以来搭建我们的模型啦。当然如果在模型搭建过程中再次发现数据异常情况,我们还是要对数据进行进一步的清洗。

4.构建模型

1)业务指标1:月均消费次数=总消费次数 / 月份数

总消费次数:同一天内,同一个人发生的所有消费算作一次消费。这里我们根据列名(销售时间,社区卡号)结合,如果这两个列值同时相同,只保留1条,将重复的数据删除

月份数:数据已经按照销售时间进行排序,只需将最后的数据与第一条数据相减就可换算出月份数

#总消费次数计算
kpDf = salesDf.drop_duplicates(subset=['销售时间','社保卡号'])
total = kpDf.shape[0]
print('总消费次数为:',total)

总消费次数为: 5345

#月份数计算
startDay = salesDf.loc[0,'销售时间']
print('开始日期:',startDay)
endDay = salesDf.loc[salesDf.shape[0]-1,'销售时间']
print('结束日期:',endDay)
monthCount = (endDay - startDay).days//30
print('月份数:',monthCount)

开始日期: 2018-01-01 00:00:00
结束日期: 2018-07-18 00:00:00
月份数: 6

#业务指标1:月均消费次数=总消费次数 / 月份数
kpi1 = total / monthCount
print('业务指标1:月均消费次数=',kpi1)

业务指标1:月均消费次数= 890.8333333333334

2)指标2:月均消费金额 = 总消费金额 / 月份数

totalMoney = salesDf['实收金额'].sum()
kpi2 = totalMoney / monthCount
print('业务指标2:月平均消费金额=',kpi2)

业务指标2:月平均消费金额= 50672.494999999624

3)指标3:客单价=总消费金额 / 总消费次数

kpi3 = kpi2 / kpi1
print('业务指标3:客单价=',kpi3)

业务指标3:客单价= 56.8821272217021

4)指标4:消费趋势,画图:折线图

#在进行操作之前,先把数据复制到另一个数据框中,防止对之前清洗后的数据框造成影响
groupDf=salesDf
#第1步:重命名行名(index)为销售时间所在列的值
groupDf.index=groupDf['销售时间']
groupDf.head()

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

#第2步:分组
gb=groupDf.groupby(groupDf.index.month)
#第3步:应用函数,计算每个月的消费总额
mounthDf=gb.sum()
mounthDf

《药房销售情况分析(python篇)》

#解决图表中中文表示不正的问题
import matplotlib as mpl
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['font.serif'] = ['SimHei']
import seaborn as sns
sns.set_style("darkgrid",{"font.sans-serif":['simhei', 'Arial']})
import matplotlib.pyplot as plt
%matplotlib inline
#绘制销售数量图
plt.plot(mounthDf['销售数量'],color = 'b')
plt.title('销售趋势')
plt.xlabel('2018年月份')
plt.ylabel('销售数量')
<matplotlib.text.Text at 0xdcf4ba8>

《药房销售情况分析(python篇)》

#设置图片大小
fig = plt.figure(figsize=(10,6))
#应收金额图
plt.plot(mounthDf['应收金额'],color = 'r',label = '应收金额')
#实收金额图
plt.plot(mounthDf['实收金额'],color = 'b',label = '实收金额')
#图标位置
plt.legend(loc='lower left')
#大标题
plt.title('应收金额与实收金额')
#X坐标标签
plt.xlabel('2018年月份')
#Y坐标标签
plt.ylabel('金额')
<matplotlib.text.Text at 0xdd81e80>

《药房销售情况分析(python篇)》
《药房销售情况分析(python篇)》

从图中可以看出销售数量和销售金额的整个趋势是基本一致的,四月份为最高点,二月份为前期一个最低点,而且在四月份以后销售一直处于向下的趋势,在记录的日期中,七月份达到了历史最低水平。

总结:

在这个简单的模型分析做成中,通过自己的发现查找以及百度顺利解决了问题,这里做一个简单的总结。

1.如果是默认索引的话,在数据清洗中发生数据行数的变化时,应该要用reset_index函数及时把索引最新化,这样可以避免很多意料之外的错误。

2.数据量不是很大的情况下,对于缺失值,我们应该多观察,看看能不能进行转换成合理的值,比如像这个数据集中【2018-02-29】这样的数据很可能是业务员录入失误,可以通过了解实际情况后修改数据。

3.在画图时发现中文的标题,xy坐标标识无法正常显示,通过百度加了关于字体的设置后顺利地解决了问题。

    原文作者:糖糖
    原文地址: https://zhuanlan.zhihu.com/p/35331561
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞