如何用Matplotlib从分类网格点绘制多边形相图?
生成带清晰线性边界的相图
我有一个包含1681个均匀分布2D网格点的DataFrame,每个点包含x、y坐标、类别(相)标签及对应颜色。目前只能绘制简单散点图,需要生成带有清晰线性边界的相图,边界取不同类别相邻点的中间位置,可使用插值方法,推测matplotlib.patches.Polygon能派上用场。注意:测试数据生成代码里的多边形信息不能用于相图生成。
数据示例
x y label color 0 -40.0 -30.0 Fe #660066 1 -40.0 -29.0 Fe #660066 2 -40.0 -28.0 FeS #ff7f50 ... ... ... ... ... 1680 0.0 10.0 Fe2(SO4)3 #8a2be2 [1681 rows x 4 columns]
现有散点图实现代码
import matplotlib.pyplot as plt import pandas as pd plt.figure(figsize=(8., 8.)) for color in df.color.unique(): df_color = df[df.color==color] plt.scatter( x=df_color.x, y=df_color.y, c=color, s=100, label=df_color.label.iloc[0] ) plt.xlim([-40., 0.]) plt.ylim([-30., 10.]) plt.xlabel('Log pO2(g)') plt.ylabel('Log pSO2(g)') plt.legend(bbox_to_anchor=(1.05, 1.)) plt.show()
测试数据生成代码
import numpy as np import pandas as pd from shapely.geometry import Point, Polygon labels = ['Fe', 'Fe3O4', 'FeS', 'Fe2O3', 'FeS2', 'FeSO4', 'Fe2(SO4)3'] colors = ['#660066', '#b6fcd5', '#ff7f50', '#ffb6c1', '#c6e2ff', '#d3ffce', '#8a2be2'] polygons = [] polygons.append(Polygon([(-26.7243,-14.7423), (-26.7243,-30.0000), (-40.0000,-30.0000), (-40.0000,-28.0181)])) polygons.append(Polygon([(-18.1347,-0.4263), (-16.6048,1.6135), (-16.6048,-30.0000), (-26.7243,-30.0000), (-26.7243,-14.7423), (-18.1347,-0.4263)])) polygons.append(Polygon([(-18.1347,-0.4263), (-26.7243,-14.7423), (-40.0000,-28.0181), (-40.0000,-22.2917), (-18.1347,-0.4263)])) polygons.append(Polygon([(0.0000,-20.2615), (0.0000,-30.0000), (-16.6048,-30.0000), (-16.6048,1.6135), (-16.5517,1.6865), (-6.0517,-0.9385), (0.0000,-3.9643)])) polygons.append(Polygon([(-14.2390,10.0000), (-14.5829,7.5927), (-16.5517,1.6865), (-16.6048,1.6135), (-18.1347,-0.4263), (-40.0000,-22.2917), (-40.0000,10.0000)])) polygons.append(Polygon([(-6.0517,-0.9385), (-16.5517,1.6865), (-14.5829,7.5927), (-6.0517,-0.9385)])) polygons.append(Polygon([(0.0000,-3.9643), (-6.0517,-0.9385), (-14.5829,7.5927), (-14.2390,10.0000), (0.0000,10.0000)])) x_grid = np.arange(-40., 0.01, 1.) y_grid = np.arange(-30., 10.01, 1.) xy_grid = np.array(np.meshgrid(x_grid, y_grid)).T.reshape(-1, 2).tolist() data = [] for coords in xy_grid: point = Point(coords) for i, poly in enumerate(polygons): if poly.buffer(1e-3).contains(point): data.append({ 'x': point.x, 'y': point.y, 'label': labels[i], 'color': colors[i] }) break df = pd.DataFrame(data)
解决方案
核心思路:先识别不同类别相邻网格点的中点作为边界参考,再为每个类别拟合多边形轮廓,用Polygon绘制填充区域与边界。
实现代码
import matplotlib.pyplot as plt import pandas as pd import numpy as np from matplotlib.patches import Polygon from scipy.spatial import ConvexHull # 1. 将数据转换为网格矩阵,方便邻点查找 x_unique = sorted(df.x.unique()) y_unique = sorted(df.y.unique()) x_idx = {x:i for i,x in enumerate(x_unique)} y_idx = {y:i for i,y in enumerate(y_unique)} label_grid = np.full((len(y_unique), len(x_unique)), '', dtype=object) color_grid = np.full((len(y_unique), len(x_unique)), '', dtype=object) for _, row in df.iterrows(): i = x_idx[row['x']] j = y_idx[row['y']] label_grid[j, i] = row['label'] color_grid[j, i] = row['color'] # 2. 收集不同类别相邻点的中点(边界候选) boundary_midpoints = [] # 只检查右、下方向邻点,避免重复计算 for j in range(len(y_unique)): for i in range(len(x_unique)): current_label = label_grid[j, i] # 右侧邻点 if i < len(x_unique)-1: right_label = label_grid[j, i+1] if current_label != right_label: boundary_midpoints.append(((x_unique[i]+x_unique[i+1])/2, y_unique[j])) # 下方邻点 if j < len(y_unique)-1: down_label = label_grid[j+1, i] if current_label != down_label: boundary_midpoints.append((x_unique[i], (y_unique[j]+y_unique[j+1])/2)) # 3. 绘制相图 plt.figure(figsize=(8, 8)) # 为每个类别生成多边形并绘制 for label in df.label.unique(): class_points = df[df.label == label][['x', 'y']].values # 用凸包拟合类别外轮廓,若需更精细边界可加入边界中点优化 if len(class_points) >= 3: hull = ConvexHull(class_points) poly_vertices = class_points[hull.vertices] color = df[df.label == label]['color'].iloc[0] # 绘制填充区域+黑色边界 poly = Polygon(poly_vertices, facecolor=color, edgecolor='black', linewidth=1.5, alpha=0.8) plt.gca().add_patch(poly) # 可选:叠加原始网格点 # for color in df.color.unique(): # df_color = df[df.color==color] # plt.scatter(df_color.x, df_color.y, c=color, s=20, edgecolor='white') plt.xlim([-40., 0.]) plt.ylim([-30., 10.]) plt.xlabel('Log pO2(g)') plt.ylabel('Log pSO2(g)') # 添加图例 handles = [plt.Rectangle((0,0),1,1, color=df[df.label==lbl]['color'].iloc[0]) for lbl in df.label.unique()] plt.legend(handles, df.label.unique(), bbox_to_anchor=(1.05, 1.)) plt.tight_layout() plt.show()
说明
- 邻点检查:仅遍历右、下方向,避免重复计算相同边界
- 轮廓拟合:使用
ConvexHull快速生成类别外轮廓,适合规则相区域;若相区域有凹形,可改用shapely库进行多边形生成或插值优化 - 边界优化:可将收集到的边界中点加入对应类别的点集后再做拟合,提升边界精度
内容的提问来源于stack exchange,提问作者Shaun Han
相关产品推荐
相关产品推荐

