朝阳医院2018年销售数据分析
一、提出问题
一般来说,数据分析师拿到这个需求之后不能马上就开始导入然后分析,必须在分析之前要明确自己的分析目的。数据分析只是手段,只是工具而已,能不能解决所要分析的问题,能不能从数据中发现问题,发现商机,才是重点。
重要的话重复三遍:
分析之前一定要明确自己这次分析是为了什么
分析之前一定要明确自己这次分析是为了什么
分析之前一定要明确自己这次分析是为了什么
现在有一份朝阳医院2018年的销售数据,不能随便分析,分析到什么结果算什么结果。分析之前我们要有针对性的分析目标。
那么此次分析的目标是:
- 月均消费次数
- 月均消费金额
- 客单价
- 消费趋势
当我们清楚的了解自己的分析目标之后,就可以开始分析了。
二、理解数据
理解数据是为了清洗数据做准备的,如果你都不了解你手里的数据有多少,分别是什么类型,有多少缺失等,那就无法进行清洗数据的工作,从而无法开始真正的数据分析。
# 首先加载数据分析常用库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
% matplotlib inline
#然后导入数据
sales = pd.read_excel('D:/办公/数据分析/chaoyangyiyuan9062/朝阳医院2018年销售数据.xlsx')
#先看一下总体数据的描述统计分析
sales.describe()
image.png
这里我们可以发现销售数量、应收金额、实收金额出现负值,这些肯定是属于需要清洗筛选的数据了。
#再看一下数据的真实情况
sales.head()
image.png
#再看一下数据信息
sales.info()
image.png
通过以上的理解数据,我们可以着手开始清洗数据了。
三、清洗数据
1、选择子集
在我们获取到的数据中,数据量十分庞大,但是不是每一列都是我们所需要分析的呢,不一定,那么这个时候就要选择整个数据中合适的子集去进行分析,这样可以使后续的分析变得更加方便,在本次案例中,不需要选择子集,所以可以先跳过这一步。
2、列重命名
有些数据的原始列命名对于数据分析来说容易误解,或者容易产生歧义,一不小心就理解错了,很不利于分析,在这个时候,就需要给列名重命名。
#把购药时间改成销售时间,直接在原数据框进行修改。
sales.rename(columns= {'购药时间':'销售时间'},inplace=True)
#看一下修改后的数据
sales.head()
image.png
3、缺失数据处理
任何一个得到的数据都很有可能会有缺失值,那么对于这些缺失值一定需要处理一下,不然会干扰后来的分析结果。删除缺失值用dropna函数,代码如下
#没有时间和社保卡号的消费数据对于本次分析是无效的,
#所以清理一下缺失值
sales = sales.dropna(subset=['销售时间','社保卡号'],how='any')
4、数据类型处理
在导入的时候为了防止有些数据导入不进来,所以强制所有数据都是object类型,但在实际分析上这样是不可能的,所以要把需要改变类型的数据类型改变了,通过观察,我们发现,销售数量,应收金额,实收金额应该改成float类型,销售时间应该清理后改成时间类型,对于改变成float类型的几列,使用astype函数,代码如下。
这里因为科赛网导入的时候无法选择object,所以跳过。
sales['销售数量'] = sales['销售数量'].astype('float64')
sales['应收金额'] = sales['应收金额'].astype('float64')
sales['实收金额'] = sales['实收金额'].astype('float64')
而销售时间那一列,则需要进行处理后才能转换为时间类型,把销售时间的日期和星期分开。
sales['销售时间'], sales['销售星期'] = sales['销售时间'].str.split(' ', 1).str
#切分好之后,把销售时间变为时间类型
sales['销售时间'] = pd.to_datetime(sales['销售时间'],format='%Y-%m-%d',errors='coerce')
#先看一下清洗到这个阶段的数据
sales.head()
image.png
现在数据看起来已经有点那么回事了,接下来把数据按照时间排序一下。排序之后索引会被打乱,所以也需要重置一下索引。代码如下
#将数据按照销售时间排序
sales = sales.sort_values('销售时间',ascending=True)
#重置索引
sales = sales.reset_index(drop=True)
#再看一下数据
sales.head()
image.png
6、异常值处理
这里就要清洗前面理解数据时提到的负值的数据了,这些数据必须是要大于0才属于正常数据。
从数据基本情况可以看出,销售数量和应收金额,实收金额,都有负的异常值,需要把这些值舍去,即选取销售数量和应收金额大于0的列,代码如下
#选取销售数量和应收金额大于0的列
sales = sales[(sales['销售数量'] > 0) & (sales['应收金额'] > 0)]
#看一下目前的数据
sales.head()
image.png
做完以上几个清洗的工作,接下来就可以开始正式的数据分析了。
四、数据分析
1 月均消费次数
这里的月均消费次数定义为总次数除以月份,其中假如一个人一天买了两次药,但只算做消费了一次,即计算次数的时候需要进行去重处理。
#首先对数据进行一个去重,使用drop_duplicates函数
sales = sales.drop_duplicates(subset=['销售时间','社保卡号'])
#去重后看一下一共有多少条数据
total = sales.shape[0]
这里可以看到去重后的数据一共5363条,相比较原始数据的6578条,已经去除了1200多条重复数据。
#再计算月份
#用销售时间的最大值减去最小值即可得到天数,再除以(地板除)三十就可以得到月份了
month = (sales['销售时间'].max() - sales['销售时间'].min()).days // 30
KPI1 = total / month
print('月均消费次数为:',KPI1)
image.png
2 月均消费金额
同样,月均消费金额为总实收金额除以总月份,在计算总金额的时候不能去重,需要都计算上金额。
#计算总金额
sum_sale = sales['实收金额'].sum()
KPI2 = sum_sale / month
print('月均消费金额为:',KPI2)
image.png
3 客单价
客单价就是总实收金额除以总消费次数
kdj = sum_sale / total
print('客单价为:',kdj)
image.png
4 消费趋势
关于消费趋势,首先我们先来看一下每天的消费总金额的变化,把数据按天聚合,绘图,代码如下。
##对去重后的数据按照天进行重新采样
#首先要把索引变成时间
sales.index = pd.DatetimeIndex(sales['销售时间'])
#将索引按天聚合
b = sales.resample('D').sum()
b.head()
image.png
#画图
plt.plot(b.index,b['实收金额'])
plt.xlabel = 'Time'
plt.ylabel = 'Money'
plt.title = '总金额消费趋势图'
plt.show()
image.png
根据上图可以看出每日消费金额主要在1000-4000波动,波动幅度较大,并且有几个峰值特别高的日子。
#按月采样
salesm = sales.resample('M').sum()
#画图
plt.plot(salesm.index, salesm['实收金额'])
plt.show()
image.png
这里看出该药店的销售总额和客流量基本成正比。二月份的客流量最少,同样销售业绩也最差,同样的四月份客流量最高,销售总额也最多。
但是六月份客流量变少了,而消费总额却变多了,可能是因为人均买的药更贵了,具体原因尚且不得而知,七月份的数据如此小的原因是因为七月份只统计了半个月的数据,数据不全,不能拿来做比较。
image.png
通过这里,我们可以发现,周五周六的销售总额要显著的的高于其他日期,即周五周六应该前来买药的人更多,销售的药品更多。
即每周的销售趋势是周日到周四销售总额会有波动,但是幅度不大,周五周六的销售总额相对较高,按月份比较的话,四月份的销售总额显著的高,而二月份的销售总额显著的低,猜测销售总额非常低是因为春节的缘故。
五、反思与总结
本次数据分析过程中遇到一个问题,耗费了许多时间。
问题便是在最后的两个趋势图上,由于数据和题目都是科赛网拿到的,所以一开始在做每月消费金额趋势图的时候代码如下:
#画图之前的代码全部一致
b.plot(x = b.index,y = '实收金额')
plt.xlabel = 'Time'
plt.ylabel = 'Money'
plt.title = '总金额消费趋势图'
plt.show()
就是这样的代码,与科赛网的代码一致,但是却无法正常运行,报错如下:
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-90-25f586ca3e93> in <module>()
1 #画图
2 #plt.plot(b.index,b['实收金额'])
----> 3 b.plot(x = b.index,y = '实收金额',style='-')
4 plt.xlabel = 'Time'
5 plt.ylabel = 'Money'
D:\IT\Anaconda5\lib\site-packages\pandas\plotting\_core.py in __call__(self, x, y, kind, ax, subplots, sharex, sharey, layout, figsize, use_index, title, grid, legend, style, logx, logy, loglog, xticks, yticks, xlim, ylim, rot, fontsize, colormap, table, yerr, xerr, secondary_y, sort_columns, **kwds)
2939 fontsize=fontsize, colormap=colormap, table=table,
2940 yerr=yerr, xerr=xerr, secondary_y=secondary_y,
-> 2941 sort_columns=sort_columns, **kwds)
2942 __call__.__doc__ = plot_frame.__doc__
2943
D:\IT\Anaconda5\lib\site-packages\pandas\plotting\_core.py in plot_frame(data, x, y, kind, ax, subplots, sharex, sharey, layout, figsize, use_index, title, grid, legend, style, logx, logy, loglog, xticks, yticks, xlim, ylim, rot, fontsize, colormap, table, yerr, xerr, secondary_y, sort_columns, **kwds)
1975 yerr=yerr, xerr=xerr,
1976 secondary_y=secondary_y, sort_columns=sort_columns,
-> 1977 **kwds)
1978
1979
D:\IT\Anaconda5\lib\site-packages\pandas\plotting\_core.py in _plot(data, x, y, subplots, ax, kind, **kwds)
1764 if is_integer(x) and not data.columns.holds_integer():
1765 x = data_cols[x]
-> 1766 elif not isinstance(data[x], ABCSeries):
1767 raise ValueError("x must be a label or position")
1768 data = data.set_index(x)
D:\IT\Anaconda5\lib\site-packages\pandas\core\frame.py in __getitem__(self, key)
2680 if isinstance(key, (Series, np.ndarray, Index, list)):
2681 # either boolean or fancy integer index
-> 2682 return self._getitem_array(key)
2683 elif isinstance(key, DataFrame):
2684 return self._getitem_frame(key)
D:\IT\Anaconda5\lib\site-packages\pandas\core\frame.py in _getitem_array(self, key)
2724 return self._take(indexer, axis=0)
2725 else:
-> 2726 indexer = self.loc._convert_to_indexer(key, axis=1)
2727 return self._take(indexer, axis=1)
2728
D:\IT\Anaconda5\lib\site-packages\pandas\core\indexing.py in _convert_to_indexer(self, obj, axis, is_setter)
1325 if mask.any():
1326 raise KeyError('{mask} not in index'
-> 1327 .format(mask=objarr[mask]))
1328
1329 return com._values_from_object(indexer)
KeyError: "DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',\n '2018-01-05', '2018-01-06', '2018-01-07', '2018-01-08',\n '2018-01-09', '2018-01-10',\n ...\n '2018-07-10', '2018-07-11', '2018-07-12', '2018-07-13',\n '2018-07-14', '2018-07-15', '2018-07-16', '2018-07-17',\n '2018-07-18', '2018-07-19'],\n dtype='datetime64[ns]', name='销售时间', length=200, freq=None) not in index"
可以说我为了解决这个问题耗费了小半天的时间,因为科赛网的源代码我复制了一遍运行也是出错的,所以个人估计是matplotlib库的版本问题?具体原因我也不是很确定。
在百度搜了很久没有相关结果,后来还是在stackoverflow找到了尝试的代码。
image.png
这才有了第四部分的正确代码和趋势图。
另外还有一点就是科赛网的最后两个趋势图个人觉得选取的数据不对,因为源代码对日期重采样之后不是以sum计算的而是以count计算的。代码如下:
##对去重后的数据按照天进行重新采样
#首先要把索引变成时间
sales.index = pd.DatetimeIndex(sales['销售时间'])
#然后对其按照每天从新采样
salesd = sales.resample('D').count()
#画图
salesd.plot(x = salesd.index, y = '实收金额')
plt.xlabel('Time')
plt.ylabel('Money')
plt.title('xiao shou shu ju')
plt.show()
对此我特意将代码改成count然后把数据展开查看
image.png
所以按照代码来看,源代码画图的数据是X为索引-销售时间,y为实收金额,但是怎么看这样的实收金额都不会是一天的实收金额。因为实际上数字21是2018年1月1日销售了21单,而实际的实收金额则需要重采样之后按照sum来计算。
后面两个趋势图都是这样的情况,就趋势图来说我相信自己这种做法才是正确的。
但是对于画图的plot()的代码我还是只知其然不知其所以然,希望有大佬能够指点迷津。
以上。
谢谢。
网友评论