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 中删除,
使用Spectral
或nipy_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:如何添加图例?的主要内容,如果未能解决你的问题,请参考以下文章