Can't Get Axis to Align Right on MatPlotLib 3d - python

I'm trying to do a 3d matplot graph. I'm having trouble getting the full axis to show with nicely aligned labels. I've outlined the steps I've tried below.
1) I can set the y-axis labels using:
yTicks = list(range(0,90,5)
ax.set_yticks(range(len(yTicks)), True)
However, as you can see, the labels are very badly aligned. It also isn't matching what I've actually defined, which should have been ticks counting by 5, not 10.
2) If I try using set_yticklabels as well, though, the alignment fixes but it only prints part of the axis. Here is the code and image:
ax.set_yticklabels(yTicks, verticalalignment='baseline',
horizontalalignment='left')
Notice how the y-axis went from 80 to 40.
3) And if I get rid of the True in set_yticks, everything squishes together:
4) Finally, if I use both set_yticks and set_yticklabels calling get_yticks() in the labels function, it almost works but you can see the axis lines extend beyond the "surface" of the graph:
ax.set_yticks(range(len(yTicks)), True)
ax.set_yticklabels(ax.get_yticks(), verticalalignment='baseline',
horizontalalignment='left')
5) Here is a more complete version of my code for reference:
plt.clf()
ax = plt.axes(projection='3d')
ax.bar3d(x,y,z,
1,1,[val*-1 if val != 0 else 0 for val in z])
xTicks = list(range(0,25,2))
yTicks = list(range(30,90,5))
ax.set_zlim(0, 1)
ax.set_xticks(range(len(xTicks)), True)
ax.set_yticks(range(len(yTicks)), True)
ax.set_xticklabels(ax.get_xticks(),
verticalalignment='baseline',
horizontalalignment='left')
ax.set_yticklabels(ax.get_yticks(),
verticalalignment='baseline',
horizontalalignment='left')
plt.savefig(file_path)
How can I get it to show my full axis (0-90) at intervals of 5 and have it aligned well?
6) UPDATE: Per the conversation below with #ImportanceOfBeingErnest, here is the issue I'm still experiencing using the following code:
x=[15,28,20]; y=[30,50,80]; z=[1,1,1]
plt.clf()
ax = plt.axes(projection='3d')
ax.bar3d(x,y,z,
1,1,[val*-1 if val != 0 else 0 for val in z])
xTicks = list(range(0,25,2))
yTicks = list(range(30,90,5))
ax.set_xticks(xTicks)
ax.set_yticks(yTicks)
ax.set_yticklabels(ax.get_yticks(),
verticalalignment='baseline',
horizontalalignment='left')
ax.set_zlim(0, 1)
plt.savefig(getSaveGraphPath(save_name))

As commented, you can set the ticks via ax.set_yticks.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x=[15,28,20]; y=[30,50,80]; z=[1,1,1]
ax = plt.axes(projection='3d')
ax.bar3d(x,y,z,
1,1,[val*-1 if val != 0 else 0 for val in z])
yTicks = list(range(30,90,5))
ax.set_yticks(yTicks)
ax.set_yticklabels(ax.get_yticks(),
verticalalignment='baseline',
horizontalalignment='left')
ax.set_zlim(0, 1)
plt.show()
This will show the desired 5 unit steps on the y axis.

So after much trial-and-error, the only way I could get the graph to render axes appropriately in various limit cases is as follows. I'm not completely happy with it (notice how the last y-tick label doesn't appear) but it is the only version that has the numbers actually next to their tick marks). I had to let x and y limits be effective only if the data didn't exceed their values, whereas the z boundary is a hard limit. I don't claim to understand why these permutations are all necessary (this all is only an issue with 3D plotting), but this is the solution that works for me:
plt.clf()
ax = plt.axes(projection='3d')
# Need to force include fake NaN data at the axis limits to make sure labels
# render correctly
#
# xLims and yLims create boundaries only if data doesn't stretch beyond them
xstart, xend = xLims
ystart, yend = yLims
x = [xstart] + x + [xend]
y = [ystart] + y + [yend]
z = [numpy.nan] + z + [numpy.nan]
# Plot graph
ax.bar3d(x,y,z,1,1,[val*-1 if val != 0 else 0 for val in z])
# Set z boundary (after graph creation)
ax.set_zbound(zBounds)
# Need to adjust labels slightly to make sure they align properly
use_x_ticks = ax.get_xticks()
### ON SOME SYSTEMS, use_x_ticks = ax.get_xticks()[1:] is needed for correct alignment ###
ax.set_xticklabels([int(x) if x==int(x) else x for x in use_x_ticks],
horizontalalignment='right',
verticalalignment='baseline')
ax.set_yticklabels([int(y) if y==int(y) else y for y in ax.get_yticks()],
verticalalignment='top')
# Save graph
plt.savefig(file_save_path)
As you can see below, everything is nicely aligned:

Related

Matplotlib not showing correct and desired x-axis

I have a barplot I am trying to plot without the x-axis ticks overlapping. I have settled on an angle of 45 degrees, and a max. number of ticks of 50, as this is about the max. of what can be shown without overlapping (IF the ticks are tilted at 45 degrees).
However, in my attempts I ran into the problem of Matplotlib not setting the x-axis to what I desire, whatever I try. I need to plot multiple datasets, for all of which the time runs from -15.8 through somewhere around 1200-1800.
I tried several solutions I found online, but all to no avail. The code below does not work, as it does not show the correct ticks. The range stops well before the last number in the timepoints list.
import numpy as np
from matplotlib import pyplot as plt
# Mock data
timepoints = list(np.arange(-15.8, 1276.2, 4))
patient_counts = np.random.randint(300, 600, len(timepoints))
x_tick_pos = [i + 0.5 for i in range(len(timepoints))]
# Plot barplot
fig, ax = plt.subplots(figsize=(16, 10))
ax.bar(x_tick_pos, patient_counts, align='center', width=1.0)
# Set x axis ticks
ax.set_xticklabels(timepoints, rotation=45)
ax.locator_params(axis='x', nbins=20)
plt.show()
Clearly, the x-axis does not come close to the expected values.
EDIT
To expand, this question is a follow-up from this thread. The code based on the answer in that question is as follows
# Plot barplot
fig, ax = plt.subplots(figsize=(16, 10))
ax.bar(x_tick_pos, patient_counts, align='center', width=1.0)
# Set x axis ticks
ax.set_xticks(x_tick_pos)
ax.set_xticklabels(x_ticks, rotation=45)
This appears to set the right x-ticks, except they overlap a lot- hence why I want only a max of 50 ticks to show:
This might be a simple case of fixing the x_tick_pos list expression. In your mock example, if we print them out ...
x_tick_pos = [i + 0.5 for i in range(len(timepoints))]
print(x_tick_pos[:5], x_tick_pos[-5:])
... we get what your figure reflects:
[0.5, 1.5, 2.5, 3.5, 4.5] [318.5, 319.5, 320.5, 321.5, 322.5]
Changing the assignment to
x_tick_pos = [i + 0.5 for i timepoints]
would appear to give the expected ticks.
The issue is that the positioning of the ticks is written so that they line up with another graph above this one, as per this post.
There are two solutions:
forget about positioning the ticks relative to another graph, in case this bar plot is plotted in a standalone fashion
resetting the ticks after plotting the bar plot to give them correct labels:
# Plot barplot
fig, ax = plt.subplots(figsize=(16, 10))
ax.bar(x_tick_pos, patient_counts, align='center', width=1.0)
# Set x axis ticks
ticks_step = int(len(missings_df.index) / 50) # 50 here is the max. nr of ticks
x_ticks = [missings_df.index[i] for i in range(0, len(missings_df.index), int(len(missings_df)/50))]
x_tick_pos = [i + 0.5 for i in range(0, len(missings_df.index), int(len(missings_df)/50))]
ax.set_xticks(x_tick_pos)
ax.set_xticklabels(x_ticks, rotation=45)
This correctly plots the x-axis:

Forcing axis labels - How to include x,y inputs for a Z matrix to have sensible axes

I have a Z matrix and when I plug it into pcolormesh, it works perfectly and gives me the following plot. The only problem is that the axes now displays the matrix indices. The code that I used make it is given below:
#boo - most of the parameters like title,xyz labels, filename comes from command line
data = np.loadtxt((args.data),dtype=float, comments="#")
cmap = plt.get_cmap('bwr')
fig, ax0 = plt.subplots()
divnorm = colors.DivergingNorm(vmin=np.amin(data), vcenter=0, vmax=np.amax(data))
im0 = ax0.pcolormesh(data,norm=divnorm, cmap=cmap)
fig.colorbar(im0,ax=ax0)
ax0.set_title(str(title))
plt.xlabel(str(xlabel))
plt.ylabel(str(ylabel))
filename = str(prefix) + "."+ str(fileformat)
plt.savefig(filename)
I wanted to rescale the x-axis by a factor of 0.1 (ended up doing it manually since I did not see a workaround) and set the y-axis to change with respect to another array (Note that: I'm not manipulating Z matrix instead I'm using a physically meaningful experimental value array - here, sortData - corresponding to matrix indices). I changed my code as follows - x axis and yaxis seem alright but my heatmap looks different. Can someone shine some light on this? Many Thanks
#foo
Data = np.loadtxt((args.data),dtype=float, comments="#")
sort = np.loadtxt((args.sortData),dtype=float, comments="#")
fig, ax0 = plt.subplots()
cmap = plt.get_cmap('bwr')
divnorm = colors.DivergingNorm(vmin=np.amin(Data), vcenter=0, vmax=np.amax(Data))
# im0 = ax0.pcolormesh(Data,norm=divnorm, cmap=cmap)
# ax0.set_xscale(1, "linear")
x = np.arange(0.0,10.6,0.1) # need to set the ticks manually
y = sort[:,1]
X,Y = np.meshgrid(x,y)
Z=z.reshape(len(y),len(x))
im0 = ax0.pcolormesh(X,Y,Data,norm=divnorm, cmap=cmap)#, extent=[x.min(), x.max(), y.min(), y.max()])
#im0 = ax0.pcolormesh(x,y,Data,norm=divnorm, cmap=cmap)#, extent=[x.min(), x.max(), y.min(), y.max()])
cbar = fig.colorbar(im0,ax=ax0)
if args.zlabel !=None:
cbar.ax.set_ylabel(str(args.zlabel))
ax0.set_title(str(args.title))
plt.xlabel(str(args.xlabel))
plt.ylabel(str(args.ylabel))
filename = str(args.prefix) + "."+ str(args.fileformat)
plt.savefig(filename)
EDIT 1:
When I plot the boo, y-axis is uniformly spaced since we are dealing with the matrix indices. When I plot foo, they are not since the array values corresponding to these indices ( not that of Data matrix but the external y array that is of same dim as Data but has values stored in it corresponding to expt) are not equally spaced. The problem is The y values corresponding to The first 5 y indexes of Data matrix are 1.32, 3.200, 3.311, 3.38, 3.40 and their x values change throughout the range [xmin to xmax]. But there's a giant blob of red thing between 0 and 5 (Y) that goes horizontally all the way till the end of xlim. Clearly something is wrong but can't figure out what it is.
I'm not 100% clear on what you're trying to do, but if you're trying to plot the data in Boo but with some different set of tick labels then I think a modification of the following self-contained example would probably work for you.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import colors
title = 'Foo'
xlabel = 'X'
ylabel = 'Y'
rv = np.random.rand(100) # uniform random vector
data = rv[:,None] - rv.T # pairwise diffs
cmap = plt.get_cmap('bwr')
fig, ax = plt.subplots()
divnorm = colors.DivergingNorm(vmin=np.amin(data), vcenter=0, vmax=np.amax(data))
im0 = ax.pcolormesh(data, norm=divnorm, cmap=cmap)
fig.colorbar(im0, ax=ax)
# do tick labeling stuff here
nticks = 5
x_tick_pos = np.linspace(0,100,nticks)
y_tick_pos = np.linspace(0,100,nticks)
ax.set_xticks(x_tick_pos)
ax.set_yticks(y_tick_pos)
xtick_labels = [str(x) for x in np.linspace(0, 10, nticks)] # can be any list of strings
ytick_labels = [str(y) for y in np.linspace(0, 10, nticks)] # len must match nticks
ax.set_xticklabels(xtick_labels)
ax.set_yticklabels(ytick_labels)
ax.set_title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.show()
Note that if you want to do fancier things, like have the tick labels rotated so that they can be easier to read, you might be aided by checking out the matplotlib tutorial on labeling heatmaps.

Is it possible to test if the legend is covering any data in matplotlib/pyplot

Python beginner so apologies if incorrect terminology at any point.
I am using the legend(loc='best', ...) method and it works 99% of the time. However, when stacking more than 9 plots (i.e. i>9 in example below) on a single figure, with individual labels, it defaults to center and covers the data.
Is there a way to run a test in the script that will give a true/false value if the legend is covering any data points?
Very simplified code:
fig = plt.figure()
for i in data:
plt.plot(i[x, y], label=LABEL)
fig.legend(loc='best')
fig.savefig()
Example of legend covering data
One way is to add some extra space at the bottom/top/left or right side of the axis (in your case I would prefer top or bottom), by changing the limits slightly. Doing so makes the legend fit below the data. Add extra space by setting a different y-limit with ax.set_ylim(-3e-4, 1.5e-4) (the upper limit is approximately what it is in your figure and -3 is a estimate of what you need).
What you also need to do is to add split the legend into more columns, with the keyword ncol=N when creating the legend.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(0, 1, 100)
y = 3.5 * x - 2
for i in range(9):
ax.plot(x, y + i / 10., label='iiiiiiiiiiii={}'.format(i))
ax.set_ylim(-3, 1.5)
ax.legend(loc='lower center', ncol=3) # ncol=3 looked nice for me, maybe you need to change this
plt.show()
EDIT
Another solution is to put the legend in a separate axis like I do in the code below. The data-plot does not need to care about making space for the legend or anything and you should have enough space in the axis below to put all your line-labels. If you need more space, you can easily change the ratio of the upper axis to the lower axis.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(211)
ax_leg = fig.add_subplot(212)
x = np.linspace(0, 1, 100)
y = 3.5 * x - 2
lines = []
for i in range(9): #for plotting the actual data
li, = ax.plot(x, y + i / 10., label='iiiiiiiiiiii={}'.format(i))
lines.append(li)
for line in lines: # just to make the legend plot
ax_leg.plot([], [], line.get_color(), label=line.get_label())
ax_leg.legend(loc='center', ncol=3, ) # ncol=3 looked nice for me, maybe you need to change this
ax_leg.axis('off')
fig.show()

How to change the x-axis unit in matplotlib? [duplicate]

I am creating a plot in python. Is there a way to re-scale the axis by a factor? The yscale and xscale commands only allow me to turn log scale off.
Edit:
For example. If I have a plot where the x scales goes from 1 nm to 50 nm, the x scale will range from 1x10^(-9) to 50x10^(-9) and I want it to change from 1 to 50. Thus, I want the plot function to divide the x values placed on the plot by 10^(-9)
As you have noticed, xscale and yscale does not support a simple linear re-scaling (unfortunately). As an alternative to Hooked's answer, instead of messing with the data, you can trick the labels like so:
ticks = ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x*scale))
ax.xaxis.set_major_formatter(ticks)
A complete example showing both x and y scaling:
import numpy as np
import pylab as plt
import matplotlib.ticker as ticker
# Generate data
x = np.linspace(0, 1e-9)
y = 1e3*np.sin(2*np.pi*x/1e-9) # one period, 1k amplitude
# setup figures
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
# plot two identical plots
ax1.plot(x, y)
ax2.plot(x, y)
# Change only ax2
scale_x = 1e-9
scale_y = 1e3
ticks_x = ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x/scale_x))
ax2.xaxis.set_major_formatter(ticks_x)
ticks_y = ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x/scale_y))
ax2.yaxis.set_major_formatter(ticks_y)
ax1.set_xlabel("meters")
ax1.set_ylabel('volt')
ax2.set_xlabel("nanometers")
ax2.set_ylabel('kilovolt')
plt.show()
And finally I have the credits for a picture:
Note that, if you have text.usetex: true as I have, you may want to enclose the labels in $, like so: '${0:g}$'.
Instead of changing the ticks, why not change the units instead? Make a separate array X of x-values whose units are in nm. This way, when you plot the data it is already in the correct format! Just make sure you add a xlabel to indicate the units (which should always be done anyways).
from pylab import *
# Generate random test data in your range
N = 200
epsilon = 10**(-9.0)
X = epsilon*(50*random(N) + 1)
Y = random(N)
# X2 now has the "units" of nanometers by scaling X
X2 = (1/epsilon) * X
subplot(121)
scatter(X,Y)
xlim(epsilon,50*epsilon)
xlabel("meters")
subplot(122)
scatter(X2,Y)
xlim(1, 50)
xlabel("nanometers")
show()
To set the range of the x-axis, you can use set_xlim(left, right), here are the docs
Update:
It looks like you want an identical plot, but only change the 'tick values', you can do that by getting the tick values and then just changing them to whatever you want. So for your need it would be like this:
ticks = your_plot.get_xticks()*10**9
your_plot.set_xticklabels(ticks)

Second y scale repeating axis ticks

I have some code below which plots 3 sets of random numbers by adding them to a plot (simulating real world data gathered from say a temperature sensor).
I am attempting to make 2 scales on the same plot.
Here, y2List is negative and this is the data set that I would like to create the second axis for. I figured out how to do this using other questions on here.
The problem is that when each data point is added, the second y axis ticks are shown again so that the second y axis is very crowded with numbers. I can get round this by setting a limit on the second y axis, which produces an image like this:
The second y axis is slightly darker than the others, and this is because python is plotting the same numbers on top of the existing ones after each point is plotted (I can tell because the numbers get darker as each point is plotted)
My question... is there a way to make the second y axis only plot the second scale only once? This is obviously just to make the plot aesthetically pleasing but every little helps!
My code is below:
plt.ion() # enable interactivity
def makeFig():
ax.plot(xList, yList, color='blue', label='something1' if x == 0 else '')
ax.plot(xList, y1List, color='red', label='something2' if x == 0 else '')
ax2 = ax.twinx()
ax2.plot(xList, y2List, color='orange', label='something else' if x == 0 else '')
ax2.set_ylim(-20,0)
xList=list()
yList=list()
y1List=list()
y2List=list()
x=0
while x<11:
fig1=plt.figure(1)
ax = fig1.add_subplot(111)
x_1 = datetime.datetime.now()
date_formatter = DateFormatter('%H:%M:%S')
y=np.random.random()
y1=np.random.random() *3
y2=np.random.random() *(-13)
xList.append(x_1)
yList.append(y)
y1List.append(y1)
y2List.append(y2)
makeFig()
plt.gcf().autofmt_xdate()
ax = plt.gca()
ax.xaxis.set_major_formatter(date_formatter)
max_xticks = 10
xloc = plt.MaxNLocator(max_xticks)
ax.xaxis.set_major_locator(xloc)
plt.get_current_fig_manager().window.wm_geometry("940x700+5+0")
plt.draw()
plt.legend(loc=2, bbox_to_anchor=(1, 0.5), prop={'size':10})
x+=1
plt.pause(0.5)
You should move the creation of the figure and the twin axes outside of your loop. They only need to be done once.
Specifically, move fig1=plt.figure(1), ax = fig1.add_subplot(111) and ax2 = ax.twinx() outside the loop.

Categories