seaborn.objects 接口#
The seaborn.objects
命名空间是在版本 0.12 中引入的,作为一种全新的 seaborn 绘图接口。它提供了一个更加一致和灵活的 API,包含一个可组合的类集合,用于转换和绘制数据。与现有的 seaborn
函数相比,新接口旨在支持端到端绘图规范和自定义,而无需降级到 matplotlib(尽管在必要时仍然可以这样做)。
注意
objects 接口目前处于实验阶段,尚未完成。它足够稳定,可以用于严肃用途,但肯定存在一些粗糙的边缘和缺失的功能。
指定绘图并映射数据#
objects 接口应使用以下约定进行导入
import seaborn.objects as so
The seaborn.objects
命名空间将提供对所有相关类的访问权限。最重要的是 Plot
。你可以通过实例化一个 Plot
对象并调用它的方法来指定绘图。让我们看一个简单的例子
(
so.Plot(penguins, x="bill_length_mm", y="bill_depth_mm")
.add(so.Dot())
)
这段代码将生成一个散点图,看起来应该相当熟悉。就像使用 seaborn.scatterplot()
一样,我们传递了一个整洁的数据框 (penguins
),并将它的两个列分配给绘图的 x
和 y
坐标。但是,我们不是从图表类型开始,然后添加一些数据分配,而是从数据分配开始,然后添加一个图形元素。
设置属性#
The Dot
类是 Mark
的一个例子:一个以图形方式表示数据值的物体。每个标记将具有一些可以设置的属性来改变它的外观
(
so.Plot(penguins, x="bill_length_mm", y="bill_depth_mm")
.add(so.Dot(color="g", pointsize=4))
)
映射属性#
与 seaborn 的函数一样,也可以将数据值映射到各种图形属性
(
so.Plot(
penguins, x="bill_length_mm", y="bill_depth_mm",
color="species", pointsize="body_mass_g",
)
.add(so.Dot())
)
虽然这种基本功能并不新鲜,但与函数 API 的一个重要区别在于,属性使用与直接设置它们时相同的参数名称进行映射(而不是使用 hue
对比 color
等)。重要的是属性定义的位置:在初始化 Dot
时传递一个值将直接设置它,而在设置 Plot
时分配一个变量将映射相应的数据。
除了这种差异,objects 接口还允许将更广泛的标记属性映射到
(
so.Plot(
penguins, x="bill_length_mm", y="bill_depth_mm",
edgecolor="sex", edgewidth="body_mass_g",
)
.add(so.Dot(color=".8"))
)
定义组#
The Dot
标记独立地表示每个数据点,因此将变量分配给属性只会改变每个点的外观。对于对观察结果进行分组或连接的标记,例如 Line
,它还会确定不同的图形元素的数量
(
so.Plot(healthexp, x="Year", y="Life_Expectancy", color="Country")
.add(so.Line())
)
也可以使用 group
定义一个组,而不会改变任何视觉属性
(
so.Plot(healthexp, x="Year", y="Life_Expectancy", group="Country")
.add(so.Line())
)
在绘图之前转换数据#
统计转换#
与许多 seaborn 函数一样,objects 接口支持统计转换。这些转换由 Stat
对象执行,例如 Agg
(
so.Plot(penguins, x="species", y="body_mass_g")
.add(so.Bar(), so.Agg())
)
在函数接口中,统计转换可以通过一些视觉表示(例如 seaborn.barplot()
)进行,但不能通过其他视觉表示进行(例如 seaborn.scatterplot()
)。objects 接口更清晰地将表示和转换分离,允许你组合 Mark
和 Stat
对象
(
so.Plot(penguins, x="species", y="body_mass_g")
.add(so.Dot(pointsize=10), so.Agg())
)
在通过映射属性形成组时,Stat
转换将分别应用于每个组
(
so.Plot(penguins, x="species", y="body_mass_g", color="sex")
.add(so.Dot(pointsize=10), so.Agg())
)
解决过绘图#
一些 seaborn 函数也有机制可以自动解决过绘图问题,例如 seaborn.barplot()
在分配了 hue
后会“躲避”条形图。objects 接口的默认行为不那么复杂。默认情况下,表示多个组的条形图会重叠
(
so.Plot(penguins, x="species", y="body_mass_g", color="sex")
.add(so.Bar(), so.Agg())
)
但是,可以将 Bar
标记与 Agg
统计量和第二个转换(由 Dodge
实现)组合起来
(
so.Plot(penguins, x="species", y="body_mass_g", color="sex")
.add(so.Bar(), so.Agg(), so.Dodge())
)
The Dodge
类是 Move
转换的一个例子,它类似于 Stat
,但只调整 x
和 y
坐标。The Move
类可以与任何标记一起使用,并且不需要先使用 Stat
(
so.Plot(penguins, x="species", y="body_mass_g", color="sex")
.add(so.Dot(), so.Dodge())
)
也可以按顺序应用多个 Move
操作
(
so.Plot(penguins, x="species", y="body_mass_g", color="sex")
.add(so.Dot(), so.Dodge(), so.Jitter(.3))
)
通过转换创建变量#
The Agg
统计量要求 x
和 y
已经定义,但变量也可以通过统计转换创建。例如,The Hist
统计量只需要定义 x
或 y
中的一个,它将通过统计观察结果来创建另一个
(
so.Plot(penguins, x="species")
.add(so.Bar(), so.Hist())
)
The Hist
统计量在给定数值数据时还会创建新的 x
值(通过分箱)
(
so.Plot(penguins, x="flipper_length_mm")
.add(so.Bars(), so.Hist())
)
请注意,我们使用了 Bars
,而不是 Bar
,来绘制具有连续 x
轴的图形。这两个标记是相关的,但是 Bars
具有不同的默认值,并且更适合连续直方图。它还生成一个不同且更高效的 matplotlib 艺术家。您将在其他地方找到单数/复数标记的模式。复数版本通常针对具有大量标记的情况进行了优化。
一些变换接受 x
和 y
,但为每个坐标添加间隔数据。这对于在聚合后绘制误差条特别有用。
(
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())
)
标记和变换的方向#
在聚合、躲避和绘制条形图时,x
和 y
变量的处理方式不同。每个操作都有方向的概念。 Plot
尝试根据变量的数据类型自动确定方向。例如,如果我们交换 species
和 body_mass_g
的分配,我们将获得相同的图形,但方向水平。
(
so.Plot(penguins, x="body_mass_g", y="species", color="sex")
.add(so.Bar(), so.Agg(), so.Dodge())
)
有时,正确方向是模棱两可的,例如当 x
和 y
变量都是数值时。在这些情况下,您可以通过将 orient
参数传递给 Plot.add()
来明确指定。
(
so.Plot(tips, x="total_bill", y="size", color="time")
.add(so.Bar(), so.Agg(), so.Dodge(), orient="y")
)
构建和显示图形#
到目前为止,大多数示例都生成了一个只有一个子图,并且只有一个标记的子图。但是 Plot
并没有限制您只能这样。
添加多层#
可以通过重复调用 Plot.add()
来创建更复杂的单子图图形。每次调用它时,它都会定义图形中的一个层。例如,我们可能想要添加一个散点图(现在使用 Dots
),然后添加一个回归拟合。
(
so.Plot(tips, x="total_bill", y="tip")
.add(so.Dots())
.add(so.Line(), so.PolyFit())
)
在 Plot
构造函数中定义的变量映射将用于所有层。
(
so.Plot(tips, x="total_bill", y="tip", color="time")
.add(so.Dots())
.add(so.Line(), so.PolyFit())
)
特定于层的映射#
您还可以定义一个映射,使其仅在特定层中使用。这可以通过在调用 Plot.add
的相关层中定义映射来实现。
(
so.Plot(tips, x="total_bill", y="tip")
.add(so.Dots(), color="time")
.add(so.Line(color=".2"), so.PolyFit())
)
或者,定义整个图形的层,但通过将变量设置为 None
从特定层中删除它。
(
so.Plot(tips, x="total_bill", y="tip", color="time")
.add(so.Dots())
.add(so.Line(color=".2"), so.PolyFit(), color=None)
)
总而言之,有三种方法可以指定标记属性的值:(1) 通过在所有层中映射变量,(2) 通过在特定层中映射变量,以及 (3) 通过直接设置属性。
分面和配对子图#
与 seaborn 的图形级函数 (seaborn.displot()
,seaborn.catplot()
等) 一样, Plot
接口也可以生成包含多个“分面”或包含数据子集的子图的图形。这可以通过 Plot.facet()
方法实现。
(
so.Plot(penguins, x="flipper_length_mm")
.facet("species")
.add(so.Bars(), so.Hist())
)
使用应该用于定义图形列和/或行的变量调用 Plot.facet()
。
(
so.Plot(penguins, x="flipper_length_mm")
.facet(col="species", row="sex")
.add(so.Bars(), so.Hist())
)
您可以通过“包装”另一个维度来使用具有更多级别的变量进行分面。
(
so.Plot(healthexp, x="Year", y="Life_Expectancy")
.facet(col="Country", wrap=3)
.add(so.Line())
)
所有层都将被分面,除非您明确排除它们,这对于在每个子图上提供其他上下文很有用。
(
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))
)
另一种生成子图的方法是 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())
)
您可以将分面和配对组合在一起,只要操作在相反的维度上添加子图即可。
(
so.Plot(penguins, y="body_mass_g", color="species")
.pair(x=["bill_length_mm", "bill_depth_mm"])
.facet(row="sex")
.add(so.Dots())
)
与 matplotlib 集成#
在某些情况下,您可能希望在图形中显示多个子图,这些子图具有比 Plot.facet()
或 Plot.pair()
可以提供的更复杂的结构。当前的解决方案是将图形设置委托给 matplotlib,并将 matplotlib 对象提供给 Plot
应该使用的方法 Plot.on()
。此对象可以是 matplotlib.axes.Axes
,matplotlib.figure.Figure
或 matplotlib.figure.SubFigure
;后者最适合构建定制的子图布局。
f = mpl.figure.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()
)
构建和显示图形#
需要知道的一件重要的事情是,Plot
方法会克隆它们被调用的对象,并返回该克隆,而不是就地更新对象。这意味着您可以定义一个通用图形规范,然后生成该规范的几个变体。
因此,采用此基本规范。
p = so.Plot(healthexp, "Year", "Spending_USD", color="Country")
我们可以用它来绘制折线图。
p.add(so.Line())
或者绘制一个堆积面积图。
p.add(so.Area(), so.Stack())
Plot
方法是完全声明性的。调用它们会更新图形规范,但实际上不会进行任何绘图。这样做的一个结果是,可以按任意顺序调用方法,并且可以多次调用许多方法。
图形何时真正呈现?Plot
针对在笔记本环境中使用进行了优化。当 Plot
在 Jupyter REPL 中显示时,会自动触发呈现。这就是为什么我们在上面的示例中没有看到任何内容的原因,我们在其中定义了一个 Plot
,但将其分配给 p
而不是让它返回到 REPL。
要在笔记本中查看图形,请从单元格的最后一行返回它,或在对象上调用 Jupyter 的内置 display
函数。笔记本集成完全绕过了 matplotlib.pyplot
,但您可以通过调用 Plot.show()
在其他上下文中使用其图形显示机制。
您还可以通过调用 Plot.save()
将图形保存到文件(或缓冲区)。
自定义外观#
新接口旨在通过 Plot
支持深度的自定义,减少了直接使用 matplotlib 功能的需要。(但请耐心等待;实现此目标所需的所有功能尚未全部实现!)
参数化比例#
所有与数据相关的属性都由 Scale
和 Plot.scale()
方法的概念控制。此方法接受几种不同类型的参数。一种可能性,最接近在 matplotlib 中使用比例,是传递一个函数的名称,该函数将转换坐标。
(
so.Plot(diamonds, x="carat", y="price")
.add(so.Dots())
.scale(y="log")
)
Plot.scale()
还可以控制诸如 color
之类的语义属性的映射。您可以直接传递任何您将在 seaborn 函数接口中传递给 palette
参数的参数
(
so.Plot(diamonds, x="carat", y="price", color="clarity")
.add(so.Dots())
.scale(color="flare")
)
另一个选择是提供一个 (min, max)
值的元组,控制比例尺应映射到的范围。这对数字属性和颜色都有效
(
so.Plot(diamonds, x="carat", y="price", color="clarity", pointsize="carat")
.add(so.Dots())
.scale(color=("#88c", "#555"), pointsize=(2, 10))
)
为了获得更多控制,您可以传递一个 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"]),
)
)
自定义图例和刻度#
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]),
)
)
自定义限制、标签和标题#
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,
)
)
主题定制#
最后,Plot
通过 Plot.theme
方法支持与数据无关的主题。目前,此方法接受一个 matplotlib rc 参数字典。您可以直接设置它们,也可以从 seaborn 的主题函数传递一组参数
from seaborn import axes_style
theme_dict = {**axes_style("whitegrid"), "grid.linestyle": ":"}
so.Plot().theme(theme_dict)
要更改所有 Plot
实例的主题,请更新 Plot.config
中的设置
so.Plot.config.theme.update(theme_dict)