Obtener el estado del ciclo de color de matplotlib

89

¿Es posible consultar el estado actual del ciclo de color de matplotlib? En otras palabras, ¿existe una función get_cycle_stateque se comportará de la siguiente manera?

>>> plot(x1, y1)
>>> plot(x2, y2)
>>> state = get_cycle_state()
>>> print state
2

Donde espero que el estado sea el índice del siguiente color que se usará en una gráfica. Alternativamente, si devolvió el siguiente color ("r" para el ciclo predeterminado en el ejemplo anterior), eso también estaría bien.

mwaskom
fuente
Esto se puede hacer, incluso sin cambiar el estado actual, vea la respuesta a continuación
sancho.s ReinstateMonicaCellio

Respuestas:

109

Accediendo al iterador del ciclo de color

No hay un método "de cara al usuario" (también conocido como "público") para acceder al iterador subyacente, pero puede acceder a él a través de métodos "privados" (por convención). Sin embargo, no puede obtener el estado de un iteratorsin cambiarlo.

Configurar el ciclo de color

Un aparte rápido: puede establecer el ciclo de color / propiedad de varias formas (por ejemplo, ax.set_color_cycleen las versiones <1.5 o ax.set_prop_cycleren> = 1.5). Eche un vistazo al ejemplo aquí para la versión 1.5 o superior , o al estilo anterior aquí .

Accediendo al iterador subyacente

Sin embargo, aunque no existe un método de cara al público para acceder al iterable, puede acceder a él para un objeto de ejes determinado ( ax) a través de la _get_linesinstancia de la clase auxiliar. ax._get_lineses un toque de nombre confuso, pero es la maquinaria detrás de escena la que permite que el plotcomando procese todas las formas extrañas y variadas que plotse pueden llamar. Entre otras cosas, es lo que realiza un seguimiento de los colores que se asignan automáticamente. Del mismo modo, hay ax._get_patches_for_fillque controlar el ciclo a través de los colores de relleno predeterminados y las propiedades del parche.

En cualquier caso, el ciclo de color iterable es ax._get_lines.color_cyclepara líneas y ax._get_patches_for_fill.color_cycleparches. En matplotlib> = 1.5, esto ha cambiado para usar la cyclerbiblioteca , y se llama al iterable en prop_cyclerlugar de color_cycley produce una dictde propiedades en lugar de solo un color.

Con todo, harías algo como:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
color_cycle = ax._get_lines.color_cycle
# or ax._get_lines.prop_cycler on version >= 1.5
# Note that prop_cycler cycles over dicts, so you'll want next(cycle)['color']

No puede ver el estado de un iterator

Sin embargo, este objeto es un "desnudo" iterator. Podemos obtener fácilmente el siguiente elemento (por ejemplo next_color = next(color_cycle), pero eso significa que el siguiente color después de ese es el que se trazará. Por diseño, no hay forma de obtener el estado actual de un iterador sin cambiarlo.

En v1.5o superior, sería bueno obtener el cyclerobjeto que se usa, ya que podríamos inferir su estado actual. Sin embargo, el cyclerobjeto en sí no es accesible (de forma pública o privada) en ninguna parte. En cambio, solo se puede acceder a la itertools.cycleinstancia creada a partir del cyclerobjeto. De cualquier manera, no hay forma de llegar al estado subyacente del ciclador de color / propiedad.

En su lugar, haga coincidir el color del elemento trazado anteriormente

En su caso, parece que desea hacer coincidir el color de algo que acaba de trazar. En lugar de intentar determinar cuál será el color / propiedad, establezca el color / etc. de su nuevo elemento en función de las propiedades de lo que se trazó.

Por ejemplo, en el caso que describiste, haría algo como esto:

import matplotlib.pyplot as plt
import numpy as np

def custom_plot(x, y, **kwargs):
    ax = kwargs.pop('ax', plt.gca())
    base_line, = ax.plot(x, y, **kwargs)
    ax.fill_between(x, 0.9*y, 1.1*y, facecolor=base_line.get_color(), alpha=0.5)

x = np.linspace(0, 1, 10)
custom_plot(x, x)
custom_plot(x, 2*x)
custom_plot(x, -x, color='yellow', lw=3)

plt.show()

ingrese la descripción de la imagen aquí

No es la única forma, pero es más limpia que intentar obtener el color de la línea trazada de antemano, en este caso.

Joe Kington
fuente
Estoy escribiendo algunas funciones de nivel superior para automatizar tareas de trazado comunes para mi dominio de trabajo. A menudo, esto implica dibujar varios objetos matplotlib, y me gustaría que los varios objetos compartieran un color en el ciclo de color, por lo que no siempre tengo que proporcionar un argumento de color si me siento perezoso.
mwaskom
12
Si solo desea hacer coincidir el color de un objeto en particular, siempre puede elegir su color. por ejemplo, line, = ax.plot(x, y)y luego usar line.get_color()para obtener el color de la línea trazada previamente.
Joe Kington
3
¿Parece que ax._get_lines.color_cycleya no existe en 1.5?
endolito
6
Creo que el (casi) equivalente es un iterable, creo ax._get_lines.prop_cyclerque puede tener una construcción similar if 'color' in ax._get_lines._prop_keys:seguida de next(ax._get_lines.prop_cycler)['color']para hacer el equivalente de lo que se sugiere color_cycleen la respuesta. No estoy seguro de si puede obtener una iteración solo de los colores, necesitaría explorar más.
J Richard Snape
1
@endolith Seguro (y gracias), pero sentí que sería mejor sentarse en la ya muy buena y aceptada respuesta de Joe. Si no logra ponerlo para el lunes, pondré una respuesta adecuada allí en lugar de la temida "respuesta en los comentarios"
J Richard Snape
25

Aquí hay una forma que funciona en 1.5 y que, con suerte, estará preparada para el futuro, ya que no depende de métodos precedidos de guiones bajos:

colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]

Esto le dará una lista de los colores definidos en orden para el estilo actual.

Mike DePalatis
fuente
1
Funciona en 2.0.2.
KevinG
16

Nota: En las últimas versiones de matplotlib (> = 1.5) _get_linesha cambiado. Ahora necesita usar next(ax._get_lines.prop_cycler)['color']en Python 2 o 3 (o ax._get_lines.prop_cycler.next()['color']en Python 2 ) para obtener el siguiente color del ciclo de color.

Siempre que sea posible, use el enfoque más directo que se muestra en la parte inferior de la respuesta de @ joe-kington. Como _get_linesno está orientado a API, podría cambiar nuevamente de una manera no compatible con versiones anteriores en el futuro.

Y yo
fuente
¿Cómo se evita que consultar el siguiente elemento del ciclador de color cambie el estado del ciclador?
kazemakase
6

Seguro, esto lo hará.

#rainbow

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0,2*np.pi)
ax= plt.subplot(1,1,1)
ax.plot(np.sin(x))
ax.plot(np.cos(x))

rainbow = ax._get_lines.color_cycle
print rainbow
for i, color in enumerate(rainbow):
    if i<10:
        print color,

Da:

<itertools.cycle object at 0x034CB288>
r c m y k b g r c m

Aquí está la función itertools que matplotlib usa itertools.cycle

Editar: Gracias por el comentario, parece que no es posible copiar un iterador. Una idea sería volcar un ciclo completo y realizar un seguimiento del valor que está utilizando, déjeme volver a eso.

Edit2: Muy bien, esto le dará el siguiente color y creará un nuevo iterador que se comporta como si el siguiente no fuera llamado. Esto no preserva el orden de coloración, solo el siguiente valor de color, eso se lo dejo a usted.

Esto da la siguiente salida, observe que la inclinación en el gráfico corresponde al índice, por ejemplo, primero g es el gráfico más bajo y así sucesivamente.

#rainbow

import matplotlib.pyplot as plt
import numpy as np
import collections
import itertools

x = np.linspace(0,2*np.pi)
ax= plt.subplot(1,1,1)


def create_rainbow():
    rainbow = [ax._get_lines.color_cycle.next()]
    while True:
        nextval = ax._get_lines.color_cycle.next()
        if nextval not in rainbow:
            rainbow.append(nextval)
        else:
            return rainbow

def next_color(axis_handle=ax):
    rainbow = create_rainbow()
    double_rainbow = collections.deque(rainbow)
    nextval = ax._get_lines.color_cycle.next()
    double_rainbow.rotate(-1)
    return nextval, itertools.cycle(double_rainbow)


for i in range(1,10):
    nextval, ax._get_lines.color_cycle = next_color(ax)
    print "Next color is: ", nextval
    ax.plot(i*(x))


plt.savefig("SO_rotate_color.png")
plt.show()

Consola

Next color is:  g
Next color is:  c
Next color is:  y
Next color is:  b
Next color is:  r
Next color is:  m
Next color is:  k
Next color is:  g
Next color is:  c

Rotar color

arynaq
fuente
¡Gracias! Solo para aclarar, no parece que devuelva una copia, sino más bien una referencia al ciclo real. Entonces, llamar a rainbow.next () en realidad también cambiará el aspecto de la siguiente gráfica.
mwaskom
5

Solo quiero agregar a lo que @Andi dijo anteriormente. Dado que color_cycleestá obsoleto en matplotlib 1.5, debe usar prop_cycler, sin embargo, la solución de Andi ( ax._get_lines.prop_cycler.next()['color']) me devolvió este error:

AttributeError: el objeto 'itertools.cycle' no tiene atributo 'next'

El código que funcionó para mí fue: next(ax._get_lines.prop_cycler) que en realidad no está muy lejos de la respuesta original de @ joe-kington.

Personalmente, me encontré con este problema al hacer un eje twinx (), que reinicia el ciclador de color. Necesitaba una forma de hacer que los colores se ciclaran correctamente porque estaba usando style.use('ggplot'). Puede haber una manera mejor o más fácil de hacer esto, así que no dudes en corregirme.

Carson
fuente
por favor formatee su publicación como una respuesta , no como un material de discusión (de lo contrario, es un comentario), su punto principal es que next(ax._get_lines.prop_cycler)es una posible alternativa.
UmNyobe
@Carson ¿Cómo evita que la llamada next(ax._get_lines.prop_cycler)cambie realmente el estado del ciclador de color?
kazemakase
Eso es realmente lo que estaba intentando hacer: cambiar el estado del prop_cycler. Si lee la respuesta aceptada a esta pregunta, verá que no hay forma de acceder al estado de prop_cyclersin cambiar su estado porque es iterable.
Carson
El error que señala es una diferencia de Python 2 / Python 3. Gracias por señalar esto, edité mi respuesta en consecuencia.
Andi
1

Dado que matplotlib usa itertools.cycle, podemos ver todo el ciclo de color y luego restaurar el iterador a su estado anterior:

def list_from_cycle(cycle):
    first = next(cycle)
    result = [first]
    for current in cycle:
        if current == first:
            break
        result.append(current)

    # Reset iterator state:
    for current in cycle:
        if current == result[-1]:
            break
    return result

Esto debería devolver la lista sin cambiar el estado del iterador.

Úselo con matplotlib> = 1.5:

>>> list_from_cycle(ax._get_lines.prop_cycler)
[{'color': 'r'}, {'color': 'g'}, {'color': 'b'}]

o con matplotlib <1.5:

>>> list_from_cycle(ax._get_lines.color_cycle)
['r', 'g', 'b']
Freidrichen
fuente
0

La forma más sencilla posible que pude encontrar sin hacer todo el ciclo a través del ciclador es ax1.lines[-1].get_color().

Akim AKBA5439
fuente
-1

En matplotlib versión 2.2.3 hay un get_next_color()método en la _get_linespropiedad:

import from matplotlib import pyplot as plt
fig, ax = plt.subplots()
next_color = ax._get_lines.get_next_color()

get_next_color() devuelve una cadena de color html y avanza el iterador del ciclo de color.

jkokorian
fuente
-1

¿Cómo acceder al ciclo de color (y estilo completo)?

El estado actual se almacena en ax._get_lines.prop_cycler. No hay métodos incorporados para exponer la "lista base" para un genérico itertools.cycle, y en particular para ax._get_lines.prop_cycler(ver más abajo).

He publicado aquí algunas funciones para obtener información sobre un archivo itertools.cycle. Entonces uno podría usar

style_cycle = ax._get_lines.prop_cycler
curr_style = get_cycle_state(style_cycle)  # <-- my (non-builtin) function
curr_color = curr_style['color']

para obtener el color actual sin cambiar el estado del ciclo .


TL; DR

¿Dónde se almacena el ciclo de color (y estilo completo)?

El ciclo de estilo se almacena en dos lugares diferentes, uno para el predeterminado y otro para los ejes actuales (asumiendo que import matplotlib.pyplot as plty axes un controlador de eje):

default_prop_cycler = plt.rcParams['axes.prop_cycle']
current_prop_cycle = ax._get_lines.prop_cycler

Tenga en cuenta que estos tienen diferentes clases. El valor predeterminado es una "configuración de ciclo base" y no conoce ningún estado actual para ningún eje, mientras que la corriente conoce el ciclo a seguir y su estado actual:

print('type(default_prop_cycler) =', type(default_prop_cycler))
print('type(current_prop_cycle) =', type(current_prop_cycle))

[]: type(default_prop_cycler) = <class 'cycler.Cycler'>
[]: type(current_prop_cycle) = <class 'itertools.cycle'>

El ciclo predeterminado puede tener varias teclas (propiedades) para realizar el ciclo, y solo se pueden obtener los colores:

print('default_prop_cycler.keys =', default_prop_cycler.keys)
default_prop_cycler2 = plt.rcParams['axes.prop_cycle'].by_key()
print(default_prop_cycler2)
print('colors =', default_prop_cycler2['color'])

[]: default_prop_cycler.keys = {'color', 'linestyle'}
[]: {'color': ['r', 'g', 'b', 'y'], 'linestyle': ['-', '--', ':', '-.']}
[]: colors = ['r', 'g', 'b', 'y']

Incluso se podría cambiar el cycleruso para un determinado axes, después de definir eso custom_prop_cycler, con

ax.set_prop_cycle(custom_prop_cycler)

Pero no hay métodos integrados para exponer la "lista base" para un genérico itertools.cycle, y en particular para ax._get_lines.prop_cycler.

sancho.s ReinstateMonicaCellio
fuente