web-dev-qa-db-fra.com

matplotlib (longueur unitaire égale): avec un rapport d'aspect «égal», l'axe z n'est pas égal à x et y

Lorsque je configure un rapport d'aspect égal pour le graphique 3D, l'axe z ne change pas en "égal". Donc ça:

fig = pylab.figure()
mesFig = fig.gca(projection='3d', adjustable='box')
mesFig.axis('equal')
mesFig.plot(xC, yC, zC, 'r.')
mesFig.plot(xO, yO, zO, 'b.')
pyplot.show()

me donne ce qui suit: enter image description here

où évidemment la longueur unitaire de l'axe z n'est pas égale aux unités x et y.

Comment puis-je égaliser la longueur unitaire des trois axes? Toutes les solutions que j'ai pu trouver n'ont pas fonctionné. Merci.

70
user1329187

Je crois que matplotlib ne définit pas encore correctement un axe égal en 3D ... Mais j'ai trouvé il y a quelques temps (je ne me souviens plus où) que je me suis adapté en l'utilisant. Le concept est de créer une fausse boîte englobante cubique autour de vos données. Vous pouvez le tester avec le code suivant:

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

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')

X = np.random.Rand(100)*10+5
Y = np.random.Rand(100)*5+2.5
Z = np.random.Rand(100)*50+25

scat = ax.scatter(X, Y, Z)

# Create cubic bounding box to simulate equal aspect ratio
max_range = np.array([X.max()-X.min(), Y.max()-Y.min(), Z.max()-Z.min()]).max()
Xb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][0].flatten() + 0.5*(X.max()+X.min())
Yb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][1].flatten() + 0.5*(Y.max()+Y.min())
Zb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][2].flatten() + 0.5*(Z.max()+Z.min())
# Comment or uncomment following both lines to test the fake bounding box:
for xb, yb, zb in Zip(Xb, Yb, Zb):
   ax.plot([xb], [yb], [zb], 'w')

plt.grid()
plt.show()

les données z sont d'un ordre de grandeur supérieur à x et y, mais même avec une option d'axe égal, l'axe z de matplotlib met automatiquement à l'échelle:

bad

Mais si vous ajoutez la boîte englobante, vous obtenez une mise à l'échelle correcte:

enter image description here

57
Remy F

J'ai simplifié la solution de Remy F en utilisant le set_x/y/zlimfonctions .

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

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')

X = np.random.Rand(100)*10+5
Y = np.random.Rand(100)*5+2.5
Z = np.random.Rand(100)*50+25

scat = ax.scatter(X, Y, Z)

max_range = np.array([X.max()-X.min(), Y.max()-Y.min(), Z.max()-Z.min()]).max() / 2.0

mid_x = (X.max()+X.min()) * 0.5
mid_y = (Y.max()+Y.min()) * 0.5
mid_z = (Z.max()+Z.min()) * 0.5
ax.set_xlim(mid_x - max_range, mid_x + max_range)
ax.set_ylim(mid_y - max_range, mid_y + max_range)
ax.set_zlim(mid_z - max_range, mid_z + max_range)

plt.show()

enter image description here

44
tauran

J'aime les solutions ci-dessus, mais elles ont l'inconvénient dont vous avez besoin pour garder une trace des plages et des moyens sur toutes vos données. Cela peut être fastidieux si vous disposez de plusieurs ensembles de données qui seront tracés ensemble. Pour résoudre ce problème, j'ai utilisé les méthodes ax.get_ [xyz] lim3d () et mis le tout dans une fonction autonome qui peut être appelée une seule fois avant d'appeler plt.show (). Voici la nouvelle version:

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

def set_axes_equal(ax):
    '''Make axes of 3D plot have equal scale so that spheres appear as spheres,
    cubes as cubes, etc..  This is one possible solution to Matplotlib's
    ax.set_aspect('equal') and ax.axis('equal') not working for 3D.

    Input
      ax: a matplotlib axis, e.g., as output from plt.gca().
    '''

    x_limits = ax.get_xlim3d()
    y_limits = ax.get_ylim3d()
    z_limits = ax.get_zlim3d()

    x_range = abs(x_limits[1] - x_limits[0])
    x_middle = np.mean(x_limits)
    y_range = abs(y_limits[1] - y_limits[0])
    y_middle = np.mean(y_limits)
    z_range = abs(z_limits[1] - z_limits[0])
    z_middle = np.mean(z_limits)

    # The plot bounding box is a sphere in the sense of the infinity
    # norm, hence I call half the max range the plot radius.
    plot_radius = 0.5*max([x_range, y_range, z_range])

    ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
    ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
    ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')

X = np.random.Rand(100)*10+5
Y = np.random.Rand(100)*5+2.5
Z = np.random.Rand(100)*50+25

scat = ax.scatter(X, Y, Z)

set_axes_equal(ax)
plt.show()
38
karlo

Adapté de la réponse de @ karlo pour rendre les choses encore plus propres:

def set_axes_radius(ax, Origin, radius):
    ax.set_xlim3d([Origin[0] - radius, Origin[0] + radius])
    ax.set_ylim3d([Origin[1] - radius, Origin[1] + radius])
    ax.set_zlim3d([Origin[2] - radius, Origin[2] + radius])

def set_axes_equal(ax):
    '''Make axes of 3D plot have equal scale so that spheres appear as spheres,
    cubes as cubes, etc..  This is one possible solution to Matplotlib's
    ax.set_aspect('equal') and ax.axis('equal') not working for 3D.

    Input
      ax: a matplotlib axis, e.g., as output from plt.gca().
    '''

    limits = np.array([
        ax.get_xlim3d(),
        ax.get_ylim3d(),
        ax.get_zlim3d(),
    ])

    Origin = np.mean(limits, axis=1)
    radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0]))
    set_axes_radius(ax, Origin, radius)

tilisation:

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')         # important!

# ...draw here...

set_axes_equal(ax)             # important!
plt.show()
10
Mateen Ulhaq

EDIT: le code de user2525140 devrait fonctionner parfaitement bien, bien que cette réponse soit censée tenter de corriger une erreur inexistante. La réponse ci-dessous n'est qu'une mise en œuvre (alternative) en double:

def set_aspect_equal_3d(ax):
    """Fix equal aspect bug for 3D plots."""

    xlim = ax.get_xlim3d()
    ylim = ax.get_ylim3d()
    zlim = ax.get_zlim3d()

    from numpy import mean
    xmean = mean(xlim)
    ymean = mean(ylim)
    zmean = mean(zlim)

    plot_radius = max([abs(lim - mean_)
                       for lims, mean_ in ((xlim, xmean),
                                           (ylim, ymean),
                                           (zlim, zmean))
                       for lim in lims])

    ax.set_xlim3d([xmean - plot_radius, xmean + plot_radius])
    ax.set_ylim3d([ymean - plot_radius, ymean + plot_radius])
    ax.set_zlim3d([zmean - plot_radius, zmean + plot_radius])
6
dalum