对数 - 对数刻度中的椭圆和多边形修补程序

时间:2014-10-22 04:35:17

标签: python matplotlib plot

我想做一些例程来自动生成Ashby charts。这种类型的图基本上是散点图。它对材料选择很有用,通常会在材料组中添加一些包络形状。由于几个数量级的变化,将轴绘制成对数刻度也很常见。

为了生成信封,我做了两件不同的事情:

  • 找到点群的凸包;和
  • 找到代表每个数据集中2个标准偏差的椭圆体。

当我在线性缩放图中使用这些方法时,一切都很好,它们有时也会在对数 - 对数范围内工作。这里有两个例子:

enter image description here

enter image description here

在其他情况下,我的方法不起作用,即阴影区域计算正常,但它们的对数 - 对数比例图是错误的(线性比例的图总是很好)。这里有一些例子:

enter image description here

enter image description here

问题:

  • 使用对数轴时,Paths/Patches中的matplotlib图是否有问题?

  • 有没有办法做预期的情节?

修改

我添加了@farenorth建议的工作代码。我注意到负值是有问题的。在多边形(凸壳)的情况下,这不是问题,因为材料属性倾向于正,但有some exceptions。对于这种情况,我认为使用symlog缩放就足够了。

对于省略号,我需要更多地考虑我的实现。它基于使用以算术平均值为中心的椭圆,2*inc标准偏差作为半轴。这可能导致某些区域的负值(因为数据不一定围绕均值对称)。可以下载数据文件here

import numpy as np
from scipy.spatial import ConvexHull
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
from matplotlib import rcParams

rcParams['font.family'] = 'serif'
rcParams['font.size'] = 16


def poly_enclose(points, color, inc=1.2, rad=0.3, lw=2):
    """
    Plot the convex hull around a set of points as a 
    shaded polygon.
    """
    hull = ConvexHull(points)


    cent = np.mean(points, 0)
    pts = []
    for pt in points[hull.simplices]:
        pts.append(pt[0].tolist())
        pts.append(pt[1].tolist())

    pts.sort(key=lambda p: np.arctan2(p[1] - cent[1],
                                    p[0] - cent[0]))
    pts = pts[0::2]  # Deleting duplicates
    pts.insert(len(pts), pts[0])


    verts = inc*(np.array(pts)- cent) + cent
    verts2 = np.zeros((3*verts.shape[0]-2,2))
    verts2[0::3] = verts
    verts2[1::3,:] = (1-rad)*verts[0:-1,:] + rad*verts[1:,:]
    verts2[2::3,:] = rad*verts[0:-1,:] + (1-rad)*verts[1:,:]
    verts2[0:-1] = verts2[1:]
    verts2[-1] = verts2[0]

    codes = [Path.MOVETO, Path.LINETO, Path.CURVE3,]
    for j in range(len(pts)-2):
        codes.extend([Path.CURVE3, Path.LINETO, Path.CURVE3,])
    codes.append(Path.CURVE3)


    path = Path(verts2, codes)
    patch = patches.PathPatch(path, facecolor=color, lw=0, alpha=0.2)
    edge = patches.PathPatch(path, edgecolor=color, facecolor='none', lw=lw)
    plt.gca().add_patch(patch)
    plt.gca().add_patch(edge)


def ellip_enclose(points, color, inc=1.2, lw=2, nst=2):
    """
    Plot the minimum ellipse around a set of points.

    Based on: 
    https://github.com/joferkington/oost_paper_code/blob/master/error_ellipse.py
    """

    def eigsorted(cov):
        vals, vecs = np.linalg.eigh(cov)
        order = vals.argsort()[::-1]
        return vals[order], vecs[:,order]

    x = points[:,0]
    y = points[:,1]
    cov = np.cov(x, y)
    vals, vecs = eigsorted(cov)
    theta = np.degrees(np.arctan2(*vecs[:,0][::-1]))
    w, h = 2 * nst * np.sqrt(vals)        
    center = np.mean(points, 0)
    ell = patches.Ellipse(center, width=inc*w, height=inc*h, angle=theta,
                          facecolor=color, alpha=0.2, lw=0)
    edge = patches.Ellipse(center, width=inc*w, height=inc*h, angle=theta,
                          facecolor='none', edgecolor=color, lw=lw)
    plt.gca().add_artist(ell)
    plt.gca().add_artist(edge)


inc = 1.2
rad = 0.3
lw = 2
colors = ['blue', 'green', 'red', 'orange']

## Example 1
plt.figure()
for k in range(4):
    points = 1.5*(np.random.rand(20, 2) - 0.5) + k + 3
    plt.plot(points[:,0], points[:,1], 'o', ms=8, color=colors[k],
             mfc="white", mec=colors[k])
    poly_enclose(points, colors[k], inc=inc, rad=rad, lw=lw)
##    ellip_enclose(points, colors[k], inc=inc, lw=lw)

#plt.xscale('symlog')
#plt.yscale('symlog')
plt.grid(True)

##  Example 2
E = {}
E["poly"] = np.loadtxt('young_poly.txt')
E["metals"] = np.loadtxt('young_metals.txt')
E["comp"] = np.loadtxt('young_comp.txt')
E["ceramic"] = np.loadtxt('young_ceramic.txt')

rho = {}
rho["poly"] = np.loadtxt('dens_poly.txt')
rho["metals"] = np.loadtxt('dens_metals.txt')
rho["comp"] = np.loadtxt('dens_comp.txt')
rho["ceramic"] = np.loadtxt('dens_ceramic.txt')

plt.figure()
for k, key  in enumerate(E.keys()):
    x = rho[key][:,0] * 1000
    y = E[key][:,0] * 1e9
    points = np.vstack([x,y]).T
    poly_enclose(points, colors[k], inc=inc, rad=0.3, lw=lw)
##    ellip_enclose(points, colors[k], inc=1, lw=lw)
    plt.plot(x, y, 'o', ms=8, color=colors[k], mfc="white", mec=colors[k])

##plt.xscale('symlog')
##plt.yscale('symlog')
plt.grid(True)

plt.show()

1 个答案:

答案 0 :(得分:1)

您的代码中有两个地方用于假设线性图:计算点云的中心为线性平均值(numpy.mean),常数系数(inc)乘以每个点之间的差值向量和计算中心。

如果你想要视觉证据,可以尝试在对数图上绘制点云平均中心的简单练习,它应该偏离中心,偏向顶部和右边。类似地,如果你将你的点云乘以一个常数系数并绘制两个版本的图形,你就不会看到一个更大的"更大的预期线性膨胀。云,但实际上看起来更像是在原始云的右上角附近流失的点。

如果在取平均值并缩放差异向量之前将点转换为对数形式,然后在绘图之前将点更改回线性形式,那么您将获得与线性图上看到的结果相同的结果。这可以通过向函数添加额外标志或通过让原始函数返回形状坐标而不是直接绘制它们来实现。后一种情况对我来说似乎更清晰。

例如:

def poly_enclose(...):
    ....
    return patch, edge

log_points = np.log(points)
log_patch, log_edge = poly_enclose(log_points, colors[k], inc=inc, rad=0.3, lw=lw)
lin_patch = np.exp(log_patch)
lin_edge = np.exp(log_edge)
plt.gca().add_patch(lin_patch)
plt.gca().add_patch(lin_edge)