Python 3: Issues setting secondary y axis labels with FuncFormatter - python

I have created a figure that has two y axes that are sharing one x axis. The y axes are correlated to each other: the values of the left y-axis are an input to an equation that gives the values of the right y-axis. To correlate the two, I set the y ticks on each axis to be the same. Then I tried to use a function (myticks) to label the y ticks on each axis with the proper labels using set_major_formatter(ticker.FuncFormatter(myticks)). The y ticks are in the correct position on each axes and the labels are correct on the left axis, but the labels are incorrect on the right axis. For some reason, the left axis labels are showing up on the right axis as well. The values of the right axis should be the values present in right_y2. I'm brand new to Python, so any help is greatly appreciated!
#plot
fig = plt.figure(figsize=(3,4))
ax1 = fig.add_subplot(111)
y =[1.9E19,1E20,5E20,1.8E21,1E22,1.9E22,1.15E23]
ax1.plot(2,y[0],marker='o')
ax1.plot(2,y[1],marker='o')
ax1.plot(2,y[2],marker='o')
ax1.plot(2,y[3],marker='o')
ax1.plot(2,y[4],marker='o')
ax1.plot(2,y[5],marker='o')
ax1.plot(2,y[6],marker='o')
ax1.set_yscale("log")
ax1.set_ylim(1E19,2E23)
ax1.set_yticks(y)
def myticks(left_y,y):
exponent = int(np.log10(left_y))
coeff = left_y/10**exponent
return r"${:2.0f} \times 10^{{ {:2d} }}$".format(coeff,exponent)
ax1.yaxis.set_major_formatter(ticker.FuncFormatter(myticks))
ax2 = ax1.twinx()
ax2.set_yscale("log")
ax2.set_ylim(1E19,2E23)
ax2.set_yticks(y)
def myticks2(right_y2,y):
exponent2 = int(np.log10(right_y2))
coeff2 = right_y2/10**exponent2
return r"${:2.0f} \times 10^{{ {:2d} }}$".format(coeff2,exponent2)
ax2.yaxis.set_major_formatter(ticker.FuncFormatter(myticks2)
where
left_y =[1.9E19,1E20,5E20,1.8E21,1E22,1.9E22,1.15E23]
right_y2 =[5.3E12,3.8E13,1.3E14,2.7E14,5E14,9.6E14,3E15]
I get the following figure:
enter image description here

Related

How to assign custom x labels on plot with uneven spacing

I need to create a graph with uneven 'x axis' and label them.
For example:
x = [2,5,10,20,30]
y = [100,200,312,788,123]
I want the x axis on plot to be spaced as x itself. I solved the issue with following code. But instead of exact x values I would like to name them in the order of occurrence, ie 1,2,3,4,5 in place of 2,5,10,20,30.
Thank you
plt.figure(30)
plt.plot(x,y,color='b',alpha=1)
plt.title('R_variation',fontsize=20)
plt.ylabel(r'R',fontsize=20,color='k')
plt.xlabel('Time (hr)',fontsize=20,color='k')
plt.xticks(x, rotation='vertical')
plt.grid()
plt.show()

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

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:

expand plot for readability without expanding lines

I am plotting 2 lines and a dot, X axis is a date range. The dot is most important, but it appears on the boundary of the plot. I want to "expand" the plot further right so that the dot position is more visible.
In other words I want to expand the X axis without adding new values to Y values of lines. However if I just add a few dates to X values of lines I get the "x and y dimensions must be equal" error. I tried to add a few np.NaN values to Y so that dimensions are equal, but then I get an error "integer required".
My plot:
My code:
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
plot_x = train_original.index.values
train_y = train_original.values
ax1.plot(plot_x, train_y, 'grey')
x = np.concatenate([np.array([train_original.index.values[-1]]), test_original.index.values])
y = np.concatenate([np.array([train_original.dropna().values[-1]]), test_original.dropna().values])
ax1.plot(x, y, color='grey')
ax1.plot(list(predicted.index.values), list(predicted.values), 'ro')
ax1.axvline(x=train_end, alpha=0.7, linestyle='--',color='blue')
plt.show()
There are a couple of ways to do this.
An easy, automatic way to do this, without needing knowledge of the existing xlim is to use ax.margins. This will add a certain fraction of the data limits to either side of the plot. For example:
ax.margins(x=0.1)
will add 10% of the current x range to both ends of the plot.
Another method is to explicitly set the x limits using ax.set_xlim.
Just change the xlim(). Something like:
xmin, xmax = plt.xlim() # return the current xlim
plt.xlim(xmax=xmax+1)

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.

make only left subplots and bottom subplots (in MxN panels) contain x and y axis labels

Is there any way in a panel of NxM subplots to just have the axes being shown for the left column and bottom row.
A|N|N|N
A|N|N|N
A|N|N|N
A|A|A|A
Where A = axis and N = no axis
Sometimes my subplots are 10x8, 3x4, 4x9 etc. and they all have the same x and y axis. I just want it to appear on the very left and the very bottom of that subset. At the moment I have to know which axis it is plotting to and do
if (figi-1) % 7 != 0:
ax.set_yticklabels([])
if figi < 29:
ax1.set_xticklabels([])
I want to generalise this for any NxM panel arrangement without having to know before hand.
Thanks.
EDIT: I have found a way to do the y-axis. I setup the number of panels wide using:
nwide = 12
nhigh = 5
Which means I can just do
if (figi-1) % nwide != 0:
ax.set_yticklabels([])
Any ideas for bottom?
EDIT: Worked it out. x-axis is as follows:
if figi < (nwide*nhigh) - nwide:
ax.set_xticklabels([])
The best solution is probably pyplot.subplots(). You can do like:
fig, axes = pyplot.subplots(nrows=3, ncols=4, sharex=True, sharey=True)
and then only the left and bottom axes will have the labels displayed.
To access each subplot you can get it from axes like you do in a matrix: ax = axes[i,j]
To control the tick positions you can use:
ax.xaxis.set_tick_position('bottom')
ax.yaxis.set_tick_position('left')
To set an axis label you can do:
ax.set_label_text('Something')

Categories