I am trying to plot 5 subplots in Python with differing sizes and orientation. I have seen usage of gridspec to alter column width, plt.figure(figsize=[]), etc. but each seems to be slightly off from what I want (e.g. no uniform column width, all one plot, so can't use plt.figure()).
What I currently have is a subplot(3,2,2) with the first 5 plots filled such that it looks like a 3x2 grid without the bottom right filled in. What I would like is a 2x2 grid with a single plot below it. The plot below should also be larger (maybe twice as wide) as the four above it.
This is what I have versus what I would like.
Here is my code (sorry for the line numbers):
plot3 = plt.figure(1)
plt.subplot(321)
#gs = gridspec.GridSpec(1,2,3,4,5,6,width_ratios=[1,1,1,1,2,0])
plt.scatter(0.001*posEncUm[:,0],err[:,0], s=1, linewidths=1)
p = np.polyfit(0.001*posEncUm[:,0],err[:,0],1)
plt.title('Slope = {0:4.1f} um/100mm'.format(p[0]*100), fontsize=10)
plt.xlabel('Encoder Position (X), mm', fontsize=7)
plt.ylabel('Laser Error (X), um', fontsize=7)
plt.subplots_adjust(hspace = 1.0, wspace = 0.5)
plt.grid()
plt.subplot(322)
plt.scatter(0.001*posEncUm[:,1],err[:,0], s=1, linewidths=1)
p = np.polyfit(0.001*posEncUm[:,1],err[:,0],1)
plt.title('Slope = {0:4.1f} um/100mm'.format(p[0]*100), fontsize=10)
plt.xlabel('Encoder Position (Y), mm', fontsize=7)
plt.ylabel('Laser Error (X), um', fontsize=7)
plt.grid()
plt.subplot(323)
plt.scatter(0.001*posEncUm[:,0],err[:,1], s=1, linewidths=1)
p = np.polyfit(0.001*posEncUm[:,0],err[:,1],1)
plt.title('Slope = {0:4.1f} um/100mm'.format(p[0]*100), fontsize=10)
plt.xlabel('Encoder Position (X), mm', fontsize=7)
plt.ylabel('Laser Error (Y), um', fontsize=7)
plt.grid()
plt.subplot(324)
plt.scatter(0.001*posEncUm[:,1],err[:,1], s=1, linewidths=1)
p = np.polyfit(0.001*posEncUm[:,1],err[:,1],1)
plt.title('Slope = {0:4.1f} um/100mm'.format(p[0]*100), fontsize=10)
plt.xlabel('Encoder Position (Y), mm', fontsize=7)
plt.ylabel('Laser Error (Y), um', fontsize=7)
plt.grid()
plt.subplot(325)
plt.quiver(0.001*X,0.001*Y,errX,errY)
plt.grid()
plt.xlabel('Encoder Pos (X), mm')
plt.ylabel('Encoder Pos (Y), mm')
plt.gca().set_aspect('equal', adjustable = 'box')
This will give what you want, just modify for the specifics. Is this what you were thinking?
import pylab as pl
fig = pl.figure(figsize=(3.25, 4.5))
gs = pl.GridSpec(3, 2)
gs.update(left=0.08, right=0.925,
top=0.95, bottom=0.05,
hspace=0.3, wspace=0.1)
# create primary axes
ax0 = pl.subplot(gs[0, 0])
ax1 = pl.subplot(gs[0, 1])
ax2 = pl.subplot(gs[1, 0])
ax3 = pl.subplot(gs[1, 1])
ax4 = pl.subplot(gs[2, :])
I don't know what you mean by "bottom should be twice as wide as four above it."
#https://python-graph-gallery.com/125-small-multiples-for-line-chart/
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
# Initialize the figure
plt.style.use('seaborn-darkgrid')
# create a color palette
palette = plt.get_cmap('Set1')
plt.figure(figsize=(10,10))
plt.suptitle("PLOT TITLE",fontsize=20)
gridspec.GridSpec(3,3)
plt.subplots_adjust(hspace=0.4)
# multiple line plot
num=0
for column in df.drop('ODD COLUMN NAME', axis=1):
num+=1
# Find the right spot on the plot
if num==7: # adjustment to fit ODD COLUMN
plt.subplot2grid((3,3),(2,0),colspan=3)
else:
plt.subplot(3,3, num)
# plot every groups, but discreet
for v in df.drop('ODD COLUMN', axis=1):
plt.plot(df['ODD COLUMN'], df[v], marker='', color='grey', linewidth=0.6, alpha=0.3)
# Plot the lineplot
plt.plot(df['ODD COLUMN'], df[column], marker='', color=palette(num), linewidth=2.4, alpha=0.9, label=column)
# Same limits for everybody!
plt.xlim(10,100)
plt.ylim(1,100)
# Not ticks everywhere
if num in range(4) :
plt.tick_params(labelbottom='off')
if num not in [1,4,7] :
plt.tick_params(labelleft='off')
# Add title
plt.title(column, loc='left', fontsize=12, fontweight=0, color=palette(num))
Related
I have this python code.
it twinx the axis ax and plots some function on both axis
I plot the legend on ax1
The problem is that the legend is not covering the curves of ax2
It is possible to automatically positioning the legend on ax by covering the lines of ax2.
Note that in fig.legend the option loc="best" is not available.
And I need the automatic positioning inside the area of the plot.
Tnx
import matplotlib.pyplot as plt
import numpy as np
# Set the x values for the sine and cosine functions
x = np.linspace(0, 2*np.pi, 100)
# Create the figure and an axis
fig, ax = plt.subplots()
ax2 = ax.twinx()
# Plot the sine and cosine functions on the axis
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax2.plot(x, np.cos(x+1), label='Cosine 2', color="red")
ax2.plot(x, x, label='Cosine 2', color="green")
# Add a title and labels to the axis
ax.set_title('Sine and Cosine Functions')
ax.set_xlabel('X')
ax.set_ylabel('Y')
# Get the line legends from the axis
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Add a legend to the figure
ax.legend(lines + lines2, labels + labels2, framealpha=1.0)
ax.get_legend().set_zorder(10)
# Display the plot
plt.show()
Bellow is the output of the code:
References:
https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html
https://matplotlib.org/stable/gallery/misc/zorder_demo.html
The zorder of a legend is highest by default, so you don't need to modify it if you want the legend on the top of everything.
Option 1:
You can accomplish this in terms of a Figure object instead of an Axes object with plt.legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower left').
Option 2:
Or you can set the legend on ax2 instead of ax with ax2.legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower left'). This returns the same result as in option 1.
import matplotlib.pyplot as plt
import numpy as np
# Set the x values for the sine and cosine functions
x = np.linspace(0, 2 * np.pi, 100)
# Create the figure and an axis
fig, ax = plt.subplots()
ax2 = ax.twinx()
# Plot the sine and cosine functions on the axis
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax2.plot(x, np.cos(x + 1), label='Cosine 2', color="red")
ax2.plot(x, x, label='Cosine 2', color="green")
# Add a title and labels to the axis
ax.set_title('Sine and Cosine Functions')
ax.set_xlabel('X')
ax.set_ylabel('Y')
# Get the line legends from the axis
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Add a legend to the figure
plt.legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower left')
# Display the plot
plt.show()
Results (loc='lower left' and loc='lower center'):
Updated:
Reference: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.twinx.html
With option 2 in my previous answer, I edited line 36 to ax2.legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower right') and line 43 to ax22.legend(lines1 + lines22, labels1 + labels22, framealpha=1.0, loc='lower left') in your newly provided code.
If you have multiple subfigures, using Axes objects are much more flexible.
It is now working correctly:
Thanks for the answer.
But my problem is more complex: I need to do this job in tiled subplots where every plot area is twinx.
By using the plt.label it seems to be impossible to select the plot-tile where to put the legend.
With the code below the problem is there. The lines of the twined are not covered by the legend.
Any suggestion?
import matplotlib.pyplot as plt
import numpy as np
# Set the x values for the sine and cosine functions
x = np.linspace(0, 2 * np.pi, 100)
# Create the figure and an axis
fig, ax = plt.subplots(2, 1)
ax2 = ax[0].twinx()
ax22 = ax[1].twinx()
# Plot the sine and cosine functions on the axis
ax[0].plot(x, np.sin(x), label='Sine 0')
ax[0].plot(x, np.cos(x), label='Cosine 0')
# Plot the sine and cosine functions on the axis
ax[1].plot(x, np.sin(x), label='Sine')
ax[1].plot(x, np.cos(x), label='Cosine')
ax2.plot(x, np.cos(x + 1), label='Cosine 2', color="red")
ax2.plot(x, x, label='Cosine B', color="green")
ax22.plot(x, np.cos(x + 2), label='Line 2', color="red")
ax22.plot(x, x, label='Cosine 2', color="green")
# Add a title and labels to the axis
ax[0].set_title('Sine and Cosine Functions')
ax[0].set_xlabel('X')
ax[0].set_ylabel('Y')
# Get the line legends from the axis
lines, labels = ax[0].get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Add a legend to the figure
ax[0].legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower right')
lines1, labels1 = ax[1].get_legend_handles_labels()
lines22, labels22 = ax22.get_legend_handles_labels()
# Add a legend to the figure
ax[1].legend(lines1 + lines22, labels1 + labels22, framealpha=1.0, loc='lower left')
# Display the plot
plt.show()
I finally found the solution:
That is controlling the z-order of the Axis and the alpha of the axis with the highest priority.
the matplotlib functions are:
Axes.set_zorder
https://matplotlib.org/3.2.2/api/_as_gen/matplotlib.axes.Axes.set_zorder.html
&
Patch.set_alpha
https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Patch.html
Replace the last part of the code in the third answer with the above code, and it produces the desired result.
# Get the line legends from the axis
lines, labels = ax[0].get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
##############################################################################
##############################################################################
# Set the z-order of axis 2 to a low "priority"
ax2.set_zorder(0)
# Set the z-order of the axis 0 to a higher priority.
# And to make visible axis 2 set the alpha of the background at 0.
ax[0].set_zorder(1)
ax[0].patch.set_alpha(0.0)
ax[0].legend(lines + lines2, labels + labels2, framealpha=1.0, loc='lower left')
##############################################################################
##############################################################################
lines1, labels1 = ax[1].get_legend_handles_labels()
lines22, labels22 = ax22.get_legend_handles_labels()
# Add a legend to the figure
ax22.legend(lines1 + lines22, labels1 + labels22, framealpha=1.0, loc='lower left')
# Display the plot
plt.show()
I would like to have an increasing spacing between legend items instead of a single value (labelspacing). The latter only accepts an int value type, but I want a variable spacing between legend items. Also, I want the markerfacecolor to follow the colormap used when creating the scatter plot.
N = 45
x, y = np.random.rand(2, N)
s = np.random.randint(10, 1000, size=N)
fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c=s, s=s)
cbar = fig.colorbar(scatter,
ax=ax,
label='Size',
fraction=0.1,
pad=0.04)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
for hd in handles:
hd.set_markeredgewidth(2)
hd.set_markeredgecolor("red")
hd.set_markerfacecolor('blue')
legend2 = ax.legend(
handles[::2], labels[::2], loc="upper right", title="Sizes", labelspacing=1.2
)
plt.show()
I searched StackOverflow and tried some possible methods but without success. Could someone guide how I can achieve the desired output?
I managed to set markerfacecolor as the colormap. But I am still struggling with the variable labelspacing!.
Any help!
N = 45
x, y = np.random.rand(2, N)
s = np.random.randint(10, 1000, size=N)
fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c=s, s=s)
cbar = fig.colorbar(scatter,
ax=ax,
label='Size',
fraction=0.1,
pad=0.04)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
leg_colrs = [color.get_markerfacecolor() for color in scatter.legend_elements()[0]]
for hd, color in zip(handles, leg_colrs):
hd.set_markeredgewidth(2)
hd.set_markeredgecolor("red")
hd.set_markerfacecolor(color)
legend2 = ax.legend(
handles[::2], labels[::2], loc="upper right", title="Sizes", labelspacing=1.2
)
plt.show()
I am trying to make a confusion matrix plot, or just any plot with quadrants and something in it. I used imshow.
cm = [[370288, 5190],
[ 2213, 6758]]
with plt.style.context("seaborn-white"):
ax = plt.gca()
ax.imshow(cm, interpolation='none', vmin=1, vmax=1)
classNames = ['no churn','churn']
ax.set_title('Confusion Matrix',fontsize=20, fontweight='bold', color='indigo')
ax.set_ylabel('actual',fontsize=15, fontweight='bold')
ax.set_xlabel('predicted',fontsize=15, fontweight='bold')
tick_marks = np.arange(len(classNames))
plt.xticks(tick_marks, classNames)
plt.yticks(tick_marks, classNames, rotation=90)
plt.grid(color='indigo', linestyle='-', linewidth=5)
s = [['TN','FP'], ['FN', 'TP']]
for i in range(2):
for j in range(2):
plt.text(j,i, str(s[i][j])+" = "+annot[i][j], size=12, ha='center', va='center')
plt.show()
Unfortunately I don't manage to make the grid right. If I add
ax.set_xticks(np.arange(-.5, 1.5, 1))
ax.set_yticks(np.arange(-.5, 1.5, 1))
then the grid is correct but the x and y axis labels are not centered anymore and right and left border are not bold. How can I have bold borders and the correct grid that divides the whole plot into four quadrants?
I'm trying to plot some revenues against EBIT on a chart with a secondary Y-axis.
Everything seems to be ok except
the grid doesn't display entirely.
the x-axis doesn't display entirely either.
Does anyone have a solution to display entirely these 2 features ?
Here is a screen cap of my graph :
graph
Here is the data :
data
And here is my code :
x = msft_ebit_revenue_new['period']
y1 = msft_ebit_revenue_new['EBIT']
y2 = msft_ebit_revenue_new['Revenues']
Plot Line1 (Left Y Axis)
fig, ax1 = plt.subplots(1,1,figsize=(16,9), dpi= 80)
ax1.plot(x, y1, color='tab:red')
# Plot Line2 (Right Y Axis)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
ax2.plot(x, y2, color='tab:blue')
Decorations
ax1 (left Y axis)
ax1.set_xlabel('Period', fontsize=10)
ax1.tick_params(axis='x', rotation=0, labelsize=12)
ax1.set_ylabel('EBIT', color='tab:red', fontsize=20)
ax1.tick_params(axis='y', rotation=0, labelcolor='tab:red' )
ax1.grid(alpha=.4)
ax2 (right Y axis)
ax2.set_ylabel("Revenues ($ in millions)", color='tab:blue', fontsize=20)
ax2.tick_params(axis='y', labelcolor='tab:blue')
ax2.set_xticks(np.arange(0, len(x), 60))
ax2.set_xticklabels(x[::60], rotation=90, fontdict={'fontsize':10})
ax2.set_title("EBIT vs Revenues (MSFT)", fontsize=22)
fig.tight_layout()
plt.show()
Thank you !
Alex
Problem
Display x-axis labels
Display grid
Solution
Comment the following lines:
ax2.set_xticks(np.arange(0, len(x), 60))
ax2.set_xticklabels(x[::60], rotation=90, fontdict={'fontsize':10})
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)