2015-07-17 32 views

risposta

2

Penso che sia meglio usare una leggenda pieno - altrimenti, come faranno i vostri lettori conoscere la differenza tra i due modelli, oppure i due set di dati? Vorrei fare in questo modo:

enter image description here

Ma, se si vuole veramente fare a modo tuo, è possibile utilizzare una leggenda personalizzato come mostrato in questo guide. Avrai bisogno di creare la tua classe, come fanno loro, che definisce il metodo legend_artist, che quindi aggiunge quadrati e cerchi come appropriato. Ecco la trama generato e il codice utilizzato per generarlo:

enter image description here

#!/usr/bin/env python 
import matplotlib.pyplot as plt 
import matplotlib.patches as mpatches 
import numpy as np 


# ================================== 
# Define the form of the function 
# ================================== 
def model(x, A=190, k=1): 
    return A * np.exp(-k*x/50) 

# ================================== 
# How many data points are generated 
# ================================== 
num_samples = 15 

# ================================== 
# Create data for plots 
# ================================== 
x_model = np.linspace(0, 130, 200) 

x_data1 = np.random.rand(num_samples) * 130 
x_data1.sort() 

x_data2 = np.random.rand(num_samples) * 130 
x_data2.sort() 

data1 = model(x_data1, k=1) * (1 + np.random.randn(num_samples) * 0.2) 
data2 = model(x_data2, k=2) * (1 + np.random.randn(num_samples) * 0.15) 

model1 = model(x_model, k=1) 
model2 = model(x_model, k=2) 

# ================================== 
# Plot everything normally 
# ================================== 
fig = plt.figure() 
ax = fig.add_subplot('111') 
ax.plot(x_data1, data1, 'ok', markerfacecolor='none', label='Data (k=1)') 
ax.plot(x_data2, data2, 'sk', markeredgecolor='0.5', markerfacecolor='0.5', label='Data (k=2)') 
ax.plot(x_model, model1, '-k', label='Model (k=1)') 
ax.plot(x_model, model2, '--k', label='Model (k=2)') 

# ================================== 
# Format plot 
# ================================== 
ax.set_xlabel('Distance from heated face($10^{-2}$ m)') 
ax.set_ylabel('Temperature ($^\circ$C)') 
ax.set_xlim((0, 130)) 
ax.set_title('Normal way to plot') 
ax.legend() 
fig.tight_layout() 

plt.show() 


# ================================== 
# ================================== 
# Do it again, but with custom 
# legend 
# ================================== 
# ================================== 
class AnyObject(object): 
    pass 


class data_handler(object): 
    def legend_artist(self, legend, orig_handle, fontsize, handlebox): 
     scale = fontsize/22 
     x0, y0 = handlebox.xdescent, handlebox.ydescent 
     width, height = handlebox.width, handlebox.height 
     patch_sq = mpatches.Rectangle([x0, y0 + height/2 * (1 - scale) ], height * scale, height * scale, facecolor='0.5', 
       edgecolor='0.5', transform=handlebox.get_transform()) 
     patch_circ = mpatches.Circle([x0 + width - height/2, y0 + height/2], height/2 * scale, facecolor='none', 
       edgecolor='black', transform=handlebox.get_transform()) 

     handlebox.add_artist(patch_sq) 
     handlebox.add_artist(patch_circ) 
     return patch_sq 

# ================================== 
# Plot everything 
# ================================== 
fig = plt.figure() 
ax = fig.add_subplot('111') 
d1 = ax.plot(x_data1, data1, 'ok', markerfacecolor='none', label='Data (k=2)') 
d2 = ax.plot(x_data2, data2, 'sk', markeredgecolor='0.5', markerfacecolor='0.5', label='Data (k=1)') 
m1 = ax.plot(x_model, model1, '-k', label='Model (k=1)') 
m2 = ax.plot(x_model, model2, '-k', label='Model (k=2)') 

# ax.legend([d1], handler_map={ax.plot: data_handler()}) 
ax.legend([AnyObject(), m1[0]], ['Data', 'Model'], handler_map={AnyObject: data_handler()}) 

# ================================== 
# Format plot 
# ================================== 
ax.set_xlabel('Distance from heated face($10^{-2}$ m)') 
ax.set_ylabel('Temperature ($^\circ$C)') 
ax.set_xlim((0, 130)) 
ax.set_title('Custom legend') 
fig.tight_layout() 

plt.show() 
+0

Sembra bello, ma quando ho eseguito il tuo codice di esempio, ha visualizzato "TypeError: 'L'oggetto data_handler' non è richiamabile" ... – user3737702

+0

Che versione di Python stai usando? Sembra un problema nell'interpretazione di data_handler come classe, stai eseguendo <= Python 2.1? – Joel

+0

Sto eseguendo Python 3.4.0 ... – user3737702

1

Ecco una nuova soluzione che traccerà qualsiasi raccolta di marcatori con la stessa etichetta. Non ho capito come farlo funzionare con i marker di un grafico a linee, ma puoi probabilmente fare un grafico a dispersione su un grafico a linee se necessario.

from matplotlib import pyplot as plt 
import matplotlib.collections as mcol 
import matplotlib.transforms as mtransforms 
import numpy as np 
from matplotlib.legend_handler import HandlerPathCollection 
from matplotlib import cm 


class HandlerMultiPathCollection(HandlerPathCollection): 
    """ 
    Handler for PathCollections, which are used by scatter 
    """ 
    def create_collection(self, orig_handle, sizes, offsets, transOffset): 
     p = type(orig_handle)(orig_handle.get_paths(), sizes=sizes, 
           offsets=offsets, 
           transOffset=transOffset, 
          ) 
     return p 

fig, ax = plt.subplots() 
#make some data to plot 
x = np.arange(0, 100, 10) 
models = [.05 * x, 8 * np.exp(- .1 * x), np.log(x + 1), .01 * x] 
tests = [model + np.random.rand(len(model)) - .5 for model in models] 
#make colors and markers 
colors = cm.brg(np.linspace(0, 1, len(models))) 
markers = ['o', 'D', '*', 's'] 
markersize = 50 
plots = [] 
#plot points and lines 
for i in xrange(len(models)): 
    line, = plt.plot(x, models[i], linestyle = 'dashed', color = 'black', label = 'Model') 
    plot = plt.scatter(x, tests[i], c = colors[i], s = markersize, marker = markers[i]) 
    plots.append(plot) 

#get attributes 
paths = [] 
sizes = [] 
facecolors = [] 
edgecolors = [] 
for plot in plots: 
    paths.append(plot.get_paths()[0]) 
    sizes.append(plot.get_sizes()[0]) 
    edgecolors.append(plot.get_edgecolors()[0]) 
    facecolors.append(plot.get_facecolors()[0]) 

#make proxy artist out of a collection of markers 
PC = mcol.PathCollection(paths, sizes, transOffset = ax.transData, facecolors = colors, edgecolors = edgecolors) 
PC.set_transform(mtransforms.IdentityTransform()) 
plt.legend([PC, line], ['Test', 'Model'], handler_map = {type(PC) : HandlerMultiPathCollection()}, scatterpoints = len(paths), scatteryoffsets = [.5], handlelength = len(paths)) 
plt.show() 

Plot with markers sharing a label

ho una soluzione per voi, se siete disposti a utilizzare tutti i cerchi per i marcatori e differenziare solo dal colore. Puoi utilizzare una raccolta circolare per rappresentare i marcatori e quindi avere un'etichetta della legenda per la raccolta nel suo insieme.

codice Esempio:

import matplotlib.pyplot as plt 
import matplotlib.collections as collections 
from matplotlib import cm 
import numpy as np 

#make some data to plot 
x = np.arange(0, 100, 10) 
models = [.05 * x, 8 * np.exp(- .1 * x), np.log(x + 1), .01 * x] 
tests = [model + np.random.rand(len(model)) - .5 for model in models] 
#make colors 
colors = cm.brg(np.linspace(0, 1, len(models))) 
markersize = 50 
#plot points and lines 
for i in xrange(len(models)): 
    line, = plt.plot(x, models[i], linestyle = 'dashed', color = 'black', label = 'Model') 
    plt.scatter(x, tests[i], c = colors[i], s = markersize) 
#create collection of circles corresponding to markers 
circles = collections.CircleCollection([markersize] * len(models), facecolor = colors) 
#make the legend -- scatterpoints needs to be the same as the number 
#of markers so that all the markers show up in the legend 
plt.legend([circles, line], ['Test', 'Model'], scatterpoints = len(models), scatteryoffsets = [.5], handlelength = len(models)) 
plt.show() 

Scatter and Line plot with merged legend labels

+0

Questa è un'alternativa utile, tuttavia, quando stampata in bianco e nero, è ancora difficile distinguersi. :) – user3737702

0

Si può fare questo tracciando i dati senza alcuna etichetta e quindi aggiungendo l'etichetta separatamente:

from matplotlib import pyplot as plt 
from numpy import random 

xs = range(10) 
data = random.rand(10, 2)  
fig = plt.figure() 
ax = fig.add_subplot(1, 1, 1) 
kwargs = {'color': 'r', 'linewidth': 2, 'linestyle': '--'} 

ax.plot(xs, data, **kwargs) 
ax.plot([], [], label='Model', **kwargs) 
ax.legend() 
plt.show() 

enter image description here

1

Ho trovato anche this link molto utile (codice di seguito), è un modo più semplice per gestire questo problema. In pratica si utilizza un elenco di maniglie di legenda per rendere invisibile uno dei marcatori del primo manico e sovrapporlo con il marcatore del secondo manico. In questo modo, hai entrambi i marcatori uno accanto all'altro con un'etichetta.

fig, ax = plt.subplots() 
p1 = ax.scatter([0.1],[0.5],c='r',marker='s') 
p2 = ax.scatter([0.3],[0.2],c='b',marker='o') 
l = ax.legend([(p1,p2)],['points'],scatterpoints=2) 

enter image description here

With the above code, a TupleHandler is used to create legend handles which simply overplot two handles (there are red squares behind the blue circles if you look carefylly. What you want to do is make the second marker of first handle and the first marker of the second handle invisible. Unfortunately, the TupleHandler is a rather recent addition and you need a special function to get all the handles. Otherwise, you can use the Legend.legendHandles attribute (it only show the first handle for the TupleHandler).

def get_handle_lists(l): 
    """returns a list of lists of handles. 
    """ 
    tree = l._legend_box.get_children()[1] 

    for column in tree.get_children(): 
     for row in column.get_children(): 
      yield row.get_children()[0].get_children() 
handles_list = list(get_handle_lists(l)) 
handles = handles_list[0] # handles is a list of two PathCollection. 
          # The first one is for red squares, and the second 
          # is for blue circles. 
handles[0].set_facecolors(["r", "none"]) # for the fist 
        # PathCollection, make the 
        # second marker invisible by 
        # setting their facecolor and 
        # edgecolor to "none." 
handles[0].set_edgecolors(["k", "none"]) 
handles[1].set_facecolors(["none", "b"]) 
handles[1].set_edgecolors(["none", "k"]) 
fig 

enter image description here