I have created a figure that displays a shape and table using matplotlib. The problem is how its produced. They overlap each other. The shape is to scale so I don't want to alter it. I was wondering how I can alter the overall size of the plot or move the position of the table.
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots(figsize = (10,6))
ax.axis('equal')
plt.style.use('ggplot')
ax.grid(False)
xy = 0,0
circle = mpl.patches.Circle(xy, 160, lw = 3, edgecolor = 'black', color = 'b', alpha = 0.1, zorder = 5)
ax.add_patch(circle)
col_labels=['A','B','C','D','E']
row_labels=['diff','total']
table_vals=[['','','','',''],['','','','','']]
the_table = plt.table(cellText=table_vals,
colWidths = [0.05]*5,
rowLabels=row_labels,
colLabels=col_labels,
bbox = [0.8, 0.4, 0.2, 0.2])
ax.autoscale()
plt.show()
Add the bbox argument with your table. (instead of loc)
the_table = plt.table(cellText=table_vals,
colWidths = [0.05]*5,
rowLabels=row_labels,
colLabels=col_labels,
bbox = [0.2, 0.4, 0.4, 0.02])
The bbox argument takes 4 inputs: X, Y, Width, Height. Thus X and Y are the coordinates of the bottom left corner. Above, the height was far too small.
EDIT: Create room to play with
The idea is to make the ax smaller in the same manner.
box = ax.get_position()
a.set_position([box.x0, box.y0, box.width * 0.9, box.height])
EDIT 2: Trying to put the table on the right. As I said, you need to play with the box values, took me about 10 tries to get this. I'm using spyder as an IDE, so it's really fast.
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots(figsize = (10,6))
ax.axis('equal')
plt.style.use('ggplot')
ax.grid(False)
xy = 0,0
circle = mpl.patches.Circle(xy, 160, lw = 3, edgecolor = 'black', color = 'b', alpha = 0.1, zorder = 5)
ax.add_patch(circle)
col_labels=['A','B','C','D','E']
row_labels=['diff','total']
table_vals=[['','','','',''],['','','','','']]
the_table = plt.table(cellText=table_vals,
colWidths = [0.05]*5,
rowLabels=row_labels,
colLabels=col_labels,
bbox = [1.1, 0.5, 0.35, 0.1])
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
ax.autoscale()
plt.show()
Output:
Position the table outside the axes
You may use loc="right" to position the table right of the axes. Something like fig.subplots_adjust(right=0.8) will leave enough space for it.
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('ggplot')
fig, ax = plt.subplots(figsize = (10,6))
fig.subplots_adjust(right=0.8)
ax.axis('equal')
ax.grid(False)
xy = 0,0
circle = mpl.patches.Circle(xy, 160, lw = 3, edgecolor = 'black',
facecolor = 'b', alpha = 0.1, zorder = 5)
ax.add_patch(circle)
col_labels=['A','B','C','D','E']
row_labels=['diff','total']
table_vals=[['','','','',''],['','','','','']]
the_table = plt.table(cellText=table_vals,
colWidths = [0.05]*5,
rowLabels=row_labels,
colLabels=col_labels,
loc='right', zorder=3)
ax.autoscale()
plt.show()
Put the table in its own axes
You may put the table in a new axes next to the existing one. The advantage is that there is no need to then play with the column width or subplot parameters.
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('ggplot')
fig, (ax, ax_table) = plt.subplots(ncols=2, figsize = (10,6),
gridspec_kw=dict(width_ratios=[3,1]))
ax.axis('equal')
ax_table.axis("off")
ax.grid(False)
xy = 0,0
circle = mpl.patches.Circle(xy, 160, lw = 3, edgecolor = 'black',
facecolor = 'b', alpha = 0.1, zorder = 5)
ax.add_patch(circle)
col_labels=['A','B','C','D','E']
row_labels=['diff','total']
table_vals=[['','','','',''],['','','','','']]
the_table = ax_table.table(cellText=table_vals,
rowLabels=row_labels,
colLabels=col_labels,
loc='center')
ax.autoscale()
plt.show()
Related
I'm trying to plot to confusion matrix in the same image, but they come out in different sizes.
Here's the code:
fig, ax = plt.subplots(nrows=1, ncols=2, figsize = (18,8))
fig.suptitle('Matriz de Confusão')
skplt.metrics.plot_confusion_matrix(y_test, y_pred_log, normalize=True, ax=ax[0], title=('Regressão Logística'))
skplt.metrics.plot_confusion_matrix(y_test, y_pred_tree, normalize=True, ax=ax[1], title=('Árvore de decisão'))
ax[0].xaxis.set_ticklabels(['Normal', 'Fraude']); ax[0].yaxis.set_ticklabels(['Normal', 'Fraude']);
ax[1].xaxis.set_ticklabels(['Normal', 'Fraude']); ax[1].yaxis.set_ticklabels(['Normal', 'Fraude']);
plt.show()
And this is what I'm getting:
How can I change the size of the second plot?
Also if I could delete the extra color bar would be nice.
You sould define the axes where colorbar need to be placed. You can check this answer as a reference.
Applying those concept to your case would result in something similar to this:
import matplotlib.pyplot as plt
import numpy as np
M1 = np.random.rand(2, 2)
M2 = np.random.rand(2, 2)
fig, ax = plt.subplots(1, 2, figsize = (18, 8))
plt.subplots_adjust(right = 0.77)
cbar_ax_1 = fig.add_axes([0.8, 0.1, 0.04, 0.8])
cbar_ax_2 = fig.add_axes([0.9, 0.1, 0.04, 0.8])
im_1 = ax[0].imshow(M1, cmap = 'magma')
im_2 = ax[1].imshow(M2, cmap = 'magma')
plt.colorbar(im_1, cax = cbar_ax_1)
plt.colorbar(im_2, cax = cbar_ax_2)
plt.show()
If you want one colorbar only, it is wiser to normalize the unique colorbar based on both matrices' values:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import Normalize
from matplotlib import cm
M1 = np.random.rand(2, 2)
M2 = np.random.rand(2, 2)
fig, ax = plt.subplots(1, 2, figsize = (18, 8))
plt.subplots_adjust(right = 0.87)
cbar_ax = fig.add_axes([0.9, 0.1, 0.04, 0.8])
norm = Normalize(vmin = min(np.min(M1), np.min(M2)), vmax = max(np.max(M1), np.max(M2)))
cmap = cm.magma
im_1 = ax[0].imshow(M1, cmap = cmap)
im_2 = ax[1].imshow(M2, cmap = cmap)
plt.colorbar(cm.ScalarMappable(norm = norm, cmap = cmap), cax = cbar_ax)
plt.show()
This is my code:
import numpy as np
import matplotlib.pyplot as plt
def plot_graph():
fig = plt.figure()
data = [[top3_empsearch, top5_empsearch, top7_empsearch], [top3_elastic, top5_elastic, top7_elastic]]
X = np.arange(3)
ax = fig.add_axes([0, 0, 1, 1])
ax.bar(X + 0.00, data[0], color='b', width=0.25)
ax.bar(X + 0.25, data[1], color='g', width=0.25)
ax.set_ylabel('Accuracy (in %)')
plt.title('Percentage accuracy for selected result in Top-3, Top-5, Top-7 in employee search vs elastic search')
plt.yticks(np.arange(0, 101, 10))
colors = {'empsearch':'blue', 'elastic':'green'}
labels = list(colors.keys())
handles = [plt.Rectangle((0,0),1,1, color=colors[label]) for label in labels]
plt.legend(handles, labels)
plt.style.use('dark_background')
plt.show()
plot_graph()
The outcome of this code is ->
No ticks, no labels, no title nothing is visible and I'm bamboozled. Will appreciate the help.
The only problem is in this line:
ax = fig.add_axes([0, 0, 1, 1])
Looking to the bibliography (https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.figure.Figure.html), you will see that the first parameter of add_axes() function is "rect", which refers to the the dimensions [left, bottom, width, height] of the new axes, all quantitie in fractions of figure width and height. So in your code you are giving exactly the dimensions of the figure, so the title, ticks, labels... are there but hidden. So you have to leave some space, reducing a bit the plot's dimensions. You could do it just by modifying:
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
Alternatively, you could replace that line by:
ax = fig.add_subplot(1,1,1)
and the result should be the same.
Here is my result:
I would like to set the colorbar of my plot to a custom height, not necessarily to match the size of the plot. In fact I would like the height of the colorbar PLUS the title on top of it to match the height of the figure.
With
ax3 = divider.append_axes('right', size='10%', pad=0.3)
cb = plt.colorbar(Q, cax=ax3, ticks=[0.0, 3.0, 6.0, 9.0, 12.0, 15.0], format='%.1f')
I managed to have a colorbar with the same height as the plot, which has been asked for many other times, now I would like to shrink it.
Following suggestion provided in other questions I decided to explicitly give the colorbar its own axes with add_axes, after getting the position of the last plot axes with get_position. Here is what I'm trying to do. There are no data and no colorbar in this example, just to show that I'm not getting the result I expected:
from __future__ import unicode_literals
import numpy as np
from scipy.interpolate import griddata
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib.pylab import cm
import matplotlib.colors as colors
from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
matplotlib.rcParams.update({'font.size': 8})
fig = plt.figure()
fig.set_size_inches(6.3,6.3)
ax1 = plt.subplot(111)
divider = make_axes_locatable(ax1)
ax2 = divider.append_axes('right', size='100%', pad=0.3)
axes = [ax1, ax2]
ltypes = ['dashed', 'solid']
xi = np.linspace(-18.125, 18.125, 11)
yi = np.linspace(0, 28, 9)
xv, yv = np.meshgrid(xi, yi)
xcOdd = 0.2
zcOdd = 0.725
xcEven = 0.6
zcEven = 0.725
maskRadius = 0.15
for i in range(2):
ax = axes[i]
ax.set_xlabel('distance [m]')
if i == 0:
ax.set_ylabel('depth [m]')
if i == 1:
ax.set_yticklabels([])
ax.invert_yaxis()
ax.tick_params(direction='in')
ax.set_aspect('equal')
odd = Circle((xcOdd, zcOdd), .15, linewidth=1.2, color='k', fill=False)
even = Circle((xcEven, zcEven), .15, linewidth=1.2, linestyle=ltypes[i], color='k', fill=False)
vmax = 15.
vmin = 0.
norm = matplotlib.colors.Normalize(vmin,vmax, clip=False)
color_map = matplotlib.colors.ListedColormap(plt.cm.Greys(np.linspace(0.25, 1, 5)), "name")
ax.add_patch(odd)
pad = 0.03
width = 0.03
pos = ax2.get_position()
ax3 = fig.add_axes([pos.xmax + pad, pos.ymin, width, 0.7*(pos.ymax-pos.ymin) ])
plt.savefig('prova-vect-paper-test-2.eps', format='eps')
Why is get_position returning the wrong boundingbox?
You need to draw the canvas before obtaining the actual position from .get_position(). This is because due to the equal aspect ratio, the axes changes size and position at draw time.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from mpl_toolkits.axes_grid1 import make_axes_locatable
matplotlib.rcParams.update({'font.size': 8})
fig = plt.figure()
fig.set_size_inches(6.3,6.3)
ax1 = plt.subplot(111)
divider = make_axes_locatable(ax1)
ax2 = divider.append_axes('right', size='100%', pad=0.3)
axes = [ax1, ax2]
xi = np.linspace(-18.125, 18.125, 11)
yi = np.linspace(0, 28, 9)
xv, yv = np.meshgrid(xi, yi)
for i in range(2):
ax = axes[i]
ax.set_xlabel('distance [m]')
if i == 0:
ax.set_ylabel('depth [m]')
if i == 1:
ax.set_yticklabels([])
ax.invert_yaxis()
ax.tick_params(direction='in')
ax.set_aspect('equal')
vmax = 15.
vmin = 0.
norm = colors.Normalize(vmin,vmax, clip=False)
color_map = colors.ListedColormap(plt.cm.Greys(np.linspace(0.25, 1, 5)), "name")
im = ax.imshow(yv, cmap=color_map, norm=norm)
pad = 0.03
width = 0.03
fig.canvas.draw()
pos = ax2.get_position()
ax3 = fig.add_axes([pos.xmax + pad, pos.ymin, width, 0.7*(pos.ymax-pos.ymin) ])
fig.colorbar(im, cax=ax3)
plt.show()
I'm a Matlab user recently converted to Python. Most of the Python skills I manage on my own, but with plotting I have hit the wall and need some help.
This is what I'm trying to do...
I need to make a figure that consists of 3 subplots with following properties:
subplot layout is 311, 312, 313
the height of 312 and 313 is approximately half of the 311
all subplots share common X axis
the space between the subplots is 0 (they touch each other at X axis)
By the way I know how to make all this, only not in a single figure. That is the problem I'm facing now.
For example, this is my ideal subplot layout:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.exp(-t)
s3 = s1*s2
fig = plt.figure()
ax1 = plt.subplot2grid((4,3), (0,0), colspan=3, rowspan=2)
ax2 = plt.subplot2grid((4,3), (2,0), colspan=3)
ax3 = plt.subplot2grid((4,3), (3,0), colspan=3)
ax1.plot(t,s1)
ax2.plot(t[:150],s2[:150])
ax3.plot(t[30:],s3[30:])
plt.tight_layout()
plt.show()
Notice how the x axis of different subplots is misaligned. I do not know how to align the x axis in this figure, but if I do something like this:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.exp(-t)
s3 = s1*s2
fig2, (ax1, ax2, ax3) = plt.subplots(nrows=3, ncols=1, sharex=True)
ax1.plot(t,s1)
ax2.plot(t[:150],s2[:150])
ax3.plot(t[30:],s3[30:])
plt.tight_layout()
plt.show()
Now the x axis is aligned between the subplots, but all subplots are the same size (which is not what I want)
Furthermore, I would like that the subplots are touching at x axis like this:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.exp(-t)
s3 = s1*s2
fig1 = plt.figure()
plt.subplots_adjust(hspace=0)
ax1 = plt.subplot(311)
ax2 = plt.subplot(312, sharex=ax1)
ax3 = plt.subplot(313, sharex=ax1)
ax1.plot(t,s1)
ax2.plot(t[:150],s2[:150])
ax3.plot(t[30:],s3[30:])
xticklabels = ax1.get_xticklabels()+ax2.get_xticklabels()
plt.setp(xticklabels, visible=False)
plt.show()
So to rephrase my question:
I would like to use
plt.subplot2grid(..., colspan=3, rowspan=2)
plt.subplots(..., sharex=True)
plt.subplots_adjust(hspace=0)
and
plt.tight_layout()
together in the same figure. How to do that?
Just specify sharex=ax1 when creating your second and third subplots.
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.exp(-t)
s3 = s1*s2
fig = plt.figure()
ax1 = plt.subplot2grid((4,3), (0,0), colspan=3, rowspan=2)
ax2 = plt.subplot2grid((4,3), (2,0), colspan=3, sharex=ax1)
ax3 = plt.subplot2grid((4,3), (3,0), colspan=3, sharex=ax1)
ax1.plot(t,s1)
ax2.plot(t[:150],s2[:150])
ax3.plot(t[30:],s3[30:])
fig.subplots_adjust(hspace=0)
for ax in [ax1, ax2]:
plt.setp(ax.get_xticklabels(), visible=False)
# The y-ticks will overlap with "hspace=0", so we'll hide the bottom tick
ax.set_yticks(ax.get_yticks()[1:])
plt.show()
If you still what to use fig.tight_layout(), you'll need to call it before fig.subplots_adjust(hspace=0). The reason for this is that tight_layout works by automatically calculating parameters for subplots_adjust and then calling it, so if subplots_adjust is manually called first, anything in the first call to it will be overridden by tight_layout.
E.g.
fig.tight_layout()
fig.subplots_adjust(hspace=0)
A possible solution is to manually create the axis using the add_axis method like shown here:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.exp(-t)
s3 = s1*s2
left, width = 0.1, 0.8
rect1 = [left, 0.5, width, 0.4]
rect2 = [left, 0.3, width, 0.15]
rect3 = [left, 0.1, width, 0.15]
fig = plt.figure()
ax1 = fig.add_axes(rect1) #left, bottom, width, height
ax2 = fig.add_axes(rect2, sharex=ax1)
ax3 = fig.add_axes(rect3, sharex=ax1)
ax1.plot(t,s1)
ax2.plot(t[:150],s2[:150])
ax3.plot(t[30:],s3[30:])
# hide labels
for label1,label2 in zip(ax1.get_xticklabels(),ax2.get_xticklabels()):
label1.set_visible(False)
label2.set_visible(False)
plt.show()
But this way you cannot use tight_layout as you explicitly define the size of each axis.
I'd like to create a colorbar legend for a heatmap, such that the labels are in the center of each discrete color. Example borrowed from here:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap
#discrete color scheme
cMap = ListedColormap(['white', 'green', 'blue','red'])
#data
np.random.seed(42)
data = np.random.rand(4, 4)
fig, ax = plt.subplots()
heatmap = ax.pcolor(data, cmap=cMap)
#legend
cbar = plt.colorbar(heatmap)
cbar.ax.set_yticklabels(['0','1','2','>3'])
cbar.set_label('# of contacts', rotation=270)
# put the major ticks at the middle of each cell
ax.set_xticks(np.arange(data.shape[1]) + 0.5, minor=False)
ax.set_yticks(np.arange(data.shape[0]) + 0.5, minor=False)
ax.invert_yaxis()
#labels
column_labels = list('ABCD')
row_labels = list('WXYZ')
ax.set_xticklabels(column_labels, minor=False)
ax.set_yticklabels(row_labels, minor=False)
plt.show()
This generates the following plot:
Ideally I'd like to generate a legend bar which has the four colors and for each color, a label in its center: 0,1,2,>3. How can this be achieved?
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap
#discrete color scheme
cMap = ListedColormap(['white', 'green', 'blue','red'])
#data
np.random.seed(42)
data = np.random.rand(4, 4)
fig, ax = plt.subplots()
heatmap = ax.pcolor(data, cmap=cMap)
#legend
cbar = plt.colorbar(heatmap)
cbar.ax.get_yaxis().set_ticks([])
for j, lab in enumerate(['$0$','$1$','$2$','$>3$']):
cbar.ax.text(.5, (2 * j + 1) / 8.0, lab, ha='center', va='center')
cbar.ax.get_yaxis().labelpad = 15
cbar.ax.set_ylabel('# of contacts', rotation=270)
# put the major ticks at the middle of each cell
ax.set_xticks(np.arange(data.shape[1]) + 0.5, minor=False)
ax.set_yticks(np.arange(data.shape[0]) + 0.5, minor=False)
ax.invert_yaxis()
#labels
column_labels = list('ABCD')
row_labels = list('WXYZ')
ax.set_xticklabels(column_labels, minor=False)
ax.set_yticklabels(row_labels, minor=False)
plt.show()
You were very close. Once you have a reference to the color bar axis, you can do what ever you want to it, including putting text labels in the middle. You might want to play with the formatting to make it more visible.
To add to tacaswell's answer, the colorbar() function has an optional cax input you can use to pass an axis on which the colorbar should be drawn. If you are using that input, you can directly set a label using that axis.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig, ax = plt.subplots()
heatmap = ax.imshow(data)
divider = make_axes_locatable(ax)
cax = divider.append_axes('bottom', size='10%', pad=0.6)
cb = fig.colorbar(heatmap, cax=cax, orientation='horizontal')
cax.set_xlabel('data label') # cax == cb.ax
This will make you add label and change colorbar's tick and label size:
clb=plt.colorbar()
clb.ax.tick_params(labelsize=8)
clb.ax.set_title('Your Label',fontsize=8)
This can be also used if you have sublots:
plt.tight_layout()
plt.subplots_adjust(bottom=0.05)
cax = plt.axes([0.1, 0, 0.8, 0.01]) #Left,bottom, length, width
clb=plt.colorbar(cax=cax,orientation="horizontal")
clb.ax.tick_params(labelsize=8)
clb.ax.set_title('Your Label',fontsize=8)