可視化神器plotly(2):子圖的繪制


楔子

下面我們來進行高級圖表的繪制,說是高級圖表,其實也不算高級,主要是一些子圖的繪制。以下代碼在 jupyter notebook 上運行,先導入幾個模塊:

import datetime
import plotly.graph_objs as go
import numpy as np
import pandas as pd

時間序列

plotly 對時間的支持特別友好,即支持字符串格式、也支持日期格式,而且使用起來比較簡單。

x0 = [datetime.date(2018, 1, 1),
      datetime.date(2018, 2, 1),
      datetime.date(2018, 3, 1),
      datetime.date(2018, 4, 1),
      datetime.date(2018, 5, 1)]
x1 = ["2018-1-1", "2018-2-1", "2018-3-1", "2018-4-1", "2018-5-1"]

trace0 = go.Scatter(x=x0, y=[1, 2, 3, 4, 5], name="trace_date")
trace1 = go.Scatter(x=x0, y=[2, 3, 4, 5, 6], name="trace_string")
fig = go.Figure(data=[trace0, trace1])
fig

從代碼中可以看出,只要是日期、或者符合時間格式的字符串,plotly 就會自動識別為日期格式。

表格

還記得我們之前創建甘特圖嗎?我們用到了 plotly.figure_factory,創建表格也是如此。

import plotly.figure_factory as ff

data= [
    ["姓名", "年齡", "性別"],
    ["古明地覺", 17, "女"],
    ["古明地戀", 16, "女"],
    ["椎名真白", 18, "女"],
    ["坂上智代", 19, "女"],
    ["雨宮優子", 16, "女"],
]

fig = ff.create_table(data)
fig

接收一個嵌套的二維列表,里面的第一個列表會被識別為表頭,並且在里面還可以添加一些 html 屬性。

import plotly.figure_factory as ff

data= [
    ["姓名", "年齡", "性別"],
    ['<a href="http://www.baidu.com">古明地覺</a>', 17, "女"],
    ["古明地戀", 16, "女"],
    ["椎名真白", 18, "女"],
    ["坂上智代", 19, "女"],
    ["雨宮優子", 16, "女"],
]

fig = ff.create_table(data)
fig

點擊 古明地覺 即可跳轉到百度頁面,會在新標簽頁中打開百度頁面。

使用 pandas

除了二維列表,我們還可以傳遞一個 DataFrame。

import plotly.figure_factory as ff
from sqlalchemy import create_engine
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
df = pd.read_sql("select * from t_case", engine)

# 直接將DataFrame傳進去即可
fig = ff.create_table(df)
fig

如果是 DataFrame,那么列名就是這里的表頭,但是我們看到索引貌似沒了,那么如何將索引也顯示在上面呢?

import plotly.figure_factory as ff
from sqlalchemy import create_engine
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
df = pd.read_sql("select * from t_case", engine)

# 直接將DataFrame傳進去即可
fig = ff.create_table(df, index=True, index_title="自增數字")
fig

多圖表

多圖表其實在前面博客中已經說了,這里再簡單提一嘴,就是畫布上顯示多條軌跡而已。

x = list("abcde")
y0 = [1, 2, -2, 2, -3]
y1 = [1, 2, 3, 4, 5]

trace0 = go.Bar(x=x, y=y0, name="bar")
trace1 = go.Scatter(x=x, y=y1, name="line")
fig = go.Figure(data=[trace0, trace1])
fig

多個坐標軸

多坐標軸是最復雜的地方,那為什么會有多個坐標軸呢。首先我們上面的多個圖表是顯示在相同坐標軸上面的,但是如果兩個軌跡的數值相差比較大、還使用這種方法,那么 plotly 為了顯示所有的軌跡,就會把坐標軸往大的方向調整,從而導致數值小的軌跡不明顯。我們舉個栗子看一下就很直觀了:

x0 = np.linspace(1, 100, 5)
x1 = np.linspace(0, 1000, 5)
y0 = x0 ** 2 + x0 + 1
y1 = x1 ** 2 + x1 + 1

trace0 = go.Scatter(x=x0, y=y0)
trace1 = go.Scatter(x=x1, y=y1)
fig = go.Figure(data=[trace0, trace1])
fig

一條軌跡我們指定的數據是 0 到 1000,一條是 0 到 100。但是 plotly 為了展示所有的軌跡,坐標軸上刻度會按照軌跡的數值大的一方來顯示,因此這就造成了數值小的軌跡的局部細節展示不出。所以在軌跡的數值相差比較大的時候,我們需要繪制第二個坐標軸。

繪制第二個坐標軸非常簡單,只需要在 layout 中指定一個 xaxis2 和 yaxis2 即可,同理第三個坐標軸就是 xaxis3 和 xaxis3,以此類推。

x0 = np.linspace(1, 100, 5)
x1 = np.linspace(0, 1000, 5)
y0 = x0 ** 2 + x0 + 1
y1 = x1 ** 2 + x1 + 1

trace0 = go.Scatter(x=x0, y=y0)
# 我們雖然在 layout 中傳入 xaxis2 和 yaxis2 創建了第二坐標軸,但是還要指明到底是哪個軌跡使用
# 因此要在軌跡上顯式地通過 xaxis 和 yaxis 指明,到底是哪個軌跡使用哪個坐標軸
# 但是在指定的時候,第二坐標軸直接寫 x2、y2 即可。默認的坐標軸是第一坐標軸、也就是最原始的那個坐標軸
trace1 = go.Scatter(x=x1, y=y1, yaxis="y2", xaxis="x2")
fig = go.Figure(data=[trace0, trace1],
                layout={
                    # 由於在同一個畫布上,所以網格會重疊,我們將其中的一個軸的網格給隱藏掉
                    # 以及zeroline,這些參數都是關於視覺上的,對於表達數據則無影響
                    # 具體如何影響圖表的模樣,可以自己去試一下
                    "xaxis": {"title": "trace0的x軸", "showgrid": False, "zeroline": False},
                    "yaxis": {"title": "trace0的y軸", "showgrid": False},
                    # 這里就是第二坐標軸的x軸了
                    "xaxis2": {"title": "trace1的x軸", 
                               "side": "top",  # 這個很重要,要設置方向,顯然設置為上方
                               # 必須要寫,否則圖像無法顯示,同理下面的yaxis也是類似
                               "overlaying": "x"},
                    "yaxis2": {"title": "trace1的y軸", 
                               # 第二y軸要設置在右邊
                               "side": "right", 
                               "overlaying": "y"}
                } 
               )
fig

此時就繪制出來了,因為現在兩個軌跡的坐標不是同一個坐標了。但是它們之間的規律是一樣的,再加上又顯示在同一個畫布上,所以軌跡是差不多重合的。

繪制子圖(方式一)

說實話,共享坐標軸的方式展示多圖表還不是很常見,常見的是一個畫布分為多個區域,每個區域展示各自的圖表。就可以想象成之前的一張畫布顯示一個軌跡,然后把多個畫布放在一起顯示。這樣,它們各自的坐標軸、標題等屬性都是獨立的。

# 創建子圖使用make_subplots
from plotly.subplots import make_subplots

trace0 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5])
trace1 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6])
trace2 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5])
trace3 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6])

fig = make_subplots(rows=2,  # 將畫布分為兩行
                    cols=2,  # 將畫布分為兩列
                    subplot_titles=["trace0的標題", 
                                    "trace1的標題", 
                                    "trace3的標題", 
                                    "trace4的標題"],  # 子圖的標題
                    x_title="x軸標題",
                    y_title="y軸標題"
                   )
# 添加軌跡
fig.append_trace(trace0, 1, 1)  # 將trace0添加到第一行第一列的位置
fig.append_trace(trace1, 1, 2)  # 將trace1添加到第一行第二列的位置
fig.append_trace(trace2, 2, 1)  # 將trace2添加到第二行第一列的位置
fig.append_trace(trace3, 2, 2)  # 將trace3添加到第二行第二列的位置
fig

此時我們就成功繪制出來了子圖,但是有一點不完美的地方是,它的這個坐標軸的標題是針對整體的。我可不可以對不同的坐標軸施加不同的描述呢?顯然是可以的。

但是我們這里需要再補充一下,我們之前創建畫布的時候是通過 go.Figure,然后將畫布參數通過字典的方式先寫好,然后傳給 layout。但是事實上我們也可以后續在進行添加的,比如 fig["layout"]["xaxis"].update({"title": "x坐標軸"}),這樣做也是可以的。fig["data"] 拿到的就是所有的軌跡,我們也可以往里面加入新的軌跡,fig["layout"] 則是拿到的畫布的屬性,一個字典,我們同樣可以往里面添加、刪除屬性。

之所以說這些,就是因為 make_subplots 里面沒辦法直接傳遞 layout,只能通過創建出來的畫布、手動獲取里面的 layout、然后添加。

from plotly.subplots import make_subplots

trace0 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5])
trace1 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6])
trace2 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5])
trace3 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6])

fig = make_subplots(rows=2,  
                    cols=2,  
                    subplot_titles=["trace0的標題", 
                                    "trace1的標題", 
                                    "trace2的標題", 
                                    "trace3的標題"], 
                   )
fig.append_trace(trace0, 1, 1)  
fig.append_trace(trace1, 1, 2)  
fig.append_trace(trace2, 2, 1)  
fig.append_trace(trace3, 2, 2)  
# 創建了四個子圖,自動就會有四個坐標軸。
# 每個軌跡占一個,因此這種情況我們是不需要在軌跡里面通過xaxis和yaxis來指定到底使用哪一個坐標軸的,因為已經分配好了
fig["layout"]["xaxis"].update({"title": "trace0的x軸", "titlefont": {"color": "red"}})
fig["layout"]["yaxis"].update({"title": "trace0的y軸", "titlefont": {"color": "red"}})
fig["layout"]["xaxis2"].update({"title": "trace1的x軸", "titlefont": {"color": "green"}})
fig["layout"]["yaxis2"].update({"title": "trace1的y軸", "titlefont": {"color": "green"}})
fig["layout"]["xaxis3"].update({"title": "trace2的x軸", "titlefont": {"color": "pink"}})
fig["layout"]["yaxis3"].update({"title": "trace2的y軸", "titlefont": {"color": "pink"}})
fig["layout"]["xaxis4"].update({"title": "trace3的x軸", "titlefont": {"color": "yellow"}})
fig["layout"]["yaxis4"].update({"title": "trace3的y軸", "titlefont": {"color": "yellow"}})

fig["layout"]["template"] = "plotly_dark"
fig

繪制子圖(方式二)

我們下面這種繪制子圖的方式有點類似於共享坐標軸的方式,主要在於內部的一個 domain 參數。

trace0 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5])
trace1 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6], xaxis="x2", yaxis="y2")

fig = go.Figure(data=[trace0, trace1],
                layout={"xaxis": {"domain": [0, 0.6]},  # 指定第一幅圖的范圍是0到百分之60
                        "xaxis2": {"domain": [0.7, 1]},  # 第二幅圖的范圍是百分之70到百分之百
                        
                        # 這個參數可能有些費解,它是指定y軸位置的,如果是通過"side": "right"的話,那么這個軸會在第一幅圖的右邊
                        # 通過"anchor": "x2",那么y軸就是像現在這樣出現在第二幅圖的左邊。
                        "yaxis2": {"anchor": "x2"}  
                       }   
               )

fig

這種繪制子圖的方式可能不是容易讓人理解,主要就在於里面的 domain 參數。我們之前通過這種方式可以認為是兩張子圖(都占據全部位置)重合了,現在通過 domain 將兩幅子圖分開了。因此相比 make_subplots,這種繪制子圖的方式有點讓人費解,而且我們還要計算好位置。

但是這種繪制子圖的方式的好處就在於我們可以自定義子圖的位置,我們使用 make_subplots 的時候,多個子圖大小的是一樣的,並且間隔什么的是等分的,但是 domain 這種方式就不同了。

trace0 = go.Scatter(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5])
trace1 = go.Scatter(x=[2, 3, 4, 5, 6], y=[2, 3, 4, 5, 6], xaxis="x2", yaxis="y2")

fig = go.Figure(data=[trace0, trace1],
                layout={# xaxis不指定,則默認是占據全部
                        # 我們只指定xaxis2和yaxis2
                        # 我們之前好像在xaxis里面沒有指定anchor,那是因為當時x軸就是在底部,或者說y軸占據了整個垂直方向
                        # 但是現在不一樣了,我們還要指定y軸的范圍
                        "xaxis2": {"domain": [0.6, 0.95], "anchor": "y2"},  
                        "yaxis2": {"domain": [0.1, 0.4], "anchor": "x2"}  
                       }   
               )

fig

我們看到一張圖出現在了另一張圖的里面,這是因為第一坐標軸、也就是默認的軸它的范圍是百分之百的,而我們指定了第二坐標軸的 x 方向和 y 方向只占據了一部分,那么使用該坐標的軸的軌跡就會顯示在局部,而不是像之前一樣占滿全部范圍。而繪制出類似於 make_subplots 的方式,則是讓每個軸 domain 不重合即可,然后計算好位置。所以這種方式應該說更強大,只不過對於繪制標准的、一個子圖占一個坑、規規整整的排列的多個子圖的話,還是推薦使用 make_subplots。而 domain 這種方式這是讓我們自定義的。

而最后再說說那個軸里面的 anchor,這個老鐵主要是空值坐標軸的刻度顯示在什么地方的。我們之前在繪制多個坐標軸的時候,直接使用的 side,那是因為坐標軸所在的兩幅圖的大小是一樣的,但是現在不一樣了。所以我們需要指定第 n 坐標軸的 x 軸的 "anchor" 為 yn、第 n 坐標軸的 y 軸的 "anchor" 為 xn。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM