- 数据分析的四阶段
- 分析流程建议
- 1 需求
- 数据情况
- 需求
- 产出
- 2 数据规整(数据预处理,数据清洗,数据重构)
- 2.1 数据预处理
- 2.1.1 发现错误的对策
- 2.1.2 修正缺失值
- 2.2 修正错误数据
- 方法1:取平均值/中位数
- 方法2:从其他表数据设法得出本客户ID订单数据
- 2.3 快速统计,查看异常值
- 排序
- 2.4 数据保存
- 2.5 描述性分析:指标计算、可视化
- 2.5.1 案例
- 2.5.2 指标计算思路
- 2.5.3 实战
-
提出需求:确定目标
-
准备数据:数据搜集和规整,最花时间(公司内部:办公室相关人员;系统、网站管理员找数据库)(公开信息:互联网爬虫)
-
分析数据
-
描述性分析:指标计算和可视化
-
探索性分析:建模,预测 (设计建模,基础含量高,市场上绝大报告没有)
-
-
总结和建议
数据分析报告书写基本套路:
- 背景介绍
- 情况介绍(公司情况,接单情况。。。)
- 数据介绍
- 需求(分析什么)
- 数据工程
- 数据获取:找人,SQL,爬虫
- 数据预处理:清洗,重构
- 数据分析
- 描述性分析
- 指标计算(分组聚合旋转、透视表、交叉表)(反复迭代)
- 可视化(反复迭代)
- 探索性分析(建模预测),可选
- 描述性分析
- 结论
- 总结分析结果
- 提出意见建议
数据由3个单元表组成:
- 订单信息表:
- 订单ID
- 客户ID
- 订单状态
- 1表示正常完成订单
- 0表示未完成订单
- 优惠类型
- 0表示无优惠
- 1表示优惠
- 货物信息表:
- 订单ID
- 货物ID
- 货物名称
- 优惠额度
- 分组显示优惠额度
- 顾客信息表:
- 客户ID
- 登陆次数
- 注册时间(距1970-1-1的秒数)
- 本次购买时间(距1970-1-1的秒数)
- 经验值
- 订单数
-
核心需求:分离在本电商平台购物的无价值用户
- 将平台购物的用户分为正常用户和无价值用户,
- 无价值用户一般指很少购买正常价格商品,大多购买优惠和促销商品的用户
-
其他综合需求:
- 分析下平台的订单情况、商品情况
- 分离正常用户和无价值用户后,进一步分析二者在网站上的行为差异
-
针对性需求1:货物信息表
- 不同优惠额度的订单数量
- 能否根据优惠额度分组可视化产品销量情况
- 能否输出正常价格下销量最好的前10个产品
- 能否输出优惠价格下销量最好的前10个产品
-
针对性需求2:订单信息表
- 能否根据订单状态筛选出已完成订单
- 能否根据客户id和优惠类型分出 正常客户和无价值客户
-
针对性需求3:顾客信息表
- 能否将订单信息表得出的 正常客户和无价值客户列,合并到本表中
- 能否通过客户id列,和正常、无效客户列,得出正常和无效客户分别在:
- 登陆次数,注册时间,本次购买时间,经验值,订单数,等指标下的对比差异?
- 注册和登录时间间隔的对比差异?
要求:根据给定数据和需求,从头完成一个完整版的数据分析报告
并产出下列文档:
- Jupyter-Notebook版
- 综合:用于数据分析项目代码实现和演讲、传播
- HTML网页版
自动生成,用于传播交流 - PDF版
自动生成,用于传播交流
- PPT版
- 手动制作,用于演讲展示
可以使用Jupyter快速导出HTML和PDF版本(chrme打印网页),但效果一般。 如果对效果要求较高,建议导出md格式,自行编辑,再使用markdown导出HTML和PDF2 数据规整(数据预处理,数据清洗,数据重构)
数据规整是数据分析的预操作,数据分析报告中不体现
# 导入库
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
plt.style.use('seaborn') # 改变图像风格
plt.rcParams['font.family'] = ['Arial Unicode MS', 'Microsoft Yahei', 'SimHei', 'sans-serif'] # 解决中文乱码
plt.rcParams['axes.unicode_minus'] = False # simhei黑体字 负号乱码 解决
- 读取文件
shop = pd.read_excel('data\shop.xlsx',None)
shop
- 对不同表格赋予实参
dingdan = shop['订单信息'] huowu = shop['货物信息'] guke = shop['顾客信息'] dingdan
成功读取
检查数据是否有缺失值和异常列类型
提前自己判断一下每张表每列数据的一般数据类型,如果有异样就检查,例如一列只有数字的,却是object字符串类型(只要有一个字符画,整列都算字符串类型)
# 数据预处理 # 检查数据是否有缺失值和异常列类型 # 提前自己判断一下每张表每列数据的一般数据类型,如果有异样就检查,例如一列只有数字的,却是object字符串类型 dingdan.info() huowu.info() guke.info()
发现顾客信息的订单数一列,通常只有数字值,却是object字符串类型
随机找一个值看看有没有问题
# 随机找一个值看看有没有问题 type(guke.loc[0,'订单数']) # 写法1 type(guke.loc[0,'订单数']) == int # 写法2 布尔判断 type(guke.loc[0,'订单数']) == int # 写法3 正规写法,这里的int类型不是普通的int类型
如果没有
- 方法1:使用遍历方式找到错误值
# 使用遍历方式找到错误值 for index,row in guke.iterrows(): # print(index) # 表格的行号 # print(row) # 表格的行 # print(type(row)) ## print(row['订单数']) # 每行中订单数对应的值 if type(row['订单数']) != np.int: # 如果不是int类型,输出行号和该行订单数对应的值 print(index,'--',row['订单数'])
使用索引方法loc查看一下
guke.loc[46]
找到了错误值
- 方法2:使用Pandas自定义函数实现
在自定义函数中遍历对应值,并对判断正确的值返回False值,对判断错误的值返回True值,利用pandas数据结构默认输出True值的特点锁定错误值
# 使用Pandas自定义函数实现
def check(x):
# print(x['订单数']) # 我们发现可以用传入的参数锁定到‘订单数’列
if type(x['订单数']) == np.int:
# 布尔值True和False调转输出,
# 我们可以直接锁定输出True的值,而输出True的值恰恰是错误的值
return False
else:
return True
guke.apply(check,axis=1) # 默认行遍历,将axis=1按列
guke[guke.apply(check,axis=1)] # 直接输出只有True的值
- 写法3:自定义函数的匿名函数写法
#匿名函数写法 guke[guke.apply(lambda x : False if type(x['订单数']) == np.int else True,axis=1)]2.1.2 修正缺失值
#修正缺失值 guke.loc[46,'订单数'] = np.nan guke.loc[46]2.2 修正错误数据 方法1:取平均值/中位数
用经验值列判断错误订单数数据大致范围,取平均值/中位数
特点:通用,套路,无脑操作
# 修正错误数据 # 方法1 # 查看订单数和经验值的关系 guke['订单数'].mean() # 所有客户平均订单数8.448 guke['经验值'].mean() # 所有客户经验值672.35 # 该错误数据的经验值为1172,我们检查一下相似经验值的客户的订单数 # 发觉区间定在1100-1200之间订单数差距较大,改小范围 # si = guke[(guke['经验值']>1100) & (guke['经验值']<1200)] si = guke[(guke['经验值']>1150) & (guke['经验值']<1200)] si,si.shape
# 查询该区间最大值,最小值,平均值 si['订单数'].min(),si['订单数'].max(),si['订单数'].mean(),si['订单数'].median() # 写法1 si.订单数.min(),si.订单数.max(),si.订单数.mean(),si.订单数.median() # 写法2方法2:从其他表数据设法得出本客户ID订单数据
可能不完全对,但准确率超过自行求值
优点:最精确
缺点:对数据有要求。仅在部分情况下能够使用
需要超凡的洞察力,大力研究已有表格数据
-截取出出错的部分
# 方法2:从其他表数据设法得出本客户ID订单数据 # 截取出出错的部分 guke.loc[45:48]
- 对比一下其他表格
订单表格中的客户id有重合,而且有订单状态计数,尝试将客户id=6226有记录的数据找出来,两个表格对比数据对比一下
# 客户ID 6226 对应的订单数 guke.loc[45, '订单数'] # 111
查看客户ID 6226 在订单表格中对应的订单数,发觉数据比较相似
# 客户ID 6226 在订单表格中对应的订单数 dingdan[dingdan['客户ID'] == 6226].shape[0] ## 104
再缩小范围,检索一下其成交的订单数,数值减小,说明所有的订单数加起来才和客户表中的订单数值比较相似
# 客户ID 6226 在订单表格中对应的订单数 dingdan[dingdan['客户ID'] == 6226].shape[0] ## 104 # 再缩小范围,检索一下其成交的订单数,数值减小,说明所有的订单数加起来才和客户表中的订单数值比较相似 dingdan[(dingdan['客户ID'] == 6226) & (dingdan['订单状态'] == 1)].shape[0] # 97
我们选择所有的订单状态加起来的值,来找到那位缺失订单数的客户6243的大致订单数
# 选择所有的订单状态加起来的值, # 来找到那位缺失订单数的客户6243的大致订单数 dingdan[(dingdan['客户ID'] == 6243)].shape[0] # 146 # dingdan[(dingdan['客户ID'] == 6243) & (dingdan['订单状态'] == 1)].shape[0] # 124
- 赋值
# 赋值 guke.loc[46,'订单数'] = dingdan[(dingdan['客户ID'] == 6243)].shape[0] guke.loc[46:48] # 赋值成功
赋值成功后查看一下该列类型是否有变化,如果没有变成int类型,需要转换
# 赋值成功后查看一下该列类型是否有变化,如果没有变成int类型,需要转换 guke.info()
- 进行转换
# 修改类型 # 备份一份防止改错 guke2 = guke # 在备份数据中修改 guke2['订单数'] = guke2['订单数'].astype(np.int) guke2.info()
修改成功
- 输出平均值
guke2['订单数'].mean()2.3 快速统计,查看异常值
# 2.3 快速统计,查看异常值 dingdan.describe()
huowu.describe()
guke2.describe()排序
# 按登陆次数排序用户 guke2.sort_values(by='登陆次数',ascending=False)[:10]
# 按订单数排序用户 guke2.sort_values(by='订单数',ascending=False)[:10]
- 订单数为0的所有用户
# 订单数为0的所有用户 guke2[guke2['订单数'] == 0]
- 订单数为0的所有用户 按登陆次数排序
# 订单数为0的所有用户 按登陆次数排序 guke2[guke2['订单数'] == 0].sort_values(by='登陆次数',ascending=False)[:10]
- 删掉订单数为0的用户,因为这部分客户暂时没有用处
# 可以删掉订单数为0的用户,因为这部分客户暂时没有用处 guke3 = guke2 guke3[guke3['订单数'] == 0].index # drop方法只对本行操作的数据有效,并不会对目标造成实际操作 # 将改好的数据传给另外一个实参对象guke4 guke4 = guke3.drop(guke3[guke3['订单数'] == 0].index) guke4
删除成功
- 对改好的数据重新赋值
# 对改好的数据重新赋值 guke = guke4 guke
- 结论:数据整体查看,没有特别异常的数据
# 2.4 数据保存
writer = pd.ExcelWriter('2 电子商务平台数据分析实战1 - 数据规整和分析思路long_newshop2.xlsx')
dingdan.to_excel(
writer, # 写入数据
'订单信息', # 工作表标签
index=False # 不存入行索引
)
huowu.to_excel(
writer, # 写入数据
'货物信息', # 工作表标签
index=False # 不存入行索引
)
guke.to_excel(
writer, # 写入数据
'顾客信息', # 工作表标签
index=False # 不存入行索引
)
writer.save()
保存成功
- 共有多少种优惠额度
- 每个优惠额度下有多少订单
# 查看优惠额度种类
huowu.优惠额度.value_counts() # 写法1 对列直接聚合
huowu.groupby('优惠额度').size().sort_values(ascending=False) # 写法2 groupby聚合函数后size查看
- 可视化
pic = huowu.优惠额度.value_counts() pic.plot.bar()
pic.plot.pie(figsize=(12,12))2.5.2 指标计算思路
- 如果你精通要分析的业务,那么不需要套路,你会自己根据业务知识寻找计算指标
- 如果你完全不懂你要分析的业务
- 把有价值的列(离散数据列,如果是连续数据需要先离散化),每个单提出来,做交叉表分析(分组聚合,单列,乞丐版交叉表)
- 例子:优惠额度订单排名,销量前10商品排名,销量倒数10商品排名
- 将有价值的列,两两组合/三列组合(多列组合,不常用),做分组聚合(透视表,交叉表)
- 离散数据可以直接做分组基准,连续数据不能直接做分组基准,可以做分组后聚合的列(求平均值等指标)
- 连续数据需要先离散化,再做分组操作(pd.cut(), pd.qcut(),分位数分析)
- 两列或多列交换分组聚合的列的顺序,可以得到更多指标
- 例子:打折商品销量排名前10,不打折商品销量排名前10
- 把有价值的列(离散数据列,如果是连续数据需要先离散化),每个单提出来,做交叉表分析(分组聚合,单列,乞丐版交叉表)
- 引入问题
有一类客户基本只购买优惠促销商品,很少购买正价商品,这类客户对企业没有价值,只会增加营销成本
企业想要将无价值客户识别出来,同正常客户做区分,以便针对性的改变营销策略
- 问题:和正常客户相比,无价值客户有什么特征?
- 读取数据
# 2.5.3 实战
shop2 = pd.read_excel('2 电子商务平台数据分析实战1 - 数据规整和分析思路data_newshop2.xlsx',None)
dingdan = shop['订单信息']
huowu = shop['货物信息']
guke = shop['顾客信息']
# 初步检查
dingdan.info()
huowu.info()
guke.info()
- 上一节过程把错误都找出来了,指标创建:不同优惠额度的订单数量
# 指标创建:不同优惠额度的订单数量 huowu.head()
- 分组
# 分组
sale = huowu.groupby('优惠额度').size().sort_values(ascending=False) # 写法1
# sale = huowu.优惠额度.value_counts() # 写法2
sale
- 可视化
# 可视化
sale.plot.bar(figsize=(20,15),alpha=0.5,fontsize=18)
# 辅助显示
plt.title('不同优惠额度的订单数',fontsize=22 )
plt.xlabel('优惠额度',fontsize=22)
plt.ylabel('订单数量',fontsize=22)
plt.grid(linewidth=1,alpha=0.5)
- 使用上面得出的数据重新做一份数据(分类种类比较少,直接手写输入)
- 对优惠额度再一次精简,分成4种
# 重新做一份数据
d = pd.Dataframe([[np.nan, 584, 596],
[310, np.nan, np.nan],
[38, np.nan, np.nan],
[163, np.nan, np.nan]],
index=['<5', '5-10', '10-15', '>15'],
columns=['other', '0-2', '2-5'])
d
- 可视化
- bar方法的stacked将想用x值的y值数据合并
# 可视化
d.plot.bar(figsize=(18,10),alpha=0.5,fontsize=18,stacked=True)
#辅助显示
plt.title('不同优惠额度的订单数',fontsize=22 )
plt.xlabel('优惠额度',fontsize=22)
plt.ylabel('订单数量',fontsize=22)
plt.grid(linewidth=0.4,alpha=0.5)
-
结论
- 优惠额度小于5元的商品销售最多
- 优惠额度在10-15元的商品销售最小
-
分析订单商品排行
- 指标:原价商品销量前10排名
huowu.head()
# 优惠额度为0时的商品排行
# 销量前十
huowu[huowu['优惠额度'] == '0'].groupby('货物名称').size().sort_values(ascending=False)[:10]
# 输出的是Series值:pandas.core.series.Series
# type(huowu[huowu['优惠额度'] == '0'].groupby('货物名称').size().sort_values(ascending=False)[:10])
- 输出的是Series值:pandas.core.series.Series,不太好看,对筛选出来的数据重新整理
# 重新整理
# reset_index()重新设置索引,可以变成好看的pd结构
sort = huowu[huowu['优惠额度'] == '0'].groupby('货物名称').size()
.sort_values(ascending=False)[:10].reset_index()
- set_index(‘货物名称’) 将货物名称列设置成索引列,此时索引列名(货物名称)靠近商品名称
# 重新整理
# reset_index()重新设置索引,可以变成好看的pd结构
# set_index('货物名称') 将货物名称列设置成索引列,此时索引列名靠近商品名称
sort = huowu[huowu['优惠额度'] == '0'].groupby('货物名称').size()
.sort_values(ascending=False)[:10].reset_index().set_index('货物名称')
- 重新设置列名
# 重新整理
# reset_index()重新设置索引,可以变成好看的pd结构
# set_index('货物名称') 将货物名称列设置成索引列,此时索引列名靠近商品名称
# rename(columns={0:'销量'}) 重置列名
sort = huowu[huowu['优惠额度'] == '0'].groupby('货物名称').size()
.sort_values(ascending=False)[:10].reset_index().set_index('货物名称').rename(columns={0:'销量'})
- 想把索引统称(货物名称)放到和列名称一行的位置,毕竟‘货物名称’也是列名
# 重新整理
# reset_index()重新设置索引,可以变成好看的pd结构
# set_index('货物名称') 将货物名称列设置成索引列,此时索引列名靠近商品名称
# rename(columns={0:'销量'}) 重置列名
sort = huowu[huowu['优惠额度'] == '0'].groupby('货物名称').size()
.sort_values(ascending=False)[:10].reset_index().set_index('货物名称').rename(columns={0:'销量'})
# 把索引统称(货物名称)放到和列名称一行的位置,毕竟‘货物名称’也是列名
# sort.index.name = '' 将索引名去掉
sort.index.name = ''
- 打印一下column的所有元素,只有销量,并没有把索引列的统称变成列名
print(sort.columns) print(sort.columns) print(sort.columns.name) # None,并没有设置列索引的统称
- columns.name 设置一下列的统称
# 重新整理
# reset_index()重新设置索引,可以变成好看的pd结构
# set_index('货物名称') 将货物名称列设置成索引列,此时索引列名靠近商品名称
# rename(columns={0:'销量'}) 重置列名
sort = huowu[huowu['优惠额度'] == '0'].groupby('货物名称').size()
.sort_values(ascending=False)[:10].reset_index().set_index('货物名称').rename(columns={0:'销量'})
# 把索引统称(货物名称)放到和列名称一行的位置,毕竟‘货物名称’也是列名
# sort.index.name = '' 将索引名去掉
# sort.columns.name 设置一下列的统称
sort.index.name = ''
# print(sort.columns)
# print(sort.columns.name) # None,并没有设置列索引的统称
sort.columns.name = '货物名称'
sort
- 检查一下
sort.columns.name = '货物名称'
print(sort.columns)
print(sort.columns.name)
print('空格===',sort.index.name+',空格')
print(sort.index)
- 销量最差商品排名前10
# 销量最差商品排名前10
# reset_index()重新设置索引,可以变成好看的pd结构
# set_index('货物名称') 将货物名称列设置成索引列,此时索引列名靠近商品名称
# rename(columns={0:'销量'}) 重置列名
sort = huowu[huowu['优惠额度'] == '0'].groupby('货物名称').size()
.sort_values(ascending=True)[:10].reset_index().set_index('货物名称').rename(columns={0:'销量'})
# 把索引统称(货物名称)放到和列名称一行的位置,毕竟‘货物名称’也是列名
# sort.index.name = '' 将索引名去掉
# sort.columns.name 设置一下列的统称
sort.index.name = ''
# print(sort.columns)
# print(sort.columns.name) # None,并没有设置列索引的统称
sort.columns.name = '货物名称'
sort



