I want to draw a barplot with 3 different y values which belong to RMSE, R2 and MAPE metrics.
My dataframe is;
DLscores = {"GRU":[293.7372606050454,0.961253983114077,86281.57826775634],
"LSTM":[285.9872902525968,0.9632715628933957,81788.73018602304],
"LSTM_Attention":[266.6285102384448,0.9680756432778241,71090.76247197246],
"TCN":[219.30770326715282,0.9784018398981137,48095.868712313546],
"Hybrid":[216.97781461699145,0.978858312741761,47079.372035965505]}
I am able to do this with linegraph. However when I change it to bar, they overlaps. My line plot code is;
# Create figure and axis #1
fig, ax1 = plt.subplots(figsize=(16,10))
# plot line chart on axis #1
p1, = ax1.plot(DLscores.columns, DLscores.iloc[1], color='blue')
ax1.set_ylabel('R2')
#ax1.set_ylim(0, 25)
#ax1.legend(['R2'], loc="upper left")
ax1.yaxis.label.set_color(p1.get_color())
ax1.yaxis.label.set_fontsize(14)
ax1.tick_params(axis='y', colors=p1.get_color(), labelsize=14)
# set up the 2nd axis
ax2 = ax1.twinx()
# plot bar chart on axis #2
p2, = ax2.plot(DLscores.columns, DLscores.iloc[0], color='green')
ax2.grid(False) # turn off grid #2
ax2.set_ylabel('RMSE')
#ax2.set_ylim(0, 90)
#ax2.legend(['RMSE'], loc="upper center")
ax2.yaxis.label.set_color(p2.get_color())
ax2.yaxis.label.set_fontsize(14)
ax2.tick_params(axis='y', colors=p2.get_color(), labelsize=14)
# set up the 3rd axis
ax3 = ax1.twinx()
# Offset the right spine of ax3. The ticks and label have already been placed on the right by twinx above.
ax3.spines.right.set_position(("axes", 1.2))
# Plot line chart on axis #3
p3, = ax3.plot(DLscores.columns, DLscores.iloc[2], color='red')
ax3.grid(False) # turn off grid #3
ax3.set_ylabel('MAPE')
#ax3.set_ylim(0, 8)
#ax3.legend(['MAPE'], loc="upper right")
ax3.yaxis.label.set_color(p3.get_color())
ax3.yaxis.label.set_fontsize(14)
ax3.tick_params(axis='y', colors=p3.get_color(), labelsize=14)
plt.show()
Output:
I also tried seaborn (I couldn't figure it out how can I merge it with "hue"), but code and result is in below:
# plot line chart on axis #1
ax1 = sns.barplot(
x=DLscores.index,
y=DLscores['RMSE'],
color='blue'
)
ax1.set_ylabel('RMSE')
#ax1.set_ylim(0, 8)
ax1.legend(['RMSE'], loc="upper left")
ax1.yaxis.label.set_color('blue')
ax1.yaxis.label.set_fontsize(14)
ax1.tick_params(axis='y', colors='blue', labelsize=14)
# set up the 2nd axis
ax2 = ax1.twinx()
# plot bar chart on axis #2
sns.barplot(
x=DLscores.index,
y=DLscores['R2'],
color='orange',
ax = ax2 # Pre-existing axes for the plot
)
ax2.grid(False) # turn off grid #2
ax2.set_ylabel('R2')
#ax2.set_ylim(0, 90)
ax2.legend(['R2'], loc="upper center")
ax2.yaxis.label.set_color('orange')
ax2.yaxis.label.set_fontsize(14)
ax2.tick_params(axis='y', colors='orange', labelsize=14)
# set up the 3rd axis
ax3 = ax1.twinx()
# Offset the right spine of ax3. The ticks and label have already been placed on the right by twinx above.
ax3.spines.right.set_position(("axes", 1.15))
# Plot line chart on axis #3
p3 = sns.barplot(
x=DLscores.index,
y=DLscores['MAPE'],
color='red',
ax = ax3 # Pre-existing axes for the plot
)
ax3.grid(False) # turn off grid #3
ax3.set_ylabel('MAPE')
#ax3.set_ylim(0, 8)
ax3.legend(['MAPE'], loc="upper right")
ax3.yaxis.label.set_color('red')
ax3.yaxis.label.set_fontsize(14)
ax3.tick_params(axis='y', colors='red', labelsize=14)
plt.show()
I assume the problem is clear.
I want to have two equal size subplots and increase the height (size) of my third subplot. Also, my third plot sticks to my second subplot. I want to have a small distance between the second and the third subplots. I should mention that my X axis is common for all subplots. This is part of my code.
from datetime import datetime, timedelta
from matplotlib import pyplot as plt
from matplotlib import dates as mpl_dates
ax1 = plt.subplot(311)
plt.plot(date,amount, color='gray', linewidth=0.3)
plt.ylabel('2-4 Hz')
ax2 = plt.subplot(312)
plt.plot(date,amount, color='brown', linewidth=0.3)
plt.ylabel('0.4-2 Hz')
ax3 = plt.subplot(313)
plt.bar (date, amount, color='gold', edgecolor='red', align='center')
plt.ylabel('rainfall(mm/day)')
ax1.get_shared_x_axes().join(ax1, ax2, ax3)
plt.subplots_adjust(hspace=0.01)
plt.show()
enter image description here
I would approach this using the gridspec_kw argument in plt.subplots(), and then manually add a little space between the second and third axes. The gridspec_kw lets you set the height ratios of each axes.
There might be a better way to add the space between the second and third axis, but I think this meets your needs.
fig, ax = plt.subplots(3, 1, sharex=True, gridspec_kw={'height_ratios':[1,1,2]})
fig.subplots_adjust(hspace=0.0)
ax1 = ax[0]
ax2 = ax[1]
ax3 = ax[2]
bbox = list(ax3.get_position().bounds)
bbox[3] = 0.9*bbox[3] # Reduce the height of the axis a bit.
ax3.set_position(bbox)
ax1.plot(date,amount, color='gray', linewidth=0.3)
ax1.set_ylabel('2-4 Hz')
ax2.plot(date,amount, color='brown', linewidth=0.3)
ax2.set_ylabel('0.4-2 Hz')
ax3.bar (date, amount, color='gold', edgecolor='red', align='center')
ax3.set_ylabel('rainfall(mm/day)')
I'm plotting a simple scatter plot:
It represents my data correctly, however there is many datapoints with coordinates (1.00,1.00) and in the plot, they appear under a single marker (top right corner). I'd like to have a functionality that changes the size of every marker according to the number of points it is representing. Will appreciate any help. Here's my code:
def saveScatter(figureTitle, xFeature, yFeature, xTitle, yTitle):
''' save a scatter plot of xFeatures vs yFeatures '''
fig = plt.figure(figsize=(8, 6), dpi=300)
ax = fig.add_subplot(111)
ax.scatter(dfModuleCPositives[names[xFeature]][:], dfModuleCPositives[names[yFeature]][:], c='r', marker='x', alpha=1, label='Module C Positives')
ax.scatter(dfModuleCNegatives[names[xFeature]][:], dfModuleCNegatives[names[yFeature]][:], c='g', alpha=0.5, label='Module C Negatives')
ax.scatter(dfModuleDPositives[names[xFeature]][:], dfModuleDPositives[names[yFeature]][:], c='k', marker='x', alpha=1, label='Module D Positives')
ax.scatter(dfModuleDNegatives[names[xFeature]][:], dfModuleDNegatives[names[yFeature]][:], c='b', alpha=0.5, label='Module D Negatives')
ax.set_xlabel(xTitle, fontsize=10)
ax.set_ylabel(yTitle, fontsize=10)
ax.set_title(figureTitle)
ax.grid(True)
ax.legend(loc="lower right")
fig.tight_layout()
plt.show()
return ax
I am trying to plot one or more lines on the same chart as a bar chart to show a few different metrics. I heard I should use ax.twinx() for this, but I either get an error saying x and y must have the same first dimension, or a different error reading 0L based on the two things I tried. Here is my code;
x = df4['Date']
y = df4['Rate']
ax = df4[['Date','Qty']].set_index('Date') \
.plot(kind='bar',
stacked=False,
color = 'dodgerblue',
figsize=(13,4),
legend=True)
ax.set_xlabel(r"$\rm \bf{Date}$",
fontsize=18,
rotation = 0)
ax.set_ylabel(r'$\cal \bf{Qty}$ ',fontsize=18, rotation = 90)
ax2 = ax.twinx()
ax2.plot(x, y, color = 'green', linestyle = '--', linewidth= 2.0)
Note; df4 is a grouped pandas dataframe. Not sure how relevant that is but just in case.
Try this:
fig, ax = plt.subplots()
ax2 = ax.twinx()
df4[['Date','Qty']].set_index('Date') \
.plot(kind='bar',
ax=ax,
color = 'dodgerblue',
figsize=(13,4),
legend=False)
patches, labels = ax.get_legend_handles_labels()
ax.legend(patches, labels, loc='upper left')
ax.set_xlabel(r"$\rm \bf{Date}$", fontsize=18, rotation=0)
ax.set_ylabel(r'$\cal \bf{Qty}$ ',fontsize=18, rotation=90)
ax2.plot(range(len(df4)), df4['Rate'], 'green', label='Rate',
linestyle = '--', linewidth=2.0)
patches, labels = ax2.get_legend_handles_labels()
ax2.legend(patches, labels, loc='upper right')
NOTE: if you want a tested solution, please provide a sample data
I have a figure with two subplots as 2 rows and 1 column. I can add a nice looking figure legend with
fig.legend((l1, l2), ['2011', '2012'], loc="lower center",
ncol=2, fancybox=True, shadow=True, prop={'size':'small'})
However, this legend is positioned at the center of the figure and not below the center of the axes as I would like to have it. Now, I can obtain my axes coordinates with
axbox = ax[1].get_position()
and in theory I should be able to position the legend by specifying the loc keyword with a tuple:
fig.legend(..., loc=(axbox.x0+0.5*axbox.width, axbox.y0-0.08), ...)
This works, except that the legend is left aligned so that loc specifies the left edge/corner of the legend box and not the center. I searched for keywords such as align, horizontalalignment, etc., but couldn't find any. I also tried to obtain the "legend position", but legend doesn't have a *get_position()* method. I read about *bbox_to_anchor* but cannot make sense of it when applied to a figure legend. This seems to be made for axes legends.
Or: should I use a shifted axes legend instead? But then, why are there figure legends in the first place? And somehow it must be possible to "center align" a figure legend, because loc="lower center" does it too.
Thanks for any help,
Martin
In this case, you can either use axes for figure legend methods. In either case, bbox_to_anchor is the key. As you've already noticed bbox_to_anchor specifies a tuple of coordinates (or a box) to place the legend at. When you're using bbox_to_anchor think of the location kwarg as controlling the horizontal and vertical alignment.
The difference is just whether the tuple of coordinates is interpreted as axes or figure coordinates.
As an example of using a figure legend:
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')
# The key to the position is bbox_to_anchor: Place it at x=0.5, y=0.5
# in figure coordinates.
# "center" is basically saying center horizontal alignment and
# center vertical alignment in this case
fig.legend([line1, line2], ['yep', 'nope'], bbox_to_anchor=[0.5, 0.5],
loc='center', ncol=2)
plt.show()
As an example of using the axes method, try something like this:
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')
# The key to the position is bbox_to_anchor: Place it at x=0.5, y=0
# in axes coordinates.
# "upper center" is basically saying center horizontal alignment and
# top vertical alignment in this case
ax1.legend([line1, line2], ['yep', 'nope'], bbox_to_anchor=[0.5, 0],
loc='upper center', ncol=2, borderaxespad=0.25)
plt.show()
This is a very good question and the accepted answer indicates the key (i.e. loc denotes alignment and bbox_to_anchor denotes position). I have also tried some codes and would like to stress the importance of bbox_transform property that may sometimes needs to be explicitly specified to achieve desired effects. Below I will show you my findings on fig.legend. ax.legend should be very similar as loc and bbox_to_anchor works the same way.
When using the default setting, we will have the following.
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,4), sharex=True)
x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')
fig.legend([line1, line2], ['yep', 'nope'], loc='lower center', ncol=2)
This is basically satisfactory. But it could be easily found that the legend overlays with the x-axis ticklabels of ax2. This is the problem that will become even severe when figsize and/or dpi of the figure changes, see the following.
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,12), sharex=True, facecolor='w', gridspec_kw={'hspace':0.01})
x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')
fig.legend([line1, line2], ['yep', 'nope'], loc='lower center', ncol=2)
So you see there are big gaps between ax2 and the legend. That's not what we want. Like the questioner, we would like to manually control the location of the legend. First, I will use the 2-number style of bbox_to_anchor like the answer did.
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,12), sharex=True, facecolor='w', gridspec_kw={'hspace':0.01})
x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')
axbox = ax2.get_position()
# to place center point of the legend specified by loc at the position specified by bbox_to_anchor.
fig.legend([line1, line2], ['yep', 'nope'], loc='center', ncol=2,
bbox_to_anchor=[axbox.x0+0.5*axbox.width, axbox.y0-0.05])
Almost there! But it is totally wrong as the center of the legend is not at the center of what we really mean! The key to solving this is that we need to explicitly inform the bbox_transform as fig.transFigure. By default None, the Axes' transAxes transform will be used. This is understandable as most of the time we will use ax.legend().
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,12), sharex=True, facecolor='w', gridspec_kw={'hspace':0.01})
x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')
axbox = ax2.get_position()
# to place center point of the legend specified by loc at the position specified by bbox_to_anchor!
fig.legend([line1, line2], ['yep', 'nope'], loc='center', ncol=2,
bbox_to_anchor=[axbox.x0+0.5*axbox.width, axbox.y0-0.05], bbox_transform=fig.transFigure)
As an alternative, we can also use a 4-number style bbox_to_anchor for loc. This is essentially specify a real box for the legend and loc really denotes alignment! The default bbox_to_anchor should just be [0,0,1,1], meaning the entire figure box! The four numbers represent x0,y0,width,height, respectively. It is very similar to specifying a cax for a shared colorbar! Hence you can easily change the y0 just a little bit lower than axbox.y0 and adjust loc accordingly.
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,12), sharex=True, facecolor='w', gridspec_kw={'hspace':0.01})
x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')
axbox = ax2.get_position()
# to place center point specified by loc at the position specified by bbox_to_anchor!
fig.legend([line1, line2], ['yep', 'nope'], loc='lower center', ncol=2,
bbox_to_anchor=[0, axbox.y0-0.05,1,1], bbox_transform=fig.transFigure)