数据可视化,是处理数据最后的关键一步,用图形方式从大量数据中压缩信息。
数据分析结果最终需要为人所用,每个人对数据的理解能力和角度各不相同,可视化有助于降低数据理解门槛。
相比直接看数据表格,图表可以包含更多信息量,也更能直接体现数据间的关系。
Python的可视化模块非常多,按实现方式可以分为2类:

  • 原生Python:如matplotlibseaborn
  • JavaScript封装:如pyechartsplotly

模块安装:pip install matplotlib seaborn plotly
从实战角度看,原生类模块更适合应用在分析过程中,通过查看图表辅助分析;
JavaScript封装类的模块,视觉渲染效果更佳,更适合用于输出最终报告图表。
但不管采用何种模块或软件,可视化最关键的两步:理解数据、选对图例。
pandas内置了matplotlib模块基础功能,方便开箱使用。

Matplotlib

在上一节数据分析基础上,可以用柱状图、折线图、面积图、饼图这4个基本图表直观地理解数据:
7、可视化 - 图1

在Notebook中,调用数据对象的plot方法后就可以看到数据的图表。
7、可视化 - 图2

pandas自带图例种类不是很多,都是最基本如柱状图、饼图、折线图、箱线图等。
如果涉及到更复杂绘图,或需要配置格式、生成多图表集等,就需要用到matplotlib更多功能。
通用的绘制流程有3个步骤:

  1. (可选)创建图表的图像容器:Figure对象;
  2. (可选)设置xy轴特征,比如单位、格式、标签等;
  3. 调用DataFrameSeries对象的plot方法开始绘图。

比如上面案例中关于门店分析的图表集:

  1. import numpy as np
  2. import pandas as pd
  3. import matplotlib
  4. import matplotlib.pyplot as plt
  5. import matplotlib.dates as mdates
  6. from pandas.plotting import table
  7. # 设置中文显示
  8. matplotlib.rcParams['font.sans-serif'] = ['Source Han Sans CN']
  9. matplotlib.rcParams['font.family']='Source Han Sans CN'
  10. matplotlib.rcParams['axes.unicode_minus'] = False
  11. # 门店分析
  12. df_shop = pd.read_excel('sample_SP0000.xlsx')
  13. df_shop_ym = df_shop.resample('A').sum().to_period('A')['实付']
  14. df_shops_ym = df_shops.reset_index().groupby([
  15. pd.Grouper(key='订单日期',freq='A'),
  16. pd.Grouper(key='门店ID')])['实付'].sum().unstack('门店ID').to_period('A')
  17. # 开始绘图
  18. fig = plt.figure(figsize=(12, 12))
  19. plt.subplots_adjust(wspace=0.5,hspace=0.5)
  20. ax1 = fig.add_subplot(311) # 从3x1矩阵的图集中返回第1个位置
  21. ax2 = fig.add_subplot(312)# 3x1矩阵的图集中返回第2个位置
  22. ax3 = fig.add_subplot(313)# 3x1矩阵的图集中返回第3个位置
  23. # 图1
  24. ax1.set_xlabel('年份')
  25. ax1.set_ylabel('年营收(单位:万元)')
  26. ax1.set_title('各门店历年营收对比')
  27. # 用FixedFormatter定义轴刻度
  28. ax1.yaxis.set_major_formatter(plt.FixedFormatter(np.arange(0,600,step=50)))
  29. df_shops_ym.plot(ax=ax1, kind='bar', legend=False, yticks=(np.arange(6000000,step=500000)))
  30. # 图2
  31. ax2.set_xlabel('年份')
  32. ax2.set_ylabel('年营收(单位:万元)')
  33. ax2.set_title('各门店年营收增长')
  34. # 用自定义函数定义轴刻度
  35. ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x,p: f'{x/10000}'))
  36. df_shops_ym.plot(ax=ax2, kind='line', grid=True, legend=False, yticks=(np.arange(6000000,step=500000)))
  37. # 图3
  38. ax3.set_xlabel('年营收(单位:万元)')
  39. ax3.set_ylabel('年份')
  40. ax3.set_title('历年各门店营收')
  41. ax3.xaxis.set_major_locator(plt.MultipleLocator(5000000))
  42. ax3.xaxis.set_major_formatter(plt.FuncFormatter(lambda x,p: f'{x/10000}'))
  43. df_shops_ym.plot(ax=ax3, kind='barh',stacked=True, alpha=0.7)
  44. # 统一显示图例
  45. ax3.legend( loc='upper center', bbox_to_anchor=(0.5, -0.3),
  46. fancybox=True, shadow=True, ncol=5)

matplotlib功能很强大,可以通过组合绘制出几乎任何图形。
但它提供的现成图例相对有限,更多图例需要通过自定义代码实现。
绘制一些较新图例,或者调整风格,都需要额外花不少时间。
对于数据分析工作而言,我们需要更“高级”的模块。

Seaborn增强

seaborn模块是matplotlib的高级API封装,可以用更少代码绘制更美观的图表。
两者常被一起使用,比如,用seaborn可以轻松设置绘图格式:

  1. import matplotlib.pyplot as plt
  2. import seaborn as sns
  3. # 设置场景、主题、风格
  4. sns.set(context='notebook', style='darkgrid', palette='coolwarm_r')
  5. # 设置中文
  6. plt.rcParams['font.sans-serif'] = ['Source Han Sans CN']
  7. plt.rcParams['font.family']='Source Han Sans CN'
  8. plt.rcParams['axes.unicode_minus'] = False
  9. ax = df_shops_ym.plot(kind='bar', stacked=True, figsize=(12,12))
  10. ax.set_xlabel('年营收(单位:百万元)')

7、可视化 - 图3
生成的图表会更美观,可以省不少调样式时间。
此外,seaborn也提供了更多图例绘制API,共分5大类:关系型、分布型、分类型、回归型、矩阵型。
不同类别的API适用于不同分析场景,如观察数据维度间关系、数据分布、线性回归、聚集度等。

比如可以用热力图观察用户的产品喜爱度:

  1. df_shopsa = df_shops.reset_index().merge(df_shopsx, on='订单ID', suffixes=['_总订单','_单项产品'])
  2. df_user_prod_n = df_shopsa.pivot_table(index='用户ID',columns='产品',values='数量', aggfunc=np.sum).fillna(0)
  3. f, ax = plt.subplots(figsize=(20,20))
  4. sns.heatmap(df_user_prod_n)

7、可视化 - 图4
热力图颜色越浅表示用户经常下单,更喜欢某个产品。
可以用聚集图方便观察各门店历年来各月订单量:

  1. grp_orders = df_shops.reset_index().groupby([
  2. pd.Grouper(key='订单日期',freq='M'),
  3. pd.Grouper(key='门店ID')])
  4. df_orders_year_shop = grp_orders['订单ID'].count().unstack('门店ID').to_period('M').fillna(0).astype('int')
  5. sns.clustermap(df_orders_year_shop, figsize=(20,20))

7、可视化 - 图5
黑色表示所在时间段还未开店,颜色越淡说明越“旺”。
通过查表就知道:每个月订单量较多/少的是哪些门店,以及各门店在哪些月份订单量较多/少。

seaborn还提供了PairGridFacetGridJointGrid等组图功能,帮助快速观察不同变量对数据影响。
7、可视化 - 图6

matplotlib提供了底层强大的绘图能力,而seaborn则补充了更多分析类的绘图API。
通过综合应用matplotlibseaborn,基本已能够满足日常数据分析需求。
但如果需要输出更丰富的图例,比如漏斗图、桑基图等,可以借助三方模块。

Plotly另一种选择

seaborn只是matplotlib的一种增强补充,plotly则是一种替换选择。
plotly底层基于plotly.js,一个基于d3js的JavaScript模块,由Python封装成API。
相比前两者,plotly包含更丰富图例和效果,如3D、可交互、动画等,其中plotly.express子模块更是提供了快速绘图的高级API。

支持Pandas

pandas从0.25版本开始支持指定内置绘图功能的引擎,装好plotly后,可以把pandas的绘图后端指定为plotly

  1. import pandas as pd
  2. pd.options.plotting.backend = 'plotly'
  3. # 之前统计的各门店年营收数据
  4. df_shops_ym.to_timestamp().plot(kind='bar')

在Notebook中,相比matplotly绘制的静态图形,plotly还支持更多交互操作:
7、可视化 - 图7
但是,在调用绘图API时,plotly会和matplotly使用不完全一样的参数,即之前绘制的图表如果需要转为plotly后端,需要做一定修改。

用户等级分类:饼图

比如目前还无法直接在pandas中生成饼图,需要使用plotly的API:

  1. import plotly.express as px
  2. fig = px.pie(title='用户等级',color=s_ucs.index,
  3. names=s_ucs.index, values=s_ucs.values,
  4. color_discrete_map={'钻石':'slategrey',
  5. '黄金':'goldenrod',
  6. '白银':'silver',
  7. '青铜':'peru'})
  8. fig.update_traces(hole=.4)
  9. # 导出绘图数据
  10. fig.write_html('pie_plotly.html')
  11. fig.write_image('pie_plotly.png') # 导出png
  12. fig.write_image('pie_plotly.pdf') # 导出pdf
  13. fig.to_dict() # 转dict数据格式
  14. fig.to_json() # 转JSON数据格式
  15. img = fig.to_image(format='png') # 转图像数据

7、可视化 - 图8
不管是通过pandas内的plot,还是通过poltly.express的API,调用绘图后都会返回plotlyFigure对象,可以把它保存为网页或各种图片类型,或者转为其他数据格式。
分析过程中也可以借助plotly丰富的图例来理解数据。

门店级用户等级划分:多级饼图

除了观察全部门店的用户等级,也可以细粒度到每个门店:

  1. # 用户ID、门店ID、等级、数量
  2. df_users_RFM['等级'] = df_users_ym['等级']
  3. # 各门店各用户等级数量统计
  4. df_shop_ulvs = df_users_RFM.groupby([pd.Grouper(key='门店ID'),
  5. pd.Grouper(key='等级')])['R'].count().reset_index()
  6. df_shop_ulvs.columns = ['门店ID', '等级', '数量']
  7. fig_shop_ulvs = px.sunburst(df_shop_ulvs, path=['门店ID', '等级'], values='数量',
  8. color='数量', hover_data=['数量'])

7、可视化 - 图9

用户产品偏好:气泡图

分析用户对产品的偏爱度,除了用上面的热力图外,也可以用气泡图:

  1. # 把之前的透视表数据转为3列:用户ID、产品、数量
  2. df_user_prod_n_flat = df_user_prod_n.stack('产品').reset_index()
  3. df_user_prod_n_flat.columns = ['用户ID', '产品', '数量']
  4. # 筛选100名钻石用户
  5. df_user_diamond_100 = df_user_prod_n_flat[df_user_prod_n_flat['用户ID'].isin(
  6. df_users_ym[df_users_ym['等级']=='钻石'].index[:100])]
  7. fig = px.scatter(df_user_diamond_100, x='产品', y='用户ID',
  8. size='数量',color='产品',
  9. hover_name='用户ID',size_max=60)

7、可视化 - 图10
交互式的操作可以更方便地观察数据,包括切换宏观和微观视角。

RFM模型分析:3D散点图

用3D散点图来呈现RFM模型:

  1. # 最近一次消费时间
  2. s_user_last = df_shops.sort_index().reset_index().groupby('用户ID')['订单日期'].last()
  3. today = pd.to_datetime('2020-07-01') # 数据最后时间
  4. time_delta_list = [pd.Timedelta(days=d*30) for d in [70,18,12,6,3,1,0]]
  5. # 按最后消费时间分级映射: A:最近30天内消费、B:90天、C:180天、D:360天、E:540天、F:540天外
  6. R = pd.cut(s_user_last,
  7. bins=[today-dt for dt in time_delta_list],
  8. labels=['F','E','D','C','B','A'])
  9. # 用户ID、门店ID、R、F、M
  10. df_users_RFM = pd.DataFrame({
  11. '门店ID':df_shops['2019'].groupby('用户ID')['门店ID'].first(),
  12. 'R':R,
  13. 'F':df_shops['2019'].groupby('用户ID')['订单ID'].count(),
  14. 'M':df_shops['2019'].groupby('用户ID')['实付'].sum()
  15. }).dropna() # 排除2019年后未消费过的,或2020后才进来的新用户
  16. fig_RFM = px.scatter_3d(df_users_RFM, x='R', y='F', z='M',color='门店ID')

7、可视化 - 图11
同样可以通过交互式操作,旋转各个维度或观察某个门店数据。

历年各门店经营统计:树图

TreeMap图例可以方便查看层次结构数据,比如我们可以把全国所有门店按年份和区域组织起来观察。

  1. # 年份、门店ID、年营收、区域、年订单量、年消费用户数
  2. # 给门店假设一些分类:如地理分区华中、华北、华南
  3. shop_area = {
  4. 'SP0000':'华南区', 'SP0001':'华南区','SP0002':'华南区',
  5. 'SP0003':'华中区', 'SP0004':'华中区','SP0005':'华中区',
  6. 'SP0006':'华北区', 'SP0007':'华北区','SP0008':'华北区','SP0009':'华北区'}
  7. # 各门店年营收,转Series格式
  8. s_shops_ym = df_shops_ym.stack('门店ID')
  9. # 各门店年订单量
  10. s_shops_yo = df_shops.reset_index().groupby([
  11. pd.Grouper(key='订单日期',freq='A'),
  12. pd.Grouper(key='门店ID')])['订单ID'].count().unstack('门店ID').to_period('A').stack('门店ID')
  13. # 各门店年消费用户数
  14. s_shops_yu = df_shops.reset_index().groupby([
  15. pd.Grouper(key='订单日期',freq='A'),
  16. pd.Grouper(key='门店ID')])['用户ID'].apply(
  17. lambda x: x.unique().size).unstack('门店ID').to_period('A').stack('门店ID')
  18. df_shops_ysum = pd.DataFrame({
  19. '年营收':s_shops_ym,
  20. '年订单量':s_shops_yo,
  21. '年消费用户数':s_shops_yu
  22. })
  23. df_shops_tree = df_shops_ysum.reset_index()
  24. df_shops_tree.columns = ['年份','门店ID','年营收','年订单量','年消费用户数']
  25. df_shops_tree['区域'] = df_shops_tree['门店ID'].apply(lambda x: shop_area[x])
  26. fig_shop_tree = px.treemap(df_shops_tree, path=[px.Constant('全国品牌'), '年份', '区域','门店ID'], values='年营收',
  27. color='年营收', hover_data=['年订单量','年消费用户数'])

7、可视化 - 图12
TreeMap是一个自顶向下快速观察数据的工具:
通过色彩深浅,可以快速判断各年份、各区域、各门店的业绩高低。
鼠标悬停在某个板块时,还可以观察板块总业绩,以及门店级用户量、订单量等相关细节数据。

其他实用图例

此外,plotly还有更多实用图例,比如:

  • 漏斗图(funnel):如观察用户从访问网站到下单购买的转化率;
  • 桑基图(sankey):观察流量流向趋势,识别其中主要路径;
  • 雷达图(Scatterpolar):如从客流量、成本、投资等多因素综合评估门店。

7、可视化 - 图13
以上主要介绍了用plotlyexpress子模块快速绘图,其实plotly还有更多强大功能,比如:

  • subplots多图绘制,可以合并多图表实现自定义dashboard或工作面板;
  • graph_objects绘图时可加入自定义按钮,实现更灵活的数据交互功能。

    总结

    本文介绍了Python可视化的3个模块,其中matplotlib基本功能内置于pandas中,可以开箱使用;seaborn基于matplotlib补充了更多辅助分析的高级API;plotly则提供了更丰富的可交互式图例,也便于发布最终分析成果。
    三个模块可以在不同场景下互补应用。
    学习时,关键不是记住所有绘图命令,而是理解图例的适用场景。

  • 绘图时,能想起哪个图例能更好表达数据

  • 分析时,知道用哪些个图例帮助理解数据。

这才是数据可视化的意义所在。

扫码加入学习群,前100名免费。

7、可视化 - 图14