>>> from env_helper import info; info()
页面更新时间: 2024-01-06 20:44:36
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

5.4. seaborn.objects 接口

seaborn.objects命名空间是在 0.12 版本中引入的,作为制作海边图的全新接口。它提供了一个更加一致和灵活的 API,包括一组用于转换和绘制数据的可组合类。与seaborn现有功能相比,新界面旨在支持端到端的绘图规范和自定义,而无需下拉到 matplotlib(尽管在必要时仍然可以这样做)。

注意

对象接口目前处于实验阶段,尚未完成。它足够稳定,可以认真使用,但肯定有一些粗糙的边缘和缺失的功能。

5.4.1. 指定绘图和映射数据

应按照以下约定导入对象接口:

>>> import seaborn.objects as so

seaborn.objects命名空间将提供对所有相关类的访问。最重要的是Plot。您可以通过实例化Plot对象并调用其方法来指定绘图。让我们看一个简单的例子:

>>> import seaborn as sns
>>>
>>> sns.set_theme()
>>>
>>> penguins = sns.load_dataset("penguins")
>>> (
>>>     so.Plot(penguins, x="bill_length_mm", y="bill_depth_mm")
>>>     .add(so.Dot())
>>> )
_images/sec04_interface_4_0.png

生成散点图的这段代码看起来应该相当熟悉。与使用seaborn.scatterplot()时一样,我们传递了一个整洁的数据框(penguins),并将其中的两列分配给该图的xy坐标。但这里我们不是从图表类型开始,然后添加一些数据赋值,而是从数据赋值开始,然后添加一个图形元素。

设置属性

Dot类是Mark的一个例子:一个以图形方式表示数据值的对象。每个标记都有许多属性,可以通过设置来改变其外观:

>>> (
>>>     so.Plot(penguins, x="bill_length_mm", y="bill_depth_mm")
>>>     .add(so.Dot(color="g", pointsize=4))
>>> )
_images/sec04_interface_6_0.png

映射属性

与 seaborn 的函数一样,也可以将数据值映射到各种图形属性:

>>> (
>>>     so.Plot(
>>>         penguins, x="bill_length_mm", y="bill_depth_mm",
>>>         color="species", pointsize="body_mass_g",
>>>     )
>>>     .add(so.Dot())
>>> )
_images/sec04_interface_8_0.png

虽然这个基本功能并不新颖,但与函数API的一个重要区别是,属性是使用直接设置属性的相同参数名称进行映射的(而不是使用huecolor等)。重要的是属性在哪里定义:在初始化Dot时传递一个值将直接设置它,而在设置Plot时分配一个变量将映射相应的数据。

除了这种差异之外,对象接口还允许映射更广泛的标记属性:

>>> (
>>>     so.Plot(
>>>         penguins, x="bill_length_mm", y="bill_depth_mm",
>>>         edgecolor="sex", edgewidth="body_mass_g",
>>>     )
>>>     .add(so.Dot(color=".8"))
>>> )
_images/sec04_interface_10_0.png

定义组

Dot标记独立地表示每个数据点,因此将变量赋值给属性仅具有更改每个点的外观的效果。对于对观测值进行分组或连接的标记(例如Line),它还确定不同图形元素的数量:

>>>
>>> healthexp = sns.load_dataset("healthexp")
>>> #sns.load_dataset("tips",data_home='seaborn-data',cache=True)
>>> (
>>>     so.Plot(healthexp, x="Year", y="Life_Expectancy", color="Country")
>>>     .add(so.Line())
>>> )
_images/sec04_interface_12_0.png

还可以在不更改任何视觉属性的情况下定义分组,方法是使用group

>>> (
>>>     so.Plot(healthexp, x="Year", y="Life_Expectancy", group="Country")
>>>     .add(so.Line())
>>> )
_images/sec04_interface_14_0.png

5.4.2. 在绘图之前转换数据

统计转换

与许多 seaborn 函数一样,objects 接口支持统计转换。这些由Stat对象执行,例如Agg

>>> (
>>>     so.Plot(penguins, x="species", y="body_mass_g")
>>>     .add(so.Bar(), so.Agg())
>>> )
_images/sec04_interface_16_0.png

在函数界面中,统计转换可以使用一些可视化表示(例如seaborn.barplot()),但不能使用其他表示(例如seaborn.scatterplot())。对象接口更清晰地分离了表示和转换,允许你组合MarkStat对象:

>>> (
>>>     so.Plot(penguins, x="species", y="body_mass_g")
>>>     .add(so.Dot(pointsize=10), so.Agg())
>>> )
_images/sec04_interface_18_0.png

通过映射属性形成组时,Stat转换将分别应用于每个组:

>>> (
>>>     so.Plot(penguins, x="species", y="body_mass_g", color="sex")
>>>     .add(so.Dot(pointsize=10), so.Agg())
>>> )
_images/sec04_interface_20_0.png

解决过度绘图问题

一些seaborn函数还具有自动解决过度绘图的机制,例如一旦分配了色调seaborn.barplot()就会“回避”条形图。对象接口的默认行为不那么复杂。默认情况下,表示多个组的条形图将重叠:

>>> (
>>>     so.Plot(penguins, x="species", y="body_mass_g", color="sex")
>>>     .add(so.Bar(), so.Agg())
>>> )
_images/sec04_interface_22_0.png

然而,可以将bar标记与Agg属性和第二次转换组合在一起,由Dodge实现:

>>> (
>>>     so.Plot(penguins, x="species", y="body_mass_g", color="sex")
>>>     .add(so.Bar(), so.Agg(), so.Dodge())
>>> )
_images/sec04_interface_24_0.png

Dodge类是Move转换的一个例子,它类似于Stat,但只调整x和y坐标。Move类可以应用于任何标记,并且不需要先使用Stat:

>>> (
>>>     so.Plot(penguins, x="species", y="body_mass_g", color="sex")
>>>     .add(so.Dot(), so.Dodge())
>>> )
_images/sec04_interface_26_0.png

还可以按顺序应用多个Move操作:

>>> (
>>>     so.Plot(penguins, x="species", y="body_mass_g", color="sex")
>>>     .add(so.Dot(), so.Dodge(), so.Jitter(.3))
>>> )
_images/sec04_interface_28_0.png

通过转换创建变量

Agg 统计要求已经定义了xy,但是也可以通过统计转换创建变量。例如,Hist状态只需要定义xy中的一个,它将通过计数观察来创建另一个:

>>> (
>>>     so.Plot(penguins, x="species")
>>>     .add(so.Bar(), so.Hist())
>>> )
_images/sec04_interface_30_0.png

当给定数值数据时,Hist统计信息还将创建新x值(通过分箱):

>>> (
>>>     so.Plot(penguins, x="flipper_length_mm")
>>>     .add(so.Bars(), so.Hist())
>>> )

请注意我们如何使用Bar ,而不是用于具有连续x轴的Bar图。这两个标记是相关的,但具Bar有不同的默认值,并且更适合连续直方图。它还会产生一个不同的、更高效的 matplotlib 艺术家。您会在其他地方找到单数/复数标记的模式。复数版本通常针对标记数量较多的情况进行优化。

某些转换同时接受xy,但为每个坐标添加间隔数据。这对于在聚合后绘制误差线尤其重要:

>>> (
>>>     so.Plot(penguins, x="body_mass_g", y="species", color="sex")
>>>     .add(so.Range(), so.Est(errorbar="sd"), so.Dodge())
>>>     .add(so.Dot(), so.Agg(), so.Dodge())
>>> )
_images/sec04_interface_34_0.png

定向标记和变换

在聚合、躲避和绘制条形图时,xy变量的处理方式不同。每个操作都有一个方向的概念。Plot尝试根据变量的数据类型自动确定方向。例如,如果我们翻转speciesbody_mass_g 的赋值,我们将得到相同的图,但方向是水平的:

>>> (
>>>     so.Plot(penguins, x="body_mass_g", y="species", color="sex")
>>>     .add(so.Bar(), so.Agg(), so.Dodge())
>>> )
_images/sec04_interface_36_0.png

有时,正确的方向是模棱两可的,例如当xy 变量都是数字时。在这些情况下,可以通过将orient参数传递给 Plot.add()

>>> tips = sns.load_dataset("tips",data_home='seaborn-data',cache=True)
>>> (
>>>     so.Plot(tips, x="total_bill", y="size", color="time")
>>>     .add(so.Bar(), so.Agg(), so.Dodge(), orient="y")
>>> )
_images/sec04_interface_38_0.png

5.4.3. 构建和显示绘图

到目前为止,大多数示例都生成了一个子图,上面只有一种标记。但Plot并不局限于此。

添加多个图层

可以通过重复调用Plot.add()来创建更复杂的单子图图形。每次调用它时,它都会在图中定义一个图层。例如,我们可能想要添加一个散点图(现在使用Dots ),然后添加回归拟合:

>>> (
>>>     so.Plot(tips, x="total_bill", y="tip")
>>>     .add(so.Dots())
>>>     .add(so.Line(), so.PolyFit())
>>> )
_images/sec04_interface_40_0.png

构造函数中Plot定义的变量映射将用于所有层:

>>> (
>>>     so.Plot(tips, x="total_bill", y="tip", color="time")
>>>     .add(so.Dots())
>>>     .add(so.Line(), so.PolyFit())
>>> )
_images/sec04_interface_42_0.png

特定于图层的映射

您还可以定义映射,使其仅在特定图层中使用。这是通过在相关层的调用Plot.add中定义映射来实现的:

>>> (
>>>     so.Plot(tips, x="total_bill", y="tip")
>>>     .add(so.Dots(), color="time")
>>>     .add(so.Line(color=".2"), so.PolyFit())
>>> )
_images/sec04_interface_44_0.png

或者,为整个绘图定义图层,但通过将变量设置为None以下值将其从特定图层中删除:

>>> (
>>>     so.Plot(tips, x="total_bill", y="tip", color="time")
>>>     .add(so.Dots())
>>>     .add(so.Line(color=".2"), so.PolyFit(), color=None)
>>> )
_images/sec04_interface_46_0.png

总而言之,有三种方法可以指定标记属性的值: 1. 在所有层中映射变量 2. 在特定层中映射变量 3. 直接设置属性:

_images/objects_interface_48_0.svg

分面和配对子图

与 seaborn 的图形级函数(seaborn.displot()seaborn.catplot() 等)一样,Plot接口也可以生成具有多个“面”的图形,或包含数据子集的子图。这是通过Plot.facet()方法完成的:

>>> (
>>>     so.Plot(penguins, x="flipper_length_mm")
>>>     .facet("species")
>>>     .add(so.Bars(), so.Hist())
>>> )
_images/sec04_interface_48_0.png

使用应用于定义绘图的列和/或行的变量进行调用Plot.facet()

>>> (
>>>     so.Plot(penguins, x="flipper_length_mm")
>>>     .facet(col="species", row="sex")
>>>     .add(so.Bars(), so.Hist())
>>> )
_images/sec04_interface_50_0.png

您可以通过“包装”另一个维度来使用具有更多级别的变量进行分面:

>>> (
>>>     so.Plot(healthexp, x="Year", y="Life_Expectancy")
>>>     .facet(col="Country", wrap=3)
>>>     .add(so.Line())
>>> )
_images/sec04_interface_52_0.png

除非您明确排除它们,否则所有图层都将被分面,这可以 有助于为每个子图提供额外的上下文:

>>> (
>>>     so.Plot(healthexp, x="Year", y="Life_Expectancy")
>>>     .facet("Country", wrap=3)
>>>     .add(so.Line(alpha=.3), group="Country", col=None)
>>>     .add(so.Line(linewidth=3))
>>> )
_images/sec04_interface_54_0.png

生成子图的另一种方法是Plot.pair() 。就像seaborn.PairGrid 一样,这将绘制每个子图上的所有数据,对 x 和/或 y 坐标使用不同的变量:

>>> (
>>>     so.Plot(penguins, y="body_mass_g", color="species")
>>>     .pair(x=["bill_length_mm", "bill_depth_mm"])
>>>     .add(so.Dots())
>>> )
_images/sec04_interface_56_0.png

您可以组合分面和配对,只要操作在相反的维度上添加子图:

>>> (
>>>     so.Plot(penguins, y="body_mass_g", color="species")
>>>     .pair(x=["bill_length_mm", "bill_depth_mm"])
>>>     .facet(row="sex")
>>>     .add(so.Dots())
>>> )
_images/sec04_interface_58_0.png

与 matplotlib 集成

在某些情况下,您可能希望多个子图出现在一个结构比 Plot.facet()Plot.pair()可以提供的更复杂的图形中。当前的解决方案是将图形设置委托给 matplotlib,并提供Plot应与Plot.on()方法一起使用的 matplotlib 对象。此对象可以是matplotlib.axes.Axesmatplotlib.figure.Figurematplotlib.figure.SubFigure ;后者对于构建定制的子图布局最有用:

>>> import matplotlib.pyplot as plt
>>> f = plt.Figure(figsize=(8, 4))
>>> sf1, sf2 = f.subfigures(1, 2)
>>> (
>>>     so.Plot(penguins, x="body_mass_g", y="flipper_length_mm")
>>>     .add(so.Dots())
>>>     .on(sf1)
>>>     .plot()
>>> )
>>> (
>>>     so.Plot(penguins, x="body_mass_g")
>>>     .facet(row="sex")
>>>     .add(so.Bars(), so.Hist())
>>>     .on(sf2)
>>>     .plot()
>>> )
_images/sec04_interface_60_0.png

构建和显示绘图

需要知道的重要一点是,Plot方法克隆调用它们的对象并返回该克隆,而不是就地更新对象。这意味着您可以定义一个通用的绘图规范,然后在其上生成多个变体。

因此,请遵循以下基本规范:

>>> p = so.Plot(healthexp, "Year", "Spending_USD", color="Country")

我们可以用它来绘制线图:

>>> p.add(so.Line())
_images/sec04_interface_64_0.png

或者可能是堆积面积图:

>>> p.add(so.Area(), so.Stack())
_images/sec04_interface_66_0.png

Plot方法是完全声明性的。调用它们会更新情节规范,但实际上并没有进行任何绘图。这样做的一个后果是,方法可以以任何顺序调用,其中许多方法可以被多次调用。

剧情实际渲染时间是什么时候?Plot针对笔记本环境进行了优化。当Plot显示在Jupyter REPL中时,会自动触发渲染。这就是为什么我们在上面的例子中没有看到任何东西,在这个例子中,我们定义了一个Plot,但是将它赋值给p,而不是让它返回给REPL。

要查看笔记本中的绘图,要么从单元格的最后一行返回,要么调用Jupyter在对象上的内置display函数。matplotlib.pyplot。但是您可以通过调用Plot.show()在其他上下文中使用它的图形显示机制。

还可以通过调用plot.save()将绘图保存到文件(或缓冲区)。

5.4.4. 自定义外观

新的界面旨在通过Plot支持深度定制,减少切换设备和直接使用matplotlib功能的需要。(但请耐心等待;并非实现这一目标所需的所有功能都已实现!)

参数化的尺度

所有与数据相关的属性都由Scale概念和Plot.scale()方法控制。此方法接受几种不同类型的参数。一种最接近于在matplotlib中使用尺度的可能性是传递一个转换坐标的函数的名称:

>>> diamonds = sns.load_dataset("diamonds")
>>> (
>>>     so.Plot(diamonds, x="carat", y="price")
>>>     .add(so.Dots())
>>>     .scale(y="log")
>>> )
_images/sec04_interface_68_0.png

Plot.scale()还可以控制语义属性的映射,如color,你可以直接向它传递任何参数,就像在seaborn的函数接口中传递给palette参数一样:

>>> (
>>>     so.Plot(diamonds, x="carat", y="price", color="clarity")
>>>     .add(so.Dots())
>>>     .scale(color="flare")
>>> )
_images/sec04_interface_70_0.png

另一种选择是提供(min, max)值的元组,控制比例应映射到的范围。这既适用于数值属性,也适用于颜色:

>>> (
>>>     so.Plot(diamonds, x="carat", y="price", color="clarity", pointsize="carat")
>>>     .add(so.Dots())
>>>     .scale(color=("#88c", "#555"), pointsize=(2, 10))
>>> )
_images/sec04_interface_72_0.png

对于其他控制,您可以传递Scale对象。有几种不同类型的Scale,每种都有适当的参数。例如,Continuous允许你定义输入域(norm),输出范围(values),以及它们之间的映射函数(trans),而Nominal允许你指定顺序:

>>> (
>>>     so.Plot(diamonds, x="carat", y="price", color="carat", marker="cut")
>>>     .add(so.Dots())
>>>     .scale(
>>>         color=so.Continuous("crest", norm=(0, 3), trans="sqrt"),
>>>         marker=so.Nominal(["o", "+", "x"], order=["Ideal", "Premium", "Good"]),
>>>     )
>>> )
/usr/lib/python3/dist-packages/seaborn/_core/properties.py:370: RuntimeWarning: invalid value encountered in cast
  ixs = np.asarray(x, np.intp)
_images/sec04_interface_74_1.png

自定义图例和刻度

Scale对象还用于指定哪些值应在图例中作为刻度标签显示,以及它们的显示方式。例如,Continuous.tick()方法允许你控制刻度的密度或位置,Continuous.label()方法允许你修改格式:

>>> (
>>>     so.Plot(diamonds, x="carat", y="price", color="carat")
>>>     .add(so.Dots())
>>>     .scale(
>>>         x=so.Continuous().tick(every=0.5),
>>>         y=so.Continuous().label(like="${x:.0f}"),
>>>         color=so.Continuous().tick(at=[1, 2, 3, 4]),
>>>     )
>>> )
_images/sec04_interface_76_0.png

自定义限制、标签和标题

Plot具有多种简单自定义方法,包括Plot.label()Plot.limit()Plot.share()

>>> (
>>>     so.Plot(penguins, x="body_mass_g", y="species", color="island")
>>>     .facet(col="sex")
>>>     .add(so.Dot(), so.Jitter(.5))
>>>     .share(x=False)
>>>     .limit(y=(2.5, -.5))
>>>     .label(
>>>         x="Body mass (g)", y="",
>>>         color=str.capitalize,
>>>         title="{} penguins".format,
>>>     )
>>> )
_images/sec04_interface_78_0.png

主题定制

最后,Plot通过Plot.theme方法支持与数据无关的主题。目前,此方法接受 matplotlib rc 参数的字典。您可以直接设置它们和/或从 seaborn 的主题函数中传递一组参数:

>>> from seaborn import axes_style
>>> theme_dict = {**axes_style("whitegrid"), "grid.linestyle": ":"}
>>> so.Plot().theme(theme_dict)
_images/sec04_interface_80_0.png

要更改所有Plot实例的主题,请更新Plot.config设置:

so.Plot.config.theme.update(theme_dict)