Plotly:如何为标准偏差制作具有多条线和阴影区域的图形?

Posted

技术标签:

【中文标题】Plotly:如何为标准偏差制作具有多条线和阴影区域的图形?【英文标题】:Plotly: How to make a figure with multiple lines and shaded area for standard deviations? 【发布时间】:2020-08-13 02:20:26 【问题描述】:

如何使用 Plotly 生成带有阴影标准差的线图?我正在尝试实现类似于 seaborn.tsplot 的东西。任何帮助表示赞赏。

【问题讨论】:

【参考方案1】:

我能够想出类似的东西。我在这里发布代码供其他人使用或提供任何改进建议。

import matplotlib
import random
import plotly.graph_objects as go
import numpy as np


#random color generation in plotly
hex_colors_dic = 
rgb_colors_dic = 
hex_colors_only = []
for name, hex in matplotlib.colors.cnames.items():
    hex_colors_only.append(hex)
    hex_colors_dic[name] = hex
    rgb_colors_dic[name] = matplotlib.colors.to_rgb(hex)

data = [[1, 3, 5, 4],
        [2, 3, 5, 4],
        [1, 1, 4, 5],
        [2, 3, 5, 4]]
#calculating mean and standard deviation
mean=np.mean(data,axis=0)
std=np.std(data,axis=0)

#draw figure
fig = go.Figure()
c = random.choice(hex_colors_only)
fig.add_trace(go.Scatter(x=np.arange(4), y=mean+std,
                                     mode='lines',
                                     line=dict(color=c,width =0.1),
                                     name='upper bound'))
fig.add_trace(go.Scatter(x=np.arange(4), y=mean,
                         mode='lines',
                         line=dict(color=c),
                         fill='tonexty',
                         name='mean'))
fig.add_trace(go.Scatter(x=np.arange(4), y=mean-std,
                         mode='lines',
                         line=dict(color=c, width =0.1),
                         fill='tonexty',
                         name='lower bound'))
fig.show()

【讨论】:

【参考方案2】:

以下方法对于 pandas 数据框中的列数是完全灵活的,并使用 default color cycle of plotly。如果行数超过颜色数,颜色将从一开始就被重新使用。截至目前,px.colors.qualitative.Plotly 可以替换为您可以使用px.colors.qualitative 找到的任何十六进制颜色序列:

Alphabet = ['#AA0DFE', '#3283FE', '#85660D', '#782AB6', '#565656', '#1...
Alphabet_r = ['#FA0087', '#FBE426', '#B00068', '#FC1CBF', '#C075A6', '...
[...]

完整代码:

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

# 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 = list(df.index.values+1)
    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+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=x,
                              y=y1,
                              line=dict(color=new_col, width=2.5),
                              mode='lines',
                              name=col)
                                )
# set x-axis
fig.update_layout(xaxis=dict(range=[1,len(df)]))

fig.show()

【讨论】:

这看起来很棒。谢谢 你能解释一下'tozerox'填充模式是怎么回事吗?这给出的效果与我认为 tozerox 的效果完全不同。 @Jarrad AFK。明天提醒我。但首先,你认为tozerox 会做什么? 使用数字索引,它可以工作,但我不能让它与日期时间索引一起工作;如何做到这一点? @Thomas 太棒了!有时间我去看看【参考方案3】:

我写了一个函数来扩展plotly.express.line 与 Plotly Express 相同的高级接口。 line 函数(源代码如下)的使用方式与 plotly.express.line 完全相同,但允许使用标志参数 error_y_mode (可以是 'band''bar')连续错误带。在第二种情况下,它产生与原始plotly.express.line 相同的结果。这是一个使用示例:

import plotly.express as px

df = px.data.gapminder().query('continent=="Americas"')
df = df[df['country'].isin('Argentina','Brazil','Colombia')]
df['lifeExp std'] = df['lifeExp']*.1 # Invent some error data...

for error_y_mode in 'band', 'bar':
    fig = line(
        data_frame = df,
        x = 'year',
        y = 'lifeExp',
        error_y = 'lifeExp std',
        error_y_mode = error_y_mode, # Here you say `band` or `bar`.
        color = 'country',
        title = f'Using error error_y_mode',
        markers = '.',
    )
    fig.show()

产生以下两个图:

扩展plotly.express.lineline函数的源代码是这样的:

import plotly.express as px
import plotly.graph_objs as go

def line(error_y_mode=None, **kwargs):
    """Extension of `plotly.express.line` to use error bands."""
    ERROR_MODES = 'bar','band','bars','bands',None
    if error_y_mode not in ERROR_MODES:
        raise ValueError(f"'error_y_mode' must be one of ERROR_MODES, received repr(error_y_mode).")
    if error_y_mode in 'bar','bars',None:
        fig = px.line(**kwargs)
    elif error_y_mode in 'band','bands':
        if 'error_y' not in kwargs:
            raise ValueError(f"If you provide argument 'error_y_mode' you must also provide 'error_y'.")
        figure_with_error_bars = px.line(**kwargs)
        fig = px.line(**arg: val for arg,val in kwargs.items() if arg != 'error_y')
        for data in figure_with_error_bars.data:
            x = list(data['x'])
            y_upper = list(data['y'] + data['error_y']['array'])
            y_lower = list(data['y'] - data['error_y']['array'] if data['error_y']['arrayminus'] is None else data['y'] - data['error_y']['arrayminus'])
            color = f"rgba(tuple(int(data['line']['color'].lstrip('#')[i:i+2], 16) for i in (0, 2, 4)),.3)".replace('((','(').replace('),',',').replace(' ','')
            fig.add_trace(
                go.Scatter(
                    x = x+x[::-1],
                    y = y_upper+y_lower[::-1],
                    fill = 'toself',
                    fillcolor = color,
                    line = dict(
                        color = 'rgba(255,255,255,0)'
                    ),
                    hoverinfo = "skip",
                    showlegend = False,
                    legendgroup = data['legendgroup'],
                    xaxis = data['xaxis'],
                    yaxis = data['yaxis'],
                )
            )
        # Reorder data as said here: https://***.com/a/66854398/8849755
        reordered_data = []
        for i in range(int(len(fig.data)/2)):
            reordered_data.append(fig.data[i+int(len(fig.data)/2)])
            reordered_data.append(fig.data[i])
        fig.data = tuple(reordered_data)
    return fig

【讨论】:

【参考方案4】:

其他人发布的很棒的自定义回复。如果有人对 plotly 官方网站上的代码感兴趣,请参见此处:https://plotly.com/python/continuous-error-bars/

【讨论】:

以上是关于Plotly:如何为标准偏差制作具有多条线和阴影区域的图形?的主要内容,如果未能解决你的问题,请参考以下文章

如何为具有多个 y 轴的图表设置动画(python)

Plotly:如何为每个变量创建具有不同样式和颜色的线图?

如何为具有动态高度的表格单元格添加底部阴影?

带 plotly 的树状图 - 如何为层次聚类设置自定义链接方法

iOS Swift如何为透明的UIView添加阴影

Python:如何使用plotly制作阴影区域或交替背景颜色?