matplotlib (সমান এককের দৈর্ঘ্য): 'সমান' দিকের অনুপাত সহ z- অক্ষটি x- এবং y- এর সমান নয়


89

আমি যখন 3 ডি গ্রাফের জন্য সমান দিক অনুপাত সেট করি তখন 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

আমি বিশ্বাস করি ম্যাটপ্লটলিব এখনও 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 অক্ষ:

খারাপ

তবে আপনি যদি বাউন্ডিং বক্স যুক্ত করেন তবে আপনি একটি সঠিক স্কেলিং পাবেন:

এখানে চিত্র বর্ণনা লিখুন


এই ক্ষেত্রে আপনার এমনকি equalবিবৃতিটির প্রয়োজন নেই - এটি সর্বদা সমান হবে।

4
এটি ঠিক কাজ করে যদি আপনি কেবলমাত্র একটি সেট ডেটা প্লট করে থাকেন তবে যখন একই 3 ডি প্লটে আরও বেশি ডেটা সেট থাকে তখন কী হবে? প্রশ্নে 2 টি ডেটা সেট ছিল তাই এগুলিকে একত্রিত করার পক্ষে এটি একটি সহজ জিনিস তবে বেশ কয়েকটি ভিন্ন ডেটা সেট প্লট করার পরে তা অযৌক্তিক হতে পারে।
স্টিভেন সি হাওয়েল

@ stvn66, আমি এই সমাধানগুলি সহ একটি গ্রাফে পাঁচটি ডেটা সেট আপ করার পরিকল্পনা করছিলাম এবং এটি আমার পক্ষে ভাল কাজ করেছে।

4
এটি পুরোপুরি কাজ করে। যারা ফাংশন আকারে এটি চান তাদের জন্য, যা একটি অক্ষের অবজেক্ট নেয় এবং উপরের ক্রিয়াকলাপ সম্পাদন করে, আমি তাদের নীচে @ কার্লো উত্তরটি পরীক্ষা করতে উত্সাহিত করি। এটি একটি সামান্য ক্লিনার সমাধান।
স্পুরা

@ ব্যবহারকারী 1329187 - আমি খুঁজে পেয়েছি যে equalবিবৃতি ছাড়া এটি আমার পক্ষে কাজ করে না ।
সুপারগ্রা

60

আমি উপরের সমাধানগুলি পছন্দ করি তবে সেগুলির মধ্যে এমন একটি অপূর্ণতা রয়েছে যা আপনাকে আপনার সমস্ত ডেটা থেকে রেঞ্জগুলি এবং তার মাধ্যমগুলি ট্র্যাক করে রাখতে হবে। আপনার কাছে একসাথে প্লট করা হবে এমন একাধিক ডেটা সেট থাকলে এটি জটিল হতে পারে। এটি ঠিক করার জন্য, আমি ax.get_ [xyz] lim3d () পদ্ধতি ব্যবহার করেছি এবং পুরো জিনিসটিকে একটি স্ট্যান্ডেলোন ফাংশনে রেখেছি যা আপনি প্লট.শো () কল করার ঠিক আগে বলা যেতে পারে। এখানে নতুন সংস্করণ:

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

সচেতন হন যে কেন্দ্র বিন্দু হিসাবে উপায়গুলি ব্যবহার সব ক্ষেত্রে কার্যকর হবে না, আপনার মিডপয়েন্টগুলি ব্যবহার করা উচিত। তৌরানের উত্তর সম্পর্কে আমার মন্তব্য দেখুন।
রেনম্যান নুডলস

4
আমার উপরের কোডগুলি ডেটার অর্থ গ্রহণ করে না, এটি বিদ্যমান প্লটের সীমাগুলির গড় গ্রহণ করে। আমার ফাংশনটি এমন কোনও পয়েন্টগুলি কল করার আগে নির্ধারিত প্লটের সীমা অনুযায়ী বিবেচনায় রাখার গ্যারান্টিযুক্ত। যদি ব্যবহারকারী ইতিমধ্যে সমস্ত ডেটা পয়েন্ট দেখতে খুব সীমাবদ্ধভাবে প্লটের সীমা নির্ধারণ করে থাকে, তবে এটি একটি পৃথক সমস্যা। আমার ফাংশনটি আরও নমনীয়তার অনুমতি দেয় কারণ আপনি কেবলমাত্র ডেটার একটি উপসেট দেখতে চাইতে পারেন। আমি যা করি তা হ'ল অক্ষ সীমা প্রসারিত হয় তাই দিক অনুপাত 1: 1: 1।
করলো

এটি রাখার আরেকটি উপায়: আপনি যদি কেবলমাত্র 2 টি পয়েন্টের অর্থ গ্রহণ করেন, যথা একটি একক অক্ষের সীমানা, তবে তার অর্থ হল মিডপয়েন্ট। সুতরাং, যতদূর আমি বলতে পারি, নীচে ডালুমের কাজটি গাণিতিকভাবে আমার সমতুল্য হওয়া উচিত এবং `` ঠিক করার '' মতো কিছুই ছিল না।
করলো

12
আপনি যখন বিভিন্ন প্রকৃতির প্রচুর পরিমাণে জিনিস শুরু করতে শুরু করেন তখন এই গৃহীত সমাধানের চেয়ে প্রচুর পরিমাণে উন্নত।
পি-জিএন

4
সমাধানটি আমি সত্যিই পছন্দ করি, তবে আমি অ্যানাকোন্ডা আপডেট করার পরে ax.set_aspect ("সমতুল্য)" ত্রুটিটি রিপোর্ট করেছেন: NotImplementedError:
ইভান

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

এখানে চিত্র বর্ণনা লিখুন


4
আমি সরলীকৃত কোডটি পছন্দ করি। কেবল সচেতন থাকুন যে কয়েকটি (খুব অল্প) ডেটা পয়েন্ট প্লট হতে পারে না। উদাহরণস্বরূপ, ধরুন যে এক্স = [0, 0, 0, 100] যাতে X.mean () = 25। যদি ম্যাক্স_রেঞ্জ 100 (এক্স থেকে) হয়ে আসে, তবে আপনার এক্স-রেঞ্জ 25 + - 50 হবে, সুতরাং [-25, 75] এবং আপনি এক্স [3] ডেটা পয়েন্টটি মিস করবেন। যদিও ধারণাটি খুব সুন্দর এবং আপনি সমস্ত পয়েন্ট পেয়েছেন তা নিশ্চিত করে সংশোধন করা সহজ।
ট্র্যাভিসজে

4
কেন্দ্র হিসাবে উপায়গুলি ব্যবহার করা সঠিক নয় বলে সাবধান হন। আপনার মতো কিছু ব্যবহার করা উচিত midpoint_x = np.mean([X.max(),X.min()])এবং তারপরে সীমাটি midpoint_x+/- তে সেট করা উচিত max_range। যদি ডেটাটিসেটের মিডপয়েন্টে মধ্যবর্তী অবস্থানটি থাকে তবেই গড় ব্যবহার করে যা সর্বদা সত্য হয় না। এছাড়াও, একটি টিপ: সীমানার কাছাকাছি বা সীমানায় পয়েন্ট থাকলে গ্রাফটিকে আরও সুন্দর দেখানোর জন্য আপনি ম্যাক্স_রেঞ্জকে স্কেল করতে পারেন।
রেনম্যান নুডলস

আমি অ্যানাকোন্ডা আপডেট করার পরে, ax.set_aspect ("সমতুল্য)" ত্রুটিটি রিপোর্ট করেছে: নয় বাস্তবায়িত ত্রুটি:
ইওয়ান

কল করার চেয়ে বরং set_aspect('equal')ব্যবহার করুন set_box_aspect([1,1,1]), নীচে আমার উত্তরে বর্ণিত। এটি আমার জন্য ম্যাটপ্ল্লোব সংস্করণ ৩.৩.১ এ কাজ করছে!
অ্যান্ড্রুকক্স

18

জিনিসগুলিকে আরও পরিষ্কার করে তুলতে @ কার্লো এর উত্তর থেকে অভিযোজিত:

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

সম্পাদনা: এই উত্তরটি ম্যাটপ্লটলিবের আরও সাম্প্রতিক সংস্করণগুলিতে মেশানো পরিবর্তনের কারণে কার্যকর হয়নি 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 (নীচে আমার উত্তরের সম্পূর্ণ বিবরণ) এর জন্য আমি একটি কার্যপ্রণালী (যার উত্স কোডটি পরিবর্তন করার প্রয়োজন নেই) খুঁজে পেয়েছি; মূলত ax.set_box_aspect([1,1,1])কল করার আগে যুক্ত করুনset_axes_equal
অ্যান্ড্রুকক্স ২

এই পোস্টটি সবেমাত্র পাওয়া গেছে এবং ax.set_aspect ('সমান') এ ব্যর্থ হয়েছে। যদিও আপনি কেবল আপনার স্ক্রিপ্ট থেকে ax.set_aspect ('সমতুল্য') মুছে ফেলেছেন তবে দুটি কাস্টম ফাংশন set_axes_equal এবং _set_axes_radius রাখুন ... প্লট.শো () এর আগে অবশ্যই তাদের কল করবেন কিনা তা নিশ্চিত করেই কোনও সমস্যা নয়। আমার জন্য দুর্দান্ত সমাধান! অবশেষে কয়েক বছর ধরে আমি কিছুক্ষণ অনুসন্ধান করছি। 3 ডি প্লট করার জন্য আমি সবসময় পাইথনের vtk মডিউলটিতে ফিরে এসেছি, বিশেষত যখন জিনিসগুলির সংখ্যা চরম হয়।
টনি এ

15

সরল ফিক্স!

আমি এই সংস্করণ 3.3.1 সংস্করণে পেতে পরিচালিত করেছি।

দেখে মনে হচ্ছে এই সমস্যাটি সম্ভবত PR # 17172 এ সমাধান হয়েছে ; ax.set_box_aspect([1,1,1])দিকটি সঠিক কিনা তা নিশ্চিত করতে আপনি ফাংশনটি ব্যবহার করতে পারেন ( set_aspect ফাংশনের জন্য নোটগুলি দেখুন )। @Karlo এবং / অথবা @ মেটে উলহাক দ্বারা প্রদত্ত বাউন্ডিং বক্স ফাংশন (গুলি) এর সাথে যখন ব্যবহার করা হয়, প্লটগুলি এখন 3 ডি তে সঠিক দেখাচ্ছে!

matplotlib 3 ডি প্লট সমান অক্ষ সহ

ন্যূনতম কাজের উদাহরণ

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

সম্পাদনা: ব্যবহারকারী 2525140 এর কোডটি পুরোপুরি সূক্ষ্মভাবে কাজ করা উচিত, যদিও এই উত্তরটি একটি অ-অযৌক্তিক ত্রুটি সংশোধন করার চেষ্টা করেছিল। নীচের উত্তরটি কেবল একটি সদৃশ (বিকল্প) বাস্তবায়ন:

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')বা টিক মানগুলি প্যাঁচানো হতে পারে। অন্যথায় ভাল সমাধান। ধন্যবাদ,
টনি পাওয়ার

1

ম্যাটপ্লটলিব ৩.৩.০ হিসাবে, অ্যাক্সেস ৩ ডি.সেট_বক্স_স্পেক্টটি প্রস্তাবিত পদ্ধতি বলে মনে হচ্ছে।

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))
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.