常见问题解答#

这是一个关于 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,而不是 pip install <package>

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

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

您肯定在正确的位置安装了 seaborn,但导入它会导致长时间的回溯和令人困惑的错误消息,可能是类似 ImportError: DLL load failed: The specified module could not be found 的内容。

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

导致这些问题最常见的罪魁祸首是 scipy,它有许多编译组件。从 seaborn 版本 0.12 开始,scipy 是一个可选的依赖项,这将有助于减少这些问题出现的频率。

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

您正在调用 seaborn 函数(也许是在终端或带有集成 IPython 控制台的 IDE 中),但没有看到任何图表。)

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

然而,seaborn 文档中的大多数示例都没有这一行,因为有几种方法可以避免使用它。在使用“inline”(默认)或“widget”后端(由 “IPympl” 定义)的 Jupyter Notebook 中,matplotlib.pyplot.show() 会在执行单元格后自动调用,因此任何图形都会出现在单元格的输出中。您还可以通过在任何 Jupyter 或 IPython 接口中执行 %matplotlib 或在 Python 中的任何位置调用 matplotlib.pyplot.ion() 来激活更具交互性的体验。这两种方法都将配置 matplotlib,以便在每个绘图命令后显示或更新图形。

为什么每个笔记本单元格之后都会打印一些东西?#

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

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

  • 始终将最后一条语句的结果分配给一个变量(例如 ax = sns.histplot(...))。

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

  • 在每个单元格末尾添加一个没有返回值的函数(例如 plt.show(),它不需要,也不会造成问题)。

  • 添加 单元格元数据标签,如果您要将 Notebook 转换为其他表示形式。

为什么 Jupyter Notebook 中的图表看起来模糊?#

默认的“inline”后端(由 IPython 定义)对图形输出使用异常低的 dpi(“每英寸点数”)。这是一种节省空间的措施:较低的 dpi 图形占用的磁盘空间较少。(此外,较低的 dpi 内联图形在物理上看起来较小,因为它们被表示为 PNG,而 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 生成具有“无限分辨率”的 矢量图形。缺点是文件大小现在将随图表中艺术家数量和复杂程度的增加而增加,并且在某些情况下(例如大型散点图矩阵),加载会影响浏览器响应能力。

棘手的概念#

“图级别”和“轴级别”是什么意思?#

您可能在 seaborn 文档、StackOverflow 答案或 GitHub 线程中遇到了“图级别”或“轴级别”一词,但您不理解它的意思。

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

  • “轴级别”函数,它们将绘制到单个子图上,该子图可能在调用函数时存在,也可能不存在。

  • “图级别”函数,它们在内部创建一个 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 对你的数据集需要如何表示非常灵活

在大多数情况下,长格式数据(由多个向量类型表示)可以直接传递给x, y,或其他绘图参数。或者你可以将向量类型的字典传递给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 的分类图将分类轴上的唯一值映射到整数索引。因此,如果你的数据具有唯一值为 1、6、20、94 的x值,则相应的绘图元素将绘制在 0、1、2、3 处(并且刻度标签将被更改以代表实际值)。

折线图或回归图不知道发生了这种情况,因此它将使用实际的数值,并且绘图将完全不对齐。

目前,有两种方法可以解决这个问题。如果您想绘制一条线,可以使用(名称略有误导的)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 开始,它仅在stripplot()swarmplot() 中使用 native_scale=True 是可能的)。

如何移动图例?#

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

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

在旧版本中,一种常见的模式是在绘图后调用 ax.legend(loc=...)。虽然这似乎移动了图例,但实际上它替换了图例,使用任何碰巧附加到坐标轴的带标签的艺术品。这在不同的图表类型中并不始终有效。它不会传播用于格式化多变量图例的图例标题或定位调整。

move_legend() 函数实际上比其名称暗示的功能更强大,它也可以用于在绘图后修改其他图例参数(字体大小、句柄长度等)。

其他自定义#

我如何更改有关图形的内容?#

您想制作一个非常具体的图表,而 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 坐标轴并调整存储在其上的艺术家。这些将在艺术家列表中,例如 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(...)

对于多个子图,您可以使用 FacetGrid.axes(它始终是坐标轴的二维数组)或 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(或在任何其他版本上使用 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()。默认的seaborn 调色板(“deep”)与深色背景的对比度很差,因此最好使用“muted”、“bright”或“pastel”。

统计查询#

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

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

如果您只是想谨慎行事,并验证 seaborn 是否按预期运行(或与您自己的代码匹配),它是一个开源库,您可以随意阅读代码。或者,因为它是用 Python 编写的,您可以调用计算统计数据的私有方法(只是不要在生产代码中这样做)。但不要指望 seaborn 提供更适合 scipystatsmodels 的功能。

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

从 v0.12 开始,这在大多数地方都是可能的,使用新的 errorbar API(有关更多详细信息,请参阅 教程)。

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

您使用 kdeplot()估计了数据的概率分布,但 y 轴超过了 1。概率不是以 1 为界限的吗?这是一个错误吗?

这不是错误,但这是一个常见的误解(关于核密度估计和更广泛的概率分布)。连续概率分布由 概率密度函数 定义,kdeplot() 对其进行估计。概率密度函数输出概率:连续随机变量可以取无限多个值,因此观察到任何特定值的概率是无限小的。您只能有意义地谈论观察到落在某个范围内的值的概率。观察到落在所有可能值的完整范围内的值的概率为 1。同样,概率密度函数被归一化,使得其下方的面积(即函数在其域上的积分)等于 1。如果可能值的范围很小,则曲线必须超过 1 才能使这成为可能。

常见问题#

为什么 seaborn 被导入为 sns#

这是一个对该库同名的模糊引用,但您也可以将其视为“seaborn 命名空间”。

为什么 ggplot 比 seaborn 好得多?#

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