subplots with multiple colorbar - python

I am trying to plot a figure with 4 subplots and 2 colorbars. Here is my code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
from matplotlib import rcParams
rcParams["savefig.dpi"] = 100
rcParams["font.size"] = 18
x1 = np.linspace(100, 1000, 10)
y1 = np.linspace(10, 17, 10)
z1 = np.linspace(4, 18, 10)
t1 = np.linspace(-0.3, 0.4, 10)
fig, axes = plt.subplots(2, 2, sharey = True, figsize = (10, 10))
a0 = axes[0][0].scatter(x1, y1, s = 40, c = z1, marker = 'o')
cbar1 = fig.colorbar(a0)
axes[0][0].set_ylabel('y1')
axes[0][0].set_xlabel('x1')
axes[0][0].xaxis.set_major_locator(MaxNLocator(4))
a1 = axes[0][1].scatter(t1, y1, s = 40, c = z1, marker = 'o')
axes[0][1].xaxis.set_major_locator(MaxNLocator(4))
axes[0][1].set_xlabel('t1')
cbar1.ax.set_ylabel('z1', rotation = 270)
x2 = np.linspace(450, 900, 20)
y2 = np.linspace(11, 12.5, 20)
z2 = np.linspace(12, 60, 20)
t2 = np.linspace(-0.3, 0.4, 20)
a0 = axes[1][0].scatter(x2, y2, c = z2, marker = 'o')
cbar2 = fig.colorbar(a0)
axes[1][0].set_ylabel('y2')
axes[1][0].set_xlabel('x2')
axes[1][0].xaxis.set_major_locator(MaxNLocator(4))
a1 = axes[1][1].scatter(t2, y2, c = z2, marker = 'o')
axes[1][0].xaxis.set_major_locator(MaxNLocator(4))
axes[1][1].set_xlabel('t2')
cbar2.ax.set_ylabel('z2', rotation = 270)
plt.show()
Here is the figure:
The thing I want to fix is:
the colorbars are at the far end to the right. I want 1 colorbar to be on the right of the first row and another colorbar to be on the right of the second row (basically, where it simply is).
How can I do this? Thanks!

You can enter another parameter to select on which axes to plot the colorbar.
Here is the change in your code:
cbar1 = fig.colorbar(a0, ax=axes[0][1])
cbar2 = fig.colorbar(a0, ax=axes[1][1])
Which produce this plot:

Related

Picker Event to display legend labels in matplotlib Ver. 3

Continue on from previous question .. Here
This time I need to plot two sets of data with different sizes on the same plot. The issue is, since the two sets of data have different sizes, some points will fall out of index. When I pick on certain point(s), I'll get a warning like this:
File "C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\cbook\__init__.py", line 224, in process
func(*args, **kwargs)
File "C:\Users\U240335\.spyder-py3\Spyder_Dingyi\untitled0.py", line 42, in onpick
IndexError: index 16 is out of bounds for axis 0 with size 10
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\cbook\__init__.py", line 224, in process
func(*args, **kwargs)
File "C:\Users\U240335\.spyder-py3\Spyder_Dingyi\untitled0.py", line 42, in onpick
IndexError: index 17 is out of bounds for axis 0 with size 10
And I would also like to show the value of the point (including the label) on the plot of which the cursor is clicking on and be able to do something like "clear all" after marking some points on the plot.
Here's the full code:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
a = np.random.uniform(0, 20, 30)
b = np.random.uniform(0, 20, 30)
x = np.random.uniform(0, 20, 30)
y = np.random.uniform(0, 20, 30)
z = [0]*10 + [1]*20
random.shuffle(z)
# x y data and legend labels
df1 = pd.DataFrame({"x1": a, "y1": b, 'z': z})
df2 = pd.DataFrame({"x2": x, "y2": y, 'z': z})
x1 = df1['x1'].loc[df1['z']==0].reset_index(drop=True).values
y1 = df1['y1'].loc[df1['z']==0].reset_index(drop=True).values
x2 = df2['x2'].loc[df1['z']==1].reset_index(drop=True).values
y2 = df2['y2'].loc[df1['z']==1].reset_index(drop=True).values
def overlay_plot2(x1,y1,x2,y2,title,xlabel,ylabel):
# define the picker event
def onpick(event):
ind = event.ind
print('\n%s:' % xlabel, x1[ind] if event.artist.get_label() == '0' else x2[ind], # use event.artist.getlabel() when labels are made
'%s:' % ylabel, y1[ind] if event.artist.get_label() == '1' else y2[ind],
event.artist.get_label())
# plot
fig, ax = plt.subplots(figsize=(8, 6), dpi = 120)
ax.scatter(x1, y1, c='b', label = '0',s=14, marker='x', picker=True)
ax.scatter(x2, y2, c='r', label = '1',s=14, marker='o', picker=True)
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.legend(
loc="center left",
bbox_to_anchor=(1, 0.5),
)
ax.ticklabel_format(useOffset=False)
ax.tick_params(axis = 'x',labelrotation = 45)
plt.tight_layout()
# call the event
fig.canvas.mpl_connect('pick_event', onpick)
overlay_plot2(x1,y1,x2,y2,"title","xlabel","ylabel")
Before going too into the weeds with customizing picker events, have you taken a look at mplcursors? There is a fantastic on hover feature that may be more of what you need. You can simply do mplcursors.cursor(hover=True) to give a basic annotation with x, y, and label values. Or, you can customize the annotations. Here is some code that I use for one of my projects where I've customized everything (color, text, arrow, etc.). Maybe you can find some use for it as well?
Do at least look at my comment about how to correct your index error. If the below isn't what you want, give this answer a read and it should point you in the right direction on how to annotate on click.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
import mplcursors
a = np.random.uniform(0, 20, 30)
b = np.random.uniform(0, 20, 30)
x = np.random.uniform(0, 20, 30)
y = np.random.uniform(0, 20, 30)
z = [0]*10 + [1]*20
random.shuffle(z)
# x y data and legend labels
df1 = pd.DataFrame({"x1": a, "y1": b, 'z': z})
df2 = pd.DataFrame({"x2": x, "y2": y, 'z': z})
x1 = df1['x1'].loc[df1['z']==0].reset_index(drop=True).values
y1 = df1['y1'].loc[df1['z']==0].reset_index(drop=True).values
x2 = df2['x2'].loc[df1['z']==1].reset_index(drop=True).values
y2 = df2['y2'].loc[df1['z']==1].reset_index(drop=True).values
def overlay_plot2(x1,y1,x2,y2,title,xlabel,ylabel):
# define the picker event
def onpick(event):
ind = event.ind
print('\n%s:' % xlabel, x1[ind] if event.artist.get_label() == '0' else x2[ind], # use event.artist.getlabel() when labels are made
'%s:' % ylabel, y1[ind] if event.artist.get_label() == '0' else y2[ind],
event.artist.get_label())
# plot
fig, ax = plt.subplots(figsize=(8, 6), dpi = 120)
ax1 = ax.scatter(x1, y1, c='b', label = '0',s=14, marker='x', picker=True)
ax2 = ax.scatter(x2, y2, c='r', label = '1',s=14, marker='o', picker=True)
#mplcursors.cursor(hover=True)
#mplcursors.cursor(ax1, hover=2).connect("add")
def _(sel):
sel.annotation.set_text('X Value: {}\nY Value: {}\nLabel: {}'.format(round(sel.target[0],2), round(sel.target[1], 2), sel.artist.get_label()))
sel.annotation.get_bbox_patch().set(fc="blue", alpha=0.5)
sel.annotation.arrow_patch.set(arrowstyle="-|>", connectionstyle="angle3", fc="black", alpha=.5)
#mplcursors.cursor(ax2, hover=2).connect("add")
def _(sel):
sel.annotation.set_text('X Value: {}\nY Value: {}\nLabel: {}'.format(round(sel.target[0],2), round(sel.target[1], 2), sel.artist.get_label()))
sel.annotation.get_bbox_patch().set(fc="red", alpha=0.5)
sel.annotation.arrow_patch.set(arrowstyle="-|>", connectionstyle="angle3", fc="black", alpha=.5)
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.legend(
loc="center left",
bbox_to_anchor=(1, 0.5),
)
ax.ticklabel_format(useOffset=False)
ax.tick_params(axis = 'x',labelrotation = 45)
plt.tight_layout()
# call the event
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
overlay_plot2(x1,y1,x2,y2,"title","xlabel","ylabel")
Example Output Graphs (my mouse is hovering over these points):

How do I make the text appear after 3barplot in Matplotlib

Why doesn't zorder work in this case? I've tried using it but the text still ends up being covered by the bar plot towers.
import numpy as np
from matplotlib import pyplot as plt
Percentage_Differences_1 = np.array([ [7.94*(10**-10),7.94*(10**-9),7.94*(10**-8),7.94*(10**-7),7.94*(10**-6),7.94*(10**-5)],
[7.92*(10**-12),7.92*(10**-11),7.92*(10**-10),7.92*(10**-9),7.92*(10**-8),7.92*(10**-7)],
[7.72*(10**-14),7.72*(10**-13),7.72*(10**-12),7.72*(10**-11),7.72*(10**-10),7.72*(10**-9)],
[5.66*(10**-16),5.66*(10**-15),5.66*(10**-14),5.66*(10**-13),5.66*(10**-12),5.66*(10**-11)],
[1.49*(10**-17),1.49*(10**-16),1.49*(10**-15),1.49*(10**-14),1.49*(10**-13),1.49*(10**-12)],
[2.21*(10**-18),2.21*(10**-17),2.21*(10**-16),2.21*(10**-15),2.21*(10**-14),2.21*(10**-13)] ]) # Layer 1, 12
fig1 = plt.figure(dpi = 120, tight_layout = True)
fig1.set_size_inches(10, 7)
ax1 = fig1.add_subplot(111, projection='3d')
width = depth = 0.3
column_names = ['$10^{-6} m$','$10^{-5} m$','$10^{-4} m$','$10^{-3} m$','$10^{-2} m$','$10^{-1} m$']
row_names = ['$10^{-6} g$','$10^{-5} g$','$10^{-4} g$','$10^{-3} g$','$10^{-2} g$','$10^{-1} g$']
height_names = ['$10^{-2}$','$10^{-4}$','$10^{-6}$','$10^{-8}$','$10^{-10}$','$10^{-12}$','$10^{-14}$','$10^{-16}$','$10^{-18}$']
for x in range(0,6):
for y in range(0,6):
plot1 = ax1.bar3d(x, y, 0, width, depth, np.log10(Percentage_Differences_1[x][y]), color = "#0040bf", alpha=0.3, zorder = 1)
txt1 = ax1.text(x,y,1.15*np.log10(Percentage_Differences_1[x][y]),'{:.2e}'.format(Percentage_Differences_1[y][x]), verticalalignment='top', bbox=dict(facecolor='grey', alpha=0.5), zorder = 2)
ax1.view_init(-140, -30)
ax1.set_xticks(np.linspace(0, 6, num = 6))
ax1.set_yticks(np.linspace(0, 6, num = 6))
ax1.set_xticklabels(column_names)
ax1.set_yticklabels(row_names)
ax1.set_zticklabels(height_names)
ax1.set_xlabel("Mass", labelpad = 13, rotation = 45)
ax1.set_ylabel("Radius", labelpad = 10, rotation = 45)
ax1.set_zlabel("Deviation $\Delta$")
ax1.set_title("1st Initial Condition: $r(0)$ and $r'(0)$ of $\Theta(12) = 2.18 \\times 10^{7} m$", pad = 40)
plt.show()
I've tried using both set_zorder and zorder but the plot still ends up covering the majority of the text labels.
Change your zorder for a number larger than the number of bar objects, 100 for example:

Finding intersection of two graphs with different numpy sizes

I would like to find the intersection of two graphs. It took me 674 points to plot the first graph and only 14 points to plot the second graph.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.read_csv("test1.csv",,skiprows=range(9),names=['A', 'B', 'C','D'])
df2 = pd.read_csv("test2.csv",skiprows=range(1),names=['X','Y'])
x1 = df['A'].tolist()
x1 = np.array(x1)
y1 = df['D'].tolist()
y1 = np.array(y1)
x2 = df2['X'].tolist()
x2 = np.array(x2)
y2 = df2['Y'].tolist()
y2 = np.array(y2)
idx = np.argwhere(np.diff(np.sign(y1 - y2))).flatten()
fig, ax = plt.subplots()
ax.plot(x1, y1, 'blue')
ax.plot(x2, y2, 'red')
plt.show()
However, I am getting this error from the code above due to the different sizes of numpy. Any ways I can solve this?
operands could not be broadcast together with shapes (674,) (14,)
You should compute interpolations of both curves with scipy.interpolate.interp1d, then you can calculate indeces of intersection points with the method you used.
I assume you have two curves with x1, x2, y1 and y2 coordinates, with different lengths and x axis limits:
x1 = np.linspace(-2, 12, 674)
x2 = np.linspace(0, 8, 14)
y1 = np.sin(x1)
y2 = np.cos(x2) + 1
So, you have to compute interpolation functions:
f1 = interp1d(x1, y1, kind = 'linear')
f2 = interp1d(x2, y2, kind = 'linear')
Then, you need to evaluate new curves on a common x axis, so new curves will have the same length:
xx = np.linspace(max(x1[0], x2[0]), min(x1[-1], x2[-1]), 1000)
y1_interp = f1(xx)
y2_interp = f2(xx)
Finally, you can compute indices of interpolation points as you already did:
idx = np.argwhere(np.diff(np.sign(y1_interp - y2_interp))).flatten()
Complete Code
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
x1 = np.linspace(-2, 12, 674)
x2 = np.linspace(0, 8, 14)
y1 = np.sin(x1)
y2 = np.cos(x2) + 1
f1 = interp1d(x1, y1, kind = 'linear')
f2 = interp1d(x2, y2, kind = 'linear')
xx = np.linspace(max(x1[0], x2[0]), min(x1[-1], x2[-1]), 1000)
y1_interp = f1(xx)
y2_interp = f2(xx)
idx = np.argwhere(np.diff(np.sign(y1_interp - y2_interp))).flatten()
fig, ax = plt.subplots()
ax.plot(x1, y1, 'blue', label = 'y1')
ax.plot(x2, y2, 'red', label = 'y2')
for index in idx:
ax.plot(xx[index], y1_interp[index], marker = 'o', markerfacecolor = 'black', markeredgecolor = 'black')
plt.show()
Plot

Plotting a line between 2-D point to its corresponding value in 3-D

I am trying to plot a dashed line in a 3-D Matplotlib plot. I would like to get a dashed line between each (x_pt, y_pt) to its corresponding z_pt.
from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib
matplotlib.rcParams['mathtext.fontset'] = 'cm'
matplotlib.rcParams['axes.labelsize'] = 13
def z_function(x, y):
a = 1
b = 5.1/(4*np.pi**2)
c = 5/np.pi
r = 6
s = 10
t = 1/(8*np.pi)
return a*(y - b*x**2 + c*x - r)**2 + s*(1 - t)*np.cos(x) + s
x = np.linspace(-5, 10, 100)
y = np.linspace(0, 15, 100)
indexes = np.random.randint(0, 100, 5)
x_pt = x[indexes]
y_pt = y[indexes]
z_pt = z_function(x_pt, y_pt)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(x_pt, y_pt, color='k', marker='x', depthshade=False)
ax.scatter(x_pt, y_pt, z_pt, color='k', marker='^', depthshade=False)
ax.set_xticks([-5, 0, 5, 10])
ax.set_yticks([0, 5, 10, 15])
ax.set_zticks([100, 200, 300])
ax.view_init(30, -120)
ax.set_xlabel(r'$x_1$')
ax.set_ylabel(r'$x_2$')
ax.zaxis.set_rotate_label(False)
ax.set_zlabel(r'$f(x)$', rotation=0)
ax.w_xaxis.pane.fill = False
ax.w_yaxis.pane.fill = False
ax.w_zaxis.pane.fill = False
plt.show()
Can anyone help me with this?
If I understand your problem correctly, you need to connect the point (x,y,0) to (x,y,z) like so:
for x_,y_,z_ in zip(x_pt, y_pt, z_pt):
ax.plot([x_,x_],[y_,y_],[0,z_], '--', c='grey')
It should be as simple as:
ax.plot(x_pt, y_pt, zs=z_pt, color='blue', marker='--', depthshade=False)
alternatively using:
ax.plot3D(x_pt, y_pt, z_pt, marker='--')
UPDATE:
You will need to create extra dummy coordinates for each point on the x-y axis, like so:
import numpy as np
n = 10 # number of points in the line
for i in len(x_pt):
x_range = np.linspace(0, x_pt[i], n)
y_range = np.linspace(0, y_pt[i], n)
ax.plot3D(x_range, y_range, [z_pt[i]]*n, marker='--')
NOTE: Untested

Change the size of subplot so histogram and heatmap x-axes lineup [duplicate]

For a correlation plot I would like to have a plot that is optically square (same length of x and y in pixels) but also has a certain axis limit on x and y. I can get each of the 2 separately but not at the same time:
import matplotlib.pyplot as plt
f, (ax1, ax2) = plt.subplots(1, 2)
x = [1 , 4 , 6]
y1 = [4, 7, 9]
y2 = [20, 89, 99]
ax1.plot(x, y1, 'o')
ax2.plot(x, y2, 'o')
myXlim = [0, 8]
ax1.set_xlim(myXlim)
ax2.set_xlim(myXlim)
ax1.axis('square')
ax2.axis('square')
# limit is gone here
ax1.set_xlim(myXlim)
ax2.set_xlim(myXlim)
# square is gone here
plt.show()
If I just use the ax1.set_xlim(myXlim) (and not square) then I can manually adjust the window size to get what I want but how can I do this automatically?
An option to get square subplots is to set the subplot parameters such that the resulting subplots automatically adjust to be square. This is a little involved, because all the margins and spacings need to be taken into account.
import matplotlib.pyplot as plt
f, (ax1, ax2) = plt.subplots(1, 2)
x = [1 , 4 , 6]
y1 = [4, 7, 9]
y2 = [20, 89, 99]
def square_subplots(fig):
rows, cols = ax1.get_subplotspec().get_gridspec().get_geometry()
l = fig.subplotpars.left
r = fig.subplotpars.right
t = fig.subplotpars.top
b = fig.subplotpars.bottom
wspace = fig.subplotpars.wspace
hspace = fig.subplotpars.hspace
figw,figh = fig.get_size_inches()
axw = figw*(r-l)/(cols+(cols-1)*wspace)
axh = figh*(t-b)/(rows+(rows-1)*hspace)
axs = min(axw,axh)
w = (1-axs/figw*(cols+(cols-1)*wspace))/2.
h = (1-axs/figh*(rows+(rows-1)*hspace))/2.
fig.subplots_adjust(bottom=h, top=1-h, left=w, right=1-w)
ax1.plot(x, y1, 'o')
ax2.plot(x, y2, 'o')
#f.tight_layout() # optionally call tight_layout first
square_subplots(f)
plt.show()
The benefit here is to be able to freely zoom and autoscale. The drawback is that once the figure size changes, the subplot sizes are not square any more. To overcome this drawback, one may in addition register a callback on size changes of the figure.
import matplotlib.pyplot as plt
f, (ax1, ax2) = plt.subplots(1, 2)
x = [1 , 4 , 6]
y1 = [4, 7, 9]
y2 = [20, 89, 99]
class SquareSubplots():
def __init__(self, fig):
self.fig = fig
self.ax = self.fig.axes[0]
self.figw,self.figh = 0,0
self.params = [self.fig.subplotpars.left,
self.fig.subplotpars.right,
self.fig.subplotpars.top,
self.fig.subplotpars.bottom,
self.fig.subplotpars.wspace,
self.fig.subplotpars.hspace]
self.rows, self.cols = self.ax.get_subplotspec().get_gridspec().get_geometry()
self.update(None)
self.cid = self.fig.canvas.mpl_connect('resize_event', self.update)
def update(self, evt):
figw,figh = self.fig.get_size_inches()
if self.figw != figw or self.figh != figh:
self.figw = figw; self.figh = figh
l,r,t,b,wspace,hspace = self.params
axw = figw*(r-l)/(self.cols+(self.cols-1)*wspace)
axh = figh*(t-b)/(self.rows+(self.rows-1)*hspace)
axs = min(axw,axh)
w = (1-axs/figw*(self.cols+(self.cols-1)*wspace))/2.
h = (1-axs/figh*(self.rows+(self.rows-1)*hspace))/2.
self.fig.subplots_adjust(bottom=h, top=1-h, left=w, right=1-w)
self.fig.canvas.draw_idle()
s = SquareSubplots(f)
ax1.plot(x, y1, 'o')
ax2.plot(x, y2, 'o')
plt.show()
The above solution works by restricting the space the subplot has inside of its grid. An opposite approach, where the size of the subplot is somehow fixed, would be shown in the answer to Create equal aspect (square) plot with multiple axes when data limits are different?.

Categories

Resources