Matplotlib 可视化之图例与标签高级应用

百家 作者:AI100 2022-05-12 20:21:26

作者 | 云朵君? ? ?

来源 | 数据STUDIO
装饰物指的是你可以添加到一个图形上的所有额外元素,以美化它或使它更清晰。装饰物包括图例、注释、颜色条、文本等标准元素,但也可以专门设计自己的元素。今天一起继续学习图例与标签元素的应用实例。

配置图例

想在可视化图形中使用图例,可以为不同的图形元素分配标签。
图例非常容易使用,只要求用户命名图。Matplotlib将自动创建一个包含每个图形元素的图例。即使在大多数情况下,一个简单的legend() 调用就足够了,但图例还是提供了几个选项,允许我们自定义图例的各个配置。如使用
ax.legend(loc='upper?left',
??????????frameon=False,
??????????edgecolor="None")

完整代码解析

上下滑动查看更多源码
fig?=?plt.figure(figsize=(9.6,?4))
ax?=?plt.subplot(
????xlim=[-np.pi,?np.pi],
????xticks=[-np.pi,?-np.pi?/?2,?0,?np.pi?/?2,?np.pi],
????xticklabels=["$-\pi$",?"$-\pi/2$",?"0",?"$+\pi/2$",?"$+\pi$"],
????ylim=[-1,?1],
????yticks=[-1,?0,?1],
????yticklabels=["-1",?"0",?"+1"],
)

X?=?np.linspace(-np.pi,?np.pi,?256,?endpoint=True)
C,?S?=?np.cos(X),?np.sin(X)
#?绘制两条折线,颜色默认
ax.plot(X,?C,?label="$cos(x)$",?clip_on=False)
ax.plot(X,?S,?label="$sin(x)$",?clip_on=False)
#?隐藏上右边的轴线
ax.spines["right"].set_visible(False)
ax.spines["top"].set_visible(False)
#?移动下左边的轴线
ax.spines["left"].set_position(("data",?-3.25))
ax.spines["bottom"].set_position(("data",?-1.25))
ax.legend(edgecolor="None",loc=2,frameon=False)
然而,在某些情况下,用图例来添加信息可能不是最合适的方式。例如,当你有多个图表时,读者在阅读图表,视线在图表和图例之间来回切换时,可能会觉得很乏味。另一种可以解决此类困惑的方法是在下图所示的图上直接添加信息。

详细代码解析

上下滑动查看更多源码
X?=?np.linspace(-np.pi,?np.pi,?400,?endpoint=True)
C,?S?=?np.cos(X),?np.sin(X)
plot1,?plot2?=?plot(ax)?#?绘制折线图的对象
#?--------------------P1-------------------------
#?用小横线标注在折线旁边
ax.text(
????X[-1],?C[-1],
????"?—?"?+?plot1.get_label(),?#?从对象中获取标签???
????color=plot1.get_color(),???#?从对象中获取线条颜色
????size="small",?ha="left",?va="center",)
#?--------------------P2--------------------------
#?标注在对应折线上,且有透明边框
ax.text(
????X[100],?C[100],
????"?"?+?plot1.get_label(),
????bbox=dict(facecolor="white",?edgecolor="None",?alpha=0.85),
????color=plot1.get_color(),
????ha="center",?va="center",?size="small",
????rotation=42.5,)
#?--------------------P3--------------------------
#?使用箭头
ax.annotate(
????"$cos(x)$",
????(X[100],?C[100]),
????size="medium",
????color=plot1.get_color(),
????xytext=(-50,?+10),
????textcoords="offset?points",
????arrowprops=dict(
????????arrowstyle="->",?color=plot1.get_color(),?
????????connectionstyle="arc3,rad=-0.3"),)
#?--------------------P4--------------------------
#?圈点和注释的组合
index?=?10
ax.scatter(
????[X[index]],?[C[index]],
????s=100,?marker="o",?zorder=10,?????
????edgecolor=plot1.get_color(),
????facecolor="white",?linewidth=1,?clip_on=False,)
ax.text(
????X[index],?1.01?*?C[index],
????"A",
????zorder=20,size="small",
????color=plot1.get_color(),
????ha="center",?va="center",?clip_on=False,)
当然,这里是没有最好的选择,因为它真的取决于数据。对于上述的sin / cos的示例(非常简单),这四种解决方案都是合适的,但当有很多实际数据一起使用时,可能这种方法就失效了。此时我们可能需要寻求其他方式来标记数据,如将图分成几个图分别展示。

标题和标签

我们已经使用 set_titleset_xlabelset_ylabel 方法操作了标题和标签。当仅仅使用默认参数时,确实比较方便。并且它们的默认位置通常对大多数图表都比较合适。尽管如此,仍然可以使用各种参数来定制和美化图形。
如下面两个图所示,对比观察,可以明显发现:上图大部分使用了默认参数。而下图中,用轴标签替换轴刻度标签,即在轴中间加上说明标签,为了使其更靠近轴,删除了可能与标签碰撞的中心刻度。此外,将标题其向右移动,并相应地移动图例框,将其放置在标题下方,并且使用一行两列的排列方式。其实这里没有做过复杂的操作,但我认为结果在视觉上更惊艳。

完整代码解析

ax.legend(
????edgecolor="None",
????ncol=2,
????loc="upper?right",
????bbox_to_anchor=(1.01,?1.225),???
????#?用于与loc一起定位图例的框。(x, y, width, height)
????borderaxespad=1,????????????????
????#?轴线和图例边框之间的填充,以字体大小为单位。
)
#?设置标题
ax.set_title("三角函数",?x=1,?y=1.2,?ha="right",size=14)
#?设置x轴标签
ax.set_xlabel("角度",?va="center",?weight="bold",size=12)
ax.xaxis.set_label_coords(0.5,?-0.25)
#?设置标签的坐标。
#?默认情况下,y?标签的?x?坐标和?x?标签的?y?坐标由刻度标签边界框确定,
#?但是如果有多个轴,这可能会导致多个标签对齐不良。
#?设置y轴标签
ax.set_ylabel("值",?ha="center",?weight="bold",size=12)
ax.yaxis.set_label_coords(-0.025,?0.5)
在某些情况下(如会议海报),可能需要让标题更吸引眼球,如下图所示。这可以通过使用make_axes_locatable 方法来划分每个轴,并为标题区域预留15%的高度。在这个图中,还用Latex 插入了一个完全对齐的文本,它可以被看作是另一种形式或(高级)装饰。
完整代码参见latex-text-box[1]

注释

在matplotlib中,注释可能是最难处理的对象。原因是它包含的概念众多,而这些概念又具有大量的参数。此外,由于注释所涉及的文本大小是按点排列的,这无疑又是雪上加霜。此外可能需要混合使用像素、点、分数或数据单元中的绝对坐标或相对坐标。你可以这么认为,你可以对具有任何类型投影的任何轴进行注释,那么你现在应该可以理解到为什么annotate方法提供这么多参数。

上面这段话比较抽象,接下来我们一起看下具体例子。注释图形最简单的方法是在想要注释的点附近添加标签,如下图所示。图中,为了使得标签独立于数据分布保持可读性,为标签添加了一个白色的轮廓。然而,如果这样的点过多,所有不同的标签可能会使图形变得混乱,并可能会掩盖潜在的重要信息。

完整代码解析

上下滑动查看更多源码
import?matplotlib.patheffects?as?path_effects
fig?=?plt.figure(figsize=(10,?5))
ax?=?plt.subplot(1,?2,?1,?xlim=[-1,?+1],?
?????????????????xticks=[],?ylim=[-1,?+1],?
?????????????????yticks=[],?aspect=1)
#?---------------------------------------------?
#?绘制散点图
np.random.seed(123)
X?=?np.random.normal(0,?0.35,?1000)
Y?=?np.random.normal(0,?0.35,?1000)

ax.scatter(X,?Y,?edgecolor="None",?s=60,
???????????facecolor="C1",?alpha=0.5)

#?不重复采用:array([1, 4, 0, 3, 2])
I?=?np.random.choice(len(X),?size=5,?
?????????????????????replace=False)
#?根据y值,从大到小排序
Px,?Py?=?X[I],?Y[I]
I?=?np.argsort(Y[I])[::-1]
Px,?Py?=?Px[I],?Py[I]
#?将随机选取的五个点用黑色边框框选出
ax.scatter(Px,?Py,?edgecolor="black",?facecolor="white",?zorder=20)
ax.scatter(Px,?Py,?edgecolor="None",?facecolor="C1",?alpha=0.5,?zorder=30)

添加标签注释

for?i?in?range(len(I)):
#?五个注释是样式是一样的,可以使用循环添加
????text?=?ax.annotate(
????????"Point?"?+?chr(ord("A")?+?i),
????????xy=(Px[i],?Py[i]),
????????xycoords="data",
????????xytext=(0,?18),
????????textcoords="offset?points",
????????ha="center",
????????size="medium",
????????arrowprops=dict(
????????????arrowstyle="->",?shrinkA=0,?shrinkB=5,?color="black",?linewidth=0.75),
????)
????text.set_path_effects(
????????[path_effects.Stroke(linewidth=2,?foreground="white"),?path_effects.Normal()]
????)
????text.arrow_patch.set_path_effects(
????????[path_effects.Stroke(linewidth=2,?foreground="white"),?path_effects.Normal()]
????)
另一种方法是将标签推到图的一侧,并使用虚线来建立点和标签之间的链接,如下图所示。
但这些形状、位置、排列方式等样式的设计并不是图形自动的,为了绘制出该图形,就必须计算几乎所有的东西。
首先,为了不让线相互交叉,将目标标记的点排序:
X?=?np.random.normal(0,?.35,?1000)?
Y?=?np.random.normal(0,?.35,?1000)
ax.scatter(X,?Y,?edgecolor="None",?
???????????facecolor="C1",?alpha=0.5)
I?=?np.random.choice(len(X),?size=5,?replace=False)
Px,?Py?=?X[I],?Y[I]
I?=?np.argsort(Y[I])[::-1]
Px,?Py?=?Px[I],?Py[I]
从这些点开始,使用一个相当复杂的连接样式来注释它们:
上下滑动查看更多源码
y,?dy?=?0.25,?0.125
style?=?"arc,angleA=-0,angleB=0,armA=-100,armB=0,rad=0"
for?i?in?range(len(I)):
????text?=?ax2.annotate(
????????"Point?"?+?chr(ord("A")?+?i),
????????xy=(Px[i],?Py[i]),
????????xycoords="data",
????????xytext=(1.25,?y?-?i?*?dy),
????????textcoords="data",
????????arrowprops=dict(
????????????arrowstyle="->",
????????????color="black",
????????????linewidth=0.75,
????????????shrinkA=20,
????????????shrinkB=5,
????????????patchA=None,
????????????patchB=None,
????????????connectionstyle=style,
????????),
????)
????text.arrow_patch.set_path_effects(
????????[path_effects.Stroke(linewidth=2,?foreground="white"),?path_effects.Normal()]
????)
也可以使用连接补片的方式在轴外来注释的目标对象,如下图所示。
该图中,创建了几个矩形,在一些点周围显示感兴趣的区域,并创建了与相应的缩放轴的连接。注意连接开始在外面的矩形,这是一个不错的功能提供的注释:可以指定对象的性质要注释(通过提供一个patche)和matplotlib会照顾的连接边界的起源的patche。

完整代码解析

上下滑动查看更多源码
from?matplotlib.gridspec?import?GridSpec
from?matplotlib.patches?import?Rectangle,?ConnectionPatch
#?设置画布
fig?=?plt.figure(figsize=(6,?5))
n?=?5
gs?=?GridSpec(n,?n?+?1)
ax?=?plt.subplot(?gs[:n,?:n],?
??????????xlim=[-1,?+1],?xticks=[],?
??????????ylim=[-1,?+1],?yticks=[],?aspect=1)

#?绘制散点图略(见上面代码)
dx,?dy?=?0.075,?0.075
for?i,?(x,?y)?in?enumerate(zip(Px,?Py)):
#?设置子画布
????sax?=?plt.subplot(
????????gs[i,?n],
????????xlim=[x?-?dx,?x?+?dx],
????????xticks=[],
????????ylim=[y?-?dy,?y?+?dy],
????????yticks=[],
????????aspect=1,)
#?在子画布上绘制散点
????sax.scatter(X,?Y,?edgecolor="None",?
????????????????facecolor="C1",?alpha=0.5,s=60)
????sax.scatter(Px,?Py,?edgecolor="black",?
????????????????facecolor="None",?linewidth=0.75,s=60)
#?加上注释
????sax.text(
????????1.1,?0.5,
????????"Point?"?+?chr(ord("A")?+?i),
????????rotation=90,?size=8,?ha="left",?va="center",
????????transform=sax.transAxes,?)
#?绘制矩形
????rect?=?Rectangle(
????????(x?-?dx,?y?-?dy),
????????2?*?dx,?2?*?dy,
????????edgecolor="black",?facecolor="None",
????????linestyle="--",?linewidth=0.75,?)
????ax.add_patch(rect)
#?绘制连接补丁Patch
????con?=?ConnectionPatch(
????????xyA=(x,?y),?coordsA=ax.transData,
????????xyB=(0,?0.5),?coordsB=sax.transAxes,
????????linestyle="--",?linewidth=0.75,
????????patchA=rect,?arrowstyle="->",?)
????fig.add_artist(con)
GridSpec:指定子图将放置的网格的几何位置。需要设置网格的行数和列数。子图布局参数(例如,左,右等)可以选择性调整。
ConnectionPatch:用于在两点之间建立连接线。
上下滑动查看更多参数
参数:
  • xyA:?它是x-y图上也称为点A的连接线的起点。
  • xyB:?它是x-y图上连接线的起点,也称为点B。
  • coordsA:?A点的坐标。
  • coordsB:?B点的坐标。
  • axesA:?它是x-y图上连接轴的起点。
  • axesB:?它是x-y图上连接轴的终点。
  • arrowstyle:?用于设置连接箭头的样式。其默认类型为“-”。
  • arrow_transmuter:?用于忽略连接线。
  • connectionstyle:?它描述了posA和posB的连接方式。它可以是ConnectionStyle类的实例,也可以是connectionstyle名称的字符串,它具有可选的逗号分隔属性。
  • connector:?通常忽略它,并决定忽略哪个连接器。
  • patchA:?用于在A点添加补丁。
  • patchB:?用于在B点添加补丁
  • shrinkA:?用于在A点收缩连接器。
  • shrinkB:?用于在B点收缩连接器。
  • mutation_scale:?箭头样式的属性(例如head_length)的缩放比例值。
  • mutation_aspect:?变异前,矩形的高度将被该值挤压,变异框将被其倒数拉伸。
  • clip_on:?设置艺术家是否使用剪辑。
  • dpi_cor:?dpi_cor当前用于linewidth-related事物和收缩因子。突变规模受此影响。

参考资料

[1]

latex-text-box:?https://github.com/rougier/scientific-visualization-book/blob/master/code/ornaments/latex-text-box.py

[2]

Scientific Visualisation-Python & Matplotlib


资讯

变身「毒」苹果?全球首个DMP漏洞

资讯

程序员化身“侦探”识破AI律所骗局

技术

浅谈Python中的字符串格式化输出

技术

10个有趣的Python高级脚本!


分享

点收藏

点点赞

点在看

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接