2011-12-14 59 views
24

Para indicarlo en una forma general, estoy buscando una manera de unir varios puntos con una línea de color de degradado usando matplotlib, y no lo encuentro en ningún lado. Para ser más específicos, estoy trazando una caminata aleatoria 2D con una línea de un color. Pero, como los puntos tienen una secuencia relevante, me gustaría mirar la trama y ver dónde se movieron los datos. Una línea de color degradado haría el truco. O una línea con transparencia gradualmente cambiante.Cómo trazar una línea de color de degradado en matplotlib?

Estoy tratando de mejorar la visualización de mis datos. Echa un vistazo a esta hermosa imagen producida por el paquete ggplot2 de R. Estoy buscando lo mismo en matplotlib. Gracias.

enter image description here

+1

no estoy seguro de lo que entendemos por una 'línea de degradado de color': ¿Quiere decir que (por ejemplo) la caminata comienza con una línea azul y gradualmente cambia a rojo al final? ¿Puedes proporcionar un ejemplo mínimo de tu código actual que traza la caminata con una línea de un solo color? –

+0

No conozco ninguna forma de trazar degradados en líneas con matplotlib, aunque sería bueno. Puedo sugerirle que use pycairo en su lugar, allí puede usar degradados y obtener mucho más control sobre la trama. Aunque perderá algo de conveniencia de matplotlib, como eje y rango de datos automático :-( – dsign

+0

¿estás seguro? :(¿Conoces algún plan para incorporar esa función? Nunca he oído hablar de pycairo. ¿Puedes darme algunas pistas? ? @ mathematical.coffee: sí, eso es lo que quiero decir. El código puede ser un conjunto de datos simple (algunos puntos y eso es todo) – PDRX

Respuesta

18

Recientemente he respondido a una pregunta con una solicitud similar (creating over 20 unique legend colors using matplotlib). Allí mostré que puedes mapear el ciclo de colores que necesitas para trazar tus líneas en un mapa de colores. Puede usar el mismo procedimiento para obtener un color específico para cada par de puntos.

Debe elegir cuidadosamente el mapa de colores, ya que las transiciones de color a lo largo de su línea pueden parecer drásticas si el mapa de colores es colorido.

Alternativamente, puede cambiar el alfa de cada segmento de línea, que van de 0 a 1.

Incluido en el ejemplo de código siguiente es una rutina (highResPoints) para ampliar el número de puntos de su recorrido aleatorio tiene, porque si tiene muy pocos puntos, las transiciones pueden parecer drásticas. Este fragmento de código se inspiró en otra respuesta reciente que he proporcionado: 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() 

Esta figura muestra los cuatro casos:

enter image description here

+0

nice! Agregaré esto a mi colección de fragmentos de código. –

+0

@Yann: ¡Es una buena característica, Yann! No sabía sobre el mapa de colores. Pero si elimina la línea "ax.plot (x, y)" se pone mejor (menos ruido). Aún así, puedo ver 2 colores superpuestos en cada segmento. ¿Puedes arreglarlo? Este tipo de visualización puede ayudar un poco si uso un mapa de 2 colores, por ejemplo. Sin embargo, lo que estaba buscando era una transición de color uniforme incluso entre los puntos. Incluso mejor (pero quizás pedir demasiado) sería solo un color, pero con un factor alfa para que la transparencia también cambie sin problemas. Acabo de editar mi pregunta. Mira la imagen. – PDRX

+1

@Yann ¡Gracias! Tu respuesta fue de gran ayuda. Uno puede ver que te sientes cómodo con matplotlib. El enfoque "highres" es inteligente. Cuando vi su primera respuesta, inmediatamente pensé en crear puntos adicionales para hacer la graduación progresiva también, ¡pero lo hizo en un segundo! Estaba saltando, había una función de relleno en Matplotlib que podía manejar el problema y salvarme de escribir el código y tenerlo siempre cerca. Supongo que tendré que vivir con eso. – PDRX

13

Tenga en cuenta que si tiene muchos puntos, llamando plt.plot para cada línea segmento puede ser bastante lento. Es más eficiente usar un objeto LineCollection.

Utilizando el colorline recipe que podría hacer lo siguiente:

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

demasiado largo para un comentario, por lo que sólo quería confirmar que LineCollection es mucho más rápido que un bucle de borde subsegmentos.

el método LineCollection es mucho más rápido en mis manos.

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

Probaremos el trazado iterativo contra el método LineCollection anterior.

%%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 su línea para una mejor degradado de color, como la respuesta seleccionada actualmente ofrece, sigue siendo una buena idea si quieres un gradiente suave y sólo tener algunos puntos

1

He agregado mi solución usando pcolormesh Cada segmento de línea se dibuja usando un rectángulo que se está interpolando entre los colores en cada extremo. Entonces realmente se está interpolando el color, pero tenemos que pasar un grosor de la línea.

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

Cuestiones relacionadas