Thomas
Thomas

Reputation: 12087

Plotly: How to plot a range with a line in the center using a datetime index?

I would like to plot a line with a range around it, like on this photo:

enter image description here

I posted an original question, but didn't specify the index being a datetime index. I thought it wouldn't be important, but I was wrong.

There is an answer that covers it with a numerical index:

Plotly: How to make a figure with multiple lines and shaded area for standard deviations?

and documentation here:

https://plotly.com/python/continuous-error-bars/

but the issue of datetime index is not covered.

Here is some test data:

timestamp      price   min  mean   max  
1596267946298  100.0   100  100.5  101
1596267946299  101.0   100  100.5  101
1596267946300  102.0   98   99.5   102
1596267948301  99.0    98   99.5   102
1596267948302  98.0    98   99.5   102
1596267949303  99.0    98   995.   102

where I'd like the band to cover from min to max and the mean to be drawn in the center.

another option is to take the code from the first answer of the question posted above (Plotly: How to make a figure with multiple lines and shaded area for standard deviations?) and change the data generation to:

index = pd.date_range('1/1/2000', periods=25, freq='T')
df = pd.DataFrame(dict(A=np.random.uniform(low=-1, high=2, size=25).tolist(),
                       B=np.random.uniform(low=-4, high=3, size=25).tolist(),
                       C=np.random.uniform(low=-1, high=3, size=25).tolist()),
                  index=index)

this will work the same way but create a datetime index.

Upvotes: 4

Views: 2525

Answers (1)

vestland
vestland

Reputation: 61104

Compared to the setup in the linked question, what causes trouble is the fact that x+x[::-1] doesn't work very well with a datetime index. But if you set x=df.index in:

# add line and shaded area for each series and standards deviation
for i, col in enumerate(df):
    new_col = next(line_color)
    # x = list(df.index.values+1)
    x = df.index

And then replace x+x[::-1] with x=x.append(x[::-1]):

# standard deviation area
fig.add_traces(go.Scatter(
                            #x+x[::-1],
                            x=x.append(x[::-1]),

Then things should work out perfectly well.

Plot:

enter image description here

Complete code:

# imports
import plotly.graph_objs as go
import plotly.express as px
import pandas as pd
import numpy as np

# sample data in a pandas dataframe
np.random.seed(1)
df=pd.DataFrame(dict(A=np.random.uniform(low=-1, high=2, size=25).tolist(),
                    B=np.random.uniform(low=-4, high=3, size=25).tolist(),
                    C=np.random.uniform(low=-1, high=3, size=25).tolist(),
                    ))
df = df.cumsum()

# set daterange as index
df['dates'] = pd.date_range('2020', freq='D', periods=len(df))
df.set_index('dates', inplace=True)

# ---

# define colors as a list 
colors = px.colors.qualitative.Plotly

# convert plotly hex colors to rgba to enable transparency adjustments
def hex_rgba(hex, transparency):
    col_hex = hex.lstrip('#')
    col_rgb = list(int(col_hex[i:i+2], 16) for i in (0, 2, 4))
    col_rgb.extend([transparency])
    areacol = tuple(col_rgb)
    return areacol

rgba = [hex_rgba(c, transparency=0.2) for c in colors]
colCycle = ['rgba'+str(elem) for elem in rgba]

# Make sure the colors run in cycles if there are more lines than colors
def next_col(cols):
    while True:
        for col in cols:
            yield col
line_color=next_col(cols=colCycle)

# plotly  figure
fig = go.Figure()

# add line and shaded area for each series and standards deviation
for i, col in enumerate(df):
    new_col = next(line_color)
    x = df.index
    y1 = df[col]
    y1_upper = [(y + np.std(df[col])) for y in df[col]]
    y1_lower = [(y - np.std(df[col])) for y in df[col]]
    y1_lower = y1_lower[::-1]

    # standard deviation area
    fig.add_traces(go.Scatter(
                                #x+x[::-1],
                                x=x.append(x[::-1]),
                                y=y1_upper+y1_lower,
                                fill='tozerox',
                                fillcolor=new_col,
                                line=dict(color='rgba(255,255,255,0)'),
                                showlegend=False,
                                name=col))

    # line trace
    fig.add_traces(go.Scatter(x=df.index,
                              y=y1,
                              line=dict(color=new_col, width=2.5),
                              mode='lines',
                              name=col)
                                )
fig.update_layout(xaxis=dict(range=[df.index[1],df.index[-1]]))
fig.show()

Upvotes: 1

Related Questions