Matplotlib: how to get color bars that are one on top of each other as opposed to side by side? - python-3.x

I have the following code:
import matplotlib.pyplot as plt
import numpy as np
img1 = np.zeros([512,512])
img2 = np.zeros([512,512])
plt.figure(figsize=(10,10))
plt.imshow(img1, cmap='inferno')
plt.axis('off')
cba = plt.colorbar(shrink=0.25)
cba.ax.set_ylabel('Events / counts', fontsize=14)
cba.ax.tick_params(labelsize=12)
plt.imshow(img2, cmap='turbo', alpha=0.5)
plt.axis('off')
cba = plt.colorbar(shrink=0.25)
cba.ax.set_ylabel('Lifetime / ns)', fontsize=14)
cba.ax.tick_params(labelsize=12)
plt.tight_layout()
plt.show()
which produces the following output:
My question is, how can I get color bars that are on top of one another as opposed to next to each other? Ideally, I would like to get something like this:

You can grab the position of the ax and use it to create new axes for the colorbars. Here is an example:
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
data = ndimage.gaussian_filter(np.random.randn(512, 512), sigma=15, mode='nearest') * 20
fig, ax = plt.subplots()
im1 = ax.imshow(data, vmin=-1, vmax=0, cmap='viridis')
data[data < 0] = np.nan
im2 = ax.imshow(data, vmin=0.001, vmax=1, cmap='Reds_r')
ax.axis('off')
pos = ax.get_position()
bar_h = (pos.y1 - pos.y0) * 0.5 # 0.5 joins the two bars, e.g. 0.48 separates them a bit
ax_cbar1 = fig.add_axes([pos.x1 + 0.02, pos.y0, 0.03, bar_h])
cbar1 = fig.colorbar(im1, cax=ax_cbar1, orientation='vertical')
ax_cbar2 = fig.add_axes([pos.x1 + 0.02, pos.y1 - bar_h, 0.03, bar_h])
cbar2 = fig.colorbar(im2, cax=ax_cbar2, orientation='vertical')
plt.show()

Related

How to change x,y in plt to lon, lat in cartopy?

I want to change x, y to lon, lat (Picture below yellow marker) when I click some point in map and get x, y.
How can I change it? Help me please...
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np
import cartopy
def mouse_click(event):
x, y = event.xdata, event.ydata
print(x, y)
proj = ccrs.LambertConformal(central_longitude=125, central_latitude=35, false_easting=400000,false_northing=400000,
standard_parallels=(46, 49))
fig = plt.figure(figsize=(16.535433, 11.692913))
ax = fig.add_subplot(1,1,1, projection=proj)
ax.set_extent((79, 156, 10, 66))
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=False, linewidth=0.2,color='black', alpha=0.5,
linestyle=(3, (20, 5)), xlocs=np.arange(-180, 200, 10), x_inline=False, y_inline=False)
#xlabel_style = {'rotation': 0},ylabel_style = {'rotation': 0})
ax.coastlines(resolution='50m', alpha=1, linewidth=0.5)
ax.add_feature(cartopy.feature.BORDERS, alpha=1,linewidth=0.5)
ax.add_feature(cartopy.feature.LAKES, alpha=0.4)
plt.connect('button_press_event', mouse_click)
plt.show()
I tried to find about it but It's hard to understanding.
I found some way.
Change the projection.
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection=ccrs.PlateCarree())
ax.add_feature(cartopy.feature.LAND, facecolor='black')
ax.set_global()
In LambertConformal,x,y is different with lon,lat. But in PlateCarree, they are same.

Why does not this plot get bigger as expected?

From a previous question, I got that plt.figure(figsize = 2 * np.array(plt.rcParams['figure.figsize'])) will increase the plot size by 2 times. With below code, I want to plot 4 subplots in the grid 2x2.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
%config InlineBackend.figure_format = 'svg' # Change the image format to svg for better quality
don = pd.read_csv('https://raw.githubusercontent.com/leanhdung1994/Deep-Learning/main/donclassif.txt.gz', sep=';')
fig, ax = plt.subplots(nrows = 2, ncols = 2)
plt.figure(figsize = 2 * np.array(plt.rcParams['figure.figsize'])) # This is to have bigger plot
for row in ax:
for col in row:
kmeans = KMeans(n_clusters = 4)
kmeans.fit(don)
y_kmeans = kmeans.predict(don)
col.scatter(don['V1'], don['V2'], c = y_kmeans, cmap = 'viridis')
centers = kmeans.cluster_centers_
col.scatter(centers[:, 0], centers[:, 1], c = 'red', s = 200, alpha = 0.5);
plt.show()
Could you please explain why plt.figure(figsize = 2 * np.array(plt.rcParams['figure.figsize'])) does not work in this case?
I post #JohanC's comment to remove this question from unanswered list.
It could be written as fig, axes = plt.subplots(nrows=2, ncols=2, figsize=2 * np.array(plt.rcParams['figure.figsize'])). Just calling plt.figure without storing the result creates a dummy new figure, without changing fig and without creating the axes on that new figure won't have the desired result.

Matplotlib- Add a color bar below a multi-colored line subplot as shown in the image

I am having a multicolored line plot and I want to add a color bar under it in the same figure like as shown in the image below, Is it possible?
I have attached a color bar image as a reference which I took from another code.
My intention here is to use the color bar like a legend for each segment of the line in the plot.
Edit-1: I want to have the color bar using a mappable object such as an image, So don't want to create a new subplot for the sole purpose of the color bar.
Any suggestion is welcome. Thanks in Advance.
This is the code for multicolored line plot
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
Segments=[[[3,1],[6,1]],[[6,2],[9,2]],[[9,3],[12,3]],[[12,4],[15,4]], [[12,4],[15,4]]]
Points_1 = np.concatenate([Segments[:-1], Segments[1:]], axis=1)
lc = LineCollection(Points_1, colors=['r','g','b','y'], linewidths=2)
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
plt.show()
This is a workaround I'am using:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.colorbar as mcolorbar
import matplotlib.colors as mcolors
Segments=[[[3,1],[6,1]],[[6,2],[9,2]],[[9,3],[12,3]],[[12,4],[15,4]], [[12,4],[15,4]]]
Points_1 = np.concatenate([Segments[:-1], Segments[1:]], axis=1)
lc = LineCollection(Points_1, colors=['r','g','b','y'], linewidths=2)
fig, ax = plt.subplots(2, 1, gridspec_kw={'height_ratios' : [5,1]})
ax[0].add_collection(lc)
bounds = np.linspace(0, 1, 5)[:-1]
labels = ['Action1', 'Action2', 'Action3', 'Action4']
ax[0].set_xlim([0, 15])
ax[0].set_ylim([0, 10])
cb2 = mcolorbar.ColorbarBase(ax = ax[1], cmap = cmap, orientation = 'horizontal', extendfrac='auto')
cb2.set_ticks(bounds)
cb2.set_ticklabels(labels)
plt.tight_layout()
plt.show()
If you specifically want to avoid subplots, you can use a scalar mappable:
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
cmap = mcolors.ListedColormap(['r','g','b','y'])
sm = plt.cm.ScalarMappable(cmap=cmap)
sm.set_array([]) # this line may be ommitted for matplotlib >= 3.1
cbar = fig.colorbar(sm, ax=ax, orientation='horizontal',aspect=90)
bounds = np.linspace(0, 1, 5)[:-1]
labels = ['Action1', 'Action2', 'Action3', 'Action4']
ax.set_xlim([0, 15])
ax.set_ylim([0, 10])
cbar.set_ticks(bounds)
cbar.set_ticklabels(labels)
plt.tight_layout()
plt.show()
This helped me to get what I asked.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.collections import LineCollection
Segments=[[[3,1],[6,1]],[[6,2],[9,2]],[[9,3],[12,3]],[[12,4],[15,4]], [[12,4],[15,4]]]
Points_1 = np.concatenate([Segments[:-1], Segments[1:]], axis=1)
lc = LineCollection(Points_1, colors=['r','g','b','y'], linewidths=2)
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
c=[1,2,3,4,5]
labels = ['Action1', 'Action2', 'Action3', 'Action4']
cmap = mcolors.ListedColormap(['r','g','b','y'])
norm = mcolors.BoundaryNorm([1,2,3,4,5],4)
sm = plt.cm.ScalarMappable(norm=norm, cmap=cmap)
sm.set_array([]) # this line may be ommitted for matplotlib >= 3.1
cbar=fig.colorbar(sm, ticks=c, orientation='horizontal')
cbar.set_ticklabels(['Action1', 'Action2', 'Action3', 'Action4'])
plt.show()

How to have a fast crosshair mouse cursor for subplots in matplotlib?

In this video of backtrader's matplotlib implementation https://youtu.be/m6b4Ti4P2HA?t=2008 I can see that a default and very fast and CPU saving crosshair mouse cursor seems to exist in matplotlib.
I would like to have the same kind of mouse cursor for a simple multi subplot plot in matplotlib like this:
import numpy as np
import matplotlib
matplotlib.use('QT5Agg')
matplotlib.rcParams['figure.figsize'] = (20.0, 22.0)
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = plt.subplot(2, 1, 1)
ax2 = plt.subplot(2, 1, 2, sharex=ax1)
ax1.plot(np.array(np.random.rand(100)))
ax2.plot(np.array(np.random.rand(100)))
plt.show()
So, if I am with my mouse in the lower subplot, I want to see directly and very precisely, which value of x/y in the lower plot corresponds to which value pair in the upper plot.
I have found other solutions to do this but they seem to be very slow compared to the implementation in the video.
You can create a crosshair cursor via mplcursors. sel.extras.append() takes care that the old cursor is removed when a new is drawn. With sel.annotation.set_text you can adapt the popup annotation shown. To leave out the annotation, use sel.annotation.set_visible(False). To find the corresponding y-value in the other subplot, np.interp with the data extracted from the curve can be used.
import numpy as np
import matplotlib.pyplot as plt
import mplcursors
def crosshair(sel):
x, y2 = sel.target
y1 = np.interp( sel.target[0], plot1.get_xdata(), plot1.get_ydata() )
sel.annotation.set_text(f'x: {x:.2f}\ny1: {y1:.2f}\ny2: {y2:.2f}')
# sel.annotation.set_visible(False)
hline1 = ax1.axhline(y1, color='k', ls=':')
vline1 = ax1.axvline(x, color='k', ls=':')
vline2 = ax2.axvline(x, color='k', ls=':')
hline2 = ax2.axhline(y2, color='k', ls=':')
sel.extras.append(hline1)
sel.extras.append(vline1)
sel.extras.append(hline2)
sel.extras.append(vline2)
fig = plt.figure(figsize=(15, 10))
ax1 = plt.subplot(2, 1, 1)
ax2 = plt.subplot(2, 1, 2, sharex=ax1)
plot1, = ax1.plot(np.array(np.random.uniform(-1, 1, 100).cumsum()))
plot2, = ax2.plot(np.array(np.random.uniform(-1, 1, 100).cumsum()))
cursor = mplcursors.cursor(plot2, hover=True)
cursor.connect('add', crosshair)
plt.show()
Here is an alternative implementation that stores the data in global variables and moves the lines (instead of deleting and recreating them):
import numpy as np
import matplotlib.pyplot as plt
import mplcursors
def crosshair(sel):
x = sel.target[0]
y1 = np.interp(x, plot1x, plot1y)
y2 = np.interp(x, plot2x, plot2y)
sel.annotation.set_visible(False)
hline1.set_ydata([y1])
vline1.set_xdata([x])
hline2.set_ydata([y2])
vline2.set_xdata([x])
hline1.set_visible(True)
vline1.set_visible(True)
hline2.set_visible(True)
vline2.set_visible(True)
fig = plt.figure(figsize=(15, 10))
ax1 = plt.subplot(2, 1, 1)
ax2 = plt.subplot(2, 1, 2, sharex=ax1)
plot1, = ax1.plot(np.array(np.random.uniform(-1, 1, 100).cumsum()))
plot2, = ax2.plot(np.array(np.random.uniform(-1, 1, 100).cumsum()))
plot1x = plot1.get_xdata()
plot1y = plot1.get_ydata()
plot2x = plot2.get_xdata()
plot2y = plot2.get_ydata()
hline1 = ax1.axhline(plot1y[0], color='k', ls=':', visible=False)
vline1 = ax1.axvline(plot1x[0], color='k', ls=':', visible=False)
hline2 = ax2.axhline(plot2y[0], color='k', ls=':', visible=False)
vline2 = ax2.axvline(plot2x[0], color='k', ls=':', visible=False)
cursor = mplcursors.cursor([plot1, plot2], hover=True)
cursor.connect('add', crosshair)
plt.show()
Sorry for the late answer, but I was horrified by how much code was suggested above, when there is this one-liner on matplotlib to do a simple crosshair accross different axes. It won't show your labels but it's CPU-light.
from matplotlib.widgets import MultiCursor
cursor = MultiCursor(fig.canvas, (ax[0], ax[1]), color='r',lw=0.5, horizOn=True, vertOn=True)

How can I add a normal distribution curve to multiple histograms?

With the following code I create four histograms:
import numpy as np
import pandas as pd
data = pd.DataFrame(np.random.normal((1, 2, 3 , 4), size=(100, 4)))
data.hist(bins=10)
I want the histograms to look like this:
I know how to make it one graph at the time, see here
But how can I do it for multiple histograms without specifying each single one? Ideally I could use 'pd.scatter_matrix'.
Plot each histogram seperately and do the fit to each histogram as in the example you linked or take a look at the hist api example here. Essentially what should be done is
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
fig = plt.figure()
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222)
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224)
for ax in [ax1, ax2, ax3, ax4]:
n, bins, patches = ax.hist(**your_data_here**, 50, normed=1, facecolor='green', alpha=0.75)
bincenters = 0.5*(bins[1:]+bins[:-1])
y = mlab.normpdf( bincenters, mu, sigma)
l = ax.plot(bincenters, y, 'r--', linewidth=1)
plt.show()

Resources