matplotlib not honoring zorder - python

I've reviewed a number of posts (e.g., this one) discussing zorder, and based on the responses I perused, it seems like the following small reproducible example should not be drawing the grid on top of the bar. Or in other words, shouldn't the fact that the gridlines are assigned to ax2, which has a lower zorder number, make it so they are drawn below the bar and the triangle? How does one force the gridlines to be below everything else?
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots(figsize=(5, 4))
pts = ax1.plot(1, 1, 'r^', label='Stream Flow')
ax1.set_zorder(4)
ax1.set_facecolor('none')
ax1.set_xlim([0, 3])
ax1.set_ylim([0, 3])
ax2 = ax1.twinx()
ax2.set_xlim([0, 3])
ax2.set_ylim([0, 30])
bar = ax2.bar(1, 15, align='center', color='b', width=0.1, label='Some other value')
ax2.set_zorder(2)
ax2.set_ylabel(r'Other value', rotation=270, labelpad=15)
lns = pts + [bar]
labs = [l.get_label() for l in lns]
leg = ax2.legend(lns, labs, loc='lower right', frameon=True)
leg.get_frame().set_linewidth(0.0)
ax2.yaxis.grid(color='silver', zorder=1)
# Following two lines were experiments that failed
#fig.set_zorder(ax2.get_zorder()-1)
#fig.patch.set_visible(False)
plt.show()
Here's what I'm seeing, the gridlines are plotted over the bar (bad) but below the triangle (good).

Related

Modifying subplots sizes

I have been trying to find some answers, but most of them don't include a table, or they solve the problem generally and I get in trouble trying to find a workaround with the table I created as I managed to put the table through an empty axis. But now decreasing the right-axis size (as the table gets accommodated to the axis size) and increasing the left two-axis size is becoming a daunting task.
I have this code:
fig = plt.figure(figsize=(18,5))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(223)
ax3 = fig.add_subplot(122)
ax3.axis('off')
data = pd.DataFrame({'metrics': ['MSLE train', 'msle_test', 'asdsad'],
'values': [0.43, 0.52, 0.54]})
ax3.table(cellText=data.values, colLabels=data.columns, loc='center')
fig.suptitle(f'Train MSLE: {msle_train}, Test MSLE: {msle_test}')
ax1 = y_data.plot(label='Original data', ax=ax1, c='blue')
ax1 = y_pred_train.plot(ax=ax1, c='orange')
ax1 = y_pred_test.plot(ax=ax1, c='orange', linestyle='--')
ax1.legend()
ax2 = error_train.plot(label='Train error', ax=ax2)
ax2 = error_test.plot(label='Test error', ax=ax2, linestyle='--')
ax2.legend()
plt.show()
That returns this plot:
I'm looking to increase the horizontal size of the two left plots, something near the red mark:
Any suggestions?
You can use gridspec.
It even works with a vertical centered right hand side and a table:
import matplotlib.pyplot as plt
from matplotlib import gridspec
import pandas as pd
data = pd.DataFrame({'metrics': ['MSLE train', 'msle_test', 'asdsad'],
'values': [0.43, 0.52, 0.54]})
fig = plt.figure(figsize=(18,5))
gs = gridspec.GridSpec(4, 2, width_ratios=[3,1])
ax1 = fig.add_subplot(gs[0:2,:-1])
ax1.set_title('ax1')
ax2 = fig.add_subplot(gs[2:4,:-1])
ax2.set_title('ax2')
ax3 = fig.add_subplot(gs[1:3,1])
ax3.set_axis_off()
ax3.table(cellText=data.values, colLabels=data.columns, loc='center')
fig.tight_layout()
plt.show()
Notes:
Horizontal alignment is set with the ratio of width_ratios=[3,1]
fig.tight_layout() is helpfull to automatically align the spacing between the plots.
Vertical centering is achieved with a little workaround by having initially a larger vertical grid than required (no. of vertical plots) and distributing the plots and table accordingly (see e.g. gs[2:4).
The titles were just added for visual orientation.
ax3.set_axis_off() is required to suppress the plot frame at the table position - without it you'll get:

How do you controle zorder across twinx in matplotlib?

I'm trying to control the zorder of different plots across twinx axes. How can I get the blue noisy plots to appear in the background and the orange smoothed plots to appear in the foreground in this plot?
from matplotlib import pyplot as plt
import numpy as np
from scipy.signal import savgol_filter
random = np.random.RandomState(0)
x1 = np.linspace(-10,10,500)**3 + random.normal(0, 100, size=500)
x2 = np.linspace(-10,10,500)**2 + random.normal(0, 100, size=500)
fig,ax1 = plt.subplots()
ax1.plot(x1, zorder=0)
ax1.plot(savgol_filter(x1,99,2), zorder=1)
ax2 = ax1.twinx()
ax2.plot(x2, zorder=0)
ax2.plot(savgol_filter(x2,99,2), zorder=1)
plt.show()
Similar to this thread, though not ideal, this is an approach using twiny along with twinx.
# set up plots
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax3 = ax1.twiny()
ax4 = ax2.twiny()
# background
ax1.plot(x1)
ax2.plot(x2)
# smoothed
ax3.plot(savgol_filter(x1,99,2), c='orange')
ax4.plot(savgol_filter(x2,99,2), c='orange')
# turn off extra ticks and labels
ax3.tick_params(axis='x', which='both', bottom=False, top=False)
ax4.tick_params(axis='x', which='both', bottom=False, top=False)
ax3.set_xticklabels([])
ax4.set_xticklabels([])
# fix zorder
ax1.set_zorder(1)
ax2.set_zorder(2)
ax3.set_zorder(3)
ax4.set_zorder(4)
plt.show()
Output:

Python hide ticks but show tick labels

I can remove the ticks with
ax.set_xticks([])
ax.set_yticks([])
but this removes the labels as well. Any way I can plot the tick labels but not the ticks and the spine
You can set the tick length to 0 using tick_params (http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.tick_params):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1],[1])
ax.tick_params(axis=u'both', which=u'both',length=0)
plt.show()
Thanks for your answers #julien-spronck and #cmidi.
As a note, I had to use both methods to make it work:
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(11, 3))
data = np.random.random((4, 4))
ax1.imshow(data)
ax1.set(title='Bad', ylabel='$A_y$')
# plt.setp(ax1.get_xticklabels(), visible=False)
# plt.setp(ax1.get_yticklabels(), visible=False)
ax1.tick_params(axis='both', which='both', length=0)
ax2.imshow(data)
ax2.set(title='Somewhat OK', ylabel='$B_y$')
plt.setp(ax2.get_xticklabels(), visible=False)
plt.setp(ax2.get_yticklabels(), visible=False)
# ax2.tick_params(axis='both', which='both', length=0)
ax3.imshow(data)
ax3.set(title='Nice', ylabel='$C_y$')
plt.setp(ax3.get_xticklabels(), visible=False)
plt.setp(ax3.get_yticklabels(), visible=False)
ax3.tick_params(axis='both', which='both', length=0)
plt.show()
While attending a coursera course on Python, this was a question.
Below is the given solution, which I think is more readable and intuitive.
ax.tick_params(top=False,
bottom=False,
left=False,
right=False,
labelleft=True,
labelbottom=True)
This worked for me:
plt.tick_params(axis='both', labelsize=0, length = 0)
matplotlib.pyplot.setp(*args, **kwargs) is used to set properties of an artist object. You can use this in addition to get_xticklabels() to make it invisible.
something on the lines of the following
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(2,1,1)
ax.set_xlabel("X-Label",fontsize=10,color='red')
plt.setp(ax.get_xticklabels(),visible=False)
Below is the reference page
http://matplotlib.org/api/pyplot_api.html
You can set the yaxis and xaxis set_ticks_position properties so they just show on the left and bottom sides, respectively.
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')
Furthermore, you can hide the spines as well by setting the set_visible property of the specific spine to False.
axes[i].spines['right'].set_visible(False)
axes[i].spines['top'].set_visible(False)
This Worked out pretty well for me! try it out
import matplotlib.pyplot as plt
import numpy as np
plt.figure()
languages =['Python', 'SQL', 'Java', 'C++', 'JavaScript']
pos = np.arange(len(languages))
popularity = [56, 39, 34, 34, 29]
plt.bar(pos, popularity, align='center')
plt.xticks(pos, languages)
plt.ylabel('% Popularity')
plt.title('Top 5 Languages for Math & Data \nby % popularity on Stack Overflow',
alpha=0.8)
# remove all the ticks (both axes),
plt.tick_params(top='off', bottom='off', left='off', right='off', labelleft='off',
labelbottom='on')
plt.show()
Currently came across the same issue, solved as follows on version 3.3.3:
# My matplotlib ver: 3.3.3
ax.tick_params(tick1On=False) # "for left and bottom ticks"
ax.tick_params(tick2On=False) # "for right and top ticks, which are off by default"
Example:
fig, ax = plt.subplots()
ax.plot([1, 2, 3, 4, 5], [1, 2, 3, 4, 5])
ax.tick_params(tick1On=False)
plt.show()
Output:
Assuming that you want to remove some ticks on the Y axes and only show the yticks that correspond to the ticks that have values higher than 0 you can do the following:
from import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# yticks and yticks labels
yTicks = list(range(26))
yTicks = [yTick if yTick % 5 == 0 else 0 for yTick in yTicks]
yTickLabels = [str(yTick) if yTick % 5 == 0 else '' for yTick in yTicks]
Then you set up your axis object's Y axes as follow:
ax.yaxis.grid(True)
ax.set_yticks(yTicks)
ax.set_yticklabels(yTickLabels, fontsize=6)
fig.savefig('temp.png')
plt.close()
And you'll get a plot like this:

One legend for all subplots in pyplot

I am currently plotting the same data but visualizing it differently in two subplots (see figure):
Code snippet used for producing the above figure:
# Figure
plt.figure(figsize=(14,8), dpi=72)
plt.gcf().suptitle(r'Difference between TI and $\lambda$D', size=16)
# Subplot 1
ax1 = plt.subplot2grid((1,3),(0,0),colspan=2)
# Plot scattered data in first subplot
plt.scatter(LE_x, LE_y, s=40, lw=0, color='gold', marker='o', label=r'$\lambda$D')
plt.scatter(MD_x, MD_y, s=40, lw=0, color='blue', marker='^', label=r'TI')
# Subplot 2
ax2 = plt.subplot2grid((1,3),(0,2))
plt.barh(vpos1, LE_hist, height=4, color='gold', label=r'$\lambda$D')
plt.barh(vpos2, MD_hist, height=4, color='blue', label=r'TI')
# Legend
legend = plt.legend()
Is there any way to make the legend show both the scatter dots and the bars? Would this also go per dummy as described here? Could somebody then please post a minimal working example for this, since I'm not able to wrap my head around this.
This worked for me, you essentially capture the patch handles for each graph plotted and manually create a legend at the end.
import pylab as plt
import numpy as NP
plt.figure(figsize=(14,8), dpi=72)
plt.gcf().suptitle(r'Difference between TI and $\lambda$D', size=16)
# Subplot 1
ax1 = plt.subplot2grid((1,3),(0,0),colspan=2)
N = 100
LE_x = NP.random.rand(N)
LE_y = NP.random.rand(N)
MD_x = NP.random.rand(N)
MD_y = NP.random.rand(N)
# Plot scattered data in first subplot
s1 = plt.scatter(LE_x, LE_y, s=40, lw=0, color='gold', marker='o', label=r'$\lambda$D')
s2 = plt.scatter(MD_x, MD_y, s=40, lw=0, color='blue', marker='^', label=r'TI')
data = NP.random.randn(1000)
LE_hist, bins2 = NP.histogram(data, 50)
data = NP.random.randn(1000)
MD_hist, bins2 = NP.histogram(data, 50)
# Subplot 2
ax2 = plt.subplot2grid((1,3),(0,2))
vpos1 = NP.arange(0, len(LE_hist))
vpos2 = NP.arange(0, len(MD_hist)) + 0.5
h1 = plt.barh(vpos1, LE_hist, height=0.5, color='gold', label=r'$\lambda$D')
h2 = plt.barh(vpos2, MD_hist, height=0.5, color='blue', label=r'TI')
# Legend
#legend = plt.legend()
lgd = plt.legend((s1, s2, h1, h2), (r'$\lambda$D', r'TI', r'$\lambda$D', r'TI'), loc='upper center')
plt.show()

How to add a single colobar that will show the data from 2 different subplot

What i wanna do is adding a single colorbar (at the right side of the figure shown below), that will show the colorbar for both subplots (they are at the same scale).
Another thing doesn't really make sense for me is why the lines I try to draw on the end of the code are not drawn (they are supposed to be horizontal lines on the center of both plots)
Thanks for the help.
Here are the code:
idx=0
b=plt.psd(dOD[:,idx],Fs=self.fs,NFFT=512)
B=np.zeros((2*len(self.Chan),len(b[0])))
B[idx,:]=20*log10(b[0])
c=plt.psd(dOD_filt[:,idx],Fs=self.fs,NFFT=512)
C=np.zeros((2*len(self.Chan),len(b[0])))
C[idx,:]=20*log10(c[0])
for idx in range(2*len(self.Chan)):
b=plt.psd(dOD[:,idx],Fs=self.fs,NFFT=512)
B[idx,:]=20*log10(b[0])
c=plt.psd(dOD_filt[:,idx],Fs=self.fs,NFFT=512)
C[idx,:]=20*log10(c[0])
## Calculate the color scaling for the imshow()
aux1 = max(max(B[i,:]) for i in range(size(B,0)))
aux2 = min(min(B[i,:]) for i in range(size(B,0)))
bux1 = max(max(C[i,:]) for i in range(size(C,0)))
bux2 = min(min(C[i,:]) for i in range(size(C,0)))
scale1 = 0.75*max(aux1,bux1)
scale2 = 0.75*min(aux2,bux2)
fig, axes = plt.subplots(nrows=2, ncols=1,figsize=(7,7))#,sharey='True')
fig.subplots_adjust(wspace=0.24, hspace=0.35)
ii=find(c[1]>=frange)[0]
## Making the plots
cax=axes[0].imshow(B, origin = 'lower',vmin=scale2,vmax=scale1)
axes[0].set_ylim((0,2*len(self.Chan)))
axes[0].set_xlabel(' Frequency (Hz) ')
axes[0].set_ylabel(' Channel Number ')
axes[0].set_title('Pre-Filtered')
cax2=axes[1].imshow(C, origin = 'lower',vmin=scale2,vmax=scale1)
axes[1].set_ylim(0,2*len(self.Chan))
axes[1].set_xlabel(' Frequency (Hz) ')
axes[1].set_ylabel(' Channel Number ')
axes[1].set_title('Post-Filtered')
axes[0].annotate('690nm', xy=((ii+1)/2, len(self.Chan)/2-1),
xycoords='data', va='center', ha='right')
axes[0].annotate('830nm', xy=((ii+1)/2, len(self.Chan)*3/2-1 ),
xycoords='data', va='center', ha='right')
axes[1].annotate('690nm', xy=((ii+1)/2, len(self.Chan)/2-1),
xycoords='data', va='center', ha='right')
axes[1].annotate('830nm', xy=((ii+1)/2, len(self.Chan)*3/2-1 ),
xycoords='data', va='center', ha='right')
axes[0].axis('tight')
axes[1].axis('tight')
## Set up the xlim to aprox frange Hz
axes[0].set_xlim(left=0,right=ii)
axes[1].set_xlim(left=0,right=ii)
## Make the xlabels become the actual frequency number
ticks = linspace(0,ii,10)
tickslabel = linspace(0.,frange,10)
for i in range(10):
tickslabel[i]="%.1f" % tickslabel[i]
axes[0].set_xticks(ticks)
axes[0].set_xticklabels(tickslabel)
axes[1].set_xticks(ticks)
axes[1].set_xticklabels(tickslabel)
## Draw a line to separate the two different wave lengths, and name each region
l1 = Line2D([0,frange],[28,28],ls='-',color='black')
axes[0].add_line(l1)
axes[1].add_line(l1)
And here the figure it makes:
If any more info are needed, just ask.
Basically, figure.colorbar() is good for both images, as long as their are not with too different scales. So you could let matplotlib do it for you... or you manually position your colorbar on axes inside the images. Here is how to control the location of the colorbar:
import numpy as np
from matplotlib import pyplot as plt
A = np.random.random_integers(0, 10, 100).reshape(10, 10)
B = np.random.random_integers(0, 10, 100).reshape(10, 10)
fig = plt.figure()
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222)
mapable = ax1.imshow(A, interpolation="nearest")
cax = ax2.imshow(A, interpolation="nearest")
# set the tickmarks *if* you want cutom (ie, arbitrary) tick labels:
cbar = fig.colorbar(cax, ax=None)
fig = plt.figure(2)
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
mapable = ax1.imshow(A, interpolation="nearest")
cax = ax2.imshow(A, interpolation="nearest")
# on the figure total in precent l b w , height
ax3 = fig.add_axes([0.1, 0.1, 0.8, 0.05]) # setup colorbar axes.
# put the colorbar on new axes
cbar = fig.colorbar(mapable,cax=ax3,orientation='horizontal')
plt.show()
Note ofcourse you can position ax3 as you wish, on the side, on the top, where ever,
as long as it is in the boundaries of the figure.
I don't know why your line2D is not appearing.
I added to my code before plt.show() the following and everything is showing:
from mpl_toolkits.axes_grid1 import anchored_artists
from matplotlib.patheffects import withStroke
txt = anchored_artists.AnchoredText("SC",
loc=2,
frameon=False,
prop=dict(size=12))
if withStroke:
txt.txt._text.set_path_effects([withStroke(foreground="w",
linewidth=3)])
ax1.add_artist(txt)
## Draw a line to separate the two different wave lengths, and name each region
l1 = plt.Line2D([-1,10],[5,5],ls='-',color='black',lineswidth=10)
ax1.add_line(l1)

Categories

Resources