I have a plot that uses 9 labels on the x-axis. However, because I have split up the plot into two axes, it seems that it requires 18 labels (hence the added list of empty strings) instead for some reason.
This seems to make the x-labels be rendered twice, making them seem to have a bold typeface. Image of the problem is attached.
And here is the current code I'm using. I apologize for the quality of the code. I am new to matplotlib.
benchmark_data = all_benchmark_data[loader.pingpongKey]
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(9,7), dpi=80)
fig.subplots_adjust(hspace=0.05)
ax1.boxplot(benchmark_data.values())
ax2.boxplot(benchmark_data.values())
ax1.set_ylim(380, 650)
ax2.set_ylim(110, 180)
# hide the spines between ax and ax2
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # don't put tick labels at the top
ax2.xaxis.tick_bottom()
ax1.tick_params(axis='both', labelsize=10)
ax2.tick_params(axis='both', labelsize=10)
xlabels = ['', '', '', '', '', '', '', '', ''] + (list(benchmark_data.keys()))
ax1.set_xticklabels(xlabels)
ax1.set_ylabel('Time (ms)', fontsize=10)
ax1.yaxis.set_label_coords(-0.06,0)
#ax2.set_ylabel('Time (ms)', fontsize=10)
plt.xticks(fontsize=10, rotation=45)
ax1.yaxis.set_major_locator(ticker.MaxNLocator(nbins=5, min_n_ticks=5))
ax2.yaxis.set_major_locator(ticker.MaxNLocator(nbins=5, min_n_ticks=5))
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(marker=[(-1, -d), (1, d)], markersize=12,
linestyle="none", color='k', mec='k', mew=1, clip_on=False)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
plt.tight_layout()
plt.savefig('plots/boxplots/' + loader.pingpongKey + '-boxplot.png',
bbox_inches='tight')
Because you are using sharex=True, the second time you plot the boxplot you will create another 9 ticks that are added to the axis (which is in common between ax1 and ax2). The solution in your case is to turn off sharex (the axis will be aligned anyway) and set the xticklabels on ax2:
# No sharex.
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9,7), dpi=80)
# ...
# Set ticks for ax2 instead of ax1 and only the 9 labels are needed.
ax2.set_xticklabels(list(benchmark_data.keys()))
Related
I have two grouped bar charts of value changes between cases for two systems among 3 groups as below. Here I was able to add data labels to the bars using the code below (figure produced attached)
What I want to do is on top (or bottom for the negative change in value cases), add an extra data label that captures the % of the value changes as shown in the second figure with the 33% in red (I edited it in by hands). How do I achieve that from this code? Thank you in advance.
import matplotlib.pyplot as plt
import numpy as np
value_case0_system1 = np.array([30, 20, 40])
value_case1_system1 = np.array([20, 25, 50])
value_case2_system1 = np.array([10, 35, 45])
value_case1_system2 = np.array([60, 50, 40])
value_case2_system2 = np.array([50, 40, 55])
change_case0_to_case1_system1 = np.subtract(value_case1_system1,value_case0_system1)
change_case1_to_case2_system1 = np.subtract(value_case2_system1,value_case1_system1)
change_case1_to_case2_system2 = np.subtract(value_case2_system2,value_case1_system2)
fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(18,10))
labels = ['Group 1', 'Group 2', 'Group 3']
x = np.arange(len(labels))
ax0.set_ylabel('Change in Values', va='center', rotation='vertical',
fontsize=17, fontweight='bold')
width = 0.28
ax0.set_title('System 1', fontsize=17, fontweight='bold')
ax0.axhline(y=0, color='black', ls=':', lw=2)
ax0.set_xticks(x)
ax0.set_xticklabels(labels,fontsize=15)
rects1 = ax0.bar(x-width/2, change_case0_to_case1_system1, width, label='Case 0 to Case 1',
color='#292929', edgecolor='black', linewidth=1)
rects2 = ax0.bar(x+width/2, change_case1_to_case2_system1, width, label='Case 1 to Case 2',
color='#7f6d5f', edgecolor='black', linewidth=1)
ax0.bar_label(rects1, padding=3, fontsize=11)
ax0.bar_label(rects2, padding=3, fontsize=11)
leg = ax0.legend(loc="upper left", bbox_to_anchor=[0, 1],
ncol=1, fancybox=True)
ax0.legend(fontsize=15)
ax1.set_title('System 2', fontsize=17, fontweight='bold')
ax1.axhline(y=0, color='black', ls=':', lw=2)
ax1.set_xticks(x)
ax1.set_xticklabels(labels,fontsize=15)
rects3 = ax1.bar(x, change_case1_to_case2_system2, width, label='Case 1 to Case 2',
color='#7f6d5f', edgecolor='black', linewidth=1)
ax1.legend(shadow=True, fancybox=True)
ax1.bar_label(rects3, padding=3, fontsize=11)
leg = ax1.legend(loc="upper left", bbox_to_anchor=[0, 1],
ncol=1, fancybox=True)
ax1.legend(fontsize=15)
plt.tight_layout()
plt.show()
The code for the extra plot formatting has been left out, because it's not relevant for the answer. It can be added back, as per your requirements.
Each .bar_label colors the label globally, so unlike this answer, a second .bar_label needs to be added for the percent change, with a different color and padding
For each case-to-case, calculate the percent change, and set the string format in a list comprehension.
Set the list of string formatted calculations to the labels parameter in .bar_label.
Given the code in the OP, 6 lines of code need to be added, 3 for creating the list of labels, and 3 for adding the labels to the plot.
Additional resources:
matplotlib: Bar Label Demo
Adding value labels on a matplotlib bar chart
Tested in python 3.8.11, matplotlib 3.4.3
change_case0_to_case1_system1 = np.subtract(value_case1_system1, value_case0_system1)
# add list of string formatted percent change calculation
per_change_case0_to_case1_system1 = [f'({v}%)' for v in (change_case0_to_case1_system1 / value_case0_system1).round(2)*100]
change_case1_to_case2_system1 = np.subtract(value_case2_system1, value_case1_system1)
# add list of string formatted percent change calculation
per_change_case1_to_case2_system1 = [f'({v}%)' for v in (change_case1_to_case2_system1 / value_case1_system1).round(2)*100]
change_case1_to_case2_system2 = np.subtract(value_case2_system2, value_case1_system2)
# add list of string formatted percent change calculation
per_case1_to_case2_system2 = [f'({v}%)' for v in (change_case1_to_case2_system2 / value_case1_system2).round(2)*100]
fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(18,10))
labels = ['Group 1', 'Group 2', 'Group 3']
x = np.arange(len(labels))
width = 0.28
ax0.set_xticks(x)
ax0.set_xticklabels(labels, fontsize=15)
rects1 = ax0.bar(x-width/2, change_case0_to_case1_system1, width, label='Case 0 to Case 1', color='#292929', edgecolor='black', linewidth=1)
rects2 = ax0.bar(x+width/2, change_case1_to_case2_system1, width, label='Case 1 to Case 2', color='#7f6d5f', edgecolor='black', linewidth=1)
ax0.bar_label(rects1, padding=3, fontsize=11)
# add a second annotation with the string formatted labels
ax0.bar_label(rects1, labels=per_change_case0_to_case1_system1, padding=15, fontsize=11, color='red')
ax0.bar_label(rects2, padding=3, fontsize=11)
# add a second annotation with the string formatted labels
ax0.bar_label(rects2, labels=per_change_case1_to_case2_system1, padding=15, fontsize=11, color='red')
rects3 = ax1.bar(x, change_case1_to_case2_system2, width, label='Case 1 to Case 2', color='#7f6d5f', edgecolor='black', linewidth=1)
ax1.set_xticks(x)
ax1.set_xticklabels(labels,fontsize=15)
ax1.bar_label(rects3, padding=3, fontsize=11)
# add a second annotation with the string formatted labels
ax1.bar_label(rects3, labels=per_case1_to_case2_system2, padding=15, fontsize=11, color='red')
plt.tight_layout()
plt.show()
I've reviewed a number of posts (e.g., this one) discussing zorder, and based on the responses I perused, it seems like the following small reproducible example should not be drawing the grid on top of the bar. Or in other words, shouldn't the fact that the gridlines are assigned to ax2, which has a lower zorder number, make it so they are drawn below the bar and the triangle? How does one force the gridlines to be below everything else?
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots(figsize=(5, 4))
pts = ax1.plot(1, 1, 'r^', label='Stream Flow')
ax1.set_zorder(4)
ax1.set_facecolor('none')
ax1.set_xlim([0, 3])
ax1.set_ylim([0, 3])
ax2 = ax1.twinx()
ax2.set_xlim([0, 3])
ax2.set_ylim([0, 30])
bar = ax2.bar(1, 15, align='center', color='b', width=0.1, label='Some other value')
ax2.set_zorder(2)
ax2.set_ylabel(r'Other value', rotation=270, labelpad=15)
lns = pts + [bar]
labs = [l.get_label() for l in lns]
leg = ax2.legend(lns, labs, loc='lower right', frameon=True)
leg.get_frame().set_linewidth(0.0)
ax2.yaxis.grid(color='silver', zorder=1)
# Following two lines were experiments that failed
#fig.set_zorder(ax2.get_zorder()-1)
#fig.patch.set_visible(False)
plt.show()
Here's what I'm seeing, the gridlines are plotted over the bar (bad) but below the triangle (good).
# Plotting Forward Modelling
fig1, ax = plt.subplots(2, figsize=(10, 10))
ax[0].set_title('model')
ax[0].plot(x, g, '.')
ax[0].set_xlim([min(x), max(x)])
ax[0].set_ylabel('gravity anomaly (mgal)')
coll = PolyCollection(kotak, array=drho, cmap='jet', edgecolors='none', linewidth=0.0)
ax[1].add_collection(coll)
ax[1].autoscale_view()
# coll.set_clim() # set batasan colorbar
ax[1].set_xlim(min(x), max(x))
ax[1].set_ylim(0, 1)
ax[1].invert_yaxis()
plt.plot(centroide[:, 0], centroide[:, 1], 'k.')
d = plt.colorbar(coll, ax=ax[1])
ax[1].set_ylabel('Depth (m)')
ax[1].set_xlabel('Distance (m)')
d.set_label('\u0394 \u03C1 (kg/m^3)')
plt.tight_layout()
plt.show()
and the result is:
My question is, how do I make image 1 above image 2 aligned and the colorbar is to the right of image 2?
Thank You
I have a figure with two subplots in log-log scale. I would like to plot the minor ticks as well. Even though I have applied different solutions from Stack Overflow, my figure does not look as I want.
One of the solutions I have modified comes from ImportanceOfBeingErnest and the code looks like this:
fig, ((ax1, ax2)) = plt.subplots(1, 2, figsize=(8, 5), sharey=True)
# First plot
ax1.loglog(PLOT1['X'], PLOT1['Y'], 'o',
markerfacecolor='red', markeredgecolor='red', markeredgewidth=1,
markersize=1.5, alpha=0.2)
ax1.set(xlim=(1e-4, 1e4), ylim=(1e-8, 1e2))
ax1.set_xscale("log"); ax1.set_yscale("log")
ax1.xaxis.set_major_locator(matplotlib.ticker.LogLocator(base=10.0, numticks=25))
ax1.yaxis.set_major_locator(matplotlib.ticker.LogLocator(base=10.0, numticks=25))
locmaj = matplotlib.ticker.LogLocator(base=10,numticks=25)
ax1.xaxis.set_major_locator(locmaj)
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(0.2,0.4,0.6,0.8),numticks=25)
ax1.xaxis.set_minor_locator(locmin)
ax1.xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
locmaj = matplotlib.ticker.LogLocator(base=10,numticks=25)
ax1.yaxis.set_major_locator(locmaj)
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(0.2,0.4,0.6,0.8),numticks=25)
ax1.yaxis.set_minor_locator(locmin)
ax1.yaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
ax1.set_xlabel('X values', fontsize=10, fontweight='bold')
ax1.set_ylabel('Y values', fontsize=10, fontweight='bold')
# Plot 2
ax2.loglog(PLOT2['X'], PLOT2['Y'], 'o',
markerfacecolor='blue', markeredgecolor='blue', markeredgewidth=1,
markersize=1.5, alpha=0.2)
ax2.set(xlim=(1e-4, 1e4), ylim=(1e-8, 1e2))
ax2.xaxis.set_major_locator(matplotlib.ticker.LogLocator(base=10.0, numticks=25))
ax2.yaxis.set_major_locator(matplotlib.ticker.LogLocator(base=10.0, numticks=25))
locmaj = matplotlib.ticker.LogLocator(base=10,numticks=25)
ax2.xaxis.set_major_locator(locmaj)
ax2.yaxis.set_major_locator(locmaj)
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(0.2,0.4,0.6,0.8),numticks=25)
ax2.xaxis.set_minor_locator(locmin)
ax2.yaxis.set_minor_locator(locmin)
ax2.xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
ax2.yaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
ax2.set_xlabel('X values', fontsize=10, fontweight='bold')
ax2.set_ylabel('Y values', fontsize=10, fontweight='bold')
ax2.minorticks_on()
plt.show()
The plot I get is the following. As you can see, the minor ticks only appear on the x-axis from ax1.
How can I set the minor ticks in both subplots and both axis (x and y)?
Thank you so much.
In the below example, how can I have ax2 (bottom) taking the full space on the left?
By "taking full space" I mean extending the plot area to the left limit of the figure, ie. using also the whitespace left below the label title and ticks from ax1.
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
def example_plot(ax, fontsize=12):
ax.plot([1, 2])
ax.locator_params(nbins=3)
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_ylabel('y-label', fontsize=fontsize)
ax.set_title('Title', fontsize=fontsize)
def example_plot_noY(ax, fontsize=12):
ax.plot([1, 2])
ax.locator_params(nbins=3)
ax.set_xlabel('x-label', fontsize=fontsize)
ax.set_yticks([])
ax.set_title('Title', fontsize=fontsize)
plt.close('all')
fig = plt.figure()
gs1 = gridspec.GridSpec(2, 1)
ax1 = fig.add_subplot(gs1[0])
ax2 = fig.add_subplot(gs1[1])
example_plot(ax1)
example_plot_noY(ax2)
gs1.tight_layout(fig)
plt.show()
See GridSpec with Varying Cell Sizes.
You can add another column in the gridspec which takes up the space of the labels of the upper plot. If you afterwards call tight layout, you do not need to care about its width, it can be 0 size,
gs1 = gridspec.GridSpec(2, 2, width_ratios=[0, 1])
ax1 = fig.add_subplot(gs1[0,1])
ax2 = fig.add_subplot(gs1[1,0:])
# ...
fig.tight_layout()