天天看點

可視化神器Plotly玩轉多子圖繪制

公衆号:尤而小屋

作者:Peter

編輯:Peter

可視化神器Plotly玩轉多子圖繪制

大家好,我是Peter~

很長時間沒有Plotly繪圖的文章,之前已經介紹如何繪制柱狀圖、餅圖、小提琴圖、桑基圖等,今天給大家帶來的是一篇關于Plotly如何繪制多子圖的文章,在一個畫布中如何實作多種類型圖形的繪制。

可視化神器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。直接上官方英文,感覺更合适,暫時找不到比較恰當的中文來翻譯。

不同圖形切面展示

先導入内置的消費資料集:

可視化神器Plotly玩轉多子圖繪制

1、基于散點圖的切面圖形

fig = px.scatter(tips,  #  資料
                 x="total_bill", # xy軸 
                 y="tip", 
                 color="smoker", # 顔色
                 facet_col="day"  # 列方向切面字段
                )
fig.show()
           
可視化神器Plotly玩轉多子圖繪制

2、基于柱狀圖的切面展示

# 2、柱狀圖切面

fig = px.bar(tips, 
             x="size", 
             y="total_bill", 
             color="day", 
             facet_row="smoker"  # 行方向切面字段:是否抽煙
            )
fig.show()
           
可視化神器Plotly玩轉多子圖繪制

控制子圖個數

3、wrapping column Facets:控制顯示元素個數的切面圖

當我們指定的某個切面的字段的取值有很多種不同的情況,我們可以通過facet_col_wrap參數來控制每行最多顯示的圖形個數,wrap可以了解成:被限制的意思,就是每行限制顯示多少。

使用内置的GDP資料集:

可視化神器Plotly玩轉多子圖繪制
# 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()
           
可視化神器Plotly玩轉多子圖繪制

上面的切面圖形的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()
           
可視化神器Plotly玩轉多子圖繪制

每行最多顯示4個圖形:

fig = px.scatter(gdp, 
                 x='gdpPercap', 
                 y='lifeExp', 
                 color='continent', 
                 size='pop',
                 facet_col='year', 
                 facet_col_wrap=4  # 每行最多4個圖形
                )
fig.show()
           
可視化神器Plotly玩轉多子圖繪制

子圖坐标軸設定

預設情況下子圖的y軸是相同的:

# 獨立軸的切面:預設情況是相同的y軸
# 預設下y軸的取值範圍相同
fig = px.scatter(tips, 
                 x="total_bill", 
                 y="tip", 
                 color='day', 
                 facet_row="time"
                )
fig.show()
           
可視化神器Plotly玩轉多子圖繪制

通過參數設定不共享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()
           
可視化神器Plotly玩轉多子圖繪制

如果是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()
           
可視化神器Plotly玩轉多子圖繪制

子圖示題設定

fig = px.scatter(tips,
                 x="total_bill", 
                 y="tip", 
                 color="time",
                 facet_col="smoker"
                )
fig.show()
           
可視化神器Plotly玩轉多子圖繪制

通過設定改變子圖的标題:

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()
           
可視化神器Plotly玩轉多子圖繪制

基于邊際圖Marginal

該方法主要是通過marginal_x和marginal_y來實作的。首先導入資料集:

可視化神器Plotly玩轉多子圖繪制

基于不同圖形邊際圖

1、基于散點圖:

fig = px.scatter(iris,  # 資料集 
                 x="sepal_length",  # 指定xy軸
                 y="sepal_width",
                 marginal_x="rug",  # 邊際圖形類型:直方圖
                 marginal_y="histogram"  # 軸須圖
                )

fig.show()
           
可視化神器Plotly玩轉多子圖繪制

2、基于密度熱圖的邊際圖形設定:

fig = px.density_heatmap(
    iris,  # 資料
    x="sepal_width",  # 兩個軸
    y="sepal_length",  
    marginal_x="violin",  # 邊際圖:小提琴和箱型圖
    marginal_y="box")

fig.show()
           
可視化神器Plotly玩轉多子圖繪制

3、邊際圖顔色設定

fig = px.scatter(iris,
                 x="sepal_length", 
                 y="sepal_width", 
                 color="species",   # 顔色的設定同樣适用于邊際圖
                 marginal_x="violin", 
                 marginal_y="box",
                 title="邊際圖顔色設定")
fig.show()
           
可視化神器Plotly玩轉多子圖繪制

邊際圖和切面圖連用

fig = px.scatter(
    tips, 
    x="total_bill",
    y="tip", 
    color="sex", 
    facet_col="day",  # 日期字段切面
    marginal_x="violin"  # 邊際圖用小提琴圖
)

fig.show()
           
可視化神器Plotly玩轉多子圖繪制

基于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()
           
可視化神器Plotly玩轉多子圖繪制
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()
           
可視化神器Plotly玩轉多子圖繪制

多行多列子圖

多行多列的時候,我們可以指定第一個圖形的位置,表示圖形從哪裡開始。預設左上角是第一個圖形的位置。

\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()
           
可視化神器Plotly玩轉多子圖繪制

多子圖示題設定

很多時候我們要給每個子圖取名,用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()
           
可視化神器Plotly玩轉多子圖繪制

多子圖示注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()
           
可視化神器Plotly玩轉多子圖繪制

子圖寬度設定

上面繪制的多子圖都是大小相同的,我們可以通過參數來進行設定顯示不同的大小: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()
           
可視化神器Plotly玩轉多子圖繪制

共享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()
           
可視化神器Plotly玩轉多子圖繪制

共享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()
           
可視化神器Plotly玩轉多子圖繪制

坐标軸自定義

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()
           
可視化神器Plotly玩轉多子圖繪制

共享顔色軸

使用的參數是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()
           
可視化神器Plotly玩轉多子圖繪制

子圖位置自定義

子圖位置的自定義是通過參數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()
           
可視化神器Plotly玩轉多子圖繪制
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()
           
可視化神器Plotly玩轉多子圖繪制

讓我們看一個更為複雜的例子:

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()
           
可視化神器Plotly玩轉多子圖繪制

子圖類型自定義

子圖可供選擇的圖形類型:

  • “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()
           
可視化神器Plotly玩轉多子圖繪制

多子圖類型和位置的連用:

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()
           
可視化神器Plotly玩轉多子圖繪制

參數解釋

最後附上官網位址,多多學習: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)
           
可視化神器Plotly玩轉多子圖繪制

通過下面的方式檢視幫助文檔:

from plotly.subplots import make_subplots
help(make_subplots)