python: How to plot and put annotation at a natural position - python

I am using python to plot and my codes are:
import matplotlib.pyplot as plt
import numpy as np
# these are the data to be plot
x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
x_test = ['grid50', 'grid100', 'grid150', 'grid250', 'grid500', 'grid750', 'NN5', 'NN10', 'NN15', 'NN20', 'NN50', 'NN100', 'CB', 'CBG']
clf = [0.58502, 0.60799, 0.60342, 0.59629, 0.56464, 0.53757, 0.62567, 0.63429, 0.63583, 0.63239, 0.63315, 0.63156, 0.60630, 0.52755]
hitrate = [0.80544, 0.89422, 0.94029, 0.98379, 0.99413, 0.99921, 0.99478, 0.99961, 0.99997, 0.99980, 0.99899, 0.99991, 0.88435, 1.0]
level = [23.04527, 9.90955, 4.35757, 1.46438, 0.51277, 0.15071, 1.30057, 0.00016, 0.00001, 0.00021, 0.00005, 0.00004, 6.38019, 0]
fig = plt.figure(figsize=(20,7))
ax = fig.add_subplot(111)
fig.subplots_adjust(right=0.8)
# this is the function to put annotation on bars
def autolabel(rects):
# attach some text labels
for ii,rect in enumerate(rects):
height = rect.get_height()
plt. text(rect.get_x()+rect.get_width()/2., 1.02*height, '%s'% (clf[ii]),ha='center', va='bottom')
plt.xticks(x,x_test)
# this part is to plot the red bar charts
ins1 = ax.bar(x,clf,color='Red', align='center',label='classification results')
ax.set_ylabel('classification results', color='Red')
ax.tick_params(axis='y',colors='Red')
ax.set_ylim(0,1.5)
autolabel(ins1)
# this part is to plot the green hitrate and the for-loop is to put annotation next to the line
ax2 = ax.twinx()
ins2, = ax2.plot(x,hitrate,marker='o',color='Green', linewidth=3.0, label='hitrate')
ax2.set_ylabel('hitrate', color='Green')
ax2.tick_params(axis='y',colors='Green')
ax2.set_ylim(0,1.5)
for i,j in zip(x, hitrate):
ax2.annotate(str(j),xy=(i,j+0.02))
# this part is to plot the blue level, forloop same as that of hitrate
ax3 = ax.twinx()
axes = [ax, ax2, ax3]
ax3.spines['right'].set_position(('axes', 1.1))
ax3.set_frame_on(True)
ax3.patch.set_visible(False)
ins3, = ax3.plot(x,level,marker='^', color='Blue', linewidth=3.0, label='obfuscation level')
ax3.set_ylabel('obfuscation level', color='Blue')
ax3.tick_params(axis='y',colors='Blue')
ax3.set_ylim(0,25)
for i,j in zip(x, level):
ax3.annotate(str(j),xy=(i,j+0.02))
ax.set_xlabel('Cell Configurations')
ax.set_xlim(0,15)
ax.set_title('benchmark')
ax.legend([ins1,ins2,ins3],['clf', 'hit', 'level'])
plt.grid()
plt.show()
And I got a figure like :
The problem is that, some numbers are not put in a good place so to be read clearly, but I don't know whether there is a method to put the annotation naturally at a blank area. Any ideas?

Related

Scatterplot with hollow and filled points with matplotlib

I would like to reproduce the scatterplot below. Here is the code I have so far, but I cannot seem to get the points similar to the seed terms to be the same color as the filled points (seed terms). Any help is appreciated.
Also, I cannot figure out why the first word is the color white, even though I used a specific palette?
import pandas as pd
import numpy as np
import matplotlib
seed_terms = ['clean', 'recovery', 'spiral', 'tolerance', 'program']
embeddings_ex = np.random.rand(5, 10, 2)
embeddings_ex = np.array(embeddings_ex)
words_ex = [['quit', 'finally', 'pill', 'vomit', 'survive' ,'lil', 'chance' ,'chain', 'zero',
'quickly'],
['bullshit' ,'unrelated', 'everywhere', 'appear' ,'probably' ,'deal',
'mistake', 'window', 'comment', 'honest'],
['majority' ,'familiar', 'queer', 'edgy', 'skin', 'withdrawl' ,'sad', 'develop',
'perfectly', 'daughter'],
['snort', 'cheap', 'brain', 'teach' ,'shoot' ,'inject' ,'freak', 'type', 'black',
'absolute'],
['substitution', 'suboxone', 'country' ,'clinic', 'nerve', 'representation',
'2', 'website' ,'youtuber', 'insane']]
words_ex = np.array(words_ex)
fig, ax = plt.subplots(figsize=(16, 9))
sc = embeddings_ex[:, :, 0].flatten()
sw = embeddings_ex[:, :, 1].flatten()
plt.scatter(sc, sw, s=45, marker='o', alpha=0.2, color="none", edgecolors='k')
# annotate(ax, sc, sw, words, size=11)
# fill points that are seed words and make font bold
# Okabe and Ito color palette
colors = ['#FA4D4D', '#FBC93D', '#E37E3B', '#C13BE3', '#4B42FD', '#D55E00', '#CC79A7']
for i, word in enumerate(seed_terms):
plt.scatter(sc[i], sw[i], marker='o', alpha=.9,
color=colors[i], edgecolors='none', s = 100)
plt.annotate(word, alpha=.5, xy=(sc[i], sw[i]), xytext=(
5, 2), textcoords='offset points', ha='right', va='bottom', size=11)
# annotate similar words
for word in seed_terms:
# get the index of the seed word in the list of seed words
idx = seed_terms.index(word)
# get the x and y coordinates of the seed word
x = embeddings_ex[idx, :, 0].flatten()
y = embeddings_ex[idx, :, 1].flatten()
# get the list of similar words
similar_words = words_ex[idx]
# add annotations with smaller font
annotate(ax, x, y, similar_words, size=6)
# legend
plt.legend(seed_terms, loc=4)
plt.grid(False)
# remove axes and frame
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['bottom'].set_visible(False)
plt.gca().spines['left'].set_visible(False)
# ticks
plt.tick_params(axis='both', which='both', bottom=False,
left=False, labelbottom=False, labelleft=False)
The word groups related to the central keyword are taken from the five arrays in a list and are about to be annotated, but since the related word groups are a list, a loop process is required to add scattering and annotation. One thing to be careful of in this method is the order in which the scatter and annotations are drawn. First we need to draw the gray scatter plot, then the scatter and annotations for the related terms, and finally the scatter and annotations for the central terms. The reason is that everything is drawn with the same coordinate data, so the hollow markers will be overwritten after the fill. The attached image controls the overlapping of the annotations, which I assume cannot be achieved with matplotlib alone, so perhaps some other tool is being introduced.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(20230115)
seed_terms = ['clean', 'recovery', 'spiral', 'tolerance', 'program']
embeddings_ex = np.random.rand(5, 10, 2)
embeddings_ex = np.array(embeddings_ex)
words_ex = [['quit', 'finally', 'pill', 'vomit', 'survive' ,'lil', 'chance' ,'chain', 'zero', 'quickly'],
['bullshit' ,'unrelated', 'everywhere', 'appear' ,'probably' ,'deal', 'mistake', 'window', 'comment', 'honest'],
['majority' ,'familiar', 'queer', 'edgy', 'skin', 'withdrawl' ,'sad', 'develop', 'perfectly', 'daughter'],
['snort', 'cheap', 'brain', 'teach' ,'shoot' ,'inject' ,'freak', 'type', 'black', 'absolute'],
['substitution', 'suboxone', 'country' ,'clinic', 'nerve', 'representation', '2', 'website' ,'youtuber', 'insane']]
words_ex = np.array(words_ex)
fig, ax = plt.subplots(figsize=(16, 9))
sc = embeddings_ex[:, :, 0].flatten()
sw = embeddings_ex[:, :, 1].flatten()
plt.scatter(sc, sw, s=45, marker='o', alpha=0.2, color="none", edgecolors='k')
# fill points that are seed words and make font bold
# Okabe and Ito color palette
colors = ['#FA4D4D', '#FBC93D', '#E37E3B', '#C13BE3', '#4B42FD', '#D55E00', '#CC79A7']
# annotate similar words
for word in seed_terms:
# get the index of the seed word in the list of seed words
idx = seed_terms.index(word)
# get the x and y coordinates of the seed word
x = embeddings_ex[idx, :, 0].flatten()
y = embeddings_ex[idx, :, 1].flatten()
# get the list of similar words
similar_words = words_ex[idx]
# add annotations with smaller font
for w,xx,yy in zip(similar_words, x,y):
plt.scatter(xx, yy, s=45, marker='o', color='white', edgecolors=colors[idx])
plt.annotate(w, xy=(xx, yy), xytext=(5,2), textcoords='offset points', ha='right',va='bottom', size=6)
for i, word in enumerate(seed_terms):
plt.scatter(sc[i], sw[i], marker='o', alpha=.9, color=colors[i], edgecolors='none', s=100, label=word)
plt.annotate(word, alpha=.5, xy=(sc[i], sw[i]), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom', size=11)
# legend
plt.legend(loc=4)
plt.grid(False)
# remove axes and frame
plt.gca().spines[:].set_visible(False)
# ticks
plt.tick_params(axis='both', which='both', bottom=False,
left=False, labelbottom=False, labelleft=False)
plt.show()

Plot 2 images side by side for each for loop

I'm training a KNN model and I want to plot 2 images per for loop, as shown in the imagen below:
What I need
At the left, I plot the boundary visualization of my model for a certain amoung of neighbours. At the right, I plot the confusion matrix.
To accomplish something along those lines I've written the following code:
fig = plt.figure()
for i in range(1,3):
neigh = KNeighborsClassifier(n_neighbors=i)
neigh.fit(X, y)
y_pred = neigh.predict(X)
acc = accuracy_score(y_pred,y)
# Boundary
ax1 = fig.add_subplot(1,2,1)
visualize_classifier(neigh, X, y, ax=ax1) # Defined by me
# Plot confusion matrix. Defined by sklearn.metrics
ax2 = fig.add_subplot(1,2,2)
plot_confusion_matrix(neigh, X, y, cmap=plt.cm.Blues, values_format = '.0f',ax=ax2)
ax1.set_title(f'Neighbors = {i}.\nAccuracy = {acc:.4f}',
fontsize = 14)
ax2.set_title(f'Neighbors = {i}.\nAccuracy = {acc:.4f}',
fontsize = 14)
plt.tight_layout()
plt.figure(i)
plt.show()
The visualize_classifier() function:
def visualize_classifier(model, X, y, ax=None, cmap='Dark2'):
ax = ax or plt.gca()
# Plot the training points
ax.scatter(X.iloc[:, 0], X.iloc[:, 1], c=y, s=30, cmap=cmap, # Changed to iloc.
clim=(y.min(), y.max()), zorder=3, alpha = 0.5)
ax.axis('tight')
ax.set_xlabel('x1')
ax.set_ylabel('x2')
# ax.axis('off')
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xx, yy = np.meshgrid(np.linspace(*xlim, num=200),
np.linspace(*ylim, num=200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
# Create a color plot with the results
n_classes = len(np.unique(y))
contours = ax.contourf(xx, yy, Z, alpha=0.3,
levels=np.arange(n_classes + 1) - 0.5,
cmap=cmap, clim=(y.min(), y.max()),
zorder=1)
ax.set(xlim=xlim, ylim=ylim)
What I get
What I get. Continues...
As you can see, only the first loop is plotted. the second one is not plotted and I can't figure out why.
Furthermore, I have the same title for the plot at the right and at the left. I would like to have only one on top of both, how can this be accomplished?
Now, you might be wondering why do I need to do this and the answer is that I would like to see how the boundaries change depending on the number of neighbors. It's just to get a visual sense of KNN algorithm.
Any suggestion would be pretty much appreciated.
I was able to make it work. What I had wrong was the first line inside the for loop. I assigned plt.figure(i, figsize=(18, 8)) to the variable fig.
for i in range(1,30):
fig = plt.figure(i, figsize=(18, 8))
sns.set(font_scale=2.0) # Adjust to fit
neigh = KNeighborsClassifier(n_neighbors=i)
neigh.fit(X, y)
y_pred = neigh.predict(X)
acc = accuracy_score(y_pred,y)
# Boundary
ax1 = fig.add_subplot(1,2,1)
visualize_classifier(neigh, X, y, ax=ax1) # Defined by me
# Plot confusion matrix. Defined by sklearn.metrics
ax2 = fig.add_subplot(1,2,2)
plot_confusion_matrix(neigh, X, y, cmap=plt.cm.Blues, values_format = '.0f',ax=ax2)
fig.suptitle(f'Neighbors = {i}. Accuracy = {acc:.4f}',y=1)
plt.show()
For the title I used: fig.suptitle(f'Neighbors = {i}. Accuracy = {acc:.4f}',y=1)

Draw lines connecting points between two separate one-D plots

As title, I am working on time-series alignment, and a visualization of the alignment result is desired.
To this end, I want to draw lines connecting "anchor points" generated by the alignment algorithm.
np.random.seed(5)
x = np.random.rand(10) # time-series 1
y = np.random.rand(20) # time-series 2
ap = np.array(([0, 4, 9], # the anchor points
[0, 9, 19]))
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
ax1.plot(x, 'r')
ax2.plot(y, 'g')
the anchor points ap in the example specify the one-to-one "mapping" between the indices of two time series x and y, i.e., x[0] is corresponding to y[0]; x[4] to y[9]; and x[9] to y[19]. The goal is to draw lines between two separate plot to show the result of the alignment.
To connect two subplots in matplotlib you may use a ConnectionPatch.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
np.random.seed(5)
x = np.random.rand(21) # time-series 1
y = np.random.rand(21) # time-series 2
ap = np.array(([0, 5, 10], # the anchor points
[0,10, 20]))
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
ax1.plot(x, 'r')
ax2.plot(y, 'g')
ls = ["-","--"]
c = ["gold", "blue"]
for i, row in enumerate(ap):
for j, ind in enumerate(row):
px = (ind, x[ind])
py = (ind, y[ind])
con = ConnectionPatch(py,px, coordsA="data", coordsB="data",
axesA=ax2, axesB=ax1, linestyle=ls[i], color=c[i])
ax2.add_artist(con)
plt.show()
Thanks to #ImportanceOfBeingErnest, I identified the typo in the OP and achieved connecting indices between two series of different length:
np.random.seed(5)
x = np.random.rand(10)
y = np.random.rand(20)
ap = np.array(([0, 4, 9],
[0,9, 19]))
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212, sharex=ax1)
ax1.plot(x, 'r')
ax2.plot(y, 'g')
plt.setp(ax1.get_xticklabels(), visible=False)
for j in ap.T:
ax1.axvline(x=j[0], linestyle='--', color='k')
ax2.axvline(x=j[1], linestyle='--', color='k')
x_ind = (j[0], ax1.get_ylim()[0])
y_ind = (j[1], ax2.get_ylim()[1])
con = ConnectionPatch(y_ind, x_ind, coordsA="data", coordsB="data",
axesA=ax2, axesB=ax1, linewidth='1.5')
ax2.add_artist(con)
I know it is off the topic, but how to further truncate the blank part in order to make the range of x-axis fit the signal length, while maintain the actual ratio of the length of the two signals? Though sharex=ax1 shows the ratio of signal length, the blank part on the right of the top figure is annoying.

Python Matplotlib Multi-color Legend Entry

I would like to make a legend entry in a matplotlib look something like this:
It has multiple colors for a given legend item. Code is shown below which outputs a red rectangle. I'm wondering what I need to do to overlay one color ontop of another? Or is there a better solution?
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
red_patch = mpatches.Patch(color='red', label='Foo')
plt.legend(handles=[red_patch])
plt.show()
The solution I am proposing is to combine two different proxy-artists for one entry legend, as described here: Combine two Pyplot patches for legend.
The strategy is then to set the fillstyle of the first square marker to left while the other one is set to right (see http://matplotlib.org/1.3.0/examples/pylab_examples/filledmarker_demo.html). Two different colours can then be attributed to each marker in order to produce the desired two-colour legend entry.
The code below show how this can be done. Note that the numpoints=1 argument in plt.legend is important in order to display only one marker for each entry.
import matplotlib.pyplot as plt
plt.close('all')
#---- Generate a Figure ----
fig = plt.figure(figsize=(4, 4))
ax = fig.add_axes([0.15, 0.15, 0.75, 0.75])
ax.axis([0, 1, 0, 1])
#---- Define First Legend Entry ----
m1, = ax.plot([], [], c='red' , marker='s', markersize=20,
fillstyle='left', linestyle='none')
m2, = ax.plot([], [], c='blue' , marker='s', markersize=20,
fillstyle='right', linestyle='none')
#---- Define Second Legend Entry ----
m3, = ax.plot([], [], c='cyan' , marker='s', markersize=20,
fillstyle='left', linestyle='none')
m4, = ax.plot([], [], c='magenta' , marker='s', markersize=20,
fillstyle='right', linestyle='none')
#---- Plot Legend ----
ax.legend(((m2, m1), (m3, m4)), ('Foo', 'Foo2'), numpoints=1, labelspacing=2,
loc='center', fontsize=16)
plt.show(block=False)
Which results in:
Disclaimer: This will only work for a two-colors legend entry. If more than two colours is desired, I cannot think of any other way to do this other than the approach described by #jwinterm (Python Matplotlib Multi-color Legend Entry)
Perhaps another hack to handle more than two patches. Make sure you order the handles/labels according to the number of columns:
from matplotlib.patches import Patch
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
pa1 = Patch(facecolor='red', edgecolor='black')
pa2 = Patch(facecolor='blue', edgecolor='black')
pa3 = Patch(facecolor='green', edgecolor='black')
#
pb1 = Patch(facecolor='pink', edgecolor='black')
pb2 = Patch(facecolor='orange', edgecolor='black')
pb3 = Patch(facecolor='purple', edgecolor='black')
ax.legend(handles=[pa1, pb1, pa2, pb2, pa3, pb3],
labels=['', '', '', '', 'First', 'Second'],
ncol=3, handletextpad=0.5, handlelength=1.0, columnspacing=-0.5,
loc='center', fontsize=16)
plt.show()
which results in:
I absolutely loved #raphael's answer.
Here is a version with circles. Furthermore, I've refactored and trimmed the code a bit to make it more modular.
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
class MulticolorCircles:
"""
For different shapes, override the ``get_patch`` method, and add the new
class to the handler map, e.g. via
ax_r.legend(ax_r_handles, ax_r_labels, handlelength=CONF.LEGEND_ICON_SIZE,
borderpad=1.2, labelspacing=1.2,
handler_map={MulticolorCircles: MulticolorHandler})
"""
def __init__(self, face_colors, edge_colors=None, face_alpha=1,
radius_factor=1):
"""
"""
assert 0 <= face_alpha <= 1, f"Invalid face_alpha: {face_alpha}"
assert radius_factor > 0, "radius_factor must be positive"
self.rad_factor = radius_factor
self.fc = [mcolors.colorConverter.to_rgba(fc, alpha=face_alpha)
for fc in face_colors]
self.ec = edge_colors
if edge_colors is None:
self.ec = ["none" for _ in self.fc]
self.N = len(self.fc)
def get_patch(self, width, height, idx, fc, ec):
"""
"""
w_chunk = width / self.N
radius = min(w_chunk / 2, height) * self.rad_factor
xy = (w_chunk * idx + radius, radius)
patch = plt.Circle(xy, radius, facecolor=fc, edgecolor=ec)
return patch
def __call__(self, width, height):
"""
"""
patches = []
for i, (fc, ec) in enumerate(zip(self.fc, self.ec)):
patch = self.get_patch(width, height, i, fc, ec)
patches.append(patch)
result = PatchCollection(patches, match_original=True)
#
return result
class MulticolorHandler:
"""
"""
#staticmethod
def legend_artist(legend, orig_handle, fontsize, handlebox):
"""
"""
width, height = handlebox.width, handlebox.height
patch = orig_handle(width, height)
handlebox.add_artist(patch)
return patch
Sample usage and image, note that some of the legend handles have radius_factor=0.5 because the true size would be too small.
ax_handles, ax_labels = ax.get_legend_handles_labels()
ax_labels.append(AUDIOSET_LABEL)
ax_handles.append(MulticolorCircles([AUDIOSET_COLOR],
face_alpha=LEGEND_SHADOW_ALPHA))
ax_labels.append(FRAUNHOFER_LABEL)
ax_handles.append(MulticolorCircles([FRAUNHOFER_COLOR],
face_alpha=LEGEND_SHADOW_ALPHA))
ax_labels.append(TRAIN_SOURCE_NORMAL_LABEL)
ax_handles.append(MulticolorCircles(SHADOW_COLORS["source"],
face_alpha=LEGEND_SHADOW_ALPHA))
ax_labels.append(TRAIN_TARGET_NORMAL_LABEL)
ax_handles.append(MulticolorCircles(SHADOW_COLORS["target"],
face_alpha=LEGEND_SHADOW_ALPHA))
ax_labels.append(TEST_SOURCE_ANOMALY_LABEL)
ax_handles.append(MulticolorCircles(DOT_COLORS["anomaly_source"],
radius_factor=LEGEND_DOT_RATIO))
ax_labels.append(TEST_TARGET_ANOMALY_LABEL)
ax_handles.append(MulticolorCircles(DOT_COLORS["anomaly_target"],
radius_factor=LEGEND_DOT_RATIO))
#
ax.legend(ax_handles, ax_labels, handlelength=LEGEND_ICON_SIZE,
borderpad=1.1, labelspacing=1.1,
handler_map={MulticolorCircles: MulticolorHandler})
There is in fact a proper way to do this by implementing a custom
legend handler as explained in the matplotlib-doc under "implementing a custom legend handler" (here):
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
# define an object that will be used by the legend
class MulticolorPatch(object):
def __init__(self, colors):
self.colors = colors
# define a handler for the MulticolorPatch object
class MulticolorPatchHandler(object):
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
width, height = handlebox.width, handlebox.height
patches = []
for i, c in enumerate(orig_handle.colors):
patches.append(plt.Rectangle([width/len(orig_handle.colors) * i - handlebox.xdescent,
-handlebox.ydescent],
width / len(orig_handle.colors),
height,
facecolor=c,
edgecolor='none'))
patch = PatchCollection(patches,match_original=True)
handlebox.add_artist(patch)
return patch
# ------ choose some colors
colors1 = ['g', 'b', 'c', 'm', 'y']
colors2 = ['k', 'r', 'k', 'r', 'k', 'r']
# ------ create a dummy-plot (just to show that it works)
f, ax = plt.subplots()
ax.plot([1,2,3,4,5], [1,4.5,2,5.5,3], c='g', lw=0.5, ls='--',
label='... just a line')
ax.scatter(range(len(colors1)), range(len(colors1)), c=colors1)
ax.scatter([range(len(colors2))], [.5]*len(colors2), c=colors2, s=50)
# ------ get the legend-entries that are already attached to the axis
h, l = ax.get_legend_handles_labels()
# ------ append the multicolor legend patches
h.append(MulticolorPatch(colors1))
l.append("a nice multicolor legend patch")
h.append(MulticolorPatch(colors2))
l.append("and another one")
# ------ create the legend
f.legend(h, l, loc='upper left',
handler_map={MulticolorPatch: MulticolorPatchHandler()},
bbox_to_anchor=(.125,.875))
Probably not exactly what you're looking for, but you can do it (very) manually by placing patches and text yourself on the plot. For instance:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
red_patch = mpatches.Patch(color='red', label='Foo')
plt.legend(handles=[red_patch])
r1 = mpatches.Rectangle((0.1, 0.1), 0.18, 0.1, fill=False)
r2 = mpatches.Rectangle((0.12, 0.12), 0.03, 0.06, fill=True, color='red')
r3 = mpatches.Rectangle((0.15, 0.12), 0.03, 0.06, fill=True, color='blue')
ax.add_patch(r1)
ax.add_patch(r2)
ax.add_patch(r3)
ax.annotate('Foo', (0.2, 0.13), fontsize='x-large')
plt.show()

Laying out several plots in matplotlib + numpy

I am pretty new to python and want to plot a dataset using a histogram and a heatmap below. However, I am a bit confused about
How to put a title above both plots and
How to insert some text into bots plots
How to reference the upper and the lower plot
For my first task I used the title instruction, which inserted a caption in between both plots instead of putting it above both plots
For my second task I used the figtext instruction. However, I could not see the text anywhere in the plot. I played a bit with the x, y and fontsize parameters without any success.
Here is my code:
def drawHeatmap(xDim, yDim, plot, threshold, verbose):
global heatmapList
stableCells = 0
print("\n[I] - Plotting Heatmaps ...")
for currentHeatmap in heatmapList:
if -1 in heatmapList[currentHeatmap]:
continue
print("[I] - Plotting heatmap for PUF instance", currentHeatmap,"(",len(heatmapList[currentHeatmap])," values)")
# Convert data to ndarray
#floatMap = list(map(float, currentHeatmap[1]))
myArray = np.array(heatmapList[currentHeatmap]).reshape(xDim,yDim)
# Setup two plots per page
fig, ax = plt.subplots(2)
# Histogram
weights = np.ones_like(heatmapList[currentHeatmap]) / len(heatmapList[currentHeatmap])
hist, bins = np.histogram(heatmapList[currentHeatmap], bins=50, weights=weights)
width = 0.7 * (bins[1] - bins[0])
center = (bins[:-1] + bins[1:]) / 2
ax[0].bar(center, hist, align='center', width=width)
stableCells = calcPercentageStable(threshold, verbose)
plt.figtext(100,100,"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", fontsize=40)
heatmap = ax[1].pcolor(myArray, cmap=plt.cm.Blues, alpha=0.8, vmin=0, vmax=1)
cbar = fig.colorbar(heatmap, shrink=0.8, aspect=10, fraction=.1,pad=.01)
#cbar.ax.tick_params(labelsize=40)
for y in range(myArray.shape[0]):
for x in range(myArray.shape[1]):
plt.text(x + 0.5, y + 0.5, '%.2f' % myArray[y, x],
horizontalalignment='center',
verticalalignment='center',
fontsize=(xDim/yDim)*5
)
#fig = plt.figure()
fig = matplotlib.pyplot.gcf()
fig.set_size_inches(60.5,55.5)
plt.savefig(dataDirectory+"/"+currentHeatmap+".pdf", dpi=800, papertype="a3", format="pdf")
#plt.title("Heatmap for PUF instance "+str(currentHeatmap[0][0])+" ("+str(numberOfMeasurements)+" measurements; "+str(sizeOfMeasurements)+" bytes)")
if plot:
plt.show()
print("\t[I] - Done ...")
And here is my current output:
Perhaps this example will make things easier to understand. Things to note are:
Use fig.suptitle to add a title to the top of a figure.
Use ax[i].text(x, y, str) to add text to an Axes object
Each Axes object, ax[i] in your case, holds all the information about a single plot. Use them instead of calling plt, which only really works well with one subplot per figure or to modify all subplots at once. For example, instead of calling plt.figtext, call ax[0].text to add text to the top plot.
Try following the example code below, or at least read through it to get a better idea how to use your ax list.
import numpy as np
import matplotlib.pyplot as plt
histogram_data = np.random.rand(1000)
heatmap_data = np.random.rand(10, 100)
# Set up figure and axes
fig = plt.figure()
fig.suptitle("These are my two plots")
top_ax = fig.add_subplot(211) #2 rows, 1 col, 1st plot
bot_ax = fig.add_subplot(212) #2 rows, 1 col, 2nd plot
# This is the same as doing 'fig, (top_ax, bot_ax) = plt.subplots(2)'
# Histogram
weights = np.ones_like(histogram_data) / histogram_data.shape[0]
hist, bins = np.histogram(histogram_data, bins=50, weights=weights)
width = 0.7 * (bins[1] - bins[0])
center = (bins[:-1] + bins[1:]) / 2
# Use top_ax to modify anything with the histogram plot
top_ax.bar(center, hist, align='center', width=width)
# ax.text(x, y, str). Make sure x,y are within your plot bounds ((0, 1), (0, .5))
top_ax.text(0.5, 0.5, "Here is text on the top plot", color='r')
# Heatmap
heatmap_params = {'cmap':plt.cm.Blues, 'alpha':0.8, 'vmin':0, 'vmax':1}
# Use bot_ax to modify anything with the heatmap plot
heatmap = bot_ax.pcolor(heatmap_data, **heatmap_params)
cbar = fig.colorbar(heatmap, shrink=0.8, aspect=10, fraction=.1,pad=.01)
# See how it looks
plt.show()

Categories

Resources