I'm trying to draw two rows with three columns of pcolormesh plots and a combined colorbar for all plots. So far it seems to work. However, I'm sure I'm not using the most elegant way...
The only problem I have, is that I can't decrease the horizontal spacing any further. The following line should set the horizontal spacing to zero:
fig.subplots_adjust(left=0.05, right=0.98, top=0.93, bottom=0.00, wspace=0, hspace=0.03)
But this does not work in conjunction with
ax.set_aspect('equal')
I've attached a small code snippet that creates the following figure:
Example figure
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
rows = 2
columns = 3
fig = plt.figure()
gs = gridspec.GridSpec(rows+1, columns)
lines = []
x = np.linspace(1,10,100)
y = x
X, Y = np.meshgrid(x,y)
Z = np.random.rand(100,100)
lines = []
for i in range(rows):
lines.append([])
for j in range(columns):
ax = fig.add_subplot(gs[i, j])
line = ax.pcolormesh(X, Y, Z, cmap=plt.cm.Reds)
lines[i].append(line)
ax.set_aspect('equal')
for tick in ax.get_xticklabels():
tick.set_rotation(45)
if i!=rows-1:
ax.set_xticklabels([])
if j!=0:
ax.set_yticklabels([])
#title
props = dict(boxstyle='round', facecolor='white', alpha=0.7)
ax.text(0.05, 0.95, "plot (%i, %i)" % (i,j), transform=ax.transAxes, fontsize=5,
verticalalignment='top', bbox=props)
ax.tick_params(labelsize=7)
cb_ax = fig.add_subplot(gs[-1,:])
cb_ax.set_aspect(0.05)
cbar = fig.colorbar(lines[0][0], cax=cb_ax, orientation='horizontal')
cb_ax.tick_params(labelsize=7)
fig.subplots_adjust(left=0.05, right=0.98, top=0.93, bottom=0.00, wspace=0, hspace=0.03)
#fig.tight_layout()
fig.text(0.5, 0.2, "x axis", ha='center', va='center')
fig.text(0.5, 0.97, "overall title", ha='center', va='center')
fig.text(0.02, 0.5, "y axis", ha='center', va='center', rotation='vertical')
fig.text(0.5, 0.02, "quantity [unit]", ha='center', va='center',)
plt.savefig("test.png", dpi=600)
Related
I want to create a 3D scatter plot with legends for the sizes and the colors. However, the legend for the colors only shows the first color in the list.
import matplotlib.pyplot as plt
import matplotlib.colors
# Visualizing 5-D mix data using bubble charts
# leveraging the concepts of hue, size and depth
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)
xs = [1,2,3,5,4]
ys = [6,7,3,5,4]
zs = [1,5,3,9,4]
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
ss = [100,200,390,500,400]
colors = ['red','red','blue','yellow','yellow']
scatter = ax.scatter(xs, ys, zs, alpha=0.4, c=colors, s=ss)
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
legend1 = ax.legend(*scatter.legend_elements()[0],
loc="upper right", title="Classes", labels=colors, bbox_to_anchor=(1.5, 1),prop={'size': 20})
ax.add_artist(legend1)
# produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
legend2 = ax.legend(handles, labels, loc="upper right", title="Sizes", bbox_to_anchor=(1.5, 0.5), prop={'size': 20})
The issue might result from the fact that matplotlib only receives one series to plot and thus assumes that one legend entry suffices. If I make scatter plots of the red, blue and yellow series individually, then all three classes are displayed correctly in the legend (but it causes issues when plotting the legend with sizes).
It's perhaps not the most elegant solution, but the legend with classes can be created manually:
import matplotlib.pyplot as plt
import matplotlib.colors
from matplotlib.lines import Line2D
# Visualizing 5-D mix data using bubble charts
# leveraging the concepts of hue, size and depth
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
t = fig.suptitle('Wine Residual Sugar - Alcohol Content - Acidity - Total Sulfur Dioxide - Type', fontsize=14)
xs = [1,2,3,5,4]
ys = [6,7,3,5,4]
zs = [1,5,3,9,4]
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]
ss = [100,200,390,500,400]
colors = ['red','red','blue','yellow','yellow']
scatter = ax.scatter(xs, ys, zs, alpha=0.4, c=colors, s=ss)
ax.set_xlabel('Residual Sugar')
ax.set_ylabel('Alcohol')
ax.set_zlabel('Fixed Acidity')
# Create additional legend
UniqueColors = list(dict.fromkeys(colors))
Legend2Add = []
for color in UniqueColors:
Legend2Add.append( Line2D([0], [0], marker='o', color='w', label=color,
markerfacecolor=color, markersize=15, alpha=0.4) )
# Produce a legend with a cross section of sizes from the scatter
handles, labels = scatter.legend_elements(prop="sizes", alpha=0.6)
legend1 = ax.legend(handles,
loc="upper right", title="Classes", handles=Legend2Add, bbox_to_anchor=(1.5, 1),prop={'size': 20})
ax.add_artist(legend1)
legend2 = ax.legend(handles, labels, loc="upper right", title="Sizes", bbox_to_anchor=(1.5, 0.5), prop={'size': 20})
plt.show()
I plotted an array in seaborn heatmap, and I want to add tick limits to the axis.
My code:
# plot
eixoz = numpy.linspace(0, Z)
eixor = numpy.linspace(ra, R, nr)
eixox = D
numpy.meshgrid(eixoz, eixor)
ax = seaborn.heatmap(eixox)
ax.invert_yaxis()
plt.xlabel("Eixo z", fontsize=20)
plt.ylabel("Eixo r", fontsize=20)
ax.get_xaxis().set_ticks([])
ax.get_yaxis().set_ticks([])
ax.collections[0].colorbar.set_label("Celsius", fontsize=20)
plt.show()
How can I add those limit ticks in blue? And also, how can I resize the color bar numbers?
The size of the colorbar tick labels can be changed via ax.collections[0].colorbar.ax.tick_params(labelsize=20).
Text at the start and end of the axes can be place using the axes transform, where 0 is the left (or bottom) and 1 is the right (or top) of the axes. Negative values (or values larger than 1) are proportionall outside the axes area. Horizontal and vertical lines can use the same transform, but unlike text need clip_on=False to be drawn outside the axes area.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
len_eixoz = 20
eixox = np.repeat(np.arange(37.55, 37.66, 0.02), len_eixoz).reshape(-1, len_eixoz)
ax = sns.heatmap(eixox)
ax.invert_yaxis()
ax.set_xlabel("Eixo z", fontsize=20)
ax.set_ylabel("Eixo r", fontsize=20)
ax.get_xaxis().set_ticks([])
ax.get_yaxis().set_ticks([])
ax.collections[0].colorbar.set_label("Celsius", fontsize=20)
cbar = ax.collections[0].colorbar.ax.tick_params(labelsize=20)
x0, x1 = 1, 2
y0, y1 = 0, 1
ax.text(0, -0.07, x0, ha='center', va='top', fontsize=20, color='steelblue', transform=ax.transAxes)
ax.text(1, -0.07, x1, ha='center', va='top', fontsize=20, color='steelblue', transform=ax.transAxes)
ax.text(-0.05, 0, y0, ha='right', va='center', fontsize=20, color='steelblue', transform=ax.transAxes)
ax.text(-0.05, 1, y1, ha='right', va='center', fontsize=20, color='steelblue', transform=ax.transAxes)
ax.vlines([0, 1], [0, 0], [-0.06, -0.06], color='crimson', clip_on=False, transform=ax.transAxes)
ax.hlines([0, 1], [0, 0], [-0.04, -0.04], color='crimson', clip_on=False, transform=ax.transAxes)
plt.tight_layout()
plt.show()
Note that calling sns.set(font_scale=1.8) at the start would scale all fonts.
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 have produced a graph with two subplots and am trying to add a histogram to the end of the residuals plot but am unable to remove the x-axis of the histogram plot and get it to line up with the end of the residual plot.
Here is a copy of my current code:
#graph with histogram and std error plot thing
fig1 = plt.figure(figsize =(9.6,7.2))
ax = fig1.add_axes((0.2,0.4,.75,.6))
ax.errorbar(xval, yval*1000, yerr=yerr*1000, xerr=xerr, marker='x', linestyle='None')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')
# Axis labels
plt.xlabel('Height (m)', fontsize = 12)
plt.ylabel('dM/dt (g $s^{-1}$) × $10^{3}$', fontsize = 12)
# Generate best fit line using model function and best fit parameters, and add to plot
fit_line=model_funct(xval, [a_soln, b_soln])
plt.plot(xval, fit_line*1000)
# Set suitable axis limits: you will probably need to change these...
#pyplot.xlim(-1, 61)
#pyplot.ylim(65, 105)
# pyplot.show()
ax2 = fig1.add_axes((0.2,0.2,.75,.2)) #start frame1 at 0.2, 0.4
plt.xlabel("Height of Water (m)", fontsize = 12)
plt.ylabel("Normalised\nResiduals", fontsize = 12) #\n is used to start a new line
ax2.plot(h,normalised_residuals,"x", color = "green")
plt.axhline(0, linewidth=1, linestyle="--", color="black")
plt.savefig("Final Graph with added parts.png", dpi = 500)
ax2.axhspan(ymin = -np.std(normalised_residuals), ymax = np.std(normalised_residuals), color = 'gray', alpha =0.5)
ax3 = fig1.add_axes((1,0.2,0.2,0.2))
ax3.hist(normalised_residuals, bins=8, orientation="horizontal")
ax3.spines['right'].set_visible(False)
ax3.spines['top'].set_visible(False)
ax3.spines['left'].set_visible(False)
ax3.yaxis.set_ticks_position('left')
ax3.xaxis.set_ticks_position('bottom')
and here is a picture of my graph currently:
An example with random data. Using tick_params and manually setting both ylim and the histogram range, did the trick.
import matplotlib.pyplot as plt
import numpy as np
fig1 = plt.figure(figsize=(20, 15))
ax = fig1.add_axes((0.2, 0.4, .75, .6))
ax2 = fig1.add_axes((0.2, 0.2, .75, .2))
ax3 = fig1.add_axes((.95, 0.2, 0.2, 0.2))
xval = (np.linspace(0.02, 0.15, 20)
+ (np.random.default_rng(0).random(20) - 0.5) / 30)
yval = 2 * xval + 0.08
xerr = (np.random.default_rng(0).random(20) * 2 - 1) / 60
yerr = (np.random.default_rng(1).random(20) * 2 - 1) / 60
ax.errorbar(xval, yval, yerr=yerr, xerr=xerr, marker='x', linestyle='None')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.tick_params(labelbottom=False)
ax.set_xlabel('Height (m)', fontsize=12)
ax.set_ylabel('dM/dt (g $s^{-1}$) × $10^{3}$', fontsize=12)
ax2.plot(xval, xerr, 'x', color='green')
ax2.axhline(0, linewidth=1, linestyle='--', color='black')
ax2.axhspan(ymin=-np.std(xerr), ymax=np.std(xerr), color='gray', alpha=0.5)
ax2.set_xlabel('Height of Water (m)', fontsize=12)
ax2.set_ylabel('Normalised\nResiduals', fontsize=12)
resLim = ax2.get_ylim()
ax3.hist(xerr, bins=8, orientation='horizontal', range=resLim, rwidth=0.9)
ax3.spines['right'].set_visible(False)
ax3.spines['top'].set_visible(False)
ax3.spines['left'].set_visible(False)
ax3.spines['bottom'].set_visible(False)
ax3.tick_params(labelbottom=False, labelleft=False, bottom=False, left=False)
ax3.set_ylim(resLim)
fig1.savefig('so.png', bbox_inches='tight')
I'm trying to make my first plots in python using matplotlib, but i would like the text in the plot to be "outside" the plot i.e. next to the line instead of above it or under it.
plt.figure()
plt.scatter(mean, diff, color='k')
plt.axhline(md, color='black', linestyle='-', lw=3)
plt.axhline(md + 1.96*sd, color='black', linestyle='--')
plt.axhline(md - 1.96*sd, color='black', linestyle='--')
plt.axhline(0, color='black', linestyle='--')
plt.ylim(-max(diff)*2, max(diff)*2)
plt.xlabel('Mean')
plt.ylabel('Difference')
plt.title('Bland altman plot for ' + variable)
txt1=('+1.96 SD')
txt2=('-1.96 SD')
txt3 =('Mean')
x = max(mean)
plt.text(x, md+1.96*sd, txt1, horizontalalignment='left', verticalalignment='bottom', fontweight='bold')
plt.text(x, md-1.96*sd, txt2, horizontalalignment='left', verticalalignment='top', fontweight='bold')
plt.text(x, 0.1, txt3)
The result is:
The main ingredient you still need to position the labels at the edge of the axes, is the coordinate of the edge of the axes. You can get those via plt.gca().get_xlim(). The upper limit can then be used as x position of the text label.
import matplotlib.pyplot as plt
import numpy as np
X = np.random.normal(size=(12))
Y = np.random.normal(size=(12))
plt.figure()
plt.scatter(X, Y, color='k')
plt.axhline(Y.mean(), color='black', linestyle='-', lw=3)
plt.axhline(Y.mean() + 1.96*Y.std(), color='black', linestyle='--')
plt.axhline(Y.mean() - 1.96*Y.std(), color='black', linestyle='--')
plt.axhline(0, color='black', linestyle='--')
txt1=(' +1.96 SD')
txt2=(' -1.96 SD')
txt3 =(' Mean')
x0,x1 = plt.gca().get_xlim()
plt.text(x1, Y.mean() + 1.96*Y.std(), txt1, ha='left', va='center', fontweight='bold')
plt.text(x1, Y.mean() - 1.96*Y.std(), txt2, ha='left', va='center', fontweight='bold')
plt.text(x1, 0, txt3, ha='left', va='center',)
# make more space on right side to host the labels
plt.subplots_adjust(right=0.8)
plt.show()
Note that this is the very basic (but also quite understandable) version.
If you're comfortable with transformations, you can use the technique in this question:
Add a label to y-axis to show the value of y for a horizontal line in matplotlib