在看"Mastering Python for Finance"這本書的時候,做者推薦了一個收集有趣的Jupyter notebook的集合,其中涵蓋的門類很是廣。python
其中由James A. Bednar撰寫的一篇文章,介紹在可視化大量數據的時候容易犯的錯誤。利用Jupyter notebook直觀並且容易分享和展現的特色,Bednar直觀地討論了這些很容易掉入,但不容易意識到的陷阱。app
原文連接dom
在利用可視化(Visualization)方法來從數據(一般極大量,以致於咱們不可能一個個地分析每一個數據點)中獲取信息的過程當中,咱們可能會沉浸在繪製的細節中,從而過快地下結論,而不對這些結論產生應有的懷疑。可是事實上,咱們每每由於某些繪圖參數的偶然性,進入到一些陷阱中,從而產生了錯誤的觀察和錯誤的結論。瞭解這些常見的陷阱,合理地懷疑和推敲本身的結論,才能挖掘數據中的金子,而不是垃圾。函數
咱們會在這篇文章中討論:大數據
,以及如何減輕或者消除他們。spa
咱們將會須要這些包,能夠運行下面的命令安裝:翻譯
conda install -c bokeh -c ioam holoviews colorcet matplotlib scikit-image
咱們首先未來自兩個不一樣來源的數據集繪製在同一座標系下--A圖中的點和B圖中的點。出人意料的是,先畫A仍是先畫B,咱們看到的結果很是不一樣。code
def blues_reds(offset=0.5,pts=300): blues = (np.random.normal( offset,size=pts), np.random.normal( offset,size=pts), -1*np.ones((pts))) reds = (np.random.normal(-offset,size=pts), np.random.normal(-offset,size=pts), 1*np.ones((pts))) return hv.Points(blues, vdims=['c']), hv.Points(reds, vdims=['c']) blues,reds = blues_reds() blues + reds + reds*blues + blues*reds
C圖和D圖顯示的是同一組數據,可是給人大相徑庭的印象:在C圖中藍色的點顯得更多,而在D圖中紅色的點顯得更多。單憑C圖或者D圖,咱們就會獲得錯誤的結論。實際上兩種點一樣多,這裏在做祟的是occlusion(閉塞)。orm
一個數據集被另外一個數據集閉塞時,叫作overplotting(過密繪圖,沒有找到翻譯,譯者本身創造的)或者overdrawing,任什麼時候候咱們將一個點或者一條線疊加在另外一個點或者另外一條線上,均可能發生。不只是散點圖,曲線圖、3D曲面圖等等都會出現occlusion。排序
能夠經過減少alpha值來減輕過密繪圖。alpha是繪圖軟件上提供的控制透明度的參數,例如:若是alpha是0.1,當十個數據點重合的時候,塗色達到飽和。這樣以來,繪圖順序的影響會變小,可是看清每一個點變得更加困難。
%%opts Points (s=50 alpha=0.1) blues + reds + reds*blues + blues*reds
這裏,C和D看起來很是類似(也理應如此,由於分佈是同樣的),可是在一些地方(有超過十個點重合的地方),仍是有過飽和(oversaturation)問題。在這幅圖裏,過飽和出如今中間。檢測過飽和的惟一可靠方法是透過繪製兩個版本,而後比較,或者依靠檢查像素值來看是否有達到飽和的像素(必要可是不充分的條件)。設置了alpha以後,當不少個點重合在一塊兒,只有最後被繪製上去的十個點會影響最終的顏色(alpha=0.1)。
壞消息是,正確的alpha值和每一個數據集有關。若是一個圖中較多點重合在某個特定的區域,手動調參獲得的alpha值可能仍是會失實描述這個數據集。
%%opts Points (alpha=0.1) blues,reds = blues_reds(pts=600) blues + reds + reds*blues + blues*reds
例如這裏,C和D仍是看起來不一樣,可是他們本該是很類似地。既然可視化數據的意義是研究數據集,那麼必須根據數據集的特性,手動找出某個參數是一個很是很差的跡象。
%%opts Points (s=10 alpha=0.1 edgecolor=None) blues + reds + reds*blues + blues*reds
即使是對於很小的數據集,找到正確的數據點面積和alpha參數也不容易。對於愈來愈大的未知性質數據集,咱們更加容易犯這些錯誤而沒意識到。
咱們如今改變一下數據,換成一個單一種類的數據集。單一種類的數據集中,過飽和會模糊密度上的區別。舉個例子,當alpha等於0.1,10個、20個、2000個同一種類(同一顏色)的點重合在一塊兒,他們看起來是徹底同樣的。
咱們生成兩個中心離開一點距離的正態分佈,而後把他們疊加在一塊兒,且不區分兩個種類(全部的點都用黑色標記)。
%%opts Points.Small_dots (s=1 alpha=1) Points.Tiny_dots (s=0.1 alpha=0.1) def gaussians(specs=[(1.5,0,1.0),(-1.5,0,1.0)],num=100): """ A concatenated list of points taken from 2D Gaussian distributions. Each distribution is specified as a tuple (x,y,s), where x,y is the mean and s is the standard deviation. Defaults to two horizontally offset unit-mean Gaussians. """ np.random.seed(1) dists = [(np.random.normal(x,s,num), np.random.normal(y,s,num)) for x,y,s in specs] return np.hstack([d[0] for d in dists]), np.hstack([d[1] for d in dists]) hv.Points(gaussians(num=600), label="600 points", group="Small dots") + \ hv.Points(gaussians(num=60000), label="60000 points", group="Small dots") + \ hv.Points(gaussians(num=600), label="600 points", group="Tiny dots") + \ hv.Points(gaussians(num=60000), label="60000 points", group="Tiny dots")
參數依然致使結果很不穩定。對於600個數據點,小點(0.1面積,alpha=1)效果很好,可是大的數據集,overplotting問題會模糊分佈B的形狀和密度;微小點(比小點小十倍,alpha=0.1)在大數據集D中表現很好,可是在C中很是的差,幾乎一個點都看不到。
憑藉現有的繪圖軟件地計算力,隨着數據集愈來愈大,繪製所有數據的散點圖會在某一個時刻變得不可能。爲了解決,人們有時候簡單地抽樣出一萬個點以後繪製。可是在A圖中能看到,若是一個分佈很不幸地被undersample了,咱們有可能看不出形狀。要讓分佈的形態可見,必需要有足夠的數據點在稀疏的區域,和正確的繪圖參數,但這須要反覆試錯。
研究者有時使用熱力圖而不是散點圖。一個熱力圖有若干個固定大小的網格。熱力圖不限制數據點的總理。熱力圖實際上逼近給定空間內的機率密度函數。
更粗糙的熱力圖將噪音抹掉來展現真實的分佈,而更精細的熱力圖能夠展現更多的細節。
咱們來看看一組表達同一個分佈地熱力圖,惟一不一樣的網格的數量(number of bins)。
def heatmap(coords,bins=10,offset=0.0,transform=lambda d,m:d, label=None): hist,xs,ys = np.histogram2d(coords[0], coords[1], bins=bins) counts = hist[:,::-1].T transformed = transform(counts,counts!=0) span = transformed.max()-transformed.min() compressed = np.where(counts!=0,offset+(1.0-offset)*transformed/span,0) args = dict(label=label) if label else {} return hv.Image(compressed,bounds=(xs[-1],ys[-1],xs[1],ys[1]),**args) hv.Layout([heatmap(gaussians(num=60000),bins) for bins in [8,20,200]])
、
A過於粗糙,不能準確地描述分佈。有足夠多網格地C,逼近散點圖(上圖的D)。有中等數量網格的B能夠磨平採樣過疏;B實際上比C更忠實於數據,可是C更好的表明了抽樣(譯者也沒明白這句話)。因此尋找一個合適的熱力圖網格大小須要一些經驗,對比多個不一樣網格大小的熱力圖能夠提供一些幫助。至少,網格大小這個參數是一個有意義的東西,在數據而不屬於繪圖的細節--好比點有多透明,這些東西每每是被隨機肯定下來的。
原則上來說,熱力圖方法能夠徹底避免以上三個陷阱:
固然,熱力圖有本身的缺陷。熱力圖和附帶alpha的散點圖都會有,又不多被意識到的一個問題是undersaturation。在這個問題出現時,大量的數據點可能會被忽略,由於它們要麼分佈在很是廣大的網格中,或者在不少幾乎透明的散點中。咱們來看看一組有着不一樣的分散度(標準差)的高斯分佈:
dist = gaussians(specs=[(2,2,0.02), (2,-2,0.1), (-2,-2,0.5), (-2,2,1.0), (0,0,3)],num=10000) hv.Points(dist) + hv.Points(dist)(style=dict(s=0.1)) + hv.Points(dist)(style=dict(s=0.01,alpha=0.05))
A,B,C圖畫的是同一組數據的:5個不一樣中心,不一樣標準差的高斯分佈:
在A圖中,最散的分佈(分佈5)覆蓋了一切,咱們徹底看不到任何的結構。B和C好一點,但仍是不能使人滿意。在B中有四個明顯的高斯分佈,除了最大的以外都看起來有着相同的密度,最窄的分佈的乎看不到。而後,咱們嘗試改變alpha,獲得C。undersaturation出現了:最離散的高斯分佈徹底消失了!若是咱們只看C,就會弄丟這個分佈。
那麼熱力圖能夠倖免欠飽和嗎?
hv.Layout([heatmap(dist,bins) for bins in [8,20,200]])
很是窄的分佈,表現爲一些有着很是高計數的網格。由於色譜和數軸的映射是線性的,其餘的網格比起來實在是太淡了。C甚至成了一篇雪白。
爲了不欠飽和,你能夠增長一個補償來確保低計數(但非零)的網格被映射爲一個可視顏色,剩下的色譜區間用來表達計數的差別。
hv.Layout([heatmap(dist,bins,offset=0.2) for bins in [8,20,200]]).cols(4)
欠飽和被完美地消除了:像素要麼是零(純白色),要麼是一個咱們選定的非背景顏色。最大的高斯分佈能夠被清楚地看見。
但是,五個分佈不一樣的高斯的結構仍是不能被辨別出來。A太粗糙了,B也過於粗糙。C的粗糙度合適,可是它看來像是隻有一個最大的高斯分佈,而不是五個。
爲何C呈現這個樣子呢?這個陷阱更微妙:五個高斯分佈之間,數據點密度的區別難以被眼睛捕捉,由於幾乎全部的像素,要麼在可視區間的底部(淺灰色),要麼是在頂部(純黑色)。其餘的可視區間直接被丟棄了,徹底沒被利用起來!豐富內在的結構沒有被傳遞出來。若是若是數據點均勻地分佈在0到10000區間中,那麼上面的圖沒有問題,但現實中不多是這樣。
因此,咱們應該將計數轉換成某些更好的,能視覺上表達計數的相對區別的指數。這個指數應該能保存計數的相對區別,可是又能將他們映射在整個可視區間內。對數轉換是一個選擇:
hv.Layout([heatmap(dist,bins,offset=0.2,transform=lambda d,m: np.where(m,np.log1p(d),0)) for bins in [8,20,200]])
很棒!C裏面,咱們能夠清楚看到細節。五個高斯分佈不一樣的離散度在C中很清楚。
還有一個疑問:爲何是對數轉換?對數轉換奏效,其實跟咱們的標準差大體遵循一個等比數列有關。那麼對於更大的,未知結構的數據,有一些指引咱們轉換的原則嗎?
有。咱們換個思路,其實在繪製這個數據集的時候,咱們遇到的困難源自每一個網格里的計數差距過大, 從10000(很是窄)到1(很是離散)。普通的顯示器只有256個灰度,並且人類能感知不一樣灰度的能力是有限的,若是把數據直接映射到灰度上,效果不會很好。既然咱們已經在上面的方法中拋棄了直接映射,而用了對數轉換來克服欠飽和,那咱們能不能徹底擯棄數值映射這個思路,而使用相對排序呢?這樣的圖會保留順序(order)而不是量度(magnitude)。假設顯示器有100個灰度,最低的1%的網格會被分配第一個可視的灰度,下一個1%會被分配到下一個可視的灰度,……,最高的1%會被分配到灰度255(黑色)。真實的數值會被忽略掉,可是他們的相對量仍然會決定他們在屏幕上顯示什麼顏色。因此,分佈的結構而不是數值被保存下來。
使用於圖像處理包中的histogram equalization function,每一個灰度大約會有一樣數量的像素:
try: from skimage.exposure import equalize_hist eq_hist = lambda d,m: equalize_hist(1000*d,nbins=100000,mask=m) except ImportError: eq_hist = lambda d,m: d print("scikit-image not installed; skipping histogram equalization") hv.Layout([heatmap(dist,bins,transform=eq_hist) for bins in [8,20,200]])
C圖如今很完美:五個高斯分佈清晰可見,並且咱們沒有使用任何隨意肯定的參數。固然,咱們也丟失了原始的計數。
每一個熱力圖須要一個顏色映射,也就是,一個從數值查詢像素顏色的表。可視化的目的是解釋數據的特性,爲了這一點咱們須要挑選可讓觀察者客觀地感知數據的顏色映射。不幸的是,繪圖程序大多數經常使用的顏色映射都是不均勻的(也是很差的)。
好比,在’jet'(2015年前matlab和maplotlib使用的默認顏色映射)中,很大一部分的數值會落在綠色中很難區分的一段,在‘hot'中,落在黃色中很難區分的一段。
來看看咱們以前用的數據,在兩個不一樣的顏色映射下,有什麼區別:
import colorcet hv.Layout([heatmap(dist,200,transform=eq_hist,label=cmap)(style=dict(cmap=cmap)) for cmap in ["hot","cet_fire"]]).cols(2)
很明顯,cet_fire
的表達能力更強,也更精準地表現出區塊之間的密度差別。hot
將全部的高密度區域都映射到亮黃色/白色中難以在官能上區分的一段,給咱們一種「過飽和」的感受(但咱們的繪製原理其實應該確保了過飽和不會出現)。幸運的是,各個語言中都有很多均勻顏色映射:colorcet
包中的50多個,或者matplotlib
自帶的四個(viridis
,plasma
,inferno
,magma
),或者Matlab的Parula
。
完。
【翻譯能力有限,錯誤在所不免,歡迎指出】