序 (可以不看的廢話)
從上篇博客開始就一直在忙患朱,忙了好幾個(gè)月的文章以后鲁僚,最近終于是在一個(gè)deadline之前把文章投了出去,所以也就可以重新回歸寫博客的日子了裁厅。在新年之前蕴茴,再看看能不能寫一兩篇的博客吧。當(dāng)然看的人可能也不是很多就是姐直。
關(guān)于今天的這篇博客倦淀,主要還是來講一下我現(xiàn)在超級(jí)超級(jí)超級(jí)喜愛和用的也很得心應(yīng)手的python的一個(gè)畫圖的包--plotly。
更新預(yù)告(flag): 可能會(huì)寫一些關(guān)于speciation的東西声畏。
plotly介紹
plotly是一個(gè)開源的python的庫撞叽,本質(zhì)上它應(yīng)該是一個(gè)收費(fèi)的企業(yè)級(jí)的軟件,官網(wǎng)在plotly插龄,它對(duì)自己公司的定位即
Modern Analytics Apps for the Enterprise
所以其實(shí)是它們的公司想必除了開發(fā)plotly以外還是想做一些分析的平臺(tái)的工作的愿棋,應(yīng)該是偏向于統(tǒng)計(jì)分析的部分,當(dāng)然這不是我們需要關(guān)注的地方均牢】酚辏回到plotly本身,plotly其實(shí)主要是一個(gè)基于d3.js開發(fā)的一個(gè)js的庫徘跪,而除了js的開發(fā)以外甘邀,它也對(duì)不同的編程語言開發(fā)了多種多樣的庫和工具包。例如R垮庐、matlab松邪、python。由于它的核心代碼其實(shí)js哨查,其它的工具包和庫其實(shí)僅僅是作為一個(gè)相應(yīng)的編程軟件對(duì)js的一個(gè)API逗抑,所以我們就不詳細(xì)的討論它的源代碼了 (畢竟我也看不太懂。。邮府。)荧关,而在plotly.py中,相關(guān)的內(nèi)容也藏的比較深褂傀,結(jié)構(gòu)比較復(fù)雜忍啤,我也不想過于深入的去討論它的源碼。
除了對(duì)plotly的背景介紹外紊服,plotly能做的事情當(dāng)然是重中之重的介紹點(diǎn)檀轨⌒鼐海基于js的繪圖庫最大的特點(diǎn)就是可交互欺嗤。最原始的output結(jié)果是html格式的,所以輸出的結(jié)果就是一個(gè)使用plotly.js進(jìn)行繪制的一個(gè)網(wǎng)頁卫枝,當(dāng)然如果是需要它生成一個(gè)靜態(tài)的圖的話也是沒什么問題的煎饼,它同樣也支持直接生成pdf,png之類的校赤。
plotly的資料
plotly這個(gè)軟件在國(guó)內(nèi)的使用率似乎并不高吆玖,所以很多中文的相關(guān)資料都十分的匱乏,所以資料方面也不是很多马篮,這里就只放一個(gè)plotly的官網(wǎng)以及一個(gè)Stack Overflow的專區(qū)的鏈接好了沾乘。
官網(wǎng)鏈接
stack overflow tagged plotly
plotly的起始(哲學(xué))
由于我使用plotly也有一段時(shí)間了,加上如果我從基礎(chǔ)開始講一個(gè)庫的使用其實(shí)也十分的沒有意思浑测,還不如讓你們直接去看官網(wǎng)的教程(畢竟我自己就是這么開始的)翅阵,所以我在這里從著重講一些高度概括的,一些更為核心的東西迁央。不要求初學(xué)者一看就能懂掷匠,但是可以在聽說了這些哲學(xué)以后,在學(xué)習(xí)和掌握plotly中更為得心應(yīng)手岖圈,我也覺得這才是一個(gè)博客的意義所在讹语,而不是照搬照抄,那完全只是一堆冗余知識(shí)的堆疊蜂科。
- 層/蹤跡(trace)的思想
plotly的設(shè)計(jì)上相較于
matplotlib
這些庫顽决,思想上更為接近photoshop
和illustrator
。所以對(duì)于接觸過這些的人來說也更為便于學(xué)習(xí)和理解导匣。意思就是說擎值,在畫一張圖時(shí),基本單位應(yīng)該是一層逐抑,這一層原則上應(yīng)該是同類的對(duì)象(例如scatter, bar, box, heatmap等)鸠儿,初學(xué)者都可以把這些認(rèn)為是一些對(duì)象(高級(jí)玩家可以自定義對(duì)象,因?yàn)橄駂eatmap就是一堆bar的堆疊嘛。)
- data與layout分離
除了上一個(gè)中說的層的思想进每,層都是在data內(nèi)部的汹粤。然后就是說layout和data都是分離的,這個(gè)其實(shí)很好理解田晚,因?yàn)橄駇atplotlib之類的繪圖庫其實(shí)也差不多嘱兼,所以這個(gè)也不多加講解。
- 基于dict
(json)的數(shù)據(jù)結(jié)構(gòu)
在上述的plotly的介紹中其實(shí)講到了一個(gè)很重要的一點(diǎn)贤徒,也就是plotly的核心是一個(gè)js的庫芹壕,那么與庫交流時(shí),最常見的是一種叫json的數(shù)據(jù)結(jié)構(gòu)接奈,json的數(shù)據(jù)結(jié)構(gòu)在python中不是一種常規(guī)的數(shù)據(jù)結(jié)構(gòu)踢涌,但是最為相近的也就是
dict
,所以plotly.py在設(shè)計(jì)時(shí)序宦,也就將一張圖的數(shù)據(jù)睁壁,定位在dict
上。
那么plotly的這種思想有什么特點(diǎn)呢互捌?在后面具體的例子中大家應(yīng)該會(huì)有更深的體會(huì)潘明,這里只簡(jiǎn)單的說。dict類似的數(shù)據(jù)結(jié)構(gòu)秕噪,非常方便大家記憶和查找相關(guān)的屬性钳降。像最為基礎(chǔ)的python的一個(gè)繪圖庫matplotlib
,我用不慣的原因很大程度上就是因?yàn)樗幕ハ嘁煤蛿?shù)據(jù)結(jié)構(gòu)過于復(fù)雜彎曲腌巾,一個(gè)figure有axes遂填,axes又可以繞回figure,所以不是一個(gè)單向的純粹的層級(jí)結(jié)構(gòu)壤躲。plotly的設(shè)計(jì)則十分的簡(jiǎn)潔明了城菊。
- 子圖(subfigure)和坐標(biāo)軸(axes)及其標(biāo)簽(ticklabels)是相互獨(dú)立的
在繪圖時(shí),除開了以上幾點(diǎn)碉克,最需要細(xì)心的地方也是最容易出問題的地方就是子圖或者坐標(biāo)軸這些問題了凌唬。其中坐標(biāo)軸的標(biāo)簽也是勸退大部分人的地方,plotly的設(shè)計(jì)我也不確定是好是壞漏麦,但是它將每一個(gè)子圖和坐標(biāo)軸割裂開了客税,通過layout中的一些名稱進(jìn)行綁定,你可以使坐標(biāo)軸綁定多個(gè)子圖撕贞,也可以讓子圖自由的伸展更耻、排布成不同的布局。同時(shí)捏膨,標(biāo)簽這些也是很容易的去設(shè)置秧均,只要你了解不論是類別變量的標(biāo)簽(ticktext)還是數(shù)值變量的標(biāo)簽(ticktext)其實(shí)本質(zhì)都是基于數(shù)值變量的標(biāo)簽(tickvalues)食侮,前兩個(gè)是看到的,最后一個(gè)大部分時(shí)候是隱藏的目胡。
- 交互動(dòng)畫的開關(guān)機(jī)制
上面講的一些其實(shí)都是跟交互沒太大關(guān)系的锯七,最終當(dāng)然也能實(shí)現(xiàn)交互的結(jié)果,不過那些更多的是由于本身就自帶的縮放(scale/zoom)的功能了誉己。plotly中眉尸,所有的交互,例如button巨双、slider和play都是在layout中的噪猾,這一點(diǎn)一定程度上是為了適應(yīng)js的代碼吧。因?yàn)閜lotly的基本邏輯是這樣的筑累,你把所有你需要繪制的東西全部一次性的放在一個(gè)figure內(nèi)部袱蜡,然后通過layout中的updatemeanus內(nèi)的開關(guān)進(jìn)行調(diào)控。而這些開關(guān)其實(shí)都直接對(duì)應(yīng)到了js的一些function疼阔,然后通過這些函數(shù)實(shí)現(xiàn)點(diǎn)擊(click)button/拖動(dòng)slider時(shí)戒劫,隱藏某些traces半夷,顯示某些traces這樣子的功能以達(dá)到交互(interactive)的功能婆廊。
plotly的哲學(xué)基本上以上的幾點(diǎn),在了解和知道這些的基礎(chǔ)上巫橄,我覺得可以更好的去理解和使用這個(gè)繪圖的庫淘邻。
plotly的基本使用(結(jié)合上述提到的所謂plotly哲學(xué)的一些demo)
trace和dict
import plotly
import plotly.graph_objs as go
import numpy as np
N = 1000
random_x = np.random.randn(N)
random_y = np.random.randn(N)
# Create a trace
trace1 = go.Scatter(
x = random_x,
y = random_y,
mode = 'markers'
)
random_y2= np.random.randn(N)
trace2 = go.Bar(x = random_x,y = random_y2)
data = [trace,trace2]
plotly.offline.plot(dict(data=[trace,trace2]))
最終它可以輸出這樣的一個(gè)圖,其實(shí)理論上說這個(gè)圖(毫無意義)湘换,也很丑宾舅,但其實(shí)我只是為了說明bar 圖和scatter圖的共存,在plotly中彩倚,他們兩個(gè)屬于兩個(gè)互不干擾的trace筹我,并且,每次初始化一個(gè)trace時(shí)帆离,除了直接使用go.Scatter
或者go.Bar
以外蔬蕊,內(nèi)容物真的只需要通過類似于dict
的方式就可以指定。如上的代碼哥谷。
subplot岸夯、interactive和dict
import plotly
import plotly.graph_objs as go
import plotly.figure_factory as ff
import numpy as np
from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import *
dataset = fetch_california_housing()
X_full, y_full = dataset.data, dataset.target
X = X_full[:, [0, 5]]
distributions = dict([
('Unscaled data', X),
('Data after standard scaling',
StandardScaler().fit_transform(X)),
('Data after min-max scaling',
MinMaxScaler().fit_transform(X)),
('Data after max-abs scaling',
MaxAbsScaler().fit_transform(X)),
('Data after robust scaling',
RobustScaler(quantile_range=(25, 75)).fit_transform(X)),
('Data after power transformation (Yeo-Johnson)',
PowerTransformer(method='yeo-johnson').fit_transform(X)),
('Data after power transformation (Box-Cox)',
PowerTransformer(method='box-cox').fit_transform(X)),
('Data after quantile transformation (gaussian pdf)',
QuantileTransformer(output_distribution='normal')
.fit_transform(X)),
('Data after quantile transformation (uniform pdf)',
QuantileTransformer(output_distribution='uniform')
.fit_transform(X)),
('Data after sample-wise L2 normalizing',
Normalizer().fit_transform(X)),
])
y = minmax_scale(y_full)
# scale the output between 0 and 1 for the colorbar
#
fig = plotly.tools.make_subplots(rows=2,cols=2,specs=[[{},None],[{},{}]],shared_xaxes=True,shared_yaxes=True)
#This is the format of your plot grid:
#[ (1,1) x1,y1 ] (empty)
#[ (2,1) x2,y2 ] [ (2,2) x3,y3 ]
# 初始化了一個(gè)如上的一個(gè)figure布局,以便進(jìn)行繪圖
traces = []
for scaler,scaled_X in list(distributions.items())[:1]:
# 只畫第一個(gè)的話们妥,如下
_fig1 = ff.create_distplot([scaled_X[:,0]], ['a'],show_hist=False,show_rug=False)
_fig2 = ff.create_distplot([scaled_X[:,1]],['a'],show_hist=False,show_rug=False)
trace = go.Scatter(x=scaled_X[:,0],y=scaled_X[:,1],mode='markers',marker=dict(color=y),yaxis='y2',xaxis='x2',name=scaler,legendgroup=scaler)
fig.append_trace(go.Scatter(x=_fig1.data[0]['x'],y=_fig1.data[0]['y'],mode='lines',name='upper left',legendgroup=scaler),1,1)
fig.append_trace(go.Scatter(x=_fig2.data[0]['y'],y=_fig2.data[0]['x'],mode='lines',name='down right',legendgroup=scaler),2,2)
fig.append_trace(trace,2,1)
fig.layout.xaxis1.domain = [0,0.8]
fig.layout.xaxis2.domain = [0,0.8]
fig.layout.xaxis3.domain = [0.85,1.0]
fig.layout.yaxis1.domain = [0.85,1.0]
fig.layout.yaxis2.domain = [0,0.8]
fig.layout.yaxis3.domain = [0,0.8]
fig.layout.width = 1000
fig.layout.height = 1000
plotly.offline.plot(fig)
上面的代碼畫出來是這樣的猜扮。
由于是交互的,我覺得大家最好還是自己試一試會(huì)體會(huì)更深监婶。(當(dāng)然我要是放在jupyter notebook上也就更好了旅赢,但是我有點(diǎn)懶齿桃。。煮盼。)
那么我們上面的代碼僅僅只畫了一個(gè)源譬,那怎么能行呢!對(duì)吧孕似。接下來我們?cè)?strong>上面的代碼的基礎(chǔ)上踩娘,再加一些東西,使它更有意思起來喉祭。