Agregar una línea arbitraria a un diagrama de matplotlib en el cuaderno ipython

119

Soy bastante nuevo en python / matplotlib y lo uso a través del cuaderno ipython. Estoy tratando de agregar algunas líneas de anotación a un gráfico existente y no puedo averiguar cómo representar las líneas en un gráfico. Entonces, por ejemplo, si trazo lo siguiente:

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")

Obtengo el siguiente gráfico:

hermoso diagrama de dispersión

Entonces, ¿cómo agregaría una línea vertical desde (70,100) hasta (70,250)? ¿Qué tal una línea diagonal de (70,100) a (90,200)?

He intentado algunas cosas con el Line2D()resultado de nada más que confusión de mi parte. En R, simplemente usaría la función segmentos () que agregaría segmentos de línea. ¿Existe un equivalente en matplotlib?

JD Long
fuente

Respuestas:

185

Puede trazar directamente las líneas que desee alimentando el plotcomando con los datos correspondientes (límites de los segmentos):

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

(por supuesto, puede elegir el color, el ancho de línea, el estilo de línea, etc.)

De tu ejemplo:

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()

nuevo gráfico

gcalmettes
fuente
gran respuesta con excelentes y completas ilustraciones! ¡muchas muchas gracias!
JD Long
2
Corrección menor, debería leerse el código anterior x = np.arange(1, 101).
WP McNeill
Esto no dibujará una línea, sino solo un segmento. La pregunta de cómo trazar una línea arroja dos puntos dados sigue sin respuesta.
Alexey
6
@Rmano puedes evitar que los segmentos se tengan en cuenta en la leyenda agregando un argumento de etiqueta que comience con "_". Ej .:plt.plot([70, 70], [100, 250], 'k-', lw=2, label="_not in legend")
gcalmettes
1
El hecho de que 90se utilice tanto como x2y como y1conduce a mucha ambigüedad. Para cualquiera que vea esto, tenga en cuenta que [70, 90]no se refiere a un solo punto en la ubicación x1,y1. Como referencia, aquí están los significados de los valores:[x1: 70, x2: 90], [y1: 90, y2: 200]
pookie
61

No es demasiado tarde para los recién llegados .

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

También toma el rango de y, usando ymin e ymax.

salvavidas
fuente
1
Los parámetros min / max de axhline y axvline son valores escalares entre 0 y 1 que trazan líneas en referencia al borde del gráfico. Aunque es una buena herramienta, probablemente no sea la mejor solución al planteamiento del problema del autor de dibujar líneas de anotación.
binarysubstrate
3
Esto es perfecto para querer agregar una línea de anotación en el fondo que abarque todo el gráfico. Si utilizo la solución elegida anteriormente para dibujar una línea vertical en x = 1, tengo que especificar el mínimo y el máximo y, y luego el gráfico cambia de tamaño automáticamente con un búfer, por lo que la línea no se extiende por todo el gráfico, y eso es una molestia. Esto es más elegante y no cambia el tamaño de la trama.
Bonnie
40

Usando 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)

Las firmas de llamada básicas son:

vlines(x, ymin, ymax)
hlines(y, xmin, xmax)
Austin Richardson
fuente
2
eso es excelente. No había visto las funciones vline()o hline(). ¿Qué pasa con las líneas diagonales? Edité la pregunta para agregar el bit diagonal ahora que me mostró las líneas h & v.
JD Long
Intente hacer un que DataFramecontenga las coordenadas x, y y grafíquelas constyle='k-'
Austin Richardson
Gracias, eso es muy útil
Alex
6

Matplolib ahora permite 'líneas de anotación' como buscaba el OP. La annotate()función permite varias formas de conectar caminos y una flecha sin cabeza y sin tai, es decir, una línea simple, es una de ellas.

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

En la documentación dice que solo puede dibujar una flecha con una cadena vacía como primer argumento.

Del ejemplo del OP:

%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()

Ejemplo de imagen en línea

Al igual que en el enfoque de la respuesta de gcalmettes, puede elegir el color, el ancho de línea, el estilo de línea, etc.

Aquí hay una alteración en una parte del código que haría que una de las dos líneas de ejemplo sea roja, más ancha y no 100% opaca.

# 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."), 
              )

También puede agregar una curva a la línea de conexión ajustando el connectionstyle.

Wayne
fuente
1
Esto es lo que terminé necesitando. Quería trazar una línea que saliera de los límites de la trama, lo cual .plot()no puedo.
Nick S
5

En lugar de abusar de ploto annotate, que será ineficaz para muchas líneas, puede usar 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()

Figura con dos líneas trazadas mediante LineCollection

Toma una lista de líneas [l1, l2, ...], donde cada línea es una secuencia de N coordenadas ( N puede ser más de dos).

Las palabras clave de formato estándar están disponibles, aceptando un solo valor, en cuyo caso el valor se aplica a cada línea, o una secuencia de M values , en cuyo caso el valor de la i- ésima línea es values[i % M].

Qualia
fuente