pyplot combine multiple line labels in legend

28,676

Solution 1

I'd make a small helper function personally, if i planned on doing it often;

from matplotlib import pyplot
import numpy


a = numpy.array([[ 3.57,  1.76,  7.42,  6.52],
                 [ 1.57,  1.2 ,  3.02,  6.88],
                 [ 2.23,  4.86,  5.12,  2.81],
                 [ 4.48,  1.38,  2.14,  0.86],
                 [ 6.68,  1.72,  8.56,  3.23]])


def plotCollection(ax, xs, ys, *args, **kwargs):

  ax.plot(xs,ys, *args, **kwargs)

  if "label" in kwargs.keys():

    #remove duplicates
    handles, labels = pyplot.gca().get_legend_handles_labels()
    newLabels, newHandles = [], []
    for handle, label in zip(handles, labels):
      if label not in newLabels:
        newLabels.append(label)
        newHandles.append(handle)

    pyplot.legend(newHandles, newLabels)

ax = pyplot.subplot(1,1,1)  
plotCollection(ax, a[:,::2].T, a[:, 1::2].T, 'r', label='data_a')
plotCollection(ax, a[:,1::2].T, a[:, ::2].T, 'b', label='data_b')
pyplot.show()

An easier (and IMO clearer) way to remove duplicates (than what you have) from the handles and labels of the legend is this:

handles, labels = pyplot.gca().get_legend_handles_labels()
newLabels, newHandles = [], []
for handle, label in zip(handles, labels):
  if label not in newLabels:
    newLabels.append(label)
    newHandles.append(handle)
pyplot.legend(newHandles, newLabels)

Solution 2

Numpy solution based on will's response above.

import numpy as np
import matplotlib.pylab as plt
a = np.array([[3.57, 1.76, 7.42, 6.52],
              [1.57, 1.20, 3.02, 6.88],
              [2.23, 4.86, 5.12, 2.81],
              [4.48, 1.38, 2.14, 0.86],
              [6.68, 1.72, 8.56, 3.23]])

plt.plot(a[:,::2].T, a[:, 1::2].T, 'r', label='data_a')
handles, labels = plt.gca().get_legend_handles_labels()

Assuming that equal labels have equal handles, get unique labels and their respective indices, which correspond to handle indices.

labels, ids = np.unique(labels, return_index=True)
handles = [handles[i] for i in ids]
plt.legend(handles, labels, loc='best')
plt.show()

Solution 3

Matplotlib gives you a nice interface to collections of lines, LineCollection. The code is straight forward

import numpy
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection

a = numpy.array([[ 3.57,  1.76,  7.42,  6.52],
                 [ 1.57,  1.2 ,  3.02,  6.88],
                 [ 2.23,  4.86,  5.12,  2.81],
                 [ 4.48,  1.38,  2.14,  0.86],
                 [ 6.68,  1.72,  8.56,  3.23]])

xs = a[:,::2]
ys = a[:, 1::2]
lines = LineCollection([list(zip(x,y)) for x,y in zip(xs, ys)], label='data_a')
f, ax = plt.subplots(1, 1)
ax.add_collection(lines)
ax.legend()
ax.set_xlim([xs.min(), xs.max()]) # have to set manually
ax.set_ylim([ys.min(), ys.max()])
plt.show()

This results in the output below: A plot of the lines collected under a single legend entry.

Solution 4

So using will's suggestion and another question here, I am leaving my remedy here

handles, labels = plt.gca().get_legend_handles_labels()
i =1
while i<len(labels):
    if labels[i] in labels[:i]:
        del(labels[i])
        del(handles[i])
    else:
        i +=1

plt.legend(handles, labels)

And the new plot looks like, modified multiple line plot legend

Solution 5

A low tech solution is to make two plot calls. One that plots your data and a second one that plots nothing but carries the handle:

a = np.array([[ 3.57,  1.76,  7.42,  6.52],
              [ 1.57,  1.2 ,  3.02,  6.88],
              [ 2.23,  4.86,  5.12,  2.81],
              [ 4.48,  1.38,  2.14,  0.86],
              [ 6.68,  1.72,  8.56,  3.23]])

plt.plot(a[:,::2].T, a[:, 1::2].T, 'r')
plt.plot([],[], 'r', label='data_a')

plt.legend(loc='best')

Here's the result:

result

Share:
28,676

Related videos on Youtube

hashmuke
Author by

hashmuke

merge keep

Updated on July 09, 2022

Comments

  • hashmuke
    hashmuke almost 2 years

    I have data that results in multiple lines being plotted, I want to give these lines a single label in my legend. I think this can be better demonstrated using the example below,

    a = np.array([[ 3.57,  1.76,  7.42,  6.52],
                  [ 1.57,  1.2 ,  3.02,  6.88],
                  [ 2.23,  4.86,  5.12,  2.81],
                  [ 4.48,  1.38,  2.14,  0.86],
                  [ 6.68,  1.72,  8.56,  3.23]])
    
    plt.plot(a[:,::2].T, a[:, 1::2].T, 'r', label='data_a')
    
    plt.legend(loc='best')
    

    As you can see at Out[23] the plot resulted in 5 distinct lines. The resulting plot looks like this legend of multiple line plot

    Is there any way that I can tell the plot method to avoid multiple labels? I don't want to use custom legend (where you specify the label and the line shape all at once) as much as I can.

    • will
      will over 9 years
      You need newscast to create a custom legend. Can't paste a link because my phone is being crap. Google "matplotlib manually create legend". There is an SO answer which has everything you need a couple of hits down.
    • hashmuke
      hashmuke over 9 years
      @will thanks, that is helpful
  • Philipp
    Philipp almost 4 years
    This did not work when I tried it. Throws No handles with labels found to put in legend. when calling plt.legend()
  • Chien Nguyen
    Chien Nguyen almost 4 years
    The complete code is: import numpy as np import matplotlib.pyplot as plt a = np.array([[ 3.57, 1.76, 7.42, 6.52], [ 1.57, 1.2 , 3.02, 6.88], [ 2.23, 4.86, 5.12, 2.81], [ 4.48, 1.38, 2.14, 0.86], [ 6.68, 1.72, 8.56, 3.23]]) for i in range(len(a)): plt.plot(a[i,::2].T, a[i, 1::2].T, 'r', label='data_a' if i==0 else None) plt.legend(loc='best') plt.savefig('test_plot_legend.png')
  • Night Train
    Night Train almost 4 years
    another very short solution: legs = ax.get_legend_handles_labels(); list(zip(*[[legs[0][i], legs[1][i]] for i in [legs[1].index(l) for l in set(legs[1])]]))
  • alphazwest
    alphazwest almost 3 years
    100% this should be the answer. Simple, pythonic, and generates the desired result.
  • zfj3ub94rf576hc4eegm
    zfj3ub94rf576hc4eegm almost 3 years
    If you use a set for newLabels you'll avoid iterating over the entire thing when checking for membership.
  • Pig
    Pig about 2 years
    @zfj3ub94rf576hc4eegm set doesn't preserve the elements in the order in which they were added, so your labels could appear in the wrong order in the legend. Also, when dealing with so few elements, it's not necessarily faster to use a set. Also, I think it's unnecessary to optimise plotting code :D
  • will
    will about 2 years
    @zfj3ub94rf576hc4eegm the point of using set is not to optimize it, it's to prevent the legend entries being duplicated. if you want to preserve order, then just use an ordered set.