Secondary axis with twinx(): how to add to legend? - python

I have a plot with two y-axes, using twinx(). I also give labels to the lines, and want to show them with legend(), but I only succeed to get the labels of one axis in the legend:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
ax2.plot(time, temp, '-r', label = 'temp')
ax.legend(loc=0)
ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()
So I only get the labels of the first axis in the legend, and not the label 'temp' of the second axis. How could I add this third label to the legend?

You can easily add a second legend by adding the line:
ax2.legend(loc=0)
You'll get this:
But if you want all labels on one legend then you should do something like this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')
time = np.arange(10)
temp = np.random.random(10)*30
Swdown = np.random.random(10)*100-10
Rn = np.random.random(10)*100-10
fig = plt.figure()
ax = fig.add_subplot(111)
lns1 = ax.plot(time, Swdown, '-', label = 'Swdown')
lns2 = ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
lns3 = ax2.plot(time, temp, '-r', label = 'temp')
# added these three lines
lns = lns1+lns2+lns3
labs = [l.get_label() for l in lns]
ax.legend(lns, labs, loc=0)
ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()
Which will give you this:

I'm not sure if this functionality is new, but you can also use the get_legend_handles_labels() method rather than keeping track of lines and labels yourself:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')
pi = np.pi
# fake data
time = np.linspace (0, 25, 50)
temp = 50 / np.sqrt (2 * pi * 3**2) \
* np.exp (-((time - 13)**2 / (3**2))**2) + 15
Swdown = 400 / np.sqrt (2 * pi * 3**2) * np.exp (-((time - 13)**2 / (3**2))**2)
Rn = Swdown - 10
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
ax2.plot(time, temp, '-r', label = 'temp')
# ask matplotlib for the plotted objects and their labels
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines + lines2, labels + labels2, loc=0)
ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

From matplotlib version 2.1 onwards, you may use a figure legend. Instead of ax.legend(), which produces a legend with the handles from the axes ax, one can create a figure legend
fig.legend(loc="upper right")
which will gather all handles from all subplots in the figure. Since it is a figure legend, it will be placed at the corner of the figure, and the loc argument is relative to the figure.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0,10)
y = np.linspace(0,10)
z = np.sin(x/3)**2*98
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x,y, '-', label = 'Quantity 1')
ax2 = ax.twinx()
ax2.plot(x,z, '-r', label = 'Quantity 2')
fig.legend(loc="upper right")
ax.set_xlabel("x [units]")
ax.set_ylabel(r"Quantity 1")
ax2.set_ylabel(r"Quantity 2")
plt.show()
In order to place the legend back into the axes, one would supply a bbox_to_anchor and a bbox_transform. The latter would be the axes transform of the axes the legend should reside in. The former may be the coordinates of the edge defined by loc given in axes coordinates.
fig.legend(loc="upper right", bbox_to_anchor=(1,1), bbox_transform=ax.transAxes)

You can easily get what you want by adding the line in ax:
ax.plot([], [], '-r', label = 'temp')
or
ax.plot(np.nan, '-r', label = 'temp')
This would plot nothing but add a label to legend of ax.
I think this is a much easier way.
It's not necessary to track lines automatically when you have only a few lines in the second axes, as fixing by hand like above would be quite easy. Anyway, it depends on what you need.
The whole code is as below:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')
time = np.arange(22.)
temp = 20*np.random.rand(22)
Swdown = 10*np.random.randn(22)+40
Rn = 40*np.random.rand(22)
fig = plt.figure()
ax = fig.add_subplot(111)
ax2 = ax.twinx()
#---------- look at below -----------
ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2.plot(time, temp, '-r') # The true line in ax2
ax.plot(np.nan, '-r', label = 'temp') # Make an agent in ax
ax.legend(loc=0)
#---------------done-----------------
ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()
The plot is as below:
Update: add a better version:
ax.plot(np.nan, '-r', label = 'temp')
This will do nothing while plot(0, 0) may change the axis range.
One extra example for scatter
ax.scatter([], [], s=100, label = 'temp') # Make an agent in ax
ax2.scatter(time, temp, s=10) # The true scatter in ax2
ax.legend(loc=1, framealpha=1)

Preparation
import numpy as np
from matplotlib import pyplot as plt
fig, ax1 = plt.subplots( figsize=(15,6) )
Y1, Y2 = np.random.random((2,100))
ax2 = ax1.twinx()
Content
I'm surprised it did not show up so far but the simplest way is to either collect them manually into one of the axes objs (that lie on top of each other)
l1 = ax1.plot( range(len(Y1)), Y1, label='Label 1' )
l2 = ax2.plot( range(len(Y2)), Y2, label='Label 2', color='orange' )
ax1.legend( handles=l1+l2 )
or have them collected automatically into the surrounding figure by fig.legend() and fiddle around with the the bbox_to_anchor parameter:
ax1.plot( range(len(Y1)), Y1, label='Label 1' )
ax2.plot( range(len(Y2)), Y2, label='Label 2', color='orange' )
fig.legend( bbox_to_anchor=(.97, .97) )
Finalization
fig.tight_layout()
fig.savefig('stackoverflow.png', bbox_inches='tight')

A quick hack that may suit your needs..
Take off the frame of the box and manually position the two legends next to each other. Something like this..
ax1.legend(loc = (.75,.1), frameon = False)
ax2.legend( loc = (.75, .05), frameon = False)
Where the loc tuple is left-to-right and bottom-to-top percentages that represent the location in the chart.

I found an following official matplotlib example that uses host_subplot to display multiple y-axes and all the different labels in one legend. No workaround necessary. Best solution I found so far.
http://matplotlib.org/examples/axes_grid/demo_parasite_axes2.html
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)
par1 = host.twinx()
par2 = host.twinx()
offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",
axes=par2,
offset=(offset, 0))
par2.axis["right"].toggle(all=True)
host.set_xlim(0, 2)
host.set_ylim(0, 2)
host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Velocity")
par1.set_ylim(0, 4)
par2.set_ylim(1, 65)
host.legend()
plt.draw()
plt.show()

If you are using Seaborn you can do this:
g = sns.barplot('arguments blah blah')
g2 = sns.lineplot('arguments blah blah')
h1,l1 = g.get_legend_handles_labels()
h2,l2 = g2.get_legend_handles_labels()
#Merging two legends
g.legend(h1+h2, l1+l2, title_fontsize='10')
#removes the second legend
g2.get_legend().remove()

As provided in the example from matplotlib.org, a clean way to implement a single legend from multiple axes is with plot handles:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
fig.subplots_adjust(right=0.75)
twin1 = ax.twinx()
twin2 = ax.twinx()
# Offset the right spine of twin2. The ticks and label have already been
# placed on the right by twinx above.
twin2.spines.right.set_position(("axes", 1.2))
p1, = ax.plot([0, 1, 2], [0, 1, 2], "b-", label="Density")
p2, = twin1.plot([0, 1, 2], [0, 3, 2], "r-", label="Temperature")
p3, = twin2.plot([0, 1, 2], [50, 30, 15], "g-", label="Velocity")
ax.set_xlim(0, 2)
ax.set_ylim(0, 2)
twin1.set_ylim(0, 4)
twin2.set_ylim(1, 65)
ax.set_xlabel("Distance")
ax.set_ylabel("Density")
twin1.set_ylabel("Temperature")
twin2.set_ylabel("Velocity")
ax.yaxis.label.set_color(p1.get_color())
twin1.yaxis.label.set_color(p2.get_color())
twin2.yaxis.label.set_color(p3.get_color())
tkw = dict(size=4, width=1.5)
ax.tick_params(axis='y', colors=p1.get_color(), **tkw)
twin1.tick_params(axis='y', colors=p2.get_color(), **tkw)
twin2.tick_params(axis='y', colors=p3.get_color(), **tkw)
ax.tick_params(axis='x', **tkw)
ax.legend(handles=[p1, p2, p3])
plt.show()

Here is another way to do this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')
fig = plt.figure()
ax = fig.add_subplot(111)
pl_1, = ax.plot(time, Swdown, '-')
label_1 = 'Swdown'
pl_2, = ax.plot(time, Rn, '-')
label_2 = 'Rn'
ax2 = ax.twinx()
pl_3, = ax2.plot(time, temp, '-r')
label_3 = 'temp'
ax.legend([pl[enter image description here][1]_1, pl_2, pl_3], [label_1, label_2, label_3], loc=0)
ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()
enter image description here

The solutions proposed so far have one or two inconvenients:
Handles needs to be collected individually when plotting, e.g. lns1 = ax.plot(time, Swdown, '-', label = 'Swdown'). There is a risk of forgetting handles when updating the code.
Legend is drawn for the whole figure, not by subplot, which is likely a no-go if you have multiple subplots.
This new solution takes advantage of Axes.get_legend_handles_labels() to collect existing handles and labels for the main axis and for the twin axis.
Collecting handles and labels automatically
This numpy operation will scan all axes which share the same subplot area than ax, including ax and return merged handles and labels:
hl = np.hstack([axis.get_legend_handles_labels()
for axis in ax.figure.axes
if axis.bbox.bounds == ax.bbox.bounds])
It can be used to feed legend() arguments this way:
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(1, 200)
signals = [np.exp(-t/20) * np.cos(t*k) for k in (1, 2)]
fig, axes = plt.subplots(nrows=2, figsize=(10, 3), layout='constrained')
axes = axes.flatten()
for i, (ax, signal) in enumerate(zip(axes, signals)):
# Plot as usual, no change to the code
ax.plot(t, signal, label=f'plotted on axes[{i}]', c='C0', lw=9, alpha=0.3)
ax2 = ax.twinx()
ax2.plot(t, signal, label=f'plotted on axes[{i}].twinx()', c='C1')
# The only specificity of the code is when plotting the legend
h, l = np.hstack([axis.get_legend_handles_labels()
for axis in ax.figure.axes
if axis.bbox.bounds == ax.bbox.bounds]).tolist()
ax2.legend(handles=h, labels=l, loc='upper right')

Related

Properly displaying pyplot scatter plot with X/Y histograms and a colorbar

I saw this tutorial on how to make a scatter plot with a histogram for the x and y axes and I thought it would be neat to also tack on a colorbar for an extra dimension of information. To do this, I utilized "the make_axes_locatable" function, like so:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
# generating fake data
tx = np.random.randn(1000)
ty = np.random.randn(1000)
tz = np.random.randn(1000)
fig = plt.figure(figsize=(5, 5))
gs = fig.add_gridspec(2, 2, width_ratios=(4, 1), height_ratios=(1, 4),
left=0.1, right=0.9, bottom=0.1, top=0.9,
wspace=0.05, hspace=0.05)
# Create the Axes.
ax = fig.add_subplot(gs[1, 0])
ax_histx = fig.add_subplot(gs[0, 0], sharex=ax)
ax_histy = fig.add_subplot(gs[1, 1], sharey=ax)
def scatter_hist_and_colorbar(x, y, c, ax, ax_histx, ax_histy,label):
# no labels
ax_histx.tick_params(axis="x", labelbottom=False)
ax_histy.tick_params(axis="y", labelleft=False)
# the scatter plot:
sc=ax.scatter(x,y,marker='o',label=label,c=c)
# now determine nice limits by hand:
binwidth = 0.25
xlim = (int(np.max(np.abs(x))/binwidth) + 1) * binwidth
ylim = (int(np.max(np.abs(y))/binwidth) + 1) * binwidth
xbins = np.arange(-xlim, xlim + binwidth, binwidth)
ybins = np.arange(-ylim, ylim + binwidth, binwidth)
ax_histx.hist(x, bins=xbins)
ax_histy.hist(y, bins=ybins, orientation='horizontal')
return sc
sc1= scatter_hist_and_colorbar(tx,ty,tz, ax, ax_histx, ax_histy,label='data')
ax.set_ylabel('x data')
ax.set_xlabel('y data')
ax.legend()
divider = make_axes_locatable(ax)
cax = divider.append_axes('left', size='5%', pad=1)
cbar=fig.colorbar(sc1, cax=cax, orientation='vertical')
cbar.ax.set_ylabel('z data',rotation=90,labelpad=5)
cbar.ax.yaxis.set_ticks_position("left")
plt.savefig('example.png')
plt.show()][2]][2]
This almost works except the "ax_histx" axis is now stretched and doesn't properly line up due to the addition of the colorbar. Is there a way to resize the "ax_histx" axis or is there a better way to add a colorbar to the "ax" subplot so that it wouldn't affect the "ax_histx" or "ax_histy" axes?
After getting a suggestion form #r-beginners , I tried tweaking this code to place a colorbar in the upper right, perpendicular to the histogram axes. This way, it doesn't distort the width/heights of the other shared axes:
# some random data
tx = np.random.randn(1000)
ty = np.random.randn(1000)
tz = np.random.randn(1000)
fig = plt.figure(figsize=(5, 5))
gs = fig.add_gridspec(2, 2, width_ratios=(4, 1), height_ratios=(1, 4),
left=0.1, right=0.9, bottom=0.1, top=0.9,
wspace=0.05, hspace=0.05)
# Create the Axes.
ax0 = fig.add_subplot(gs[0, 1])
ax = fig.add_subplot(gs[1, 0])
ax_histx = fig.add_subplot(gs[0, 0], sharex=ax)
ax_histy = fig.add_subplot(gs[1, 1], sharey=ax)
def scatter_hist_and_colorbar(x, y, c, ax, ax_histx, ax_histy,label):
# no labels
ax_histx.tick_params(axis="x", labelbottom=False)
ax_histy.tick_params(axis="y", labelleft=False)
# the scatter plot:
sc=ax.scatter(x,y,marker='o',label=label,c=c)
# now determine nice limits by hand:
binwidth = 0.25
xymax = max(np.max(np.abs(x)), np.max(np.abs(y)))
lim = (int(xymax/binwidth) + 1) * binwidth
xlim = (int(np.max(np.abs(x))/binwidth) + 1) * binwidth
ylim = (int(np.max(np.abs(y))/binwidth) + 1) * binwidth
xbins = np.arange(-xlim, xlim + binwidth, binwidth)
ybins = np.arange(-ylim, ylim + binwidth, binwidth)
ax_histx.hist(x, bins=xbins)
ax_histy.hist(y, bins=ybins, orientation='horizontal')
return sc
sc1= scatter_hist_and_colorbar(tx,ty,tz, ax, ax_histx, ax_histy,label='data')
ax.set_ylabel('x data')
ax.set_xlabel('y data')
ax.legend()
divider = make_axes_locatable(ax)
divider = make_axes_locatable(ax0)
ca = divider.append_axes('left', size='50%')
ax0.axis('off')
cbar=fig.colorbar(sc1, cax=ca, orientation='vertical')
cbar.ax.set_ylabel('z data',rotation=270,labelpad=5)
cbar.ax.yaxis.set_ticks_position("right")
gs.tight_layout(fig,pad=1)
plt.savefig('example.png')
plt.show()

How to loop over axis plots in python

I am trying to create an axis plot. I was trying to loop over it as I am plotting the same variable for two different categories. Currently, I have written code two times but I am looking for a smarter way with looping, if possible. Any other suggestion will also be helpful.
zone = ['AB','CD']
plt.style.use('default')
fig,(ax0,ax1) = plt.subplots(2,1, figsize = (18,18), sharex = False)
i = 0
while i < len(zone):
if zone[i] == zone[0]:
ax0.plot(df0['datetime'], df0['pnl1'], color='k', linewidth=1, label ='PnL1')
ax0.plot(df0['datetime'], df0['pnl2'], color='m', linewidth=1, label ='PnL2')
ax00 = ax0.twinx()
ax00.bar(df0['datetime'], df0['qty'], width = 1/96, color='g', align = 'edge', alpha = 0.5, label ='Qty')
elif zone[i] == zone[1]:
ax1.plot(df0['datetime'], df0['pnl1'], color='k', linewidth=1, label ='PnL1')
ax1.plot(df0['datetime'], df0['pnl2'], color='m', linewidth=1, label ='PnL2')
ax01 = ax1.twinx()
ax01.bar(df0['datetime'], df0['hedge'], width = 1/96, color='g', align = 'edge', alpha = 0.5, label ='Qty')
i = i + 1
I want to check if something like below can be done with axis plots or not.
zone = ['AB','CD']
plt.style.use('default')
fig,(ax0,ax1) = plt.subplots(2,1, figsize = (18,18), sharex = False)
i = 0
while i < len(zone):
ax{''}.format(i).plot(df0['datetime'], df0['pnl1'], color='k', linewidth=1, label ='PnL1')
ax{''}.format(i).plot(df0['datetime'], df0['pnl2'], color='m', linewidth=1, label ='PnL2')
ax0{''}.format(i) = ax{''}.format(i).twinx()
ax0{''}.format(i).bar(df0['datetime'], df0['qty'], width = 1/96, color='g', align = 'edge', alpha = 0.5, label ='Qty')
It did not work for me. Any leads to execute axis plot with loop will be helpful.
Here are some ways:
Simply loop over the list of axes
import matplotlib.pyplot as plt
import numpy as np
fig,axes = plt.subplots(2,1)
x = np.linspace(0,5,21)
for ax in axes:
ax.plot(x,np.sin(x))
plt.show()
Works also with index:
for i in range(len(axes)):
axes[i].plot(x,np.sin(x))
For a grid of plot you can use a similar approach:
import matplotlib.pyplot as plt
import numpy as np
fig,axes = plt.subplots(2,2)
x = np.linspace(0,5,21)
for i in range(len(axes)):
for j in range(len(axes[0])):
axes[i][j].plot(x,np.sin(x))
plt.show()
If you don't like double-loops, you can flatten the array with np.ravel()
fig,axes = plt.subplots(2,2)
x = np.linspace(0,5,21)
for ax in np.ravel(axes):
ax.plot(x,np.sin(x))
plt.show()

two barplots in one

I have two working barplots about the sentiments of tweets (neutral, positive, negative). How can I merge them into one, side by side?
First bar:
plt.figure(figsize=(6,5))
plt.title('Classification of All tweets into sentiment categories',fontsize=15)
plt.ylabel('Percentage [%]',fontsize=18)
ax = (df_navalny.sentiment.value_counts()/len(df_navalny)*100).plot(kind="bar", rot=0,color=['#04407F','#0656AC','#0A73E1'])
ax.set_yticks(np.arange(0, 110, 10))
plt.grid(color='#95a5a6', linestyle='-.', linewidth=1, axis='y', alpha=0.7)
ax2 = ax.twinx()
ax2.set_yticks(np.arange(0, 110, 10)*len(df_navalny)/100)
for p in ax.patches:
ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
Second bar:
plt.figure(figsize=(6,5))
plt.title('Classification of All tweets into sentiment categories',fontsize=15)
plt.ylabel('Percentage [%]',fontsize=18)
ax = (df_putin.sentiment.value_counts()/len(df_putin)*100).plot(kind="bar", rot=0,color=['#04407F','#0656AC','#0A73E1'])
ax.set_yticks(np.arange(0, 110, 10))
plt.grid(color='#95a5a6', linestyle='-.', linewidth=1, axis='y', alpha=0.7)
ax2 = ax.twinx()
ax2.set_yticks(np.arange(0, 110, 10)*len(df_putin)/100)
for p in ax.patches:
ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
It's a bit complicated but Matplotlib site offers a demo and when you copy and past you have the following
Here it is the code
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
from mpl_toolkits import axisartist
# fake data
a = [2341, 5670, 4822]
b = [4290, 5205, 3966]
pca, pcb = [[round(100*x/sum(l),2) for x in l] for l in (a, b)]
# prepare all the vertical axes
ax = host_subplot(111, axes_class=axisartist.Axes)
plt.subplots_adjust(right=0.67)
axa = ax.twinx() ; axb = ax.twinx()
axb.axis['right'] = axb.new_fixed_axis(loc="right", offset=(60, 0))
axa.axis['right'].toggle(all=True)
axb.axis['right'].toggle(all=True)
# plot the bars PLUS invisible lines to represent the samples numerosities
x, w = np.array((1,2,3)), 0.30
ax.bar(x-w, pca, width=w, align='edge', label='a', zorder=1)
ax.bar(x-0, pcb, width=w, align='edge', label='b', zorder=1)
axa.plot((1,1),(0,sum(a)),lw=0)
axb.plot((1,1),(0,sum(b)),lw=0)
# fix xtics, xlabels, 'regular' yticks
plt.xticks((1,2,3)) ; ax.set_xticklabels('NO == YES'.split())
ax.set_yticks(range(0, 101, 10))
# all the ylabels
ax.set_ylabel('Percentages')
axa.set_ylabel('Numerosity of a')
axb.set_ylabel('Numerosity of b')
axa.set_ylim(bottom=0.0)
axb.set_ylim(bottom=0.0)
plt.legend()
plt.grid(zorder=0)
plt.show()

How can I draw a radar chart and bar graph in the subplot in Python?

I have a problem about putting a radar chart and bar graph in the subplot in Python.
I defined 1 row and 2 columns to put each one into each slot.
I tried to handle with this process but I couldn't.
How can I do that?
Here is my radar function shown below.
def radar_chart(values=[]):
labels=np.array(['Crew',
'Length',
'Wingspan',
'Height',
'WingArea'
]
)
angles=np.linspace(0, 2*np.pi, len(labels), endpoint=False)
#print(angles)
fig=plt.figure(figsize=(6,6))
#plt.suptitle(title, y=1.04)
for v in values:
stats=np.array(ww2aircraft_df[ww2aircraft_df["Name"]==v][labels])[0]
#print(stats)
ax = fig.add_subplot(111, polar=True)
ax.plot(angles, stats, 'o-', linewidth=2, label = v)
ax.fill(angles, stats, alpha=0.25)
ax.set_thetagrids(angles * 180/np.pi, labels)
ax.grid(True)
#plt.legend(loc="upper right",bbox_to_anchor=(1.2,1.0))
ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.10),
fancybox=True, shadow=True, ncol=1, fontsize=13)
Here is my code snippets shown below.
f,a = plt.subplots(1,2,figsize=(24,10))
radar_chart(values=ww2aircraft_df_top_5["Name"])
graph_1 = sns.barplot(data = ww2aircraft_df_top_5,
x = "MaxSpeed",
y = "Name" , ax = a[1])
show_values_on_bars(graph_1, "h", 0.3)
plt.suptitle('Top 5 fastest of WW2 warplane by their features',
fontsize=20,
fontweight="semibold",
)
plt.tight_layout()
plt.savefig('images/image10.png', bbox_inches = "tight")
plt.show()
Possible solution is the following:
The dataset can be found HERE
# pip install matplotlib
# pip install pandas
# pip install seaborn
import csv
import pandas as pd
import numpy as np
from math import pi
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import seaborn as sns
# read csv to dataframe
df = pd.read_csv('ww2aircraft.csv', sep=';')
# select top-5 rows by 'MaxSpeed' column
df_top5_maxspeed = df.nlargest(5, 'MaxSpeed').reset_index(drop=True)
# convert column values to float type
df_top5_maxspeed['Length'] = df_top5_maxspeed['Length'].astype('float64')
df_top5_maxspeed['Wingspan'] = df_top5_maxspeed['Wingspan'].astype('float64')
# limit dataframe to required columns
df_top5_maxspeed_data = df_top5_maxspeed[["Name","Crew","Length","Wingspan","Height","WingArea","MaxSpeed"]]
df_top5_maxspeed_data
def create_radar_chart(df):
# limit data drame
df = df.iloc[:, :-1]
categories=list(df_top5_maxspeed_data)[1:-1]
N = len(categories)
angles = [n / float(N) * 2 * pi for n in range(N)]
angles += angles[:1]
ax = fig.add_subplot(gs[0, 0], polar=True)
ax.set_theta_offset(pi / 2)
ax.set_theta_direction(-1)
plt.xticks(angles[:-1], categories, size=10)
ax.set_rlabel_position(0)
plt.yticks([10,20,30,40], ["10","20","30","40"], color="grey", size=10)
plt.ylim(0,40)
for row in range(0, len(df.index)):
values=df.loc[row].drop(['Name']).values.flatten().tolist()
values+= values[:1]
ax.plot(angles, values, 'o-', linewidth=2, label = df.loc[row]["Name"])
ax.fill(angles, values, alpha=0.2)
ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.10),
fancybox=False, shadow=False, ncol=1, fontsize=10, frameon=False)
def create_bar_chart(df):
ax = fig.add_subplot(gs[0, 1])
df = df[['Name','MaxSpeed']]
df.plot.bar(x='Name', y='MaxSpeed', ax = ax, legend=False)
plt.xlabel("")
# create plots area
fig = plt.figure(figsize=(15, 5))
gs = GridSpec(nrows=1, ncols=2, width_ratios=[1, 1], wspace=0.1)
fig.suptitle('Top 5 fastest of WW2 warplane by their features', fontsize=16)
# add charts
create_radar_chart(df_top5_maxspeed_data)
create_bar_chart(df_top5_maxspeed_data)
# adjust space between title and charts
plt.subplots_adjust(top=0.85)

Seaborn plot with second y axis

i wanted to know how to make a plot with two y-axis so that my plot that looks like this :
to something more like this by adding another y-axis :
i'm only using this line of code from my plot in order to get the top 10 EngineVersions from my data frame :
sns.countplot(x='EngineVersion', data=train, order=train.EngineVersion.value_counts().iloc[:10].index);
I think you are looking for something like:
import matplotlib.pyplot as plt
x = [1,2,3,4,5]
y = [1000,2000,500,8000,3000]
y1 = [1050,3000,2000,4000,6000]
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.bar(x, y)
ax2.plot(x, y1, 'o-', color="red" )
ax1.set_xlabel('X data')
ax1.set_ylabel('Counts', color='g')
ax2.set_ylabel('Detection Rates', color='b')
plt.show()
Output:
#gdubs If you want to do this with Seaborn's library, this code set up worked for me. Instead of setting the ax assignment "outside" of the plot function in matplotlib, you do it "inside" of the plot function in Seaborn, where ax is the variable that stores the plot.
import seaborn as sns # Calls in seaborn
# These lines generate the data to be plotted
x = [1,2,3,4,5]
y = [1000,2000,500,8000,3000]
y1 = [1050,3000,2000,4000,6000]
fig, ax1 = plt.subplots() # initializes figure and plots
ax2 = ax1.twinx() # applies twinx to ax2, which is the second y axis.
sns.barplot(x = x, y = y, ax = ax1, color = 'blue') # plots the first set of data, and sets it to ax1.
sns.lineplot(x = x, y = y1, marker = 'o', color = 'red', ax = ax2) # plots the second set, and sets to ax2.
# these lines add the annotations for the plot.
ax1.set_xlabel('X data')
ax1.set_ylabel('Counts', color='g')
ax2.set_ylabel('Detection Rates', color='b')
plt.show(); # shows the plot.
Output:
Seaborn output example
You could try this code to obtain a very similar image to what you originally wanted.
import seaborn as sb
from matplotlib.lines import Line2D
from matplotlib.patches import Rectangle
x = ['1.1','1.2','1.2.1','2.0','2.1(beta)']
y = [1000,2000,500,8000,3000]
y1 = [3,4,1,8,5]
g = sb.barplot(x=x, y=y, color='blue')
g2 = sb.lineplot(x=range(len(x)), y=y1, color='orange', marker='o', ax=g.axes.twinx())
g.set_xticklabels(g.get_xticklabels(), rotation=-30)
g.set_xlabel('EngineVersion')
g.set_ylabel('Counts')
g2.set_ylabel('Detections rate')
g.legend(handles=[Rectangle((0,0), 0, 0, color='blue', label='Nontouch device counts'), Line2D([], [], marker='o', color='orange', label='Detections rate for nontouch devices')], loc=(1.1,0.8))

Categories

Resources