朝阳医院2018年销售数据分析

朝阳医院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()的代码我还是只知其然不知其所以然,希望有大佬能够指点迷津。

以上。
谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • 项目名称:CD用户消费行为分析 数据来源:网上下载数据CDNow网站的用户购买明细CDNOW_master.txt...
    e10be475c3fa阅读 246评论 0 0
  • 这是一本通俗易懂,但是很经典的书。 一:财务分析总体方法 老唐说企业财务分析四步走:首先,浏览资产负债表和利润表各...
    ForeverXiaofeng阅读 1,641评论 0 2
  • 一、“人是没办法管理时间的,时间也不听从任何人的安排”? 当我看到“人是没办法管理时间的,时间也不听从任何人的管理...
    采蘑菇的小Timor阅读 299评论 0 0
  • 今天练习可以杀人的两根弦,我好喜欢 这个了不起看到12岁的孩子的坚持我练琴何尝不是
    每个人的孟母堂阅读 354评论 0 2
  • 晚宴是在别墅的背面进行,草坪很大,修剪的也很整齐,太阳已经快看不到了,不过,餐桌上的烛光,树上缠绕着的灯光,却又有...
    玉折兰摧阅读 621评论 0 48