2011-12-14 31 views
24

Per indicarlo in una forma generale, sto cercando un modo per unire più punti con una linea di colore sfumata utilizzando matplotlib e non lo trovo da nessuna parte. Per essere più specifici, sto tracciando una passeggiata casuale 2D con una linea a colori. Ma, dato che i punti hanno una sequenza rilevante, vorrei osservare la trama e vedere dove si sono spostati i dati. Una linea colorata sfumata farebbe il trucco. O una linea con trasparenza che cambia gradualmente.Come tracciare una linea di colore sfumato in matplotlib?

Sto solo cercando di migliorare la visibilità dei miei dati. Dai un'occhiata a questa bellissima immagine prodotta dal pacchetto ggplot2 di R. Sto cercando lo stesso in matplotlib. Grazie.

enter image description here

+1

io non sono sicuro di cosa si intende per una 'linea di sfumatura di colore': Vuoi dire che (ad esempio) la passeggiata inizia con una linea blu e gradualmente cambia in rosso alla fine? Puoi fornire un esempio minimale del tuo codice corrente che traccia la camminata con una linea monocolore? –

+0

Non conosco alcun modo di tracciare i gradienti nelle linee con Matplotlib, anche se sarebbe bello. Posso suggerirti di usare pycairo invece, lì puoi sicuramente usare le sfumature e ottenere molto più controllo sulla trama. Anche se perderai un po 'di praticità da Matplotlib, come l'asse e il range automatico dei dati :-( – dsign

+0

sei sicuro? :(conosci qualche piano per incorporare questa funzione? Non ho mai sentito parlare di pycairo. Puoi darmi qualche suggerimento? ? @ mathematical.coffee: sì, questo è quello che intendo.il codice può essere un semplice set di dati (alcuni punti e questo è tutto) – PDRX

risposta

18

Recentemente ho risposto a una domanda con una richiesta simile (creating over 20 unique legend colors using matplotlib). Lì ho dimostrato che è possibile mappare il ciclo di colori necessario per tracciare le linee su una mappa a colori. Puoi usare la stessa procedura per ottenere un colore specifico per ogni coppia di punti.

È consigliabile scegliere con attenzione la mappa dei colori, poiché le transizioni di colore lungo la linea potrebbero apparire drastiche se la mappa dei colori è colorata.

In alternativa, è possibile modificare l'alfa di ciascun segmento di linea, che va da 0 a 1.

Incluso nel codice di esempio sotto è una routine (highResPoints) per espandere il numero di punti la passeggiata casuale ha, perché se hai troppi punti, le transizioni possono sembrare drastiche. Questo bit di codice è stato ispirato da un'altra risposta recente ho fornito: https://stackoverflow.com/a/8253729/717357

import numpy as np 
import matplotlib.pyplot as plt 

def highResPoints(x,y,factor=10): 
    ''' 
    Take points listed in two vectors and return them at a higher 
    resultion. Create at least factor*len(x) new points that include the 
    original points and those spaced in between. 

    Returns new x and y arrays as a tuple (x,y). 
    ''' 

    # r is the distance spanned between pairs of points 
    r = [0] 
    for i in range(1,len(x)): 
     dx = x[i]-x[i-1] 
     dy = y[i]-y[i-1] 
     r.append(np.sqrt(dx*dx+dy*dy)) 
    r = np.array(r) 

    # rtot is a cumulative sum of r, it's used to save time 
    rtot = [] 
    for i in range(len(r)): 
     rtot.append(r[0:i].sum()) 
    rtot.append(r.sum()) 

    dr = rtot[-1]/(NPOINTS*RESFACT-1) 
    xmod=[x[0]] 
    ymod=[y[0]] 
    rPos = 0 # current point on walk along data 
    rcount = 1 
    while rPos < r.sum(): 
     x1,x2 = x[rcount-1],x[rcount] 
     y1,y2 = y[rcount-1],y[rcount] 
     dpos = rPos-rtot[rcount] 
     theta = np.arctan2((x2-x1),(y2-y1)) 
     rx = np.sin(theta)*dpos+x1 
     ry = np.cos(theta)*dpos+y1 
     xmod.append(rx) 
     ymod.append(ry) 
     rPos+=dr 
     while rPos > rtot[rcount+1]: 
      rPos = rtot[rcount+1] 
      rcount+=1 
      if rcount>rtot[-1]: 
       break 

    return xmod,ymod 


#CONSTANTS 
NPOINTS = 10 
COLOR='blue' 
RESFACT=10 
MAP='winter' # choose carefully, or color transitions will not appear smoooth 

# create random data 
np.random.seed(101) 
x = np.random.rand(NPOINTS) 
y = np.random.rand(NPOINTS) 

fig = plt.figure() 
ax1 = fig.add_subplot(221) # regular resolution color map 
ax2 = fig.add_subplot(222) # regular resolution alpha 
ax3 = fig.add_subplot(223) # high resolution color map 
ax4 = fig.add_subplot(224) # high resolution alpha 

# Choose a color map, loop through the colors, and assign them to the color 
# cycle. You need NPOINTS-1 colors, because you'll plot that many lines 
# between pairs. In other words, your line is not cyclic, so there's 
# no line from end to beginning 
cm = plt.get_cmap(MAP) 
ax1.set_color_cycle([cm(1.*i/(NPOINTS-1)) for i in range(NPOINTS-1)]) 
for i in range(NPOINTS-1): 
    ax1.plot(x[i:i+2],y[i:i+2]) 


ax1.text(.05,1.05,'Reg. Res - Color Map') 
ax1.set_ylim(0,1.2) 

# same approach, but fixed color and 
# alpha is scale from 0 to 1 in NPOINTS steps 
for i in range(NPOINTS-1): 
    ax2.plot(x[i:i+2],y[i:i+2],alpha=float(i)/(NPOINTS-1),color=COLOR) 

ax2.text(.05,1.05,'Reg. Res - alpha') 
ax2.set_ylim(0,1.2) 

# get higher resolution data 
xHiRes,yHiRes = highResPoints(x,y,RESFACT) 
npointsHiRes = len(xHiRes) 

cm = plt.get_cmap(MAP) 

ax3.set_color_cycle([cm(1.*i/(npointsHiRes-1)) 
        for i in range(npointsHiRes-1)]) 


for i in range(npointsHiRes-1): 
    ax3.plot(xHiRes[i:i+2],yHiRes[i:i+2]) 

ax3.text(.05,1.05,'Hi Res - Color Map') 
ax3.set_ylim(0,1.2) 

for i in range(npointsHiRes-1): 
    ax4.plot(xHiRes[i:i+2],yHiRes[i:i+2], 
      alpha=float(i)/(npointsHiRes-1), 
      color=COLOR) 
ax4.text(.05,1.05,'High Res - alpha') 
ax4.set_ylim(0,1.2) 



fig.savefig('gradColorLine.png') 
plt.show() 

Questa figura mostra i quattro casi:

enter image description here

+0

bello! Lo aggiungerò alla mia raccolta di snippet di codice. –

+0

@ann: questa è una bella caratteristica di Yann! Non sapevo della mappa dei colori. Ma se si rimuove la linea "ax.plot (x, y)" si migliora (meno rumore). Tuttavia, posso vedere 2 colori sovrapposti in ogni segmento. Puoi aggiustarlo? Questo tipo di visualizzazione può aiutare un po 'se ad esempio utilizzo una mappa a 2 colori. Tuttavia, quello che stavo cercando era una transizione di colore uniforme anche tra i punti. Ancora meglio (ma forse chiedendo troppo) sarebbe solo un colore, ma con un fattore alfa anche per la trasparenza. Ho appena modificato la mia domanda. Guarda l'immagine. – PDRX

+1

@Yann Grazie! La tua risposta è stata di grande aiuto. Si può vedere che sei a tuo agio con matplotlib. L'approccio "highres" è intelligente. Quando ho visto la tua prima risposta, ho subito pensato di creare punti aggiuntivi per fare anche la progressione progressiva, ma l'hai fatto in un secondo! Stavo saltellando c'era una funzione buit-in in matplotlib che poteva gestire il problema e salvarmi dal scrivere il codice e averlo sempre in giro! Credo che dovrò conviverci. – PDRX

13

Si noti che se si hanno molti punti, chiamando plt.plot per ogni linea il segmento può essere piuttosto lento. È più efficiente utilizzare un oggetto LineCollection.

Utilizzando il colorline recipe si poteva effettuare le seguenti operazioni:

import matplotlib.pyplot as plt 
import numpy as np 
import matplotlib.collections as mcoll 
import matplotlib.path as mpath 

def colorline(
    x, y, z=None, cmap=plt.get_cmap('copper'), norm=plt.Normalize(0.0, 1.0), 
     linewidth=3, alpha=1.0): 
    """ 
    http://nbviewer.ipython.org/github/dpsanders/matplotlib-examples/blob/master/colorline.ipynb 
    http://matplotlib.org/examples/pylab_examples/multicolored_line.html 
    Plot a colored line with coordinates x and y 
    Optionally specify colors in the array z 
    Optionally specify a colormap, a norm function and a line width 
    """ 

    # Default colors equally spaced on [0,1]: 
    if z is None: 
     z = np.linspace(0.0, 1.0, len(x)) 

    # Special case if a single number: 
    if not hasattr(z, "__iter__"): # to check for numerical input -- this is a hack 
     z = np.array([z]) 

    z = np.asarray(z) 

    segments = make_segments(x, y) 
    lc = mcoll.LineCollection(segments, array=z, cmap=cmap, norm=norm, 
           linewidth=linewidth, alpha=alpha) 

    ax = plt.gca() 
    ax.add_collection(lc) 

    return lc 


def make_segments(x, y): 
    """ 
    Create list of line segments from x and y coordinates, in the correct format 
    for LineCollection: an array of the form numlines x (points per line) x 2 (x 
    and y) array 
    """ 

    points = np.array([x, y]).T.reshape(-1, 1, 2) 
    segments = np.concatenate([points[:-1], points[1:]], axis=1) 
    return segments 

N = 10 
np.random.seed(101) 
x = np.random.rand(N) 
y = np.random.rand(N) 
fig, ax = plt.subplots() 

path = mpath.Path(np.column_stack([x, y])) 
verts = path.interpolated(steps=3).vertices 
x, y = verts[:, 0], verts[:, 1] 
z = np.linspace(0, 1, len(x)) 
colorline(x, y, z, cmap=plt.get_cmap('jet'), linewidth=2) 

plt.show() 

enter image description here

3

troppo lungo per un commento, quindi volevo solo confermare che LineCollection è molto più veloce di un ciclo for su linea sottosegmenti.

il metodo LineCollection è molto più veloce nelle mie mani.

# Setup 
x = np.linspace(0,4*np.pi,1000) 
y = np.sin(x) 
MAP = 'cubehelix' 
NPOINTS = len(x) 

Verificheremo il tracciamento iterativo rispetto al metodo LineCollection precedente.

%%timeit -n1 -r1 
# Using IPython notebook timing magics 
fig = plt.figure() 
ax1 = fig.add_subplot(111) # regular resolution color map 
cm = plt.get_cmap(MAP) 
for i in range(10): 
    ax1.set_color_cycle([cm(1.*i/(NPOINTS-1)) for i in range(NPOINTS-1)]) 
    for i in range(NPOINTS-1): 
     plt.plot(x[i:i+2],y[i:i+2]) 

1 loops, best of 1: 13.4 s per loop

%%timeit -n1 -r1 
fig = plt.figure() 
ax1 = fig.add_subplot(111) # regular resolution color map 
for i in range(10): 
    colorline(x,y,cmap='cubehelix', linewidth=1) 

1 loops, best of 1: 532 ms per loop

upsampling la linea per una migliore sfumatura di colore, come la risposta attualmente selezionato fornisce, è ancora una buona idea se si vuole una sfumatura liscia e solo tu avere alcuni punti.

1

Ho aggiunto la mia soluzione utilizzando pcolormesh Ogni segmento di linea viene disegnato utilizzando un rettangolo che interpola tra i colori a ciascuna estremità. Quindi interpola davvero il colore, ma dobbiamo passare uno spessore della linea.

import numpy as np 
import matplotlib.pyplot as plt 

def colored_line(x, y, z=None, linewidth=1, MAP='jet'): 
    # this uses pcolormesh to make interpolated rectangles 
    xl = len(x) 
    [xs, ys, zs] = [np.zeros((xl,2)), np.zeros((xl,2)), np.zeros((xl,2))] 

    # z is the line length drawn or a list of vals to be plotted 
    if z == None: 
     z = [0] 

    for i in range(xl-1): 
     # make a vector to thicken our line points 
     dx = x[i+1]-x[i] 
     dy = y[i+1]-y[i] 
     perp = np.array([-dy, dx]) 
     unit_perp = (perp/np.linalg.norm(perp))*linewidth 

     # need to make 4 points for quadrilateral 
     xs[i] = [x[i], x[i] + unit_perp[0] ] 
     ys[i] = [y[i], y[i] + unit_perp[1] ] 
     xs[i+1] = [x[i+1], x[i+1] + unit_perp[0] ] 
     ys[i+1] = [y[i+1], y[i+1] + unit_perp[1] ] 

     if len(z) == i+1: 
      z.append(z[-1] + (dx**2+dy**2)**0.5)  
     # set z values 
     zs[i] = [z[i], z[i] ] 
     zs[i+1] = [z[i+1], z[i+1] ] 

    fig, ax = plt.subplots() 
    cm = plt.get_cmap(MAP) 
    ax.pcolormesh(xs, ys, zs, shading='gouraud', cmap=cm) 
    plt.axis('scaled') 
    plt.show() 

# create random data 
N = 10 
np.random.seed(101) 
x = np.random.rand(N) 
y = np.random.rand(N) 
colored_line(x, y, linewidth = .01) 

enter image description here

Problemi correlati