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

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

【问题讨论】:

    标签: python plotly plotly-python


    【解决方案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://stackoverflow.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/

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-09-30
          • 1970-01-01
          • 1970-01-01
          • 2017-03-18
          • 1970-01-01
          • 2011-12-14
          • 1970-01-01
          相关资源
          最近更新 更多