【问题标题】:surface plots in matplotlibmatplotlib 中的曲面图
【发布时间】:2012-02-28 13:28:02
【问题描述】:

我有一个 3 元组列表,表示 3D 空间中的一组点。我想绘制一个覆盖所有这些点的曲面。

mplot3d 包中的 plot_surface 函数要求 X、Y 和 Z 为二维数组作为参数。 plot_surface 是绘制表面的正确函数吗?如何将我的数据转换为所需的格式?

data = [(x1,y1,z1),(x2,y2,z2),.....,(xn,yn,zn)]

【问题讨论】:

标签: python numpy matplotlib surface


【解决方案1】:

对于表面,它与 3 元组列表有点不同,您应该在 2d 数组中为域传递一个网格。

如果您只有一个 3d 点列表,而不是某个函数f(x, y) -> z,那么您将遇到问题,因为有多种方法可以将 3d 点云三角剖分到一个表面中。

这是一个光滑表面的例子:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D  
# Axes3D import has side effects, it enables using projection='3d' in add_subplot
import matplotlib.pyplot as plt
import random

def fun(x, y):
    return x**2 + y

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-3.0, 3.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array(fun(np.ravel(X), np.ravel(Y)))
Z = zs.reshape(X.shape)

ax.plot_surface(X, Y, Z)

ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')

plt.show()

【讨论】:

  • 嗨,谢谢。您能否详细说明拥有f(x,y) -> z 函数如何比仅使用 OP 最初使用的列表方法获得更多信息。
  • 但是当 z 是自变量而不是 x 和 y 的函数时,你会怎么做?
  • 在这种情况下,也许您应该查看plot_trisurf。但正如我所提到的,这并非易事,因为您需要对曲面进行三角测量,并且有多种解决方案。作为一个基本示例,仅考虑 (0, 0, 0.2), (0, 1, 0), (1, 1, 0.2), (1, 0, 0) 给出的 4 个点。从上面看,它只是一个带有轻微褶皱的正方形。 但是“折叠”发生在哪条对角线上? 是 0.2 处的“高”对角线还是 0 处的“低”对角线?两者都是有效的表面!因此,您需要先选择三角剖分算法,然后才能获得明确定义的解决方案。
  • 为什么from mpl_toolkits.mplot3d import Axes3D,而Axes3D在上面的代码中却没有用到?
  • 这个导入有副作用。如果没有此导入,在调用 fig.add_subplot 中使用 kwarg projection='3d' 将不可用。
【解决方案2】:

查看官方示例。 X,Y 和 Z 确实是 2d 数组,numpy.meshgrid() 是一种从 1d x 和 y 值中获取 2d x,y 网格的简单方法。

http://matplotlib.sourceforge.net/mpl_examples/mplot3d/surface3d_demo.py

这是将 3 元组转换为 3 个 1d 数组的 Pythonic 方法。

data = [(1,2,3), (10,20,30), (11, 22, 33), (110, 220, 330)]
X,Y,Z = zip(*data)
In [7]: X
Out[7]: (1, 10, 11, 110)
In [8]: Y
Out[8]: (2, 20, 22, 220)
In [9]: Z
Out[9]: (3, 30, 33, 330)

这是 mtaplotlib delaunay 三角剖分(插值),它将 1d x,y,z 转换为兼容的东西 (?):

http://matplotlib.sourceforge.net/api/mlab_api.html#matplotlib.mlab.griddata

【讨论】:

  • 否...在该示例中 X Y Z 是二维的。
  • 我的立场是正确的。如果您的数据是均匀分布的,请使用 meshgrid(),如链接示例中所示。插值例如如果您的数据不是均匀分布的,则使用 griddata()。
【解决方案3】:

在 Matlab 中,我只在 xy 坐标(不是 z)上使用 delaunay 函数做了类似的事情,然后使用 trimeshtrisurf 进行绘图,使用 z 作为高度。

SciPy 有 Delaunay 类,它基于与 Matlab 的 delaunay 函数相同的底层 QHull 库,因此您应该得到相同的结果。

从那里开始,只需几行代码即可将此 Plotting 3D Polygons in python-matplotlib 示例转换为您希望实现的目标,因为 Delaunay 为您提供了每个三角形多边形的规格。

【讨论】:

【解决方案4】:

我刚刚遇到了同样的问题。我有 3 个 1-D 数组中的均匀间隔数据,而不是 matplotlibplot_surface 想要的 2-D 数组。我的数据碰巧在pandas.DataFrame 中,所以这里是matplotlib.plot_surface example,经过修改以绘制 3 个一维数组。

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np

X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)

fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
    linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)

ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

fig.colorbar(surf, shrink=0.5, aspect=5)
plt.title('Original Code')

这是原始示例。添加下一位会从 3 个一维数组创建相同的图。

# ~~~~ MODIFICATION TO EXAMPLE BEGINS HERE ~~~~ #
import pandas as pd
from scipy.interpolate import griddata
# create 1D-arrays from the 2D-arrays
x = X.reshape(1600)
y = Y.reshape(1600)
z = Z.reshape(1600)
xyz = {'x': x, 'y': y, 'z': z}

# put the data into a pandas DataFrame (this is what my data looks like)
df = pd.DataFrame(xyz, index=range(len(xyz['x']))) 

# re-create the 2D-arrays
x1 = np.linspace(df['x'].min(), df['x'].max(), len(df['x'].unique()))
y1 = np.linspace(df['y'].min(), df['y'].max(), len(df['y'].unique()))
x2, y2 = np.meshgrid(x1, y1)
z2 = griddata((df['x'], df['y']), df['z'], (x2, y2), method='cubic')

fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x2, y2, z2, rstride=1, cstride=1, cmap=cm.coolwarm,
    linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)

ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

fig.colorbar(surf, shrink=0.5, aspect=5)
plt.title('Meshgrid Created from 3 1D Arrays')
# ~~~~ MODIFICATION TO EXAMPLE ENDS HERE ~~~~ #

plt.show()

以下是结果数据:

【讨论】:

  • 我想知道是否可以去除表面上出现的线条(上图),我的意思是是否可以让表面看起来有光泽而不是鳞片状?谢谢。 @stvn66
  • @diffracteD,尝试使用较小的网格尺寸。我几乎可以肯定这就是设置轮廓之间宽度的原因。通过在更精细的网格上进行评估,您实际上应该减小“像素大小”并提高分辨率,从而接近更平滑的渐变。
  • 有没有办法根据具体的类别给上面的表面上色?例如。 类别 x, y, z 是数据格式,我想根据特定类别为通过 x,y,z 的表面着色。
  • @RudreshAjgaonkar,您应该能够使用三个单独的绘图命令,一个用于您的每个类别,使用您想要的三个颜色中的每一个。
  • 你能提供一个示例代码吗?我对 matplotlib 和 python 很陌生。
【解决方案5】:

您可以直接从某个文件中读取数据并进行绘图

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from sys import argv

x,y,z = np.loadtxt('your_file', unpack=True)

fig = plt.figure()
ax = Axes3D(fig)
surf = ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.1)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.savefig('teste.pdf')
plt.show()

如有必要,您可以传递 vmin 和 vmax 来定义颜色条范围,例如

surf = ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.1, vmin=0, vmax=2000)

奖金部分

我想知道如何做一些交互式绘图,在这种情况下使用人工数据

from __future__ import print_function
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets
from IPython.display import Image

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

def f(x, y):
    return np.sin(np.sqrt(x ** 2 + y ** 2))

def plot(i):

    fig = plt.figure()
    ax = plt.axes(projection='3d')

    theta = 2 * np.pi * np.random.random(1000)
    r = i * np.random.random(1000)
    x = np.ravel(r * np.sin(theta))
    y = np.ravel(r * np.cos(theta))
    z = f(x, y)

    ax.plot_trisurf(x, y, z, cmap='viridis', edgecolor='none')
    fig.tight_layout()

interactive_plot = interactive(plot, i=(2, 10))
interactive_plot

【讨论】:

  • 严格来说,这里不需要pandas。
  • 我很难重现这个情节。实现这一目标的一些(较小的)样本值是多少?
【解决方案6】:

为了插话,伊曼纽尔得到了我(可能还有其他许多人)正在寻找的答案。如果您在 3 个单独的数组中有 3d 分散数据,那么 pandas 是一个令人难以置信的帮助,并且比其他选项工作得更好。详细地说,假设您的 x,y,z 是一些任意变量。在我的情况下,这些是 c、gamma 和错误,因为我正在测试支持向量机。绘制数据有很多潜在的选择:

  • scatter3D(cParams, gammas, avg_errors_array) - 这可行,但过于简单
  • plot_wireframe(cParams, gammas, avg_errors_array) - 这行得通,但如果您的数据没有很好地排序,就会看起来很丑,因为大量真实科学数据可能会出现这种情况
  • ax.plot3D(cParams, gammas, avg_errors_array) - 类似于线框

数据的线框图

数据的 3d 散点图

代码如下所示:

    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.set_xlabel('c parameter')
    ax.set_ylabel('gamma parameter')
    ax.set_zlabel('Error rate')
    #ax.plot_wireframe(cParams, gammas, avg_errors_array)
    #ax.plot3D(cParams, gammas, avg_errors_array)
    #ax.scatter3D(cParams, gammas, avg_errors_array, zdir='z',cmap='viridis')

    df = pd.DataFrame({'x': cParams, 'y': gammas, 'z': avg_errors_array})
    surf = ax.plot_trisurf(df.x, df.y, df.z, cmap=cm.jet, linewidth=0.1)
    fig.colorbar(surf, shrink=0.5, aspect=5)    
    plt.savefig('./plots/avgErrs_vs_C_andgamma_type_%s.png'%(k))
    plt.show()

这是最终输出:

【讨论】:

    【解决方案7】:

    无法使用您的数据直接制作 3d 表面。我建议您使用pykridge 等工具构建插值模型。该过程将包括三个步骤:

    1. 使用pykridge 训练插值模型
    2. 使用meshgridXY 构建一个网格
    3. Z 插值

    已经创建了您的网格和相应的Z 值,现在您可以使用plot_surface。请注意,根据数据的大小,meshgrid 函数可以运行一段时间。解决方法是使用np.linspaceXY 轴创建均匀间隔的样本,然后应用插值来推断必要的Z 值。如果是这样,则插值可能与原始 Z 不同,因为 XY 已更改。

    【讨论】:

      【解决方案8】:

      只是添加一些进一步的想法,这可能会帮助其他有不规则域类型问题的人。对于用户具有三个向量/列表的情况,x,y,z 表示 2D 解决方案,其中 z 将绘制在矩形网格上作为表面,ArtifixR 的“plot_trisurf()”cmets 是适用的。一个类似但非矩形域的例子是:

      import matplotlib.pyplot as plt
      from matplotlib import cm
      from mpl_toolkits.mplot3d import Axes3D 
      
      # problem parameters
      nu = 50; nv = 50
      u = np.linspace(0, 2*np.pi, nu,) 
      v = np.linspace(0, np.pi, nv,)
      
      xx = np.zeros((nu,nv),dtype='d')
      yy = np.zeros((nu,nv),dtype='d')
      zz = np.zeros((nu,nv),dtype='d')
      
      # populate x,y,z arrays
      for i in range(nu):
        for j in range(nv):
          xx[i,j] = np.sin(v[j])*np.cos(u[i])
          yy[i,j] = np.sin(v[j])*np.sin(u[i])
          zz[i,j] = np.exp(-4*(xx[i,j]**2 + yy[i,j]**2)) # bell curve
      
      # convert arrays to vectors
      x = xx.flatten()
      y = yy.flatten()
      z = zz.flatten()
      
      # Plot solution surface
      fig = plt.figure(figsize=(6,6))
      ax = Axes3D(fig)
      ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0,
                      antialiased=False)
      ax.set_title(r'trisurf example',fontsize=16, color='k')
      ax.view_init(60, 35)
      fig.tight_layout()
      plt.show()
      

      以上代码产生:

      但是,这可能无法解决所有问题,尤其是在问题定义在不规则域上的情况下。此外,在域具有一个或多个凹面区域的情况下,delaunay 三角剖分可能会导致在域外部生成虚假三角形。在这种情况下,必须从三角剖分中移除这些流氓三角形,以实现正确的表面表示。对于这些情况,用户可能必须明确包括 delaunay 三角剖分计算,以便可以通过编程方式删除这些三角形。在这种情况下,下面的代码可以代替之前的情节代码:

      
      import matplotlib.tri as mtri 
      import scipy.spatial
      # plot final solution
      pts = np.vstack([x, y]).T
      tess = scipy.spatial.Delaunay(pts) # tessilation
      
      # Create the matplotlib Triangulation object
      xx = tess.points[:, 0]
      yy = tess.points[:, 1]
      tri = tess.vertices # or tess.simplices depending on scipy version
      
      #############################################################
      # NOTE: If 2D domain has concave properties one has to
      #       remove delaunay triangles that are exterior to the domain.
      #       This operation is problem specific!
      #       For simple situations create a polygon of the
      #       domain from boundary nodes and identify triangles
      #       in 'tri' outside the polygon. Then delete them from
      #       'tri'.
      #       <ADD THE CODE HERE>
      #############################################################
      
      triDat = mtri.Triangulation(x=pts[:, 0], y=pts[:, 1], triangles=tri)
      
      # Plot solution surface
      fig = plt.figure(figsize=(6,6))
      ax = fig.gca(projection='3d')
      ax.plot_trisurf(triDat, z, linewidth=0, edgecolor='none',
                      antialiased=False, cmap=cm.jet)
      ax.set_title(r'trisurf with delaunay triangulation', 
                fontsize=16, color='k')
      plt.show()
      

      下面给出的示例图说明了解决方案 1) 带有虚假三角形的解决方案,以及 2) 它们已被移除的位置:

      希望以上对解数据有凹凸情况的人有所帮助。

      【讨论】:

        【解决方案9】:

        这不是一个通用的解决方案,但可能会帮助许多刚在 Google 中输入“matplotlib surface plot”并登陆这里的人。

        假设您有data = [(x1,y1,z1),(x2,y2,z2),.....,(xn,yn,zn)],那么您可以使用x, y, z = zip(*data) 获得三个一维列表。现在你当然可以create 3d scatterplot 使用三个一维列表。

        但是,为什么一般不能使用这些数据来创建曲面图?要理解这一点,请考虑一个空的 3-d 图:

        现在,假设对于“离散”常规网格上的 (x, y) 的每个可能值,您都有一个 z 值,那么就没有问题了,实际上您可以得到一个曲面图:

        import numpy as np
        from matplotlib import pyplot as plt
        from mpl_toolkits.mplot3d import Axes3D
        from matplotlib import cm
        
        x = np.linspace(0, 10, 6)  # [0, 2,..,10] : 6 distinct values
        y = np.linspace(0, 20, 5)  # [0, 5,..,20] : 5 distinct values
        z = np.linspace(0, 100, 30)  # 6 * 5 = 30 values, 1 for each possible combination of (x,y)
        
        X, Y = np.meshgrid(x, y)
        Z = np.reshape(z, X.shape)  # Z.shape must be equal to X.shape = Y.shape
        
        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')
        
        ax.plot_surface(X, Y, Z)
        
        ax.set_xlabel('X Label')
        ax.set_ylabel('Y Label')
        ax.set_zlabel('Z Label')
        plt.show()
        

        如果 (x, y) 的所有可能组合都没有 z,会发生什么?然后在该点(在上面空白图中 x-y 平面上两条黑线的交点处),我们不知道 z 的值是多少。它可以是任何东西,我们不知道那个时候我们的表面应该有多“高”或“低”(尽管它可以使用其他函数来近似,surface_plot 要求您提供 X.shape = Y 的参数.shape = Z.shape)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-02-26
          • 2016-10-11
          • 1970-01-01
          • 2017-06-01
          • 1970-01-01
          相关资源
          最近更新 更多