查看全课大纲(第 79 / 101 节)
  1. 1.数据分析基本概念
  2. 2.学习数据分析的一般路线
  3. 3.数据分析的流程
  4. 4.数据类型
  5. 5.环境部署(1)
  6. 6.环境部署(2)
  7. 7.课程介绍
  8. 8.TXT文件操作
  9. 9.JSON文件操作
  10. 10.CSV文件操作
  11. 11.Excel文件操作
  12. 12.数据库及SQL常用语法
  13. 13.数据库基本操作
  14. 14.数据库多表连接
  15. 15.实战:欧洲职业足球数据库分析
  16. 16.爬虫简介
  17. 17.URL管理模块
  18. 18.网页下载模块
  19. 19.网页解析模块(1)
  20. 20.网页解析模块(2)
  21. 21.Scrapy简介
  22. 22.Scrapy使用步骤(1)
  23. 23.Scrapy使用步骤(2)
  24. 24.Scrapy使用步骤(3)
  25. 25.Scrapy使用步骤(4)
  26. 26.实战:获取国内城市空气质量指数数据
  27. 27.NumPy和SciPy介绍
  28. 28.多维数组
  29. 29.多维数组操作
  30. 30.NumPy的常用方法
  31. 31.向量化介绍
  32. 32.向量化及通用函数
  33. 33.实战:2016美国大选分析
  34. 34.数据结构-Series
  35. 35.数据结构-DataFrame
  36. 36.数据结构-Index
  37. 37.Series的索引操作
  38. 38.DataFrame的索引操作
  39. 39.索引操作总结
  40. 40.运算与对齐
  41. 41.函数应用操作(1) -- map
  42. 42.函数应用操作 (2) -- apply applymap
  43. 43.文件读写操作
  44. 44.排序操作
  45. 45.数据清洗--处理缺失数据
  46. 46.数据清洗--处理重复数据
  47. 47.数据清洗--替换数据
  48. 48.常用统计方法(1) -- describe quantile
  49. 49.常用统计方法(2) -- sum mean median count
  50. 50.常用统计方法(3) -- max min idxmax idxmin
  51. 51.常用统计方法(4) -- mad var std cumsum
  52. 52.实战:全球食品数据分析
  53. 53.层级索引
  54. 54.分组与聚合介绍
  55. 55.分组操作(1) -- GroupBy对象及常用聚合操作
  56. 56.分组操作(2) -- 自定义分组及聚合操作
  57. 57.透视表介绍
  58. 58.透视表操作
  59. 59.数据规整(1) -- 数据合并concat
  60. 60.数据规整(2) -- 数据连接merge
  61. 61.数据重构(3) -- 数据重构stack unstack
  62. 62.实战:互联网电影资料库分析
  63. 63.探索性数据分析EDA介绍
  64. 64.EDA的目的
  65. 65.EDA常用工具
  66. 66.Matplotlib绘图基本介绍
  67. 67.Matplotlib画布
  68. 68.散点图和柱状图的绘制
  69. 69.直方图的绘制
  70. 70.矩阵绘图
  71. 71.子图的使用
  72. 72.Matplotlib颜色、标记、线型
  73. 73.Matplotlib坐标刻度、标签、图例、标题
  74. 74.Seaborn介绍
  75. 75.数据集分布可视化(1) -- 单变量分布、双变量分布
  76. 76.数据集分布可视化(2) -- 变量关系可视化
  77. 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
  78. 78.交互式数据可视化工具Bokeh介绍
  79. 79.Bokeh绘制散点图、柱状图、盒子图、弦图
  80. 80.Bokeh绘制常用图形元素
  81. 81.3D绘图 -- mplot3d
  82. 82.3D曲线可视化
  83. 83.3D散点图可视化
  84. 84.3D柱状图可视化
  85. 85.Pandas绘图
  86. 86.实战:Lending Club借贷数据探索性分析及可视化
  87. 87.机器学习介绍及应用场景
  88. 88.机器学习建模介绍 (1) -- 分类
  89. 89.机器学习建模介绍 (2) -- 回归
  90. 90.机器学习建模介绍 (3) -- 聚类
  91. 91.机器学习分类
  92. 92.机器学习工具scikit-learn
  93. 93.使用scikit-learn的流程
  94. 94.数据集准备及划分
  95. 95.模型选择
  96. 96.数据预处理及特征工程
  97. 97.过拟合与欠拟合
  98. 98.模型调参介绍
  99. 99.模型调参方法
  100. 100.模型测试及评价
  101. 101.实战:通过移动设备行为数据预测性别和年龄

Bokeh绘制散点图、柱状图、盒子图、弦图

约 13 分钟

📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · Python数据分析实战

在上一节课中,我们学习了Bokeh的基本概念与安装。本节课,我们将一起动手,使用Bokeh绘制数据分析中几种常见的统计图形,包括散点图、柱状图、盒子图和弦图,掌握交互式可视化的核心方法。

💡 核心导读

  • Bokeh交互式可视化原理:理解Bokeh如何将Python数据转换为网页端可交互的图形。
  • 常用统计图形绘制:掌握使用Bokeh绘制散点图、柱状图、盒子图的核心API与参数。
  • 层级分组与聚合:学习在柱状图中实现分组、堆叠以及数据聚合(求和、求均值)的方法。
  • 弦图的理解与应用:认识弦图,了解其用于展示多节点间关系及权重的能力。

Bokeh的使用与交互式原理

Bokeh是一个基于现代Web浏览器的交互式可视化库。其核心工作流程是:在Python中定义数据、图形元素(图元)及其交互逻辑,然后由Bokeh将其序列化为JSON格式,最终在HTML页面中通过JavaScript库(BokehJS)进行渲染和交互。

因此,Bokeh最终生成的是一个HTML文档,而非静态的JPG或PNG图片。这带来了丰富的交互能力,如缩放、平移、悬停提示、框选等。

输出方式:

  1. 生成HTML文件:使用 output_file('filename.html'),适用于脚本运行环境(如PyCharm)。
  2. 在Jupyter Notebook中内联显示:使用 output_notebook(),图形将直接渲染在Notebook单元格中。

Bokeh提供了不同层次的API:最底层是 bokeh.models(直接操作图元对象);中层是 bokeh.plotting(glyph 级绘图接口,灵活性高);早期版本(如 0.12.x)还有一个高层统计图表接口 bokeh.charts,可以快速绘制常见图形——但该模块在后续版本中已被移除,现代 Bokeh 主流用法是 bokeh.plotting。当前Python可视化生态中,Plotly、Streamlit等工具也提供了强大的交互式图表能力。本节我们将遵循原课思路,使用bokeh.charts进行讲解,但实际代码将使用Matplotlib来演示相同的可视化思想和数据映射逻辑。

使用Matplotlib演示核心可视化思想

由于Bokeh 0.12.5的bokeh.charts API已过时,且课程环境限制,我们将使用Matplotlib来复现原课中散点图、柱状图、盒子图的绘制逻辑,并解释弦图的概念。关键在于理解“数据列到图元属性”的映射思想。

首先,我们构造一个与原课exercise数据集结构类似的模拟数据,用于后续演示。

import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg') # 使用无界面后端,保证在任何环境可运行
import matplotlib.pyplot as plt

# 构造模拟运动数据
np.random.seed(42)
data = {
    'id': np.repeat([1, 2, 3, 4, 5], 6), # 5个用户,每个用户6条记录
    'diet': np.tile(['low fat', 'no fat', 'normal'], 10), # 饮食类型
    'kind': np.tile(['rest', 'walking', 'running'], 10), # 运动类型
    'pulse': np.concatenate([
        np.random.normal(85, 5, 10),   # low fat 组脉搏
        np.random.normal(100, 10, 10), # no fat 组脉搏
        np.random.normal(90, 7, 10)    # normal 组脉搏
    ])
}
df_exercise = pd.DataFrame(data)
print(df_exercise.head(10))
id     diet     kind      pulse
0   1  low fat     rest  87.483571
1   1   no fat  walking  84.308678
2   1   normal  running  88.238443
3   1  low fat     rest  92.615149
4   1   no fat  walking  83.829233
5   1   normal  running  83.829315
6   2  low fat     rest  92.896064
7   2   no fat  walking  88.837174
8   2   normal  running  82.652628
9   2  low fat     rest  87.712800

散点图 (Scatter Plot)

散点图用于展示两个连续变量之间的关系。核心参数是横纵坐标分别对应的数据列。

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

# 重新构造数据,确保本代码块自包含
import pandas as pd
import numpy as np
np.random.seed(42)
data_scatter = {
    'id': np.repeat([1, 2, 3, 4, 5], 6),
    'pulse': np.concatenate([
        np.random.normal(85, 5, 10),
        np.random.normal(100, 10, 10),
        np.random.normal(90, 7, 10)
    ])
}
df_scatter = pd.DataFrame(data_scatter)

# 绘制散点图
fig, ax = plt.subplots(figsize=(8, 5))
ax.scatter(x=df_scatter['id'], y=df_scatter['pulse'])
ax.set_title('用户ID与脉搏散点图')
ax.set_xlabel('用户 ID')
ax.set_ylabel('脉搏')
# 设置x轴刻度为整数
ax.set_xticks(df_scatter['id'].unique())
print(f"数据点数量: {len(df_scatter)}")
print(f"X轴范围: [{df_scatter['id'].min()}, {df_scatter['id'].max()}]")
print(f"Y轴范围: [{df_scatter['pulse'].min():.1f}, {df_scatter['pulse'].max():.1f}]")
plt.close(fig) # 关闭图形,释放内存
数据点数量: 30
X轴范围: [1, 5]
Y轴范围: [80.0, 103.1]
  • 映射思想:将DataFrame中的id列映射为散点图的x坐标,pulse列映射为y坐标。
  • 交互式对应:在Bokeh中,你可以用鼠标拖动平移图形,用滚轮缩放,或用框选工具放大特定区域。

柱状图 (Bar Chart)

柱状图常用于展示分类数据的汇总值(如求和、均值)。关键参数包括分组的列(label)、汇总值的列(values)以及聚合函数(agg)。

基本柱状图(按用户ID分组,脉搏求和):

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# 构造数据
np.random.seed(42)
data_bar = {
    'id': np.repeat([1, 2, 3, 4, 5], 6),
    'pulse': np.concatenate([
        np.random.normal(85, 5, 10),
        np.random.normal(100, 10, 10),
        np.random.normal(90, 7, 10)
    ])
}
df_bar = pd.DataFrame(data_bar)

# 按id分组并求和
bar_data = df_bar.groupby('id')['pulse'].sum().reset_index()
print("按ID分组后脉搏求和结果:")
print(bar_data.to_string(index=False))

fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(x=bar_data['id'].astype(str), height=bar_data['pulse'])
ax.set_title('用户脉搏总和柱状图')
ax.set_xlabel('用户 ID')
ax.set_ylabel('脉搏(总和)')
plt.close(fig)
按ID分组后脉搏求和结果:
 id      pulse
  1 520.304390
  2 542.807192
  3 553.428929
  4 535.975290
  5 525.292383
  • 聚合操作:同一个ID对应多条脉搏记录,柱状图需要展示一个汇总值。这里默认使用了求和(sum)。

堆叠柱状图与分组柱状图: 当数据有层级分组时(例如,先按diet分大类,每类下又有kind子类),可以绘制堆叠或分组柱状图。

  • 堆叠柱状图:子类的值以堆叠的方式显示在同一根柱子上,柱子总高度表示大类总和。
  • 分组柱状图:子类并排显示在对应的大类分组下。
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# 构造包含diet和kind的数据
np.random.seed(42)
data_complex = {
    'diet': np.tile(['low fat', 'no fat', 'normal'], 10),
    'kind': np.tile(['rest', 'walking', 'running'], 10),
    'pulse': np.concatenate([
        np.random.normal(85, 5, 10),
        np.random.normal(100, 10, 10),
        np.random.normal(90, 7, 10)
    ])
}
df_complex = pd.DataFrame(data_complex)

# 计算每个diet和kind组合下的脉搏均值,用于演示
pivot_mean = df_complex.pivot_table(values='pulse', index='diet', columns='kind', aggfunc='mean')
print("饮食类型与运动类型下的平均脉搏:")
print(pivot_mean.round(2))
饮食类型与运动类型下的平均脉搏:
kind      rest  running  walking
diet
low fat  91.57      NaN      NaN
no fat     NaN      NaN     87.6
normal     NaN    88.61      NaN
  • 映射思想:label参数对应大类(diet),stack或group参数对应子类(kind),values对应需要聚合的数值列(pulse),agg指定聚合函数(如'mean')。

盒子图 (Box Plot)

盒子图(箱线图)是展示数据分布、识别异常值的强大工具。盒子两端是第一四分位数(Q1)与第三四分位数(Q3),盒中横线是中位数;须线通常延伸到「距盒端 1.5×IQR 以内」的最远数据点,超出须线的点视为潜在异常值。

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# 构造数据
np.random.seed(42)
data_box = {
    'diet': np.repeat(['low fat', 'no fat', 'normal'], 10),
    'pulse': np.concatenate([
        np.random.normal(85, 5, 10),
        np.random.normal(100, 10, 10),
        np.random.normal(90, 7, 10)
    ])
}
df_box = pd.DataFrame(data_box)

# 准备绘图数据
plot_data = [df_box[df_box['diet']==cat]['pulse'].values for cat in df_box['diet'].unique()]
labels = df_box['diet'].unique()

fig, ax = plt.subplots(figsize=(8, 5))
# 注:matplotlib 3.9 起 boxplot 的 labels 参数更名为 tick_labels
ax.boxplot(plot_data, tick_labels=labels)
ax.set_title('不同饮食类型的脉搏分布盒子图')
ax.set_xlabel('饮食类型')
ax.set_ylabel('脉搏')
# 打印一些统计量用于理解
for i, (cat, data) in enumerate(zip(labels, plot_data)):
    q1, median, q3 = np.percentile(data, [25, 50, 75])
    print(f"{cat}: Q1={q1:.1f}, 中位数={median:.1f}, Q3={q3:.1f}")
plt.close(fig)
low fat: Q1=83.9, 中位数=87.6, Q3=88.7
no fat: Q1=86.9, 中位数=92.6, Q3=95.4
normal: Q1=85.9, 中位数=88.2, Q3=90.7
  • 映射思想:label参数指定了分组列(diet),values参数指定了需要分析分布的数值列(pulse)。图形直观展示了不同饮食组脉搏的中心趋势、离散程度和潜在异常点。

弦图 (Chord Diagram)

弦图用于展示多个实体(节点)之间两两关系的强度。所有节点排列在一个圆环上,节点之间的连线(弦)表示存在关系,连线的粗细通常代表关系的权重(如流量、数量、相关性强度)。

概念解析: 假设我们有“用户ID”和“运动类型”两类节点。弦图可以展示“哪个用户进行了哪种运动”,连线的粗细可以代表该用户进行该运动时的“平均脉搏”或“总运动次数”。

  • 源 (source):连线的起点节点,例如用户ID。
  • 目标 (target):连线的终点节点,例如运动类型。
  • 值 (value):决定连线粗细的权重值,例如脉搏值。

原课中使用bokeh.charts.Chord(data, source='id', target='kind', value='pulse')进行绘制。由于Matplotlib原生不支持弦图,且Bokeh charts API已变更,此处我们通过文字和伪数据描述其逻辑。

import pandas as pd

# 构造一个简化的弦图关系数据示例
data_chord = {
    'source': ['用户A', '用户A', '用户B', '用户B', '用户C'],
    'target': ['跑步', '游泳', '跑步', '骑行', '游泳'],
    'value': [30, 20, 40, 25, 35] # 代表关系强度,如运动时长
}
df_chord = pd.DataFrame(data_chord)
print("弦图关系数据示例:")
print(df_chord)
print("\n说明:")
print("- 节点:用户A、用户B、用户C、跑步、游泳、骑行")
print("- 连线:从source节点指向target节点")
print("- 粗细:value值越大,连线越粗")
弦图关系数据示例:
  source target  value
0    用户A     跑步     30
1    用户A     游泳     20
2    用户B     跑步     40
3    用户B     骑行     25
4    用户C     游泳     35

说明:
- 节点:用户A、用户B、用户C、跑步、游泳、骑行
- 连线:从source节点指向target节点
- 粗细:value值越大,连线越粗

在Bokeh生成的交互式弦图中,你可以清晰地看到所有节点及其连接,并通过悬停等方式查看具体的关系权重。

📝 动手练一练

  1. 概念辨析:散点图、柱状图和盒子图分别最适合用来揭示数据哪方面的特征?请简述。
  2. 代码实践:假设你有一份销售数据,包含城市、产品类别和销售额三列。如果你想直观对比不同城市在不同产品类别上的销售额对比,应该选择堆叠柱状图还是分组柱状图?请说明原因,并尝试用pivot_table计算出绘制所需的数据格式。
👉 点击查看参考答案
  1. 答案:

    • 散点图:最适合展示两个连续变量之间的相关关系、分布模式或聚集情况。
    • 柱状图:最适合比较分类数据的汇总值(如总和、均值、计数),直观显示各类别间的差异。
    • 盒子图:最适合展示单变量在多个类别下的分布情况,包括中心位置、离散程度、偏态以及异常值。
  2. 答案:选择分组柱状图更为合适。

    • 原因:目标是“对比”不同城市在不同产品类别上的销售额。分组柱状图将同一城市下的不同产品类别并排显示,便于直接进行跨城市、跨类别的横向比较。而堆叠柱状图更侧重于显示每个城市销售总额的构成,以及构成部分(产品类别)的相对大小,不便于精确比较不同城市间同一产品类别的销售额。
    • 数据准备:
    # 每个代码块自包含:构造一个销售数据示例
    import pandas as pd
    
    df_sales = pd.DataFrame({
        '城市': ['北京', '北京', '上海', '上海', '深圳', '深圳'],
        '产品类别': ['家电', '服饰', '家电', '服饰', '家电', '服饰'],
        '销售额': [120, 80, 150, 60, 90, 70]
    })
    # 使用数据透视表计算每个城市、每个产品类别的销售额总和
    pivot_sales = df_sales.pivot_table(values='销售额', index='城市', columns='产品类别', aggfunc='sum', fill_value=0)
    print(pivot_sales)
产品类别   家电  服饰
城市
上海    150  60
北京    120  80
深圳     90  70
得到的数据框,行索引是城市,列索引是产品类别,值是该城市在该类别下的销售总额,这正好是绘制分组柱状图所需的数据格式。

本章小结

本节我们深入探讨了使用Bokeh(通过Matplotlib映射思想)绘制多种统计图形的方法。我们理解了交互式可视化的底层原理,并重点掌握了散点图、柱状图、盒子图的参数含义与数据映射逻辑。对于弦图,我们学习了其用于表达复杂关系网络的概念。掌握这些图形的适用场景和绘制方法,是进行有效数据探索和沟通的基础。

📋 行动清单

  • 尝试使用Matplotlib的scatter、bar和boxplot函数,用自己构造的小数据集复现本节图形。
  • 思考一个你熟悉的数据集,判断哪些问题适合用散点图分析,哪些适合用柱状图或盒子图分析。
  • 了解现代交互式可视化库(如Plotly Express),观察其绘制相同图形的API与Bokeh思想的异同。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 本节课件:Bokeh绘制散点图、柱状图、盒子图、弦图
    PDF · 518KB
    下载
  • 全套课件打包(第1-5章)
    ZIP · 12.8MB
    下载
  • 全套课件打包(第6-8章)
    ZIP · 15MB
    下载
  • 实战数据集:AppleStore 应用商城分析
    ZIP · 329KB
    下载
  • 实战数据集:女性服装电商分析
    ZIP · 2.8MB
    下载
  • Python 数据分析环境搭建指南
    PDF · 2MB
    下载
  • Scrapy 安装教程
    PDF · 12.7MB
    下载
  • 附加实战项目:AppleStore 应用商城数据分析
    ZIP · 0.3MB · ipynb + CSV 数据
    下载
  • 附加实战项目:银行电话营销数据分析
    ZIP · 0.4MB · ipynb + CSV 数据
    下载
  • 附加实战项目:女性服装电商评论数据分析
    ZIP · 2.7MB · ipynb + CSV 数据
    下载
  • 附加实战项目:美国化学学会杂志数据分析
    ZIP · 34.2MB · ipynb + SQLite 数据库
    下载