沿用70多年的經典數據可視化方法,如何用Python實現?
- 2020 年 2 月 14 日
- 筆記
作者:屈希峰,資深Python工程師,知乎多個專欄作者
來源:大數據DT(ID:hzdashuju)
01 概述
時間序列(Time series)是指將某種現象某一個統計指標在不同時間上的各個數值,按時間先後順序排列而形成的序列。時間序列法是一種定量預測方法,也稱簡單外延法,在統計學中作為一種常用的預測手段被廣泛應用。
時間序列分析在第二次世界大戰前應用於經濟預測。「二戰」中和「二戰」後,在軍事科學、空間科學、氣象預報和工業自動化等領域的應用更加廣泛。
時間序列分析(Time Series Analysis)是一種動態數據處理的統計方法。該方法基於隨機過程理論和數理統計學方法,研究隨機數據序列所遵從的統計規律,用於解決實際問題。時間序列構成要素是現象所屬的時間和反映現象發展水平的指標數值,如下圖所示。
▲時間序列
時間序列中的每個觀察值大小,是影響變化的各種不同因素在同一時刻發生作用的綜合結果。從這些影響因素髮生作用的大小和方向變化的時間特性來看,這些因素造成的時間序列數據的變動分為如下4種類型。
- 趨勢性:某個變數隨著時間進展或自變數變化,呈現出一種比較緩慢而長期的持續上升、下降、停留的同性質變動趨向,但變動幅度可能不相等。
- 周期性:某因素由於外部影響隨著自然季節的交替出現高峰與低谷的規律。
- 隨機性:個別為隨機變動,整體呈統計規律。
- 綜合性:實際變化情況是幾種變動的疊加或組合。預測時設法過濾除去不規則變動,突出反映趨勢性和周期性變動。
02 實例
時間序列程式碼示例如下所示。
- 程式碼示例①
from bokeh.sampledata.stocks import AAPL import numpy as np # 數據 aapl = np.array(AAPL['adj_close']) aapl_dates = np.array(AAPL['date'], dtype=np.datetime64) window_size = 30 window = np.ones(window_size)/float(window_size) aapl_avg = np.convolve(aapl, window, 'same') # 畫布 p = figure(width=800, height=350, x_axis_type="datetime") # 圖層 p.circle(aapl_dates, aapl, size=4, color='darkgrey', alpha=0.2, legend='close') p.line(aapl_dates, aapl_avg, color='red', legend='avg') # 自定義屬性 p.title.text = "AAPL One-Month Average" p.legend.location = "top_left" p.grid.grid_line_alpha=0 p.xaxis.axis_label = 'Date' p.yaxis.axis_label = 'Price' p.ygrid.band_fill_color="gray" p.ygrid.band_fill_alpha = 0.1 p.legend.click_policy="hide" # 點擊圖例顯示隱藏數據 # 顯示結果 show(p)
運行結果如圖1所示。
▲圖1 程式碼示例①運行結果
程式碼示例①第8行np.convolve用來計算離散點的移動平均值;第10行在畫布中預定義x軸的數據類型為datetime;第12行繪製離散的點(散點圖);第13行繪製曲線。第15~22行是關於圖例、坐標軸的一些自定義屬性,將在後文進行詳述。
- 程式碼示例②
import numpy as np from bokeh.models import ColumnDataSource, CustomJSTransform from bokeh.plotting import figure from bokeh.io import output_file, show from bokeh.sampledata.stocks import AAPL, GOOG from bokeh.transform import transform # 數據轉換為時間類型 def datetime(x): return np.array(x, dtype=np.datetime64) # 畫布 plot = figure(x_axis_type="datetime", title="Normalized Stock Closing Prices", plot_width=800, plot_height=350) # 其他 plot.background_fill_color = "#f0f0f0" plot.xgrid.grid_line_color = None plot.ygrid.grid_line_color = "black" plot.ygrid.grid_line_alpha = 0.1 plot.xaxis.axis_label = 'Date' plot.yaxis.axis_label = 'Normalized Price' # 數據 aapl_source = ColumnDataSource(data=dict( aapl_date=datetime(AAPL['date']), aapl_close=AAPL['adj_close'], )) goog_source = ColumnDataSource(data=dict( goog_date=datetime(GOOG['date']), goog_close=GOOG['adj_close'], )) # CustomJSTransform v_func = """ const first = xs[0] const norm = new Float64Array(xs.length) for (let i = 0; i < xs.length; i++) { norm[i] = xs[i] / first } return norm """ normalize = CustomJSTransform(v_func=v_func) # 繪圖 plot.line(x='aapl_date', y=transform('aapl_close', normalize), line_width=2, color='#cf3c4d', alpha=0.6,legend="Apple", source=aapl_source) plot.line(x='goog_date', y=transform('goog_close', normalize), line_width=2, color='#2f7bce', alpha=0.6, legend="Google", source=goog_source) plot.legend.location='top_left' # 顯示 show(plot)
運行結果如圖3所示。
▲圖3 程式碼示例②運行結果
程式碼示例②第11行在畫布中預定義x軸的數據類型為datetime;第41、43行繪製兩條時間序列曲線。第31行採用JavaScript函數對y軸數據進行標準化處理,如果對JavaScript函數不熟悉,可以在Pandas中對原始數據進行預處理,然後直接進行調用。
- 程式碼示例③
from bokeh.models import BoxAnnotation from bokeh.sampledata.glucose import data as data_or import numpy as np # 工具條 TOOLS = "pan,wheel_zoom,box_zoom,reset,save" # 數據 data = data_or.sort_index()['2010-03-24':'2010-03-25'] # 畫布 p = figure(x_axis_type="datetime", tools=TOOLS, title="Glocose Readings, Oct 4th (Red = Outside Range)") 10. # 繪圖 p.line(np.array(data.index.tolist(), dtype=np.datetime64), data.glucose.values, line_color='gray') p.circle(data.index, data.glucose, color='grey', size=1) # 箱形標記 p.add_layout(BoxAnnotation(top=80, fill_alpha=0.1, fill_color='red', line_color='red')) p.add_layout(BoxAnnotation(bottom=180, fill_alpha=0.1, fill_color='red', line_color='red')) # 其他 p.background_fill_color = "#efefef" p.xgrid.grid_line_color=None p.xaxis.axis_label = 'Time' p.yaxis.axis_label = 'Value' show(p)
運行結果如圖3所示。
▲圖3 程式碼示例③運行結果
程式碼示例③在時間序列曲線的基礎上增加了箱形標記,深色區域為需要突出顯示的數據,讀者僅需要知道這種標記展示方式,後文會詳述箱形標記方法。
- 程式碼示例④
import numpy as np from bokeh.layouts import gridplot from bokeh.sampledata.stocks import AAPL, GOOG, IBM, MSFT def datetime(x): return np.array(x, dtype=np.datetime64) # 畫布1 p1 = figure(x_axis_type="datetime", title="Stock Closing Prices") p1.grid.grid_line_alpha=0.3 p1.xaxis.axis_label = 'Date' p1.yaxis.axis_label = 'Price' # 繪圖1 p1.line(datetime(AAPL['date']), AAPL['adj_close'], color='#A6CEE3', legend='AAPL') p1.line(datetime(GOOG['date']), GOOG['adj_close'], color='#B2DF8A', legend='GOOG') p1.line(datetime(IBM['date']), IBM['adj_close'], color='#33A02C', legend='IBM') p1.line(datetime(MSFT['date']), MSFT['adj_close'], color='#FB9A99', legend='MSFT') p1.legend.location = "top_left" # 數據2 aapl = np.array(AAPL['adj_close']) aapl_dates = np.array(AAPL['date'], dtype=np.datetime64) window_size = 30 window = np.ones(window_size)/float(window_size) aapl_avg = np.convolve(aapl, window, 'same') # 畫布2 p2 = figure(x_axis_type="datetime", title="AAPL One-Month Average") p2.grid.grid_line_alpha = 0 p2.xaxis.axis_label = 'Date' p2.yaxis.axis_label = 'Price' p2.ygrid.band_fill_color = "olive" p2.ygrid.band_fill_alpha = 0.1 p2.circle(aapl_dates, aapl, size=4, legend='close', color='darkgrey', alpha=0.2) p2.line(aapl_dates, aapl_avg, legend='avg', color='navy') p2.legend.location = "top_left" # 顯示 show(gridplot([[p1,p2]],plot_width=400, plot_height=400))
運行結果如圖4所示。
▲圖4 程式碼示例④運行結果
程式碼示例④採用網格布局顯示兩張時間序列曲線,可以對某一曲線進行橫向比較。
- 程式碼示例⑤
from numpy import pi, exp, linspace, sin import time from bokeh.util.browser import view from bokeh.document import Document from bokeh.embed import file_html from bokeh.models.glyphs import Circle from bokeh.models import Plot, DatetimeAxis, ColumnDataSource, PanTool, WheelZoomTool from bokeh.resources import INLINE # 數據 N = 200 x = linspace(-2 * pi, 2 * pi, N) y = sin(x)*exp(-x) # 創建一組時間數據,以當前時間往後延伸24小時 times = (linspace(0, 24*3600, N) + time.time()) * 1000 source = ColumnDataSource(data=dict(x=x, y=y, times=times)) # 畫布 plot = Plot(min_border=80, plot_width=800, plot_height=350, background_fill_color="#efefef") # 繪圖 circle = Circle(x="times", y="y", fill_color="red", size=3, line_color=None, fill_alpha=0.5) plot.add_glyph(source, circle) # 設置時間軸 plot.add_layout(DatetimeAxis(), 'below') plot.add_layout(DatetimeAxis(), 'left') # 設置工具條 plot.add_tools(PanTool(), WheelZoomTool(zoom_on_axis=False, speed=1/5000.)) # 顯示 show(plot)
運行結果如圖5所示。
▲圖5 程式碼示例⑤運行結果
程式碼示例⑤採用modes介面進行圖形繪製,第25行為該圖形增加平移工具並自定義滾輪縮放的速率。讀者僅需要了解採用這種方式進行繪圖的基本流程即可。
關於作者:屈希峰,資深Python工程師,Bokeh領域的實踐者和佈道者,對Bokeh有深入的研究。擅長Flask、MongoDB、Sklearn等技術,實踐經驗豐富。知乎多個專欄(Python中文社區、Python程式設計師、大數據分析挖掘)作者,專欄累計關注用戶十餘萬人。
本文摘編自《Python數據可視化:基於Bokeh的可視化繪圖》,經出版方授權發布。
延伸閱讀《Python數據可視化》