matplotlib (समान इकाई लंबाई): 'बराबर' पहलू अनुपात z- अक्ष x- और y- के बराबर नहीं है


89

जब मैंने 3D ग्राफ के लिए समान पहलू अनुपात सेट किया तो z- अक्ष 'बराबर' में नहीं बदलता। तो यह:

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

मुझे निम्नलिखित देता है: यहाँ छवि विवरण दर्ज करें

जहाँ स्पष्ट रूप से z- अक्ष की इकाई लंबाई x- और y- इकाइयों के बराबर नहीं है।

मैं तीनों अक्षों की इकाई लंबाई को समान कैसे बना सकता हूं? मेरे द्वारा खोजे गए सभी उपाय काम नहीं आए। धन्यवाद।

जवाबों:


71

मेरा मानना ​​है कि matplotlib अभी तक 3 डी में सही बराबर अक्ष नहीं सेट करता है ... लेकिन मुझे कुछ समय पहले एक ट्रिक मिली (मुझे याद नहीं है कि कहां) मैंने इसका उपयोग करने के लिए अनुकूलित किया है। अवधारणा यह है कि आपके डेटा के चारों ओर एक नकली घन बाउंडिंग बॉक्स बनाया जाए। आप इसे निम्न कोड से जांच सकते हैं:

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

z डेटा x और y से बड़े परिमाण के क्रम के बारे में है, लेकिन समान अक्ष विकल्प के साथ भी, matplotlib ऑटोस्कोप z:

खराब

लेकिन यदि आप बाउंडिंग बॉक्स जोड़ते हैं, तो आपको एक सही स्केलिंग प्राप्त होती है:

यहाँ छवि विवरण दर्ज करें


इस मामले में आपको equalबयान की आवश्यकता भी नहीं है - यह हमेशा बराबर होगा।

1
यह ठीक काम करता है अगर आप डेटा के केवल एक सेट की साजिश रच रहे हैं, लेकिन क्या होगा जब एक ही 3 डी प्लॉट पर अधिक डेटा सेट हैं? प्रश्न में, 2 डेटा सेट थे, इसलिए उन्हें संयोजित करना एक सरल बात है लेकिन कई अलग-अलग डेटा सेटों की साजिश रचने पर यह अनुचित हो सकता है।
स्टीवन सी। हॉवेल

@ stvn66, मैं इस समाधान के साथ एक ग्राफ में पांच डेटा सेट की साजिश रच रहा था और यह मेरे लिए ठीक काम करता था।

1
यह पूरी तरह से काम करता है। जो लोग इसे फ़ंक्शन रूप में चाहते हैं, जो एक अक्ष ऑब्जेक्ट लेते हैं और ऊपर दिए गए कार्यों को करते हैं, मैं उन्हें नीचे @Karlo उत्तर की जांच करने के लिए प्रोत्साहित करता हूं। यह थोड़ा क्लीनर घोल है।
7

@ user1329187 - मैंने पाया कि यह equalबयान के बिना मेरे लिए काम नहीं किया ।
सुपरगैस

60

मुझे उपरोक्त समाधान पसंद हैं, लेकिन उनके पास यह दोष है कि आपको अपने सभी डेटा पर पर्वतमाला और साधनों का ध्यान रखने की आवश्यकता है। यदि आपके पास कई डेटा सेट हैं जो एक साथ प्लॉट किए जाएंगे तो यह बोझिल हो सकता है। इसे ठीक करने के लिए, मैंने ax.get_ [xyz] lim3d () विधियों का उपयोग किया और पूरी चीज़ को एक स्टैंडअलोन फ़ंक्शन में डाल दिया, जिसे आप plt.show () कहने से ठीक पहले एक बार कॉल कर सकते हैं। यहाँ नया संस्करण है:

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

ध्यान रखें कि केंद्र बिंदु का उपयोग करने का मतलब सभी मामलों में काम नहीं करेगा, आपको मध्यबिंदु का उपयोग करना चाहिए। मेरी टिप्पणी को टौरन के उत्तर पर देखें।
रेनमैन नूडल्स

1
ऊपर मेरा कोड डेटा का मतलब नहीं लेता है, यह मौजूदा प्लॉट सीमा का मतलब लेता है। इस प्रकार मेरे कार्य को किसी भी बिंदु को ध्यान में रखने की गारंटी दी जाती है जो कि कॉल किए जाने से पहले सेट की गई सीमा के अनुसार थी। यदि उपयोगकर्ता ने पहले से ही सभी डेटा बिंदुओं को देखने के लिए बहुत सीमित रूप से प्लॉट सीमाएं निर्धारित की हैं, तो यह एक अलग मुद्दा है। मेरा कार्य अधिक लचीलेपन की अनुमति देता है क्योंकि आप डेटा का केवल सबसेट देखना चाहते हैं। सभी मैं कर रहा हूँ अक्ष सीमाओं का विस्तार ताकि पहलू अनुपात 1: 1: 1 है।
करलो

इसे लगाने का एक और तरीका: यदि आप केवल 2 बिंदुओं का मतलब लेते हैं, अर्थात् एक अक्ष पर सीमाएं, तो इसका मतलब मध्य बिंदु है। इसलिए, जहां तक ​​मैं बता सकता हूं, नीचे डालीम का कार्य गणितीय रूप से मेरे बराबर होना चाहिए और `` फिक्स '' के लिए कुछ भी नहीं था।
करलो

12
वर्तमान में स्वीकार किए गए समाधान से बहुत बेहतर है कि एक गड़बड़ है जब आप अलग-अलग प्रकृति की बहुत सारी वस्तुएं शुरू करते हैं।
पी-जीएन

1
मुझे वास्तव में समाधान पसंद है, लेकिन मैंने एनाकोंडा, ax.set_aspect ("बराबर") को अपडेट करने के बाद त्रुटि की सूचना दी: NotImplementedError: यह वर्तमान में 3D अक्ष पर पहलू को मैन्युअल रूप से सेट करना संभव नहीं है
इवान

52

मैंने set_x/y/zlim कार्यों का उपयोग करके रेमी एफ के समाधान को सरल बनाया ।

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

यहाँ छवि विवरण दर्ज करें


1
मुझे सरलीकृत कोड पसंद है। बस इस बात से अवगत रहें कि कुछ (बहुत कम) डेटा बिंदु प्लॉट नहीं हो सकते हैं। उदाहरण के लिए, मान लीजिए कि X = [0, 0, 0, 100] ताकि X.mean () = 25 हो। यदि max_range 100 (X से) निकलता है, तो आप x-श्रेणी 25 + - 50 होंगे, इसलिए [-25, 75] और आपको X [3] डेटा बिंदु याद होगा। यह विचार बहुत अच्छा है, और यह सुनिश्चित करने के लिए कि आपको सभी बिंदुओं को प्राप्त करना आसान है।
ट्रैविसज

1
ध्यान रखें कि केंद्र के रूप में उपयोग करना सही नहीं है। आपको कुछ का उपयोग करना चाहिए midpoint_x = np.mean([X.max(),X.min()])और फिर midpoint_x+/- की सीमा निर्धारित करनी चाहिए max_range। माध्य का उपयोग केवल तभी काम करता है जब माध्य डेटासेट के मध्य बिंदु पर स्थित होता है, जो हमेशा सत्य नहीं होता है। इसके अलावा, एक टिप: यदि आप बिंदुओं के पास या सीमाओं पर हैं, तो ग्राफ़ को अच्छा बनाने के लिए आप max_range को स्केल कर सकते हैं।
रेनमैन नूडल्स

जब मैंने एनाकोंडा को अपडेट किया, उसके बाद ax.set_aspect ("बराबर") रिपोर्ट की गई त्रुटि: NotImplementedError: वर्तमान में 3D एक्सिस पर पहलू को सेट करना संभव नहीं है
Ewan

कॉल करने के बजाय set_aspect('equal'), उपयोग करें set_box_aspect([1,1,1]), जैसा कि नीचे मेरे उत्तर में वर्णित है। यह मेरे लिए matplotlib संस्करण 3.3.1 में काम कर रहा है!
23 अक्टूबर को एंड्रयूकॉक्स

18

चीजों को भी साफ करने के लिए @ karlo के उत्तर से अनुकूलित:

def set_axes_equal(ax: plt.Axes):
    """Set 3D plot axes to equal scale.

    Make axes of 3D plot have equal scale so that spheres appear as
    spheres and cubes as cubes.  Required since `ax.axis('equal')`
    and `ax.set_aspect('equal')` don't work on 3D.
    """
    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)

def _set_axes_radius(ax, origin, radius):
    x, y, z = origin
    ax.set_xlim3d([x - radius, x + radius])
    ax.set_ylim3d([y - radius, y + radius])
    ax.set_zlim3d([z - radius, z + radius])

उपयोग:

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

# ...draw here...

set_axes_equal(ax)             # important!
plt.show()

संपादित करें: इस उत्तर परिवर्तन में विलय कर दिया की वजह से matplotlib के नवीनतम संस्करण पर काम नहीं करता pull-request #13474है, जो में ट्रैक की जाती issue #17172है और issue #1077। इसके लिए अस्थायी वर्कअराउंड के रूप में, कोई नई जोड़ी गई लाइनों को हटा सकता है lib/matplotlib/axes/_base.py:

  class _AxesBase(martist.Artist):
      ...

      def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
          ...

+         if (not cbook._str_equal(aspect, 'auto')) and self.name == '3d':
+             raise NotImplementedError(
+                 'It is not currently possible to manually set the aspect '
+                 'on 3D axes')

इसे प्यार करें, लेकिन मैंने एनाकोंडा अपडेट करने के बाद, ax.set_aspect ("बराबर") रिपोर्ट की गई त्रुटि: NotImplementedError: वर्तमान में 3 डी अक्ष पर पहलू को मैन्युअल रूप से सेट करना संभव नहीं है
Ewan

@ इवान ने जांच में मदद करने के लिए अपने उत्तर के निचले भाग में कुछ लिंक जोड़े। ऐसा लग रहा है कि एमपीएल लोग किसी कारण से समस्या को ठीक किए बिना वर्कआर्ड को तोड़ रहे हैं। ¯ \\ _ (ツ) _ /
M

मुझे लगता है कि मुझे NotImplementedError (नीचे मेरे उत्तर में पूर्ण विवरण) के लिए वर्कअराउंड (स्रोत कोड को संशोधित करने की आवश्यकता नहीं है) मिला; मूल रूप ax.set_box_aspect([1,1,1])से कॉल करने से पहले जोड़ेंset_axes_equal
एंड्रयूकॉक्स

बस इस पोस्ट को पाया और कोशिश की, ax.set_aspect ('बराबर') पर विफल। हालांकि यह मुद्दा नहीं है कि यदि आप सिर्फ अपनी स्क्रिप्ट से ax.set_aspect ('बराबर') हटाते हैं, लेकिन दो कस्टम फ़ंक्शन set_axes_equal और _set_axes_radius ... plt.show () से पहले उन्हें कॉल करना सुनिश्चित करते हैं। मेरे लिए महान समाधान! मैं कुछ वर्षों से कुछ समय के लिए खोज रहा हूं, आखिरकार। मैं हमेशा 3 डी प्लॉटिंग के लिए अजगर के वीटीके मॉड्यूल पर वापस लौट आया हूं, खासकर जब चीजों की संख्या चरम पर होती है।
टोनी ए

15

सरल तय!

मैं 3.3.1 संस्करण में यह काम पाने में कामयाब रहा हूं।

ऐसा लगता है कि यह समस्या संभवतः PR # 17172 में हल हो गई है ; ax.set_box_aspect([1,1,1])पहलू सही है यह सुनिश्चित करने के लिए आप फ़ंक्शन का उपयोग कर सकते हैं ( set_aspect फ़ंक्शन के लिए नोट्स देखें )। @Karlo और / या @Matee Ulhaq द्वारा प्रदान किए गए बाउंडिंग बॉक्स फ़ंक्शन (ओं) के साथ संयोजन में उपयोग किए जाने पर, प्लॉट अब 3D में सही दिखते हैं!

बराबर अक्षों के साथ matplotlib 3D प्लॉट

न्यूनतम कार्य उदाहरण

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

# Functions from @Mateen Ulhaq and @karlo
def set_axes_equal(ax: plt.Axes):
    """Set 3D plot axes to equal scale.

    Make axes of 3D plot have equal scale so that spheres appear as
    spheres and cubes as cubes.  Required since `ax.axis('equal')`
    and `ax.set_aspect('equal')` don't work on 3D.
    """
    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)

def _set_axes_radius(ax, origin, radius):
    x, y, z = origin
    ax.set_xlim3d([x - radius, x + radius])
    ax.set_ylim3d([y - radius, y + radius])
    ax.set_zlim3d([z - radius, z + radius])

# Generate and plot a unit sphere
u = np.linspace(0, 2*np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = np.outer(np.cos(u), np.sin(v)) # np.outer() -> outer vector product
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(x, y, z)

ax.set_box_aspect([1,1,1]) # IMPORTANT - this is the new, key line
# ax.set_proj_type('ortho') # OPTIONAL - default is perspective (shown in image above)
set_axes_equal(ax) # IMPORTANT - this is also required
plt.show()

हां आखिरकार! धन्यवाद - अगर मैं केवल आपको शीर्ष पर पहुंचा सकता हूं :)
एन। जोनास फिगर

7

EDIT: user2525140 का कोड पूरी तरह से ठीक काम करना चाहिए, हालांकि इस उत्तर को गैर-मौजूद त्रुटि को ठीक करने का प्रयास किया गया है। नीचे दिया गया उत्तर केवल एक डुप्लिकेट (वैकल्पिक) कार्यान्वयन है:

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

आपको अभी भी करने की आवश्यकता है: ax.set_aspect('equal')या टिक मूल्यों को खराब किया जा सकता है। अन्यथा अच्छा समाधान। धन्यवाद,
टोनी पावर

2

Matplotlib 3.3.0 के रूप में, Axes3D.set_box_aspect अनुशंसित दृष्टिकोण प्रतीत होता है।

import numpy as np

xs, ys, zs = <your data>
ax = <your axes>

# Option 1: aspect ratio is 1:1:1 in data space
ax.set_box_aspect((np.ptp(xs), np.ptp(ys), np.ptp(zs)))

# Option 2: aspect ratio 1:1:1 in view space
ax.set_box_aspect((1, 1, 1))

2021 का रास्ता। एक जादू की तरह काम करता है।
Jan Joneš
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.