web-dev-qa-db-fra.com

Matplotlib - ajouter une barre de couleur à une séquence de tracés de ligne

J'ai une séquence de tracés linéaires pour deux variables (x, y) pour un certain nombre de valeurs différentes d'une variable z. J'ajouterais normalement les tracés de ligne avec des légendes comme ceci:

import matplotlib.pyplot as plt

fig = plt.figure()
ax  = fig.add_subplot(111)
# suppose mydata is a list of tuples containing (xs, ys, z) 
# where xs and ys are lists of x's and y's and z is a number. 
legns = []
for(xs,ys,z) in mydata:
   pl = ax.plot(xs,ys,color = (z,0,0))
   legns.append("z = %f"%(z))
ax.legends(legns) 
plt.show()

Mais j'ai trop de graphiques et les légendes couvriront le graphique. Je préfère avoir une barre de couleur indiquant la valeur de z correspondant à la couleur. Je ne trouve rien de tel dans la galerie et toutes mes tentatives ont échoué. Apparemment, je dois créer une collection de tracés avant d'essayer d'ajouter une barre de couleurs.

Y a-t-il un moyen facile de faire ceci? Merci.

EDIT (clarification):

Je voulais faire quelque chose comme ça:

import matplotlib.pyplot as plt
import matplotlib.cm     as cm

fig = plt.figure()
ax  = fig.add_subplot(111)
mycmap = cm.hot
# suppose mydata is a list of tuples containing (xs, ys, z) 
# where xs and ys are lists of x's and y's and z is a number between 0 and 1
plots = []
for(xs,ys,z) in mydata:
   pl = ax.plot(xs,ys,color = mycmap(z))
   plots.append(pl)
fig.colorbar(plots)
plt.show()

Mais cela ne fonctionnera pas selon la référence Matplotlib car une liste de tracés n'est pas un "mappable", quoi que cela signifie.

J'ai créé une fonction de tracé alternative en utilisant LineCollection:

def myplot(ax,xs,ys,zs, cmap):
    plot = lc([Zip(x,y) for (x,y) in Zip(xs,ys)], cmap = cmap)
    plot.set_array(array(zs))
    x0,x1 = amin(xs),amax(xs)
    y0,y1 = amin(ys),amax(ys)
    ax.add_collection(plot)
    ax.set_xlim(x0,x1)
    ax.set_ylim(y0,y1)
    return plot

xs et ys sont des listes de listes de coordonnées x et y et zs est une liste des différentes conditions pour coloriser chaque ligne. Cela semble un peu comme un cludge ... Je pensais qu'il y aurait un moyen plus soigné de le faire. J'aime la flexibilité de la fonction plt.plot().

56

Voici une façon de le faire tout en utilisant plt.plot (). Fondamentalement, vous faites un tracé à jeter et obtenez la barre de couleurs à partir de là.

import matplotlib as mpl
import matplotlib.pyplot as plt

min, max = (-40, 30)
step = 10

# Setting up a colormap that's a simple transtion
mymap = mpl.colors.LinearSegmentedColormap.from_list('mycolors',['blue','red'])

# Using contourf to provide my colorbar info, then clearing the figure
Z = [[0,0],[0,0]]
levels = range(min,max+step,step)
CS3 = plt.contourf(Z, levels, cmap=mymap)
plt.clf()

# Plotting what I actually want
X=[[1,2],[1,2],[1,2],[1,2]]
Y=[[1,2],[1,3],[1,4],[1,5]]
Z=[-40,-20,0,30]
for x,y,z in Zip(X,Y,Z):
    # setting rgb color based on z normalized to my range
    r = (float(z)-min)/(max-min)
    g = 0
    b = 1-r
    plt.plot(x,y,color=(r,g,b))
plt.colorbar(CS3) # using the colorbar info I got from contourf
plt.show()

C'est un peu inutile, mais pratique. Ce n'est pas non plus un gaspillage si vous créez plusieurs tracés car vous pouvez appeler plt.colorbar () sans régénérer les informations pour cela.

enter image description here

34
Boris

(Je sais que c'est une vieille question mais ...) Les barres de couleur nécessitent un matplotlib.cm.ScalarMappable, plt.plot produit des lignes qui ne sont pas mappables scalaires, donc, pour créer une barre de couleur, nous allons avoir besoin de faire une mappable scalaire.

D'accord. Ainsi, le constructeur d'un ScalarMappable prend une instance de cmap et norm. (les normes mettent à l'échelle les données dans la plage 0-1, les cmaps avec lesquels vous avez déjà travaillé et prenez un nombre compris entre 0-1 et renvoie une couleur). Donc dans votre cas:

import matplotlib.pyplot as plt
sm = plt.cm.ScalarMappable(cmap=my_cmap, norm=plt.normalize(min=0, max=1))
plt.colorbar(sm)

Comme vos données sont déjà comprises entre 0 et 1, vous pouvez simplifier la création de sm pour:

sm = plt.cm.ScalarMappable(cmap=my_cmap)

J'espère que cela aide quelqu'un.

MODIFIER : Pour matplotlib v1.2 ou supérieur, le code devient:

import matplotlib.pyplot as plt
sm = plt.cm.ScalarMappable(cmap=my_cmap, norm=plt.normalize(vmin=0, vmax=1))
# fake up the array of the scalar mappable. Urgh...
sm._A = []
plt.colorbar(sm)

MODIFIER : Pour matplotlib v1.3 ou supérieur, le code devient:

import matplotlib.pyplot as plt
sm = plt.cm.ScalarMappable(cmap=my_cmap, norm=plt.Normalize(vmin=0, vmax=1))
# fake up the array of the scalar mappable. Urgh...
sm._A = []
plt.colorbar(sm)

MODIFIER : Pour matplotlib v3.1 ou supérieur, simplifie:

import matplotlib.pyplot as plt
sm = plt.cm.ScalarMappable(cmap=my_cmap, norm=plt.Normalize(vmin=0, vmax=1))
plt.colorbar(sm)
104
pelson

Voici un exemple légèrement simplifié inspiré par la réponse la plus haute donnée par Boris et Hooked (Merci pour la bonne idée!):

1. Barre de couleurs discrète

La barre de couleurs discrète est plus impliquée, car la palette de couleurs générée par mpl.cm.get_cmap() n'est pas une image mappable nécessaire comme argument colorbar(). Un mappable factice doit être généré comme indiqué ci-dessous:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

n_lines = 5
x = np.linspace(0, 10, 100)
y = np.sin(x[:, None] + np.pi * np.linspace(0, 1, n_lines))
c = np.arange(1, n_lines + 1)

cmap = mpl.cm.get_cmap('jet', n_lines)

fig, ax = plt.subplots(dpi=100)
# Make dummie mappable
dummie_cax = ax.scatter(c, c, c=c, cmap=cmap)
# Clear axis
ax.cla()
for i, yi in enumerate(y.T):
    ax.plot(x, yi, c=cmap(i))
fig.colorbar(dummie_cax, ticks=c)
plt.show();

Cela produira un tracé avec une barre de couleur discrète: enter image description here


2. Barre de couleurs continue

La barre de couleurs continue est moins impliquée, car mpl.cm.ScalarMappable() nous permet d'obtenir une "image" pour colorbar().

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl


n_lines = 5
x = np.linspace(0, 10, 100)
y = np.sin(x[:, None] + np.pi * np.linspace(0, 1, n_lines))
c = np.arange(1, n_lines + 1)

norm = mpl.colors.Normalize(vmin=c.min(), vmax=c.max())
cmap = mpl.cm.ScalarMappable(norm=norm, cmap=mpl.cm.jet)
cmap.set_array([])

fig, ax = plt.subplots(dpi=100)
for i, yi in enumerate(y.T):
    ax.plot(x, yi, c=cmap.to_rgba(i + 1))
fig.colorbar(cmap, ticks=c)
plt.show();

Cela produira un tracé avec une barre de couleur continue: enter image description here

[Note latérale] Dans cet exemple, je ne sais personnellement pas pourquoi cmap.set_array([]) est nécessaire (sinon nous aurions des messages d'erreur ). Si quelqu'un comprend les principes sous le capot, veuillez commenter :)

13
Shan Dou

Comme d'autres réponses ici essaient d'utiliser des tracés factices, ce qui n'est pas vraiment un bon style, voici un code générique pour un

Barre de couleurs discrète

Une barre de couleurs discrète est produite de la même manière qu'une barre de couleurs continue est créée, juste avec une normalisation différente. Dans ce cas, un BoundaryNorm doit être utilisé.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors

n_lines = 5
x = np.linspace(0, 10, 100)
y = np.sin(x[:, None] + np.pi * np.linspace(0, 1, n_lines))
c = np.arange(1., n_lines + 1)

cmap = plt.get_cmap("jet", len(c))
norm = matplotlib.colors.BoundaryNorm(np.arange(len(c)+1)+0.5,len(c))
sm = plt.cm.ScalarMappable(norm=norm, cmap=cmap)
sm.set_array([])  # this line may be ommitted for matplotlib >= 3.1

fig, ax = plt.subplots(dpi=100)
for i, yi in enumerate(y.T):
    ax.plot(x, yi, c=cmap(i))
fig.colorbar(sm, ticks=c)
plt.show()

enter image description here