Plotly: ¿Cómo establecer un color de relleno entre dos líneas verticales?

Aug 20 2020

Usando matplotlib, podemos "trivialmente" llenar el área entre dos líneas verticales usando fill_between()como en el ejemplo:

https://matplotlib.org/3.2.1/gallery/lines_bars_and_markers/fill_between_demo.html#selectively-marking-horizontal-regions-across-the-whole-axes

Usando matplotlib, puedo hacer lo que necesito:

Tenemos dos señales y estoy calculando la correlación de Pearson y Spearman de balanceo / movimiento. Cuando las correlaciones van por debajo de -0,5 o por encima de 0,5, quiero sombrear el período (azul para Pearson y naranja para Spearman). También oscurezco los fines de semana en gris en todas las parcelas.

Sin embargo, me cuesta lograr lo mismo con Plotly. Y también será de ayuda saber cómo hacerlo entre dos líneas horizontales.

Tenga en cuenta que estoy usando Plotly y Dash para acelerar la visualización de varios gráficos. Los usuarios pidieron un "tipo de cosas más dinámicas". Sin embargo, no soy un tipo de GUI y no puedo dedicar tiempo a esto, aunque necesito alimentarlos con resultados iniciales.

Por cierto, probé Bokeh en el pasado y me di por vencido por alguna razón que no recuerdo. Plotly se ve bien ya que puedo usar Python o R, que son mis principales herramientas de desarrollo.

Gracias,

Carlos

Respuestas

4 DerekO Aug 20 2020 at 04:35

No creo que haya ningún método Plotly incorporado que sea equivalente al fill_between()método de matplotlib . Sin embargo, puede dibujar formas, por lo que una posible solución es dibujar un rectángulo gris y establecer el parámetro layer="below"para que la señal siga siendo visible. También puede establecer las coordenadas del rectángulo fuera del rango de su eje para asegurarse de que el rectángulo se extienda hasta los bordes del gráfico.

Puede rellenar el área entre líneas horizontales dibujando un rectángulo y configurando los rangos de los ejes de manera similar.

import numpy as np
import plotly.graph_objects as go

x = np.arange(0, 4 * np.pi, 0.01)
y = np.sin(x)

fig = go.Figure()
fig.add_trace(go.Scatter(
    x=x,
    y=y
))

# hard-code the axes
fig.update_xaxes(range=[0, 4 * np.pi])
fig.update_yaxes(range=[-1.2, 1.2])

# specify the corners of the rectangles
fig.update_layout(
    shapes=[
    dict(
        type="rect",
        xref="x",
        yref="y",
        x0="4",
        y0="-1.3",
        x1="5",
        y1="1.3",
        fillcolor="lightgray",
        opacity=0.4,
        line_width=0,
        layer="below"
    ),
    dict(
        type="rect",
        xref="x",
        yref="y",
        x0="9",
        y0="-1.3",
        x1="10",
        y1="1.3",
        fillcolor="lightgray",
        opacity=0.4,
        line_width=0,
        layer="below"
    ),
    ]
)

fig.show()

4 vestland Aug 20 2020 at 05:53

No ha proporcionado una muestra de datos, así que voy a usar una serie de tiempo sintética para mostrarle cómo puede agregar una serie de formas con fechas de inicio y finalización definidas para varias categorías diferentes utilizando una función personalizada bgLevel


Dos líneas verticales con un relleno entre ellas se convierten rápidamente en un rectángulo. Y los rectángulos se pueden agregar fácilmente como formas usando fig.add_shape. El siguiente ejemplo le mostrará cómo encontrar fechas de inicio y finalización para períodos dados por un determinado criterio. En su caso, estos criterios son si el valor de una variable es mayor o menor que cierto nivel.

El uso de formas en lugar de trazos con fig.add_trace()le permitirá definir la posición con respecto a las capas de trazado que utilizan layer='below'. Y los contornos de las formas se pueden ocultar fácilmente usando line=dict(color="rgba(0,0,0,0)).

Gráfico 1: Figura de serie temporal con datos aleatorios:

Gráfico 2: el fondo se establece en un gris opaco cuando A > 100:

Gráfico 2: El fondo también se establece en rojo opaco cuandoD < 60

Código completo:

import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
import datetime

pd.set_option('display.max_rows', None)

# data sample
nperiods = 200
np.random.seed(123)
df = pd.DataFrame(np.random.randint(-10, 12, size=(nperiods, 4)),
                  columns=list('ABCD'))
datelist = pd.date_range(datetime.datetime(2020, 1, 1).strftime('%Y-%m-%d'),periods=nperiods).tolist()
df['dates'] = datelist 
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
df.iloc[0] = 0
df = df.cumsum().reset_index()

# function to set background color for a
# specified variable and a specified level



# plotly setup
fig = px.line(df, x='dates', y=df.columns[1:])
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,255,0.1)')
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,255,0.1)')

def bgLevels(fig, variable, level, mode, fillcolor, layer):
    """
    Set a specified color as background for given
    levels of a specified variable using a shape.
    
    Keyword arguments:
    ==================
    fig -- plotly figure
    variable -- column name in a pandas dataframe
    level -- int or float
    mode -- set threshold above or below
    fillcolor -- any color type that plotly can handle
    layer -- position of shape in plotly fiugre, like "below"
    
    """
    
    if mode == 'above':
        m = df[variable].gt(level)
    
    if mode == 'below':
        m = df[variable].lt(level)
        
    df1 = df[m].groupby((~m).cumsum())['dates'].agg(['first','last'])

    for index, row in df1.iterrows():
        #print(row['first'], row['last'])
        fig.add_shape(type="rect",
                        xref="x",
                        yref="paper",
                        x0=row['first'],
                        y0=0,
                        x1=row['last'],
                        y1=1,
                        line=dict(color="rgba(0,0,0,0)",width=3,),
                        fillcolor=fillcolor,
                        layer=layer) 
    return(fig)

fig = bgLevels(fig = fig, variable = 'A', level = 100, mode = 'above',
               fillcolor = 'rgba(100,100,100,0.2)', layer = 'below')

fig = bgLevels(fig = fig, variable = 'D', level = -60, mode = 'below',
               fillcolor = 'rgba(255,0,0,0.2)', layer = 'below')

fig.show()