Search code examples
pythonmatplotliblegendscatter-plotline-plot

How to create two different legends from multiple plot calls


I have to create a visualization where there are multiple line plots (trend lines/moving averages etc.), and multiple scatter charts.

I have successfully created all the charts in a single plot, however I want the legend of scatter chart to be different from that of the line plots.

I want the legend of lineplots to be at the center and that of scatter charts to be at the upper left corner of the plot.

Code for the 4 scatter charts

#Scatter plot
#Using matplotlib scatter plot
scatter1 = plt.scatter(data=df[df.ABC < 10], x='Date', y='ABC', c = 'cyan' , s = 5, label='less')
scatter2 = plt.scatter(data=df[(df.ABC >= 10) & (df.ABC < 40)], x='Date', y='ABC', c = 'dodgerblue' , s = 5, label='normal')
scatter3 = plt.scatter(data=df[(df.ABC >= 40) & (df.ABC < 60)], x='Date', y='ABC', c = 'orangered' , s = 5, label='good')
scatter4 = plt.scatter(data=df[df.ABC > 60], x='Date', y='ABC', c = 'brown' , s = 5, label='more')

Code for the line plots and a calculated metric (all have to be in the same legend box)

#Empty legend line specifying a calulated metric
plt.plot([], [], ' ', label=f'Points above Target Budget PR = {num}/{den} = {ans}%')

#plot using rolling average
#using seaborn.lineplot()
sns.lineplot( x = 'Date',
             y = 'moving avg',
             data = df,
             color='red',
             linewidth=1.5,
             label = 'Moving Average')

#plot a simple time series plot
#using seaborn.lineplot()
sns.lineplot( x = 'Date',
             y = 'Yield',
             color = 'darkgreen',
             linewidth = 1.5,
             data = df,
             label = 'Yield')

I have tried following method to make multiple legends:-

legend1 = plt.legend((scatter1,scatter2,scatter3,scatter4),['less','normal','good','more'], loc = 'upper left')
plt.gca().add_artist(legend1)
plt.legend(loc = 'center')

What above code does is, it creates a legend for scatter chart in top left corner. However the same points are re-written in the legend that is supposed to contain only line plots and the calculated metric. so the legend for scatter chart is represented twice in the chart.

I tried to do other changes in the code too, but the outcome always leads to 1 legend including all, or 2 legends with scatter chart part repeated, or no legend at all.


Solution

  • One way to do this (and what I think is the easier way) is to simply completing the plotting and get the legend with all the 6 entries. Then use get_legend_handles_labels() to get the legends and handles, split them up as you need and create them as separate legends. Note the add_artist is so that you add the first one to the axis. The entire code is below... mostly what you have, removed the empty legend line you have and most of the added code is towards the bottom... Also, Title1 & Title2 are optional and you can use/remove it as it suits you.

    df=pd.DataFrame({'ABC':np.random.uniform(low=0, high=100, size=(100,)),
                    'Date':pd.date_range('2019/07/01', periods=100, freq='SM')})
    #Using matplotlib scatter plot
    scatter1 = plt.scatter(data=df[df.ABC < 10], x='Date', y='ABC', c = 'cyan' , s = 5, label='less')
    scatter2 = plt.scatter(data=df[(df.ABC >= 10) & (df.ABC < 40)], x='Date', y='ABC', c = 'dodgerblue' , s = 5, label='normal')
    scatter3 = plt.scatter(data=df[(df.ABC >= 40) & (df.ABC < 60)], x='Date', y='ABC', c = 'orangered' , s = 5, label='good')
    scatter4 = plt.scatter(data=df[df.ABC > 60], x='Date', y='ABC', c = 'brown' , s = 5, label='more')
    
    #Empty legend line specifying a calulated metric
    #plt.plot([], [], ' ', label=f'Points above Target Budget PR = {num}/{den} = {ans}%')
    
    #plot using rolling average
    #using seaborn.lineplot()
    sns.lineplot( x = 'Date',
                 y = df.ABC.rolling(10).mean(),
                 data = df,
                 color='red',
                 linewidth=1.5,
                 label = 'Moving Average')
    
    #plot a simple time series plot
    #using seaborn.lineplot()
    sns.lineplot( x = 'Date',
                 y = df.ABC.rolling(10).median(),
                 color = 'darkgreen',
                 linewidth = 1.5,
                 data = df,
                 label = 'Yield')
    
    h,l = plt.gca().get_legend_handles_labels() ##Get the legend handles and lables
    
    l1 = plt.gca().legend(h[:2],l[:2], loc='center', title='Title 1') ##Plot the seborn lines as the first legend
    l2 = plt.gca().legend(h[2:],l[2:], loc='upper right', title='Title 2') ##Plot the seborn lines as the first legend
    
    plt.gca().add_artist(l1) # 2nd legend will erases the first, so need to add it
    plt.show()
    

    enter image description here