Logo
Section 1 / 5

子图

了解什么是子图以及如何使用你已经学过的图表类型来创建子图。

导入

子图

了解什么是子图以及如何使用你已经学过的图表类型来创建子图。

我们的数据

我们将使用两个数据集:nike 变量存储了耐克股票的高价和调整后收盘价数据,而 nike_log_returns 数据集则存储了高价和调整后收盘价列的对数收益率。我们不需要深入理解对数收益率的具体计算方式——只需知道这是一种解读金融时间序列数据的另一种方法。

让我们打印前几行数据,查看数据的结构:

1# 导入库 2import pandas as pd 3import numpy as np 4 5# 导入数据集 6nike = pd.read_csv('/usr/local/csvfiles/nike.csv') 7print(nike.head())

我们再看一下第二个数据集:

1# 导入库 2import pandas as pd 3import numpy as np 4 5# 导入数据集 6nike_log_returns = pd.read_csv('/usr/local/csvfiles/nike_log_returns.csv') 7print(nike_log_returns.head())

列对齐的子图

要在 Plotly 中创建子图,首先需要导入必要的功能:

1from plotly.subplots import make_subplots

在 make_subplots 中,可以通过传递 rows 和 cols 作为关键字参数,创建一个网格布局,从而从多个角度探索我们的数据集。

接下来,我们将创建一个单行两列的网格布局,代码如下:

1fig = make_subplots(rows=1, cols=2)

然后,在第一个子图中绘制调整后收盘价随日期的变化,在第二个子图中绘制调整后收盘价的对数收益率随日期的变化,如下所示:

1trace1 = go.Scatter(x=nike['Date'], 2 y=nike['Adj Close'], 3 name='Adj Close', 4 mode='lines') 5 6trace2 = go.Scatter(x=nike_log_returns['Date'], 7 y=nike_log_returns['Adj Close'], 8 name='Adj Close Log Returns', 9 mode='lines')

为了精确指定每个图表的位置,我们使用图形对象 fig 并调用 add_trace 方法,先传入感兴趣的轨迹,再指定希望放置图表的行和列。在此示例中,我们将 trace1 放在第1行第1列,将 trace2 放在第1行第2列。代码如下:

1fig.add_trace(trace1, row=1, col=1) 2fig.add_trace(trace2, row=1, col=2)

我们还更新了布局并显示图形,完整代码如下:

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9nike = pd.read_csv('/usr/local/csvfiles/nike.csv') 10nike_log_returns = pd.read_csv('/usr/local/csvfiles/nike_log_returns.csv') 11 12# 创建子图 13fig = make_subplots(rows=1, cols=2) 14 15# 为每个子图创建轨迹 16trace1 = go.Scatter(x=nike['Date'], 17 y=nike['Adj Close'], 18 name='Adj Close', 19 mode='lines') 20 21trace2 = go.Scatter(x=nike_log_returns['Date'], 22 y=nike_log_returns['Adj Close'], 23 name='Adj Close Log Returns', 24 mode='lines') 25 26# 将轨迹添加到图形中并显示 27fig.add_trace(trace1, row=1, col=1) 28fig.add_trace(trace2, row=1, col=2) 29 30fig.update_layout(title='价格与对数收益率') 31fig.show()

行对齐的子图

我们也可以通过将 rows 参数设为大于1的整数,并将 cols 固定为1,来将图表上下堆叠。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9nike = pd.read_csv('/usr/local/csvfiles/nike.csv') 10nike_log_returns = pd.read_csv('/usr/local/csvfiles/nike_log_returns.csv') 11 12# 创建子图 13fig = make_subplots(rows=2, cols=1) 14 15# 创建图表轨迹 16trace1 = go.Scatter(x=nike['Date'], 17 y=nike['High'], 18 name='高价', 19 mode='lines') 20 21trace2 = go.Scatter(x=nike_log_returns['Date'], 22 y=nike_log_returns['High'], 23 name='高价对数收益率', 24 mode='lines') 25 26# 添加轨迹到图形并显示 27fig.add_trace(trace1, row=1, col=1) 28fig.add_trace(trace2, row=2, col=1) 29fig.update_layout(title='价格与对数收益率') 30fig.show()

网格布局的子图

接下来我们将介绍多行多列的子图布局。start_cell="top-left" 表示 Plotly 将第一个子图放置在左上角。start_cell="bottom-left" 是另一个选项,但较少使用。

在下面的图表中,我们注意到耐克每日最高价的价格和对数收益率位于第一行,而耐克每日调整后收盘价位于第二行。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9nike = pd.read_csv('/usr/local/csvfiles/nike.csv') 10nike_log_returns = pd.read_csv('/usr/local/csvfiles/nike_log_returns.csv') 11 12# 创建子图 13fig = make_subplots(rows=2, cols=2, start_cell="top-left") 14 15# 创建4个不同的图表 16trace1 = go.Scatter(x=nike['Date'], 17 y=nike['High'], 18 name='高价 (轨迹 #1)', 19 mode='lines') 20 21trace2 = go.Scatter(x=nike_log_returns['Date'], 22 y=nike_log_returns['High'], 23 name='高价对数收益率 (轨迹 #2)', 24 mode='lines') 25 26trace3 = go.Scatter(x=nike['Date'], 27 y=nike['Adj Close'], 28 name='调整后收盘价 (轨迹 #3)', 29 mode='lines') 30 31trace4 = go.Scatter(x=nike_log_returns['Date'], 32 y=nike_log_returns['Adj Close'], 33 name='调整后收盘价对数收益率 (轨迹 #4)', 34 mode='lines') 35 36# 按网格布局添加图表 37fig.add_trace(trace1, row=1, col=1) 38fig.add_trace(trace2, row=1, col=2) 39fig.add_trace(trace3, row=2, col=1) 40fig.add_trace(trace4, row=2, col=2) 41 42# 稍微格式化并显示 43fig.update_layout(title=go.layout.Title(text='价格与对数收益率', x=0.5)) 44fig.show()

子图标题与坐标轴调整

现在我们将关注如何设置子图标题并修改坐标轴。为此,我们使用一个名为 activations 的数据集。该数据集展示了多种深度学习激活函数,并包含一个 x 列,其值在 -10 到 10 之间。其余列显示了对这些 x 值应用特定变换/激活函数后的对应结果。

1# 导入库 2import pandas as pd 3import numpy as np 4 5# 导入数据集 6activations = pd.read_csv('/usr/local/csvfiles/activations.csv') 7print(activations.head())

通过在 make_subplots 中使用 subplot_titles 参数,我们可以传入一个列表,表示希望在每个子图上放置的标题(从左上角开始,按 start_cell='top-left' 指定)。我们还可以通过 vertical_spacing 和 horizontal_spacing 参数分别调整子图之间的垂直和水平间距。请确保传入 0 到 1 之间的值,因为这些是归一化的绘图坐标。我们可以尝试不同的值,找到最适合我们需求的组合。这在以下代码中可以清楚看到:

1fig = make_subplots(rows=2, 2 cols=2, 3 start_cell='top-left', 4 subplot_titles=['ReLU 激活', 5 'Sigmoid 激活', 6 'Mish 激活', 7 'Softplus 激活'], 8 vertical_spacing=0.3, 9 horizontal_spacing=0.1)

然后我们可以绘制各列数据,并使用 add_trace 方法将每个轨迹添加到主图形中。在这个例子中,如果第一个数字代表行,第二个代表列,那么位置 (1, 1) 是流行的 ReLU 变换;(1, 2) 是 Sigmoid 变换;(2, 1) 是 Mish 变换;(2, 2) 是 Softplus 变换。

1trace1 = go.Scatter(x=activations['x'], 2 y=activations['relu'], 3 name='ReLU', 4 mode='lines') 5 6trace2 = go.Scatter(x=activations['x'], 7 y=activations['sigmoid'], 8 name='Sigmoid', 9 mode='lines') 10 11trace3 = go.Scatter(x=activations['x'], 12 y=activations['mish'], 13 name='Mish', 14 mode='lines') 15 16trace4 = go.Scatter(x=activations['x'], 17 y=activations['softplus'], 18 name='Softplus', 19 mode='lines') 20 21fig.add_trace(trace1, row=1, col=1) 22fig.add_trace(trace2, row=1, col=2) 23fig.add_trace(trace3, row=2, col=1) 24fig.add_trace(trace4, row=2, col=2)

然后我们可以选择统一更新所有子图的坐标轴,或分别自定义每个子图的坐标轴。以下是两种方式的详细说明:

假设我们希望统一更新所有 x 轴。我们可以向 update_xaxes 方法传递参数,将所有子图的坐标轴标签设为 "x",并将刻度标记颜色设为蓝色。代码如下:

1fig.update_xaxes(title_text="x", tickfont=dict(color='blue'))

接下来我们关注 y 轴,展示如何单独定制每个 y 轴。我们不再使用 update_xaxes,而是使用 update_yaxes,并通过指定行号和列号来单独修改每个子图。这里我们分别修改每个 y 轴的标题:

1# 更新 y 轴属性(按坐标轴) 2fig.update_yaxes(title_text="ReLU(x)", row=1, col=1) 3fig.update_yaxes(title_text="Sigmoid(x)", row=1, col=2) 4fig.update_yaxes(title_text="Mish(x)", row=2, col=1) 5fig.update_yaxes(title_text="Softplus(x)", row=2, col=2)

让我们看一个实际应用的例子:

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9activations = pd.read_csv('/usr/local/csvfiles/activations.csv') 10 11# 创建子图 12fig = make_subplots(rows=2, 13 cols=2, 14 start_cell='top-left', 15 subplot_titles=['ReLU 激活', 16 'Sigmoid 激活', 17 'Mish 激活', 18 'Softplus 激活'], 19 vertical_spacing=0.3, 20 horizontal_spacing=0.1) 21# 创建轨迹 22trace1 = go.Scatter(x=activations['x'], 23 y=activations['relu'], 24 name='ReLU', 25 mode='lines') 26 27trace2 = go.Scatter(x=activations['x'], 28 y=activations['sigmoid'], 29 name='Sigmoid', 30 mode='lines') 31 32trace3 = go.Scatter(x=activations['x'], 33 y=activations['mish'], 34 name='Mish', 35 mode='lines') 36 37trace4 = go.Scatter(x=activations['x'], 38 y=activations['softplus'], 39 name='Softplus', 40 mode='lines') 41 42# 将轨迹添加到图形 43fig.add_trace(trace1, row=1, col=1) 44fig.add_trace(trace2, row=1, col=2) 45fig.add_trace(trace3, row=2, col=1) 46fig.add_trace(trace4, row=2, col=2) 47 48# 更新 x 轴属性(所有子图) 49fig.update_xaxes(title_text="x", tickfont=dict(color='blue')) 50 51# 更新 y 轴属性(按坐标轴) 52fig.update_yaxes(title_text="ReLU(x)", row=1, col=1) 53fig.update_yaxes(title_text="Sigmoid(x)", row=1, col=2) 54fig.update_yaxes(title_text="Mish(x)", row=2, col=1) 55fig.update_yaxes(title_text="Softplus(x)", row=2, col=2) 56 57fig.update_layout(title=go.layout.Title(text='激活函数', x=0.5)) 58fig.show()

共享 x 轴

要将子图垂直堆叠并共享相同的 x 轴,只需在 make_subplots() 中传入 shared_xaxes=True。

这里我们更仔细地比较深度学习中 ReLU 和 Sigmoid 激活函数的差异。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9activations = pd.read_csv('/usr/local/csvfiles/activations.csv') 10 11# 创建子图 12fig = make_subplots(rows=2, 13 cols=1, 14 shared_xaxes=True, # 新增部分 15 start_cell='top-left', 16 subplot_titles=['ReLU 激活', 17 'Sigmoid 激活', 18 'Mish 激活', 19 'Softplus 激活']) 20# 创建轨迹并添加到图形 21trace1 = go.Scatter(x=activations['x'], 22 y=activations['relu'], 23 name='ReLU', 24 mode='lines') 25 26trace2 = go.Scatter(x=activations['x'], 27 y=activations['sigmoid'], 28 name='Sigmoid', 29 mode='lines') 30 31fig.add_trace(trace1, row=1, col=1) 32fig.add_trace(trace2, row=2, col=1) 33 34# 更新 x 轴属性(所有子图) 35fig.update_xaxes(title_text="x", row=2, col=1) 36 37# 更新 y 轴属性(按坐标轴) 38fig.update_yaxes(title_text="ReLU(x)", row=1, col=1) 39fig.update_yaxes(title_text="Sigmoid(x)", row=2, col=1) 40 41fig.update_layout(title='深度学习激活函数') 42fig.show()

共享 y 轴

如果我们将子图水平堆叠,可以通过 shared_yaxes=True 实现共享 y 轴。

这里我们观察 ReLU 和 Softplus 深度学习激活函数之间的差异。

1# 导入库 2import pandas as pd 3import numpy as np 4import plotly.express as px 5import plotly.graph_objects as go 6from plotly.subplots import make_subplots 7 8# 导入数据集 9activations = pd.read_csv('/usr/local/csvfiles/activations.csv') 10 11# 创建子图 12fig = make_subplots(rows=1, 13 cols=2, 14 shared_yaxes=True, # 新增部分 15 subplot_titles=['ReLU 激活', 16 'Softplus 激活']) 17 18# 创建轨迹并添加到图形 19trace1 = go.Scatter(x=activations['x'], 20 y=activations['relu'], 21 name='ReLU', 22 mode='lines') 23 24trace2 = go.Scatter(x=activations['x'], 25 y=activations['softplus'], 26 name='Softplus', 27 mode='lines') 28 29fig.add_trace(trace1, row=1, col=1) 30fig.add_trace(trace2, row=1, col=2) 31 32# 更新 x 轴属性(全局) 33fig.update_xaxes(title_text="x") 34 35# 更新 y 轴属性(按坐标轴) 36fig.update_yaxes(title_text="ReLU(x)", row=1, col=1) 37fig.update_yaxes(title_text="Softplus(x)", row=1, col=2) 38 39# 格式化并显示 40fig.update_layout(title='激活函数') 41fig.show()

这是本课程中所有代码练习的笔记本:

介绍 Dash

高级子图