plotly从入门到精通-实战篇

plotly从入门到精通-实战篇

前言

不同于《基础篇》的通用性,《实战篇》更注重实际应用。在本篇文章中,将会从实际业务场景出发,向大家介绍更贴近工程实践的图像绘制方式。具体来说,《实战篇》分为以下三部分内容:

  • 初级实战:动态添加trace
  • 中级实战:双轴图
  • 高级实战:双轴多子图

在正文开始之前,先导入需要用到的模块。

import pandas as pd
import plotly
import plotly.graph_objs as go
from plotly.subplots import make_subplots

初级实战:动态添加 trace

先来看这样一份数据:

>>> df = pd.DataFrame({'type': ['wine', 'beer', 'milk', 'juice', 
                                'wine', 'beer', 'milk', 'juice'], 
                       'year': [2020, 2020, 2020, 2020, 
                                2021, 2021, 2021, 2021], 
                       'amt': [750, 820, 600, 453, 
                               560, 720, 750, 650]})
>>> df
    type  year  amt
0   wine  2020  750
1   beer  2020  820
2   milk  2020  600
3  juice  2020  453
4   wine  2021  560
5   beer  2021  720
6   milk  2021  750
7  juice  2021  650

如果说,我们需要比较不同类型的酒水、2020年和2021年销售金额的差异,根据《基础篇》讲解的内容,可以轻松绘制一份柱状图:

df_2020 = df.query('year==2020').copy()
trace_01 = go.Bar(x=df_2020['type'], 
                  y=df_2020['amt'], 
                  name=2020)

df_2021 = df.query('year==2021').copy()
trace_02 = go.Bar(x=df_2021['type'], 
                  y=df_2021['amt'], 
                  ame=2021)

traces = [trace_01, trace_02]
layout = {'title': "Comparison of sales amounts in 2020 and 2021", 
          'xaxis.title': 'type', 
          'yaxis.title': 'amt'}

fig = go.Figure(data=traces, layout=layout)
fig.show()

plotly_advanced_01

毫无疑问,我们实现了最初的需求。但这样就足够了吗?

思考这样一种场景:如果需要比较四年的数据呢?要手动生成四个trace吗?如果有更多的年份呢?

显然还存在着更优解,那就是fig.add_trace

Signature: fig.add_trace(trace, row=None, col=None, secondary_y=None)
Docstring:
Add a trace to the figure

Parameters
----------
trace : BaseTraceType or dict
    Either:
      - An instances of a trace classe from the plotly.graph_objs
        package (e.g plotly.graph_objs.Scatter, plotly.graph_objs.Bar)
      - or a dicts where:

          - The 'type' property specifies the trace type (e.g.
            'scatter', 'bar', 'area', etc.). If the dict has no 'type'
            property then 'scatter' is assumed.
          - All remaining properties are passed to the constructor
            of the specified trace type.

这里主要介绍的参数就是trace,其余参数在高级实战中会见到。对于参数trace,接收两种形式的参数:

  • 类的实例。实际就是go.Bargo.Scatter等生成的实例,这在上面的代码中已经出现了;
  • 字典。看到帮助文档中的描述,不知各位读者是否还记得fig.to_dict()['data']。直接来看具体的例子:
fig = go.Figure()
fig.add_trace({'name': '2020', 
               'x': ['wine', 'beer', 'milk', 'juice'],
               'y': [750, 820, 600, 453],
               'type': 'bar'})
fig.add_trace({'name': '2021',
               'x': ['wine', 'beer', 'milk', 'juice'],
               'y': [560, 720, 750, 650],
               'type': 'bar'})

传入fig.add_trace的内容和fig.to_dict()['data']返回的内容一模一样。

虽然使用字典传参也能将图像画出来,但有了pandas的加持,很难想象会有人用这种方式画图。

接下来,使用fig.add_trace,改写最初的代码,并添加更多的细节:

fig = go.Figure()
for year in [2020, 2021]:
    
    df_temp = df.query('year==@year').copy()
    
    trace = go.Bar(x=df_temp['type'], 
                   y=df_temp['amt'], 
                   name=year, 
                   text=df_temp['amt'], 
                   textposition='outside')
    
    fig.add_trace(trace)

layout = {'title': {'text': 'Comparison of sales amounts in 2020 and 2021', 
                    'x': 0.5, 
                    'xanchor': 'center'}, 
          'xaxis.title': 'type', 
          'yaxis.title': 'amt', 
          'font.size': 13}
fig.update_layout(layout)
fig.show()

plotly_advanced_02

对于动态添加trace的内容就先介绍这么多,现在想扯一些“题外话”。对于有matplotlib使用经验的读者来说,可能会敏锐地注意到一个问题。这里先卖个关子,不妨在揭晓谜底之前用matplotlib尝试画一下上面的图像:

fig, ax = plt.subplots(figsize=(12, 7))
for year in [2020, 2021]:
    df_temp = df.query('year==@year').copy()
    ax.bar(x=df_temp['type'], 
           height=df_temp['amt'], 
           label=year, 
           width=0.6)
ax.set_title('Comparison of sales amounts in 2020 and 2021')
ax.set_xlabel('type')
ax.set_ylabel('amt')
ax.legend()

plotly_advanced_03

如各位所见,bar发生了重叠。实际上,要解决这个问题并不难,只要计算好bar的宽度以及每个bar应该出现的位置,就能实现并列的效果(seaborn通过参数hue可以轻松实现这一点):

import matplotlib.pyplot as plt
import numpy as np

width, height = 10, 5
fig, ax = plt.subplots(figsize=(width, height))

# the bar width
bar_width = (width - 1) / df['type'].nunique() * 0.3
hue_width = bar_width / df['year'].nunique()
x_offset = (bar_width - hue_width) / 2

for index,year in enumerate([2020, 2021]):
    df_temp = df.query('year==@year').copy()
    ax.bar(x=np.array(range(len(df_temp['type']))) + index * hue_width - x_offset, 
           height=df_temp['amt'], 
           label=year, 
           width=hue_width)
ax.set_title('Comparison of sales amounts in 2020 and 2021')
ax.set_xlabel('type')
ax.set_ylabel('amt')
ax.set_xticks(np.array(range(len(df_temp['type']))))
ax.set_xticklabels([i for i in df_temp['type']])
ax.legend()

plotly_advanced_04

对于matplotlib而言,bar会发生重叠,所以需要进行额外的处理。但对于plotly,重叠的现象并没有发生。原因就隐藏在layout的属性之中。在go.Figure的帮助文档中,我们可以找到barmode这个参数:

barmode
    Determines how bars at the same location
    coordinate are displayed on the graph. With
    "stack", the bars are stacked on top of one
    another With "relative", the bars are stacked
    on top of one another, with negative values
    below the axis, positive values above With
    "group", the bars are plotted next to one
    another centered around the shared location.
    With "overlay", the bars are plotted over one
    another, you might need to an "opacity" to see
    multiple bars.

barmode参数默认的取值就是group,所以才避免了重叠的情况。barmode还有其他的取值,感兴趣的读者可以自行尝试。

中级实战:双轴图

先来介绍绘制双轴图使用的数据:

>>> df
    year  week  cnt  temperature
0   2018     1   25            9
1   2018     2   17            7
2   2018     3   19           13
3   2018     4   16            6
4   2018     5   16            7
..   ...   ...  ...          ...
47  2018    48   23           18
48  2018    49   21           15
49  2018    50   21           11
50  2018    51   22           14
51  2018    52   26            9

这是2018年52个周的销售数据和气温数据。现在,想通过折线图,来同时观察销量和气温随时间推移的变化。这个需求很好理解,但关键的地方就在于,销量和气温有各自的数量级,实际上就是两个完全不同的指标,所以不能将他们简单绘制在同一个y轴上。由此,绘制双轴图的需求就产生了。

在新建trace的过程中,可以指定trace要参考的坐标轴,以下是从go.Scatter中摘录出来的内容:

xaxis
    Sets a reference between this trace's x coordinates and
    a 2D cartesian x axis. If "x" (the default value), the
    x coordinates refer to `layout.xaxis`. If "x2", the x
    coordinates refer to `layout.xaxis2`, and so on.
yaxis
    Sets a reference between this trace's y coordinates and
    a 2D cartesian y axis. If "y" (the default value), the
    y coordinates refer to `layout.yaxis`. If "y2", the y
    coordinates refer to `layout.yaxis2`, and so on.

依照帮助文档的指示,我们可以这样来组织代码:

trace_01 = go.Scatter(name='cnt', 
                      x=df['week'], 
                      y=df['cnt'])
trace_02 = go.Scatter(name='temperature', 
                      x=df['week'], 
                      y=df['temperature'], 
                      yaxis='y2')
fig = go.Figure(data=[trace_01, trace_02])
fig.show()

plotly_advanced_05

但奇怪的事情发生了:指定的y2轴确实起作用了,但yy2堆叠到了一起,y2甚至覆盖掉了y。接下来,需要对此进行改进。我们目标有两个:

  • 让两个y轴的数据都能显示出来;
  • y在左边、y2在右边;

为了实现这两点,必须要对坐标轴进行更精细的调整。

刚刚摘录出来的帮助文档已经给出了提示:If "y2", the y coordinates refer to layout.yaxis2, and so on.。在go.Figure的帮助文档中,对于layout介绍的部分,可以找到如下有关坐标轴的信息:

xaxis
    :class:`plotly.graph_objects.layout.XAxis`
    instance or dict with compatible properties
yaxis
    :class:`plotly.graph_objects.layout.YAxis`
    instance or dict with compatible properties

虽然layout中没有提到yaxis2,但当traceyaxis设定为y2时,yaxis2就在layout中诞生了。更重要的是,yaxisyaxis2都是plotly.graph_objects.layout.YAxis的实例,这意味着二者需要的参数是一致的。

接下来以yaxis为例,在plotly.graph_objects.layout.YAxis的帮助文档中,可以找到如下的内容:

overlaying
    If set a `overlaying` id, this axis is overlaid on
    top of the corresponding same-letter axis, with traces
    and axes visible for both axes. If False, this axis
    does not overlay any same-letter axes. In this case,
    for axes with overlapping domains only the highest-
    numbered axis will be visible.
side
    Determines whether a x (y) axis is positioned at the
    "bottom" ("left") or "top" ("right") of the plotting
    area.

对于overlaying,关键是要理解帮助文档中所说的same-letter axis,翻译过来就是“同字母的轴”。以当前的例子进行说明,yy2就是同字母的轴。从代码中我们还能得知,y2trace覆盖在y之上,这里暂时用[y2, y]来表示这种顺序。

按照overlaying的说明,如果yaxis={'overlaying': 'y2'},顺序就变成了[y, y2],也即ytrace会变成顶部的trace,同时自身会变成透明,在其之下的y2trace也会显示出来。同理,如果yaxis2={'overlaying': 'y'},顺序仍会是[y2, y],但y2trace就会变成透明,ytrace就能显示出来了。

对于side这个参数,就很容易理解了。如果设置yaxis,可以选择left或者right;如果设置xaxis,可以选择bottom或者top。当前,我们的需求是把y2放到右边,只需要设置yaxis2={'side': 'right'}就可以了。

下面是完整的代码:

trace_01 = go.Scatter(name='cnt', 
                      x=df['week'], 
                      y=df['cnt'])
trace_02 = go.Scatter(name='temperature', 
                      x=df['week'], 
                      y=df['temperature'], 
                      yaxis='y2')
fig = go.Figure(data=[trace_01, trace_02])

layout = {'title': {'text': 'Comparison of cnt and temperature', 
                    'x': 0.5, 
                    'xanchor': 'center'}, 
          'xaxis.title': 'week', 
          'yaxis.title': 'cnt', 
          'yaxis2': {'title': 'temperature', 
                     'overlaying': 'y', 
                     'side': 'right'},
          'font.size': 13, 
          'legend': {'x': 1, 
                     'y': 1.02, 
                     'xanchor': 'right', 
                     'yanchor': 'bottom', 
                     'orientation': 'h'},
         }
fig.update_layout(layout)
fig.show()

plotly_advanced_06

至此,绘制双轴图的需求就实现好了。总结下来,过程其实非常简单,总共分两步:

  • trace中,将yaxis设置成y2
  • layout中,将yaxis2overlaying设置成y、将side设置成right

双轴图就这样画出来了。

但实际上,overlaying这个参数却有很多值得探讨的内容。举一个简单例子:

trace_01 = go.Scatter(name='y', x=[1, 2, 3, 4, 5], y=[1, 1, 1, 1, 1])
trace_02 = go.Scatter(name='y2', x=[1, 2, 3, 4, 5], y=[2, 2, 2, 2, 2], yaxis='y2')
trace_03 = go.Scatter(name='y3', x=[1, 2, 3, 4, 5], y=[3, 3, 3, 3, 3], yaxis='y3')

fig = go.Figure(data=[trace_01, trace_02, trace_03])
# 图像省略,读者可自行绘制

毫无疑问,我们只能看到y3,因为它的trace在最顶端,yy2都被y3给遮挡住了,此时trace的呈现顺序为[y3, y2, y]。但在layout中,对yaxis.overlaying进行不同的设置,会出现一些有趣的结果:

  • 如果设置yaxis={'overlaying': 'y3'},此时顺序会变成[y, y3, y2],同时y也会变成透明,底下的y3也能显示出来。但是,我们看不到y2,因为y3还不是透明的,y2依旧处于被遮挡的状态;
  • 如果设置yaxis2={'overlaying': 'y3'},此时顺序会变成[y2, y3, y],但我们只能看到y2y3,看不到y,因为y3不透明;
  • 如果设置yaxis={'overlaying': 'y2'},注意,此时顺序会变成[y3, y, y2]y3依旧在最顶端,只有yy2的顺序发生了变化。最终,虽然y会变透明,但y3没有,所以我们还是只能看到y3
  • 如果设置yaxis={'overlaying': 'y3'}, yaxis2={'overlaying': 'y3'},此时顺序会变成[y2, y, y3],同时,所有的trace都能看见了。

说到这里,overlaying的使用规律基本已经摸清了,所以对于它的探索也可以告一段落了。

overlaying这种表面人畜无害实则包藏祸心的参数在plotly中不是少数,感兴趣的读者可以在实践过程中多尝试几次。探索的过程也未尝不充满乐趣。

高级实战:双轴多子图

中级实战:双轴图中,我们使用了一年的数据去绘制图像,如果说,现在需要绘制四年数据的图像,甚至更进一步,将四年的图像以 2 ∗ 2 2*2 22 的布局放在同一个画布上,这样的需求,要如何去实现呢?

先来看一下需要用到的数据:

>>> df
     year  week  cnt  temperature
0    2018     1   25            9
1    2018     2   17            7
2    2018     3   19           13
3    2018     4   16            6
4    2018     5   16            7
..    ...   ...  ...          ...
203  2021    48   12           15
204  2021    49   11           13
205  2021    50   11           16
206  2021    51   11           12
207  2021    52   11           12

[208 rows x 4 columns]

这是2018年到2021年共计四年208个周的销售数据和气温数据,通过下面的代码,就可以将它们呈现在 2 ∗ 2 2*2 22 的多子图中:

from plotly.subplots import make_subplots

fig = make_subplots(rows=2, cols=2, 
                    vertical_spacing=0.12, 
                    horizontal_spacing=0.09, 
                    subplot_titles=['2018', '2019', '2020', '2021'], 
                    specs=[
                        [{'secondary_y': True}, {'secondary_y': True}], 
                        [{'secondary_y': True}, {'secondary_y': True}],
                          ], 
                   )
# 统一 y 轴的范围
min_y1 = df['cnt'].min()
max_y1 = df['cnt'].max()
adjust_y1 = (max_y1 - min_y1) * 0.05

min_y2 = df['temperature'].min()
max_y2 = df['temperature'].max()
adjust_y2 = (max_y2 - min_y2) * 0.05

# 使用循环绘制 4 张子图
for year,(row,col),showlegend in [[2018, (1,1), True], [2019, (1,2), False], 
                                  [2020, (2,1), False], [2021, (2,2), False]]:
    df_temp = df.query('year==@year').copy()
    trace_01 = go.Scatter(name='cnt', 
                          x=df_temp['week'], 
                          y=df_temp['cnt'], 
                          marker=dict(color="#636EFA"), 
                          legendgroup='indicator', 
                          showlegend=showlegend, 
                         )
    fig.add_trace(trace_01,
                  row=row, 
                  col=col, 
                  secondary_y=False,  # 主轴
                 )

    trace_02 = go.Scatter(name='temperature', 
                          x=df_temp['week'], 
                          y=df_temp['temperature'], 
                          marker=dict(color="black"), 
                          legendgroup='factor', 
                          showlegend=showlegend, 
                         )
    fig.add_trace(trace_02, 
                  row=row, 
                  col=col, 
                  secondary_y=True,  # 副轴
                 )

# 调整图像的格式
fig.update_layout(height=800, 
                  width=1400, 
                  title=dict(text='Comparison of cnt and temperature', 
                             x=0.5, 
                             xanchor='center'),
                  legend=dict(orientation="h", 
                              x=0.95, 
                              y=1.03, 
                              xanchor='right', 
                              yanchor='bottom'
                             ),
                 )
fig.update_xaxes(title='week')
fig.update_yaxes(title='cnt', 
                 range=[min_y1 - adjust_y1, max_y1 + adjust_y1], 
                 secondary_y=False)
fig.update_yaxes(title='temperature', 
                 range=[min_y2 - adjust_y2, max_y2 + adjust_y2], 
                 secondary_y=True)
fig.show()

plotly_advanced_07

下面对代码进行详细地说明:

  1. make_subplots

    Signature: make_subplots(rows=1, cols=1, shared_xaxes=False, shared_yaxes=False, start_cell='top-left', print_grid=False, horizontal_spacing=None, vertical_spacing=None, subplot_titles=None, column_widths=None, row_heights=None, specs=None, insets=None, column_titles=None, row_titles=None, x_title=None, y_title=None, **kwargs)
    Docstring:
    Return an instance of plotly.graph_objs.Figure with predefined subplots configured in 'layout'.
    

    Docstring的描述中,我们就能知道,make_subplotsgo.Figure返回的是同一个对象,只不过make_subplots会提前对layout进行一些配置。下面是在代码中是使用的参数,其余参数感兴趣的读者可以自行尝试:

    • rows & cols:整个画布中,子图的行数和列数。也即每行或每列子图的数量

    • horizontal_spacing & vertical_spacing:子图间的水平间距和垂直间距

    • subplot_titles:子图的标题,按照从左往右、从上到下的顺序依次和子图对应起来

    • specs:用来对每一个子图进行精细设置。它接收一个二维列表,外层索引对应子图的行序号,内层索引对应列序号。在列表的内部,接收一个字典,字典的键值对就是属性及其取值,下面来看从帮助文档中摘录出来的内容:

      specs: list of lists of dict or None (default None)
          Per subplot specifications of subplot type, row/column spanning, and
          spacing.
      
          ex1: specs=[[{}, {}], [{'colspan': 2}, None]]
      
          ex2: specs=[[{'rowspan': 2}, {}], [None, {}]]
      
          - Each item in 'specs' is a dictionary.
              The available keys are:
              ......
              * secondary_y (bool, default False): If True, create a secondary
                  y-axis positioned on the right side of the subplot. Only valid
                  if type='xy'.
      

      在上面的代码中,我们对子图的secondary_y属性进行了设置,为所有子图开启了右侧的副轴。

  2. go.Scatter

    legendgroup
        Sets the legend group for this trace. Traces part of
        the same legend group hide/show at the same time when
        toggling legend items.
    showlegend
        Determines whether or not an item corresponding to this
        trace is shown in the legend.
    

    先说一下使用这两个参数的原因。在代码中将这两个参数注释掉,得到的图像将会是这样:

    ![plotly_advanced_08][]

    差别就在图例上。我们可以总结出以下两点:

    • 每个子图,都有一组图例,并且在操作某一子图的图例时,不会影响到其他三个子图;
    • 四个子图的图例都是重复的;

    对于当前的需求而言,只需要保留一组图例。同时,还需要将所有子图的图例都统一起来,用一组图例来同时控制四个子图。legendgroupshowlegend就用来实现这样的功能。

    为了将四个子图统一起来,我们就需要使用legendgroup,对子图的trace进行编组。在代码中,我们分别对cnttemperature进行了编组,这样就能保证子图间图例行为的一致性。那既然图例的行为统一了,接下来只要保留一组图例就可以了。showlegend就帮助我们将其余三组图例隐藏起来。

  3. fig.add_trace

    现在,正式回收在初级实战中遗留下来的问题。

    row : int or None (default None)
        Subplot row index (starting from 1) for the trace to be added.
        Only valid if figure was created using
        `plotly.subplots.make_subplots`
    col : int or None (default None)
        Subplot col index (starting from 1) for the trace to be added.
        Only valid if figure was created using
        `plotly.subplots.make_subplots`
    secondary_y: boolean or None (default None)
        If True, associate this trace with the secondary y-axis of the
        subplot at the specified row and col. Only valid if all of the
        following conditions are satisfied:
          * The figure was created using `plotly.subplots.make_subplots`.
          * The row and col arguments are not None
          * The subplot at the specified row and col has type xy
            (which is the default) and secondary_y True.  These
            properties are specified in the specs argument to
            make_subplots. See the make_subplots docstring for more info.
          * The trace argument is a 2D cartesian trace
            (scatter, bar, etc.)
    

    如大家所见,剩下的三个参数都跟子图相关。

    • row & col:将trace定位到指定的子图上,比如row=2, col=1指的就是第二行第一列的子图。
    • secondary_y:是否将trace关联到副轴上。在代码中,我们将温度的数据全都关联到了副轴上。

    中级实战:双轴图里,我们通过设置tracelayoutyaxis参数来实现双轴的效果,而绘制子图时,主要依靠secondary_y参数来实现双轴的效果。

  4. fig.update_xaxes

    在之前的讲解中,主要是通过fig.update_layout来对xaxis进行设置。但到了多子图中,fig.update_layout只能修改一个子图,无法满足我们当前的需求。所以,我们选择使用fig.update_xaxes,以达到修改所有子图的目的:

    Signature: fig.update_xaxes(patch=None, selector=None, overwrite=False, 
                                row=None, col=None, **kwargs)
    Docstring:
    Perform a property update operation on all xaxis objects
    that satisfy the specified selection criteria
    
    Parameters
    ----------
    patch: dict
        Dictionary of property updates to be applied to all
        xaxis objects that satisfy the selection criteria.
    selector: dict or None (default None)
        Dict to use as selection criteria.
        xaxis objects will be selected if they contain
        properties corresponding to all of the dictionary's keys, with
        values that exactly match the supplied values. If None
        (the default), all xaxis objects are selected.
    

    从帮助文档的内容来看,是不是有种似曾相识的感觉。没错,和fig.update_traces十分相似,尤其是最核心的两个参数patchselector,连使用规则都一样,只不过当前接收的参数,都来自plotly.graph_objects.layout.XAxis

  5. fig.update_yaxes

    使用这个方法的原因有两个,一是要同时修改所有子图,二是要分别修改主轴和副轴。先看帮助文档:

    Signature: fig.update_yaxes(patch=None, selector=None, overwrite=False, 
                                row=None, col=None, secondary_y=None, **kwargs)
    Docstring:
    Perform a property update operation on all yaxis objects
    that satisfy the specified selection criteria
    
    • patch & selector:它们接收的参数,都来自plotly.graph_objects.layout.YAxis
    • overwrite:如果是False,更新属性;如果是True,覆盖属性。
    • row & col:用来指定待修改的子图。如果没有指定,则默认所有子图。
    • secondary_y:如果是True,将会对副轴进行属性的更新;如果是False,则会对主轴进行属性的更新

代码已经介绍完了,根据需求的变化,还可以添加更多的trace,比如有多个商品的情况:

>>> df
     year  week  item01  item02  item03  temperature
0    2018     1      25      22      28            9
1    2018     2      17      13      23            7
2    2018     3      19      14      20           13
3    2018     4      16      16      22            6
4    2018     5      16      15      17            7
..    ...   ...     ...     ...     ...          ...
203  2021    48      12      15      22           15
204  2021    49      11      11      19           13
205  2021    50      11      15      12           16
206  2021    51      11      13      21           12
207  2021    52      11       6      13           12

[208 rows x 6 columns]
item_list = ['item01', 'item02', 'item03']
colors_list = ['#636EFA', '#EF553B', '#00CC96', '#AB63FA', '#FFA15A', 
               '#19D3F3', '#FF6692', '#B6E880', '#FF97FF', '#FECB52']

fig = make_subplots(rows=2, cols=2, 
                    vertical_spacing=0.12, 
                    horizontal_spacing=0.09, 
                    subplot_titles=['2018', '2019', '2020', '2021'], 
                    specs=[
                        [{'secondary_y': True}, {'secondary_y': True}], 
                        [{'secondary_y': True}, {'secondary_y': True}],
                          ], 
                   )
# 统一 y 轴的范围
min_y1 = df.loc[:, item_list].values.min()
max_y1 = df.loc[:, item_list].values.max()
adjust_y1 = (max_y1 - min_y1) * 0.05

min_y2 = df['temperature'].min()
max_y2 = df['temperature'].max()
adjust_y2 = (max_y2 - min_y2) * 0.05

# 使用循环绘制 4 张子图
for year,(row,col),showlegend in [[2018, (1,1), True], [2019, (1,2), False], 
                                  [2020, (2,1), False], [2021, (2,2), False]]:
    df_temp = df.query('year==@year').copy()
    
    for i,item in enumerate(item_list):
        trace_01 = go.Scatter(name=item, 
                              x=df_temp['week'], 
                              y=df_temp[item], 
                              marker={"color": colors_list[i%10]}, 
                              legendgroup='group' + str(i), 
                              showlegend=showlegend, 
                             )
        fig.add_trace(trace_01,
                      row=row, 
                      col=col, 
                      secondary_y=False,  # 主轴
                     )

    trace_02 = go.Scatter(name='temperature', 
                          x=df_temp['week'], 
                          y=df_temp['temperature'], 
                          marker=dict(color="black"), 
                          legendgroup='factor', 
                          showlegend=showlegend, 
                         )
    fig.add_trace(trace_02, 
                  row=row, 
                  col=col, 
                  secondary_y=True,  # 副轴
                 )

# 调整图像的格式
fig.update_layout(height=800, 
                  width=1400, 
                  title=dict(text='Comparison of cnt and temperature', 
                             x=0.5, 
                             xanchor='center'),
                  legend=dict(orientation="h", 
                              x=0.95, 
                              y=1.03, 
                              xanchor='right', 
                              yanchor='bottom'
                             ),
                 )
fig.update_xaxes(title='week')
fig.update_yaxes(title='cnt', 
                 range=[min_y1 - adjust_y1, max_y1 + adjust_y1], 
                 secondary_y=False)
fig.update_yaxes(title='temperature', overwrite=True, 
                 range=[min_y2 - adjust_y2, max_y2 + adjust_y2], 
                 secondary_y=True)
fig.show()

![plotly_advanced_09][]

到这里,双轴多子图的讲解就结束了,读者可以在此基础上进行更多的尝试。

后记

plotly为我们构筑了一个相当广阔的世界。企图在有限的文章中将所有重点向各位读者娓娓道来,只能是痴人说梦。所以作者本人在起草两篇文章的时候,内心满怀着“授人以渔”的美好憧憬,并寄希望于读者自发进行举一反三。这实际是一种“逆势而为”。打开任何一个搜索引擎,能找到的画图教程,无一不是开门见山地讲述着实际案例,字里行间都充斥着一种务实精神。对于这种表达方式,无意推崇更无意批评,只是这样教程,过于浅显,能解决的需求也是过于单一。一旦我们的需求和教程罗列的事例有些许偏差,我们就会陷于一种尴尬的境地。总结下来就是,“我们的问题好像解决了,又好像没有解决”。这对读者的精神打击是巨大的。“好不容易”、“千辛万苦”找到的“救命稻草”,不过是一厢情愿。成年人的崩溃往往就在这一瞬间。这哪是“救命稻草”,这是“压死骆驼的最后一根稻草”。

所以,在文章写作之初,为了避免这种“破大防”的情况出现,我决定使用一种颇为冷门的讲述方式来组织这份教程,也就是从基础概念讲起,不厌其烦地带领读者翻阅有着茫茫多参数的帮助文档。这正是因为,答案基本都藏在帮助文档之中。不过,这种方式并非我的原创,虽然它一直都存在,但鲜有人用。个人推测,是读者更务实了,而作者群体嗅到了这一丝躁动,并逐渐开始灵活地迎合群众的喜好。正如萧伯纳所言,“流行的就一定高尚吗”,所以,对于这份教程来说,虽然不是用读者广泛认可的形式进行讲解,但未必没有价值,起码在涤荡鱼龙混杂的互联网创作氛围上,做出了自己的努力。

但理想终究是丰满的。在文章写作的过程中,难免有力不从心的时候。所以,借此机会,对文章中出现的瑕疵进行澄清:这并非作者本人一贯的敷衍,而是因为作者能力有限,不足以支撑丰满的理想,而瑕疵的出现,实属无心之举。但作者也在这里做出保证,一定会对文章进行持续的更新,给各位读者一个交代。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值