1. 程式人生 > >Seaborn中文教程(一):視覺化變數間的關係

Seaborn中文教程(一):視覺化變數間的關係

眾所周知,Seaborn“可能”是Python下最友好、易用的視覺化工具了,視覺化效果也非常好。但是截止目前,並沒有一份中文教程供廣大國內Python使用者查閱學習。怎麼能因為語言的問題,讓大家錯過這麼好用的一個視覺化工具呢?

思考再三,我決定花一些時間將官方的英文文件整理出來,為大家提供一份最權威的中文教程。考慮到我的時間比較碎片化,這項工作可能會在未來的幾周內完成,感興趣的朋友可以先關注和收藏下,後續的更新會在CSDN和我的個人部落格中(www.data-insights.cn/www.data-insight.cn)釋出。

今天,我整理了第一部分:如何用Seaborn視覺化變數之間的關係,這一部分非常實用,基本上學習後馬上就能給我們帶來很多幫助。那麼接下來,就開始我們的學習吧!

文章較長,建議收藏後在PC站或者我的個人部落格閱讀。


統計分析是這樣的一個過程:嘗試去理解一個數據集中變數之間的關係,以及這些關係如何受到其他變數的影響。視覺化是這個過程的核心元素,因為當資料以非常恰當的方式展示出來時,我們可以非常直觀地觀察到某些趨勢或者模式,而這些,就揭示了變數之間的關係。

在這篇教程中,我們會討論三個seaborn函式。我們用的最多的是relplot()。這是一個圖形級別的函式,它用散點圖和線圖兩種常用的手段來表現統計關係。relplot()使用兩個座標軸級別的函式來結合了FacetGrid

  • scatterplot():(使用kind="scatter"
    ,這是預設引數)
  • lineplot():(使用`kind=“line”)

正如我們所見,這些函式非常直白,因為它們用了簡單易懂的展示方式,卻可以呈現複雜的資料集結構。這是因為通過huesizestyle引數,我們可以在二維圖形中表現出更多的變數(除了x軸和y軸的兩個,還可以通過不同的方式額外展示3個變數)。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="darkgrid")

一、用散點圖展示相關變數

散點圖是統計圖形中的中流砥柱。它用一系列的散點將兩個變數的聯合分佈描繪出來,其中每個點就是一個觀測樣本。這種描述方式可以讓我們從視覺上推斷出大量資訊,來判斷兩個變數之間是否存在某種有意義的關係。

seaborn中,我們有數種方法可以實現散點圖的繪製。最基本的一種適用於兩個變數都是數值型變數的情況,它就是scatterplot()。在分類視覺化教程中,我們會看到如何繪製分類資料的散點圖。relplot()的預設型別(kind)就是scatterplot()(當然,我們也可以強制指定引數kind="scatter",這和不指定這一引數時效果是一樣的)。

tips = sns.load_dataset("tips")
sns.relplot(x="total_bill", y="tip", data=tips);

png

當我們已經將散點繪製在二維的平面上時,我們還可以根據第三個變數來對這些點施以不同的顏色,從而引入一個新的維度。在seaborn中,我們用hue引數實現了這種想法,因為點的顏色是有意義的。

sns.relplot(x="total_bill", y="tip", hue="smoker", data=tips);

png

如果我們想要強調不同分類之間的差異,同時增加易讀性,我們可以對不同的分類使用不同的標記樣式:

sns.relplot(x="total_bill", y="tip", hue="smoker", style="smoker", data=tips);

png

我們也可以同時展示四個變數,只需要將huestyle引數單獨調整到不同的分類變數即可。但是我們要謹慎使用這種方法,因為我們的眼睛對於形狀的敏感性遠遠不如對顏色的敏感性。

sns.relplot(x="total_bill", y="tip", hue="smoker", style="time", data=tips);

png

在上邊這個例子中,hue引數對應的變數是分型別資料,因此seaborn自動為它應用了預設的定性(分類)調色盤。如果hue引數對應的變數是數值型的(可轉化為浮點數的),那麼預設的顏色也會隨之變為連續的定量調色盤。

sns.relplot(x="total_bill", y="tip", hue="size", data=tips);

png

上述兩種情況下(分類或連續資料),我們都可以自定義我們的調色盤。有很多選項可以實現這一目的。下面,我們使用cubehelix_palette()的字串介面來定製我們的連續調色盤:

sns.relplot(x="total_bill", y="tip", hue="size", 
            palette="ch:r=-.5,l=.75", data=tips);

png

我們還可以使用點的大小來引入第三個額外的變數:

sns.relplot(x="total_bill", y="tip", size="size", data=tips);

png

matplotlib.pyplot.scatter()不同的是,這裡並不是使用原始資料中的數值來為每個點選擇面積大小,seaborn將原始資料歸一化(正則化)到了某個範圍,這個範圍可以由我們來指定:

sns.relplot(x="total_bill", y="tip", size="size", sizes=(15, 200), data=tips);

png

更多關於如何定製不同引數來展示統計關係的示例可以在scatterplot()的API中找到。

二、使用線圖表現連續性

散點圖很高效,但是沒有哪種視覺化型別可以完美應對所有情況。事實上,我們的視覺化呈現方式要適應資料集的種類以及我們想要通過圖形回答的問題。

在某些資料集中,我們可能想要理解某個變數隨著時間的變化規律,或者想要理解某個連續型的變數。這種情況下,線圖會是一個不錯的選擇。在seaborn中,我們可以通過lineplot()函式或者使用帶有kind="line"引數的relplot()來實現線圖的繪製。

df = pd.DataFrame(dict(time=np.arange(500), value=np.random.randn(500).cumsum()))
g = sns.relplot(x="time", y="value", kind="line", data=df)
g.fig.autofmt_xdate()

png

由於lineplot()假設使用者在大多數情況下是在嘗試描繪y相對於x的函式(變化規律),因此它在繪製之前會預設先對x做一個排序。不過我們可以禁止它。

df = pd.DataFrame(np.random.randn(500, 2).cumsum(axis=0), columns=["x", "y"])
sns.relplot(x="x", y="y", sort=False, kind="line", data=df);

png

聚合並展示不確定性

在更多複雜的資料集中,會出現一個x軸變數對應了多個觀測值(y)的情況。seaborn會預設將多個觀測值聚合起來,並且將它們的均值以及95%的置信區間展示出來:

fmri = sns.load_dataset("fmri")
sns.relplot(x="timepoint", y="signal", kind="line", data=fmri);

png

置信區間是通過自助取樣法(bootstrapping)計算的,這在遇到大型資料集時可以幫助我們節省時間。當然,我們也可以禁止它。

sns.relplot(x="timepoint", y="signal", ci=None, kind="line", data=fmri);

png

另一個不錯的選擇是,我們可以用標準差替代置信區間來展示每個時間點下觀測值的分佈,當資料集比較大時這一選擇尤其明智。

sns.relplot(x="timepoint", y="signal", kind="line", ci="sd", data=fmri);

png

如果想要關閉所有的聚合操作,我們可以設定estimator=None。不過當同一時間點存在多個觀測值時,我們的圖會看起來有些奇怪。

sns.relplot(x="timepoint", y="signal", estimator=None, kind="line", data=fmri);

png

通過引數對映視覺化資料子集

lineplot()scatterplot()一樣具有很強的靈活性:它也可以通過huesizestyle引數來展示額外的三個變數。它和scatterplot()使用了相同的API,因此我們不需要停下來絞盡腦汁地思考哪些引數是用來控制線條、哪些引數是用來控制散點。

使用不同的引數會決定我們的資料如何聚合。比如,增加一個具有兩個水平的分類變數作為hue引數,會將我們的圖形分為兩條線以及兩個誤差帶,並分別施以不同的顏色來區分資料的分類歸屬。

sns.relplot(x="timepoint", y="signal", hue="event", 
            kind="line", data=fmri);

png

我們可以增加一個style引數,以不同的線條樣式來展示不同的分類:

sns.relplot(x="timepoint", y="signal",  hue="region",  
            style="event", kind="line", data=fmri);

png

我們還可以設定不同分類的標記樣式,標記樣式既可以和線條樣式同時設定,也可以各自單獨設定。

sns.relplot(x="timepoint", y="signal", hue="region", style="event", 
            dashes=False, markers=True, kind="line", data=fmri);

png

跟散點圖一樣,我們要慎重使用這些引數來展示太多變數。有些時候它們會展示豐富的資訊,但是有些時候它們會使圖形太過複雜導致我們難以解析和解釋它。然而當你僅打算考慮額外的一個變數時,同時修改它們的顏色和樣式會很有幫助。當需要考慮到色盲人群時,將圖形顏色設定為黑白色調則是一個不錯的選擇。

sns.relplot(x="timepoint", y="signal", hue="event", style="event",
            kind="line", data=fmri);

png

當我們需要應對重複測量的資料時,我們可以將不同的抽樣單元(單次實驗觀測到的資料系列)分離開來展示,這並不需要我們使用一個語義引數(hue/style/size)。後者會導致圖例看起來像一個災難(想象一下幾十個分類的情況):

sns.relplot(x="timepoint", y="signal",  hue="region", 
            units="subject", estimator=None, kind="line", 
            data=fmri.query("event == 'stim'"));

png

scatterplot()類似,lineplot()預設的調色盤以及圖例處理方式也取決於hue對應的資料是分型別還是連續數值型。

dots = sns.load_dataset("dots").query("align == 'dots'")
sns.relplot(x="time", y="firing_rate",
            hue="coherence", style="choice",
            kind="line", data=dots);

png

hue引數對應的變數的資料是均勻分佈在對數刻度上的(即資料分佈範圍非常大,比如從1到1億),即使是連續的調色盤也無法很好地應對這種情況。但是我們可以使用列表或者字典對每條線指定一個顏色。

palette = sns.cubehelix_palette(light=.8, n_colors=6)
sns.relplot(x="time", y="firing_rate",
            hue="coherence", style="choice",
            palette=palette,
            kind="line", data=dots);

png

或者我們可以直接修改調色盤數值的正則方式:

from matplotlib.colors import LogNorm
palette = sns.cubehelix_palette(light=.7, n_colors=6)
sns.relplot(x="time", y="firing_rate", hue="coherence", style="choice",
            hue_norm=LogNorm(), kind="line", data=dots);

png

不要忘了我們還有個size引數,它用來修改線條的寬度:

sns.relplot(x="time", y="firing_rate", size="coherence", 
            style="choice", kind="line", data=dots);

png

size一般接受連續數值型變數,但是我們也可以傳入分型別變數。但是要慎重考慮這種做法,因為這樣比“粗線 vs. 細線”的區分難多了。然而,當資料具有非常高頻的變異性時,我們使用style表現的不同線條樣式會很難區分,這時使用不同的線條寬度就是一個更高效的選擇了:

sns.relplot(x="time", y="firing_rate",
           hue="coherence", size="choice",
           palette=palette,
           kind="line", data=dots);

png

繪製時間序列資料

線圖常用來描繪日期、時間相關的詩句。這些方法以原始格式傳入更底層的matplotlib函式中,這樣它們就可以利用matplotlib的能力來格式化日期資料。但是所有的時間格式化過程都是在matplotlib層實現的,想要知道更多實現的細節就需要去看一下matplotlib中關於這部分的文件:

df = pd.DataFrame(dict(time=pd.date_range("2017-1-1", periods=500),
                       value=np.random.randn(500).cumsum()))
g = sns.relplot(x="time", y="value", kind="line", data=df)
g.fig.autofmt_xdate()

png

三、用更多子圖展示多重關係

前邊我們已經強調過,雖然我們可以在一張圖中展示數個不同的語義變數(通過hue/style/size引數),但是這麼做並不是總會高效。那麼當我們真的很想理解在某些額外變數的影響下兩個變數之間的關係有什麼不同時怎麼辦呢?

最好的辦法就是畫更多的圖。由於relplot()是基於FacetGrid的,因此這很容易做到。當我們想要表現出一個額外變數的影響時,我們可以不用將它賦給前邊提到的語義引數(hue/style/size),而是用它來將圖形“面板”化。這意味著我們會建立多個座標軸,分別用來繪製不同的子資料集:

sns.relplot(x="total_bill", y="tip", hue="smoker", col="time", data=tips);

png

我們還可以同時使用col(列)和row(行)引數來展示兩個變數的影響。當我們在圖中增加了更多的變數時(會有更多的子圖),我們可能會想要調整圖形的大小。要記住在FacetGrid中,我們用height(子圖高度)和aspect(高寬比)來定製每個子圖的大小:

sns.relplot(x="timepoint", y="signal", hue="subject", col="region", 
            row="event", height=3, kind="line", estimator=None, data=fmri);

png

當我們想要檢驗某個具有大量水平的變數的影響時,我們可以將這個變數賦給col引數,同時我們通過col_wrap引數設定每行達到多少列就換行:

sns.relplot(x="timepoint", y="signal", hue="event", style="event",
            col="subject", col_wrap=5,
            height=3, aspect=.75, linewidth=2.5,
            kind="line", data=fmri.query("region == 'frontal'"));

png

這種常被叫做“格子圖”或“small-multiples”的視覺化方式,非常高效,因為它們呈現資料的方式使得我們很容易同時發現整體的模式以及不同模式之間的偏差。當你需要利用scatterplot()relplot()的靈活性來表現更多資訊時,一定要記住,多幅簡單的圖通常比一幅複雜的圖更加高效。