Ajout d'une ligne arbitraire à un tracé matplotlib dans un notebook ipython

119

Je suis plutôt nouveau à la fois dans python / matplotlib et en l'utilisant via le notebook ipython. J'essaie d'ajouter des lignes d'annotation à un graphique existant et je ne peux pas comprendre comment rendre les lignes sur un graphique. Ainsi, par exemple, si je trace ce qui suit:

import numpy as np
np.random.seed(5)
x = arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
p =  plot(x, y, "o")

J'obtiens le graphique suivant:

beau nuage de points

Alors, comment ajouter une ligne verticale de (70 100) à (70 250)? Qu'en est-il d'une ligne diagonale de (70 100) à (90 200)?

J'ai essayé quelques trucs avec pour Line2D()résultat rien d'autre que de la confusion de ma part. Dans Rj'utiliserais simplement la fonction segments () qui ajouterait des segments de ligne. Y a-t-il un équivalent dans matplotlib?

JD Long
la source

Réponses:

185

Vous pouvez directement tracer les lignes que vous souhaitez en alimentant la plotcommande avec les données correspondantes (limites des segments):

plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)

(bien sûr, vous pouvez choisir la couleur, la largeur de la ligne, le style de ligne, etc.)

D'après votre exemple:

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")


# draw vertical line from (70,100) to (70, 250)
plt.plot([70, 70], [100, 250], 'k-', lw=2)

# draw diagonal line from (70, 90) to (90, 200)
plt.plot([70, 90], [90, 200], 'k-')

plt.show()

nouveau graphique

gcalmettes
la source
excellente réponse avec des illustrations excellentes et complètes! Merci beaucoup!
JD Long
2
Correction mineure, le code ci-dessus devrait lire x = np.arange(1, 101).
WP McNeill
Cela ne dessinera pas une ligne, mais seulement un segment. La question de savoir comment tracer une ligne en jetant deux points donnés reste sans réponse.
Alexey le
6
@Rmano vous pouvez éviter que les segments soient pris en compte dans la légende en ajoutant un argument d'étiquette commençant par "_". Ex:plt.plot([70, 70], [100, 250], 'k-', lw=2, label="_not in legend")
gcalmettes
1
Le fait que cela 90soit utilisé à la fois comme x2et et y1conduit à beaucoup d'ambiguïté. Pour toute personne qui regarde ceci, notez que [70, 90]cela ne fait pas référence à un seul point à l'emplacement x1,y1. Pour référence, voici la signification des valeurs:[x1: 70, x2: 90], [y1: 90, y2: 200]
pookie
61

Il n'est pas trop tard pour les nouveaux arrivants .

plt.axvline(x, color='r')

Il prend également la plage de y, en utilisant ymin et ymax.

lifelogger
la source
1
Les paramètres min / max de axhline et axvline sont des valeurs scalaires comprises entre 0 et 1 qui tracent des lignes en référence au bord du tracé. Bien qu'il s'agisse d'un bon outil, ce n'est probablement pas la meilleure solution à l'énoncé du problème de l'auteur concernant le dessin des lignes d'annotation.
binarysubstrate
3
Ceci est parfait pour vouloir ajouter une ligne d'annotation en arrière-plan qui couvre tout le graphique. Si j'utilise la solution choisie ci-dessus pour dessiner une ligne verticale à x = 1, je dois spécifier le min et le max y, puis le tracé se redimensionne automatiquement avec un tampon, de sorte que la ligne ne s'étire pas sur tout le tracé, et c'est un problème. Ceci est plus élégant et ne redimensionne pas l'intrigue.
Bonnie
40

Utilisation vlines:

import numpy as np
np.random.seed(5)
x = arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
p =  plot(x, y, "o")
vlines(70,100,250)

Les signatures d'appel de base sont:

vlines(x, ymin, ymax)
hlines(y, xmin, xmax)
Austin Richardson
la source
2
C'est excellent. Je n'avais pas vu les fonctions vline()ou hline(). Qu'en est-il des lignes diagonales? J'ai modifié la question pour ajouter le bit diagonal maintenant que vous m'avez montré les lignes h & v.
JD Long
Essayez de faire un DataFramecontenant les coordonnées x, y et de les tracer avecstyle='k-'
Austin Richardson
Merci, c'est très pratique
Alex
6

Matplolib autorise désormais les «lignes d'annotation» comme le recherchait l'OP. La annotate()fonction permet plusieurs formes de chemins de connexion et une flèche sans tête et sans queue, c'est-à-dire une simple ligne, en fait partie.

ax.annotate("",
            xy=(0.2, 0.2), xycoords='data',
            xytext=(0.8, 0.8), textcoords='data',
            arrowprops=dict(arrowstyle="-",
                      connectionstyle="arc3, rad=0"),
            )

Dans la documentation, il est indiqué que vous ne pouvez dessiner qu'une flèche avec une chaîne vide comme premier argument.

De l'exemple du PO:

%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")


# draw vertical line from (70,100) to (70, 250)
plt.annotate("",
              xy=(70, 100), xycoords='data',
              xytext=(70, 250), textcoords='data',
              arrowprops=dict(arrowstyle="-",
                              connectionstyle="arc3,rad=0."), 
              )

# draw diagonal line from (70, 90) to (90, 200)
plt.annotate("",
              xy=(70, 90), xycoords='data',
              xytext=(90, 200), textcoords='data',
              arrowprops=dict(arrowstyle="-",
                              connectionstyle="arc3,rad=0."), 
              )

plt.show()

Exemple d'image en ligne

Tout comme dans l'approche de la réponse de gcalmettes, vous pouvez choisir la couleur, la largeur de ligne, le style de ligne, etc.

Voici une modification apportée à une partie du code qui rendrait l'une des deux lignes d'exemple rouge, plus large et non opaque à 100%.

# draw vertical line from (70,100) to (70, 250)
plt.annotate("",
              xy=(70, 100), xycoords='data',
              xytext=(70, 250), textcoords='data',
              arrowprops=dict(arrowstyle="-",
                              edgecolor = "red",
                              linewidth=5,
                              alpha=0.65,
                              connectionstyle="arc3,rad=0."), 
              )

Vous pouvez également ajouter une courbe à la ligne de connexion en ajustant le connectionstyle.

Wayne
la source
1
C'est ce dont j'ai fini par avoir besoin. Je voulais tracer une ligne sortant des limites de l'intrigue, ce qui .plot()ne peut pas faire.
Nick S
5

Plutôt que d'abuser plotou annotate, ce qui sera inefficace pour de nombreuses lignes, vous pouvez utiliser matplotlib.collections.LineCollection:

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

np.random.seed(5)
x = np.arange(1, 101)
y = 20 + 3 * x + np.random.normal(0, 60, 100)
plt.plot(x, y, "o")

# Takes list of lines, where each line is a sequence of coordinates
l1 = [(70, 100), (70, 250)]
l2 = [(70, 90), (90, 200)]
lc = LineCollection([l1, l2], color=["k","blue"], lw=2)

plt.gca().add_collection(lc)

plt.show()

Figure avec deux lignes tracées via LineCollection

Il prend une liste de lignes [l1, l2, ...], où chaque ligne est une séquence de N coordonnées ( N peut être plus de deux).

Les mots-clés de formatage standard sont disponibles, acceptant soit une valeur unique, auquel cas la valeur s'applique à chaque ligne, soit une séquence de M values , auquel cas la valeur de la i ème ligne est values[i % M].

Qualia
la source