plotagens de superfície em matplotlib

104

Tenho uma lista de 3 tuplas que representam um conjunto de pontos no espaço 3D. Quero traçar uma superfície que cubra todos esses pontos.

A plot_surfacefunção no mplot3dpacote requer que os argumentos X, Y e Z sejam matrizes 2d. É plot_surfacea função certa para plotar a superfície e como faço para transformar meus dados no formato necessário?

data = [(x1,y1,z1),(x2,y2,z2),.....,(xn,yn,zn)]
Graddy
fonte
Comece marcando todas as superfícies duplicadas e fechando as duplicatas umas nas outras. Também tag numpy , mesh para aqueles que são sobre geração de meshgrid.
smci

Respostas:

120

Para superfícies é um pouco diferente de uma lista de 3 tuplas, você deve passar uma grade para o domínio em matrizes 2d.

Se tudo o que você tem é uma lista de pontos 3d, ao invés de alguma função f(x, y) -> z, então você terá um problema porque existem várias maneiras de triangular essa nuvem de pontos 3d em uma superfície.

Aqui está um exemplo de superfície lisa:

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()

3d

wim
fonte
1
Oi, obrigado por isso. Você pode explicar como ter uma função f(x,y) -> zfornece mais informações do que simplesmente usar uma abordagem de lista como o OP inicialmente tinha.
Gregory Kuhn,
16
Mas o que você faz quando z é uma variável independente e não uma função de x e y?
Labibah,
4
Nesse caso, talvez você deva olhar em plot_trisurfvez disso. Mas, como mencionei, não é trivial porque você precisa triangular a superfície e há várias soluções. Como exemplo básico, considere apenas os 4 pontos dados por (0, 0, 0,2), (0, 1, 0), (1, 1, 0,2), (1, 0, 0). Visto de cima, parece apenas um quadrado com uma ligeira dobra. Mas ao longo de qual diagonal a "dobra" ocorre? É a diagonal "alta" em 0,2 ou a diagonal "baixa" em 0? Ambas são superfícies válidas! Portanto, você precisa escolher um algoritmo de triangulação antes de ter uma solução bem definida.
wim de
Por que de mpl_toolkits.mplot3d importar Axes3D, embora Axes3D não seja usado em nenhum lugar do código acima?
絢 瀬 絵 里
5
Essa importação tem efeitos colaterais. O uso do kwarg projection='3d'na chamada fig.add_subplotficará indisponível sem essa importação.
wim
34

Você pode ler os dados direto de algum arquivo e traçar

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()

Se necessário, você pode passar vmin e vmax para definir a faixa da barra de cores, por exemplo

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

superfície

Seção de bônus

Eu queria saber como fazer alguns gráficos interativos, neste caso com dados artificiais

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
Emanuel Fontelles
fonte
5
estritamente falando, pandas é desnecessário aqui.
downer
Tenho dificuldade em reproduzir esse enredo. Quais seriam alguns valores de amostra (menores) para conseguir isso?
JRsz
21

Acabei de encontrar o mesmo problema. I se uniformemente espaçadas de dados que é, em 3 1-matrizes d, em vez das matrizes de 2-D, que matplotlib's plot_surfacenecessidades. Meus dados estavam em um pandas.DataFrameentão aqui está o matplotlib.plot_surfaceexemplo com as modificações para plotar 3 arrays 1-D.

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')

Esse é o exemplo original. Adicionar este próximo bit cria o mesmo gráfico de 3 arrays 1-D.

# ~~~~ 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()

Aqui estão os números resultantes:

insira a descrição da imagem aqui insira a descrição da imagem aqui

Steven C. Howell
fonte
Eu queria saber se é possível remover as linhas que vêm na superfície (a imagem acima), quero dizer, é possível dar à superfície uma aparência brilhante em vez de escamosa? obrigado. @ stvn66
diffracteD
@diffracteD, tente usar um tamanho de grade menor. Tenho quase certeza de que é isso que define a largura entre os contornos. Ao avaliar em uma grade mais fina, você deve essencialmente diminuir o "tamanho do pixel" e aumentar a resolução, aproximando-se de um gradiente mais suave.
Steven C. Howell,
Existe uma maneira de colorir a superfície acima de acordo com categorias específicas? Por ex. Categoria x, y, z é o formato de dados e eu gostaria de colorir a superfície passando por x, y, z de acordo com uma categoria particular.
Rudresh Ajgaonkar de
@RudreshAjgaonkar, você deve ser capaz de usar três comandos de plot separados, um para cada uma das suas categorias, usando a cor que desejar para cada um dos três.
Steven C. Howell de
você pode fornecer um código de amostra, por favor? eu sou muito novo em matplotlib e python.
Rudresh Ajgaonkar de
4

Só para entrar na conversa, Emanuel tinha a resposta que eu (e provavelmente muitos outros) estamos procurando. Se você tiver dados espalhados em 3D em 3 arrays separados, o pandas é uma ajuda incrível e funciona muito melhor do que as outras opções. Para elaborar, suponha que x, y, z são algumas variáveis ​​arbitrárias. No meu caso, foram c, gama e erros porque estava testando uma máquina de vetores de suporte. Existem muitas opções possíveis para plotar os dados:

  • scatter3D (cParams, gammas, avg_errors_array) - isso funciona, mas é excessivamente simplista
  • plot_wireframe (cParams, gammas, avg_errors_array) - isso funciona, mas ficará feio se seus dados não forem classificados bem, como é potencialmente o caso com grandes blocos de dados científicos reais
  • ax.plot3D (cParams, gammas, avg_errors_array) - semelhante a wireframe

Gráfico de wireframe dos dados

Gráfico de wireframe dos dados

Dispersão 3D dos dados

Dispersão 3D dos dados

O código é parecido com este:

    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()

Aqui está o resultado final:

plot_trisurf de dados xyz

ArtifexR
fonte
3

verifique o exemplo oficial. X, Y e Z são de fato matrizes 2d, numpy.meshgrid () é uma maneira simples de obter a malha 2d x, y dos valores 1d xey.

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

aqui está uma maneira pythônica de converter suas 3-tuplas em arrays 3 1d.

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)

Aqui está a triangulação mtaplotlib delaunay (interpolação), ela converte 1d x, y, z em algo compatível (?):

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

Dima Tisnek
fonte
Não ... XYZ são bidimensionais nesse exemplo.
wim de
Eu estou corrigido. Use meshgrid () se seus dados estiverem uniformemente espaçados, como no exemplo vinculado. Interpole, por exemplo, com griddata () se seus dados não estiverem uniformemente espaçados.
Dima Tisnek
1

No Matlab fiz algo semelhante usando a delaunayfunção em x, ycoords only (não the z), e plotando com trimeshou trisurf, usando zcomo a altura.

SciPy tem a classe Delaunay , que é baseada na mesma biblioteca QHull subjacente que a delaunayfunção do Matlab , então você deve obter resultados idênticos.

A partir daí, deve haver algumas linhas de código para converter este exemplo de Plotagem de polígonos 3D no python-matplotlib no que você deseja alcançar, pois Delaunayfornece a especificação de cada polígono triangular.

Evgeni Sergeev
fonte
Veja esta resposta com base em ax.plot_trisurf(..).
Evgeni Sergeev
1

Apenas para adicionar mais algumas idéias que podem ajudar outras pessoas com problemas de tipo de domínio irregular. Para uma situação em que o usuário tem três vetores / listas, x, y, z representando uma solução 2D onde z deve ser plotado em uma grade retangular como uma superfície, os comentários 'plot_trisurf ()' de ArtifixR são aplicáveis. Um exemplo semelhante, mas com domínio não retangular é:

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()

O código acima produz:

Gráfico de superfície para problema de grade não retangular

No entanto, isso pode não resolver todos os problemas, especialmente quando o problema é definido em um domínio irregular. Além disso, no caso em que o domínio tem uma ou mais áreas côncavas, a triangulação delaunay pode resultar na geração de triângulos espúrios exteriores ao domínio. Em tais casos, esses triângulos errados devem ser removidos da triangulação para obter a representação correta da superfície. Para essas situações, o usuário pode ter que incluir explicitamente o cálculo de triangulação delaunay para que esses triângulos possam ser removidos programaticamente. Nessas circunstâncias, o código a seguir pode substituir o código de plotagem anterior:


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()

Os gráficos de exemplo são fornecidos abaixo, ilustrando a solução 1) com triângulos espúrios e 2) onde eles foram removidos:

insira a descrição da imagem aqui

triângulos removidos

Espero que o exposto acima possa ajudar as pessoas com situações de concavidade nos dados da solução.

Graham G
fonte
0

Não é possível fazer diretamente uma superfície 3D usando seus dados. Eu recomendaria que você construísse um modelo de interpolação usando algumas ferramentas como o pykridge . O processo incluirá três etapas:

  1. Treine um modelo de interpolação usando pykridge
  2. Construir uma grade de Xe Yusandomeshgrid
  3. Interpolar valores para Z

Tendo criado sua grade e os Zvalores correspondentes , agora você está pronto para prosseguir plot_surface. Observe que, dependendo do tamanho dos seus dados, a meshgridfunção pode ser executada por um tempo. A solução alternativa é criar amostras com espaçamento uniforme usando os eixos np.linspacefor Xe e Y, em seguida, aplicar a interpolação para inferir os Zvalores necessários . Nesse caso, os valores interpolados podem ser diferentes do original Zporque Xe Yforam alterados.

Lenhhoxung
fonte