学习了Python的各种基础语法和常用包后,你是否对如何使用Python在实际工作中进行数据分析一头雾水?如果是,今天这篇文章一定能带给你一些用数据分析解决实际问题的思路。
数据分析的目的决定了你的分析步骤,比如你的分析的目标是提供商业可视化方案,或者是建立模型进行预测,再或者是通过数据挖掘发现并改善现有流程等。今天我们介绍一个简单的数据分析例子-根据销售记录创建可视化报表。
我是一名药品销售公司的分析师,今天运营部的小丽发给我一份我们某一家药店的销售记录,希望我可以从销售数据中得到以下指标:
- 月均消费次数
- 月均消费金额
- 客单价
- 消费趋势
我想作为一个优秀的数据分析师,这个请求完全小菜一叠。我告诉小丽:没问题,我会用代码计算这些指标并做一个PDF的可视化报告给你,未来有相似的需求只要改改数据和参数就能生成一样的报表啦!
在分析前,我先整理了一下这个分析任务的大致步骤:
- 提出问题,理解需求
- 导入、理解数据
- 数据清洗
- 构建模型
- 数据可视化
1. 提出问题,理解需求
这是用数据分析解决问题非常重要的一步。因为如果没有理解业务背景、前提和需求就进行分析,就很难得到满足客户要求的解决方案。
我首先要理解小丽的需求和方案的截至日期,明白这4个指标的含义和计算方法。一般来说对于新的概念我会在网络搜索相关信息再与客户确认,从而确保对问题的理解和前提是和客户一致的。我通过网络查询和小丽的讨论我整理了指标的计算:
- 月均消费次数 = 总消费次数/月份数 (一个社保卡号代表一个人,同一天内的同一个卡号的所有消费算一次消费)
- 月均消费金额 = 总消费金额/月份数
- 客单价 = 总消费金额/总消费次数
- 消费趋势 可视化图标展现1.每月消费次数变化 2.每月消费金额变化 3.每月客单价变化
其次我会确定需要的数据。根据问题的不同,可能需要从数据库,网络,或者文件(CSV, EXCEL, TXT)获取数据。这个需求中小丽提供给我的Excel销售记录就够用了。
最后要和客户确认问题的解决方案是什么,是一个模型、一份商业分析报告、还是可视化报表。这里小丽要求是PDF的指标和可视化报告,所以我决定使用Python和Jupyter Notebook分析数据,设计指标和制作可视化图形。这样下次有相似的需求只要输入数据改动一些参数就可以另存为PDF文档。
2. 导入、理解数据
接下来就是导入数据,理解数据的基本属性,比如数据的类型、数据的大小、是否有缺失值、是否需要转换数据等。
# 导入数据包,一般习惯在分析开头导入所有数据包。
from datetime import datetime
import pandas as pd
from pylab import mpl
import matplotlib.pylab as plt # 数据可视化
plt.style.use('ggplot') # 设置风格使图标更美观
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定字体雅黑,使图标可以显示中文
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
# 关闭warnings
import warnings
warnings.filterwarnings('ignore')
# 数据导入
# Excel文件路经
f_path = r'/mnt/data-ubuntu/Projects/data_science_chinese/input/朝阳医院2018年销售数据.xlsx'
df = pd.read_excel(f_path)
# 打印查看数据前5行
df.head()
# 查看数据类型
df.dtypes
购药时间 object
社保卡号 float64
商品编码 float64
商品名称 object
销售数量 float64
应收金额 float64
实收金额 float64
dtype: object
#数据大小
df.shape
(6578, 7)
# 检查缺失值
df.isnull().sum()
购药时间 2
社保卡号 2
商品编码 1
商品名称 1
销售数量 1
应收金额 1
实收金额 1
dtype: int64
# 检查数据统计
df.describe()
经过上面步骤我知道:
- 购药时间不是日期格式,我们需要转换时间格式。
- 社保卡号和商品编码应该转换为字符格式,因为它们只是一串编号。
- 数据存在少量缺失值需要处理。
- 销售数量、应收金额和实收金额存在不合理的负值需要查看。
- 应收金额和实收金额出现较大值(可能为异常值)需要进一步查看。
3. 数据清洗
数据清洗的目的是使得数据更整洁,方便后续建模和可视化。一般数据清洗的步骤分为1.选择子集。2.列名重命名。3.缺失值处理。4.数据类型转换。5.异常值处理。6.数据排序。
根据问题的需求和对数据的理解,我确定了以下数据清洗步骤。
3.1 缺失值处理
要处理缺失值的原因是,缺失值会使计算、可视化、或有些模型的应用发生错误。 处理缺失值有多种方法:一般常用方法有1.删除行或列。2.补全缺失值。其中补全缺失值需要对数据和业务背景有一定了解。这里不再展开,有兴趣的同学可以看下这篇文章。
# 简单看下缺失值
df[df.isna().any(axis=1)]
# 删除所有缺失值的行
print(f'删除缺失值前:{df.shape}')
df.dropna(inplace = True)
print(f'删除缺失值后:{df.shape}')
删除缺失值前:(6578, 7)
删除缺失值后:(6575, 7)
这样我们就删除了包含缺失值的3行数据。
3.2 数据类型转换
正确的数据类型才能使得后面数据分析正常进行,我们需要根据问题的背景去决定数据类型。这里根据经验社保卡号和商品编码是字符串类型,而不是数字。
# 购药时间转换成日期格式
df['购药时间'] = df['购药时间']
.map(lambda x: x.split(' ')[0])
# errors='coerce'值如果有不合理日期格式'%Y-%m-%d'则返回NaT。
df['购药时间'] = pd.to_datetime(df.loc[:,'购药时间'],
errors='coerce',
format = '%Y-%m-%d')
# 社保卡号和商品编码转换成字符格式
df['社保卡号'] = df['社保卡号'].astype('int').astype('str')
df['商品编码'] = df['商品编码'].astype('int').astype('str')
# 删除日期错误的行
print(f'删除错误日期前:{df.shape}')
df.dropna(inplace = True)
print(f'删除错误日期后:{df.shape}')
df.dtypes
删除错误日期前:(6575, 7)
删除错误日期后:(6552, 7)
购药时间 datetime64[ns]
社保卡号 object
商品编码 object
商品名称 object
销售数量 float64
应收金额 float64
实收金额 float64
dtype: object
3.3 异常值处理
异常值的出现可能会导致我们分析中计算或者模型的不准确。异常值一般是指极大值、极小值或者其他不符合问题背景的值,然后通过对问题和数据的再理解,搜集背景知识,或和客户讨论是否要进行相应的数据处理。
# 查看极值
df[(df['应收金额']>2000)|(df['销售数量']<0)]
经过与小丽的确认后呢,我知道负数代表退货,我不需要计算进去。此外有2笔销售金额较大的是正常数据,因为购买数量大并且药价相对高。
在实际工作中也会碰到相似的问题,有时候需要一定的背景知识才能决定如何处理。
# 删除销量为负数的行
print(f'删除销量为负前:{df.shape}')
df = df[df['销售数量']>0]
print(f'删除销量为负后:{df.shape}')
删除销量为负前:(6552, 7)
删除销量为负后:(6509, 7)
3.4 数据排序
因为我处理的数据是销售数据有销售时间,按照时间排序有助于建模和数据可视化。
# 数据按照购药时间
df = df.sort_values(by = '购药时间')
# 重写整理index
df.reset_index(drop = True, inplace = True)
df.head()
4. 模型构建
根据我们的目的,这里模型构建是指计算客户要求的指标。通过之前和小丽的讨论已经理整理好了指标的计算:
- 月均消费次数 = 总消费次数/月份数 (一个社保卡号代表一个人,同一天内的同一个卡号的所有消费算一次消费)
- 月均消费金额 = 总消费金额/月份数
- 客单价 = 总消费金额/总消费次数
- 消费趋势 可视化图标展现1.每月消费次数变化 2.每月消费金额变化 3.每月客单价变化
首先要确定总月份数,如果不是完整的月份数据,我们再计算月均值的时候需要额外考虑。
# 生成年列和月列方便后期计算
df['购药时间_年'] = df['购药时间'].dt.year
df['购药时间_月'] = df['购药时间'].dt.month
# 检查每个月天数
df
.groupby([df['购药时间_年'],df['购药时间_月']])
.nunique()
.loc[:,'购药时间']
购药时间_年 购药时间_月
2018 1 31
2 28
3 31
4 30
5 31
6 30
7 19
Name: 购药时间, dtype: int64
我发现7月只有19天的数据,于是与小丽确认后我只要计算完整月份的数据,所以我就删除了7月的数据。
# 删除7月数据
df = df[df['购药时间_月'] != 7]
# 总月份数,以年月合并数据。表格的index数量就是月份数
num_month = df
.groupby([df['购药时间_年'],df['购药时间_月']])
.count()
.index
.size
print(f'数据一共包含{num_month}个月份。')
下一步我需要生成一个指标的数据表格df_kpi,方便数据处理和计算。
# 月消费次数表并合并到总表
df_kpi = df
.groupby([df['购药时间_年'],df['购药时间_月']])
.nunique()
.reset_index()
.loc[:, ['购药时间_年', '购药时间_月', '社保卡号']]
df_kpi.columns = ['购药时间_年', '购药时间_月', '当月消费次数']
# 当月消费金额表并合并到总表
df_temp = df
.groupby([df['购药时间_年'],df['购药时间_月']])
.sum()
.reset_index()
.loc[:, ['购药时间_年', '购药时间_月', '实收金额']]
df_temp.columns = ['购药时间_年', '购药时间_月', '当月消费金额']
df_kpi = df_kpi.merge(df_temp, how = 'inner', on =['购药时间_年', '购药时间_月'])
# 当月客单价
df_kpi['当月客单价'] = (df_kpi['当月消费金额']/df_kpi['当月消费次数']).round(2)
df_kpi
# 计算指标
kpi_avg_visit = df_kpi['当月消费次数'].sum()/num_month
kpi_avg_sales = df_kpi['当月消费金额'].sum()/num_month
print(f'月均消费次数为:{kpi_avg_visit:.2f}')
print(f'月均消费金额:{kpi_avg_sales:.2f}')
print(f'客单价:{kpi_avg_sales/kpi_avg_visit:.2f}')
月均消费次数为:654.50
月均消费金额:45652.46
客单价:69.75
5. 数据可视化
俗话说“一图胜千言”(我也不知道哪里来的俗话。。。),用图表来展示数据可以更简洁地表达内容,还可以展示出一些隐藏信息。
# 月消费次数趋势图
df_kpi.set_index(['购药时间_年', '购药时间_月'])['当月消费次数']
.plot(kind = 'bar',
figsize = (10,6),
title = '月消费次数趋势图',
color = 'steelblue')
plt.show()
# 月消费金额趋势图
df_kpi.set_index(['购药时间_年', '购药时间_月'])['当月消费金额']
.plot(kind = 'bar',
figsize = (10,6),
title = '月消费金额趋势图',
color = 'darkseagreen')
plt.show()
# 月消费次数趋势图
df_kpi.set_index(['购药时间_年', '购药时间_月'])['当月客单价']
.plot(kind = 'bar',
figsize = (10,6),
title = '月客单价趋势图',
color = 'coral')
plt.show()
从月趋势图我们看到:
- 月消费次数和月消费金额在2月比较低,也许是因为春节放假和2月天数少的影响。
- 客单价在3,4月相对较低,而其他月份比较平稳。
以上就是用Python进行数据分析的简单步骤,需要对应Jupyter notebook的同学请点这里。感激你的阅读!