沿用70多年的经典数据可视化方法,如何用Python实现?
导读:什么是时间序列?用来展示什么样的数据关系?怎样用Python实现?本文将为你解答。
01 概述
时间序列(Time series)是指将某种现象某一个统计指标在不同时间上的各个数值,按时间先后顺序排列而形成的序列。时间序列法是一种定量预测方法,也称简单外延法,在统计学中作为一种常用的预测手段被广泛应用。
时间序列分析在第二次世界大战前应用于经济预测。“二战”中和“二战”后,在军事科学、空间科学、气象预报和工业自动化等领域的应用更加广泛。
时间序列分析(Time Series Analysis)是一种动态数据处理的统计方法。该方法基于随机过程理论和数理统计学方法,研究随机数据序列所遵从的统计规律,用于解决实际问题。时间序列构成要素是现象所属的时间和反映现象发展水平的指标数值,如下图所示。
▲时间序列
时间序列中的每个观察值大小,是影响变化的各种不同因素在同一时刻发生作用的综合结果。从这些影响因素发生作用的大小和方向变化的时间特性来看,这些因素造成的时间序列数据的变动分为如下4种类型。
趋势性:某个变量随着时间进展或自变量变化,呈现出一种比较缓慢而长期的持续上升、下降、停留的同性质变动趋向,但变动幅度可能不相等。
周期性:某因素由于外部影响随着自然季节的交替出现高峰与低谷的规律。
随机性:个别为随机变动,整体呈统计规律。
综合性:实际变化情况是几种变动的叠加或组合。预测时设法过滤除去不规则变动,突出反映趋势性和周期性变动。
02 实例
时间序列代码示例如下所示。
代码示例①
1from bokeh.sampledata.stocks import AAPL
2import numpy as np
3# 数据
4aapl = np.array(AAPL['adj_close'])
5aapl_dates = np.array(AAPL['date'], dtype=np.datetime64)
6window_size = 30
7window = np.ones(window_size)/float(window_size)
8aapl_avg = np.convolve(aapl, window, 'same')
9# 画布
10p = figure(width=800, height=350, x_axis_type='datetime')
11# 图层
12p.circle(aapl_dates, aapl, size=4, color='darkgrey', alpha=0.2, legend='close')
13p.line(aapl_dates, aapl_avg, color='red', legend='avg')
14# 自定义属性
15p.title.text = 'AAPL One-Month Average'
16p.legend.location = 'top_left'
17p.grid.grid_line_alpha=0
18p.xaxis.axis_label = 'Date'
19p.yaxis.axis_label = 'Price'
20p.ygrid.band_fill_color='gray'
21p.ygrid.band_fill_alpha = 0.1
22p.legend.click_policy='hide' # 点击图例显示隐藏数据
23# 显示结果
24show(p)
运行结果如图1所示。
▲图1 代码示例①运行结果
代码示例①第8行np.convolve用来计算离散点的移动平均值;第10行在画布中预定义x轴的数据类型为datetime;第12行绘制离散的点(散点图);第13行绘制曲线。第15~22行是关于图例、坐标轴的一些自定义属性,将在后文进行详述。
代码示例②
1import numpy as np 2from bokeh.models import ColumnDataSource, CustomJSTransform 3from bokeh.plotting import figure 4from bokeh.io import output_file, show 5from bokeh.sampledata.stocks import AAPL, GOOG 6from bokeh.transform import transform 7# 数据转换为时间类型 8def datetime(x): 9 return np.array(x, dtype=np.datetime64) 10# 画布 11plot = figure(x_axis_type='datetime', title='Normalized Stock Closing Prices',12 plot_width=800, plot_height=350) 13# 其他 14plot.background_fill_color = '#f0f0f0' 15plot.xgrid.grid_line_color = None 16plot.ygrid.grid_line_color = 'black' 17plot.ygrid.grid_line_alpha = 0.1 18plot.xaxis.axis_label = 'Date' 19plot.yaxis.axis_label = 'Normalized Price' 20# 数据 21aapl_source = ColumnDataSource(data=dict( 22 aapl_date=datetime(AAPL['date']), 23 aapl_close=AAPL['adj_close'], 24)) 2526goog_source = ColumnDataSource(data=dict( 27 goog_date=datetime(GOOG['date']), 28 goog_close=GOOG['adj_close'], 29)) 30# CustomJSTransform 31v_func = ''' 32 const first = xs[0] 33 const norm = new Float64Array(xs.length) 34 for (let i = 0; i < xs.length; i++) { 35 norm[i] = xs[i] / first 36 } 37 return norm 38''' 39normalize = CustomJSTransform(v_func=v_func) 40# 绘图 41plot.line(x='aapl_date', y=transform('aapl_close', normalize), line_width=2, 42 color='#cf3c4d', alpha=0.6,legend='Apple', source=aapl_source) 43plot.line(x='goog_date', y=transform('goog_close', normalize), line_width=2, 44 color='#2f7bce', alpha=0.6, legend='Google', source=goog_source) 45plot.legend.location='top_left' 46# 显示 47show(plot)
运行结果如图3所示。
▲图3 代码示例②运行结果
代码示例②第11行在画布中预定义x轴的数据类型为datetime;第41、43行绘制两条时间序列曲线。第31行采用JavaScript函数对y轴数据进行标准化处理,如果对JavaScript函数不熟悉,可以在Pandas中对原始数据进行预处理,然后直接进行调用。
代码示例③
1from bokeh.models import BoxAnnotation
2from bokeh.sampledata.glucose import data as data_or
3import numpy as np
4# 工具条
5TOOLS = 'pan,wheel_zoom,box_zoom,reset,save'
6# 数据
7data = data_or.sort_index()['2010-03-24':'2010-03-25']
8# 画布
9p = figure(x_axis_type='datetime', tools=TOOLS, title='Glocose Readings, Oct 4th (Red = Outside Range)')
1010.
11# 绘图
12p.line(np.array(data.index.tolist(), dtype=np.datetime64), data.glucose.values, line_color='gray')
13p.circle(data.index, data.glucose, color='grey', size=1)
14# 箱形标记
15p.add_layout(BoxAnnotation(top=80, fill_alpha=0.1, fill_color='red', line_color='red'))
16p.add_layout(BoxAnnotation(bottom=180, fill_alpha=0.1, fill_color='red', line_color='red'))
17# 其他
18p.background_fill_color = '#efefef'
19p.xgrid.grid_line_color=None
20p.xaxis.axis_label = 'Time'
21p.yaxis.axis_label = 'Value'
22show(p)
运行结果如图3所示。
▲图3 代码示例③运行结果
代码示例③在时间序列曲线的基础上增加了箱形标记,深色区域为需要突出显示的数据,读者仅需要知道这种标记展示方式,后文会详述箱形标记方法。
代码示例④
1import numpy as np 2from bokeh.layouts import gridplot 3from bokeh.sampledata.stocks import AAPL, GOOG, IBM, MSFT 4def datetime(x): 5 return np.array(x, dtype=np.datetime64) 6# 画布1 7p1 = figure(x_axis_type='datetime', title='Stock Closing Prices') 8p1.grid.grid_line_alpha=0.3 9p1.xaxis.axis_label = 'Date' 10p1.yaxis.axis_label = 'Price' 11# 绘图1 12p1.line(datetime(AAPL['date']), AAPL['adj_close'], color='#A6CEE3', legend='AAPL')13p1.line(datetime(GOOG['date']), GOOG['adj_close'], color='#B2DF8A', legend='GOOG')14p1.line(datetime(IBM['date']), IBM['adj_close'], color='#33A02C', legend='IBM')15p1.line(datetime(MSFT['date']), MSFT['adj_close'], color='#FB9A99', legend='MSFT')16p1.legend.location = 'top_left' 17# 数据2 18aapl = np.array(AAPL['adj_close']) 19aapl_dates = np.array(AAPL['date'], dtype=np.datetime64) 20window_size = 30 21window = np.ones(window_size)/float(window_size) 22aapl_avg = np.convolve(aapl, window, 'same') 23# 画布2 24p2 = figure(x_axis_type='datetime', title='AAPL One-Month Average') 25p2.grid.grid_line_alpha = 0 26p2.xaxis.axis_label = 'Date' 27p2.yaxis.axis_label = 'Price' 28p2.ygrid.band_fill_color = 'olive' 29p2.ygrid.band_fill_alpha = 0.1 30p2.circle(aapl_dates, aapl, size=4, legend='close', 31 color='darkgrey', alpha=0.2) 32p2.line(aapl_dates, aapl_avg, legend='avg', color='navy') 33p2.legend.location = 'top_left' 34# 显示 35show(gridplot([[p1,p2]],plot_width=400, plot_height=400))
运行结果如图4所示。
▲图4 代码示例④运行结果
代码示例④采用网格布局显示两张时间序列曲线,可以对某一曲线进行横向比较。
代码示例⑤
1from numpy import pi, exp, linspace, sin
2import time
3from bokeh.util.browser import view
4from bokeh.document import Document
5from bokeh.embed import file_html
6from bokeh.models.glyphs import Circle
7from bokeh.models import Plot, DatetimeAxis, ColumnDataSource, PanTool, WheelZoomTool
8from bokeh.resources import INLINE
9# 数据
10N = 200
11x = linspace(-2 * pi, 2 * pi, N)
12y = sin(x)*exp(-x)
13# 创建一组时间数据,以当前时间往后延伸24小时
14times = (linspace(0, 24*3600, N) + time.time()) * 1000
15source = ColumnDataSource(data=dict(x=x, y=y, times=times))
16# 画布
17plot = Plot(min_border=80, plot_width=800, plot_height=350, background_fill_color='#efefef')
18# 绘图
19circle = Circle(x='times', y='y', fill_color='red', size=3, line_color=None, fill_alpha=0.5)
20plot.add_glyph(source, circle)
21# 设置时间轴
22plot.add_layout(DatetimeAxis(), 'below')
23plot.add_layout(DatetimeAxis(), 'left')
24# 设置工具条
25plot.add_tools(PanTool(), WheelZoomTool(zoom_on_axis=False, speed=1/5000.))
26# 显示
27show(plot)
运行结果如图5所示。
▲图5 代码示例⑤运行结果
代码示例⑤采用modes接口进行图形绘制,第25行为该图形增加平移工具并自定义滚轮缩放的速率。读者仅需要了解采用这种方式进行绘图的基本流程即可。
关于作者:屈希峰,资深Python工程师,Bokeh领域的实践者和布道者,对Bokeh有深入的研究。擅长Flask、MongoDB、Sklearn等技术,实践经验丰富。知乎多个专栏(Python中文社区、Python程序员、大数据分析挖掘)作者,专栏累计关注用户十余万人。