I have a grid of subplots and I would like to adjust the white space between only two of them such that the shared x labels are centred without overlapping either graph.
This question has a solution for when these are the only two subplots. However I'm struggling to adjust this to two specific subplots in a grid of many.
This code can be used to illustrate my problem.
In [1]
fig = plt.figure(figsize = (15, 10))
gs = fig.add_gridspec(2,4)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:3])
ax3 = fig.add_subplot(gs[0, 3])
ax4 = fig.add_subplot(gs[1, 0])
ax5 = fig.add_subplot(gs[1, 1])
ax6 = fig.add_subplot(gs[1, 2])
ax7 = fig.add_subplot(gs[1, 3])
np.random.seed(19680801)
# Example data
people = ('Really Really Long Name', 'Another Long Name', 'Short Name', 'Name', 'N')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
ax5.barh(y_pos, performance, align='center')
ax5.set_yticks(y_pos, labels=people)
ax5.invert_xaxis()
ax5.set_xlabel('Label')
ax5.set_title('Bar 1')
ax6.barh(y_pos, performance, align='center')
ax6.set_yticks(y_pos, labels=people)
ax6.set_xlabel('Label')
ax6.set_title('Bar 2')
Out [1]
If I apply the solution to the linked question here then every subplot's white space is effected. I know this is because it calls on fig.dpi_scale_trans which effects the whole figure but I'm new to transforms and can't work out what to use in its place
In [2]
fig.tight_layout()
fig.subplots_adjust(wspace=0.7)
plt.setp(axes[0].yaxis.get_majorticklabels(), ha='center')
# Create offset transform by some points in x direction
dx = 60 / 72.
dy = 0 / 72.
offset = mlb.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
# apply offset transform to all y ticklabels.
for label in ax6.yaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
Out [2]
I figured out how to solve this so posting my own answer in case anybody has a similar problem in the future.
This question and answer from 7 years ago contained the necessary help to solve the problem.
Essentially you must plot and position different GridSpecs in the figure using GridSpec from matplotlib.gridspec rather than calling one with fig.add_gridspec()
Link to GridSpec documentation
Following on from my example above I wanted to create a 2x4 grid. To do that we can plot the following grids in set positions of the figure:
Left: 1x2
Top Centre: 1x1
Bottom Centre: 2x1
Right: 1x2
In [1]:
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize = (15, 10))
# Example Data
people = ('Really Really Long Name', 'Another Long Name', 'Short Name', 'Name',
'N')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
# Left portion of grid (2x1).
# 'left' and 'right' tell the grid where it should start and finish
gs1 = GridSpec(2, 1)
gs1.update(left = 0, right = 0.2)
# Plotting empty subplots for illustration purposes
for i in gs1:
ax = plt.subplot(i)
# Mirroring on the right portion of the grid
gs2 = GridSpec(2, 1)
gs2.update(left = 0.8, right = 1)
for i in gs2:
ax = plt.subplot(i)
# Plotting in top center
# Note here we only need to plot a 1x1
gs3 = GridSpec(1, 1)
gs3.update(left = 0.25, right = 0.75, bottom = 0.53) #0.53 aligns with sides
ax3 = plt.subplot(gs3[0])
# Plotting the barh in the bottom center
# wsapce only adjusts this grid not the entire figure
gs4 = GridSpec(1, 2)
gs4.update(left = 0.2, right = 0.8, top = 0.45, wspace = 0.75)
# Left barh
ax5 = plt.subplot(gs4[0])
ax5.barh(y_pos, performance, align='center')
ax5.set_yticks([])
ax5.invert_xaxis()
ax5.set_xlabel('Label')
ax5.set_title('Bar 1')
# Right barh
ax6 = plt.subplot(gs4[1])
ax6.barh(y_pos, performance, align='center')
ax6.set_yticks(y_pos, labels=people)
ax6.set_xlabel('Label')
ax6.set_title('Bar 2')
plt.show()
Out [1]:
Related
I have made a 2x2 gridspec and trying to plot the catplot in the second row like so:
fig = plt.figure(figsize=(10,5), constrained_layout=True)
gs = GridSpec(nrows=2, ncols=2, figure=fig)
# Chart 1
ax1 = fig.add_subplot(gs[0,0])
ax1=sns.countplot(x='product', data = df) #Countplot
plt.title('Product sales', fontweight='bold', fontsize = 8)
plt.ylabel('Count', fontsize = 7)
plt.xlabel('Product', fontsize = 7)
# Chart 2
ax2 = fig.add_subplot(gs[0,1])
ax2= sns.countplot(x='maritalstatus', data = df) #Countplot
plt.title('Marital status of customers', fontweight='bold', fontsize = 8)
plt.ylabel('', fontsize = 7)
plt.xlabel('Marital status', fontsize = 7)
# chart 3
ax2 = fig.add_subplot(gs[1,:])
ax3 = sns.catplot(x = 'product', hue = "gender", col = "maritalstatus", data = df, kind = 'count')
plt.show()
But the second row is not plotted by the catplot, but appears below the blank graph.
Output:
Unfortunately, catplot is a figure-level interface, not an axes-level interface, so you cannot plot it in this way. This is a common problem with other figure-level interfaces such as displot, and the workaround I have found is to use the underlying components individually (for displot specifically it was kdeplot and histogram, for catplot you will have to peek into the source code).
You can tell which interfaces are figure or axes level by observing whether they accept the ax argument in their call. In your case you can explore the documentation to see which plots are supported and also the source code for implementation details.
I am using subplots side by side
plt.subplot(1, 2, 1)
# plot 1
plt.xlabel('MEM SET')
plt.ylabel('Memory Used')
plt.bar(inst_memory['MEMORY_SET_TYPE'], inst_memory['USED_MB'], alpha = 0.5, color = 'r')
# pol 2
plt.subplot(1, 2, 2)
plt.xlabel('MEM POOL')
plt.ylabel('Memory Used')
plt.bar(set_memory['POOL_TYPE'], set_memory['MEMORY_POOL_USED'], alpha = 0.5, color = 'g')
they have identical size - but is it possible to define the width for each subplot, so the right one could be wider as it has more entries and text would not squeeze or would it be possible to replace the bottom x-text by a number and have a legend with 1:means xx 2:means yyy
I find GridSpec helpful for subplot arrangements, see this demo at matplotlib.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import pandas as pd
N=24
inst_memory = pd.DataFrame({'MEMORY_SET_TYPE': np.random.randint(0,3,N),
'USED_MB': np.random.randint(0,1000,N)})
set_memory = pd.DataFrame({'MEMORY_POOL_USED': np.random.randint(0,1000,N),
'POOL_TYPE': np.random.randint(0,10,N)})
fig = plt.figure()
gs = GridSpec(1, 2, width_ratios=[1, 2],wspace=0.3)
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1])
ax1.bar(inst_memory['MEMORY_SET_TYPE'], inst_memory['USED_MB'], alpha = 0.5, color = 'r')
ax2.bar(set_memory['POOL_TYPE'], set_memory['MEMORY_POOL_USED'], alpha = 0.5, color = 'g')
You may need to adjust width_ratios and wspace to get the desired layout.
Also, rotating the text in x-axis might help, some info here.
The final goal I'm trying to achieve here is to have a figure saved as a .pdf with a certain size ([5,2] in the example code below), with no padding outside the axis labels/tick labels.
I usually achieve this by creating a figure using a combination of figsize and setting the padding to zero via tight_layout (I added the grey background color to show the edges/padding better):
fig = plt.figure(
figsize = [5,2],
tight_layout = {'pad': 0}
)
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2*np.pi*t)
plt.plot(t, s)
plt.savefig('figure.pdf', facecolor = (0.7,0.7,0.7))
This creates a nice pdf with size 5x2.
But I'm having trouble doing this with a figure where I'm using GridSpec to create subplots. The strange thing is that the problem is only apparent when setting a custom wspace for the GridSpec.
Example without wspace
fig, (ax0, ax1) = plt.subplots(
nrows = 1, ncols = 2,
gridspec_kw = {'width_ratios' : [3,2]},
tight_layout = {'pad': 0},
figsize = [5,2]
)
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2*np.pi*t)
ax0.plot(t, s)
ax1.plot(t, s)
ax1.yaxis.tick_right()
ax0.set_xlim([0, 2.25])
ax1.set_xlim([-0.25, 2])
plt.savefig('figure.pdf')
Example with wspace
I add some wspace to have some space between the subplots, since they are so close together in the example above
fig, (ax0, ax1) = plt.subplots(
nrows = 1, ncols = 2,
gridspec_kw = {'width_ratios' : [3,2], 'wspace' : 0.1},
tight_layout = {'pad': 0},
figsize = [5,2]
)
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2*np.pi*t)
ax0.plot(t, s)
ax1.plot(t, s)
ax1.yaxis.tick_right()
ax0.set_xlim([0, 2.25])
ax1.set_xlim([-0.25, 2])
plt.savefig('figure.pdf', facecolor = (0.7,0.7,0.7))
(the only change from above is the added wspace to the gridspec_kw dict)
This gives me an error in the savefig command
C:\Users\<username>\Anaconda3\lib\site-packages\matplotlib\figure.py:1744:
UserWarning: This figure includes Axes that are not compatible with
tight_layout, so its results might be incorrect.
warnings.warn("This figure includes Axes that are not "
# the warning is cut off here for some reason
and produces the following image, clearly not using tight_layout
Does anyone know of a way to get around this issue, or a better way to do what I'm trying?
If the aim is to save the image, you may leave out the tight_layout argument to the figure, and use the arguments bbox_inches='tight', pad_inches=0 when savng the figure:
plt.savefig('figure.pdf', bbox_inches='tight', pad_inches=0)
I want y-ticks on both sides(left & right), but with different labels at the same y points. I tried following, but I'm not able to position ticks at same location.
I'm newbie to matplotlib. I have gone through the matplotlib example, but couldn't figure it out the solution to my problem.
http://matplotlib.org/examples/pylab_examples/barchart_demo2.html
Greatly appreciate any suggestions.
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
groups = [ 1, 2, 3, 4, 5 ]
members = [ 1, 2, 3, 4 ]
colors = [ 'r', 'y', 'b', 'k']
#store score of members for the groups
scores = {member: 100*np.random.rand(len(groups)) for member in members}
group_cnt = group_cnt = sum([scores[member] for member in members])
print scores
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
width_bar = 0.5
width_space = 0.2
#position of barh
total_space = len(groups)*(len(members)*width_bar)+(len(groups)-1)*width_space
ind_space = len(members)*width_bar
step = ind_space/2.
#pos for labels
pos = np.arange(step, total_space+width_space, ind_space+width_space)
#pos for grin lines
minor_pos = np.arange(ind_space, total_space+width_space, ind_space+width_space)
for idx in xrange(len(members)):
ax.barh(pos-step+idx*width_bar, scores[members[idx]], width_bar, edgecolor='k', color=colors[idx], linewidth=3)
ax.invert_yaxis()
ax.set_yticks(pos)
ax.set_yticklabels(groups)
ax.set_yticks(minor_pos, minor=True)
ax.grid(True, which='minor')
ax.set_ylabel('Groups')
ax2 = ax.twinx()
ax2.set_ylabel('Group totals')
ax2.set_yticks(pos)
ax2.set_yticklabels(group_cnt)
ax2.invert_yaxis()
plt.show()
I think you got caught by a bit of trickery in that example. There is a plot([100, 100], [0, 5]) in the demo code which is doing a lot of non-obvious work (I am working on submitting a PR to improve the demo) in making sure that the ylimits are the same for both yaxis.
You just need to add a
ax2.set_ylim(ax.get_ylim())
before you call show.
You also have an un-related error ax2.set_yticklabels(group_cnt) -> ax2.set_yticklabels(groups).
[side note, generated PR #2327]
I have such a plot, and would like to add a the colorbar code (which color corresponds to what number) on the right hand below. I saw some example which where used for imshow not pie chart.
#!/usr/bin/env python
"""
http://matplotlib.sf.net/matplotlib.pylab.html#-pie for the docstring.
"""
from pylab import *
fracs = [33,33,33]
starting_angle = 90
axis('equal')
for item in range(9):
color_vals = [-1, 0, 1]
my_norm = matplotlib.colors.Normalize(-1, 1) # maps your data to the range [0, 1]
my_cmap = matplotlib.cm.get_cmap('RdBu') # can pick your color map
patches, texts, autotexts = pie(fracs, labels = None, autopct='%1.1f%%', startangle=90, colors=my_cmap(my_norm(color_vals)))
subplot(3,3,item+1)
fracs = [33,33,33]
starting_angle = 90
axis('equal')
patches, texts, autotexts = pie(fracs, labels = None, autopct='%1.1f%%', startangle=90, colors=my_cmap(my_norm(color_vals)))
for item in autotexts:
item.set_text("")
subplots_adjust(left=0.125, bottom=0.1, right=0.9, top=0.9, wspace=0.0, hspace=0.5)
savefig('/home/superiois/Downloads/projectx3/GRAIL/pie1.png')
show()
Also, it would be great if you tell me how to customize the size and location of colorbar code; Thanks.
Usually a legend is more appropriate for discrete values and a colorbar for continuous values. That said, its off course possible since mpl allows you to create a colorbar from scratch.
import matplotlib.pyplot as plt
import matplotlib as mpl
fracs = [33,33,33]
starting_angle = 90
fig, axs = plt.subplots(3,3, figsize=(6,6))
fig.subplots_adjust(hspace=0.1,wspace=0.0)
axs = axs.ravel()
for n in range(9):
color_vals = [-1, 0, 1]
my_norm = mpl.colors.Normalize(-1, 1) # maps your data to the range [0, 1]
my_cmap = mpl.cm.get_cmap('RdBu', len(color_vals)) # can pick your color map
patches, texts, autotexts = axs[n].pie(fracs, labels = None, autopct='%1.1f%%', startangle=90, colors=my_cmap(my_norm(color_vals)))
axs[n].set_aspect('equal')
for item in autotexts:
item.set_text("")
ax_cb = fig.add_axes([.9,.25,.03,.5])
cb = mpl.colorbar.ColorbarBase(ax_cb, cmap=my_cmap, norm=my_norm, ticks=color_vals)
cb.set_label('Some label [-]')
cb.set_ticklabels(['One', 'Two', 'Three'])
I have added custom ticklabels just to show how that would work, to get the default values simply remove the last line.