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.
Related
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]:
A quite basic question about ticks' labels for x and y-axis. According to this code
fig, axes = plt.subplots(6,12, figsize=(50, 24), constrained_layout=True, sharex=True , sharey=True)
fig.subplots_adjust(hspace = .5, wspace=.5)
custom_xlim = (-1, 1)
custom_ylim = (-0.2,0.2)
for i in range(72):
x_data = ctheta[i]
y_data = phi[i]
y_err = err_phi[i]
ax = fig.add_subplot(6, 12, i+1)
ax.plot(x_data_new, bspl(x_data_new))
ax.axis('off')
ax.errorbar(x_data,y_data, yerr=y_err, fmt="o")
ax.set_xlim(custom_xlim)
ax.set_ylim(custom_ylim)
I get the following output:
With y labels for plots on the first column and x labels for theone along the last line, although I call them off.
Any idea?
As #BigBen wrote in their comment, your issue is caused by you adding axes to your figure twice, once via fig, axes = plt.subplots() and then once again within your loop via fig.add_subplot(). As a result, the first set of axes is still visible even after you applied .axis('off') to the second set.
Instead of the latter, you could change your loop to:
for i in range(6):
for j in range(12):
ax = axes[i,j] # these are the axes created via plt.subplots(6,12,...)
ax.axis('off')
# … your other code here
Believe it or not I need help with formatting the title of the legend (not the title of the plot) in a simple plot. I am plotting two series of data (X1 and X2) against Y in a twiny() plot.
I call matplotlib.lines to construct lines for the legend and then call plt.legend to construct a legend pass text strings to name/explain the lines, format that text and place the legend. I could also pass a title-string to plt.legend but I cannot format it.
The closest I have come to a solution is to create another 'artist' for the title using .legend()set_title and then format the title text. I assign it to a variable and call the variable in the above mentioned plt.legend. This does not result in an error nor does it produce the desired effect. I have no control over the placement of the title.
I have read through a number of S-O postings and answers on legend-related issues, looked at the MPL docs, various tutorial type web-pages and even taken a peak at a GIT-hub issue (#10391). Presumably the answer to my question is somewhere in there but not in a format that I have been able to successfully implement.
#Imports
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import numpy as np
import seaborn as sns
plt.style.use('seaborn')
#Some made up data
y = np.arange(0, 1200, 100)
x1 = (np.log(y+1))
x2 = (2.2*x1)
#Plot figure
fig = plt.figure(figsize = (12, 14))
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()
sy1, sy2 = 'b-', 'r-'
tp, bm = 0, 1100
red_ticks = np.arange(0, 11, 2)
ax1.plot(x1, y, sy1)
ax1.set_ylim(tp, bm)
ax1.set_xlim(0, 10)
ax1.set_ylabel('Distance (m)')
ax1.set_xlabel('Area')
ax1.set_xticks(red_ticks)
blue_ticks = np.arange(0, 22, 4)
ax2.plot(x2, y, sy2)
ax2.set_xlim(0, 20)
ax2.set_xlabel('Volume')
ax2.set_xticks(blue_ticks)
ax2.grid(False)
x1_line = mlines.Line2D([], [], color='blue')
x2_line = mlines.Line2D([], [], color='red')
leg = ax1.legend().set_title('Format Legend Title ?',
prop = {'size': 'large',
'family':'serif',
'style':'italic'})
plt.legend([x1_line, x2_line], ['Blue Model', 'Red Model'],
title = leg,
prop ={'size':12,
'family':'serif',
'style':'italic'},
bbox_to_anchor = (.32, .92))
So what I want is a simple way to control the formatting of both the legend-title and legend-text in a single artist, and also have control over the placement of said legend.
The above code returns a "No handles with labels found to put in legend."
You need one single legend. You can set the title of that legend (not some other legend); then style it to your liking.
leg = ax2.legend([x1_line, x2_line], ['Blue Model', 'Red Model'],
prop ={'size':12, 'family':'serif', 'style':'italic'},
bbox_to_anchor = (.32, .92))
leg.set_title('Format Legend Title ?', prop = {'size': 24, 'family':'sans-serif'})
Unrelated, but also important: Note that you have two figures in your code. You should remove one of them.
I am contouring some 2D data but want to have a continuous color bar instead of a discrete. How do I get this? Check out my code and output.
I have tried plotting without providing color levels, different color maps but does not help.
ylim = [0, 8]
xlim = [starttime, endtime]
fig = plt.figure(figsize=(10,7))
ax = plt.subplot()
levels1 = np.arange(5,61,5)
cmap=plt.get_cmap('jet')
Zplot=ax.contourf(timesNew, hgtNew, ZNew, levels1, cmap=cmap,
vmin=min(levels1), vmax=max(levels1), extend='both')
cbZ=plt.colorbar(Zplot, ticks=levels1)
niceDates = []
for timestamp in np.arange(ax.get_xlim()[0],ax.get_xlim()[1]+sec,sec):
niceDates.append(str(datetime.datetime.utcfromtimestamp(timestamp).strftime("%H")))
ax.set_ylim(ylim)
ax.set_xlim(xlim)
ax.set_xticks(np.arange(ax.get_xlim()[0],ax.get_xlim()[1]+sec,sec))
ax.set_xticklabels([])
ax.set_xticklabels(niceDates) #plot nice dates
ax.set_ylabel('Height (km)', fontsize=labelsize)
ax.set_xlabel('Time (UTC)', fontsize=labelsize)
The code works and plots nicely but the color bar is discrete and I want it to be continuous. What am I doing wrong? The data is in 2D numpy arrays. I have done some extensive processing to the data so not showing the full code other than the plotting.
As far as I know there is no way to get a real continuous colorbar. Try adding a lot more levels. This way it will look more like a continuous colorbar.
ylim = [0, 8]
xlim = [starttime, endtime]
fig = plt.figure(figsize=(10,7))
ax = plt.subplot()
levels1 = np.linspace(5,61,500)
level_ticks = np.arange(5, 61, 5)
cmap=plt.get_cmap('jet')
Zplot=ax.contourf(timesNew, hgtNew, ZNew, levels1, cmap=cmap,
vmin=min(levels1), vmax=max(levels1), extend='both')
cbZ=plt.colorbar(Zplot, ticks=level_ticks)
niceDates = []
for timestamp in np.arange(ax.get_xlim()[0],ax.get_xlim()[1]+sec,sec):
niceDates.append(str(datetime.datetime.utcfromtimestamp(timestamp).strftime("%H")))
ax.set_ylim(ylim)
ax.set_xlim(xlim)
ax.set_xticks(np.arange(ax.get_xlim()[0],ax.get_xlim()[1]+sec,sec))
ax.set_xticklabels([])
ax.set_xticklabels(niceDates) #plot nice dates
ax.set_ylabel('Height (km)', fontsize=labelsize)
ax.set_xlabel('Time (UTC)', fontsize=labelsize)
I'm writing a function that modifies the axes size and position on a figure, but when comes twin axes it makes a problem:
import matplotlib.pyplot as plt
def fig_layout(fig, vspace = 0.3): # function to make space at the bottom for legend box and
#+ other text input
for ax in ~~~fig.axes~~~: # Here 'fig.axes' is not right, I need to find the exact syntax
#+ I need to put
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * vspace,
box.width, box.height * (1 - vspace)])
x = np.arange(10)
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
n = 3
line = {}
for i in range(3):
line['lines'].append(ax1.plot(x, i*x**2))
line['labels'].append(r'$y = %i \cdot x^2$'%i)
ax1.set_title('example plot')
ax2 = ax1.twinx()
line['lines'].append(ax2.plot(x, x^-1, label = r'$y = x^-1$'))
line['labels'].append(r'$y = x^-1$')
leg = ax1.legend(line['lines'], line['labels'])
fig_layout(fig)
# I will put the legend box at the bottom of the axes with another function.
plt.show()
I think you can use fig.get_axes().
For example, to modify the title of the first sub-plot, you can do:
plt.gcf().get_axes()[0].set_title("example plot")