I am trying to add some text to the legend. It is about the text in the box. Another solution is that the textbox stays beneath the legend and doesn't move when you enlarge the graph.
plt.scatter(stageheight,discharge,color='b',label='measured data')
plt.plot(stageheight_hecras,discharge_hecras,'y^',label='modeled with HEC-RAS')
plt.plot(stageheight_masked,discharge_predicted,'r-',label='regression line measured data')
plt.plot(stageheight_hecras,discharge_predicted_hecras,'g-',label='regression line HEC-RAS')
plt.plot(stageheight_masked,upper,'r--',label='15% error measured data')
plt.plot(stageheight_masked,lower,'r--')
plt.plot(stageheight_hecras,upper_hecras,'g--',label='30% error HEC-RAS')
plt.plot(stageheight_hecras,lower_hecras,'g--')
plt.fill_between(stageheight_masked,upper,lower,facecolor='red',edgecolor='red',alpha=0.5,label='test')
plt.fill_between(stageheight_hecras,upper_hecras,lower_hecras,facecolor='green',alpha=0.5)
plt.axhline(y=0.6,xmin=0,xmax=1,color='black',linewidth = 4.0,label='measuring range')
plt.text(0.02,0.7,'measured rating curve $Q = 1.37H^2 + 0.34H - 0.007$\nmodeled ratign curve $Q = 2.71H^2 - 2.20H + 0.98$',bbox=dict(facecolor='none',edgecolor='black',boxstyle='square'))
plt.title('Rating curve Catsop')
plt.ylabel('discharge')
plt.ylim(0,2.5)
plt.xlim(0,1.2)
plt.xlabel('stageheight[m]')
plt.legend(loc='upper left', title='Legend')
plt.grid(True)
plt.show()
This is the graph I have now:
Rather than plotting a fake rectangle, you can use a Patch, which doesn't show up on the figure or axes:
import matplotlib.patches as mpatches
extraString = 'measured rating curve $Q = 1.37H^2 + 0.34H - 0.007$\nmodeled rating curve $Q = 2.71H^2 - 2.20H + 0.98$'
handles, labels = plt.get_legend_handles_labels()
handles.append(mpatches.Patch(color='none', label=extraString))
plt.legend(handles=handles)
This method has the bonus effect that you first get whatever is already in the legend, so you don't have to explicitly build it by hand.
This adds the text to the legend (inspired by this answer):
from matplotlib.patches import Rectangle
plt.plot(range(10))
p = plt.axhline(y=0.6,xmin=0,xmax=1,color='black',linewidth = 4.0,label='measuring range')
plt.ylabel('discharge')
plt.ylim(0,2.5)
plt.xlim(0,1.2)
plt.xlabel('stageheight[m]')
text1 = 'measured rating curve $Q = 1.37H^2 + 0.34H - 0.007$'
text2 = 'modeled ratign curve $Q = 2.71H^2 - 2.20H + 0.98$'
extra = Rectangle((0, 0), 1, 1, fc="w", fill=False, edgecolor='none', linewidth=0)
plt.legend([p, extra, extra],[p.get_label(), text1, text2], loc='upper left', title='Legend')
plt.grid(True)
plt.show()
Try to use transform in plt.text. Now the first two coordinates are relative to the axis.
ax = plt.gca()
ax.text(0.3,0.05,'measured rating curve $Q = 1.37H^2 + 0.34H - 0.007$\nmodeled ratign curve $Q = 2.71H^2 - 2.20H + 0.98$',transform=ax.transAxes, bbox=dict(facecolor='none',edgecolor='black',boxstyle='square'))
Related
I was trying to plot some data and include linear trends. I wanted the legend to include the function of the trend but somehow the legend always shows a weird vertical offset of the symbol relative to the text (see Screenshot marked with light blue). Does anybody know why and could maybe also include a fix?
Thank you!
l_y1_1 = ax.plot(x, y1, 'ro', label='Lithium', markersize=10)
l_y1_2 = ax.plot(x_extrapolate, y1_extrapolate,'r:', label = "%s" % poly1)
l_y2_1 = ax.plot(x, y2, 'bx', label='Chlor', markersize=10)
l_y2_2 = ax.plot(x_extrapolate, y2_extrapolate,'b:', label = "%s" % poly2)
ax2 = ax.twinx()
l_y3_1 = ax2.plot(x, y3, 'gD', label='Fluor', markersize=10)
l_y3_2 = ax2.plot(x_extrapolate, y3_extrapolate,'g:',label = "%s" % poly3)
l_y_tot = l_y1_1 + l_y1_2 + l_y2_1 + l_y2_2 + l_y3_1 + l_y3_2
y_labs = [l.get_label() for l in l_y_tot]
legend = ax.legend(l_y_tot, y_labs, loc = 'upper left')
I have the code below and I needed to find the numerical value of the intersection point between the axline and the axvline, I have no idea how to solve this in a simple way, does anyone know how to solve it? Infinite thanks in advance! :)
!pip install matplotlib==3.4
%matplotlib inline
import matplotlib.pyplot as plt
x = [0,2,4,6,8,10,12,14.3,16.2,18,20.5,22.2,25.1,
26.1,28,30,33.3,34.5,36,38,40]
y = [13.4,23.7,35.1,48.3,62.7,76.4,91.3,106.5,119.6,131.3,
146.9,157.3,173.8,180.1,189.4,199.5,215.2,220.6,227,234.7,242.2]
slope = (131.3-119.6)/(18-16.2)
plt.figure(figsize=(10, 5))
plt.axline((16.2,119.6), slope = slope, linestyle = '--', color = 'r')
plt.grid()
plt.minorticks_on()
plt.axvline(30,linestyle = '--', color = 'black')
plt.plot(x,y, linewidth = 2.5)
plt.show()
Plot Result
First, you need to find the equation of the oblique line, y=slope*x+q: that' easy to do, since you know the slope and a point of the line.
Next you solve the system of equations: y=slope*x+q, x=x0 (the vertical line).
Here I plotted the intersection point with a green marker.
import matplotlib.pyplot as plt
x = [0,2,4,6,8,10,12,14.3,16.2,18,20.5,22.2,25.1,
26.1,28,30,33.3,34.5,36,38,40]
y = [13.4,23.7,35.1,48.3,62.7,76.4,91.3,106.5,119.6,131.3,
146.9,157.3,173.8,180.1,189.4,199.5,215.2,220.6,227,234.7,242.2]
slope = (131.3-119.6)/(18-16.2)
plt.figure(figsize=(10, 5))
point_1 = (16.2, 119.6)
q = point_1[1] - slope * point_1[0]
x2 = 30
point_2 = (x2, slope * x2 + q)
plt.axline(point_1, slope = slope, linestyle = '--', color = 'r')
plt.grid()
plt.minorticks_on()
plt.axvline(x2,linestyle = '--', color = 'black')
plt.plot(x,y, linewidth = 2.5)
plt.scatter(*point_2, color="g", s=100)
plt.show()
I'm trying to create plot with shadings which are based on this MIC(1) line.
Different shading above than beneath.
from scipy import stats
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
def createSkewDist(mean, sd, skew, size):
# calculate the degrees of freedom 1 required to obtain the specific skewness statistic, derived from simulations
loglog_slope=-2.211897875506251
loglog_intercept=1.002555437670879
df2=500
df1 = 10**(loglog_slope*np.log10(abs(skew)) + loglog_intercept)
# sample from F distribution
fsample = np.sort(stats.f(df1, df2).rvs(size=size))
# adjust the variance by scaling the distance from each point to the distribution mean by a constant, derived from simulations
k1_slope = 0.5670830069364579
k1_intercept = -0.09239985798819927
k2_slope = 0.5823114978219056
k2_intercept = -0.11748300123471256
scaling_slope = abs(skew)*k1_slope + k1_intercept
scaling_intercept = abs(skew)*k2_slope + k2_intercept
scale_factor = (sd - scaling_intercept)/scaling_slope
new_dist = (fsample - np.mean(fsample))*scale_factor + fsample
# flip the distribution if specified skew is negative
if skew < 0:
new_dist = np.mean(new_dist) - new_dist
# adjust the distribution mean to the specified value
final_dist = new_dist + (mean - np.mean(new_dist))
return final_dist
desired_mean = 30
desired_skew = 1.5
desired_sd = 20
final_dist = createSkewDist(mean=desired_mean, sd=desired_sd, skew=desired_skew, size=1000000)
# inspect the plots & moments, try random sample
fig, ax = plt.subplots(figsize=(12,7))
sns.distplot(final_dist,
hist=False,
ax=ax,
color='darkred',
kde_kws=dict(linewidth=4))
l1 = ax.lines[0]
# Get the xy data from the lines so that we can shade
x1 = l1.get_xydata()[:,0]
x1[0] = 0
y1 = l1.get_xydata()[:,1]
y1[0] = 0
ax.fill_between(x1,y1, color="lemonchiffon", alpha=0.3)
ax.set_ylim(0.0001,0.03)
ax.axhline(0.002, ls="--")
ax.set_xlim(1.5, 200)
ax.set_yticklabels([])
ax.set_xticklabels([])
trans = transforms.blended_transform_factory(
ax.get_yticklabels()[0].get_transform(), ax.transData)
ax.text(0,0.0025, "{}".format("MIC(1) = 1"), color="blue", transform=trans,
ha="right", va="top", fontsize = 12)
trans_2 = transforms.blended_transform_factory(
ax.get_xticklabels()[0].get_transform(), ax.transData)
ax.text(84,0, "{}".format("\n84"), color="darkred", transform=trans_2,
ha="center", va="top", fontsize = 12)
ax.text(1.5,0, "{}".format("\n0"), color="darkred", transform=trans_2,
ha="center", va="top", fontsize = 12)
ax.axvline(x = 84, ymin = 0, ymax = 0.03, ls = '--', color = 'darkred' )
ax.set_yticks([])
ax.set_xticks([])
ax.spines['top'].set_color(None)
ax.spines['right'].set_color(None)
ax.spines['left'].set_linewidth(2)
ax.spines['bottom'].set_linewidth(2)
ax.set_ylabel("Concentration [mg/L]", labelpad = 80, fontsize = 15)
ax.set_xlabel("Time [h]", labelpad = 80, fontsize = 15)
ax.set_title("AUC/MIC", fontsize = 20, pad = 30)
plt.annotate("AUC/MIC",
xy=(18, 0.02),
xytext=(18, 0.03),
arrowprops=dict(arrowstyle="->"), fontsize = 12);
;
That's what I have:
And that's what I'd like to have (it's done in paint, so forgive me :) ):
I was experimenting with fill_between and fill_betweenx. However, without any satisfying results. Definitely, run out of ideas. I'd really appreciate any help on this. Best wishes!
Your fill_between works as expected. The problem is that color="lemonchiffon" with alpha=0.3 is barely visible. Try to use a brighter color and/or a higher value for alpha.
So, this colors the part of the graph between zero and the kde curve.
Now, to create a different coloring above and below the horizontal line, where= and np.minimum can be used in fill_between:
pos_hline = 0.002
ax.fill_between(x1, pos_hline, y1, color="yellow", alpha=0.3, where=y1 > pos_hline)
ax.fill_between(x1, 0, np.minimum(y1, pos_hline), color="blue", alpha=0.3)
Without where=y1 > pos_hline, fill_between would also color the region above the curve where the curve falls below that horizontal line.
PS: Note that sns.histplot has been deprecated since Seaborn version 0.11. To only plot the kde curve, you can use sns.kdeplot:
sns.kdeplot(final_dist, ax=ax, color='darkred', linewidth=4)
I'd like to draw a lognormal distribution of a given bar plot.
Here's the code
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
import numpy as np; np.random.seed(1)
import scipy.stats as stats
import math
inter = 33
x = np.logspace(-2, 1, num=3*inter+1)
yaxis = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.01,0.03,0.3,0.75,1.24,1.72,2.2,3.1,3.9,
4.3,4.9,5.3,5.6,5.87,5.96,6.01,5.83,5.42,4.97,4.60,4.15,3.66,3.07,2.58,2.19,1.90,1.54,1.24,1.08,0.85,0.73,
0.84,0.59,0.55,0.53,0.48,0.35,0.29,0.15,0.15,0.14,0.12,0.14,0.15,0.05,0.05,0.05,0.04,0.03,0.03,0.03, 0.02,
0.02,0.03,0.01,0.01,0.01,0.01,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0,0]
fig, ax = plt.subplots()
ax.bar(x[:-1], yaxis, width=np.diff(x), align="center", ec='k', color='w')
ax.set_xscale('log')
plt.xlabel('Diameter (mm)', fontsize='12')
plt.ylabel('Percentage of Total Particles (%)', fontsize='12')
plt.ylim(0,8)
plt.xlim(0.01, 10)
fig.set_size_inches(12, 12)
plt.savefig("Test.png", dpi=300, bbox_inches='tight')
Resulting plot:
What I'm trying to do is to draw the Probability Density Function exactly like the one shown in red in the graph below:
An idea is to convert everything to logspace, with u = log10(x). Then draw the density histogram in there. And also calculate a kde in the same space. Everything gets drawn as y versus u. When we have u at a top twin axes, x can stay at the bottom. Both axes get aligned by setting the same xlims, but converted to logspace on the top axis. The top axis can be hidden to get the desired result.
import matplotlib.pyplot as plt
import numpy as np
from scipy import stats
inter = 33
u = np.linspace(-2, 1, num=3*inter+1)
x = 10**u
us = np.linspace(u[0], u[-1], 500)
yaxis = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.01,0.03,0.3,0.75,1.24,1.72,2.2,3.1,3.9,
4.3,4.9,5.3,5.6,5.87,5.96,6.01,5.83,5.42,4.97,4.60,4.15,3.66,3.07,2.58,2.19,1.90,1.54,1.24,1.08,0.85,0.73,
0.84,0.59,0.55,0.53,0.48,0.35,0.29,0.15,0.15,0.14,0.12,0.14,0.15,0.05,0.05,0.05,0.04,0.03,0.03,0.03, 0.02,
0.02,0.03,0.01,0.01,0.01,0.01,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0,0]
yaxis = np.array(yaxis)
# reconstruct data from the given frequencies
u_data = np.repeat((u[:-1] + u[1:]) / 2, (yaxis * 100).astype(np.int))
kde = stats.gaussian_kde((u[:-1]+u[1:])/2, weights=yaxis, bw_method=0.2)
total_area = (np.diff(u)*yaxis).sum() # total area of all bars; divide by this area to normalize
fig, ax = plt.subplots()
ax2 = ax.twiny()
ax2.bar(u[:-1], yaxis, width=np.diff(u), align="edge", ec='k', color='w', label='frequencies')
ax2.plot(us, total_area*kde(us), color='crimson', label='kde')
ax2.plot(us, total_area * stats.norm.pdf(us, u_data.mean(), u_data.std()), color='dodgerblue', label='lognormal')
ax2.legend()
ax.set_xscale('log')
ax.set_xlabel('Diameter (mm)', fontsize='12')
ax.set_ylabel('Percentage of Total Particles (%)', fontsize='12')
ax.set_ylim(0,8)
xlim = np.array([0.01,10])
ax.set_xlim(xlim)
ax2.set_xlim(np.log10(xlim))
ax2.set_xticks([]) # hide the ticks at the top
plt.tight_layout()
plt.show()
PS: Apparently this also can be achieved directly without explicitly using u (at the cost of being slightly more cryptic):
x = np.logspace(-2, 1, num=3*inter+1)
xs = np.logspace(-2, 1, 500)
total_area = (np.diff(np.log10(x))*yaxis).sum() # total area of all bars; divide by this area to normalize
kde = gaussian_kde((np.log10(x[:-1])+np.log10(x[1:]))/2, weights=yaxis, bw_method=0.2)
ax.bar(x[:-1], yaxis, width=np.diff(x), align="edge", ec='k', color='w')
ax.plot(xs, total_area*kde(np.log10(xs)), color='crimson')
ax.set_xscale('log')
Note that the bandwidth set for gaussian_kde is a somewhat arbitrarily value. Larger values give a more equalized curve, smaller values keep closer to the data. Some experimentation can help.
I have a strange behaviour in my python code, where I plot a bar-chart and it will be showed in a window, but pressing the "save"-button does nothing and .savefig() creates an empty file with 0KB.
Even more strange, I have a for-loop before the barchart, which is creating several complicated plots and all of them are capable to be plotted and then saved, except the barchart, which is only plotted.
I do not receive any errors from the python-console.
This is my code from the plot-loop before the bar-chart:
for l in range(NumberOfResults):
plottitle=groupLimits[0][l+1]+' - Result Width and Probability Distribution of ' + str(numberCombinations) + ' possible Combinations'
fig=plt.figure(plottitle,figsize=(GetSystemMetrics(0)/96,GetSystemMetrics(1)/96),dpi=96)
plt.clf()
ax=fig.add_subplot(1,1,1)
ResultColumn=range(NumberOfInputs,NumberOfInputs+NumberOfResults)[l]
positions=range(len(groupResults))
#positions=list(map(int, np.array(groupLimits[1:])[:,0]))
#plt.grid(1)
y1=[]
x1=[]
for i in range(len(groupResults)):
y1.append(groupResults[i][:,ResultColumn])
x1.append([positions[i]]*len(groupResults[i]))
y=list(np.array(y1).flatten())
s=[]
for i in range(len(groupResults)):
smin=min(groupResults[i][:,ProbabilityColumn])
smax=max(groupResults[i][:,ProbabilityColumn])
s.append((groupResults[i][:,ProbabilityColumn]-smin)/(smax-smin)*25000)
plt.scatter(x1,y1,s=s,marker='_',color='cyan',alpha=0.45)
bp=plt.boxplot(y1, positions=positions, widths=0.5,
labels=list(np.array(groupLimits[1:])[:,0]),
patch_artist=True,whis=[0,100])
#plt.xlim(min(positions)*0.9,max(positions)*1.1)
ax.xaxis.set_tick_params(labelsize=24)
ax.xaxis.grid(True,'major',color='k', linestyle='-', linewidth=0.75)
ax.xaxis.grid(True,'minor',color='k', linestyle=':', linewidth=0.25)
ax.xaxis.set_minor_locator(MultipleLocator(50));
ax.yaxis.set_tick_params(labelsize=24)
ax.yaxis.grid(True,'major',color='k', linestyle='-', linewidth=0.25)
ax.yaxis.grid(True,'minor',color='k', linestyle=':', linewidth=0.25)
ax.yaxis.set_minor_locator(MultipleLocator(0.1));
ax.yaxis.set_ticks(np.arange(int(min(y)),int(max(y))*1.1,0.5))
## change outline color, fill color and linewidth of the boxes
for box in bp['boxes']:
# change outline color
box.set( color='blue', linewidth=3)
# change fill color
box.set( facecolor = 'None')
## change color and linewidth of the whiskers
for whisker in bp['whiskers']:
whisker.set(color='blue', linewidth=3)
## change color and linewidth of the caps
for cap in bp['caps']:
cap.set(color='blue', linewidth=3,alpha=0.8)
## change color and linewidth of the medians
for median in bp['medians']:
median.set(color='magenta', linewidth=3)
y2=[]
x2=[]
for i in range(1,len(groupResults)+1):
if groupLimits[i][l+1]=='-':
pass
else:
y2.append(float(groupLimits[i][l+1]))
if FailedCombinations[i-1][l] is None:
failureString = 'No Results below the Limit'
else:
failureString= str(len(FailedCombinations[i-1][l]))+' failed # ' + "{:.3g}".format(FailureProbability[i-1][l]*100) + '%'
plt.text(positions[i-1],float(groupLimits[i][l+1])*0.9,
failureString,fontsize=18,horizontalalignment='center',
bbox=dict(facecolor='yellow', edgecolor='black'))
x2.append(positions[i-1])
plt.plot(x2,y2,marker='x',color='red', mew=4,ms=15,linestyle='None')
blue_patch = mpatches.Patch(color='blue', label='Result width')
red_patch = mlines.Line2D([],[],marker='x',color='red', mew=4,ms=15,linestyle='None', label='Limit')
magenta_patch = mpatches.Patch(color='magenta', label='Median')
cyan_patch = mpatches.Patch(color='cyan', label='Probability of Result')
plt.legend(handles=[blue_patch, red_patch, magenta_patch, cyan_patch],
loc='upper left',ncol=1,fontsize=24)
plt.title(plottitle,fontsize=24)
plt.xlabel('Engine Rotation Speed [rpm]',fontsize=24)
plt.ylabel('Absolute Pressure [bar]',fontsize=24)
labelsize = 24
rcParams['xtick.labelsize'] = labelsize
rcParams['ytick.labelsize'] = labelsize
plt.savefig(plottitle)
plt.show()
In between I run some calculations to prepare data for the bar-chart plot. However, the code for the bar-chart is:
# Plot group-wise combined failures with all-combined failures at end
plottitle = 'Failed combinations per group - and engine-all combined - TOTAL combinations: ' + str(len(groupResults[0]))
plt.figure(plottitle,figsize=(GetSystemMetrics(0)/96,GetSystemMetrics(1)/96),dpi=96)
plt.clf()
ax=plt.subplot(1,1,1)
y=[]
ind = np.arange(len(groupFailureProbabilities))
for i in range(len(groupFailures)):
y.append(len(groupFailures[i]))
y=tuple(y)
for i in range(len(y)):
if y[i]==0:
pass
else:
string=str(y[i])+' failed # ' + "{:.3g}".format(groupFailureProbabilities[i]*100)+' %'
plt.text(ind[i],y[i]+100,
string,fontsize=18, horizontalalignment='center',
bbox=dict(facecolor='yellow', edgecolor='black'))
width = 0.35
y=list(y)
plt.bar(ind, y, align='center', color='b')
labels=list(np.array(groupLimits[1:])[:,0])
labels.append('engine \n complete')
ax.set_xticks(ind)
ax.set_xticklabels(tuple(labels))
ax.yaxis.set_tick_params(labelsize=24)
ax.yaxis.grid(True,'major',color='k', linestyle='-', linewidth=0.25)
ax.yaxis.grid(True,'minor',color='k', linestyle=':', linewidth=0.25)
ax.yaxis.set_ticks(np.arange(int(min(y)),int(max(y))*1.1,500))
plt.title(plottitle,fontsize=24)
plt.xlabel('Engine Rotation Speed [rpm]',fontsize=24)
plt.ylabel('Number of Failed Combinations',fontsize=24)
plt.savefig(plottitle)
plt.show()
What is the reason why I cannot save my bar-chart ?
It's kinda difficult to write the shortest code necessary to reproduce the problem, if I have no idea why it happened in the first place.
However, I managed to get it working with a rearrangement of the code lines:
plottitle = 'Failed combinations per group - TOTAL combinations ' + str(len(groupResults[0]))
plt.figure(plottitle,figsize=(GetSystemMetrics(0)/96,GetSystemMetrics(1)/96),dpi=96)
plt.clf()
ax=plt.subplot(1,1,1)
plt.bar(ind, y, align='center')
labels=list(np.array(groupLimits[1:])[:,0])
labels.append('engine \n complete')
ax.set_xticks(ind)
ax.set_xticklabels(tuple(labels))
ax.yaxis.set_tick_params(labelsize=24)
ax.yaxis.grid(True,'major',color='k', linestyle='-', linewidth=0.25)
ax.yaxis.grid(True,'minor',color='k', linestyle=':', linewidth=0.25)
ax.yaxis.set_ticks(np.arange(int(min(y)),int(max(y))*1.1,500))
plt.title(plottitle,fontsize=24)
plt.xlabel('Engine Rotation Speed [rpm]',fontsize=24)
plt.ylabel('Number of Failed Combinations',fontsize=24)
plt.savefig(plottitle+'.png')
plt.show()