How to specify vertical spacing between overlapping combined legend entries? [Python/Matplotlib] - python

Goal
Plot two lines or curves in a figure, together with corresponding filled areas which indicate accuracy. See MWE.
Challenge: all handlers overlap in the legend entry. How could I specify vertical spacing such that dotted and solid lines don't overlap?
Desired output: one legend entry with red and blue lines and one filled area (either blue or red).
Desired output
Note
This is a simplified extension of this question.
Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.legend_handler import HandlerTuple
n = 11
x1 = np.linspace(0,1,n)
x2 = np.linspace(0,2,n)
y1 = x1 + 10
y2 = x2 + 5
y1err = 0.25 * np.ones(len(y1))
y2err = 0.50 * np.ones(len(y2))
handlers = []
labels = []
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
label1 = 'hello'
label2 = 'world'
p1, = ax.plot(x1,y1, linestyle='dotted', color='red')
f1 = ax.fill_between(x1,y1-y1err,y1+y1err, alpha=0.16, color='red')
p2, = ax.plot(x2,y2, linestyle='solid', color='blue')
f2 = ax.fill_between(x2,y2-y2err,y2+y2err, alpha=0.16, color='blue')
handlers.append((p1,f1,p2,f2))
labels.append((label1+'\n'+label2))
ax.legend(handlers,labels)
ax.grid()
ax.set_xlim(np.min(x2),np.max(x2))
plt.show()
Output

The plot requires the specification of legend for the respective plot and its label. This can be done by specifying the plt.legend([plot1, plot2],[label1, label1])
It could be achieved as mentioned below:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.legend_handler import HandlerTuple
n = 11
x1 = np.linspace(0,1,n)
x2 = np.linspace(0,2,n)
y1 = x1 + 10
y2 = x2 + 5
y1err = 0.25 * np.ones(len(y1))
y2err = 0.50 * np.ones(len(y2))
handlers = []
labels = []
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
label1 = 'hello'
label2 = 'world'
p1, = ax.plot(x1,y1, linestyle='dotted', color='red')
f1 = ax.fill_between(x1,y1-y1err,y1+y1err, alpha=0.16, color='red')
p2, = ax.plot(x2,y2, linestyle='solid', color='blue')
f2 = ax.fill_between(x2,y2-y2err,y2+y2err, alpha=0.16, color='blue')
handlers.append((p1,f1,p2,f2))
labels.append((label1+'\n'+label2))
ax.legend(handlers,labels)
ax.grid()
ax.set_xlim(np.min(x2),np.max(x2))
plt.legend([(p1,f1), (p2,f2)],[label1,label2])
plt.show()

Related

Embed subplot in cartopy map

I want to embed subplots canvas inside a cartopy projected map. I wrote this code to show the expected result by using rectangles:
#%%
import numpy as np
import cartopy as cr
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from cartopy.io import shapereader
import geopandas
resolution = '10m'
category = 'cultural'
name = 'admin_0_countries'
shpfilename = shapereader.natural_earth(resolution, category, name)
# read the shapefile using geopandas
df = geopandas.read_file(shpfilename)
# read the country borders
usa = df.loc[df['ADMIN'] == 'United States of America']['geometry'].values[0]
can = df.loc[df['ADMIN'] == 'Canada']['geometry'].values[0]
central_lon, central_lat = -80, 60
extent = [-85, -55, 40, 62]
# ax = plt.axes(projection=ccrs.Orthographic(central_lon, central_lat))
#Golden ratio
phi = 1.618033987
h = 7
w = phi*h
fig = plt.figure(figsize=(w,h))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
#Set map extent
ax.set_extent(extent)
ax.set_xticks(np.linspace(extent[0],extent[1],11))
ax.set_yticks(np.linspace(extent[2],extent[3],6))
ax.add_geometries(usa, crs=ccrs.PlateCarree(), facecolor='none',
edgecolor='k')
# ax.gridlines()
ax.coastlines(resolution='50m')
nx, ny = 7,6
#Begin firts rectangle
xi = extent[0] + 0.5
yi = extent[2] + 0.5
x, y = xi, yi
#Loop for create the plots grid
for i in range(nx):
for j in range(ny):
#Inner rect height
in_h = 2.8
#Draw the rect
rect = ax.add_patch(mpatches.Rectangle(xy=[x, y], width=phi*in_h, height=in_h,
facecolor='blue',
alpha=0.2,
transform=ccrs.PlateCarree()))
#Get vertex of the drawn rectangle
verts = rect.get_path().vertices
trans = rect.get_patch_transform()
points = trans.transform(verts)
#Refresh rectangle coordinates
x += (points[1,0]-points[0,0]) + 0.2
if j == ny-1:
x = xi
y += (points[2,1]-points[1,1]) + 0.2
# print(points)
fig.tight_layout()
fig.savefig('Figure.pdf',format='pdf',dpi=90)
plt.show()
This routine prints this figure
What I am looking for is a way to embed plots that match every single rectangle in the figure. I tried with fig.add_axes, but I couldn't get that mini-canvas match with the actual rectangles.
Since you want to embed the axes inside the parent axes is recommend using inset_axes, see the documentation here.
I wrote simple code to demonstrate how it works. Clearly there will be some tweaking of the inset_axes positions and sizes necessary for your desired output, but I think my trivial implementation already does decent.
All created axes instances are stored in a list so that they can be accessed later.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
axis = []
x = np.linspace(-85, -55)
y = np.linspace(40, 62)
ax.plot(x, y)
offset_l = 0.05
offset_h = 0.12
num_x = 6
num_y = 7
xs = np.linspace(offset_l, 1-offset_h, num_x)
ys = np.linspace(offset_l, 1-offset_h, num_y)
for k in range(num_x):
for j in range(num_y):
ax_ins = ax.inset_axes([xs[k], ys[j], 0.1, 0.1])
ax_ins.axhspan(0, 1, color='tab:blue', alpha=0.2)
axis.append(ax_ins)
Alternatively, you can also specify the inset_axes positions using data coordinates, for this you have to set the kwarg transform in the method to transform=ax.transData, see also my code below.
import matplotlib.pyplot as plt
import numpy as np
#Golden ratio
phi = 1.618033987
h = 7
w = phi*h
fig, ax = plt.subplots(figsize=(w, h))
axis = []
x = np.linspace(-85, -55)
y = np.linspace(40, 62)
ax.plot(x, y)
offset_l = 0.05
offset_h = 0.12
num_x = 6
num_y = 7
fig.tight_layout()
extent = [-85, -55, 40, 62]
xi = extent[0] + 0.5
yi = extent[2] + 0.5
in_h = 2.8
in_w = phi * 2.8
spacing = 0.4
for k in range(num_x):
for j in range(num_y):
ax_ins = ax.inset_axes([xi+k*(in_w + phi*spacing), yi+j*(in_h + spacing),
in_w, in_h], transform=ax.transData)
ax_ins.axhspan(0, 1, color='tab:blue', alpha=0.2)
axis.append(ax_ins)

How to add error values next to error bars?

I'm using matplotlib for my plots. I have with me the plot and errorbar. I want to specify the error value in text next to the errorbars. I'm looking for something like this (edited in pinta):
Is this possible to do in this code:
import numpy as np
import matplotlib.pyplot as plt
import math
N = 8
y1 = [0.1532, 0.1861, 0.2618, 0.0584, 0.1839, 0.2049, 0.009, 0.2077]
y1err = []
for item in y1:
err = 1.96*(math.sqrt(item*(1-item)/10000))
y1err.append(err)
ind = np.arange(N)
width = 0.35
fig, ax = plt.subplots()
ax.bar(ind, y1, width, yerr=y1err, capsize=7)
ax.grid()
plt.show()
You can use the annotate function to add text labels in the plot. Here is how you could do it:
import numpy as np
import matplotlib.pyplot as plt
import math
N = 8
y1 = [0.1532, 0.1861, 0.2618, 0.0584, 0.1839, 0.2049, 0.009, 0.2077]
y1err = []
for item in y1:
err = 1.96*(math.sqrt(item*(1-item)/10000))
y1err.append(err)
ind = np.arange(N)
width = 0.35
fig, ax = plt.subplots()
ax.bar(ind, y1, width, yerr=y1err, capsize=7)
# add error values
for k, x in enumerate(ind):
y = y1[k] + y1err[k]
r = y1err[k] / y1[k] * 100
ax.annotate(f'{y1[k]:.2f} +/- {r:.2f}%', (x, y), textcoords='offset points',
xytext=(0, 3), ha='center', va='bottom', fontsize='x-small')
ax.grid()
plt.show()

X-Axis tick spacing

I'm trying to format my x-Axis.
I have Values for 585,590,595,605. I already eliminated the tick for 600 but now I have a huge gap there. What's the solution to get a clean look?
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
Tabelle = pd.read_excel(
"C:\\Users\\alexk\\Dropbox\\WW\\WW Master\\1. Semester\\WW2\\WW2 Kernfachpraktikum\\KFP2\\Ergebnisse.xlsx",
"Auswertung Zugversuche T-d")
T = Tabelle["T./°C"]
y = Tabelle["MW. Rp0,2"]
y2 = Tabelle["MW. Rm"]
y3 = Tabelle["MW. e"]
z1 = Tabelle["StabW. Rp0,2"]
z2 = Tabelle["StabW. Rm"]
z3 = Tabelle["StabW. Rm"]
fig = plt.figure()
ax1 = fig.add_subplot(1, 2, 1)
ax1.bar(T, y, label=("Rp0,2")), ax1.bar(T, y2, label=("Rm"))
ax1.set_title("Titel")
ax1.set_xlabel("Gießtemperatur / °C")
ax1.set_ylabel("Rp0,2 und Rm / MPa")
ax1.set_ylim(0, 250)
ax1.set_xticks(T)
# Values in T:585,590,595,605
plt.legend()
plt.show()
That's how it looks at the moment:

Python (numpy) - correlate two binned plots

My question is how do I correlate my two binned plots and output a Pearson's correlation coefficient?
I'm not sure how to properly extract the binned arrays necessary for the np.corrcoef function. Here's my script:
import numpy as np
import matplotlib.pyplot as plt
A = np.genfromtxt('data1.txt')
x1 = A[:,1]
y1 = A[:,2]
B=np.genfromtxt('data2.txt')
x2 = B[:,1]
y2 = B[:,2]
fig = plt.figure()
plt.subplots_adjust(hspace=0.5)
plt.subplot(121)
AA = plt.hexbin(x1,y1,cmap='jet',gridsize=500,vmin=0,vmax=450,mincnt=1)
plt.axis([-180,180,-180,180])
cb = plt.colorbar()
plt.title('Data1')
plt.subplot(122)
BB = plt.hexbin(x2,y2,cmap='jet',gridsize=500,vmin=0,vmax=450,mincnt=1)
plt.axis([-180,180,-180,180])
cb = plt.colorbar()
plt.title('Data 2')
array1 = np.ndarray.flatten(AA)
array2 = np.ndarray.flatten(BB)
print np.corrcoef(array1,array2)
plt.show()
The answer can be found in the documentation:
Returns: object
a PolyCollection instance; use get_array() on this PolyCollection to get the counts in each hexagon.
Here's a revised version of you code:
A = np.genfromtxt('data1.txt')
x1 = A[:,1]
y1 = A[:,2]
B = np.genfromtxt('data2.txt')
x2 = B[:,1]
y2 = B[:,2]
# make figure and axes
fig, (ax1, ax2) = plt.subplots(1, 2)
# define common keyword arguments
hex_params = dict(cmap='jet', gridsize=500, vmin=0, vmax=450, mincnt=1)
# plot and set titles
hex1 = ax1.hexbin(x1, y1, **hex_params)
hex2 = ax2.hexbin(x2, y2, **hex_params)
ax1.set_title('Data 1')
ax2.set_title('Data 2')
# set axes lims
[ax.set_xlim(-180, 180) for ax in (ax1, ax2)]
[ax.set_ylim(-180, 180) for ax in (ax1, ax2)]
# add single colorbar
fig.subplots_adjust(right=0.8, hspace=0.5)
cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
fig.colorbar(hex2, cax=cbar_ax)
# get binned data and corr coeff
binned1 = hex1.get_array()
binned2 = hex2.get_array()
print np.corrcoef(binned1, binned2)
plt.show()
Two comments though: are you sure you want the pearson correlation coefficient? What are you actually trying to show? If you want to show the distributions are the same/different, you might want to use a Kolmogorov-Smirnov test.
Also don't use jet as a colormap. Jet is bad.

make axes the same length in matplotlib pyplot

I want to make square independent from the axis units. I know I can set the figure dimensions equal with figure(figsize=(10, 10)) for example, and I can set the axis scale ratio equal with set_aspect('equal'), but how can I force the actual axis length to be equal, e.g., make xaxis and yaxis each 10 inches long?
EDIT Example code
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.ticker import MaxNLocator
import numpy as np
x1 = 1
y1 = [10., 1000.]
err1 = 0.00865
x2 = 2
y2 = [9., 900.]
err2 = 0.00658
len_xaxis,len_yaxis = 5.,5. #fix here your numbers
xspace, yspace = .9, .9 # change the size of the void border here.
x_fig,y_fig = len_xaxis / xspace, len_yaxis / yspace
for i in range(2):
plt.clf()
# fig = plt.figure(figsize=(6, 6))
fig = plt.figure(figsize=(x_fig,y_fig))
plt.subplots_adjust(left=1-xspace, right = xspace, top=yspace, bottom = 1-yspace)
gs = gridspec.GridSpec(3, 1)
gs.update(hspace=0., wspace=0.)
ax1 = plt.subplot(gs[0:2, 0])
ax1.errorbar(x1, y1[i], yerr=err1)
ax1.errorbar(x2, y2[i], yerr=err2)
ax1.invert_yaxis()
plt.setp(ax1.get_xticklabels(), visible=False) # Remove x-labels between the plots
plt.xlim(0, 3)
ax2 = plt.subplot(gs[2, 0], sharex=ax1)
nbins = len(ax1.get_yticklabels())
ax1.yaxis.set_major_locator(MaxNLocator(nbins=8, prune='both'))
nbins = len(ax2.get_yticklabels())
ax2.yaxis.set_major_locator(MaxNLocator(nbins=6, prune='both'))
plt.tight_layout()
plt.savefig('prune_%d.png' % i)
plt.close()
Play with the plt.subplots_adjust() function. Example for 5 inches instead of 10:
len_xaxis,len_yaxis = 5.,5. #fix here your numbers
xspace, yspace = .9, .9 # change the size of the void border here.
x_fig,y_fig = len_xaxis / xspace, len_yaxis / yspace
figure(figsize=(x_fig,y_fig))
plt.subplots_adjust(left=1-xspace, right = xspace, top=yspace, bottom = 1-yspace)
plot([1,2,3],[-1,3,5])

Categories