【问题标题】:Emulating Excel's "scatter with smooth curve" spline function in Matplotlib for 3 points在 Matplotlib 中模拟 Excel 的“平滑曲线散点”样条函数 3 点
【发布时间】:2016-05-13 16:14:52
【问题描述】:

我正在尝试模仿 Excel 的

Insert>Scatter>带有平滑线条和标记的散点图

Matplotlib 中的命令

scipy 函数 interpolate 创造了类似的效果,这里有一些很好的例子来说明如何简单地实现它: How to draw cubic spline in matplotlib

不过,Excel 的样条算法也能够仅通过三个点生成平滑曲线(例如 x = [0,1,2] y = [4,2,1]);并且不可能用三次样条来做到这一点。

我看到一些讨论建议 Excel 算法使用 Catmull-Rom 样条;但并不真正了解这些,或者它们如何适应 Matplotlib: http://answers.microsoft.com/en-us/office/forum/office_2007-excel/how-does-excel-plot-smooth-curves/c751e8ff-9f99-4ac7-a74a-fba41ac80300

有没有一种简单的方法可以使用 interpolate 库修改上述示例以实现通过三个或更多点的平滑曲线?

非常感谢

【问题讨论】:

    标签: python excel matplotlib scipy splines


    【解决方案1】:

    现在您可能已经找到了Centripetal Catmull-Rom spline 的 Wikipedia 页面,但如果您还没有,它包含以下示例代码:

    import numpy
    import matplotlib.pyplot as plt
    
    def CatmullRomSpline(P0, P1, P2, P3, nPoints=100):
      """
      P0, P1, P2, and P3 should be (x,y) point pairs that define the
      Catmull-Rom spline.
      nPoints is the number of points to include in this curve segment.
      """
      # Convert the points to numpy so that we can do array multiplication
      P0, P1, P2, P3 = map(numpy.array, [P0, P1, P2, P3])
    
      # Calculate t0 to t4
      alpha = 0.5
      def tj(ti, Pi, Pj):
        xi, yi = Pi
        xj, yj = Pj
        return ( ( (xj-xi)**2 + (yj-yi)**2 )**0.5 )**alpha + ti
    
      t0 = 0
      t1 = tj(t0, P0, P1)
      t2 = tj(t1, P1, P2)
      t3 = tj(t2, P2, P3)
    
      # Only calculate points between P1 and P2
      t = numpy.linspace(t1,t2,nPoints)
    
      # Reshape so that we can multiply by the points P0 to P3
      # and get a point for each value of t.
      t = t.reshape(len(t),1)
    
      A1 = (t1-t)/(t1-t0)*P0 + (t-t0)/(t1-t0)*P1
      A2 = (t2-t)/(t2-t1)*P1 + (t-t1)/(t2-t1)*P2
      A3 = (t3-t)/(t3-t2)*P2 + (t-t2)/(t3-t2)*P3
    
      B1 = (t2-t)/(t2-t0)*A1 + (t-t0)/(t2-t0)*A2
      B2 = (t3-t)/(t3-t1)*A2 + (t-t1)/(t3-t1)*A3
    
      C  = (t2-t)/(t2-t1)*B1 + (t-t1)/(t2-t1)*B2
      return C
    
    def CatmullRomChain(P):
      """
      Calculate Catmull Rom for a chain of points and return the combined curve.
      """
      sz = len(P)
    
      # The curve C will contain an array of (x,y) points.
      C = []
      for i in range(sz-3):
        c = CatmullRomSpline(P[i], P[i+1], P[i+2], P[i+3])
        C.extend(c)
    
      return C
    

    很好地计算 n >= 4 点的插值,如下所示:

    points = [[0,1.5],[2,2],[3,1],[4,0.5],[5,1],[6,2],[7,3]]
    c = CatmullRomChain(points)
    px, py = zip(*points)
    x, y = zip(*c)
    
    plt.plot(x, y)
    plt.plot(px, py, 'or')
    

    导致此matplotlib 图像:

    更新:

    或者,BarycentricInterpolatorscipy.interpolate 函数似乎可以满足您的需求。它使用起来相当简单,适用于只有 3 个数据点的情况。

    from scipy.interpolate import BarycentricInterpolator
    
    # create some data points
    points1 = [[0, 2], [1, 4], [2, -2], [3, 6], [4, 2]]
    points2 = [[1, 1], [2, 5], [3, -1]]
    
    # put data into x, y tuples
    x1, y1 =zip(*points1)
    x2, y2 = zip(*points2)
    
    # create the interpolator
    bci1 = BarycentricInterpolator(x1, y1)
    bci2 = BarycentricInterpolator(x2, y2)
    
    # define dense x-axis for interpolating over
    x1_new = np.linspace(min(x1), max(x1), 1000)
    x2_new = np.linspace(min(x2), max(x2), 1000)
    
    # plot it all
    plt.plot(x1, y1, 'o')
    plt.plot(x2, y2, 'o')
    plt.plot(x1_new, bci1(x1_new))
    plt.plot(x2_new, bci2(x2_new))
    plt.xlim(-1, 5)
    

    更新 2

    scipy 中的另一个选项是通过Akima1DInterpolator 进行 akima 插值。它与 Barycentric 一样易于实现,但具有避免数据集边缘出现大振荡的优点。以下是一些测试用例,它们展示了您迄今为止所要求的所有标准。

    from scipy.interpolate import Akima1DInterpolator
    
    x1, y1 = np.arange(13), np.random.randint(-10, 10, 13)
    x2, y2 = [0,2,3,6,12], [100,50,30,18,14]
    x3, y3 = [4, 6, 8], [60, 80, 40]
    
    akima1 = Akima1DInterpolator(x1, y1)
    akima2 = Akima1DInterpolator(x2, y2)
    akima3 = Akima1DInterpolator(x3, y3)
    
    x1_new = np.linspace(min(x1), max(x1), 1000)
    x2_new = np.linspace(min(x2), max(x2), 1000)
    x3_new = np.linspace(min(x3), max(x3), 1000)
    
    plt.plot(x1, y1, 'bo')
    plt.plot(x2, y2, 'go')
    plt.plot(x3, y3, 'ro')
    plt.plot(x1_new, akima1(x1_new), 'b', label='random points')
    plt.plot(x2_new, akima2(x2_new), 'g', label='exponential')
    plt.plot(x3_new, akima3(x3_new), 'r', label='3 points')
    plt.xlim(-1, 15)
    plt.ylim(-10, 110)
    plt.legend(loc='best')
    

    【讨论】:

    • 感谢您的及时回复和示例。不过,这并不能回答我原来的问题:1)样条曲线不经过第一点和最后一点 2)它不适用于 n=3 。另外,这是否意味着无法使用 interpolate 库模拟 Excel 的样条函数?如果有任何进一步的建议,我将不胜感激。
    • 参考维基百科页面,Catmull-Rom“是一种插值样条由四个控制点p0p1p2p3定义, 绘制的曲线仅从p1 绘制到p2”,因此样条线永远不会经过第一个点和最后一个点。我的猜测是,Excel 在进行样条拟合时会进行某种合理的推断,使得样条通常看起来很合适。
    • 回答你的第二个问题,我在scipy.interpolate 中找不到任何看起来像是在使用 Catmull-Rom 的东西。也就是说,还有另一种样条方法,BarycentricInterpolator,看起来它可以做你想做的事。给我一分钟更新我的答案。
    • BarycentricInterpolator 使用拉格朗日插值将单个(高次)多项式拟合到点的每个坐标。众所周知,这对于许多(比如超过 8 个)点来说是病态的。相反,其他方案使用的样条是分段低次多项式。对于一般情况,我当然不会推荐 BarycentricInterpolator。
    【解决方案2】:

    @Lanery:回复:更新 2:最好的变得更好!

    必须将列表 x2,y2,x3,y3 重新定义为 numpy 数组才能让您的示例在我的系统上运行(Spyder / Python 2.7):

    x2 = np.array([0,2,3,6,12])
    y2 = np.array([100,50,30,18,14])
    x3 = np.array([4, 6, 8])
    y3 = np.array([60, 80, 40])
    

    但现在就像做梦一样!非常感谢您的专业知识和清晰的解释。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-02
      • 2016-04-17
      • 1970-01-01
      • 2014-08-17
      • 1970-01-01
      • 2018-01-10
      • 2020-06-16
      • 2020-11-20
      相关资源
      最近更新 更多