Fork me on GitHub

可视化神器plotly绘制多子图

可视化神器Plotly玩转多子图绘制

很长时间没有Plotly绘图的文章,之前已经介绍如何绘制柱状图、饼图、小提琴图、桑基图等,今天给大家带来的是一篇关于Plotly如何绘制多子图的文章,在一个画布中如何实现多种类型图形的绘制。

Plotly连载文章

多子图绘制

首先看看实际的效果:

Plotly中有两种方式来绘制子图,基于plotly_express和 graph_objects。

但是plotly_express只支持 facet_plots(切面图) 和 marginal distribution subplots(边际分布子图),只有graph_objects是基于make_subplots模块才能够绘制真正意义上的多子图。下面通过实际例子来讲解。

1
2
3
4
5
6
7
8
import pandas as pd
import numpy as np

import plotly_express as px
import plotly.graph_objects as go

# 绘制子图
from plotly.subplots import make_subplots

最重要的是要导入make_subplots模块。

基于plotly_express

plotly_express绘制“子图”是通过参数marginal_xmarginal_y 来实现的,表示在边际上图形的类型,可以是"histogram", “rug”, “box”, or “violin”。

基于facet_plots

切面图是由具有相同轴集的多个子图组成的图形,其中每个子图显示数据的子集,也称之为:trellis(网格) plots or small multiples。直接上官方英文,感觉更合适,暂时找不到比较恰当的中文来翻译。

不同图形切面展示

先导入内置的消费数据集:

1、基于散点图的切面图形

1
2
3
4
5
6
7
fig = px.scatter(tips,  #  数据
x="total_bill", # xy轴
y="tip",
color="smoker", # 颜色
facet_col="day" # 列方向切面字段
)
fig.show()

2、基于柱状图的切面展示

1
2
3
4
5
6
7
8
9
# 2、柱状图切面

fig = px.bar(tips,
x="size",
y="total_bill",
color="day",
facet_row="smoker" # 行方向切面字段:是否抽烟
)
fig.show()

控制子图个数

3、wrapping column Facets:控制显示元素个数的切面图

当我们指定的某个切面的字段的取值有很多种不同的情况,我们可以通过facet_col_wrap参数来控制每行最多显示的图形个数,wrap可以理解成:被限制的意思,就是每行限制显示多少。

使用内置的GDP数据集:

1
2
3
4
5
6
7
8
9
10
11
# 3、被限制每行图形个数的切面图

fig = px.scatter(gdp, # 数据集
x='gdpPercap', # x、y、颜色、点的大小size
y='lifeExp',
color='continent',
size='pop',
facet_col='continent', # 列切面字段
facet_col_wrap=3 # 每行最多3个图形
)
fig.show()

上面的切面图形的col字段是洲,最多只有5个洲。下面的图形选用时间year:

1
2
3
4
5
6
7
8
9
fig = px.scatter(gdp,   # 数据集
x='gdpPercap', # x、y、颜色、点的大小size
y='lifeExp',
color='continent',
size='pop',
facet_col='year', # 列切面字段
facet_col_wrap=3 # 每行最多3个图形
)
fig.show()

每行最多显示4个图形:

1
2
3
4
5
6
7
8
9
fig = px.scatter(gdp,
x='gdpPercap',
y='lifeExp',
color='continent',
size='pop',
facet_col='year',
facet_col_wrap=4 # 每行最多4个图形
)
fig.show()

子图坐标轴设置

默认情况下子图的y轴是相同的:

1
2
3
4
5
6
7
8
9
# 独立轴的切面:默认情况是相同的y轴
# 默认下y轴的取值范围相同
fig = px.scatter(tips,
x="total_bill",
y="tip",
color='day',
facet_row="time"
)
fig.show()

通过参数设置不共享y轴:

1
2
3
4
5
6
7
8
9
10
11
fig = px.scatter(tips,
x="total_bill",
y="tip",
color='day',
facet_row="time" # 列方向上切面图
)

# 设置不共享y轴,对应的是facet_row
fig.update_yaxes(matches=None)

fig.show()

如果是facet_col在列方向上的切面,则可以设置不共享x轴

1
2
3
4
5
6
7
8
9
10
11
fig = px.scatter(tips,
x="total_bill",
y="tip",
color='day',
facet_col="time" # 列方向上切面图
)

# 设置不共享x轴,对应的是facet_col
fig.update_xaxes(matches=None)

fig.show()

子图标题设置

1
2
3
4
5
6
7
fig = px.scatter(tips,
x="total_bill",
y="tip",
color="time",
facet_col="smoker"
)
fig.show()

通过设置改变子图的标题:

1
2
3
4
5
6
7
8
9
10
11
fig = px.scatter(tips,
x="total_bill",
y="tip",
color="time",
facet_col="smoker"
)

# 增加代码:对每行标题通过=切割,取出最后的元素
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

fig.show()

基于边际图Marginal

该方法主要是通过marginal_x和marginal_y来实现的。首先导入数据集:

基于不同图形边际图

1、基于散点图:

1
2
3
4
5
6
7
8
fig = px.scatter(iris,  # 数据集
x="sepal_length", # 指定xy轴
y="sepal_width",
marginal_x="rug", # 边际图形类型:直方图
marginal_y="histogram" # 轴须图
)

fig.show()

2、基于密度热图的边际图形设置:

1
2
3
4
5
6
7
8
fig = px.density_heatmap(
iris, # 数据
x="sepal_width", # 两个轴
y="sepal_length",
marginal_x="violin", # 边际图:小提琴和箱型图
marginal_y="box")

fig.show()

3、边际图颜色设置

1
2
3
4
5
6
7
8
fig = px.scatter(iris,
x="sepal_length",
y="sepal_width",
color="species", # 颜色的设置同样适用于边际图
marginal_x="violin",
marginal_y="box",
title="边际图颜色设置")
fig.show()

边际图和切面图连用

1
2
3
4
5
6
7
8
9
10
fig = px.scatter(
tips,
x="total_bill",
y="tip",
color="sex",
facet_col="day", # 日期字段切面
marginal_x="violin" # 边际图用小提琴图
)

fig.show()

基于graph_objects

graph_objects方式其实是通过make_subplots函数来实现的。一定要先导入:

1
2
3
4
# 这种方式一定要导入的模块

from plotly.subplots import make_subplots
import plotly.graph_objects as go

基础子图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 两个基本参数:设置行、列
fig = make_subplots(rows=1, cols=2) # 1行2列

# 添加两个数据轨迹,构成两个图形
fig.add_trace(
go.Scatter(x=[1, 2, 3], y=[5, 10, 15]),
row=1, col=1 # 第一行第一列
)

fig.add_trace(
go.Scatter(x=[20, 30, 40], y=[60, 70, 80]),
row=1, col=2 # 第一行第二列
)

# 设置图形的宽高和标题
fig.update_layout(height=600,
width=800,
title_text="子图制作")
fig.show()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fig = make_subplots(rows=3, cols=1)  # 3行1列

# 添加3个数据轨迹
fig.add_trace(
go.Scatter(x=[1, 2, 3], y=[5, 10, 15]),
row=1, col=1 # 1*1
)

fig.add_trace(
go.Scatter(x=[20, 30, 40], y=[60, 70, 80]),
row=2, col=1 # 2*1
)

fig.add_trace(
go.Scatter(x=[50, 60, 70], y=[110, 120, 130]),
row=3, col=1 # 3*1
)

fig.update_layout(height=600,
width=800,
title_text="子图制作")
fig.show()

多行多列子图

多行多列的时候,我们可以指定第一个图形的位置,表示图形从哪里开始。默认左上角是第一个图形的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
\fig = make_subplots(rows=2, cols=2,# 2行2列
start_cell="bottom-left"# 第一个图形的位置,两个选择:bottom-left', 'top-left
)

# 添加4个数据轨迹
fig.add_trace(
go.Bar(x=[1, 2, 3], y=[5, 10, 15]),
row=1, col=1 # 1*1
)

fig.add_trace(
go.Scatter(x=[20, 30, 40], y=[60, 70, 80]),
row=1, col=2 # 1*2
)

fig.add_trace(
go.Scatter(x=[50, 60, 70], y=[110, 120, 130]),
row=2, col=1 # 2*1
)
fig.add_trace(
go.Bar(x=[50, 60, 70], y=[110, 120, 130]),
row=2, col=2 # 2*2
)

fig.update_layout(height=600,
width=800,
title_text="子图制作")
fig.show()

多子图标题设置

很多时候我们要给每个子图取名,用subplot_titles来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fig = make_subplots(rows=2, cols=2,
start_cell="bottom-left", # 'bottom-left', 'top-left
subplot_titles=["子图1","子图2","子图3","子图4"] # 每个子图的名字
)

# 添加4个数据轨迹
fig.add_trace(
go.Bar(x=[1, 2, 3], y=[5, 10, 15]),
row=1, col=1 # 1*1
)

fig.add_trace(
go.Scatter(x=[20, 30, 40], y=[60, 70, 80]),
row=1, col=2 # 1*2
)

fig.add_trace(
go.Scatter(x=[50, 60, 70], y=[110, 120, 130]),
row=2, col=1 # 2*1
)
fig.add_trace(
go.Bar(x=[50, 60, 70], y=[110, 120, 130]),
row=2, col=2 # 2*2
)

fig.update_layout(height=600,
width=800,
title_text="多行多列子图制作")
fig.show()

多子图标注Annotations

在子图中我们还可以给数据添加标注:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
fig = make_subplots(rows=1, cols=2,
subplot_titles=["子图1","子图2"] # 子图名字
)

# 添加数据
fig.add_trace(
go.Bar(x=[1, 2, 3],
y=[5, 10, 15],
text=["文字1", "文字2", "文字3"], # 标注内容
textposition="inside" # 位置
),
row=1, col=1 # 1*1
)

# 添加数据
fig.add_trace(
go.Scatter(x=[1, 2, 3],
y=[5, 10, 15],
mode="markers+text", # 散点图的数据显示形式
text=["文字4", "文字5", "文字6"], # 标注内容
textposition="bottom center" # 位置
),
row=1, col=2 # 1*2
)


fig.update_layout(height=600,
width=800,
title_text="多子图添加标注")
fig.show()

子图宽度设置

上面绘制的多子图都是大小相同的,我们可以通过参数来进行设置显示不同的大小:column_widths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
fig = make_subplots(rows=1,
cols=2,
column_widths=[0.35,0.65], # 重点:两个子图的宽度占比
subplot_titles=["子图1","子图2"] # 名字
)

fig.add_trace(
go.Bar(x=[1, 2, 3],
y=[5, 10, 15],
text=["文字1", "文字2", "文字3"], # 标注内容
textposition="inside" # 位置
),
row=1, col=1 # 1*1
)

# 添加数据
fig.add_trace(
go.Scatter(x=[1, 2, 3],
y=[5, 10, 15],
mode="markers+text", # 散点图的数据显示形式
text=["文字4", "文字5", "文字6"], # 标注内容
textposition="bottom center" # 位置
),
row=1, col=2 # 1*2
)

fig.update_layout(height=600,
width=800,
title_text="多子图添加标注")
fig.show()

共享x轴

共享x轴指的是针对在同列行中的多个图形共享x轴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
fig = make_subplots(rows=3,
cols=1,
# 重点参数
shared_xaxes=True, # 设置共享x轴
vertical_spacing=0.03, # 图之间的间隙大小
subplot_titles=["子图1","子图2","子图3"] # 名字
)

fig.add_trace(
go.Bar(x=[1, 2, 3],
y=[5, 10, 15],
text=["文字1", "文字2", "文字3"],
textposition="inside" # 位置
),
row=1, col=1 # 1*1
)

# 添加数据
fig.add_trace(
go.Scatter(x=[4, 5, 6],
y=[5, 10, 15],
mode="markers+text",
text=["文字4", "文字5", "文字6"],
textposition="bottom center"
),
row=2, col=1 # 2*1
)


# 添加数据
fig.add_trace(
go.Scatter(x=[10, 20, 30],
y=[25, 30, 45],
mode="markers+text", # 散点图的数据显示形式
text=["文字7", "文字8", "文字9"], # 标注内容
textposition="bottom center" # 位置
),
row=3, col=1 # 3*1
)


fig.update_layout(height=600,
width=1000,
title_text="多子图添加标注")
fig.show()

共享y轴

共享y轴指的是针对在同一行中的多个图形共享y轴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fig = make_subplots(rows=2, cols=2,   # 2*2
subplot_titles=["子图1","子图2","子图3","子图4"],
shared_yaxes=True # 重点:共享y轴
)

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 3, 4]), # 两个轴的数据
row=1, col=1) # 行列位置

fig.add_trace(go.Scatter(x=[20, 30, 40], y=[5, 6, 7]),
row=1, col=2)

fig.add_trace(go.Bar(x=[20, 30, 40], y=[20, 40, 10]),
row=2, col=1)

fig.add_trace(go.Scatter(x=[40, 50, 60], y=[70, 80, 90]),
row=2, col=2)

fig.update_layout(height=600, width=600,
title_text="多子图共享y轴")
fig.show()

坐标轴自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from plotly.subplots import make_subplots
import plotly.graph_objects as go


fig = make_subplots(
rows=2,
cols=2,
subplot_titles=("Plot 1", "Plot 2", "Plot 3", "Plot 4")
)

# 添加不同数据
fig.add_trace(go.Scatter(x=[1, 2, 3],
y=[4, 5, 6]),
row=1, col=1)

fig.add_trace(go.Scatter(x=[20, 30, 40],
y=[50, 60, 70]),
row=1, col=2)
fig.add_trace(go.Scatter(x=[300, 400, 500],
y=[600, 700, 800]),
row=2, col=1)

fig.add_trace(go.Scatter(x=[4000, 5000, 6000],
y=[7000, 8000, 9000]),
row=2, col=2)

# 自定义x轴
fig.update_xaxes(title_text="xaxis—1 标题", row=1, col=1) # 正常显示
fig.update_xaxes(title_text="xaxis-2 标题", range=[10, 50], row=1, col=2) # 设置范围range
fig.update_xaxes(title_text="xaxis-3 标题", showgrid=False, row=2, col=1) # 不显示网格线
fig.update_xaxes(title_text="xaxis-4 标题", type="log", row=2, col=2) # 基于对数

# 自定义y轴
fig.update_yaxes(title_text="yaxis 1 标题", row=1, col=1)
fig.update_yaxes(title_text="yaxis 2 标题", range=[40, 80], row=1, col=2)
fig.update_yaxes(title_text="yaxis 3 标题", showgrid=False, row=2, col=1)
fig.update_yaxes(title_text="yaxis 4 标题", row=2, col=2)

# Update title and height
fig.update_layout(title_text="自定义子图轴坐标", height=700)

fig.show()

共享颜色轴

使用的参数是coloraxis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fig = make_subplots(rows=1, cols=2,
shared_yaxes=True) # 在y轴方向上共享

fig.add_trace(go.Bar(x=[1, 2, 3], y=[4, 5, 6],
marker=dict(color=[4, 5, 6],
coloraxis="coloraxis")),
1, 1) # 直接表示位置在(1,1)

fig.add_trace(go.Bar(x=[1, 2, 3], y=[2, 3, 5],
marker=dict(color=[2, 3, 5],
coloraxis="coloraxis")),
1, 2) # 位置在(1,2)

fig.update_layout(coloraxis=dict(colorscale='Bluered'), # 颜色轴
showlegend=False) # 不显示图例

fig.show()

子图位置自定义

子图位置的自定义是通过参数spec来实现的,spec是一个2维的列表集合,列表中包含rows和cols两个参数。

比如我们想绘制2*2的图形,但是只有3个图形,那么肯定有个图形会占据2行1列或者是1行2列的位置,通过例子来解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fig = make_subplots(
rows=2, cols=2,
specs=[[{}, {}], # 1*1,1*2
[{"colspan": 2}, None]], # 2*1的位置占据两列,2*2的位置没有图
subplot_titles=("子图1","子图2", "子图3"))

fig.add_trace(go.Scatter(x=[1, 2], y=[5, 6]),
row=1, col=1) # 1*1

fig.add_trace(go.Scatter(x=[4, 6], y=[8, 9]),
row=1, col=2) # 1*2

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 5, 2]),
row=2, col=1) # 2*1占据两行

fig.update_layout(showlegend=False, # 不显示图例
title_text="子图位置自定义")
fig.show()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fig = make_subplots(
rows=2, cols=2,
specs=[[{"rowspan":2}, {}], # 1*1 占据两列,1*2
[None,{}]], # 2*1的位置没有图
subplot_titles=("子图1","子图2", "子图3"))

fig.add_trace(go.Scatter(x=[1, 2], y=[5, 6]),
row=1, col=1) # 1*1 占据两列

fig.add_trace(go.Scatter(x=[4, 6], y=[8, 9]),
row=1, col=2) # 1*2

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 5, 2]),
row=2, col=2) # 2*1

fig.update_layout(showlegend=False, # 不显示图例
title_text="子图位置自定义")
fig.show()

让我们看一个更为复杂的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
fig = make_subplots(
rows=5, cols=2, # 5*2的图形
specs=[[{}, {"rowspan": 2}], # 1*1 ; 2*1的位置占据两行rows
[{}, None], # 2*1; 2*2的位置已经被上面的2*1占据
[{"rowspan": 2, "colspan": 2}, None], # 3*1的位置占据2行2列所以,第3、4行的两列只有单个图形
[None, None],
[{}, {}]] # 5*1;5*2
)

fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(1,1)"), row=1, col=1)

fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(1,2)"), row=1, col=2)

fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(2,1)"), row=2, col=1)

fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(3,1)"), row=3, col=1)

fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(5,1)"), 5,1) # row和col可以省略
fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(5,2)"), 5, 2)

fig.update_layout(height=600,
width=600,
title_text="多子图位置自定义")

fig.show()

子图类型自定义

子图可供选择的图形类型:

  • “xy”: 二维的散点scatter、柱状图bar等

  • “scene”: 3维的scatter3d、球体cone

  • “polar”: 极坐标图形如scatterpolar, barpolar等

  • “ternary”: 三元图如scatterternary

  • “mapbox”: 地图如scattermapbox

  • “domain”: .针对有一定域的图形,如饼图pie, parcoords, parcats,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
rows=2, cols=2,
specs=[[{"type": "xy"}, {"type": "polar"}],
[{"type": "domain"}, {"type": "scene"}]], # 通过type来指定类型
)

fig.add_trace(go.Bar(y=[2, 3, 1]),
1, 1)

fig.add_trace(go.Barpolar(theta=[0, 45, 90], r=[2, 3, 1]),
1, 2)

fig.add_trace(go.Pie(values=[2, 3, 1]),
2, 1)

fig.add_trace(go.Scatter3d(x=[2, 3, 1], y=[0, 0, 0],
z=[0.5, 1, 2], mode="lines"),
2, 2)

fig.update_layout(height=700, showlegend=False)

fig.show()

多子图类型和位置的连用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
rows=2, cols=2,
specs=[[{"type": "xy"}, {"type": "polar"}], # 设置类型
[{"colspan":2,"type": "domain"},None]], # 设置位置:2*1的位置占据两列; 2*2没有图形
)

fig.add_trace(go.Bar(y=[2, 3, 1]),
1, 1)

fig.add_trace(go.Barpolar(theta=[0, 45, 90], r=[2, 3, 1]),
1, 2)

fig.add_trace(go.Pie(values=[2, 3, 1]),
2, 1) #

fig.update_layout(height=700, showlegend=False)

fig.show()

参数解释

最后附上官网地址,多多学习:https://plotly.com/python/subplots/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
plotly.subplots.make_subplots(rows=1,   # 行列数值决定位置
cols=1,
shared_xaxes=False, # 是否共享xy轴
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, # xy轴axis的标题
y_title=None,
figure=None,
**kwargs)

通过下面的方式查看帮助文档:

1
2
from plotly.subplots import make_subplots
help(make_subplots)

本文标题:可视化神器plotly绘制多子图

发布时间:2021年07月18日 - 22:07

原始链接:http://www.renpeter.cn/2021/07/18/%E5%8F%AF%E8%A7%86%E5%8C%96%E7%A5%9E%E5%99%A8plotly%E7%BB%98%E5%88%B6%E5%A4%9A%E5%AD%90%E5%9B%BE.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Coffee or Tea