一、数据分析的目的
数据分析是指用适当的统计分析方法对收集来的大量数据进行分析,提取有用信息和形成结论而对数据加以详细研究和概括总结的过程。
在数据分析之前,我们先要明确分析目标是什么,这样可以避免我们像无头苍蝇一样拿着数据无从下手,也可以帮助我们更高效的选取数据,进行分析研究。
以朝阳医院2018年销售数据为例,目的是了解朝阳医院在2018年里的销售情况,这就需要知道几个业务指标,本次的分析目标是从销售数据中分析出以下业务指标:
(1)业务指标1:月均消费次数
月均消费次数 = 总消费次数 / 月份数(同一天内,同一个人所有消费算作一次消费)
(2)业务指标2:月均消费金额
月均消费金额 = 总消费金额 / 月份数
(3)客单价
客单价 = 总消费金额 / 总消费次数
4)消费趋势(可视化展示,并根据可视化结果给出下属问题分析得出的结论)
a、分析每天的消费金额
b、分析每月的消费金额
c、分析药品销售情况(截取销售数量最多的前十种药品,并用条形图展示结果)
二、数据分析基本过程
链接:https://pan.baidu.com/s/1j9WTL8gzTjBtR3cmVfA1Xw?pwd=6688
提取码:6688
数据分析基本过程包括:获取数据、数据清洗、构建模型、数据可视化以及消费趋势分析。
1、数据获取
先导入包,然后读取文件,读取的时候用object读取,防止有些数据读不了
import pandas as pd
# 读取数据(最好使用 object 类型读取)
data = pd.read_excel("朝阳医院2018年销售数据.xlsx", dtype="object") #防止数据无法导入
# 修改为 DataFrame 格式
dataDF = pd.DataFrame(data)
#head()函数是用来显示DataFrame的前n行,默认情况下,n=5
print(dataDF.head())#仅读取前几行
# 查看数据的形状,即几行几列
print('读取数据的行和列:',dataDF.shape)
#查看索引
dataDF.index
# 查看每一列的列表头内容
dataDF.columns
# 对dataDF数据查看每一列数据统计数目
# dataDF.count()
# data.info()
description = dataDF.describe()#describe()方法将返回其统计属性的摘要,这包括dataDF中每一列的计数、平均值、标准差、最小值和最大值。
description
总共有6578行7列数据,但是“购药时间”和“社保卡号”这两列只有6576个数据,而“商品编码”一直到“实收金额”这些列都是只有6577个数据,这就意味着数据中存在缺失值,可以推断出数据中存在一行缺失值,此外“购药时间”和“社保卡号”这两列都各自存在一个缺失数据,这些缺失数据在后面步骤中需要进一步处理。
2.数据清洗
数据清洗过程包括:选择子集、列名重命名、缺失数据处理、数据类型转换、数据排序及异常值处理。
(1)选择子集
在我们获取到的数据中,可能数据量非常庞大,并不是每一列都有价值都需要分析,这时候就需要从整个数据中选取合适的子集进行分析,这样能从数据中获取最大价值。在本次案例中不需要选取子集,暂时可以忽略这一步。
(2)列重命名
在数据分析过程中,有些列名和数据容易混淆或产生歧义,不利于数据分析,这时候需要把列名换成容易理解的名称,可以采用rename函数实现:
# 使用 rename 函数,把"购药时间" 改为 "销售时间"
dataDF.rename(columns={'购药时间':'销售时间'},inplace=True)
print(dataDF.columns)
(3)缺失值处理
获取的数据中很有可能存在缺失值,通过查看基本信息可以推测“购药时间”和“社保卡号”这两列存在缺失值,如果不处理这些缺失值会干扰后面的数据分析结果。缺失数据常用的处理方式为删除含有缺失数据的记录或者利用算法去补全缺失数据。在本次案例中为求方便,直接使用dropna函数删除缺失数据,具体如下:
# 删除缺失值之前
print("删除缺失值之前dataDF.shape:{0}".format(dataDF.shape))
#查看数据的统计信息
print(dataDF.info())
# 使用dropna函数删除缺失值
dataDF = dataDF.dropna(subset=['销售时间','社保卡号'],how = 'any')#dropna函数用于从一个DataFrame中移除缺失的值,
# 删除缺失值之后
print("删除缺失值之后dataDF.shape:{0}".format(dataDF.shape))
# 查看数据统计信息
print(dataDF.info())
(4)数据类型转换
在导入数据时为了防止导入不进来,会强制所有数据都是object类型,但实际数据分析过程中“销售数量”,“应收金额”,“实收金额”,这些列需要浮点型(float)数据,“销售时间”需要改成时间格式,因此需要对数据类型进行转换。
可以使用astype()函数转为浮点型数据:
# 将字符串转为浮点型数据
dataDF["销售数量"] = dataDF["销售数量"].astype('float')
dataDF["应收金额"] = dataDF["应收金额"].astype('float')
dataDF["实收金额"] = dataDF["实收金额"].astype('float')
#打印查看数据类型columns/dtypes
print("dataDF.dtypes:{0}".format(dataDF.dtypes))
在“销售时间”这一列数据中存在星期这样的数据,但在数据分析过程中不需要用到,因此要把销售时间列中日期和星期使用split函数进行分割,分割后的时间,返回的是Series数据类型:
#删除星期的函数splitsaletime
def splitsaletime(timeColser):
timelist = []
for t in timeColser:
# [0]表示选取的分片,这里表示切割完后选取第一个分片
timelist.append(t.split(" ")[0])
# 将列表转行为一维数据Series类型
timeSer = pd.Series(timelist)
return timeSer
# 获取"销售时间"这一列数据
t = dataDF.loc[:,'销售时间']
# 调用函数去除星期,获取日期
timeser = splitsaletime(t)#调用前面的splitsaletime函数
# 修改"销售时间"这一列日期
dataDF.loc[:, "销售时间"] = timeser#把修改以后的数据赋值到原“销售时间”列上面
#查看前几行数据
dataDF.head()#仅读取前几行
接着把切割后的日期转为时间格式,方便后面的数据统计,方法实现:to_datetime()
# 字符串转日期
# errors='coerce'如果原始数据不符合日期的格式,转换后的值为NaT,使用to_datetime()函数转换“”
#"销售时间"那一列
dataDF.loc[:, "销售时间"] = pd.to_datetime(dataDF.loc[:, "销售时间"],format='%Y-%m-%d',errors='coerce')
# 这里删除为空的行,使用dropna()函数
dataDF = dataDF.dropna()
print("dataDF.shape:{}".format(dataDF.shape))
(5)数据排序
此时时间是没有按顺序排列的,所以还是需要排序一下,排序之后索引会被打乱,所以也需要重置一下索引。其中by:表示按哪一列进行排序,ascending=True表示升序排列,ascending=False表示降序排列。
#使用sort_values按照销售时间进行升序排序
dataDF = dataDF.sort_values(by='销售时间', ascending=True)
print("dataDF.head():{}".format(dataDF.head()))
#使用reset_index()函数重置索引,参数?
dataDF = dataDF.reset_index(drop=True)
(6)异常值处理
先查看数据的描述统计信息
# 查看描述统计信息,使用describe()函数,该函数的作用?用法?
dataDF.describe()
#describe()函数是pandas库中的一个函数,用于对数据集进行统计描述,包括计算均值、标准差、最大值、最小值、中位数、四分位数等。使用方法为在数据集上调用该函数即可
通过描述统计信息可以看到,“销售数量”、“应收金额”、“实收金额”这三列数据的最小值出现了负数,这明显不符合常理,数据中存在异常值的干扰,因此要对数据进一步处理,以排除异常值的影响:
三、构建模型及数据可视化
数据清洗完成后,需要利用数据构建模型(就是计算相应的业务指标),并用可视化的方式呈现结果。
(1)业务指标1:月均消费次数
月均消费次数 = 总消费次数 / 月份数(同一天内,同一个人所有消费算作一次消费)
#计算月份数
# 使用drop_duplicates()删除重复数据,函数参数怎么考虑?
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)
(2)业务指标2:月均消费金额
月均消费金额 = 总消费金额 / 月份数
# 总消费金额(‘实收金额’的总额)
totalMoneyF = dataDF.loc[:,'实收金额'].sum()
# 月均消费金额(总金额/月份数)
monthMoneyF = totalMoneyF/mounthI
print('业务指标2:月均消费金额=', monthMoneyF)
(3)客单价
客单价 = 总消费金额 / 总消费次数
# 客单价 = 总消费金额 / 总消费次数
pct = totalMoneyF/totalI
print('业务指标3:客单价=', pct)
import matplotlib.pyplot as plt
# 画图时用于显示中文字符
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# SimHei是黑体的意思
# 在操作之前先复制一份数据,防止影响清洗后的数据
groupDf = dataDF
# 重命名行(index)为销售时间所在列的值
# groupDf.index = dataDF.rename(columns={'index': dataDF.columns[dataDF.columns.get_loc('销售时间')]} )
groupDf.index = groupDf['销售时间']
groupDf.head()
# 重命名行(index)为销售时间所在列的值
groupDf.index = groupDf['销售时间']
groupDf.head()
#标题按天消费金额图(折线图) 横轴“时间”,纵轴“实收金额”
gb = groupDf.groupby(groupDf.index)
print(gb)
dayDF = gb.sum()
print(dayDF)
dataDF['实收金额']
#画图
plt.plot(dataDF['实收金额'])
plt.title('按天消费金额图')
plt.xlabel('时间')
plt.ylabel('实收金额')
plt.show()
#从结果可以看出,每天消费总额差异较大,除了个别天出现比较大笔的消费,大部分人消费情况维持在1000-2000元以内
# 将销售时间聚合按月分组,groupby()
gb = groupDf.groupby(groupDf.index.month)
# 应用函数,计算每个月的消费总额
monthDf = gb.sum()
# 描绘按月消费金额图
plt.plot(monthDf['实收金额'])
plt.title('按月消费金额图')
plt.xlabel('时间')
plt.ylabel('实收金额')
plt.show()
# 聚合统计各种药品的销售数量
medicine = groupDf[['商品名称','销售数量']]
bk = medicine.groupby('商品名称')[['销售数量']]
re_medicine = bk.sum()
# 对药品销售数量按降序排序
re_medicine = re_medicine.sort_values(by = '销售数量',ascending=False)
re_medicine.head(10)
# 截取销售数量最多的十种药品
top_medicine = re_medicine.iloc[:10,:]
# 用条形图展示销售数量前十的药品,标签'药品销售前十情况',横轴'药品种类',纵轴'销售数量'
# 数据可视化,用条形图展示前十的药品
top_medicine.plot(kind = 'bar')
plt.title('药品销售前十情况')
plt.xlabel('药品种类')
plt.ylabel('销售数量')
plt.show()