常見問題#

這是關於 seaborn 常見問題的解答集合。

開始使用#

我已經安裝了 seaborn,為什麼無法匯入?#

看起來您透過執行 pip install seaborn 成功安裝了 seaborn,但無法匯入。當您嘗試時,會收到類似「ModuleNotFoundError: No module named 'seaborn'」的錯誤訊息。

這可能不是 seaborn 本身的問題。如果您的電腦上有多個 Python 環境,則有可能您在一個環境中執行了 pip install,然後嘗試在另一個環境中匯入該函式庫。在 Unix 系統上,您可以檢查終端機命令 which pipwhich python 和(如果適用)which jupyter 是否指向相同的 bin/ 目錄。如果不是,您需要解決 $PATH 變數的定義。

兩種使用 pip 安裝的替代模式也可能更能解決此問題

  • 在命令列上使用 python -m pip install <package> 而不是 pip install <package> 來叫用 pip

  • 在 Jupyter 筆記本中使用 %pip install <package> 來將其安裝在與核心相同的位置

我無法匯入 seaborn,即使它確實已經安裝了!#

您肯定已在正確的位置安裝了 seaborn,但匯入時會產生很長的追溯並顯示令人困惑的錯誤訊息,例如 ImportError: DLL load failed: The specified module could not be found

此類錯誤通常表示 Python 函式庫使用編譯資源的方式存在問題。由於 seaborn 是純 Python,因此不會直接遇到這些問題,但其相依性(numpy、scipy、matplotlib 和 pandas)可能會。要解決此問題,您首先需要仔細閱讀追溯,並找出發生錯誤時匯入的哪個相依性。然後查閱相關套件的安裝文件,其中可能提供有關如何在您的特定系統上使安裝運作的建議。

這些問題最常見的罪魁禍首是 scipy,它具有許多編譯元件。從 seaborn 0.12 版開始,scipy 是可選的相依性,這應該有助於減少這些問題的發生頻率。

為什麼我的圖表沒有顯示?#

您正在呼叫 seaborn 函數 — 也許在終端機或具有整合式 IPython 主控台的 IDE 中 — 但沒有看到任何圖表。

在 matplotlib 中,建立 圖表和 顯示 圖表之間存在區別,在某些情況下,有必要在您想要看到圖表時明確呼叫 matplotlib.pyplot.show()。由於該命令預設會封鎖,且並非總是需要(例如,您可能正在執行將檔案儲存到磁碟的指令碼),因此 seaborn 在此處不會偏離標準 matplotlib 做法。

然而,seaborn 文件中的大多數範例都沒有這行,因為有多種方法可以避免需要它。在具有 「內嵌」(預設)或 「widget」 後端的 Jupyter 筆記本中,matplotlib.pyplot.show() 會在執行單元格後自動呼叫,因此任何圖表都會顯示在單元格的輸出中。您也可以在任何 Jupyter 或 IPython 介面中執行 %matplotlib 或在 Python 中的任何位置呼叫 matplotlib.pyplot.ion() 來啟用更互動的體驗。兩種方法都會將 matplotlib 設定為在每個繪圖命令後顯示或更新圖表。

為什麼在每個筆記本單元格後都列印出東西?#

您正在 Jupyter 筆記本中使用 seaborn,並且每個單元格在顯示圖表之前都會列印出類似 <AxesSuplot:> 或 <seaborn.axisgrid.FacetGrid at 0x7f840e279c10> 的內容。

Jupyter 筆記本會將單元格中最後一個陳述式的結果顯示為其輸出的一部分,並且每個 seaborn 的繪圖函數都會傳回對包含圖表的 matplotlib 或 seaborn 物件的參考。如果這很麻煩,您可以使用幾種方法抑制此輸出

  • 始終將最後一個陳述式的結果指定給變數(例如,ax = sns.histplot(...)

  • 在最後一個陳述式結尾新增分號(例如,sns.histplot(...);

  • 使用沒有傳回值的函數結束每個單元格(例如,plt.show(),這不是必需的,但也不會造成任何問題)

  • 如果您要將筆記本轉換為不同的表示法,請新增 單元格中繼資料標籤

為什麼在 Jupyter 筆記本中繪圖看起來模糊?#

預設的「內嵌」後端(由 IPython 定義)使用異常低的 dpi(「每英吋點數」)來輸出圖表。這是一種節省空間的措施:較低的 dpi 圖表佔用的磁碟空間較少。(此外,較低的 dpi 內嵌圖形在 實際 上看起來更小,因為它們以 PNG 格式表示,而 PNG 並沒有明確的解析度概念。)因此,人們面臨著經濟/品質的權衡。

您可以使用 matplotlib API 透過重設 rc 參數來增加 DPI,方法是使用

plt.rcParams.update({"figure.dpi": 96})

或者在您啟用 seaborn 主題時執行此操作

sns.set_theme(rc={"figure.dpi": 96})

如果您的螢幕具有高像素密度,您可以使用「Retina 模式」使圖表更清晰

%config InlineBackend.figure_format = "retina"

這不會變更圖表在 Jupyter 介面中的外觀大小,但在其他內容中(例如,在 GitHub 上)它們可能會顯示得非常大。而且它們將佔用 4 倍的磁碟空間。或者,您可以製作 SVG 圖表

%config InlineBackend.figure_format = "svg"

這會將 matplotlib 設定為發出具有「無限解析度」的 向量圖形。缺點是檔案大小現在將隨著圖表中藝術家的數量和複雜性而調整,並且在某些情況下(例如,大型散佈圖矩陣),負載會影響瀏覽器的反應速度。

棘手的概念#

「圖表層級」和「軸層級」是什麼意思?#

您可能在 seaborn 文件、StackOverflow 回答或 GitHub 線程中遇到了「圖表層級」或「軸層級」一詞,但您不理解它是什麼意思。

簡而言之,seaborn 中的所有繪圖函數都屬於以下兩個類別之一

  • 「軸層級」函數,它繪製到單一子圖上,該子圖可能存在,也可能在呼叫函數時不存在

  • 「圖表層級」函數,它會在內部建立 matplotlib 圖表,其中可能包含多個子圖

此設計旨在滿足兩個目標

  • seaborn 應提供作為 matplotlib 方法「直接替代」的函數

  • seaborn 應能夠產生在不同子圖上顯示「刻面」或邊際分佈的圖表

圖表層級的函式總是會將一個或多個軸層級的函式與管理佈局的物件結合。因此,舉例來說,relplot() 是一個圖表層級的函式,它會將 scatterplot()lineplot()FacetGrid 結合。相對地,jointplot() 是一個圖表層級的函式,它可以將多個不同的軸層級函式結合起來 — 預設為 scatterplot()histplot() — 與 JointGrid 結合。

如果你的目的只是用單一個 seaborn 函式呼叫來建立圖表,那麼這不是你太需要擔心的問題。但是,當你想要在每個函式 API 所提供的範圍之外進行自訂時,它就變得相關了。這也是造成各種其他混亂的原因,所以理解(至少大致上)並記住這個重要的區別是很重要的。

這在教學課程這篇部落格文章中有更詳細的說明。

什麼是「類別圖」或「類別函式」?#

除了圖表層級/軸層級的區別外,這個概念可能是造成混淆行為的第二大原因。

一些seaborn 函式被稱為「類別」函式,因為它們旨在支援一種使用情境,其中圖表中的 x 或 y 變數是類別變數(也就是說,該變數取有限數量的可能非數值)。

在編寫這些函式時,matplotlib 對非數值資料類型沒有任何直接支援。因此,seaborn 會在內部建立從資料中的唯一值到從 0 開始的整數索引的映射,並將其傳遞給 matplotlib。如果你的資料是字串,那很好,而且它或多或少符合matplotlib 現在如何處理字串類型的資料。

但是一個潛在的問題是,這些函式預設總是這樣做,即使 x 和 y 變數都是數值的。這導致許多混淆的行為,尤其是在混合類別和非類別圖表時(例如,組合長條圖和折線圖)。

v0.13 版本新增了一個 native_scale 參數,可控制此行為。預設值為 False,但將其設定為 True 將保留用於類別分組的資料的原始屬性。

指定資料#

我的資料需要如何組織?#

為了充分利用 seaborn,你的資料應該具有「長格式」或「整齊」的表示形式。在資料框中,這表示每個變數都有自己的欄位,每個觀察值都有自己的列,每個值都有自己的儲存格。使用長格式資料,你可以透過將資料集(欄位)中的變數分配到圖表中的角色,來簡潔且準確地指定視覺化。

資料組織是初學者常遇到的絆腳石,部分原因是資料通常不是以長格式表示形式收集或儲存的。因此,通常有必要在繪圖前使用 pandas 重新塑形資料。資料重新塑形可能是一項複雜的工作,需要對資料框結構有紮實的理解,並具備 pandas API 的知識。花一些時間培養這項技能可以獲得豐厚的回報。

雖然 seaborn 在提供長格式資料時強大,但幾乎每個 seaborn 函式也會接受並繪製「寬格式」資料。你可以透過將物件傳遞給 seaborn 的 data= 參數,而無需指定其他繪圖變數(xy、…)來觸發此行為。使用寬格式資料時,你將受到限制:每個函式只能產生一種寬格式圖表。在大多數情況下,seaborn 會嘗試比對 matplotlib 或 pandas 對具有相同結構的資料集所做的處理。將資料重新塑形為長格式將為你提供更大的彈性,但盡早快速查看資料可能會有所幫助,而 seaborn 試圖使這成為可能。

了解你的資料應該如何表示 — 以及如果資料一開始雜亂無章,如何使其達到這種表示方式 — 對於有效率且完整地使用 seaborn 非常重要,並且在使用者指南中有詳細說明。

seaborn 是否只能與 pandas 搭配使用?#

一般而言,不是:seaborn 對資料集的表示方式相當有彈性

在大多數情況下,由多個類似向量的類型表示的長格式資料可以直接傳遞給 xy 或其他繪圖參數。或者,你可以將向量類型字典傳遞給 data,而不是 DataFrame。使用寬格式資料繪圖時,你可以使用 2D numpy 陣列,甚至是巢狀列表,以寬格式模式繪圖。

有幾個較舊的函式(即 catplot()lmplot())確實需要你傳遞 pandas.DataFrame。但目前它們是例外,並且它們將在接下來的幾個發布週期中獲得更大的彈性。

佈局問題#

如何更改圖表大小?#

這將比你希望的更複雜,部分原因是 matplotlib 中有多種方法可以更改圖表大小,部分原因是 seaborn 中圖表層級/軸層級的區別。

在 matplotlib 中,你通常可以透過rc 參數來設定所有圖表的預設大小,特別是 figure.figsize。並且你可以在建立個別圖表時設定其大小(例如 plt.subplots(figsize=(w, h)))。如果你使用的是軸層級的 seaborn 函式,則這兩種方法都會如預期般運作。

圖表層級的函式既會忽略預設圖表大小,也會以不同的方式參數化圖表大小。呼叫圖表層級函式時,你可以將值傳遞給 height=aspect= 來設定(大致)每個子圖的大小。這裡的優點是,當你加入分面變數時,圖表的大小會自動調整。但這可能會令人困惑。

幸運的是,有一種與函式無關的方式來設定精確的圖表大小。與其在建立圖表時設定圖表大小,不如在繪圖後呼叫 obj.figure.set_size_inches(...) 來修改它,其中 obj 是 matplotlib 軸(通常分配給 ax)或 seaborn FacetGrid(通常分配給 g)。

請注意,只有在 seaborn >= 0.11.2 時才存在 FacetGrid.figure;在此之前,你必須存取 FacetGrid.fig

此外,如果你正在製作 png(或在 Jupyter 筆記本中),你可以透過更改 dpi來縮放所有圖表。

為什麼 seaborn 沒有將圖表繪製到我指定的位置?#

你已明確建立具有一個或多個子圖的 matplotlib 圖表,並嘗試在其上繪製 seaborn 圖表,但最終得到一個額外的圖表和一個空白的子圖。也許你的程式碼看起來像這樣:

f, ax = plt.subplots()
sns.catplot(..., ax=ax)

這是一個關於圖形層級/軸層級的陷阱。圖形層級的函式總是會建立自己的圖形,因此你無法像使用軸層級函式那樣將它們導向現有的軸。當這種情況發生時,大多數函式會警告你,建議你使用適當的軸層級函式,並忽略 ax= 參數。一些較舊的函式可能會將圖形放置在你想要的位置(因為它們在內部將 ax 傳遞給它們的軸層級函式),同時仍然建立額外的圖形。後者這種行為應被視為錯誤,不應依賴它。

目前的工作方式是,你可以自己設定 matplotlib 圖形,也可以使用圖形層級的函式,但你不能同時做這兩件事。

為什麼我不能在長條圖/盒狀圖/條狀圖/小提琴圖上繪製線條?#

你正在嘗試使用多個 seaborn 函式建立單一圖表,可能是透過在長條圖或小提琴圖上繪製線圖或迴歸圖。你期望線條穿過每個盒子的平均值(等等),但它看起來對不齊,或者可能完全偏離到一側。

你正在嘗試將「類別圖」與另一種圖表類型結合。如果你的 x 變數具有數值,看起來應該可以這樣做。但請回想一下:seaborn 的類別圖會將類別軸上的唯一值映射到整數索引。因此,如果你的資料具有 1、6、20、94 的唯一 x 值,則對應的繪圖元素將繪製在 0、1、2、3 上(並且刻度標籤將被更改為表示實際值)。

線條或迴歸圖不知道發生了這種情況,因此它將使用實際的數值,並且圖表根本不會對齊。

目前,有兩種方法可以解決這個問題。在你想繪製線條的情況下,你可以使用(名稱有些誤導的)pointplot() 函式,它也是一個「類別」函式,將使用相同的規則繪製圖表。如果這不能解決問題(例如,它的視覺彈性不如 lineplot()),你可以自己實作從實際值到整數索引的映射,並以這種方式繪製圖表。

unique_xs = sorted(df["x"].unique())
sns.violinplot(data=df, x="x", y="y")
sns.lineplot(data=df, x=df["x"].map(unique_xs.index), y="y")

這在計劃的未來版本中會更容易實現,因為屆時將有可能使類別函式將數值資料視為數值。(截至 v0.12,僅在 stripplot()swarmplot() 中使用 native_scale=True 時才有可能。)

如何移動圖例?#

當將語義映射應用於圖表時,seaborn 會自動建立圖例並將其添加到圖形中。但自動選擇的圖例位置並不總是理想的。

對於 seaborn v0.11.2 或更高版本,請使用 move_legend() 函式。

在較舊的版本中,常見的模式是在繪圖後呼叫 ax.legend(loc=...)。雖然這似乎會移動圖例,但它實際上是用新的圖例替換它,使用任何碰巧附加到軸的已標記藝術家。這在不同圖表類型中並非一致有效。並且它不會傳播用於格式化多變數圖例的圖例標題或位置調整。

move_legend() 函式實際上比其名稱所暗示的更強大,它也可以用於在繪圖後修改其他圖例參數(字體大小、手柄長度等)。

其他自訂#

如何變更圖形的某些屬性?#

你想建立一個非常特定的圖表,而 seaborn 的預設值對你沒有幫助。

自訂 seaborn 圖形基本上有四層階層:

  1. 明確的 seaborn 函式參數

  2. 傳遞的 matplotlib 關鍵字引數

  3. Matplotlib 軸方法

  4. Matplotlib 藝術家方法

首先,請閱讀相關 seaborn 函式的 API 文件。每個函式都有許多參數(可能太多了),你或許可以使用 seaborn 自己的 API 來完成你想要的自訂。

但 seaborn 確實將許多自訂委派給 matplotlib。大多數函式的簽名中都有 **kwargs,它會捕獲額外的關鍵字引數並將其傳遞給底層的 matplotlib 函式。例如,scatterplot() 有許多參數,但你也可以使用 matplotlib.axes.Axes.scatter() 的任何有效關鍵字引數,它會在內部呼叫該引數。

傳遞關鍵字引數可以讓你自訂表示資料的藝術家,但你通常會想要自訂圖形的其他方面,例如標籤、刻度和標題。你可以透過呼叫 seaborn 繪圖函式傳回的物件的方法來執行此操作。根據你呼叫的是軸層級還是圖形層級函式,這可能是一個 matplotlib.axes.Axes 物件或 seaborn 包裝器(例如 seaborn.FacetGrid)。兩種物件都有許多方法可以讓你自訂圖形的幾乎任何內容。最簡單的方法通常是呼叫 matplotlib.axes.Axes.set()seaborn.FacetGrid.set(),它們可以讓你一次修改多個屬性,例如:

ax = sns.scatterplot(...)
ax.set(
    xlabel="The x label",
    ylabel="The y label",
    title="The title"
    xlim=(xmin, xmax),
    xticks=[...],
    xticklabels=[...],
)

最後,最深入的自訂可能需要你觸及 matplotlib 軸的「內部」,並調整儲存在其中的藝術家。這些將位於藝術家列表中,例如 ax.linesax.collectionsax.patches 等。

警告:matplotlib 和 seaborn 都不認為其繪圖函式產生的特定藝術家是穩定 API 的一部分。由於無法優雅地警告即將到來的藝術家類型或它們的儲存順序變更,因此與這些屬性互動的程式碼可能會意外中斷。儘管如此,seaborn 確實會盡力避免進行這種變更。

等等,我還需要學習如何使用 matplotlib 嗎?#

這實際上取決於你需要多少自訂。你當然可以在主要或僅與 seaborn API 互動的同時執行大量探索性資料分析。但是,如果你正在為簡報或出版品潤飾圖形,你可能會發現自己至少需要了解一點 matplotlib 的運作方式。Matplotlib 非常靈活,如果你鑽研得夠深入,它可以讓你控制圖形的任何方面。

Seaborn 最初的設計理念是,它將透過非常高階的 API 來處理一組特定的、定義完善的操作,同時讓使用者在需要額外自訂時「降級」到 matplotlib。如果已經知道如何使用 matplotlib,這可能是一個非常強大的組合,而且效果相當好。但是,隨著 seaborn 獲得更多功能,先學習 seaborn 也變得更加可行。在這種情況下,需要切換 API 往往會讓人感到更加困惑/沮喪。這促使 seaborn 開發新的 物件介面,旨在為高階和低階圖形規範提供更具凝聚力的 API。希望隨著它的成熟,它將緩解「雙函式庫問題」。

話雖如此,matplotlib 提供的深度控制程度確實是無與倫比的,因此如果你關心做非常特定的事情,那麼學習它真的很有價值。

如何將 seaborn 與 matplotlib 的物件導向介面一起使用?#

你偏好使用 matplotlib 的明確或 「物件導向」 介面,因為它使你的程式碼更容易推理和維護。但物件導向介面包含 matplotlib 物件的方法,而 seaborn 為你提供獨立的函式。

這是另一個記住圖形層級/軸層級區別會很有幫助的情況。

軸層級函式可以像任何 matplotlib 軸方法一樣使用,但不要呼叫 ax.func(...),而是呼叫 func(..., ax=ax)。它們還會傳回軸物件(如果目前在 matplotlib 的全域狀態中沒有活動的圖形,它們可能會建立該物件)。你可以使用該物件上的方法來進一步自訂圖表,即使你沒有從 matplotlib.pyplot.figure()matplotlib.pyplot.subplots() 開始。

ax = sns.histplot(...)
ax.set(...)

圖形層級函式無法導向現有圖形,但它們會將 matplotlib 物件儲存在它們傳回的 FacetGrid 物件上(seaborn 文件始終將其指定為名為 g 的變數)。

如果你的圖形層級函式只建立了一個子圖,你可以直接存取它:

g = sns.displot(...)
g.ax.set(...)

對於多個子圖,您可以使用 FacetGrid.axes(它永遠是一個 2D 軸陣列),或者使用 FacetGrid.axes_dict(它將行/列鍵映射到對應的 matplotlib 物件)。

g = sns.displot(..., col=...)
for col, ax in g.axes_dict.items():
    ax.set(...)

但是,如果您要批次設定所有子圖的屬性,請使用 FacetGrid.set() 方法,而不是迭代處理個別軸。

g = sns.displot(...)
g.set(...)

要存取底層的 matplotlib figure,請在 seaborn >= 0.11.2 版本使用 FacetGrid.figure(或在任何其他版本使用 FacetGrid.fig)。

我可以在長條圖上標註長條的值嗎?#

seaborn 沒有內建這樣的功能,但 matplotlib v3.4.0 新增了一個方便的函數(matplotlib.axes.Axes.bar_label()),它使操作相對容易。這裡有一些方法;請注意,您需要根據長條圖來自圖層級或軸層級函數而使用不同的方法。

# Axes-level
ax = sns.histplot(df, x="x_var")
for bars in ax.containers:
    ax.bar_label(bars)

# Figure-level, one subplot
g = sns.displot(df, x="x_var")
for bars in g.ax.containers:
    g.ax.bar_label(bars)

# Figure-level, multiple subplots
g = sns.displot(df, x="x_var", col="col_var)
for ax in g.axes.flat:
    for bars in ax.containers:
        ax.bar_label(bars)

我可以在暗黑模式下使用 seaborn 嗎?#

seaborn 沒有直接支援此功能,但 matplotlib 有一個 「dark_background」 樣式表,您可以這樣使用,例如:

sns.set_theme(style="ticks", rc=plt.style.library["dark_background"])

請注意,「dark_background」會將預設調色盤變更為「Set2」,並會覆蓋您在 set_theme() 中定義的任何調色盤。如果您希望使用不同的調色盤,則必須單獨呼叫 sns.set_palette()。預設的seaborn 調色盤(「deep」)在深色背景下的對比度很差,因此最好使用「muted」、「bright」或「pastel」。

統計查詢#

我可以存取 seaborn 統計轉換的結果嗎?#

由於 seaborn 在建立繪圖時會執行一些統計運算(彙總、自舉法、擬合迴歸模型),因此有些使用者希望能夠存取它計算的統計資料。這是無法做到的:seaborn(一個視覺化函式庫)明確地將提供用於查詢統計模型的 API 視為超出範圍。

如果您只是想認真驗證 seaborn 是否正確執行操作(或它是否與您自己的程式碼相符),它是開源的,因此您可以隨意閱讀程式碼。或者,由於它是 Python,您可以呼叫計算統計資料的私有方法(只是不要在生產程式碼中這樣做)。但不要期望 seaborn 提供在 scipystatsmodels 中更常見的功能。

我可以顯示標準誤差而不是信賴區間嗎?#

從 v0.12 開始,這在大多數地方都是可行的,可以使用新的 errorbar API(詳細資訊請參閱教學)。

為什麼 KDE 圖的 y 軸會超過 1?#

您使用 kdeplot() 估計資料的機率分佈,但 y 軸超過 1。機率不是以 1 為界嗎?這是一個錯誤嗎?

這不是錯誤,而是一個常見的混淆(關於核心密度圖和更廣泛的機率分佈)。連續機率分佈由機率密度函數定義,kdeplot() 會估計此函數。機率密度函數並非輸出機率:連續隨機變數可以取無限多個值,因此觀察到任何特定值的機率都無限小。您只能有意義地談論觀察到落在某個範圍內的值的機率。觀察到落在可能值的完整範圍內的值的機率為 1。同樣地,機率密度函數會正規化,使其下的面積(即該函數在其定義域上的積分)等於 1。如果可能值的範圍很小,則曲線必須超過 1 才能實現此目的。

常見的疑問#

為什麼 seaborn 被匯入為 sns#

這是對函式庫同名者的一個晦澀的引用,但您也可以將其視為「seaborn 名稱空間」。

為什麼 ggplot 比 seaborn 好得多?#

好問題。可能是因為您可以使用「geom」這個詞很多次,而且說起來很有趣。「Geom」。 「Geeeeooom」。