沿用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数据可视化》