tracés de surface dans matplotlib

104

J'ai une liste de 3 tuples représentant un ensemble de points dans l'espace 3D. Je veux tracer une surface qui couvre tous ces points.

La plot_surfacefonction du mplot3dpackage nécessite que les arguments X, Y et Z soient des tableaux 2d. Est-ce que plot_surfacela bonne fonction pour tracer la surface et comment transformer mes données au format requis?

data = [(x1,y1,z1),(x2,y2,z2),.....,(xn,yn,zn)]
Graddy
la source
Veuillez commencer à marquer la surface de tous ces doublons et fermer les doublons les uns dans les autres. Balisez également numpy , mesh pour ceux qui concernent la génération de maillage.
smci

Réponses:

120

Pour les surfaces, c'est un peu différent d'une liste de 3-tuples, vous devriez passer dans une grille pour le domaine dans des tableaux 2D.

Si tout ce que vous avez est une liste de points 3D, plutôt qu'une fonction f(x, y) -> z, vous aurez un problème car il existe plusieurs façons de trianguler ce nuage de points 3D en une surface.

Voici un exemple de surface lisse:

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
la source
1
Salut, merci pour cela. Pouvez-vous s'il vous plaît expliquer comment avoir une fonction f(x,y) -> zvous apporte plus d'informations que la simple utilisation d'une approche de liste comme l'OP avait initialement.
Gregory Kuhn
16
Mais que faites-vous lorsque z est une variable indépendante et non une fonction de x et y?
Labibah
4
Dans ce cas, vous devriez peut-être regarder à la plot_trisurfplace. Mais comme je l'ai mentionné, ce n'est pas trivial car vous devez trianguler la surface et il existe plusieurs solutions. À titre d'exemple de base, considérons seulement les 4 points donnés par (0, 0, 0,2), (0, 1, 0), (1, 1, 0,2), (1, 0, 0). Vu d'en haut, il ressemble juste à un carré avec un léger pli. Mais le long de quelle diagonale se produit le «pli»? Est-ce la diagonale «haute» à 0,2 ou la diagonale «basse» à 0? Les deux sont des surfaces valides! Vous devez donc choisir un algorithme de triangulation avant d'avoir une solution bien définie.
wim
Pourquoi de mpl_toolkits.mplot3d importer Axes3D, mais Axes3D n'est utilisé nulle part dans le code ci-dessus?
絢 瀬 絵 里
5
Cette importation a des effets secondaires. L'utilisation de kwarg projection='3d'dans l'appel fig.add_subplotne sera pas disponible sans cette importation.
wim
34

Vous pouvez lire les données directement à partir d'un fichier et d'un tracé

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 nécessaire, vous pouvez passer vmin et vmax pour définir la plage de la barre de couleurs, par exemple

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

surface

Section bonus

Je me demandais comment faire des tracés interactifs, dans ce cas avec des données artificielles

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
la source
5
à proprement parler, les pandas sont inutiles ici.
downer le
J'ai du mal à reproduire cette intrigue. Quels seraient quelques exemples de valeurs (plus petites) pour y parvenir?
JRsz
21

Je viens de rencontrer ce même problème. J'ai des données uniformément espacées qui sont dans 3 tableaux 1-D au lieu des tableaux 2-D qui matplotlible plot_surfacesouhaitent. Mes données se trouvaient dans un pandas.DataFramealors voici l' matplotlib.plot_surfaceexemple avec les modifications pour tracer 3 tableaux 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')

C'est l'exemple original. L'ajout de ce bit suivant crée le même tracé à partir de 3 tableaux 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()

Voici les chiffres obtenus:

entrez la description de l'image ici entrez la description de l'image ici

Steven C. Howell
la source
Je me demandais s'il est possible de supprimer les lignes venant sur la surface (l'image ci-dessus), je veux dire est-il possible de donner à la surface un aspect brillant au lieu d'un aspect écailleux? Merci. @ stvn66
diffracteD
@diffracteD, essayez d'utiliser une taille de grille plus petite. Je suis presque certain que c'est ce qui définit la largeur entre les contours. En évaluant sur une grille plus fine, vous devriez essentiellement diminuer la «taille de pixel» et augmenter la résolution, en approchant un dégradé plus lisse.
Steven C. Howell
Existe-t-il un moyen de colorer la surface ci-dessus selon des catégories spécifiques? Par ex. La catégorie x, y, z est le format des données et je voudrais colorier la surface passant par x, y, z selon une catégorie particulière.
Rudresh Ajgaonkar
@RudreshAjgaonkar, vous devriez pouvoir utiliser trois commandes de tracé distinctes, une pour chacune de vos catégories, en utilisant la couleur que vous voulez pour chacune des trois.
Steven C. Howell
pouvez-vous fournir un exemple de code s'il vous plaît? je suis assez nouveau sur matplotlib et python.
Rudresh Ajgaonkar
4

Juste pour intervenir, Emanuel avait la réponse que je (et probablement beaucoup d'autres) recherchaient. Si vous avez des données dispersées en 3D dans 3 tableaux séparés, pandas est une aide incroyable et fonctionne beaucoup mieux que les autres options. Pour élaborer, supposons que vos x, y, z sont des variables arbitraires. Dans mon cas, il s'agissait de c, gamma et erreurs parce que je testais une machine vectorielle de support. Il existe de nombreux choix potentiels pour tracer les données:

  • scatter3D (cParams, gammas, avg_errors_array) - cela fonctionne mais est trop simpliste
  • plot_wireframe (cParams, gammas, avg_errors_array) - cela fonctionne, mais aura l'air moche si vos données ne sont pas bien triées, comme c'est potentiellement le cas avec d'énormes morceaux de données scientifiques réelles
  • ax.plot3D (cParams, gammas, avg_errors_array) - similaire à wireframe

Tracé filaire des données

Tracé filaire des données

Diffusion 3D des données

Diffusion 3D des données

Le code ressemble à ceci:

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

Voici la sortie finale:

plot_trisurf des données xyz

ArtifexR
la source
3

consultez l'exemple officiel. X, Y et Z sont en effet des tableaux 2d, numpy.meshgrid () est un moyen simple d'obtenir un maillage 2d x, y à partir des valeurs 1d x et y.

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

voici un moyen pythonique de convertir vos 3 tuples en 3 tableaux 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)

Voici la triangulation delaunay mtaplotlib (interpolation), elle convertit 1d x, y, z en quelque chose de conforme (?):

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

Dima Tisnek
la source
Non ... XYZ sont à 2 dimensions dans cet exemple.
wim
Je me suis trompé. Utilisez meshgrid () si vos données sont uniformément espacées, comme dans l'exemple lié. Interpolez par exemple avec griddata () si vos données ne sont pas uniformément espacées.
Dima Tisnek
1

Dans Matlab, j'ai fait quelque chose de similaire en utilisant la delaunayfonction sur x, ycoords uniquement (pas le z), puis en traçant avec trimeshou trisurf, en utilisantz comme hauteur.

SciPy a la classe Delaunay , qui est basée sur la même bibliothèque QHull sous-jacente que le Matlabdelaunay fonction , vous devriez donc obtenir des résultats identiques.

À partir de là, il devrait y avoir quelques lignes de code pour convertir cet exemple de tracé de polygones 3D en python-matplotlib en ce que vous souhaitez atteindre, comme Delaunayvous le donne la spécification de chaque polygone triangulaire.

Evgeni Sergeev
la source
Voir cette réponse basée sur ax.plot_trisurf(..).
Evgeni Sergeev
1

Juste pour ajouter quelques réflexions supplémentaires qui peuvent aider les autres avec des problèmes de type de domaine irrégulier. Pour une situation où l'utilisateur dispose de trois vecteurs / listes, x, y, z représentant une solution 2D où z doit être tracé sur une grille rectangulaire comme une surface, les commentaires 'plot_trisurf ()' par ArtifixR sont applicables. Un exemple similaire mais avec un domaine non rectangulaire est:

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

Le code ci-dessus produit:

Tracé de surface pour problème de grille non rectangulaire

Cependant, cela peut ne pas résoudre tous les problèmes, en particulier lorsque le problème est défini sur un domaine irrégulier. De même, dans le cas où le domaine a une ou plusieurs zones concaves, la triangulation de delaunay peut conduire à générer des triangles parasites extérieurs au domaine. Dans de tels cas, ces triangles non fiables doivent être supprimés de la triangulation afin d'obtenir la représentation de surface correcte. Pour ces situations, l'utilisateur peut avoir à inclure explicitement le calcul de triangulation de delaunay afin que ces triangles puissent être supprimés par programme. Dans ces circonstances, le code suivant pourrait remplacer le code de tracé précédent:


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

Des exemples de graphiques sont donnés ci-dessous, illustrant la solution 1) avec des triangles parasites, et 2) où ils ont été supprimés:

entrez la description de l'image ici

triangles supprimés

J'espère que ce qui précède peut être utile aux personnes présentant des situations de concavité dans les données de la solution.

Graham G
la source
0

Il n'est pas possible de créer directement une surface 3D en utilisant vos données. Je vous recommande de créer un modèle d'interpolation à l'aide de certains outils comme pykridge . Le processus comprendra trois étapes:

  1. Former un modèle d'interpolation à l'aide de pykridge
  2. Construire une grille à partir de XetY utilisantmeshgrid
  3. Interpoler les valeurs pour Z

Après avoir créé votre grille et les Zvaleurs correspondantes , vous êtes maintenant prêt à partir plot_surface. Notez que selon la taille de vos données, la meshgridfonction peut s'exécuter pendant un certain temps. La solution de contournement consiste à créer des échantillons régulièrement espacés à l'aide np.linspacedes axes for Xet Y, puis d'appliquer une interpolation pour déduire les Zvaleurs nécessaires . Si tel est le cas, les valeurs interpolées peuvent différer de l'original Zcar Xet Yont changé.

lenhhoxung
la source