公衆号:尤而小屋
作者:Peter
編輯:Peter
可視化神器Plotly玩轉多子圖繪制
大家好,我是Peter~
很長時間沒有Plotly繪圖的文章,之前已經介紹如何繪制柱狀圖、餅圖、小提琴圖、桑基圖等,今天給大家帶來的是一篇關于Plotly如何繪制多子圖的文章,在一個畫布中如何實作多種類型圖形的繪制。
Plotly連載文章
多子圖繪制
首先看看實際的效果:
Plotly中有兩種方式來繪制子圖,基于plotly_express和 graph_objects。
但是plotly_express隻支援 facet_plots(切面圖) 和 marginal distribution subplots(邊際分布子圖),隻有graph_objects是基于make_subplots子產品才能夠繪制真正意義上的多子圖。下面通過實際例子來講解。
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_x
和
marginal_y
來實作的,表示在邊際上圖形的類型,可以是"histogram", “rug”, “box”, or “violin”。
基于facet_plots
切面圖是由具有相同軸集的多個子圖組成的圖形,其中每個子圖顯示資料的子集,也稱之為:trellis(網格) plots or small multiples。直接上官方英文,感覺更合适,暫時找不到比較恰當的中文來翻譯。
不同圖形切面展示
先導入内置的消費資料集:
1、基于散點圖的切面圖形
fig = px.scatter(tips, # 資料
x="total_bill", # xy軸
y="tip",
color="smoker", # 顔色
facet_col="day" # 列方向切面字段
)
fig.show()
2、基于柱狀圖的切面展示
# 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資料集:
# 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:
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個圖形:
fig = px.scatter(gdp,
x='gdpPercap',
y='lifeExp',
color='continent',
size='pop',
facet_col='year',
facet_col_wrap=4 # 每行最多4個圖形
)
fig.show()
子圖坐标軸設定
預設情況下子圖的y軸是相同的:
# 獨立軸的切面:預設情況是相同的y軸
# 預設下y軸的取值範圍相同
fig = px.scatter(tips,
x="total_bill",
y="tip",
color='day',
facet_row="time"
)
fig.show()
通過參數設定不共享y軸:
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軸
fig = px.scatter(tips,
x="total_bill",
y="tip",
color='day',
facet_col="time" # 列方向上切面圖
)
# 設定不共享x軸,對應的是facet_col
fig.update_xaxes(matches=None)
fig.show()
子圖示題設定
fig = px.scatter(tips,
x="total_bill",
y="tip",
color="time",
facet_col="smoker"
)
fig.show()
通過設定改變子圖的标題:
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、基于散點圖:
fig = px.scatter(iris, # 資料集
x="sepal_length", # 指定xy軸
y="sepal_width",
marginal_x="rug", # 邊際圖形類型:直方圖
marginal_y="histogram" # 軸須圖
)
fig.show()
2、基于密度熱圖的邊際圖形設定:
fig = px.density_heatmap(
iris, # 資料
x="sepal_width", # 兩個軸
y="sepal_length",
marginal_x="violin", # 邊際圖:小提琴和箱型圖
marginal_y="box")
fig.show()
3、邊際圖顔色設定
fig = px.scatter(iris,
x="sepal_length",
y="sepal_width",
color="species", # 顔色的設定同樣适用于邊際圖
marginal_x="violin",
marginal_y="box",
title="邊際圖顔色設定")
fig.show()
邊際圖和切面圖連用
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函數來實作的。一定要先導入:
# 這種方式一定要導入的子產品
from plotly.subplots import make_subplots
import plotly.graph_objects as go
基礎子圖
# 兩個基本參數:設定行、列
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()
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()
多行多列子圖
多行多列的時候,我們可以指定第一個圖形的位置,表示圖形從哪裡開始。預設左上角是第一個圖形的位置。
\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來實作
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
在子圖中我們還可以給資料添加标注:
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
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軸:
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軸:
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()
坐标軸自定義
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
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列的位置,通過例子來解釋。
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()
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()
讓我們看一個更為複雜的例子:
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,
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()
多子圖類型和位置的連用:
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/
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)
通過下面的方式檢視幫助文檔:
from plotly.subplots import make_subplots
help(make_subplots)