matplotlib 中的 3D PCA:如何添加图例?

Posted

技术标签:

【中文标题】matplotlib 中的 3D PCA:如何添加图例?【英文标题】:3D PCA in matplotlib: how to add legend? 【发布时间】:2018-09-08 22:18:32 【问题描述】:

我正在尝试将http://scikit-learn.org/stable/auto_examples/decomposition/plot_pca_iris.html 用于我自己的数据来构建 3D PCA 图。但是,本教程没有指定如何添加图例。另一个页面https://matplotlib.org/users/legend_guide.html 做了,但我看不到如何将第二个教程中的信息应用到第一个。

如何修改下面的代码来添加图例?

# Code source: Gae"l Varoquaux
# License: BSD 3 clause

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn import decomposition
from sklearn import datasets

np.random.seed(5)

centers = [[1, 1], [-1, -1], [1, -1]]
iris = datasets.load_iris()
X = iris.data#the floating point values
y = iris.target#unsigned integers specifying group


fig = plt.figure(1, figsize=(4, 3))
plt.clf()
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)

plt.cla()
pca = decomposition.PCA(n_components=3)
pca.fit(X)
X = pca.transform(X)

for name, label in [('Setosa', 0), ('Versicolour', 1), ('Virginica', 2)]:
    ax.text3D(X[y == label, 0].mean(),
              X[y == label, 1].mean() + 1.5,
              X[y == label, 2].mean(), name,
              horizontalalignment='center',
              bbox=dict(alpha=.5, edgecolor='w', facecolor='w'))
# Reorder the labels to have colors matching the cluster results
y = np.choose(y, [1, 2, 0]).astype(np.float)
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=y, cmap=plt.cm.spectral,
           edgecolor='k')

ax.w_xaxis.set_ticklabels([])
ax.w_yaxis.set_ticklabels([])
ax.w_zaxis.set_ticklabels([])

plt.show()

【问题讨论】:

调查plt.legend。如果遇到障碍,请返回 ***,详细说明现实与您的期望有何不同。 【参考方案1】:

其他答案存在一些问题,OP 和回答者似乎都不清楚;因此,这不是一个完整的答案,而是现有答案的附录。

spectral 颜色图已在 2.2 版中从 matplotlib 中删除, 使用Spectralnipy_spectral 或任何other valid colormap。

matplotlib 中的任何颜色图的范围都是从 0 到 1。如果您使用该范围之外的任何值调用它, 它只会给你最外层的颜色。因此,要从颜色图中获取颜色,您需要对值进行规范化。 这是通过Normalize 实例完成的。在这种情况下,这是 scatter 内部的。

因此使用sc = ax.scatter(...),然后使用sc.cmap(sc.norm(value)),根据散点图中使用的相同映射获取值。 因此代码应该使用

[sc.cmap(sc.norm(i)) for i in [1, 2, 0]] 

图例在图外。该图的大小为 4 x 3 英寸 (figsize=(4, 3))。 轴占据了该空间宽度的 95% (rect=[0, 0, .95, 1])。 对legend 的调用将图例的右中心点置于轴宽度的 1.7 倍 = 4*0.95*1.7 = 6.46 英寸。 (bbox_to_anchor=(1.7,0.5))。

我这边的替代建议:使图形更大(figsize=(5.5, 3)),以便图例适合,使轴仅占图形宽度的 70%,这样你就剩下 30% 用于图例。将图例的左侧放置在靠近坐标区边界的位置 (bbox_to_anchor=(1.0, .5))。

有关此主题的更多信息,请参阅How to put the legend out of the plot。

您仍然在 jupyter 笔记本中看到包括图例在内的完整图形的原因是 jupyter 只会将所有内容保存在画布内,即使它重叠并因此放大图形。

总而言之,代码可能看起来像

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np; np.random.seed(5)
from sklearn import decomposition, datasets 

centers = [[1, 1], [-1, -1], [1, -1]]
iris = datasets.load_iris()
X = iris.data #the floating point values
y = iris.target #unsigned integers specifying group

fig = plt.figure(figsize=(5.5, 3))
ax = Axes3D(fig, rect=[0, 0, .7, 1], elev=48, azim=134)

pca = decomposition.PCA(n_components=3)
pca.fit(X)
X = pca.transform(X)

labelTups = [('Setosa', 0), ('Versicolour', 1), ('Virginica', 2)]
for name, label in labelTups:
    ax.text3D(X[y == label, 0].mean(),
              X[y == label, 1].mean() + 1.5,
              X[y == label, 2].mean(), name,
              horizontalalignment='center',
              bbox=dict(alpha=.5, edgecolor='w', facecolor='w'))
# Reorder the labels to have colors matching the cluster results
y = np.choose(y, [1, 2, 0]).astype(np.float)
sc = ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=y, cmap="Spectral", edgecolor='k')

ax.w_xaxis.set_ticklabels([])
ax.w_yaxis.set_ticklabels([])
ax.w_zaxis.set_ticklabels([])

colors = [sc.cmap(sc.norm(i)) for i in [1, 2, 0]]
custom_lines = [plt.Line2D([],[], ls="", marker='.', 
                mec='k', mfc=c, mew=.1, ms=20) for c in colors]
ax.legend(custom_lines, [lt[0] for lt in labelTups], 
          loc='center left', bbox_to_anchor=(1.0, .5))

plt.show()

并产生

【讨论】:

这些是一些可爱的细节。特别感谢您澄清ax.scatter 的规范化行为。有点神秘 我可能会删除“中心”列表,它没有被使用,而且代码混乱 @ImportanceOfBeingErnest 请您查看相关question。提前致谢【参考方案2】:

需要一些调整(plt.cm.spectral 是我处理过的最奇怪的颜色图),但现在看起来不错:

from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from sklearn import decomposition
from sklearn import datasets

np.random.seed(5)

centers = [[1, 1], [-1, -1], [1, -1]]
iris = datasets.load_iris()
X = iris.data#the floating point values
y = iris.target#unsigned integers specifying group


fig = plt.figure(1, figsize=(4, 3))
plt.clf()
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)

plt.cla()
pca = decomposition.PCA(n_components=3)
pca.fit(X)
X = pca.transform(X)

labelTups = [('Setosa', 0), ('Versicolour', 1), ('Virginica', 2)]
for name, label in labelTups:
    ax.text3D(X[y == label, 0].mean(),
              X[y == label, 1].mean() + 1.5,
              X[y == label, 2].mean(), name,
              horizontalalignment='center',
              bbox=dict(alpha=.5, edgecolor='w', facecolor='w'))
# Reorder the labels to have colors matching the cluster results
y = np.choose(y, [1, 2, 0]).astype(np.float)
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=y, cmap=plt.cm.spectral, edgecolor='k')

ax.w_xaxis.set_ticklabels([])
ax.w_yaxis.set_ticklabels([])
ax.w_zaxis.set_ticklabels([])

colors = [plt.cm.spectral(np.float(i/2)) for i in [1, 2, 0]]
custom_lines = [Line2D([0], [0], linestyle="none", marker='.', markeredgecolor='k', markerfacecolor=c, markeredgewidth=.1, markersize=20) for c in colors]
ax.legend(custom_lines, [lt[0] for lt in labelTups], loc='right', bbox_to_anchor=(1.7, .5))

plt.show()

Here's a link to an online Jupyter notebook with a live version of the script(不过需要一个帐户才能重新运行)。

简短说明

您正在尝试为单个图添加三个图例标记,这是非标准行为。因此,您需要手动创建图例将显示的形状。

更长的解释

这行代码重新创建了您在绘图中使用的颜色:

colors = [plt.cm.spectral(np.float(i/2)) for i in [1, 2, 0]]

然后这行代码绘制了一些看起来合适的点,我们最终会显示在您的图例上:

custom_lines = [Line2D([0], [0], linestyle="none", marker='.', markeredgecolor='k', markerfacecolor=c, markeredgewidth=.1, markersize=20) for c in colors]

前两个 args 只是将要绘制的单个点的(内部)x 和 y 坐标,linestyle="none" 抑制了 Line2D 通常默认绘制的线,其余的 args 创建和设置点本身的样式(在matplotlib api 的术语中称为marker)。

最后,这个语句实际上创建了图例:

ax.legend(custom_lines, [lt[0] for lt in labelTups], loc='right', bbox_to_anchor=(1.7, .5))

第一个参数当然是我们刚刚绘制的点的列表,第二个参数是标签列表(每个点一个)。剩下的两个参数告诉 matplotlib 在哪里绘制包含图例的实际框。最后一个参数bbox_to_anchor 基本上是一种手动调整图例位置的方法,因为matplotlib 对3D 的支持仍然有点落后,所以我不得不这样做。在 2D 绘图上,您通常不需要它,而且由于 matplotlib 通常首先会自动将图例定位在 2D 绘图上,因此您通常甚至不需要 loc arg。

一些颜色图的怪异

不太清楚 plt.cm.spectral 发生了什么,但为了让它正常运行,对于我输入的每个值,我都必须:

a) 首先将值转换为float

b) 然后将该值除以 2

a) 确实出现在 OP 的原始代码中,就在它们绘制之前。除以2的东西,我不知道这是从哪里来的。不知何故,对ax.scatter 的调用隐含地对所有 y 值进行归一化,以便最大值为 1?我猜?

【讨论】:

嗨@tel 是您用来生成图像的同一个脚本吗?图片确实是我想要的,但右侧的图例框没有显示此处发布的脚本 @con 这……很奇怪。首先要检查的是您的matplotlib 版本。打开 Python 解释器并运行 import matplotlib; matplotlib.__version__ 并告诉我你得到了什么。与此同时,我将设置并发布脚本的实时版本 我得到 matplotlib 2.1.1 版 @con 需要调整这部分代码bbox_to_anchor=(1.7, .5) 没有 bbox_to_anchor 是完全确定的。我在下面提供了一个答案,它澄清了这个答案引起的所有未决问题。

以上是关于matplotlib 中的 3D PCA:如何添加图例?的主要内容,如果未能解决你的问题,请参考以下文章

sklearn - PCA 的标签点

如何在 matplotlib 中为 3d plot_surface 设置动画

在Matplotlib中,如何将3D图作为插图包含进去?

如何绘制不同颜色的 3D PCA?

从 matplotlib 中的元组列表中绘制 3d 曲面

Matplotlib 可视化进阶之 PCA 主成分分布图