25

I've been looking high and low for a solution to this simple problem but I can't find it anywhere! There are a loads of posts detailing semilog / loglog plotting of data in 2D e.g. plt.setxscale('log') however I'm interested in using log scales on a 3d plot(mplot3d).

I don't have the exact code to hand and so can't post it here, however the simple example below should be enough to explain the situation. I'm currently using Matplotlib 0.99.1 but should shortly be updating to 1.0.0 - I know I'll have to update my code for the mplot3d implementation.

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

The above code will plot fine in 3D, however the three scales (X, Y, Z) are all linear. My 'Y' data spans several orders of magnitude (like 9!), so it would be very useful to plot it on a log scale. I can work around this by taking the log of the 'Y', recreating the numpy array and plotting the log(Y) on a linear scale, but in true python style I'm looking for smarter solution which will plot the data on a log scale.

Is it possible to produce a 3D surface plot of my XYZ data using log scales, ideally I'd like X & Z on linear scales and Y on a log scale?

Any help would be greatly appreciated. Please forgive any obvious mistakes in the above example, as mentioned I don't have my exact code to have and so have altered a matplotlib gallery example from my memory.

Thanks

asked Oct 11, 2010 at 20:42

5 Answers 5

16

I came up with a nice and easy solution taking inspiration from Issue 209. You define a small formatter function in which you set your own notation.

import matplotlib.ticker as mticker
# My axis should display 10−1 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 sets the exponential to only use integers 10−1, 10−2 without 10^-1.5 etc. Source

Important! remove the cast int() in the return statement if you don't use set_major_locator and you want to display 10^-1.5 otherwise it will still print 10−1 instead of 10^-1.5.

Example: LinearLog

Try it yourself!

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−3 until 103, 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()
answered May 31, 2021 at 12:59

2 Comments

I think you are better off defining the custom formatter as def log_tick_formatter(val, pos=None): return f"10ドル^{{{val:g}}}$" instead of using the int(...) construct. This tick formatter will then work fine for integer=True and integer=False.
The advantage of not using the int(...) contruct is that the output will be reasonable also for data like Z = (np.full((120, 120), 10)) ** (Z / 20) + 1e3 where mticker.MaxNLocator is unable to account for integer=True.
15

Since I encountered the same question and Alejandros answer did not produced the desired Results here is what I found out so far.

The log scaling for Axes in 3D is an ongoing issue in matplotlib. Currently you can only relabel the axes with:

ax.yaxis.set_scale('log')

This will however not cause the axes to be scaled logarithmic but labeled logarithmic. ax.set_yscale('log') will cause an exception in 3D

See on github issue 209

Therefore you still have to recreate the numpy array

Donald Duck is with Ukraine
8,97523 gold badges80 silver badges105 bronze badges
answered Jun 28, 2013 at 10:54

4 Comments

Are there known solutions to this yet? It seems set_yscale still does not work in 3D.
@Blink: I don't think so. :(
On OSX: AttributeError: 'YAxis' object has no attribute 'set_scale'
I get the same AttributeError in Linux.
10

in osx: ran ax.zaxis._set_scale('log') (notice the underscore)

answered Oct 11, 2017 at 10:13

1 Comment

This only labels the axis in logarithm scale but not actually plotting in logarithm scale.
1

There is no solution because of the issue 209. However, you can try doing this:

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

If in "Y" there is a 0, it is going to appear a warning but still works. Because of this warning color maps don ́t work, so try to avoid 0 and negative numbers. For example:

 Y[Y != 0] = np.log10(Y[Y != 0])
ax.plot_surface(X, Y, Z, cmap='jet', linewidth=0.5)
answered May 24, 2018 at 10:42

Comments

1

I wanted a symlog plot and, since I fill the data array by hand, I just made a custom function to calculate the log to avoid having negative bars in the bar3d if the data is < 1:

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)

Since I have no negative values, I did not implement handling this values in this function, but it should not be hard to change it.

answered Aug 25, 2020 at 23:46

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.