【问题标题】:Plotting mplot3d / axes3D xyz surface plot with log scale?用对数比例绘制 mplot3d / axes3D xyz 曲面图?
【发布时间】:2025-12-27 03:10:16
【问题描述】:

我一直在寻找解决这个简单问题的方法,但我在任何地方都找不到!有大量帖子详细介绍了 2D 数据的半对数/对数对数绘图,例如plt.setxscale('log') 但是我有兴趣在 3d 图上使用对数刻度(mplot3d)。

我手头没有确切的代码,所以不能在这里发布,但是下面的简单示例应该足以解释这种情况。我目前正在使用 Matplotlib 0.99.1,但很快就会更新到 1.0.0 - 我知道我必须更新我的代码以实现 mplot3d。

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FixedLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = Axes3D(fig)
X = np.arange(-5, 5, 0.025)
Y = np.arange(-5, 5, 0.025)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.jet, extend3d=True)
ax.set_zlim3d(-1.01, 1.01)

ax.w_zaxis.set_major_locator(LinearLocator(10))
ax.w_zaxis.set_major_formatter(FormatStrFormatter('%.03f'))

fig.colorbar(surf)

plt.show()

上面的代码在 3D 中可以很好地绘制,但是三个比例(X、Y、Z)都是线性的。我的“Y”数据跨越了几个数量级(比如 9!),因此将其绘制在对数刻度上会非常有用。我可以通过获取“Y”的日志、重新创建 numpy 数组并以线性比例绘制 log(Y) 来解决这个问题,但是在真正的 python 风格中,我正在寻找更智能的解决方案,它将数据绘制在对数刻度。

是否可以使用对数刻度生成我的 XYZ 数据的 3D 曲面图,理想情况下,我希望 X 和 Z 在线性刻度上,Y 在对数刻度上?

任何帮助将不胜感激。请原谅上述示例中的任何明显错误,如前所述,我没有确切的代码,因此根据我的记忆更改了 matplotlib 库示例。

谢谢

【问题讨论】:

    标签: python numpy matplotlib


    【解决方案1】:

    由于我遇到了同样的问题,而 Alejandros 的回答并没有产生预期的结果,所以这是我目前发现的结果。

    3D 坐标轴的对数缩放是 matplotlib 中的一个持续问题。目前您只能使用以下命令重新标记轴:

    ax.yaxis.set_scale('log')
    

    但是,这不会导致坐标轴按对数缩放,而是标记为对数。 ax.set_yscale('log') 将导致 3D 异常

    见githubissue 209

    因此你仍然需要重新创建 numpy 数组

    【讨论】:

    • 有已知的解决方案吗?看来 set_yscale 在 3D 中仍然不起作用。
    • @Blink:我不这么认为。 :(
    • 在 OSX 上:AttributeError: 'YAxis' object has no attribute 'set_scale'
    【解决方案2】:

    我从问题 209 中获得灵感,想出了一个简单易用的解决方案。您定义了一个小型格式化函数,您可以在其中设置自己的符号。

    import matplotlib.ticker as mticker
    
    # My axis should display 10⁻¹ but you can switch to e-notation 1.00e+01
    def log_tick_formatter(val, pos=None):
        return f"$10^{{{int(val)}}}$"  # remove int() if you don't use MaxNLocator
        # return f"{10**val:.2e}"      # e-Notation
    
    ax.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
    ax.zaxis.set_major_locator(mticker.MaxNLocator(integer=True))
    

    set_major_locator 将指数设置为仅使用整数 10⁻¹、10⁻² 而不使用 10^-1.5 等。Source

    Important! 如果您不使用 set_major_locator 并且想要显示 10^-1.5 则在 return 语句中删除转换 int() 否则它仍将打印 10⁻¹ 而不是 10^-1.5。

    Example:

    自己试试吧!

    from mpl_toolkits.mplot3d import axes3d
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.ticker as mticker
    
    fig = plt.figure(figsize=(11,8))
    ax1 = fig.add_subplot(121,projection="3d")
    
    # Grab some test data.
    X, Y, Z = axes3d.get_test_data(0.05)
    # Now Z has a range from 10⁻³ until 10³, so 6 magnitudes
    Z = (np.full((120, 120), 10)) ** (Z / 20)
    ax1.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
    ax1.set(title="Linear z-axis (small values not visible)")
    
    
    def log_tick_formatter(val, pos=None):
        return f"$10^{{{int(val)}}}$"
    
    
    ax2 = fig.add_subplot(122,projection="3d")
    
    # You still have to take log10(Z) but thats just one operation
    ax2.plot_wireframe(X, Y, np.log10(Z), rstride=10, cstride=10)
    ax2.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
    ax2.zaxis.set_major_locator(mticker.MaxNLocator(integer=True))
    ax2.set(title="Logarithmic z-axis (much better)")
    plt.savefig("LinearLog.png", bbox_inches='tight')
    plt.show()
    
    

    【讨论】:

      【解决方案3】:

      在 osx 中:运行 ax.zaxis._set_scale('log')(注意下划线)

      【讨论】:

      • 这仅以对数刻度标记轴,但实际上并未以对数刻度绘制。
      【解决方案4】:

      您所要做的就是定义您想要的轴的比例。例如,如果你希望 x 和 y 轴在对数刻度上,你应该写:

      ax.xaxis.set_scale('log')
      ax.yaxis.set_scale('log')
      

      最终:

      ax.zaxis.set_scale('log')
      

      【讨论】:

      • 上面的答案声称这个答案是错误的。你对此有何回应?
      • 是的。这只会重新标记,但实际上不会重新缩放轴。
      • ax.zaxis.set_scale('log') 在 mpl 3.0.1 中不起作用:AttributeError: 'ZAxis' object has no attribute 'set_scale'
      【解决方案5】:

      由于问题 209 没有解决方案。但是,您可以尝试这样做:

      ax.plot_surface(X, np.log10(Y), Z, cmap='jet', linewidth=0.5)
      

      如果在“Y”中有一个 0,它将出现警告但仍然有效。由于此警告颜色图不起作用,因此请尽量避免使用 0 和负数。例如:

         Y[Y != 0] = np.log10(Y[Y != 0])
      ax.plot_surface(X, Y, Z, cmap='jet', linewidth=0.5)
      

      【讨论】:

        【解决方案6】:

        我想要一个符号图,并且由于我手动填充数据数组,我只是创建了一个自定义函数来计算日志以避免在数据为 bar3d 中出现负条:

        import math as math
        
        def manual_log(data):
          if data < 10: # Linear scaling up to 1
            return data/10
          else: # Log scale above 1
            return math.log10(data)
        

        由于我没有负值,所以我没有在这个函数中实现处理这个值,但应该不难改变它。

        【讨论】: