I want to plot a square on the right hand side of the colorbar as a reference with the same color coding (see the image below).
But I couldn't find a way to achieve this goal. Is there any kind and intelligent man that could make this happen?
You can create a custom legend object and locate it next to the colorbar. Shown in a random plot:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
import matplotlib.patches as patches
class SquareObject(object):
pass
# Custom legend object
class SquareObjectHandler(object):
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
l1 = patches.Rectangle(
(x0, y0), # (x,y)
width / 2, # width
height, # height
fill=True,
facecolor="green",
)
handlebox.add_artist(l1)
return [l1]
fig, ax1 = plt.subplots(1, 1, figsize=(14, 6))
im = ax1.imshow(np.arange(100).reshape((10, 10)))
# To locate the colorbar
divider = make_axes_locatable(ax1)
cax = divider.append_axes('right', size='5%', pad=0.05)
plt.colorbar(im, cax=cax, label="colorbar")
# Add the legend
ax1.legend([SquareObject()],
['Reference'],
handler_map={SquareObject: SquareObjectHandler()},
loc='right center',
bbox_to_anchor=(1.4, 0.8), #(x, y)
frameon=False,
handletextpad=-0.5)
plt.show()
You can move the legend with the bbox_to_anchor parameter.
Just to post it here if someone could have the same question that I did. To get the color from the colorbar, I calculated the corresponding proportion of the given reference in the colorbar.
cmap = cm.get_cmap("OrRd") # get the corresponding colorbar
reference = 90 # set the reference
rgb = cmap( (reference - vmin) / (vmax - vmin) ) # find the color in the colorbar
finallty, set it to the "facecolor" in the class "SquareObjectHandler".
The location would be the same way. Figure out the coordinates of the colorbar and set the "bbox_to_anchor" in the "legend" accordingly.
Related
plt.figure(figsize = (8,5))
sns.countplot(data = HRdfMerged, x = 'Gender', hue='Attrition').set_title('Gender vs Attrition')
I'm having a hard time adding a label to the top of my bar that states the total number. I have tried many different ways but can't get it right. Im using matplotlib. Picture of bar chart added.
Once you have called sns.countplot, we will explore the list ax.patches to get information from the bars and place the texts you want:
# Imports.
import matplotlib.pyplot as plt
import seaborn as sns
# Load a dataset to replicate what you have in the question.
data = sns.load_dataset("titanic")
fig, ax = plt.subplots() # Use the object-oriented approach with Matplotlib when you can.
sns.countplot(data=data, x="class", hue="who", ax=ax)
ax.set_title("title goes here")
fig.show()
# For each bar, grab its coordinates and colors, find a suitable location
# for a text and place it there.
for patch in ax.patches:
x0, y0 = patch.get_xy() # Bottom-left corner.
x0 += patch.get_width()/2 # Middle of the width.
y0 += patch.get_height() # Top of the bar
color = patch.get_facecolor()
ax.text(x0, y0, str(y0), ha="center", va="bottom", color="white", clip_on=True, bbox=dict(ec="black",
fc=color))
Play around with the kwargs of ax.text to get the result you prefer. An alternative:
ax.text(x0, y0, str(y0), ha="center", va="bottom", color=color, clip_on=True)
You can also use the convenient Axes.bar_label method here to do this in just a couple lines.
Since seaborn does not return the BaContainer objects to us, we will need to access them from the Axes object via Axes.containers attribute.
import matplotlib.pyplot as plt
import seaborn as sns
data = sns.load_dataset("titanic")
fig, ax = plt.subplots()
sns.countplot(data=data, x="class", hue="who", ax=ax)
for bar_contain in ax.containers:
ax.bar_label(bar_contain)
I use the following code to add a colorbar at the top left corner of each subplot.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# Create figure
fig = plt.figure(figsize=(5, 2))
# Specify geometry of the grid for subplots
gs0 = gridspec.GridSpec(1, 3, wspace=0.7)
# Data
a = np.arange(3*5).reshape(3,5)
for ax_i in range(3):
# Create axes
ax = plt.subplot(gs0[ax_i])
# Plot data
plot_pcolor = plt.pcolormesh(a)
# ******** Plot a nested colorbar inside the plot ********
# Define position of the desired colorbar in axes coordinate
# [(lower left x, lower left y), (upper right x, upper right y)]
ax_coord = [(0.05, 0.5), (0.2, 0.95)]
# Transform the two points from axes coordinates to display coordinates
tr1 = ax.transAxes.transform(ax_coord)
# Create an inverse transversion from display to figure coordinates
inv = fig.transFigure.inverted()
tr2 = inv.transform(tr1)
# Position in figure coordinates [left, bottom, width, height]
datco = [tr2[0,0], tr2[0,1], tr2[1,0]-tr2[0,0], tr2[1,1]-tr2[0,1]]
# Create colorbar axes
cbar_ax = fig.add_axes(datco)
# Plot colorbar
cbar = plt.colorbar(plot_pcolor, cax=cbar_ax)
# ********************************************************
if False:
plt.subplots_adjust(left=0.15, bottom=0.2, right=0.95, top=0.8)
plt.savefig('test.png', dpi=500)
which gives the following plot:
However, if I use the subplots_adjust() function (by replacing False to True in the code above), the colorbars do not move properly:
Do you know how I can handle it?
Using the inset_axes() function from the mpl_toolkits module solves the problem. It is also possible to simply use ax.inset_axes().
Here is the new code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
# Create figure
fig = plt.figure(figsize=(5, 2))
# Specify geometry of the grid for subplots
gs0 = gridspec.GridSpec(1, 3, wspace=0.7)
# Data
a = np.arange(3*5).reshape(3,5)
for ax_i in range(3):
# Create axes
ax = plt.subplot(gs0[ax_i])
# Plot data
plot_pcolor = plt.pcolormesh(a)
axins = inset_axes(ax, width="5%", height="50%", loc='upper left')
# Plot colorbar
cbar = plt.colorbar(plot_pcolor, cax=axins)
# ********************************************************
if True:
plt.subplots_adjust(left=0.15, bottom=0.2, right=0.95, top=0.8)
plt.savefig('test.png', dpi=500)
Here is the result:
I would like to plot circles with matplotlib (patches), and annotate them. The annotation would be a word, and it needs to be in the centre of the circle.
So far, I can plot a circle and annotate it:
But the annotation is not centred, neither horizontally or vertically. In order to do that, I would need access to the dimensions of the text.
Is there a way to access the dimensions of the text in "the coordinate systems" ?. For example, if the circle has a radius of 15 (15 something, not pixels), the text would have a length of 12 something (not pixels).
I'm open to any other suggestion on how to do that.
Here is my code so far:
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
fig, ax = plt.subplots()
ax = fig.add_subplot(111)
x = 0
y = 0
circle = plt.Circle((x, y), radius=1)
ax.add_patch(circle)
label = ax.annotate("cpicpi", xy=(x, y), fontsize=30)
ax.axis('off')
ax.set_aspect('equal')
ax.autoscale_view()
plt.show()
You need to set the horizontal alignment in ax.annotate using ha="center". The same thing can be done for the vertical direction if necessary using the argument va="center"
fig, ax = plt.subplots()
ax = fig.add_subplot(111)
x = 0
y = 0
circle = plt.Circle((x, y), radius=1)
ax.add_patch(circle)
label = ax.annotate("cpicpi", xy=(x, y), fontsize=30, ha="center")
ax.axis('off')
ax.set_aspect('equal')
ax.autoscale_view()
plt.show()
You can add two additional arguments to the annotate() call:
label = ax.annotate(
"cpicpi",
xy=(x, y),
fontsize=30,
verticalalignment="center",
horizontalalignment="center"
)
(See the docs for the arguments of annotate and of Text – whose constructor is called by annotate)
Using matplotlib.pyplot, I have two plots. One is a waveform of an audio file. The second is a spectrogram of the same audio. I want the wave form to be directly above the spectrogram (same x-axis, and aligned together). I also want a colorbar for the spectrogram.
Problem - when I put the colorbar in, it attaches to the spectrogram row and the waveform extends over the colorbar (i.e. is no longer time-aligned with the spectrogram and is wider than the spectrogram).
I am close to the solution, I think, but I just can't quite figure out what I'm doing wrong or what to change to get it working the way I want. Hope someone can point me in the right direction!
Using the following python code (I made the code as MWE as possible):
import matplotlib
matplotlib.use("TkAgg")
from scipy.io import wavfile
from matplotlib import mlab
from matplotlib import pyplot as plt
import numpy as np
from numpy.lib import stride_tricks
samplerate, data = wavfile.read('FILENAME.wav')
times = np.arange(len(data))/float(samplerate)
plt.close("all")
####
#Waveform
####
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(13.6, 7.68))
plt.subplot(211)
plt.plot(times, data, color='k')
plt.xlabel('time (s)')
plt.xlim(times[0], times[-1])
max_amp = max(abs(np.amin(data)), abs(np.amax(data)))
min_amp = (max_amp * -1) - abs(np.amin(data) - np.amax(data))/50
max_amp = max_amp + abs(np.amin(data) - np.amax(data))/50
plt.ylim(min_amp, max_amp)
ax = plt.gca()
ax.set_yticks(np.array([min_amp, min_amp/2, 0, max_amp/2, max_amp]))
ax.spines['bottom'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.xaxis.set_tick_params(pad=115)
####
#Spectrogram
####
Fs = 5000*2.#10000.
NFFT = min(512, len(data))
noverlap = NFFT / 2
pad_to = NFFT * 16
dynamicRange = 27.5
vmin = 20*np.log10(np.max(data)) - dynamicRange
cmap = plt.get_cmap('inferno')
plt.subplot(212)
Pxx, freqs, times, cax = plt.specgram(data, NFFT=NFFT, Fs=samplerate, noverlap=noverlap, mode='magnitude', scale='dB', vmin=vmin, pad_to=pad_to, cmap=cmap)
axes_spec = plt.gca()
axes_spec.set_xlim([0., max(times)])
axes_spec.set_ylim([0, 5000])
plt.xlabel("Time (s)")
plt.ylabel("Frequency (hz)")
plt.colorbar(cax, label='(dB)').ax.yaxis.set_label_position('left')
plt.tight_layout()
plt.show()
I can get the following plot:
Making these slight modifications below, I can get the plot to look almost how I want. The problem is, it creates a blank figure next to the colorbar. This version, minus the blank figure, is what I am trying to create.
#Replace this for waveform
plt.subplot(221)
#Replace this for spectrogram
plt.subplot(223)
#Add this before colorbar
plt.subplot(122)
New version of plot:
EDIT: There is another possibility that I am also OK with (or perhaps both, for good measure!)
Here is an example of colorbar based on one of the answers in matplotlib-2-subplots-1-colorbar. The parameter pad in fig.colorbar is used to specify the space between the plots and the colorbar, and aspect is used to specify the aspect ratio between the height and width of the colorbar. Specgram outputs the image as the 4th output parameter, so I'm using that for the colorbar.
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-');
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
axcb = fig.colorbar(im, ax=axs.ravel().tolist(), pad=0.04, aspect = 30)
It is important to notice that when fig.colorbar function is called using the ax parameter, the original plots will be resized to make room for the colorbar. If it is only applied to one of the plots, only that axis will be resized. Se below:
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
axcb = fig.colorbar(im, ax=axs[1], pad=0.04, aspect = 30)
Below it is shown a way of controlling the resizing of your original axes in order to make room for a colorbar using fig.colorbar with the cax parameter that will not resize further your original plots. This approach requires to manually make some room for your colorbar specifying the right parameter inside the function fig.subplots_adjust :
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
fig.subplots_adjust(right=0.85) # making some room for cbar
# getting the lower left (x0,y0) and upper right (x1,y1) corners:
[[x10,y10],[x11,y11]] = axs[1].get_position().get_points()
pad = 0.01; width = 0.02
cbar_ax = fig.add_axes([x11+pad, y10, width, y11-y10])
axcb = fig.colorbar(im, cax=cbar_ax)
And doing the same to span two rows by reading coordinates of the original two plots:
fig,axs = matplotlib.pyplot.subplots(ncols=1, nrows=2 )
N=1000; fs=10e3
x = np.sin(np.arange(N))+np.random.random(N)
spectrum, freqs, t, im = axs[1].specgram(x,Fs=fs,
cmap=matplotlib.cm.inferno,noverlap=255)
axs[0].plot(np.arange(0,N)/fs,x,'-')
axs[0].set_xlim(t[0],t[-1]);axs[1].set_xlim(t[0],t[-1])
fig.subplots_adjust(right=0.85) # making some room for cbar
# getting the lower left (x0,y0) and upper right (x1,y1) corners:
[[x00,y00],[x01,y01]] = axs[0].get_position().get_points()
[[x10,y10],[x11,y11]] = axs[1].get_position().get_points()
pad = 0.01; width = 0.02
cbar_ax = fig.add_axes([x11+pad, y10, width, y01-y10])
axcb = fig.colorbar(im, cax=cbar_ax)
The best solution I came up with is subplot2grid() function. This requies the use of subplots, which I was not using originally. Following this method, I needed to change everything from using plt (matplotlib.pyplot) to using the axes for the given plot for each .plot() or .specgram() invocation. The relevant changes are included here:
#No rows or columns need to be specified, because this is handled within a the `subplot2grid()` details
fig, axes = plt.subplots(figsize=(13.6, 7.68))
#Setup for waveform
ax1 = plt.subplot2grid((2, 60), (0, 0), rowspan=1, colspan=56)
####WAVEFORM PLOTTING
#Setup for spectrogram
ax2 = plt.subplot2grid((2, 60), (1, 0), rowspan=1, colspan=56)
####SPECTROGRAM PLOTTING
#Setup for colorbar
ax3 = plt.subplot2grid((2, 60), (0, 59), rowspan=1, colspan=1)
cbar = plt.colorbar(cax, cax=ax3, ax=ax2)
And a MWE bringing it all together:
import matplotlib as mpl
mpl.use("TkAgg")
from scipy.io import wavfile
from matplotlib import mlab
from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
from numpy.lib import stride_tricks
samplerate, data = wavfile.read('FILENAME.wav')
times = np.arange(len(data))/float(samplerate)
plt.close("all")
fig, axes = plt.subplots(figsize=(13.6, 7.68))#nrows=2, ncols=2,
gs = gridspec.GridSpec(2, 60)
####
#Waveform
####
ax1 = plt.subplot2grid((2, 60), (0, 0), rowspan=1, colspan=56)
ax1.plot(times, data, color='k')
ax1.xaxis.set_ticks_position('none')
ax1.yaxis.set_ticks_position('none')
####
#Spectrogram
####
maxFrequency = 5000
Fs = maxFrequency*2.#10000.
NFFT = min(512, len(data))
noverlap = NFFT / 2
pad_to = NFFT * 16
dynamicRange = 27.5
vmin = 20*np.log10(np.max(data)) - dynamicRange
cmap = plt.get_cmap('inferno')
ax2 = plt.subplot2grid((2, 60), (1, 0), rowspan=1, colspan=56)
Pxx, freqs, times, cax = ax2.specgram(data, NFFT=NFFT, Fs=samplerate, noverlap=noverlap, mode='magnitude', scale='dB', vmin=vmin, pad_to=pad_to, cmap=cmap)
ax2.set_ylim([0, maxFrequency])
ax2.xaxis.set_ticks_position('none')
ax2.yaxis.set_ticks_position('none')
####
#Colorbar (for spectrogram)
####
ax3 = plt.subplot2grid((2, 60), (1, 59), rowspan=1, colspan=1)
cbar = plt.colorbar(cax, cax=ax3, ax=ax2)
cbar.ax.yaxis.set_tick_params(pad=3, left='off', right='off', labelleft='on', labelright='off')
plt.show()
Here's an example of the output from this MWE:
Best part! You need only change the 0 to 1 and the rowspan to be 1 in this line (i.e. :)
ax3 = plt.subplot2grid((2, 60), (1, 59), rowspan=1, colspan=1)
to make the colorbar span only the height of the spectrogram. Meaning that changing between the two options is incredibly simple. Here's an example of the output from this change:
EDIT: GridSpec actually was unused, and so I edited it out. The only relevant details that I needed involved calling subplot2grid() to set up the subplots.
How to use python and matplotlib to plot a picture like following?
I know how to plot the 2D heat map, but it frustrated me a lot with plotting the bar on top of the heat map, and the bar between the color bar and heat map.
How to add those two bars on the picture, and show the number in x axis or y axis belongs to which group?
Thanks very much for all the responses.
A systematic and straightforward approach, although a bit more cumbersome at the start, is to use matplotlib.gridspec.GridSpec.
First set up the grid:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure()
gs = GridSpec(2, 3, width_ratios=[10, 1, 1], height_ratios=[1, 10])
This gives us a grid of 2 rows and 3 columns, where the lower left axis will be 10x10 and the other axes will be either 10x1 or 1x10 in relative sizes. These ratios can be tweaked to your liking. Note that the top center/right axes will be empty.
big_ax = fig.add_subplot(gs[1,0]) # bottom left
top_ax = fig.add_subplot(gs[0,0]) # top left
right_ax = fig.add_subplot(gs[1,1]) # bottom center
cbar_ax = fig.add_subplot(gs[1,2]) # bottom right
I will use a generic genome picture I found via google for the top and right image:
and will generate a random heatmap. I use imshow(aspect='auto') so that the image objects and heatmap take up the full space of their respective axes (otherwise they will override the height/width ratios set by gridspec).
im = plt.imread('/path/to/image.png')
# Plot your heatmap on big_ax and colorbar on cbar_ax
heatmap = big_ax.imshow(np.random.rand(10, 10), aspect='auto', origin='lower')
cbar = fig.colorbar(heatmap, cax=cbar_ax)
# Show your images on top_ax and right_ax
top_ax.imshow(im, aspect='auto')
# need to rotate my image.
# you may not have to if you have two different images
from scipy import ndimage
right_ax.imshow(ndimage.rotate(im, 90), aspect='auto')
# Clean up the image axes (remove ticks, etc.)
right_ax.set_axis_off()
top_ax.set_axis_off()
# remove spacing between axes
fig.subplots_adjust(wspace=0.05, hspace=0.05)
It's not super glamorous (especially with the default jet colormap), but you could easily use this to reproduce the figure your OP.
Edit: So if you want to generate that genome-like plot on the top and right, you could try something like this for the top bar:
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
# draw the black line
top_ax.axhline(0, color='k', zorder=-1)
# box x-coords and text labels
boxes = zip(np.arange(0.1, 1, 0.2), np.arange(0.2, 1, 0.2))
box_text = ('A1', 'B1', 'B2', 'A2')
# color indicators for boxes
colors = (0, 1, 1, 0)
# construct Rects
patches = [Rectangle(xy=(x0, -1), width=(x1-x0), height=2) for x0,x1 in boxes]
p = PatchCollection(patches, cmap='jet')
# this maps the colors in [0,1] to the cmap above
p.set_array(np.array(colors))
top_ax.add_collection(p)
# add text
[top_ax.text((x0+x1)/2., 1.2, text, ha='center')
for (x0,x1), text in zip(boxes, box_text)]
# adjust ylims
top_ax.set_ylim(-2, 2)
For something the right axis, you can do the same thing but use axvline and swap the x-coords for y-coords.
right_ax.axvline(0, color='k', zorder=-1)
patches = [Rectangle(xy=(-1, y0), width=2, height=(y1-y0)) for y0, y1 in boxes]
p = PatchCollection(patches, cmap='jet')
p.set_array(np.array(colors))
right_ax.add_collection(p)
[right_ax.text(1.2, (y0+y1)/2., text, va='center')
for (y0, y1), text in zip(boxes, box_text)]
right_ax.set_xlim(-2,2)
These modifications lead to something like: