Python
Seaborn
可视化
多变量
热图
使用 Seaborn 绘制多变量可视化图表:散点图、折线图、热力图、双变量图、分面网格图等。
作者

Pinn Xu

发布于

2024年10月27日

22 min read · 762 EN words · 5283 中文字
环境与版本
python==3.12
pandas==2.2.2
matplotlib==3.9.x
seaborn==0.13.2

1 多变量数据

“tidy/flat” 的表格数据: 每行为一个 observation (或 instance、record) ,每列为一个 variable (变量,也称 attribute、feature、parameter) 。

通常,multivariate (多变量) 用于描述变量超过两个的数据,即,multivariate plot 通常使用了原 DataFrame 中三个或三个以上的列作为数据。

绘制柱状图时,除了 x、y 两个变量之外,还可以设置 hue 参数,从而绘制包含三个变量的柱状图。比如 图 1 中包含了 'island'、'body_mass_g'、'sex' 三个变量。

# 导入库
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 从 Seaborn 加载示例数据集
df = sns.load_dataset('penguins')
sns.barplot(df, x='island', y='body_mass_g', hue='sex')
plt.show()
图 1: 包含三个变量的柱状图

然而,有的图表类型不支持同时设置 x、y 和 hue 三个参数。比如直方图只支持 x 和 hue (见 图 2) x 轴展示鳍长的分布区间,y 轴展示每个区间内的企鹅数量,只涉及两个变量。

sns.histplot(data=df, x='flipper_length_mm', hue='island')
plt.show()
图 2: 包含两个变量的直方图

2 分面网格

分面网格 (facetgrid) 是展示多元变量的常用方法。若干个小图表 (分面,facet) 以网格状排列,组成分面网格 (facetgrid) 。如果既想展示直方图,又想展示两个以上变量,可以使用分面网格。

通过 Seaborn 绘制分面网格的方法如下:

# 创建空的分面网格
g = sns.FacetGrid(
    data=df,
    col='sex',    # 'sex' 有多少种,就生成多少列
    row='island', # 'island' 有三个值,生成三行
)
plt.show()
图 3: 空的 3×2 分面网格

如 图 3,我们创建了一个 3×2 的分面网格,但此时只有网格,还需要通过 .map_dataframe() 方法来指定填充内容。

# 填充完整分面网格

# 第一步:创建分面网格
g = sns.FacetGrid(
    data=df,
    col='sex',
    row='island',
    hue='species',
)

# 第二步:填充直方图
g.map_dataframe(
    sns.histplot,
    x='flipper_length_mm',
    stat='density',    # 各组 bar 面积之和为 1
    common_norm=False, # 每个分面内各颜色分别归一化
)

# 第三步:添加并调整图例
g.add_legend()
sns.move_legend(
    g,
    loc='lower center',
    bbox_to_anchor=(0.5, 1),
    ncol=3,  # 图例列数
)
plt.show()
图 4: 填充后的分面网格

stat 与 common_norm
stat='density' 用频率密度统计绘图,使直方图内所有 bar 的面积之和为 1。common_norm=False 配合 stat 使用,表示在同一分面中,每种颜色的 bar 面积之和分别为 1,而非所有颜色的 bar 面积总和为 1。

loc 与 bbox_to_anchor
同时设置两个参数,让图例的下边缘中点 (lower center) 位于整个网格图的 (0.5, 1) 位置。如果只设置 loc='lower center',图例将直接定位于整个网格图的中下部。

如 图 4 ,分面网格按岛屿分行、按性别分列、按种类分色,同时展示了四个变量 (鳍长、岛屿、性别、种类) 。通过分行和分列可以额外增加两个离散型变量;通过颜色区分还可再增加一个离散型变量。

注:由于分行和分列的特性,分面网格只能用于额外增加离散型变量。若要对连续型变量分面,可先将其划分区间,保存为新的离散型变量列,再用分面网格进行分行或分列。

刻度范围一致性
同时展示多个子图时,每个子图的刻度范围应当相同,因为读者往往默认多图的刻度范围一致,不一致会造成误判 (Wilke, 2019, pp. 257-258)。

图 5 中各子图纵坐标刻度范围各不相同,容易让人误判。将其改为 图 6 这样所有刻度范围都相同,可减少阅读困难。

差的多图刻度范围 (Wilke, 2019, p. 258)
图 5: 反面案例: 刻度范围不同 (Wilke, 2019, p. 258)
改良版多图刻度范围 (Wilke, 2019, p. 259)
图 6: 改良版: 统一刻度范围 (Wilke, 2019, p. 259)

3 散点图

3.1 区分散点图和点图

在画散点图之前,需要注意区分 散点图 (scatter plot) 和 点图(point plot)。

注:点图又称 dot plot (Wilke, 2019, p.43)。以下对散点图与点图的区别的理解均基于 Seaborn 库,在其他语境下可能有所不同。

点图 (point plot) 可以起到类似柱状图的作用:点图中每个点所在的竖线相当于柱状图的 bar,点相当于柱状图中每个 bar 的顶边缘中点。像柱状图一样,点图可以用于展示 category-amount 型图表 (见 图 7 和 图 8) :

sns.pointplot(
    data=df, x='species', y='bill_length_mm',
    linestyles='none',  # 点与点之间不连线
)
plt.show()
图 7: 点图: 企鹅种类-喙长 (黑线为误差线)
sns.barplot(data=df, x='species', y='bill_length_mm')
plt.show()
图 8: 柱状图: 企鹅种类-喙长 (黑线为误差线)

如 图 7 和 图 8 所示,点图和柱状图都可以表示 类型-数量 (category-amount) 关系。二者区别之一是:柱状图的 数量 (amount) 必须从 0 开始,而点图不需要。点图和柱状图都只展示纵坐标的平均值 (也可设置为其他统计值,如中位数) ,每个横坐标值只能对应一个纵坐标值。

散点图 (scatter plot) 会显示每一个数据点 (见 图 9) :

sns.scatterplot(data=df, x='species', y='bill_length_mm')
plt.show()
图 9: 散点图: 类型-数量 (显示所有数据点,而非平均值)

散点图中每个横坐标可以对应多个纵坐标,而点图中每个横坐标只对应一个纵坐标。

散点图常用于探究两个 (或多个) 变量之间的关系 (relationship) ,查看变量间是否存在关联性 (correlation) ,主要用于探究两种连续数值型数据之间的关系。因此散点图横轴通常为 ratio data,刻度自动显示为连续数值区间 (见 图 10) :

sns.scatterplot(
    data=df, x='bill_length_mm', y='bill_depth_mm',
    alpha=0.7,  # 半透明,便于观察点的堆积
)
plt.show()
图 10: 散点图: 企鹅喙长-喙深 (ratio-ratio)

注:设置半透明有助于观察点的堆积,但若已使用渐变颜色区分某变量 (如 图 14) ,则不宜再使用半透明。

点图 (point plot) 使用 ratio data 作横轴时,每个点会对应一个特定刻度,效果如 图 11 所示:

sns.pointplot(
    data=df, x='bill_length_mm', y='bill_depth_mm',
    linestyle='none', errorbar=None, alpha=0.7,
)
plt.show()
图 11: 点图: 使用 ratio 变量作横轴

注:虽然 图 10 和 图 11 点的分布看似相似,但点图中每个横坐标有且仅有一个对应的点,散点图可以对应多个。此外,sns.pointplot() 不支持调整点的大小,sns.scatterplot() 支持。

因此,点图不适用于横坐标为连续型数据且刻度过多的情况,更适用于横坐标为离散型数据且刻度不宜过多的场景。散点图 则相反,更适用于 ratio-ratio 数据。

点图的另一个特点是点与点之间可以连线,适用于横轴为 ordinal 或 interval data 的情况。比如 图 12 中年份为 interval data,连线可以表示乘客量随年份的变化趋势;而 图 7 中横坐标为企鹅种类 (nominal data) ,顺序没有意义,因此不画连线。

这种点图变得和折线图非常类似,我们将在 折线图 Line Plot 中继续讨论。

flights = sns.load_dataset('flights')
sns.pointplot(data=flights, x='year', y='passengers')
plt.show()
图 12: 点图: 年份-乘客数 (interval-ratio)

3.2 多变量散点图

除了 x、y 轴展示的两种连续变量之外,还可以通过设置 hue (点颜色) 、size (点大小) 、style (点形状) 等参数来引入其他变量。hue 和 size 可以应用于连续型或离散型变量;style 只能应用于离散型变量,因为点形状无法连续渐变 (仅限 Seaborn 库) 。

比如 图 13 同时引入了三个离散变量:

ax = sns.scatterplot(
    data=df, x='bill_length_mm', y='bill_depth_mm',
    hue='sex',
    size='island',
    style='species',
)
sns.move_legend(ax, loc='upper left', bbox_to_anchor=(1, 1))
plt.show()
图 13: 增加三个离散变量: hue & size & style

图 13 中,除横纵轴的两个连续变量 (喙长、喙深) 外,还设置了三个离散变量 (性别、岛屿、种类) 。再比如 图 14 引入了两个连续变量:

ax = sns.scatterplot(
    data=df, x='bill_length_mm', y='bill_depth_mm',
    hue='flipper_length_mm',
    size='body_mass_g',
)
sns.move_legend(ax, loc='upper left', bbox_to_anchor=(1, 1))
plt.show()
图 14: 增加两个连续变量: hue & size (已有渐变色,不宜使用半透明)

注:虽然 hue 和 size 可以应用于连续型变量,但其图例并非严格连续的。

多变量散点图与分面网格的区别见 表 1:

表 1: 多变量散点图与分面网格的比较
维度 多变量散点图 分面网格
视图 全局,所有点在同一坐标系 分格展示,便于子图对比
图形空间 少,只需一张图 多,需创建多个子图
连续变量 可直接引入,自动生成图例 需手动划分区间保存为新变量
视觉复杂度 信息集中,可能过于拥挤 各子图独立,阅读负担较低
图例依赖 需反复对照图例 子图标题直接说明

为了让散点图更易辨认,建议 同时使用颜色和形状区分同一变量 (Wilke, 2019, pp.243-247),如 图 15:

ax = sns.scatterplot(
    data=df, x='bill_length_mm', y='bill_depth_mm',
    hue='island',
    style='island',
)
sns.move_legend(ax, loc='upper left', bbox_to_anchor=(1, 1))
plt.show()
图 15: hue+style 同时控制同一变量 (岛屿)

此外,尽量不要在同一图表内同时使用 style 和 size 控制两个不同变量,因为形状改变后大小的区分会变得不够直观 (见 图 16) :

ax = sns.scatterplot(
    data=df, x='bill_length_mm', y='bill_depth_mm',
    hue='island',
    style='island',
    size='flipper_length_mm',
)
sns.move_legend(ax, loc='upper left', bbox_to_anchor=(1, 1))
plt.show()
图 16: hue+style 控制岛屿,size 另控制鳍长

其他注意事项:

  • 使用颜色时应尽量兼顾色觉缺陷读者,选择在色相和明度上均有明显差异的颜色。
  • 当折点过多时,不宜在折线图中展示点。

这些细节将在 小节 4 中进一步讨论。

3.3 对数尺度

对数尺度 (logarithmic scale) 是一个非线性测量尺度,适用于有较大范围差异的数据,如里氏震级、声学音量、光学光强、溶液 pH 值等。为此类数据绘制散点图时,可将坐标轴设置为对数尺度 (默认为线性尺度,如 图 10 中 x 轴刻度 35, 40, 45, 50 是线性增长的数字) ,让原本拥挤的数据点变得较为均匀,更容易观察 (维基百科 - 对数尺度) 。

Wilke (2019, p.17) 以 \((1, 3.16, 10, 31.6, 100)\) 为例画了四个数轴 (图 17) ,其中 \(10^{0.5} = \sqrt{10} \approx 3.16\),\(10^{1.5} \approx 31.6\)。

四个数轴 (Wilke, 2019, p.17)
图 17: 四个数轴 (Wilke, 2019, p.17)

如 图 17,四个数轴含义如下:

  • 第一个数轴使用线性尺度,刻度为 \((0, 25, 50, 75, 100)\),展示原数据 \(x\),数据点分布不均匀。
  • 第二个数轴同样使用线性尺度,但对原数据取对数,展示 \(\log_{10}(x)\),数据点均匀分布,轴标签为 \(\log_{10}(x)\)。
  • 第三个数轴使用原数据 \(x\),但改用对数尺度,刻度为 \((1, 3.16, 10, 31.6, 100)\) (即 \((10^{0.0}, 10^{0.5}, 10^{1.0}, 10^{1.5}, 10^{2.0})\)) ,数据点均匀分布。
  • 第四个数轴同第三个,但轴标签错误地写成了 \(\log_{10}(x)\)。

对比第一个和第三个数轴:对于同一组数据,线性尺度中分布不均匀的点,在对数尺度中变得均匀。这样调整有助于观察分布不均匀的数据,尤其是呈指数型增长趋势的数据。

需要注意的是,改用对数尺度只改变了轴刻度的展示方式,并没有改变数据点的值。因此,对于仅改用刻度尺度、不改动原数据的图,不可以将轴标题写作 \(\log_{10}(x)\) (见第四个数轴) 。

对数尺度适用的情况及其优缺点:

适用情况

  • 数据范围跨度极大(跨多个数量级),例如人口、收入、距离、质量等
  • 更关注相对变化(倍数关系),而不是绝对差值
  • 数据呈指数增长或指数衰减趋势(在对数尺度下更接近线性关系)

优点

  • 压缩数据范围:同时展示极大值与极小值,避免大值“压扁”整体结构
  • 改善可视化分布:减少数据在某一区间的堆积,使分布更均匀
  • 强化倍数比较:相同的轴距离表示相同的“倍数变化”,更符合某些分析需求
  • 有助识别指数关系:指数增长/衰减在对数尺度下更容易观察

缺点

  • 不适用于零值与负值:对数函数定义域为正数
  • 可读性较低:对不熟悉对数尺度的读者不够直观
  • 可能掩盖绝对差异:压缩高值区间,弱化原始差距的视觉表达

例如,图 18 中数据范围差异较大且左边过于集中——可以改用对数尺度 (图 19) 。

planets = sns.load_dataset('planets')
g = sns.scatterplot(data=planets, x='distance', y='mass')
plt.title('Relationship between planetary distance and mass')
plt.show()
图 18: 散点图: 行星距离-质量,线性尺度
planets = sns.load_dataset('planets')
g = sns.scatterplot(data=planets, x='distance', y='mass')
g.set_xscale('log')  # 默认底数为 10,可通过 base 参数修改
g.set_yscale('log')
plt.show()
图 19: 散点图: 行星距离-质量,对数尺度

注:图 19 中,刻度 \((10^0, 10^1, 10^2, ...)\) 是等距的对数尺度刻度。每两个大刻度间的小刻度代表等差的实际数值,如从 \(10^0\) 到 \(10^1\) 之间分别为 \(2, 3, ..., 9\),从 \(10^1\) 到 \(10^2\) 之间分别为 \(20, 30, ..., 90\),以此类推。

由于对数尺度无 0 值 (以 1 即底数的 0 次方为原点) ,对数尺度适合用于展示比例数据。如 Wilke (2019, pp.18-20) 将德克萨斯州各县人口除以人口中位数作为纵轴,用对数尺度 (图 20) 展示了各县人口在中位数附近的分布 (比值为 1 时说明该县人口恰好为中位数) 。

德克萨斯州各县人口分布情况 - 对数尺度
图 20: 德克萨斯州各县人口分布 - 对数尺度

如果改用线性尺度展示 图 20 中的数据,则以 0 为原点,无法清晰展示数据在 1 两侧的分布 (图 21) 。

德克萨斯州各县人口分布情况 - 线性尺度
图 21: 德克萨斯州各县人口分布 - 线性尺度

3.4 回归图

回归图 (regression plot) 将散点图拟合成一条线,用于展示两个变量之间的关系。最常见的是 线性拟合 (linear fitting) ,即将散点图拟合成线性回归线。置信区间用于显示回归线 (作为估计值) 的不确定性:置信水平 95% 时,真实值有 95% 的可能落在该区间内。如 图 22 ,直线是线性回归线,阴影部分是置信区间。

sns.lmplot(data=df, x='bill_length_mm', y='bill_depth_mm', hue='sex')
plt.show()
图 22: 线性回归图

sns.lmplot() (linear model plot) 默认使用 95% 置信区间来衡量回归线的不确定性。

注:hue 参数仅适用于离散型数据。

4 折线图

散点图 (scatter plot) 中数据点独立存在,通常用于探索 两个变量 之间的相关性或分布,数据点之间无先后关系。折线图 (line plot) 中数据点通过线段连接,强调数据的连续性或变化趋势,通常用于时间序列数据,展示 一个变量 随时间的变化,数据点之间存在先后关系。

如 图 23 ,展示了某航空公司每月乘客量随年份增长的变化:

sns.lineplot(
    data=flights, x='year', y='passengers',
    hue='month',
)
# 注:年份和人数虽非严格连续数据,但数据量足以体现变化趋势
plt.show()
图 23: 按月份颜色区分的折线图

也可以通过设置 markers=True 和 dashes=False 展示折点 (图 24) :

sns.lineplot(
    data=flights, x='year', y='passengers',
    hue='month', style='month',
    markers=True,  # 需与 style 联用
    dashes=False,
)
plt.show()
图 24: 按月份颜色和点形状区分的折线图

当折点很多时,不建议在折线图中展示折点,过多的折点会阻碍阅读;清晰的线条更能彰显变化趋势 (Wilke, 2019, pp. 135-137)。

也可以用点图达到类似效果 (见 图 25) ,但 sns.pointplot() 不可用不同点形状区分变量,也不可去除点,因此要观察变量随时间的变化趋势,最好选择 sns.lineplot()。

sns.pointplot(
    data=flights, x='year', y='passengers', hue='month',
)
plt.show()
图 25: 点图: 乘客量随时间的变化趋势

当 y 轴变量在每个时间点上有多个值,且未对其进行颜色区分时,sns.lineplot() 会自动生成平均值折线和误差区间 (默认 95% 置信水平) ,见 图 26:

sns.lineplot(data=flights, x='year', y='passengers')
plt.show()
图 26: 不做区分则生成平均值折线 (上下边缘为旺淡季)

在折线图中使用颜色区分线条时,需要注意:

  • 图例颜色顺序应当符合线条颜色顺序。
  • 选色尽量兼顾色觉缺陷读者。

图 27 中图例和线条颜色顺序不一致,会增加阅读困难,Wilke (2019, p.248) 提供了反面案例:

反面案例:图例和线条颜色顺序不匹配
图 27: 反面案例:图例和线条颜色顺序不匹配

相反,图 28 中图例和线条颜色顺序相符,Wilke (2019, p.249) 提供了改良后的图表:

改良版:图例和线条颜色顺序匹配 (且选色兼顾色觉缺陷读者)
图 28: 改良版:图例和线条颜色顺序匹配 (且选色兼顾色觉缺陷读者)

Wilke (2019, p.250) 还展示了 图 28 在不同色觉缺陷模拟下及去除饱和度后的效果 (图 29) :

不同色觉缺陷模拟 & 去饱和度下的图表
图 29: 不同色觉缺陷模拟 & 去饱和度 (分别为:绿色弱,红色弱,蓝色弱,去除饱和度)

由 图 29 可见,图 28 的配色对三种色觉缺陷患者均可区分,去除饱和度后也能通过明度区分。因此图例与线条颜色顺序匹配尤为重要。

此外,Wilke (2019, p.251) 还建议去除图例,直接在折线旁标注文字 (图 30) ,可节省读者对照图例的工作量,让图表更易读。但这种做法通过 Seaborn 实现较困难,本课程中使用 图 28 中符合颜色顺序的图例即可。

去除图例,直接在折线旁边标注文字
图 30: 去除图例,直接在折线旁标注文字

5 热力图

5.1 数据透视表

在绘制热力图之前,需要先为原数据创建 pivot table (数据透视表,又称枢轴表) 。数据透视表将原表格中的两个变量以矩阵形式展现:列名是一个变量,行名是另一个变量。

pivot 操作将原表某列中的所有数据种类转变为新的列名 (column label) 或行名/索引 (index) 。如 表 3 将 表 2 中 'year' 列的所有数据种类 (1949~1960) 变为新列名,将 'month' 列的数据种类变为新行名。

pivot 操作和 melt 操作相反:pivot 将”长表格”变为”宽表格”,melt 将”宽表格”变为”长表格” (melt 将原列名变为数据值,让表格行数增多) 。

flights.head()
表 2: 原飞行乘客表 (预览前五行)
year month passengers
0 1949 Jan 112
1 1949 Feb 118
2 1949 Mar 132
3 1949 Apr 129
4 1949 May 121
# 创建数据透视表 (也可命名为 matrix) 
flights_pivot = pd.pivot_table(
    flights,
    index='month',   # 'month' 的数据种类作为新行名
    columns='year',  # 'year' 的数据种类作为新列名
    values='passengers',
    observed=False,  # 显示所有分类变量的可能组合
)

flights_pivot
表 3: 数据透视表: 年&月 - 乘客量
year 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960
month
Jan 112.0 115.0 145.0 171.0 196.0 204.0 242.0 284.0 315.0 340.0 360.0 417.0
Feb 118.0 126.0 150.0 180.0 196.0 188.0 233.0 277.0 301.0 318.0 342.0 391.0
Mar 132.0 141.0 178.0 193.0 236.0 235.0 267.0 317.0 356.0 362.0 406.0 419.0
Apr 129.0 135.0 163.0 181.0 235.0 227.0 269.0 313.0 348.0 348.0 396.0 461.0
May 121.0 125.0 172.0 183.0 229.0 234.0 270.0 318.0 355.0 363.0 420.0 472.0
Jun 135.0 149.0 178.0 218.0 243.0 264.0 315.0 374.0 422.0 435.0 472.0 535.0
Jul 148.0 170.0 199.0 230.0 264.0 302.0 364.0 413.0 465.0 491.0 548.0 622.0
Aug 148.0 170.0 199.0 242.0 272.0 293.0 347.0 405.0 467.0 505.0 559.0 606.0
Sep 136.0 158.0 184.0 209.0 237.0 259.0 312.0 355.0 404.0 404.0 463.0 508.0
Oct 119.0 133.0 162.0 191.0 211.0 229.0 274.0 306.0 347.0 359.0 407.0 461.0
Nov 104.0 114.0 146.0 172.0 180.0 203.0 237.0 271.0 305.0 310.0 362.0 390.0
Dec 118.0 140.0 166.0 194.0 201.0 229.0 278.0 306.0 336.0 337.0 405.0 432.0

关于 aggfunc 参数。 pd.pivot_table() 的 aggfunc (aggregation function) 默认为 'mean',即当一个单元格对应多行原数据时,默认取平均值。

表 2 中特定 year 和 month 下 passenger 只有一个值,不需要求平均值。而如 表 4 所示的企鹅数据表,同一种类、同一岛屿的企鹅不止一只,在数据透视表 (表 5) 中只对应一个单元格,因此默认求平均值。

df.head()
表 4
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex
0 Adelie Torgersen 39.1 18.7 181.0 3750.0 Male
1 Adelie Torgersen 39.5 17.4 186.0 3800.0 Female
2 Adelie Torgersen 40.3 18.0 195.0 3250.0 Female
3 Adelie Torgersen NaN NaN NaN NaN NaN
4 Adelie Torgersen 36.7 19.3 193.0 3450.0 Female
# 用企鹅数据集的种类和岛屿列制作数据透视表
df_pivot = pd.pivot_table(
    df,
    index='species',
    columns='island',
    values='bill_length_mm',
)

df_pivot
表 5: 数据透视表:企鹅种类 & 岛屿 - 喙长
island Biscoe Dream Torgersen
species
Adelie 38.975000 38.501786 38.95098
Chinstrap NaN 48.833824 NaN
Gentoo 47.504878 NaN NaN

5.2 绘制热力图

使用 表 3 绘制热力图,见 图 31:

sns.heatmap(
    data=flights_pivot,
    cbar_kws={'label': 'passengers'},  # 色彩条标签
)
plt.show()
图 31: 基础热力图: 飞行航班

.heatmap 直接使用矩阵形式的表格画图,而非通过 x/y 参数指定特定列。cbar_kws (colorbar keyword arguments) 用于设置色彩条的标签。

还可以通过设置 cmap 和 annot 等参数进一步修改热力图 (图 32) :

sns.heatmap(
    data=flights_pivot,
    cbar_kws={'label': 'passengers'},
    cmap='viridis',  # Matplotlib 预制 colormap
    annot=True,      # 在格子中显示数值
    fmt='.0f',       # 格式:保留整数 ('.nf' 保留 n 位小数) 
)
plt.show()
图 32: 热力图: 添加更多参数 (args)

cmap 可以使用 Seaborn 或 Matplotlib 中预制的 color palette 或 colormap 名称,如 'crest'、'flare'、'viridis'、'pink' 等。

同理,使用 表 5 绘制热力图 (图 33) :

sns.heatmap(
    data=df_pivot,
    cmap='crest',
    cbar_kws={'label': 'bill_length_mm'},
    annot=True,
)
plt.show()
图 33: 企鹅数据热力图

6 二维直方图

根据 Wilke (2019, pp. 222-225),含半透明点的散点图可展示数据在两个变量控制下的分布,但当数据点过多、过密时,散点图难以展示整体分布 (见 图 34: 点密度高的区域显示为均匀深色斑点,密度低的区域几乎不可见) 。

数据点过多、过密的散点图 (Wilke, 2019, p. 223)
图 34: 数据点过多、过密的散点图 (Wilke, 2019, p. 223)

此时可改用 二维直方图 (2D histogram) :将 x-y 平面细分为小矩形 (分箱,bin) ,计算落入每个分箱的数据量,并按数据量对矩形着色 (见 图 35) :

sns.histplot(
    data=df, x='bill_depth_mm', y='bill_length_mm',
    cbar=True,  # 显示色彩条
    cbar_kws={'label': 'count'},
)
plt.show()
图 35: 二维直方图

表 6 展示了热力图和二维直方图的区别:

表 6: 热力图和二维直方图的区别
特性 热力图 (Heatmap) 二维直方图 (2D Histogram)
输入数据 预处理的矩阵 (通常来自 Pivot Table) 原始数据
横纵轴 通常表示两个离散变量 通常表示两个连续变量
变量个数 三个:横纵轴和颜色 两个:横纵轴
颜色含义 单元格中的数值大小 分箱 (bin) 中的数据点数量
排布方式 行列数固定,颜色对应值固定 根据分箱数量可改变形状
主要用途 展示矩阵中的数值模式 展示两个变量的联合分布密度

7 双变量图

利用 sns.jointplot() 可以同时展示两个变量的二元分布图和各自的一元分布图。

如 图 36,可以在展示二维直方图 (同 图 35) 的同时,展示两个变量各自的一维直方图:

sns.jointplot(
    data=df, x='bill_depth_mm', y='bill_length_mm',
    kind='hist',  # 默认为 'scatter',此处改为直方图
)
plt.show()
图 36: 双变量图: 直方图形式

还可以通过 kind='hex' 让中间的格子呈六边形 (图 37) :

sns.jointplot(
    data=df, x='bill_depth_mm', y='bill_length_mm',
    kind='hex',
)
plt.show()
图 37: 双变量图: 六边形图形式

还可以用颜色区分变量 (图 38) :

sns.jointplot(
    data=df, x="bill_depth_mm", y="bill_length_mm",
    hue="species",
)
plt.show()
图 38: 双变量图: 用 hue 区分 (外部一元图自动变为密度图)

还可以通过 kind='kde' (kernel density estimation) 生成光滑化的分布曲线 (见 图 39) 。 但光滑化后的曲线并非真实数据,尤其在数据点很少时可能误导读者,因此本课程中不建议使用。

sns.jointplot(
    data=df, x="bill_depth_mm", y="bill_length_mm",
    hue="species", kind='kde',
)
plt.show()
图 39: 双变量图: 用 kde 人工光滑化 (不推荐)

8 配对图

使用 sns.pairplot() 可以轻松画出原数据表中多个变量 (即多列) 各自两两配对的分布图,形成图表阵 (图 40) :

sns.pairplot(data=df, hue='species', height=1.7)
plt.show()
图 40: 配对图 (height 调整图片高度)

图 40 中,原 DataFrame 中的四个 ratio 变量被自动识别并画图:对角线的图是各变量各自的一元分布图,其余每个子图是基于一对变量绘制的二元分布图。

还可以通过设置 diag_kind 和 kind 来修改对角线图表和其他二元图表的格式 (图 41) :

sns.pairplot(
    data=df,
    hue='species',
    diag_kind='hist',  # 对角线图表的形式
    kind='hist',       # 其他二元分布图的形式
    height=1.7,
)
plt.show()
图 41: 配对图: 直方图形式
回到顶部