How to access second plot axis parameters? - matplotlib

I am trying to access the ax parameters of the second subplot figure, ax1. I am trying to put a title and remove the overlapping ticks but can't manage to get to them.
Here is the code and figure I have made :
fig, (ax0, ax1)= plt.subplots(nrows=1,
ncols=2,
sharey=True,
tight_layout = True,
gridspec_kw={'width_ratios': [3, 24],'wspace': 0})
ax1=librosa.display.specshow(data=df.iloc[i,2],
sr=fe,
x_axis='time',
y_axis='mel',
htk=False,
x_coords=np.linspace(0,duree[i],df.iloc[i,2].shape[1]),
hop_length=1000,
cmap=plt.get_cmap("magma"),
fmin=0, fmax=fe//2,
vmin=40, vmax=140)
ax0.plot(df.loc[Names[i], "DSP"], df.loc[Names[i], "f_dsp"],
linewidth=1, label=Names[i]) # ,color='grey')
ax0.set_title('Subplot 1')
ax0.set_xlim([20, 100])
ax0.set_ylim([0, fe//2])
ax0.set_ylabel('Fréquence [Hz]')
ax0.set_xlabel('Amplitude [dB ref 1µPa²]')
ax0.grid(visible=True)
ax0.grid(b=True, which='minor', color='k', linestyle=':', lw=0.5)
yticks=np.array([10,100,1000,10000,100000],dtype=int)
yticks_hz = np.unique(np.concatenate((np.array([fe//2],dtype=int),
np.array([250*2**n for n in range(0,int(np.log2(fe//(2*250))))]))))
ax0.set_yticks(yticks, minor=False)
ax0.set_yticks(yticks_hz, minor=True)
ax0.set_yticklabels(yticks,
minor=False,
fontdict={'fontweight':'demibold','fontsize':8})
ax0.set_yticklabels(yticks_hz,
minor=True,
fontdict={'fontsize':8})
# =============================================================================
# Doesnt work :(
# =============================================================================
ax1.set_title("Subplot 2")
ax1.get_yaxislabels().set_visible(False)
ax1.get_xaxislabels()[0].set_visible(False)

Related

How to access and remove all unwanted objects in a matplotlib figure manually?

I am trying to understand the underlying concepts of matplotlib, especially Axes and Figure. Therefore I am trying to plot two scatters and then remove any superfluous space (the red one below) by accessing different APIs & objects in the hierarchy.
Yet I fail to understand where the remaining red space is coming from. This is the code:
# Random data
df = pd.DataFrame(np.random.randint(0,100,size=(100, 2)), columns=list('AB'))
# Create a single Axes and preconfigure the figure with red facecolor.
# Then plot a scatter
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10,5), facecolor='r')
ax1 = df.plot(kind='scatter', x='A', y='B', ax=axes[0])
ax2 = df.plot(kind='scatter', x='B', y='A', ax=axes[1])
# Remove except the scatter
for a in [ax1, ax2]:
a.set_xlabel(''), a.set_ylabel('') # Remove x and y labels
for loc in ['left', 'right', 'bottom', 'top']:
a.spines[loc].set_visible(False) # Remove spines
a.set_xticks([], []), a.set_yticks([], []) # Remove ticks
a.set_xmargin(0), a.set_ymargin(0) # No margin beyond outer values
# On figure-level we can make it more tight
fig.tight_layout()
It produces the following figure:
I saw that there is something like..
a.set_axis_off()
.. but this doesn't seem to be the right solution. Somewhere there seems to be some kind of padding that remains. It doesn't look like it's from some X/Y axis as it's the same for all four edges in both subplots.
Any help appreciated.
Solution
Two things are needed:
First we need to initialize the Figure with frameon=False:
fig, axes = plt.subplots(
// ...
frameon=False)
The space between the subplots can be removed using the subplot layout:
plt.subplots_adjust(wspace=.0, hspace=.0)
For the finest level of layout control, you can position your axes manually instead of relying on matplotlib to do it for you. There are a couple of ways of doing this.
One option is Axes.set_position
# Random data
df = pd.DataFrame(np.random.randint(0,100,size=(100, 2)), columns=list('AB'))
# Create a pair of Axes and preconfigure the figure with red facecolor.
# Then plot a scatter
fig, axes = plt.subplots(1, 2, figsize=(10, 5), facecolor='r')
df.plot(kind='scatter', x='A', y='B', ax=axes[0]).set_position([0, 0, 0.5, 1])
df.plot(kind='scatter', x='B', y='A', ax=axes[1]).set_position([0, 0.5, 0.5, 1])
You could also use the old-fashioned Figure.add_axes method:
# Random data
df = pd.DataFrame(np.random.randint(0,100,size=(100, 2)), columns=list('AB'))
# Create a pair of Axes and preconfigure the figure with red facecolor.
# Then plot a scatter
fig = plt.figure(figsize=(10, 5), facecolor='r')
df.plot(kind='scatter', x='A', y='B', ax=fig.add_axes([0, 0, 0.5, 1]))
df.plot(kind='scatter', x='B', y='A', ax=fig.add_axes([0, 0.5, 0.5, 1]))

Plot circle at the title in matplotlib python

I have a 2 line title and first line has a number at the end of the line.
Can we plot a circle around the number?
Here is the code to generate the figure.
from matplotlib import rcParams
from matplotlib import pyplot as plt
import numpy as np
import os
rcParams.update({'figure.autolayout': True})
some_text = 'XXX'
any_number=15
title = '%s: %d\n YYY ZZZZ WWWWW' % (some_text,any_number)
fig = plt.figure(figsize=(8, 8), dpi=100)
plt.tick_params(axis='y', which='major', labelsize=60, width=3, length=10, pad=40)
plt.tick_params(axis='y', which='minor', labelsize=60, width=3, length=10, pad=40)
ax = plt.gca()
plt.title(title, fontsize=60, pad=40, loc='center', fontweight='semibold')
plt.style.use('ggplot')
ax.set_facecolor('white')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(True)
for edge_i in ['left']:
ax.spines[edge_i].set_edgecolor("black")
ax.spines[edge_i].set_linewidth(3)
ax.spines[edge_i].set_bounds(0, 1)
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
plt.yticks(np.arange(0, 1.01, step=0.2))
data_list= np.array([1,1,1,1,1,0.9, 0.8, 0.7, 0.8,0.85])
plt.bar(x, data_list, 0.9, color='indianred',edgecolor="black", linewidth=3,zorder=1)
plt.tick_params(
axis='x', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom=False, # ticks along the bottom edge are off
top=False, # ticks along the top edge are off
labelbottom=False) # labels along the bottom edge are off
figure_name = 'figure_with_circle.png'
figure_file = os.path.join('/Users/burcakotlu/Desktop',figure_name)
fig.savefig(figure_file, dpi=100, bbox_inches="tight")
plt.close(fig)
Here is the current figure and the wanted circle.
One could use the following without ax.bar():
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_title('title')
circle1 = plt.Circle((2,4.15), 0.2, color='k', clip_on=False, zorder=100, fill=False)
ax.add_patch(circle1)
ax.set_xlim(0,4)
ax.set_ylim(0,4)
plt.show()
I have found a way to plot circle together with bar plots without distorting bars. Here is the code below:
from matplotlib import rcParams
from matplotlib import pyplot as plt
import numpy as np
import os
import matplotlib.patches as patches
from matplotlib.offsetbox import AnchoredText
rcParams.update({'figure.autolayout': True})
some_text = 'XXX'
any_number=15
title = '%s: %d\n YYY ZZZZ WWWWW' % (some_text,any_number)
fig = plt.figure(figsize=(12,12), dpi=100)
plt.tick_params(axis='y', which='major', labelsize=60, width=3, length=10, pad=40)
plt.tick_params(axis='y', which='minor', labelsize=60, width=3, length=10, pad=40)
ax = plt.gca()
number_of_xxx = '12'
anchored_text_number_of_xxx = AnchoredText(number_of_xxx,
frameon=False, borderpad=0, pad=0.1,
loc='upper right',
bbox_to_anchor=[0.95, 1.3],
bbox_transform=plt.gca().transAxes,
prop={'fontsize': 60,
'fontweight': 'semibold'})
ax.add_artist(anchored_text_number_of_xxx)
circle1 = patches.Circle((0.88, 1.25), radius=0.1, transform=ax.transAxes, zorder=100, fill=False, color='gold', lw=8, clip_on=False)
ax.add_patch(circle1)
ax.set_title(title, fontsize=60, pad=40, loc='center', fontweight='semibold', zorder=50)
ax.set_facecolor('white')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(True)
for edge_i in ['left']:
ax.spines[edge_i].set_edgecolor("black")
ax.spines[edge_i].set_linewidth(3)
ax.spines[edge_i].set_bounds(0, 1)
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ax.set_yticks(np.arange(0, 1.01, step=0.2))
data_list= np.array([1,1,1,1,1,0.9, 0.8, 0.7, 0.8,0.85])
ax.bar(x, data_list, 0.9, color='indianred',edgecolor="black", linewidth=3,zorder=1)
ax.tick_params(
axis='x', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom=False, # ticks along the bottom edge are off
top=False, # ticks along the top edge are off
labelbottom=False) # labels along the bottom edge are off
figure_name = 'figure_with_circle.png'
figure_file = os.path.join('/Users/burcakotlu/Desktop',figure_name)
fig.savefig(figure_file, dpi=100, bbox_inches="tight")
plt.close(fig)

Not getting legend in figure/plot

I am sharing Y-axis in two subplots, with the following codes but both shared plots are missing legends in them.
projectDir = r'/media/DATA/banikr_D_drive/model/2021-04-28-01-18-15_5_fold_114sub'
logPath = os.path.join(projectDir, '2021-04-28-01-18-15_fold_1_Mixed_loss_dice.bin')
with open(logPath, 'rb') as pfile:
h = pickle.load(pfile)
print(h.keys())
fig, ax = plt.subplots(2, figsize=(20, 20), dpi=100)
ax[0].plot(h['dice_sub_train'], color='tab:cyan', linewidth=2.0, label="Train")
ax[0].plot(smooth_curve(h['dice_sub_train']), color='tab:purple')
ax[0].set_xlabel('Epoch/iterations', fontsize=20)
ax[0].set_ylabel('Dice Score', fontsize=20)
ax[0].legend(loc='lower right', fontsize=20)#, frameon=False)
ax1 = ax[0].twiny()
ax1.plot(h['dice_sub_valid'], color='tab:orange', linewidth=2.0, alpha=0.9, label="Validation" )
ax1.plot(smooth_curve(h['dice_sub_valid']), color='tab:red')
# , bbox_to_anchor = (0.816, 0.85)
ax[1].plot(h['loss_sub_train'], color='tab:cyan', linewidth=2.0, label="Train")
ax[1].plot(smooth_curve(h['loss_sub_train']), color='tab:purple')
ax2 = ax[1].twiny()
ax2.plot(h['loss_sub_valid'], color='tab:orange', linewidth=2.0, label="Validation", alpha=0.6)
ax2.plot(smooth_curve(h['loss_sub_valid']), color='tab:red')
ax[1].set_xlabel('Epoch/iterations', fontsize=20)
ax[1].set_ylabel('loss(a.u.)', fontsize=20)
ax[1].legend(loc='upper right', fontsize=20)
# ,bbox_to_anchor = (0.8, 0.9)
plt.suptitle('Subject wise dice score and loss', fontsize=30)
plt.setp(ax[0].get_xticklabels(), fontsize=20, fontweight="normal", horizontalalignment="center") #fontweight="bold"
plt.setp(ax[0].get_yticklabels(), fontsize=20, fontweight='normal', horizontalalignment="right")
plt.setp(ax[1].get_xticklabels(), fontsize=20, fontweight="normal", horizontalalignment="center")
plt.setp(ax[1].get_yticklabels(), fontsize=20, fontweight="normal", horizontalalignment="right")
plt.show()
Any idea how to solve the issue?
[1]: https://i.stack.imgur.com/kg7PY.png
ax1 has a twin y-axis with ax[0], but they are two separate axes. That's why ax[0].legend() does not know about the Validation line of ax1.
To have Train and Validation on the same legend, plot empty lines on the main axes ax[0] and ax[1] with the desired color and label. This will generate dummy Validation entries on the main legend:
...
ax[0].plot([], [], color='tab:orange', label="Validation")
ax[0].legend(loc='lower right', fontsize=20)
...
ax[1].plot([], [], color='tab:orange', label="Validation")
ax[1].legend(loc='upper right', fontsize=20)
...

How to change the position of some x axis tick labels on top of the bottom x axis in matplotlib?

This is my current script:
#!/usr/bin/env python3
import math
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
"""
Setup for a typical explanatory-style illustration style graph.
"""
h = 2
x = np.linspace(-np.pi, np.pi, 100)
y = 2 * np.sin(x)
rc = {
# Tick in the middle of the axis line.
'xtick.direction' : 'inout',
'ytick.direction' : 'inout',
# Bold is easier to read when we have few ticks.
'font.weight': 'bold',
'xtick.labelbottom': False,
'xtick.labeltop': True,
}
with plt.rc_context(rc):
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title(
'2 sin(x), not $\\sqrt{2\\pi}$',
# TODO make LaTeX part bold?
# https://stackoverflow.com/questions/14324477/bold-font-weight-for-latex-axes-label-in-matplotlib
fontweight='bold',
# Too close otherwise.
# https://stackoverflow.com/questions/16419670/increase-distance-between-title-and-plot-in-matplolib/56738085
pad=20
)
# Custom visible plot area.
# ax.set_xlim(-3, 3)
ax.set_ylim(-2.5, 2.5)
# Axes
# Axes on center:
# https://stackoverflow.com/questions/31556446/how-to-draw-axis-in-the-middle-of-the-figure
ax.spines['left'].set_position('zero')
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_visible(False)
# Axes with arrow:
# https://stackoverflow.com/questions/33737736/matplotlib-axis-arrow-tip
ax.plot(1, 0, ls="", marker=">", ms=10, color="k",
transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0, 1, ls="", marker="^", ms=10, color="k",
transform=ax.get_xaxis_transform(), clip_on=False)
# Ticks
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# Make ticks a bit longer.
ax.tick_params(width=1, length=10)
# Select tick positions
# https://stackoverflow.com/questions/12608788/changing-the-tick-frequency-on-x-or-y-axis-in-matplotlib
xticks = np.arange(math.ceil(min(x)), math.floor(max(x)) + 1, 1)
yticks = np.arange(math.ceil(min(y)) - 1, math.floor(max(y)) + 2, 1)
# Remove 0.
xticks = np.setdiff1d(xticks, [0])
yticks = np.setdiff1d(yticks, [0])
ax.xaxis.set_ticks(xticks)
ax.yaxis.set_ticks(yticks)
# Another approach. But because I want to be able to remove the 0,
# anyways, I just explicitly give all ticks instead.
# ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(1.0))
# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(1.0))
# Annotations.
ax.plot([0, np.pi/2], [h, h], '--r')
ax.plot([np.pi/2, np.pi/2], [h, 0], '--r')
ax.plot(np.pi/2, h, marker='o', linewidth=2, markersize=10,
markerfacecolor='w', markeredgewidth=1.5, markeredgecolor='black')
plt.savefig(
'main.png',
format='png',
bbox_inches='tight'
)
plt.clf()
And this is the output:
And this is what I want (hacked with GIMP), notice how the negative tick labels are on a different side of the axes now.
I tried adding:
for tick in ax.xaxis.get_majorticklabels():
tick.set_verticalalignment("bottom")
as shown in answers to: How to move a tick's label in matplotlib? but that does not move the tick labels up enough, and makes the labels show on top of the axes instead.
Tested on matplotlib 3.2.2.
The following code will adjust the vertical alignment of the ticks depending one whether they are at a negative or positive x-value. However that's not enough because the labels are actually anchored at the bottom of the tick line. I'm therefore adjusting their y-position a little bit, but you have to play with the value to get the desired output
# adjust the xticks so that they are on top when x<0 and on the bottom when x≥0
ax.spines['top'].set_visible(True)
ax.spines['top'].set_position('zero')
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_position('zero')
ax.xaxis.set_tick_params(which='both', top=True, labeltop=True,
bottom=True, labelbottom=True)
fig.canvas.draw()
for tick in ax.xaxis.get_major_ticks():
print(tick.get_loc())
if tick.get_loc()<0:
tick.tick1line.set_visible(False)
tick.label1.set_visible(False)
else:
tick.tick2line.set_visible(False)
tick.label2.set_visible(False)
full code:
import math
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
"""
Setup for a typical explanatory-style illustration style graph.
"""
h = 10
x = np.linspace(-np.pi, np.pi, 100)
y = h * np.sin(x)
rc = {
# Tick in the middle of the axis line.
'xtick.direction' : 'inout',
'ytick.direction' : 'inout',
# Bold is easier to read when we have few ticks.
'font.weight': 'bold',
'xtick.labelbottom': False,
'xtick.labeltop': True,
}
with plt.rc_context(rc):
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title(
'2 sin(x), not $\\sqrt{2\\pi}$',
# TODO make LaTeX part bold?
# https://stackoverflow.com/questions/14324477/bold-font-weight-for-latex-axes-label-in-matplotlib
fontweight='bold',
# Too close otherwise.
# https://stackoverflow.com/questions/16419670/increase-distance-between-title-and-plot-in-matplolib/56738085
pad=20
)
# Custom visible plot area.
# ax.set_xlim(-3, 3)
ax.set_ylim(-2.5, 2.5)
# Axes
# Axes on center:
# https://stackoverflow.com/questions/31556446/how-to-draw-axis-in-the-middle-of-the-figure
ax.spines['left'].set_position('zero')
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_visible(False)
# Axes with arrow:
# https://stackoverflow.com/questions/33737736/matplotlib-axis-arrow-tip
ax.plot(1, 0, ls="", marker=">", ms=10, color="k",
transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0, 1, ls="", marker="^", ms=10, color="k",
transform=ax.get_xaxis_transform(), clip_on=False)
# Ticks
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# Make ticks a bit longer.
ax.tick_params(width=1, length=10)
# Select tick positions
# https://stackoverflow.com/questions/12608788/changing-the-tick-frequency-on-x-or-y-axis-in-matplotlib
xticks = np.arange(math.ceil(min(x)), math.floor(max(x)) + 1, 1)
yticks = np.arange(math.ceil(min(y)) - 1, math.floor(max(y)) + 2, 1)
# Remove 0.
xticks = np.setdiff1d(xticks, [0])
yticks = np.setdiff1d(yticks, [0])
ax.xaxis.set_ticks(xticks)
ax.yaxis.set_ticks(yticks)
# Another approach. But because I want to be able to remove the 0,
# anyways, I just explicitly give all ticks instead.
# ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(1.0))
# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(1.0))
for g,t in zip(ax.get_xticks(),ax.get_xticklabels()):
if g<0:
t.set_va('bottom')
else:
t.set_va('top')
t.set_transform(ax.transData)
t.set_position((g,0.15*-(g/abs(g))))
# Annotations.
ax.plot([0, np.pi/2], [h, h], '--r')
ax.plot([np.pi/2, np.pi/2], [h, 0], '--r')
ax.plot(np.pi/2, h, marker='o', linewidth=2, markersize=10,
markerfacecolor='w', markeredgewidth=1.5, markeredgecolor='black')
# adjust the xticks so that they are on top when x<0 and on the bottom when x≥0
ax.spines['top'].set_visible(True)
ax.spines['top'].set_position('zero')
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_position('zero')
ax.xaxis.set_tick_params(which='both', top=True, labeltop=True,
bottom=True, labelbottom=True)
fig.canvas.draw()
for tick in ax.xaxis.get_major_ticks():
print(tick.get_loc())
if tick.get_loc()<0:
tick.tick1line.set_visible(False)
tick.label1.set_visible(False)
else:
tick.tick2line.set_visible(False)
tick.label2.set_visible(False)

Sorting out labels in subplots, created with fig.add_axes

I am new to python and am currently playing around with mathplotlib. Below is my code for the plot, shown on the bottom figure.
import matplotlib.pyplot as plt
f = plt.figure(figsize=(15, 15))
ax1 = f.add_axes([0.1, 0.5, 0.8, 0.5],
xticklabels=[])
ax2 = f.add_axes([0.1, 0.4, 0.8, 0.1])
ax1.plot(particles[0, :, 0])
ax1.plot(particles[1, :, 0])
ax2.plot(distances[:])
# Prettifying the plot
plt.xlabel("t", fontsize=25)
plt.tick_params( # modifying plot ticks
axis='x',
labelsize=20)
plt.ylabel("x", fontsize=25)
plt.tick_params( # modifying plot ticks
axis='y',
labelsize=20)
# Plot title
plt.title('Harmonic oscillator in ' + str(dim) + 'D with ' + str(num_step) + ' timesteps', fontsize=30)
# Saving the plot
#plt.savefig("results/2D_dif.png")
The two graphs have the dimensions and positions as I wish, but as you can see, the labels and the title are off. I wish to have the same label style, as was applied to the bottom plot, with the y-label of the upper plot reading "x", and the title "Harmonic oscillator ..." being on top of the first graph.
I thank you kindly for your help!
Here plt is acting on the most recently created axes instance (ax2 in this case). This is why the fonts haven't changed for ax1!
So, to get what you want you need to explicitly act on both ax1 and ax2. Something like the following should do the trick:
for ax in ax1, ax2:
# Prettifying the plot
ax.set_xlabel("t", fontsize=25)
ax.tick_params( # modifying plot ticks
axis='x',
labelsize=20)
ax.set_ylabel("x", fontsize=25)
ax.tick_params( # modifying plot ticks
axis='y',
labelsize=20)
# Plot title
ax.set_title('Harmonic oscillator in ' + str(dim) + 'D with ' + str(num_step) + ' timesteps', fontsize=30)