常见问题#

这是关于seaborn常见问题解答的集合。

入门#

我已经安装了seaborn,为什么我不能导入它?#

看起来你通过 pip install seaborn 成功安装了seaborn,但无法导入。当你尝试时,会遇到类似“ModuleNotFoundError: No module named ‘seaborn’”的错误。

这可能不是 seaborn 本身的问题。如果你在电脑上有多个 Python 环境,你有可能在一个环境中执行了 pip install,而在另一个环境中尝试导入库。在 Unix 系统上,你可以检查终端命令 which pipwhich python 以及(如果适用)`which jupyter` 是否指向同一个 bin/ 目录。如果不是,你需要整理你的 $PATH 变量的定义。

使用 pip 安装的两种替代模式可能对此问题更具鲁棒性:

  • 在命令行中使用 python -m pip install <package> 而不是 pip install <package> 来调用 pip

  • 在 Jupyter 笔记本中使用 %pip install <package> 将其安装在与内核相同的位置。

我无法导入 seaborn,尽管它肯定已经安装了!#

你肯定已经正确安装了seaborn,但在导入它时产生了一个长长的回溯和一个令人困惑的错误信息,可能是类似 ImportError: DLL load failed: 指定的模块未找到

此类错误通常表明 Python 库在使用编译资源的方式上存在问题。由于 seaborn 是纯 Python 编写的,它不会直接遇到这些问题,但其依赖项(numpy、scipy、matplotlib 和 pandas)可能会遇到。要解决此问题,您首先需要阅读回溯信息,并找出错误发生时正在导入的依赖项。然后查阅相关包的安装文档,其中可能会有关于如何在您的特定系统上使安装正常工作的建议。

这些问题的最常见原因是 scipy,它有许多编译组件。从 seaborn 版本 0.12 开始,scipy 是一个可选依赖项,这应该有助于减少这些问题的发生频率。

为什么我的图表没有显示?#

你正在调用 seaborn 函数 — 可能在终端或带有集成 IPython 控制台的 IDE 中 — 但没有看到任何图表。)

在 matplotlib 中,创建 图形和 显示 图形是有区别的,在某些情况下,需要在想要查看图形的时刻显式调用 matplotlib.pyplot.show()。因为该命令默认会阻塞,并且并不总是期望的行为(例如,你可能正在执行一个将文件保存到磁盘的脚本),seaborn 在这里没有偏离标准的 matplotlib 实践。

然而,seaborn 文档中的大多数示例都没有这一行,因为有多种方法可以避免需要它。在 Jupyter 笔记本中使用 “inline” <https://ipython.readthedocs.io/en/stable/interactive/plotting.html#id1>`_(默认)或 `”widget” 后端时,执行单元格后会自动调用 matplotlib.pyplot.show(),因此任何图形都会出现在单元格的输出中。你也可以通过在任何 Jupyter 或 IPython 界面中执行 %matplotlib 或在 Python 中的任何地方调用 matplotlib.pyplot.ion() 来激活更交互式的体验。这两种方法都会配置 matplotlib 在每次绘图命令后显示或更新图形。

为什么每个笔记本单元格后都会打印某些内容?#

你在Jupyter笔记本中使用seaborn,每个单元格在显示图表之前都会打印类似<AxesSuplot:>或<seaborn.axisgrid.FacetGrid at 0x7f840e279c10>的内容。

Jupyter 笔记本会将单元格中最后一条语句的结果作为其输出的一部分显示,而 seaborn 的每个绘图函数都会返回一个对包含该图的 matplotlib 或 seaborn 对象的引用。如果这很烦人,你可以通过几种方式抑制这种输出:

  • 始终将最终语句的结果赋值给一个变量(例如 ax = sns.histplot(...)

  • 在最终语句的末尾添加一个分号(例如 sns.histplot(...);

  • 在每个单元格的末尾使用一个没有返回值的函数(例如 plt.show(),虽然不需要但也不会造成问题)

  • 如果将笔记本转换为不同的表示形式,请添加 单元元数据标签

为什么在 Jupyter 笔记本中的图表看起来模糊?#

默认的“内联”后端(由 IPython 定义)使用异常低的 dpi(“每英寸点数”)进行图形输出。这是一种节省空间的措施:较低 dpi 的图形占用较少的磁盘空间。(此外,较低 dpi 的内联图形在物理上显得更小,因为它们被表示为 PNG,这些图形并没有精确的分辨率概念。)因此,人们面临经济/质量的权衡。

你可以通过matplotlib API重置rc参数来提高DPI,使用

plt.rcParams.update({"figure.dpi": 96})

或者在你激活 seaborn 主题时这样做:

sns.set_theme(rc={"figure.dpi": 96})

如果你有一个高像素密度的显示器,你可以使用“视网膜模式”使你的图表更清晰:

%config InlineBackend.figure_format = "retina"

这不会改变你的图表在 Jupyter 界面中的外观大小,但在其他上下文中(例如在 GitHub 上)它们可能会显得非常大。而且它们会占用 4 倍的磁盘空间。或者,你可以制作 SVG 图表:

%config InlineBackend.figure_format = "svg"

这将配置 matplotlib 以生成具有“无限分辨率”的 矢量图形 。缺点是文件大小现在会随着绘图中艺术家数量和复杂性的增加而增加,并且在某些情况下(例如,大型散点图矩阵),加载会影响浏览器的响应性。

棘手的概念#

“figure-level” 和 “axes-level” 是什么意思?#

你遇到了“figure-level”或“axes-level”这个术语,可能是在seaborn文档、StackOverflow回答或GitHub讨论中,但你不知道它是什么意思。

简而言之,seaborn 中的所有绘图函数都属于以下两类之一:

  • “axes-level” 函数,用于绘制到一个可能存在也可能不存在的单个子图上,当调用该函数时。

  • “figure-level” 函数,它们内部创建了一个 matplotlib 图形,可能包括多个子图

此设计旨在满足两个目标:

  • seaborn 应该提供可以作为 matplotlib 方法的“即插即用”替代功能的函数

  • seaborn 应该能够生成显示“面”或边际分布的图形,这些图形位于不同的子图中

图级函数总是将一个或多个轴级函数与管理布局的对象结合起来。例如,relplot() 是一个图级函数,它结合了 scatterplot()lineplot()FacetGrid。相比之下,jointplot() 是一个图级函数,它可以结合多个不同的轴级函数——默认情况下是 scatterplot()histplot()——与 JointGrid

如果你只是用一个 seaborn 函数调用来创建图表,那么这并不是你需要过多担心的事情。但当你想要在每个函数的 API 提供的范围之外进行自定义时,这就变得相关了。这也是其他各种混淆点的来源,因此理解(至少大致上)并记住这一点是很重要的区别。

这在 教程 中有更详细的解释,并且在 这篇博客文章 中也有提及。

什么是“分类图”或“分类函数”?#

除了图形级/轴级区分之外,这个概念可能是导致混淆行为的第二大来源。

几个 seaborn 函数 被称为“分类”函数,因为它们旨在支持一种用例,其中图表中的 x 或 y 变量是分类的(即,该变量具有有限数量的潜在非数值)。

在编写这些函数时,matplotlib 不支持任何非数值数据类型。因此,seaborn 在内部构建了一个从数据中的唯一值到从0开始的整数索引的映射,这就是它传递给 matplotlib 的内容。如果你的数据是字符串,那就太好了,而且它或多或少地匹配了 matplotlib 现在处理 字符串类型数据的方式。

但一个潜在的陷阱是这些函数 默认总是这样做 ,即使 x 和 y 变量都是数值型的。这会导致许多令人困惑的行为,特别是在混合分类和非分类图(例如,组合条形图和折线图)时。

v0.13 版本添加了一个 native_scale 参数,该参数提供了对此行为的控制。它默认值为 False,但将其设置为 True 将保留用于分类分组的数据的原始属性。

指定数据#

我的数据需要如何组织?#

要充分利用 seaborn,您的数据应具有“长格式”或“整洁”的表示形式。在数据框中,这意味着 每个变量都有自己的列,每个观察值都有自己的行,每个值都有自己的单元格。使用长格式数据,您可以通过将数据集中的变量(列)分配给图中的角色来简洁而精确地指定可视化。

数据组织是初学者常见的绊脚石,部分原因是数据通常不是以长格式表示收集或存储的。因此,在绘图之前,通常需要使用 pandas 对数据进行 重塑。数据重塑可能是一项复杂的任务,需要对数据框结构有扎实的理解以及对 pandas API 的知识。花一些时间来培养这项技能可以带来巨大的回报。

然而,虽然 seaborn 在提供长格式数据时 强大,但几乎每个 seaborn 函数也会接受并绘制“宽格式”数据。你可以通过将一个对象传递给 seaborn 的 data= 参数而不指定其他绘图变量(x, y, …)来触发这一点。使用宽格式数据时你会受到限制:每个函数只能制作一种宽格式图。在大多数情况下,seaborn 会尝试匹配 matplotlib 或 pandas 对相同结构数据集的处理方式。将你的数据重塑为长格式将给你带来更大的灵活性,但在过程的早期快速查看你的数据可能会有所帮助,而 seaborn 试图使这成为可能。

理解你的数据应该如何表示——以及如果数据一开始很混乱时如何整理——对于高效和全面地使用seaborn非常重要,这在 用户指南 中有详细阐述。

seaborn 只能与 pandas 一起工作吗?#

一般来说,不需要:seaborn 在 数据集的表示方式 上非常灵活。

在大多数情况下,由多个类似向量的类型表示的 长格式数据 可以直接传递给 xy 或其他绘图参数。或者,您可以将向量类型的字典传递给 data,而不是 DataFrame。在宽格式数据绘图时,您可以使用 2D numpy 数组甚至嵌套列表在宽格式模式下进行绘图。

有一些较旧的函数(即 catplot()lmplot())确实需要你传递一个 pandas.DataFrame。但在这一点上,它们是例外,并且它们将在接下来的几个发布周期中获得更多的灵活性。

布局问题#

如何更改图形大小?#

这可能会比你希望的更复杂,部分原因是matplotlib中有多种改变图形大小的方法,部分原因是seaborn中存在 图形级/轴级 的区别。

在 matplotlib 中,你通常可以通过 rc 参数 设置所有图形的默认大小,特别是 figure.figsize。你也可以在创建图形时设置单个图形的大小(例如 plt.subplots(figsize=(w, h)))。如果你使用的是 seaborn 的轴级函数,这两种方法都会按预期工作。

图级函数既忽略默认的图形大小,又以不同的方式 参数化图形大小 。调用图级函数时,您可以向 height=aspect= 传递值,以(大致)设置每个 子图 的大小。这里的好处是,当您添加分面变量时,图形的大小会自动适应。但这可能会令人困惑。

幸运的是,有一种一致的方法可以在与函数无关的方式下设置确切的图形尺寸。与其在创建图形时设置图形尺寸,不如在绘图后通过调用 obj.figure.set_size_inches(...) 来修改它,其中 obj 是 matplotlib 轴(通常分配给 ax)或 seaborn 的 FacetGrid`(通常分配给 `g)。

注意,FacetGrid.figure 仅在 seaborn >= 0.11.2 中存在;在此之前,您需要访问 FacetGrid.fig

此外,如果你在制作png文件(或在Jupyter笔记本中),你可以——也许令人惊讶地——通过 改变dpi 来放大或缩小所有图表。

为什么seaborn没有在我指定的地方绘制图表?#

你已经明确创建了一个包含一个或多个子图的 matplotlib 图形,并尝试在其上绘制一个 seaborn 图,但你最终得到了一个额外的图形和一个空白的子图。也许你的代码看起来像

f, ax = plt.subplots()
sns.catplot(..., ax=ax)

这是一个 图级/轴级 的陷阱。图级函数总是创建它们自己的图形,所以你不能像使用轴级函数那样将它们指向一个现有的轴。大多数函数会在这种情况下警告你,建议使用适当的轴级函数,并忽略 ax= 参数。一些较旧的函数可能会将图形放在你想要的位置(因为它们在内部将 ax 传递给它们的轴级函数),同时仍然创建一个额外的图形。后者的行为应被视为一个错误,并且不应依赖于此。

目前的工作方式是,你可以自己设置 matplotlib 图形,或者你可以使用一个图形级别的函数,但不能同时进行两者。

为什么我不能在条形图/箱形图/条形图/小提琴图上画线?#

你正在尝试使用多个seaborn函数创建一个单一的图表,可能是通过在条形图或小提琴图上绘制线图或回归图。你期望线条穿过每个箱形图(等)的平均值,但它看起来对齐不正确,或者可能完全偏离到一边。

你正在尝试将 “分类图” 与其他类型的图表结合。如果你的 x 变量具有数值,这看起来应该可以工作。但请记住:seaborn 的分类图将分类轴上的唯一值映射到整数索引。因此,如果你的数据有唯一的 x 值 1, 6, 20, 94,相应的图表元素将在 0, 1, 2, 3 处绘制(并且刻度标签将更改为表示实际值)。

线图或回归图并不知道这一情况,因此它会使用实际的数值,图表将完全无法对齐。

目前,有两种方法可以解决这个问题。在需要绘制线条的情况下,你可以使用(名称有些误导的):func:pointplot 函数,这也是一个“分类”函数,并将使用相同的规则来绘制图表。如果这不能解决问题(例如,它在视觉上的灵活性不如 lineplot()),你可以自己实现从实际值到整数索引的映射,并以此方式绘制图表:

unique_xs = sorted(df["x"].unique())
sns.violinplot(data=df, x="x", y="y")
sns.lineplot(data=df, x=df["x"].map(unique_xs.index), y="y")

这将在计划的未来版本中变得更加容易,因为将有可能使分类函数将数值数据视为数值。(截至v0.12,仅在使用 native_scale=True 的情况下,在 stripplot()swarmplot() 中可能实现)。

如何移动图例?#

当对图表应用语义映射时,seaborn 会自动创建一个图例并将其添加到图形中。但图例位置的自动选择并不总是理想的。

使用 seaborn v0.11.2 或更高版本,请使用 move_legend() 函数。

在旧版本中,常见的模式是在绘图后调用 ax.legend(loc=...)。虽然这看起来是在移动图例,但实际上它是用一个新的图例 替换 了原有的图例,使用的是恰好附加在坐标轴上的带标签的艺术家。这在不同绘图类型之间 并不一致有效。而且它不会传播用于格式化多变量图例的图例标题或位置调整。

The move_legend() 函数实际上比其名称所暗示的更强大,它还可以在绘图后用于修改其他 `图例参数 <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html>`_(字体大小、句柄长度等)。

其他自定义#

我如何能更改关于图的某些内容?#

你想要制作一个非常特定的图表,而 seaborn 的默认设置无法满足你的需求。

定制 seaborn 图表基本上有四个层次的层次结构:

  1. 显式的 seaborn 函数参数

  2. 传递的 matplotlib 关键字参数

  3. Matplotlib 轴方法

  4. Matplotlib 艺术家方法

首先,阅读相关seaborn函数的API文档。每个函数都有很多参数(可能太多了),你或许可以使用seaborn自身的API来实现你想要的定制化。

但 seaborn 确实将很多自定义功能委托给了 matplotlib。大多数函数在其签名中都有 **kwargs,这将捕获额外的关键字参数并将它们传递给底层的 matplotlib 函数。例如,scatterplot() 有许多参数,但你也可以使用任何有效的 matplotlib.axes.Axes.scatter() 的关键字参数,因为它在内部调用了该方法。

通过传递关键字参数,您可以自定义表示数据的艺术家,但通常您会希望自定义图形的其他方面,例如标签、刻度和标题。您可以通过调用seaborn绘图函数返回的对象上的方法来实现这一点。根据您调用的是 轴级还是图形级函数,这可能是一个 matplotlib.axes.Axes 对象或一个seaborn包装器(例如 seaborn.FacetGrid)。这两种类型的对象都有许多方法,您可以调用这些方法来自定义图形的几乎任何方面。最简单的方法通常是调用 matplotlib.axes.Axes.set()seaborn.FacetGrid.set(),它们允许您一次修改多个属性,例如:

ax = sns.scatterplot(...)
ax.set(
    xlabel="The x label",
    ylabel="The y label",
    title="The title"
    xlim=(xmin, xmax),
    xticks=[...],
    xticklabels=[...],
)

最后,最深层次的定制可能需要你深入到 matplotlib 的轴(axes)中,调整存储在其上的艺术家(artists)。这些艺术家将位于艺术家列表中,例如 ax.linesax.collectionsax.patches 等。

警告: matplotlib 和 seaborn 都不认为它们的绘图函数生成的特定艺术家是稳定的API的一部分。由于无法优雅地警告即将对艺术家类型或其存储顺序的更改,与这些属性交互的代码可能会意外中断。尽管如此,seaborn 确实尽力避免这种变化。

等等,我也需要学习如何使用matplotlib吗?#

这真的取决于你需要多少定制。你当然可以在主要或仅与 seaborn API 交互的情况下进行大量的探索性数据分析。但是,如果你在为演示或出版物打磨图表,你可能会发现自己需要至少了解一点 matplotlib 的工作原理。Matplotlib 非常灵活,如果你深入研究,它可以让你控制图表的每一个细节。

Seaborn 最初设计时考虑到了通过一个非常高层次的 API 来处理一组定义明确的操作,同时让用户在需要额外定制时可以“下降”到 matplotlib。这种组合非常强大,如果你已经知道如何使用 matplotlib,它会工作得相当好。但随着 seaborn 功能的增加,首先学习 seaborn 变得更加可行。在这种情况下,切换 API 的需求往往会变得更加令人困惑/沮丧。这促使了 seaborn 新的 对象接口 的开发,该接口旨在为高层次和低层次的图形规范提供一个更连贯的 API。希望随着它的成熟,它将缓解“两个库的问题”。

话虽如此,matplotlib 提供的深度控制水平确实无法超越,所以如果你关心做非常具体的事情,学习它真的值得。

如何将 seaborn 与 matplotlib 的面向对象接口一起使用?#

你更喜欢使用 matplotlib 的显式或 “面向对象” 接口,因为它使你的代码更容易理解和维护。但面向对象接口由 matplotlib 对象上的方法组成,而 seaborn 提供了独立的函数。

在另一种情况下,记住 图级/轴级 的区别将会很有帮助。

轴级函数可以像任何 matplotlib 轴方法一样使用,但不是调用 ax.func(...),而是调用 func(..., ax=ax)。它们还会返回轴对象(如果当前 matplotlib 的全局状态中没有活动的图形,它们可能会创建该对象)。即使你不是从 matplotlib.pyplot.figure()matplotlib.pyplot.subplots() 开始,你也可以使用该对象上的方法进一步自定义绘图:

ax = sns.histplot(...)
ax.set(...)

图级函数 不能指向一个已存在的图 ,但它们确实将 matplotlib 对象存储在它们返回的 FacetGrid 对象上(seaborn 文档总是将其分配给一个名为 g 的变量)。

如果你的图级函数只创建了一个子图,你可以直接访问它:

g = sns.displot(...)
g.ax.set(...)

对于多个子图,你可以使用 :attr:`FacetGrid.axes`(这总是一个轴的二维数组)或 :attr:`FacetGrid.axes_dict`(它将行/列键映射到相应的 matplotlib 对象):

g = sns.displot(..., col=...)
for col, ax in g.axes_dict.items():
    ax.set(...)

但是,如果你要批量设置所有子图的属性,请使用 FacetGrid.set() 方法,而不是迭代单个轴:

g = sns.displot(...)
g.set(...)

要访问底层的 matplotlib 图形,请在 seaborn >= 0.11.2 上使用 FacetGrid.figure`(或在其他版本上使用 :attr:`FacetGrid.fig)。

我可以在条形图上标注条形值吗?#

seaborn 中没有内置这样的功能,但 matplotlib v3.4.0 增加了一个便捷函数(matplotlib.axes.Axes.bar_label()),使得实现相对容易。以下是几个示例;请注意,您需要根据条形图是来自 图级还是轴级函数 使用不同的方法:

# Axes-level
ax = sns.histplot(df, x="x_var")
for bars in ax.containers:
    ax.bar_label(bars)

# Figure-level, one subplot
g = sns.displot(df, x="x_var")
for bars in g.ax.containers:
    g.ax.bar_label(bars)

# Figure-level, multiple subplots
g = sns.displot(df, x="x_var", col="col_var)
for ax in g.axes.flat:
    for bars in ax.containers:
        ax.bar_label(bars)

我可以在暗模式下使用seaborn吗?#

Seaborn 没有直接支持这一点,但 matplotlib 有一个 “dark_background” 样式表,你可以使用,例如:

sns.set_theme(style="ticks", rc=plt.style.library["dark_background"])

请注意,“dark_background”会将默认调色板更改为“Set2”,这将覆盖您在 set_theme() 中定义的任何调色板。如果您想使用不同的调色板,您需要单独调用 sns.set_palette()。默认的 :doc:`seaborn 调色板 </tutorial/color_palettes>`(“deep”)在深色背景下对比度较差,因此您最好使用“muted”、“bright”或“pastel”。

统计查询#

我可以访问seaborn的统计变换结果吗?#

因为 seaborn 在构建图表时执行了一些统计操作(聚合、自举、拟合回归模型),一些用户希望访问它计算的统计数据。这是不可能的:明确认为 seaborn(一个可视化库)提供查询统计模型的 API 超出了其范围。

如果你只是想勤奋地验证 seaborn 是否正确地完成了工作(或者它是否与你的代码匹配),它是开源的,所以请随意阅读代码。或者,因为它是 Python,你可以调用计算统计数据的私有方法(但不要在生产代码中这样做)。但不要期望 seaborn 提供更多属于 scipystatsmodels 的功能。

我可以显示标准误差而不是置信区间吗?#

自 v0.12 起,在大多数地方都可以使用新的 errorbar API 实现此功能(更多详情请参阅 教程)。

为什么KDE图的y轴会超过1?#

你已经使用 kdeplot() 估计了数据的概率分布,但y轴超过了1。概率不是应该小于等于1吗?这是不是个bug?

这不是一个错误,但它是一个常见的混淆(关于核密度图和更广泛的概率分布)。连续概率分布由 概率密度函数 定义,kdeplot() 估计该函数。概率密度函数 输出 概率:连续随机变量可以取无限多个值,因此观察到任何 特定 值的概率是无限小的。你只能有意义地讨论观察到落在某个 范围 内的值的概率。观察到落在所有可能值范围内的值的概率是1。同样,概率密度函数被归一化,使得其下的面积(即函数在其定义域上的积分)等于1。如果可能值的范围很小,曲线将不得不超过1以实现这一点。

常见的好奇心#

为什么 seaborn 被导入为 sns#

这是一个对库的 同名 的隐晦引用,但你也可以将其视为“seaborn 命名空间”。

为什么 ggplot 比 seaborn 好得多?#

好问题。可能是因为你可以经常使用“geom”这个词,而且说起来很有趣。“Geom”。“Geeeeeooom”。