【问题标题】:Automated legend creation for 3D plot为 3D 绘图自动创建图例
【发布时间】:2021-10-23 23:14:06
【问题描述】:

我正在尝试更新以下函数以通过图例报告集群信息:

color_names = ["red", "blue", "yellow", "black", "pink", "purple", "orange"]

def plot_3d_transformed_data(df, title, colors="red"):
 
  ax = plt.figure(figsize=(12,10)).gca(projection='3d')
  #fig = plt.figure(figsize=(8, 8))
  #ax = fig.add_subplot(111, projection='3d')
  

  if type(colors) is np.ndarray:
    for cname, class_label in zip(color_names, np.unique(colors)):
      X_color = df[colors == class_label]
      ax.scatter(X_color[:, 0], X_color[:, 1], X_color[:, 2], marker="x", c=cname, label=f"Cluster {class_label}" if type(colors) is np.ndarray else None)
  else:
      ax.scatter(df.Type, df.Length, df.Freq, alpha=0.6, c=colors, marker="x", label=str(clusterSizes)  )

  ax.set_xlabel("PC1: Type")
  ax.set_ylabel("PC2: Length")
  ax.set_zlabel("PC3: Frequency")
  ax.set_title(title)
  
  if type(colors) is np.ndarray:
    #ax.legend()
    plt.gca().legend()
    
  
  plt.legend(bbox_to_anchor=(1.04,1), loc="upper left")
  plt.show()

所以我调用我的函数来可视化集群模式:

plot_3d_transformed_data(pdf_km_pred,
                         f'Clustering rare URL parameters for data of date: {DATE_FROM}  \nMethod: KMeans over PCA \nn_clusters={n_clusters} , Distance_Measure={DistanceMeasure}',
                         colors=pdf_km_pred.prediction_km)

print(clusterSizes)

遗憾的是我无法显示图例,我必须在 3D 绘图下手动打印集群成员。这是没有图例的输出,错误如下: No handles with labels found to put in legend.

我检查了这个post,但我无法弄清楚正确传递集群标签列表的函数有什么错误。我想更新函数,以便我可以通过clusterSizes.index 演示集群标签,并通过clusterSizes.size 演示它们的规模

预期输出:正如here 建议更好地使用legend_elements() 来确定要显示的图例条目的有用数量并自动返回一个句柄和标签元组。

更新:正如我在预期输出中提到的,应该包含一个集群标签图例和另一个集群大小图例(每个集群中的实例数)。它也可能通过单个图例报告此信息。 请参见下面的 2D 示例:

【问题讨论】:

  • 我并不完全理解您的所有问题,但我已经简化了您的代码并借用了@meTchaikovsky 的一些数据来创建图表。您的意思是要为每个集群创建此图例吗?这个图例的目的是可视化大小,所以我不确定是否可以为每个集群创建它。此外,可以在不将其划分为集群的情况下可视化大小。
  • 我也有点困惑。例如,问题提到了clusterSizes.indexclusterSizes.size,这听起来像是一个数据框,但代码使用了str(clusterSizes),这对于数据框没有意义。
  • 如果它确实是一个数据框并且理想情况下是预期输出的草图/模型,那么查看 clusterSizes 会有所帮助
  • @r-beginners 感谢您提供用于快速调试的笔记本。我在帖子末尾包含了更新以使其清楚。我检查了笔记本,第二个图例表明预测的集群标签仍然缺失。
  • @tdy 感谢您的意见。聚类算法的结果可以通过 BigData 的 spark 数据帧报告/传递。问题是提供自动图例,以在集群标签和集群大小方面指示集群结果,以使用嵌入式方法(例如 PCA)了解异常值的模式,以获得更好的可视化。请参阅此notebook。我想更新自动绘制和可视化所有集群信息的功能。

标签: python matplotlib legend


【解决方案1】:

在可视化集群的函数中,您需要ax.legend 而不是plt.legend

from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
import numpy as np
import pandas as pd

color_names = ["red", "blue", "yellow", "black", "pink", "purple", "orange"]

def plot_3d_transformed_data(df, title, colors="red"):
 
  ax = plt.figure(figsize=(12,10)).gca(projection='3d')
  #fig = plt.figure(figsize=(8, 8))
  #ax = fig.add_subplot(111, projection='3d')
  

  if type(colors) is np.ndarray:
    for cname, class_label in zip(color_names, np.unique(colors)):
      X_color = df[colors == class_label]
      ax.scatter(X_color[:, 0], X_color[:, 1], X_color[:, 2], marker="x", c=cname, label=f"Cluster {class_label}" if type(colors) is np.ndarray else None)
  else:
      ax.scatter(df.Type, df.Length, df.Freq, alpha=0.6, c=colors, marker="x", label=str(clusterSizes)  )

  ax.set_xlabel("PC1: Type")
  ax.set_ylabel("PC2: Length")
  ax.set_zlabel("PC3: Frequency")
  ax.set_title(title)
  
  if type(colors) is np.ndarray:
    #ax.legend()
    plt.gca().legend()
    
  
  ax.legend(bbox_to_anchor=(.9,1), loc="upper left")
  plt.show()

clusterSizes = 10

test_df = pd.DataFrame({'Type':np.random.randint(0,5,10),
                        'Length':np.random.randint(0,20,10),
                        'Freq':np.random.randint(0,10,10),
                        'Colors':np.random.choice(color_names,10)})

plot_3d_transformed_data(test_df,
                         'Clustering rare URL parameters for data of date:haha\nMethod: KMeans over PCA \nn_clusters={n_clusters} , Distance_Measure={DistanceMeasure}',
                         colors=test_df.Colors)

运行此示例代码,您将获得预期的图例句柄

【讨论】:

  • 感谢您的输入,但 OP 要求包含/配备功能,以便绘图指示 集群的大小集群的标签 如帖子更新中所示(请参阅 2D 示例)。在您的解决方案中缺少第二个图例。
【解决方案2】:

在创建第二个图例之前,您需要保存对第一个图例的引用并将其作为单独的艺术家添加到您的 ax。这样,对ax.legend(...) 的第二次调用不会删除第一个图例。

对于第二个图例,我只是为每种唯一颜色创建了一个圆圈并将其添加到其中。我忘记了如何绘制真正的圆圈,所以我使用 Line2Dlw=0, marker="o" 来生成一个圆圈。

使用图例的 bbox_to_anchorloc 关键字来获得令您满意的结果。

我摆脱了依赖plt.<something> 的一切,因为这是忘记哪个方法附加到哪个对象的最佳方式。现在一切都在ax.<something>fig.<something> 中。当您有多个轴时,或者当您想将画布嵌入到 PyQt 应用程序中时,这也是正确的方法。 plt 不会做你期望的那样。

初始代码是由@r-beginners提供的,我只是在它的基础上构建的。

# Imports.
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import pandas as pd
import numpy as np

# Figure.
figure = plt.figure(figsize=(12, 10))
ax = figure.add_subplot(projection="3d")
ax.set_xlabel("PC1: Type")
ax.set_ylabel("PC2: Length")
ax.set_zlabel("PC3: Frequency")
ax.set_title("scatter 3D legend") 

# Data and 3D scatter.
colors = ["red", "blue", "yellow", "black", "pink", "purple", "orange", "black", "red" ,"blue"]

df = pd.DataFrame({"type": np.random.randint(0, 5, 10),
                   "length": np.random.randint(0, 20, 10),
                   "freq": np.random.randint(0, 10, 10),
                   "size": np.random.randint(20, 200, 10),
                   "colors": np.random.choice(colors, 10)})

sc = ax.scatter(df.type, df.length, df.freq, alpha=0.6, c=colors, s=df["size"], marker="o")

# Legend 1.
handles, labels = sc.legend_elements(prop="sizes", alpha=0.6)
legend1 = ax.legend(handles, labels, bbox_to_anchor=(1, 1), loc="upper right", title="Sizes")
ax.add_artist(legend1) # <- this is important.

# Legend 2.
unique_colors = set(colors)
handles = []
labels = []
for n, color in enumerate(unique_colors, start=1):
    artist = mpl.lines.Line2D([], [], color=color, lw=0, marker="o")
    handles.append(artist)
    labels.append(str(n))
legend2 = ax.legend(handles, labels, bbox_to_anchor=(0.05, 0.05), loc="lower left", title="Classes")

figure.show()

与问题无关:because of how markersize works for circles,可以使用s = df["size"]**2 而不是s = df["size"]

【讨论】:

  • 感谢您发布您的解决方案,如果我通过单个数据框将值传递给分散参数,它就可以工作。但是,我可以提请您注意cloab notebook 以便快速调试吗?由于我尝试使用 2 个不同的数据框获取散点图,假设 ax.scatter(x=df1[x], y=df1[y], z=df1[z])ax.scatter(...., s=df2[clusterSize], c=df2[clusterSize]) 我会遇到一些错误。
  • @Mario handleslabels 是列表,因此您可以将它们与其他列表相加以添加更多元素。例如,如果您有 sc1 = ax.scatter(df1...)sc2 = ax.scatter(df2...),请像这样构建句柄和标签:h1, l1 = sc1.legend_elements(...) h2, l2 = sc2.legend_elements(...) handles = h1 + h2 labels = l1 + l2。如果您有许多数据框要使用,如果您需要,我们可以轻松地将其转换为循环。
  • 我无法调整您对 colab notebook 的评论。可以请您在提供的笔记本上申请快速调试吗?
猜你喜欢
  • 2013-10-27
  • 1970-01-01
  • 2011-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-02
  • 1970-01-01
相关资源
最近更新 更多