Python surface plotting - python

I have following table data(Please see the image)
for which I want to have a surface plot in python. Using surface plotting from matplotlib,
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
X=[2,3,5,8,20,30,50,80,100,150,175,200,250,300]
Y=[2,3,4,5,10,15,20,30,40,50,80,100,125,150,175,200]
Y,X=np.meshgrid(Y,X)
Z=np.array([
[0.2885307,0.269452,0.259193,0.2548041,0.2731868,0.4801551,0.7992361,1.7577641,3.2611327,5.428839,19.647976,37.59729,78.0871,152.21466,268.14572,0],
[0.2677955,0.2538363,0.2380033,0.2306999,0.4779794,0.9251045,1.5448972,3.508644,6.4968576,11.252151,0,0,0,0,0,0],
[0.2432982,0.2283371,0.2514196,0.3392502,0,0,0,0,0,0,0,0,0,0,0,0],
[0.2342575,0.3158406,0.4770729, 0.6795485,2.353042, 5.260077,9.78172,25.87004,59.52568, 0,0,0,0,0,0,0],
[0.6735384, 1.3873291,2.346506, 3.5654,0,0,0,0,0,0,0,0,0,0,0,0],
[1.3584715, 2.9405127,5.096819,8.155857,0,0,0,0,0,0,0,0,0,0,0,0],
[3.558062,8.216592,15.768077,27.386694,0,0,0,0,0,0,0,0,0,0,0,0],
[9.537899,25.202589,58.20041,0,0,0,0,0,0,0,0,0,0,0,0,0],
[16.083374,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[54.936775,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[89.185974,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]])
my_col = cm.jet(Z/np.amax(Z))
surf = ax.plot_surface(X, Y, Z,cmap=cm.coolwarm,linewidth=0, antialiased=False)
ax.set_zlim(0, 300)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
I get a plot like this
which is correct but not very attractive or intuitive. How can I make the visualizations more smooth and clear? Please note that I have many blanks in my data. Should I use 'zero' for the blanks or 'nan'(not a number)? For the same data, excel shows a much better graph.
I appreciate your inputs in order to make python plot more visually attractive.

The difference between the matplotlib and excel plots is that matplotlib is plotting on a linear scale and excel is logarithmic (or something that looks deceptively like a log axis but actually isn't -- see below). Therefore, in the matplotlib the slopes look extremely steep, but in excel the slopes are dramatically stretched out by the log.
Unfortunately, matplotlib doesn't yet have log axes working well in 3D. I'm not sure why this is, but it is a serious shortcoming. You can see a plot similar to Excel though if you take the log10 of your X and Y data before you do the plots. You can also go further to DIY your own log axes, but I've just done a shorthand for that using a tick formatter.
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter, FuncFormatter
from mpl_toolkits.mplot3d import axes3d
import numpy as np
def format_log(x, pos=None):
x1 = 10**x
s = "%.3f" % x1
return s[:-4] if s[-3:]=="000" else " "
fig = plt.figure()
ax = fig.gca(projection='3d')
X=[2,3,5,8,20,30,50,80,100,150,175,200,250,300]
Y=[2,3,4,5,10,15,20,30,40,50,80,100,125,150,175,200]
X = np.log10(np.array(X))
Y = np.log10(np.array(Y))
Y,X=np.meshgrid(Y,X)
Z=np.array([
[0.2885307,0.269452,0.259193,0.2548041,0.2731868,0.4801551,0.7992361,1.7577641,3.2611327,5.428839,19.647976,37.59729,78.0871,152.21466,268.14572,0],
[0.2677955,0.2538363,0.2380033,0.2306999,0.4779794,0.9251045,1.5448972,3.508644,6.4968576,11.252151,0,0,0,0,0,0],
[0.2432982,0.2283371,0.2514196,0.3392502,0,0,0,0,0,0,0,0,0,0,0,0],
[0.2342575,0.3158406,0.4770729, 0.6795485,2.353042, 5.260077,9.78172,25.87004,59.52568, 0,0,0,0,0,0,0],
[0.6735384, 1.3873291,2.346506, 3.5654,0,0,0,0,0,0,0,0,0,0,0,0],
[1.3584715, 2.9405127,5.096819,8.155857,0,0,0,0,0,0,0,0,0,0,0,0],
[3.558062,8.216592,15.768077,27.386694,0,0,0,0,0,0,0,0,0,0,0,0],
[9.537899,25.202589,58.20041,0,0,0,0,0,0,0,0,0,0,0,0,0],
[16.083374,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[54.936775,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[89.185974,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]])
my_col = cm.jet(Z/np.amax(Z))
surf = ax.plot_surface(X, Y, Z,cmap=cm.coolwarm)
ax.set_zlim(0, 300)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
ax.xaxis.set_major_formatter(FuncFormatter(format_log))
ax.yaxis.set_major_formatter(FuncFormatter(format_log))
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
Edit:
After coming back to this question, I realize that the Excel plot isn't actually showing a logarithmic axis, but is instead just plotting the X and Y values given with equal spacing along the axis even those values have no clear mathematical progression.
It's critical to note that this isn't a good representation of the data, since it gives the obvious impression that it's logarithmic (for the specific data presented), but it's actually not, although it requires unusually close inspection to see that. Here the gaps between adjacent numbers aren't even monotonic.
So I discourage this representation, but to reproduce that Excel plot, I'd suggest making equally spaced data, but labeling it with different numbers (and just this sentence alone should be enough to discourage this approach). But here's the code and approach:
fig = plt.figure()
ax = fig.gca(projection='3d')
x=[2,3,5,8,20,30,50,80,100,150,175,200,250,300]
y=[2,3,4,5,10,15,20,30,40,50,80,100,125,150,175,200]
Y,X=np.meshgrid(range(len(y)),range(len(x)))
Z=np.array([
[0.2885307,0.269452,0.259193,0.2548041,0.2731868,0.4801551,0.7992361,1.7577641,3.2611327,5.428839,19.647976,37.59729,78.0871,152.21466,268.14572,0],
[0.2677955,0.2538363,0.2380033,0.2306999,0.4779794,0.9251045,1.5448972,3.508644,6.4968576,11.252151,0,0,0,0,0,0],
[0.2432982,0.2283371,0.2514196,0.3392502,0,0,0,0,0,0,0,0,0,0,0,0],
[0.2342575,0.3158406,0.4770729, 0.6795485,2.353042, 5.260077,9.78172,25.87004,59.52568, 0,0,0,0,0,0,0],
[0.6735384, 1.3873291,2.346506, 3.5654,0,0,0,0,0,0,0,0,0,0,0,0],
[1.3584715, 2.9405127,5.096819,8.155857,0,0,0,0,0,0,0,0,0,0,0,0],
[3.558062,8.216592,15.768077,27.386694,0,0,0,0,0,0,0,0,0,0,0,0],
[9.537899,25.202589,58.20041,0,0,0,0,0,0,0,0,0,0,0,0,0],
[16.083374,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[54.936775,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[89.185974,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]])
my_col = cm.jet(Z/np.amax(Z))
surf = ax.plot_surface(X, Y, Z,cmap=cm.coolwarm)
ax.tick_params(axis='both', which='major', labelsize=6)
ax.set_zlim(0, 300)
ax.xaxis.set_major_locator(IndexLocator(1, 0))
ax.xaxis.set_major_formatter(FixedFormatter([repr(v) for v in x]))
ax.yaxis.set_major_locator(IndexLocator(1, 0))
ax.yaxis.set_major_formatter(FixedFormatter([repr(v) for v in y]))
fig.colorbar(surf, shrink=0.5, aspect=5)
If one wanted to show the specific numbers given for X and Y, one solution would be to plot with a logarithmic axis (since the numbers are spaced very approximately in a log way), and then plot the numbers specifically by lines on the axes, or alternatively, don't use these numbers instead of the usual regularly spaced numbers. (But to plot these as axes values, and space them visually at regular intervals, that is a problem.)

Related

How to specify space between matplotlib legend markers

I am looking through the matplotlib api and can't seem to find a way to change the space between legend markers. I came across a way to change the space between a marker and its respective handle with handletextpad, but I want to change the space between each marker.
Ideally, I'd like to have the markers touching eachother with the labels above (or on top of) the markers.
My legend:
What I am trying to model:
Is there a way to do this?
I am not sure if this matches your expectations. We have used the standard features to create a graph that is similar to your objectives. Since the code and data are unknown to me, I customized the example in the official reference to create it, using handletextpad and columnspacing, and since the numbers are in font units, I achieved this with a negative value.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(19680801)
fig, ax = plt.subplots(figsize=(8,8))
for color in ['tab:blue', 'tab:orange', 'tab:green']:
n = 750
x, y = np.random.rand(2, n)
scale = 200.0 * np.random.rand(n)
ax.scatter(x, y, c=color, s=scale, label=color.split(':')[1][0],
alpha=0.5, edgecolors='none')
handlers, labels = ax.get_legend_handles_labels()
print(labels)
ax.legend(handletextpad=-1.2, columnspacing=-0.5, ncol=3,loc="upper left", bbox_to_anchor=(0.75, 1.08))
ax.grid(True)
plt.show()

Linearly scale axes from kilometers to meters for all plots in matplotlib

I am working with data in meters and want to plot positions. Having the ticks in meters is obscuring the readability of the plots, so I want to plot the data in kilometers. I know that it is possible to scale all data d/1000., however, this makes the code less readable in my eyes, especially if you're plotting many different lines, where you have to do this transformation every time.
I am looking for a general way to achieve this type of transformation, I could imagine there is a beautiful way to achieve this in matplotlib.
Some sample code for you:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(7500, 30000, 300)
y_ref = np.linspace(5000, 15000, 300)
y_noised = y_ref + np.random.normal(0, 250, size=y_ref.size)
fig = plt.Figure(figsize=(6,6))
ax = fig.add_subplot(1, 1, 1)
ax.plot(x, y_ref, c='r')
ax.scatter(x, y_noised, alpha=0.2)
fig
I would like to have the following figure, without needing to scale x, y_ref and y_noised individually by 1000.
Is there a way to perform this transformation in matplotlib, such that you only need to do it for each figure once, no matter how many lines you plot?
You could use a custom tick formatter like that (passing a function into set_major_formatter creates a FuncFormatter):
m2km = lambda x, _: f'{x/1000:g}'
ax.xaxis.set_major_formatter(m2km)
ax.yaxis.set_major_formatter(m2km)

matplotlib 3: 3D scatter plots with tight_layout

I have some code which produces a 3D scatter plot using matplotlib's scatter in conjunction with tight_layout, see the simplified code below:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import proj3d
fig = plt.figure()
ax = fig.gca(projection='3d')
N = 100
x = np.random.random(N)
y = np.random.random(N)
z = np.random.random(N)
ax.scatter(x, y, z)
plt.tight_layout() # <-- Without this, everything is fine
plt.savefig('scatter.png')
In matplotlib 2.2.3 this makes a figure like so:
Similar output is generated by older versions, at least back to 1.5.1. When using the new version 3.0.0, something goes wrong at plt.tight_layout() and I get the following output:
Accompanying this is the warning
.../matplotlib/tight_layout.py:177: UserWarning: The left and right margins cannot be made large enough to accommodate all axes decorations
One may argue that using tight_layout with no arguments as here does not (on older matplotlibs) consistently lead to the expected tightened margins anyway, and so one should refrain from using tight_layout with 3D plots in the first place. However, by manually tweaking the arguments to tight_layout it is (used to be) a decent way to trim the margins even on 3D plots.
My guess is that this is a bug in matplotlib, but maybe they've made some deliberate change I havn't picked up on. Any pointers about a fix is appreciated.
Thanks to the comment by ImportanceOfBeingErnest, it now works:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import proj3d
fig = plt.figure()
ax = fig.gca(projection='3d')
N = 100
x = np.random.random(N)
y = np.random.random(N)
z = np.random.random(N)
ax.scatter(x, y, z)
# The fix
for spine in ax.spines.values():
spine.set_visible(False)
plt.tight_layout()
plt.savefig('scatter.png')
From the links in the comment, it seems that this will be fixed in matplotlib 3.0.x. For now, the above may be used.
plt.tight_layout()
plt.show()
Right below your main body code of plotting.

How do to tighten the bounds of my 'matplotlib' figures to be closer to my axes?

When matplotlib makes figures, I find that it "pads" the space around axes too much for my taste (and in an asymmetrical way). For example with
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
x, y = 12*np.random.rand(2, 1000)
ax.set(xlim=[2,10])
ax.plot(x, y, 'go')
I get something that looks like
(here for example in Adobe Illustrator).
I'd like the bounds of the figure to be closer to the axes on all sides, especially on the left and right.
How can I adjust these bounds programmatically in matplotlib, relative to each axis?
try:
plt.tight_layout()
the default parameter set is:
plt.tight_layout(pad=1.08, h_pad=None, w_pad=None, rect=None)

Plotting mplot3d / axes3D xyz surface plot with log scale?

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
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
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⁻¹ 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⁻¹, 10⁻² 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⁻¹ instead of 10^-1.5.
Example:
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⁻³ 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()
in osx: ran ax.zaxis._set_scale('log') (notice the underscore)
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)
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.

Categories

Resources