parcelas de superficie en matplotlib

104

Tengo una lista de 3 tuplas que representan un conjunto de puntos en el espacio 3D. Quiero trazar una superficie que cubra todos estos puntos.

La plot_surfacefunción del mplot3dpaquete requiere que los argumentos X, Y y Z sean matrices 2d. ¿Es plot_surfacela función correcta para trazar la superficie y cómo transformo mis datos en el formato requerido?

data = [(x1,y1,z1),(x2,y2,z2),.....,(xn,yn,zn)]
Graddy
fuente
Comience a etiquetar todas las superficies duplicadas y cierre los duplicados entre sí. También etiqueta numpy , mesh para los que tratan sobre la generación de meshgrid.
smci

Respuestas:

120

Para las superficies es un poco diferente a una lista de 3 tuplas, debe pasar una cuadrícula para el dominio en matrices 2d.

Si todo lo que tiene es una lista de puntos 3D, en lugar de alguna función f(x, y) -> z, entonces tendrá un problema porque hay varias formas de triangular esa nube de puntos 3D en una superficie.

Aquí hay un ejemplo de superficie 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
fuente
1
Hola gracias por esto ¿Puede explicar cómo tener una función f(x,y) -> zle brinda más información que simplemente usar un enfoque de lista como el que tenía inicialmente el OP?
Gregory Kuhn
16
Pero, ¿qué haces cuando z es una variable independiente y no una función de x e y?
Labibah
4
En este caso, quizás debas mirar en su plot_trisurflugar. Pero como he mencionado, no es trivial porque necesita triangular la superficie y hay múltiples soluciones. Como ejemplo básico, considere solo los 4 puntos dados por (0, 0, 0.2), (0, 1, 0), (1, 1, 0.2), (1, 0, 0). Visto desde arriba, parece un cuadrado con un ligero pliegue. Pero, ¿a lo largo de qué diagonal se produce el "pliegue"? ¿Es la diagonal "alta" en 0.2 o la diagonal "baja" en 0? ¡Ambas son superficies válidas! Por lo tanto, debe elegir un algoritmo de triangulación antes de tener una solución bien definida.
wim
¿Por qué desde mpl_toolkits.mplot3d import Axes3D, pero Axes3D no se usa en ninguna parte del código anterior?
絢 瀬 絵 里
5
Esta importación tiene efectos secundarios. El uso de kwarg projection='3d'en la llamada fig.add_subplotno estará disponible sin esta importación.
wim
33

Puede leer datos directamente desde algún archivo y trazar

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

Si es necesario, puede pasar vmin y vmax para definir el rango de la barra de colores, p. Ej.

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

superficie

Sección de bonificación

Me preguntaba cómo hacer algunos gráficos interactivos, en este caso con datos artificiales.

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
fuente
5
estrictamente hablando, los pandas son innecesarios aquí.
bajón
Me cuesta reproducir esta trama. ¿Cuáles serían algunos valores de muestra (más pequeños) para lograr esto?
JRsz
21

Acabo de encontrarme con este mismo problema. He espaciadas uniformemente los datos que están en 3 arrays 1-D en lugar de las matrices de 2-D que matplotlib's plot_surfacenecesidades. Mis datos estaban en un, pandas.DataFrameasí que aquí está el matplotlib.plot_surfaceejemplo con las modificaciones para trazar 3 matrices 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')

Ese es el ejemplo original. Agregar este siguiente bit crea el mismo gráfico a partir de 3 matrices 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()

Aquí están las cifras resultantes:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Steven C. Howell
fuente
Me preguntaba si es posible eliminar las líneas que aparecen en la superficie (la imagen de arriba), quiero decir, ¿es posible darle a la superficie un aspecto brillante en lugar de un aspecto escamoso? gracias. @ stvn66
diffracteD
@diffracteD, intente usar un tamaño de cuadrícula más pequeño. Estoy casi seguro de que eso es lo que establece el ancho entre los contornos. Al evaluar en una cuadrícula más fina, esencialmente debería disminuir el "tamaño de píxel" y aumentar la resolución, acercándose a un gradiente más suave.
Steven C. Howell
¿Hay alguna forma de colorear la superficie anterior de acuerdo con categorías específicas? Por ej. La categoría x, y, z es el formato de datos y me gustaría colorear la superficie que pasa por x, y, z de acuerdo con una categoría particular.
Rudresh Ajgaonkar
@RudreshAjgaonkar, debería poder usar tres comandos de trazado separados, uno para cada una de sus categorías, usando el color que desee para cada una de las tres.
Steven C. Howell
¿Puede proporcionar un código de muestra, por favor? Soy bastante nuevo en matplotlib y python.
Rudresh Ajgaonkar
4

Solo para intervenir, Emanuel tenía la respuesta que yo (y probablemente muchos otros) estamos buscando. Si tiene datos dispersos en 3D en 3 matrices separadas, pandas es una ayuda increíble y funciona mucho mejor que las otras opciones. Para desarrollarlo, suponga que x, y, z son algunas variables arbitrarias. En mi caso, estos fueron c, gamma y errores porque estaba probando una máquina de vectores de soporte. Hay muchas opciones posibles para trazar los datos:

  • scatter3D (cParams, gammas, avg_errors_array): esto funciona pero es demasiado simplista
  • plot_wireframe (cParams, gammas, avg_errors_array): esto funciona, pero se verá feo si tus datos no están bien ordenados, como es potencialmente el caso de grandes cantidades de datos científicos reales.
  • ax.plot3D (cParams, gammas, avg_errors_array) - similar al wireframe

Gráfico de estructura alámbrica de los datos

Gráfico de estructura alámbrica de los datos

Dispersión 3D de los datos

Dispersión 3D de los datos

El código se ve así:

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

Aquí está el resultado final:

plot_trisurf de datos xyz

ArtifexR
fuente
3

mira el ejemplo oficial. X, Y y Z son de hecho matrices 2d, numpy.meshgrid () es una forma sencilla de obtener 2d x, y malla de los valores 1d xey.

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

aquí hay una forma pitónica de convertir sus 3 tuplas en matrices 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)

Aquí está la triangulación (interpolación) de mtaplotlib delaunay, convierte 1d x, y, z en algo compatible (?):

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

Dima Tisnek
fuente
No ... XYZ son bidimensionales en ese ejemplo.
wim
Me quedo corregido. Use meshgrid () si sus datos están espaciados uniformemente, como en el ejemplo vinculado. Interpolar, por ejemplo, con griddata () si sus datos no están espaciados uniformemente.
Dima Tisnek
1

En Matlab hice algo similar usando la delaunayfunción en x, ysolo coords (no en z), luego graficando con trimesho trisurf, usandoz como altura.

SciPy tiene la clase Delaunay , que se basa en la misma biblioteca QHull subyacente que la de Matlab.delaunay función , por lo que debería obtener resultados idénticos.

A partir de ahí, debería haber unas pocas líneas de código para convertir este ejemplo de Trazado de polígonos 3D en python-matplotlib en lo que desea lograr, ya que Delaunayle brinda la especificación de cada polígono triangular.

Evgeni Sergeev
fuente
Vea esta respuesta basada en ax.plot_trisurf(..).
Evgeni Sergeev
1

Solo para agregar algunas ideas adicionales que pueden ayudar a otros con problemas de tipo de dominio irregular. Para una situación en la que el usuario tiene tres vectores / listas, x, y, z que representan una solución 2D donde z debe trazarse en una cuadrícula rectangular como una superficie, los comentarios 'plot_trisurf ()' de ArtifixR son aplicables. Un ejemplo similar pero con dominio no rectangular es:

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

El código anterior produce:

Gráfico de superficie para un problema de cuadrícula no rectangular

Sin embargo, es posible que esto no resuelva todos los problemas, en particular cuando el problema se define en un dominio irregular. Además, en el caso de que el dominio tenga una o más áreas cóncavas, la triangulación delaunay puede resultar en la generación de triángulos espurios en el exterior del dominio. En tales casos, estos triángulos rebeldes deben eliminarse de la triangulación para lograr la representación de superficie correcta. Para estas situaciones, el usuario puede tener que incluir explícitamente el cálculo de triangulación delaunay para que estos triángulos se puedan eliminar mediante programación. En estas circunstancias, el siguiente código podría reemplazar el código de trazado 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()

A continuación se muestran gráficos de ejemplo que ilustran la solución 1) con triángulos falsos y 2) donde se han eliminado:

ingrese la descripción de la imagen aquí

triángulos eliminados

Espero que lo anterior pueda ser de ayuda para las personas con situaciones de concavidad en los datos de la solución.

Graham G
fuente
0

No es posible crear directamente una superficie 3D utilizando sus datos. Le recomendaría que construya un modelo de interpolación utilizando algunas herramientas como pykridge . El proceso incluirá tres pasos:

  1. Entrene un modelo de interpolación usando pykridge
  2. Construye una cuadrícula a partir Xy Yusandomeshgrid
  3. Interpolar valores para Z

Después de haber creado su cuadrícula y los Zvalores correspondientes , ahora está listo para comenzar plot_surface. Tenga en cuenta que, según el tamaño de sus datos, la meshgridfunción puede ejecutarse durante un tiempo. La solución consiste en crear muestras espaciadas uniformemente con los ejes np.linspacefor Xy Y, luego aplicar la interpolación para inferir los Zvalores necesarios . Si es así, los valores interpolados pueden diferir del original Zporque Xy Yhan cambiado.

Lenhhoxung
fuente