4.6 时间序列分析
时间序列是用来研究数据随时间变化趋势而变化的一类算法,它是一种常用的预测性分析方法。它的基本出发点是事物的发展都具有连续性,都是按照它本身固有的规律进行的。在一定条件下,只要规律赖以发生的条件不产生质的变化,则事物在未来的基本发展趋势仍然会延续下去。
从本质上看,时间序列算法是利用统计技术与方法,从预测指标的连续型规律中找出演变模式并建立数学模型,对预测指标的未来发展趋势做出定量估计。
时间序列的常用算法包括移动平均(MA,Moving Average)、指数平滑(ES,Exponen-tial Smoothing)、差分自回归移动平均模型(ARIMA,Auto-regressive Integrated Moving Average Model)三大主要类别,每个类别又可以细分和延伸出多种算法。
时间序列可以解决在只有时间(序列项)而没有其他可控变量下对未来数据的预测问题,常用于经济预测、股市预测、天气预测等偏宏观或没有可控自变量的场景下。
4.6.1 如果有自变量,为什么还要用时间序列
时间序列通常用于在没有自变量可用的条件下做预测性分析,但在某些场景下,即使有自变量仍然需要用到时间序列。下面以笔者经历的一个案例说明该问题。
项目的背景是要做地区用电量预测,但现有的数据可用的只有三份数据,一份是地区每5分钟一个点的量测值数据,一份是该地区每隔1小时的天气数据,另外还有季节性数据例如节假日、工作休息日等。
表面上看,可以将天气和季节性数据作为自变量、将用电量数据作为因变量建立回归模型,但是从本质上考虑,地区的用电情况虽然会受到天气和季节性变化的影响(例如夏天空调),但这种影响对于整个地区的用电影响非常小,因为该地区的用户大户是企业客户、工业用电客户,这些客户的用电量往往更多地受到产销、政策和经济环境这些大因素的影响,而跟季节性本身的关系非常小。因此,这种回归模型很可能无法满足用户预测的需求。
为了验证这一猜想,我们使用了多种回归模型做用电量预测,无一例外地发现效果非常差;经过跟业务部门的沟通也证实了我们的猜想是对的。
经过后续的分析讨论,我们认为用电量趋势中一定会存在随着时间变化而变化的隐形规律,而天气和季节性因素则可以作为外部影响因素对结果加以调整。基于这样的假设,我们使用了ARIMA+SVR(svm中的回归模型器)结合起来做用电量预测,基本思路是:先通过ARIMA得到下一个时间点t的预测用电量值x1、预测值上下限x2、预测样本量x3;然后将这三个特征,再加上天气和季节性因素例如温度x4、湿度x5、风力x6、节气x7等几十个特征作为输入变量,来预测t时刻点的真实用电量。
结果证明,这种方法比单独使用任何一种回归和时间序列得到的预测结果更符合实际情况。ARIMA基于其中隐含的主要规律预测出用电的“主要部分”,而不同的天气和季节性因素则作为外部因素来增加对结果的修正,弥补了无法通过时间规律反映出的“次要部分”和“波动部分”。
该案例说明了即使在有自变量的前期下,也可以使用回归方法来解释其中的“主要规律”,然后通过其他方法对结果加以修正来实现更加精准的预测。在实际运营分析中,可能也存在类似的场景:在要分析的主题中无法确定主要变量因素,或即使确定了主要变量但是无法获得其数据,那么可采用这种方法来实现。
4.6.2 时间序列不适合商业环境复杂的企业
在国内变化多端的商业环境中,很多因素都会影响企业的运营,而这些因素中的大多数都无法通过时间性规律反映出来,因此时间序列很难在这样的场景下真正发挥效果。常见的对时间序列影响较大的因素包括:
融资、并购、收购:当新的资源进入企业后,原来的企业发展模式都会面临重构。
人工造势:很多行业巨擘都有造势的能力,例如天猫双11、京东618等,这些事件都跟时间没有太大关系,但确实也能极大影响个别日期的数据状态。
恶意商业活动:当企业被某些恶意商业活动攻击时,会产生很多无法预知的信息,例如恶意流量、黄牛订单、活动刷单、内部订单等。
广告活动:广告活动对流量和销售的影响几乎是决定性的,有关用户、销售、流量等方面的分析跟时间规律几乎没有关系,只关乎有多少广告费用。
促销活动:促销活动对于销售型公司而言,几乎是运营必备,而且现在基本都演变为“没有促销就没有销售”的状态。
人为因素:每个企业总有一些不可预知的人为因素,能产生一些意想不到的结果,例如删除数据库、商品调价错误、内部信息泄露、升级系统导致的程序崩溃等导致数据异常。
系统问题:在很多企业内部,IT服务系统自身出现了问题,这也会直接影响运营结果,例如高并发下服务器响应慢、服务器宕机、系统内部错误等都将导致数据异常。
竞争对手影响:在一定周期内的市场容量是稳定的,当竞争对手采取一定的活动后,会给自身企业来带负面影响。例如竞争对手在本企业店庆之前做促销活动,必然会吸引一部分用户去消费,尤其是对于两个网站重叠的会员来讲更是如此,被竞争对手提前透支了消费能力之后,本企业再做促销活动就很难达到预期效果。
宏观政策影响:宏观政策调整对企业运营的影响非常大,在很多情况下会成为主要因素。例如,北京的商品房调控政策出来之后,二手房的成交率下降70%。
企业经营策略的转变:很多企业尤其是中小企业都定期制定或修订经营策略,这种经营模式的变革对企业运营的影响非常大。
因此,实际运营分析中,只使用时间序列做预测性的分析场景相对较少。
4.6.3 时间序列预测的整合、横向和纵向模式
当数据集规模较大、数据粒度丰富时,我们可以选择多种时间序列的预测模式。时间序列的数据粒度可分为秒、分、小时、天、周、月、季度、年等,不同的粒度都可以用来做时间序列预测。
假如有3650天(完整10年)以每分钟时间戳为粒度的数据,那么这份数据集会有5256000(60分钟*24小时*365天*10年)条记录。基于该数据集预测今天的总数据,可以考虑的时间序列模式有三种:
整合模式:将历史数据按每天数据进行汇总,得到按天为粒度的共计3650条时间序列数据;然后应用所有数据集做时间序列分析,预测的时间序列项目为1(天)。
横向模式:将历史数据按每小时做汇总,得到按小时为粒度87600条时间序列数据;然后将要预测的1天划分为24小时(即24个预测点),这样会形成24个预测模型,每个模型只预测对应的小时点数据,预测的时间序列项目为1(小时);最后将预测得到的24个小时点的数据求和得到今天的总数据。
纵向模式:这种模式的粒度更细,无需对历史数据做任何形式的汇总,这样有525600条时间序列数据。将要预测的1天划分为1440个分钟点(60分钟*24小时),这样会形成1440个预测模型,每个模型只预测对应的分钟点的数据,预测的时间序列项目为1(分钟);最后将预测得到的1440个分钟点的数据求和得到今天的总数据。
这三种方式的训练集都是3650条,即按天为粒度的数据。横向模式和纵向模式由于做更细的粒度切分,因此需要更多的模型,这意味着需要更多的时间来做训练和预测。当第一种整合模式不能满足需求或者预测结果不佳时,可以尝试后两种模式。
4.6.4 代码实操:Python时间序列分析
Python的科学计算和数据挖掘相关库中,Pandas和statsmodels都提供了时间序列相关分析功能,本示例使用的是statsmodels。有关时间序列算法的选择,实际场景中最常用的是ARIMA或ARMA,因此本示例将使用ARIMA/ARMA来做时间序列分析。
对于这两种时间序列方法而言,应用的难点是如何根据不同的场景判断参数值。本示例将设置判断阈值,通过自动化的程序方式来完成自动的ARIMA/ARMA的参数(p、d、q)选择以及模型训练,降低时间序列算法应用的难度。
示例中模拟的是针对具有时间序列特征的数据集做未来时间序列的预测,数据源文件time_series.txt位于“附件-chapter4”中,默认工作目录为“附件-chapter4”(如果不是,请cd切换到该目录下,否则会报“IOError:File time_series.txt does not exist”)。完整代码如下:
# 导入库 import pandas as pd # Pandas库 import numpy as np # Numpy库 from statsmodels.graphics.tsaplots import plot_acf, plot_pacf # acf和pacf展示库 from statsmodels.tsa.stattools import adfuller # adf检验库 from statsmodels.stats.diagnostic import acorr_ljungbox # 随机性检验库 from statsmodels.tsa.arima_model import ARMA # ARMA库 import matplotlib.pyplot as plt # Matplotlib图形展示库 import prettytable # 导入表格库 # 多次用到的表格 def pre_table(table_name, table_rows): ''' :param table_name: 表格名称,字符串列表 :param table_rows: 表格内容,嵌套列表 :return: 展示表格对象 ''' table = prettytable.PrettyTable() # 创建表格实例 table.field_names = table_name # 定义表格列名 for i in table_rows: # 循环读多条数据 table.add_row(i) # 增加数据 return table # 数据平稳处理 def get_best_log(ts, max_log=5, rule1=True, rule2=True): ''' :param ts: 时间序列数据,Series类型 :param max_log: 最大log处理的次数,int型 :param rule1: rule1规则布尔值,布尔型 :param rule2: rule2规则布尔值,布尔型 :return: 达到平稳处理的最佳次数值和处理后的时间序列 ''' if rule1 and rule2: # 如果两个规则同时满足 return 0, ts # 直接返回0和原始时间序列数据 else: # 只要有一个规则不满足 for i in range(1, max_log): # 循环做log处理 ts = np.log(ts) # log处理 lbvalue, pvalue1 = acorr_ljungbox(ts, lags=1) # 白噪声检验结果 adf, pvalue2, usedlag, nobs, critical_values, icbest = adfuller(ts) # ADF检验 rule_1 = (adf < critical_values['1%'] and adf < critical_values['5%'] and adf < critical_values['10%'] and pvalue1 < 0.01) # 稳定性检验 rule_2 = (pvalue2 < 0.05) # 白噪声检验 rule_3 = (i < 5) if rule_1 and rule_2 and rule_3: # 如果同时满足条件 print ('The best log n is: {0}'.format(i)) # 打印输出最佳次数 return i, ts # 返回最佳次数和处理后的时间序列 # 还原经过平稳处理的数据 def recover_log(ts, log_n): ''' :param ts: 经过log方法平稳处理的时间序列,Series类型 :param log_n: log方法处理的次数,int型 :return: 还原后的时间序列 ''' for i in range(1, log_n + 1): # 循环多次 ts = np.exp(ts) # log方法还原 return ts # 返回时间序列 # 平稳性检验 def adf_val(ts, ts_title, acf_title, pacf_title): ''' :param ts: 时间序列数据,Series类型 :param ts_title: 时间序列图的标题名称,字符串 :param acf_title: acf图的标题名称,字符串 :param pacf_title: pacf图的标题名称,字符串 :return: adf值、adf的p值、三种状态的检验值 ''' plt.figure() plt.plot(ts) # 时间序列图 plt.title(ts_title) # 时间序列标题 plt.show() plot_acf(ts, lags=20, title=acf_title).show() # 自相关检测 plot_pacf(ts, lags=20, title=pacf_title).show() # 偏相关检测 adf, pvalue, usedlag, nobs, critical_values, icbest = adfuller(ts) # 平稳性检验 table_name = ['adf', 'pvalue', 'usedlag', 'nobs', 'critical_values', 'icbest'] # 表格列名列表 table_rows = [[adf, pvalue, usedlag, nobs, critical_values, icbest]] # 表格行数据,嵌套列表 adf_table = pre_table(table_name, table_rows) # 获得平稳性展示表格对象 print ('stochastic score') # 打印标题 print (adf_table) # 打印展示表格 return adf, pvalue, critical_values, # 返回adf值、adf的p值、三种状态的检验值 # 白噪声(随机性)检验 def acorr_val(ts): ''' :param ts: 时间序列数据,Series类型 :return: 白噪声检验的P值和展示数据表格对象 ''' lbvalue, pvalue = acorr_ljungbox(ts, lags=1) # 白噪声检验结果 table_name = ['lbvalue', 'pvalue'] # 表格列名列表 table_rows = [[lbvalue, pvalue]] # 表格行数据,嵌套列表 acorr_ljungbox_table = pre_table(table_name, table_rows) # 获得白噪声检验展示表格对象 print ('stationarity score') # 打印标题 print (acorr_ljungbox_table) # 打印展示表格 return pvalue # 返回白噪声检验的P值和展示数据表格对象 # arma最优模型训练 def arma_fit(ts): ''' :param ts: 时间序列数据,Series类型 :return: 最优状态下的p值、q值、arma模型对象、pdq数据框和展示参数表格对象 ''' max_count = int(len(ts) / 10) # 最大循环次数最大定义为记录数的10% bic = float('inf') # 初始值为正无穷 tmp_score = [] # 临时p、q、aic、bic和hqic的值的列表 for tmp_p in range(max_count + 1): # p循环max_count+1次 for tmp_q in range(max_count + 1): # q循环max_count+1次 model = ARMA(ts, order=(tmp_p, tmp_q)) # 创建ARMA模型对象 try: results_ARMA = model.fit(disp=-1, method='css') # ARMA模型训练 except: continue # 遇到报错继续 finally: tmp_aic = results_ARMA.aic # 模型的获得aic tmp_bic = results_ARMA.bic # 模型的获得bic tmp_hqic = results_ARMA.hqic # 模型的获得hqic tmp_score.append([tmp_p, tmp_q, tmp_aic, tmp_bic, tmp_hqic]) # 追加每个模型的训练参数和结果 if tmp_bic < bic: # 如果模型bic小于最小值,那么获得最优模型ARMA的下列参数: p = tmp_p # 最优模型ARMA的p值 q = tmp_q # 最优模型ARMA的q值 model_arma = results_ARMA # 最优模型ARMA的模型对象 aic = tmp_bic # 最优模型ARMA的aic bic = tmp_bic # 最优模型ARMA的bic hqic = tmp_bic # 最优模型ARMA的hqic pdq_metrix = np.array(tmp_score) # 将嵌套列表转换为矩阵 pdq_pd = pd.DataFrame(pdq_metrix, columns=['p', 'q', 'aic', 'bic', 'hqic']) # 基于矩阵创建数据框 table_name = ['p', 'q', 'aic', 'bic', 'hqic'] # 表格列名列表 table_rows = [[p, q, aic, bic, hqic]] # 表格行数据,嵌套列表 parameter_table = pre_table(table_name, table_rows) # 获得最佳ARMA模型结果展示表格对象 print ('each p/q traning record') # 打印标题 print (pdq_pd) # 打印输出每次ARMA拟合结果,包含p、d、q以及对应的AIC、BIC、HQIC print ('best p and q') # 打印标题 print (parameter_table) # 输出最佳ARMA模型结果展示表格对象 return model_arma # 最优状态下的arma模型对象 # 模型训练和效果评估 def train_test(model_arma, ts, log_n, rule1=True, rule2=True): ''' :param model_arma: 最优ARMA模型对象 :param ts: 时间序列数据,Series类型 :param log_n: 平稳性处理的log的次数,int型 :param rule1: rule1规则布尔值,布尔型 :param rule2: rule2规则布尔值,布尔型 :return: 还原后的时间序列 ''' train_predict = model_arma.predict() # 得到训练集的预测时间序列 if not (rule1 and rule2): # 如果两个条件有任意一个不满足 train_predict = recover_log(train_predict, log_n) # 恢复平稳性处理前的真实时间序列值 ts = recover_log(ts, log_n) # 时间序列还原处理 ts_data_new = ts[train_predict.index] # 将原始时间序列数据的长度与预测的周期对齐 RMSE = np.sqrt(np.sum((train_predict - ts_data_new) ** 2) / ts_data_new.size) # 求RMSE # 对比训练集的预测和真实数据 plt.figure() # 创建画布 train_predict.plot(label='predicted data', style='--') # 以虚线展示预测数据 ts_data_new.plot(label='raw data') # 以实线展示原始数据 plt.legend(loc='best') # 设置图例位置 plt.title('raw data and predicted data with RMSE of %.2f' % RMSE) # 设置标题 plt.show() # 展示图像 return ts # 返回还原后的时间序列 # 预测未来指定时间项的数据 def predict_data(model_arma, ts, log_n, start, end, rule1=True, rule2=True): ''' :param model_arma: 最优ARMA模型对象 :param ts: 时间序列数据,Series类型 :param log_n: 平稳性处理的log的次数,int型 :param start: 要预测数据的开始时间索引 :param end: 要预测数据的结束时间索引 :param rule1: rule1规则布尔值,布尔型 :param rule2: rule2规则布尔值,布尔型 :return: 无 ''' predict_ts = model_arma.predict(start=start, end=end) # 预测未来指定时间项的数据 print ('-----------predict data----------') # 打印标题 if not (rule1 and rule2): # 如果两个条件有任意一个不满足 predict_ts = recover_log(predict_ts, log_n) # 还原数据 print (predict_ts) # 展示预测数据 # 展示预测趋势 plt.figure() # 创建画布 ts.plot(label='raw time series') # 设置推向标签 predict_ts.plot(label='predicted data', style='--') # 以虚线展示预测数据 plt.legend(loc='best') # 设置图例位置 plt.title('predicted time series') # 设置标题 plt.show() # 展示图像 # 读取数据 date_parse = lambda dates: pd.datetime.strptime(dates, '%m-%d-%Y') # 创建解析列的功能对象 df = pd.read_table('time_series.txt', delimiter='\t', index_col='date', date_parser=date_parse) # 读取数据 ts_data = df['number'].astype('float32') # 将列转换为float32类型 print ('data summary') # 打印标题 print (ts_data.describe()) # 打印输出时间序列数据概况 # 原始数据检验 adf, pvalue1, critical_values = adf_val(ts_data, 'raw time series', 'raw acf', 'raw pacf') # 稳定性检验 pvalue2 = acorr_val(ts_data) # 白噪声检验 # 创建用于区分是否进行平稳性处理的规则 rule1 = (adf < critical_values['1%'] and adf < critical_values['5%'] and adf < critical_values[ '10%'] and pvalue1 < 0.01) # 稳定性检验 rule2 = (pvalue2[0,] < 0.05) # 白噪声检验 # 对时间序列做稳定性处理 log_n, ts_data = get_best_log(ts_data, max_log=5, rule1=rule1, rule2=rule2) # 再次做检验 adf, pvalue1, critical_values = adf_val(ts_data, 'final time series', 'final acf', 'final pacf') # 稳定性检验 pvalue2 = acorr_val(ts_data) # 白噪声检验 # 训练最佳ARMA模型并输出相关参数和对象 model_arma = arma_fit(ts_data) # 模型训练和效果评估 ts_data = train_test(model_arma, ts_data, log_n, rule1=rule1, rule2=rule2) # 模型预测应用 start = '1991-07-28' # 设置预测开始的时间索引 end = '1991-08-02' # 设置预测结束的时间索引 predict_data(model_arma, ts_data, log_n, start, end, rule1=rule1, rule2=rule2) # 预测时间序列数据
上述代码以空行分为10个部分。
1)第一部分导入库。statsmodels库里面的plot_acf、plot_pacf用于acf和pacf图形检验分析,adfuller、acorr_ljungbox用于adf检验和随机性检验,ARMA库做时间序列分析,Matplotlib和prettytable做图形和表格格式化输出。
ARIMA相对于ARMA多了一步差分的过程,但是由于statsmodels中的ARIMA对差分的支持不太好,最多只能多2阶差分,其实用性会大打折扣(其实2阶差分已经可以满足90%以上的场景了)。所以,在实际应用中,我们会手动做数据平稳性处理,然后将平稳数据传给ARMA模型做训练,完成类似ARIMA的时间序列分析。
2)第二部分定义一个名为pre_table的函数用来展示格式化表格数据。该函数的输入有2个参数:
table_name:表格名称,字符串列表;
table_rows:表格内容,嵌套列表,考虑到在添加内容时,可能会有批量添加的模式,因此批量添加的数据记录行需要转换为嵌套列表的形式,例如[[1,2,3,4],[2,2,3,4]]为要添加的两条数据记录;即使只有一条数据记录,也要写成嵌套列表的形式,例如[[1,2,3,4]]。
返回一个包含内容的可打印对象。
函数功能中,先通过prettytable.PrettyTable创建表格对象实例,然后通过实例的field_names方法增加表格名称,通过一个for循环将table_rows中的多个嵌套表格内容读出,并使用表格实例的add_row方法增加行数据。
在实际应用中,只要是用到2次或2次以上的功能,或者完成一项功能需要的代码为比较大的模块,笔者都会写成函数来调用,这样一方面能将降低代码冗余,另一方面也能增加代码的可维护性、可理解性和可编辑性。
相关知识点:Python中函数的Doc String
从本示例开始,笔者会尽量在每个函数中使用Doc String做函数注释。Doc String是文档字符串,内容一般是对象的描述信息。Doc Strings是一个重要的工具,它能帮助程序读者快速了解程序的功能、用法和注意事项,使得程序更加简单易懂;尤其当程序需要多部门流通使用,或者在大型项目交付时,通常都会通过Doc Strings来增强代码的可理解性。
Doc String使用三个引号(单引号和双引号都可以)作为标识,通常包括以下几部分内容:
函数功能简述:概述该函数或模块的主要功能。
参数信息:详细介绍每个参数含义、类型和用法。
返回:该模块会返回的内容、类型等信息。
相关信息:跟该函数模块相关的其他函数或信息。
报错:该函数会在哪些场景下报哪些错误。
注意事项:其他有关该函数模块的使用注意和信息等。
应用示例:通过简单的几个例子说明该函数或模块的用法。
例如,下面是numpy.dot的文档字符串信息:
dot(...) dot(a, b, out=None) Dot product of two arrays. For 2-D arrays it is equivalent to matrix multiplication, and for 1-D arrays to inner product of vectors (without complex conjugation). For N dimensions it is a sum product over the last axis of `a` and the second-to-last of `b`:: dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m]) Parameters ---------- a : array_like First argument. b : array_like Second argument. out : ndarray, optional Output argument. This must have the exact kind that would be returned if it was not used. In particular, it must have the right type, must be C-contiguous, and its dtype must be the dtype that would be returned for `dot(a,b)`. This is a performance feature. Therefore, if these conditions are not met, an exception is raised, instead of attempting to be flexible. Returns ------- output : ndarray Returns the dot product of `a` and `b`. If `a` and `b` are both scalars or both 1-D arrays then a scalar is returned; otherwise an array is returned. If `out` is given, then it is returned. Raises ------ ValueError If the last dimension of `a` is not the same size as the second-to-last dimension of `b`. See Also -------- vdot : Complex-conjugating dot product. tensordot : Sum products over arbitrary axes. einsum : Einstein summation convention. matmul : '@' operator as method with out parameter. Examples -------- >>> np.dot(3, 4) 12 Neither argument is complex-conjugated: >>> np.dot([2j, 3j], [2j, 3j]) (-13+0j) For 2-D arrays it is the matrix product: >>> a = [[1, 0], [0, 1]] >>> b = [[4, 1], [2, 2]] >>> np.dot(a, b) array([[4, 1], [2, 2]]) >>> a = np.arange(3*4*5*6).reshape((3,4,5,6)) >>> b = np.arange(3*4*5*6)[::-1].reshape((5,4,6,3)) >>> np.dot(a, b)[2,3,2,1,2,2] 499128 >>> sum(a[2,3,2,:] * b[1,2,:,2]) 499128
不同模块间一般以空行分隔,用于区分不同内容。在本书中,由于代码会在书中进行讲解,因此不会完整包含上述内容,但是函数功能简述、参数信息和返回这三部分关键信息都在有相应提示。
上述注释信息经常在那个地方可以查询或使用到:
help()方法获取帮助信息。在使用Python的过程中,当遇到函数功能不清楚的情况时,通常会使用help(funciton_name)来查找其帮助信息,其中funciton_name就是函数的名称,而通过help()方法调用的正是Doc String中的内容。
使用funciton_name.__doc__方法输出函数的帮助信息。除了上述使用help方法查询模块功能外,也可以使用函数的.__doc__方法获取帮助信息,该帮助信息也是获取的Doc String内容,例如function_name.__doc__。
生成Python相关帮助文档。在做项目交付时都需要完整的文档交付,其中涉及函数功能的部分,可以使用相关工具,例如pydoc、epydoc、doxygen、sphinx来直接生成对应的帮助文档,这些自动化工具可以提取这些信息来生成文档。
最简单的应用示例
在本示例代码中,当执行了pre_table的函数模块后,可在python交互窗口直接输入:help(pre_table),会返回如下帮助信息:
Help on function pre_table in module __main__: pre_table(table_name, table_rows) :param table_name: 表格名称,字符串列表 :param table_rows: 表格内容,嵌套列表 :return: 展示表格对象
这是我们在注释中写的帮助内容。无论是出于可维护、可交付还是可理解的目的,都建议读者在正式的函数编程时养成写标准注释的好习惯。
3)第三部分定义一个名为get_best_log的函数用于数据平稳处理。该函数的输入参数有5个:
ts:时间序列数据,Series类型。
max_log:最大log处理的次数,默认值为5,int型。
rule1:rule1规则布尔值,默认值为True,布尔型。
rule2:rule2规则布尔值,默认值为True,布尔型。
返回:达到平稳处理的最佳log处理次数和处理后的时间序列。
该函数功能中,先通过rule1和rule2条件判断输入的两个规则是否同时满足,如果同时满足则说明该时间序列是平稳的,无需做任何平稳性处理;否则使用对数方法(numpy.log)做平稳性处理:
通过for循环按照指定的最大处理次数做循环处理,一般情况下做2次log处理就能满足90%的时间序列平稳性。
每次处理后使用acorr_ljungbox做白噪声检验、使用adfuller方法做ADF检验,如果二者检验的结果能够符合两个规则的阈值设定并且i的值在5以内,那么返回平稳处理的次数以及处理后的时间序列;否则终止程序。平稳处理的次数用于后期数据的还原,处理后的时间序列用于ARMA模型中的时间序列的输入数据集。
相关知识点:时间序列数据的平稳性
平稳性是做时间序列分析的前提条件,所谓平稳通俗理解就是数据没有随着时间呈现明显的趋势和规律,例如剧烈波动、递增、递减等,而是相对均匀且随机地分布在均值附近。在ARIMA模型中的I就是对数据做差分以实现数据的平稳,而ARIMA关键参数p、d、q中的d即时间序列成为平稳时所做的差分次数。
如何判断时间序列数据是否需要平稳性处理?一般有三种方法:
观察法:通过输出时间序列图发现数据是否平稳。本示例中的adf_val函数便含有该方法。
自相关和偏相关法:通过观察自相关和偏相关的系数分析数据是否平稳。
ADF检验:通过ADF检验得到的显著性水平分析数据是否平稳。本示例中的adf_val函数便有含有该方法。
实现数据的平稳有以下几种方法:
对数法:对数处理可以减小数据的波动,使其线性规律更加明显。但需要注意的是对数处理只能针对时间序列的值大于0的数据集。
差分法:一般来说,非纯随机的时间序列经一阶差分或者二阶差分之后就会变得平稳(statsmodels的ARIMA模型自动支持数据差分,但最大为2阶差分)。
平滑法:根据平滑技术的不同,可分为移动平均法和指数平均法,这两个都是最基本的时间序列方法。
分解法:将时序数据分离成不同的成分,包括成长期趋势、季节趋势和随机成分等。
4)第四部分定义了一个名为recover_log的函数,目的是还原经过平稳处理的数据。在经过平稳性处理之后,原有的时间序列训练集已经被更改,通过ARMA得到的预测数据也是经过平稳处理后的数据,该数据要使用必须经过还原。该函数的参数:
ts:经过log方法平稳处理的时间序列,Series类型。
log_n:log方法处理的次数,int型。
返回:还原后的时间序列。
该函数的功能中直接通过for循环通过平稳性处理得到的最佳处理次数,使用Numpy的exp方法做逆对数处理得到原始时间序列值。
5)第五部分定义了一个名为adf_val的函数,用于平稳性检验。该平稳性检验实现了时间序列图、acf图、pacf图、ADF检验数据等多种检验方法的数据和图形输出。函数的参数:
ts:时间序列数据,Series类型。
ts_title:自定义的时间序列图的标题名称,字符串。
acf_title:自定义的acf图的标题名称,字符串。
pacf_title:自定义的pacf图的标题名称,字符串。
返回:adf值、adf的p值、三种状态(1%、5%、10%)的检验值,这些值是用来构成rule1的判断规则的参数值。
该函数的功能中,先调用Matplotlib的plot方法直接输出时间序列图,然后使用statsmodels.graphics.tsaplots的plot_acf和plot_pacf方法分别输出自相关和偏相关检测图,使用adfuller方法做平稳性检验;接着通过定义要输出的表格的列名列表和行记录列表,调用pre_table方法形成格式化表格并打印输出。
6)第六部分定义了一个名为acorr_val的函数,用于白噪声(随机性)检验。函数参数:
ts:时间序列数据,Series类型。
返回:白噪声检验的P值,用来做rule2判断规则的参数值。
函数参数:
函数功能中,使用acorr_ljungbox返回白噪声检验结果,然后调用pre_table将数据格式化为表格形式并打印出来。
相关知识点:白噪声检验
白噪声(white noise)检验也称为随机性检验,用于检验时间序列的各项数值之间是否具有任何相关关系,白噪声分布是应用时间序列分析的前提。检测时间序列是否属于随机性分布,可通过图形法和Ljung Box法检验。
图形法:时间序列应该是围绕均值随机性上下分布的状态。
Ljung Box法:是对时间序列是否存在滞后相关的一种统计检验,判断序列总体的相关性或者说随机性是否存在。
白噪声检验通常和数据平稳性检验是协同进行数据检验的,这意味着如果平稳性检验通过,白噪声检验一般也会通过。
7)第七部分定义了一个名为arma_fit的函数,用于做arma最优模型训练。该模型实现的是基于给定的时间序列数据,自动寻找BIC最小时的p和q的阶数,并形成训练好的ARMA模型。函数参数:
ts:时间序列数据,Series类型。
返回:最优状态下arma模型对象,用于后续模型训练效果评估和预测应用。需要注意的是,输入的ts时间序列数据是已经经过平稳性处理,并满足平稳性和白噪音检验的时间序列数据。
函数实现功能中,先定义了几个值。默认p和q阶数的最大值为记录数的10%;判断最佳ARMA模型的方法是BIC方法,即选择最小的BIC值时的q和p的值,因此通过float('inf')来定义一个BIC值为正无穷,后续模型得到BIC值更小时选择对应的参数信息;tmp_score为临时p、q、aic、bic和hqic的值的列表,该列表将嵌套每次训练形成的评估参数结果集。
接下来的循环主体,将通过p和q的嵌套循环实现。
最内层循环中,先通过ARMA方法基于输入的时间序列和循环得到的p、q值创建模型对象。
然后应用模型对象的fit方法做模型训练,fit中的参数disp用来控制不打印收敛信息,mothed控制最大似然的计算方法是条件平方和似然度最大化。
在应用fit过程中,由于多次循环可能会报错导致循环中止,因此使用了try、except和finally进行控制,这三个语句的含义为执行try中的程序,如果遇到错误则执行except中的程序,最终无论是否报错都要执行finally中的程序。在finally语句中,通过fit后返回的结果类的aic、bic和hqic三个参数值,然后将p、q、aic、bic、hqic以列表的形式追加到临时列表中;如果判断bic的值比最小值要小,那么认为该值下的模型为更优模型,将其p、q、aic、bic和hqic的值存储下来。
最后将临时列表tmp_score转换为矩阵,并基于该矩阵创建用于展示每次循环数据的详细数据;然后基于最优模型的数据创建格式化展示列表并分别输出详细数据和最优模型对象。
statsmodels中的ARMA通过应用fit方法后,返回的对象是一个ARMAResults类,而不再是原有的model(ARMA模型对象)本身,这一点跟sklearn不同,因此无法直接通过model本身获取模型的参数和评估指标等信息,而要通过ARMAResults来获取。
相关知识点:statsmodels中的ARMA
statsmodels常用的时间序列模型包括AR、ARMA和ARIMA,并都集中在statsmodels.tsa.arima_model下面。ARMA(p,q)是AR(p)和MA(q)模型的组合,关于p和q的选择,一种方法是观察自相关图ACF和偏相关图PACF,通过截尾、拖尾等信息分析应用的模型以及适应的p和q的阶数;另一种方法是通过循环的方法,遍历所有条件并通过AIC、BIC值自动确定阶数。
ARMA(以及statsmodels中的其他算法)跟sklearn中的算法类似,也都有方法和属性两种应用方式。常用的ARMA方法:
fit:使用卡尔曼滤波器的最大似然法拟合ARMA模型。
from_formula:基于给定的公式和数据框创建模型。
geterrors:基于给定的参数获取ARMA进程的错误。
hessian:基于给定的参数计算Hessian信息。
loglike:计算ARMA模型的对数似然。
loglike_css:条件求和方差似然函数。
predict:应用ARMA模型做预测。
score:得分函数。
8)第八部分定义了一个名为train_test的函数,用于时间序列的模型训练和效果评估。该函数可将训练的时间序列内预测值与实际值做比较,并输出RMSE(均方根误差)和时间序列对比图。函数参数:
model_arma:最优ARMA模型对象。
ts:时间序列数据,Series类型。
log_n:平稳性处理的log的次数,int型。
rule1:rule1规则布尔值,布尔型。
rule2:rule2规则布尔值,布尔型。
返回:还原后的时间序列,该时间序列用于在时间序列外做预测后的图形展示。
函数功能实现中,通过最优模型的predict方法做时间序列内的数据预测,如果数据经过平稳性处理,则将数据做还原处理;由于预测数据比原始时间序列数据的索引少,因此将二者匹配为相同索引下的数据量用于数据比较;通过自定义的公式计算RMSE;最后通过Matplotlib分别画出预测数据(虚线表示)、实际数据(实线表示)。
9)第九部分定义了一个名为predict_data的函数,用来预测未来指定时间项的数据。
函数参数:
model_arma:最优ARMA模型对象。
ts:时间序列数据,Series类型。
log_n:平稳性处理的log的次数,int型。
start:要预测数据的开始时间索引。
end:要预测数据的结束时间索引。
rule1:rule1规则布尔值,布尔型。
rule2:rule2规则布尔值,布尔型。
返回:无。
该函数功能的实现中,通过最优模型的predict方法,指定时间序列外的开始和终止时间索引来定义要预测的时间区间;如果预测的结果经过平稳性处理,那么将结果做还原处理;最后通过Matplotlib分别将原始时间序列(实现)和预测的时间序列(虚线)输出到一个图像中。
10)第十部分为整个程序的应用部分。分为以下几个步骤:
步骤1 读取数据
先通过lambda函数定义了一个用于将日期解析出来的功能项,功能项的核心是使用lambda表达式配合pandas.datetime中的strptime方法将字符串解析为日期格式,该功能项会在Pandas读取数据文件时使用,日期的格式按照源文件的格式来标记,这样能准确解析源文件日期。
相关知识点:lambda表达式
lambda表达式是一个匿名函数,它其实是一个简易版的函数,通常用于函数功能和逻辑比较简单且没有重复性使用的场景。lambda函数可以接收任意多个参数并且默认返回单个表达式的值,其函数的语法只包含一个语句:
lambda [arg1 [,arg2,.....argn]]:expression
其中arg1、arg2等是参数,expression是表达式主体部分。以下是几种常用的表达式示例。
示例一 单个参数求平方,普通的函数式可能会这样定义:
def square_x(x): x = x ** 2 return x
使用lambda表达式只需要写成:square_x=lambda x:x**2。输入square_x(2)返回结果为4。
示例二 多个参数复合运算,普通的函数可能这样定义:
def mul_var(x,y): result = x+y+x**2+y-3 return result
使用lambda表达式只需要写成:mul_var=lambda x,y:x+y+x**2+y-3。输入mul_var(2,3)返回结果为9。
示例三 条件判断计算,普通的函数可能这样定义:
def filter_function(x,y): if x >1 and y < 10: return x+y else: return x-y
使用lambda表达式只需要写成:filter_function=lambda x,y:x+y if x>1 and y<10 else x-y。输入filter_function(1,7)返回结果为-6。这里需要注意if..else的语法和def定义的函数不同。
有关lambda使用的几个注意点:
不要使用lambda定义复杂的功能(例如嵌套),虽然它也能实现。
lambda表达式虽然精简,但使用如果包含过多条件,会降低代码的可读性和可理解性。
lambda定义的函数是一次性的,不能重复使用。
不是所有的def功能都可以使用lambda重写,例如,在Python2.7.*中无法在表达式中使用print方法。
lambda默认已经包含return功能,如果再增加return方法会报错。
相关知识点:使用strftime将字符串解析为日期格式
Pandas中的datetime库是处理与日期相关主要功能库,其中的strftime用来将字符串解析为日期格式,与strptime方法对应的是strftime方法,用来将日期转换为字符串。关于日期的格式,Pandas采用的是Python默认的日期格式,完整见https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior。常用的日期格式字符串如表4-6所示。
表4-6 日期字符串
在使用Pandas的read_table读取数据文件时,通过index_col指定date列为索引列,代替默认的数值索引;通过date_parser参数指定刚才定义的日期解析功能项,由于date_parser默认的日期解析格式为YYYY-MM-DD HH:MM:SS,跟源数据格式不符,因此需要人工自定义格式。
最后应用astype方法将时间序列项的格式类型转换为float32,并通过describe方法输出时间序列概况,结果如下:
data summary count 149.000000 mean 164.382553 std 75.097740 min 47.000000 25% 100.000000 50% 156.000000 75% 201.000000 max 400.000000 Name: number, dtype: float64
describe方法输出的数据包括数据记录数、均值、标准差、最小值、最大值、25%、50%和75%分位数。
步骤2 原始数据检验
这里直接调用已经定义好的稳定性检验和白噪声检验函数,并传入原始时间序列数据,返回结果如图4-11所示。
图4-11 原始时间序列图
由原始时间序列图发现,4月份之前的数据波动较为明显,但4月份之后的数据基本处于平稳波动的状态。
在ACF图中通过配置参数lags=20(20个滞后点)来更清晰地展示数据的分布,前20个滞后点基本已经可以看出明显趋势。
图4-12 原始ACF检验图
图4-13 原始PACF检验图
ADF稳定性检验结果:stochastic score。
+----------------+-----------------+---------+--------+ ----------------+-- | adf | pvalue | usedlag | nobs | critical_values | icbest | +----------------+-----------------+---------+--------+ ---------------+-- | -3.76427781964 | 0.0032946903803 | 11 | 137 | {'5%': -2.8828782366015093, '1%': -3.4790073553689438, '10%': -2.5781488587564603} | 1405.38466046 | +----------------+-----------------+---------+------+ ---------------+--
白噪声检验结果:stationarity score
+----------------+--------------------+ | lbvalue | pvalue | +----------------+--------------------+ | [ 70.22298485] | [ 5.29656862e-17] | +----------------+--------------------+
如何通过上述指标分析数据稳定且随机分布?如果是时间序列具有稳定性,那么ADF的值(adf)需要小于critical_values中的1%、5%和10%的三个值,且P值(pvalue)一般小于0.01。在白噪声检验结果中,P值需要小于0.05即说明数据是随机分布的。从数据结果看,示例数据恰好能满足条件的定义。
输出的ACF和PACF图更多的用于辅助判断p和q的阶数,通过分析截尾和拖尾的状态来人工判断p和q的值。这里不使用这种方法。
步骤3 创建用于区分是否进行平稳性处理的规则。该规则使用调用步骤2中返回的结果,在条件的定义中,比较关键的是ADF的P值和白噪声检验的P值的阈值定义,这两个值的定义标准在步骤2的返回结果中已经给出。
步骤4 对时间序列做稳定性处理。在该步骤中,如果数据符合平稳性,那么不需要做平稳性处理;否则需要处理直到同时满足2个条件的阈值要求。由于示例数据满足稳定性和白噪声检验,因此直接返回0和原始数据集。
步骤5 再次做检验,包括平稳性和白噪声检验。由于没有做任何处理,返回的结果仍然与步骤2相同。
步骤6 训练最佳ARMA模型并输出相关参数和对象。在该步骤中,命令中交互窗口会有警告信息,忽略该信息即可。该过程将需要一点时间来完成。部分输出如下结果:
each p/q traning record p q aic bic hqic 0 0.0 0.0 1712.839862 1718.847755 1715.280770 1 0.0 1.0 1653.446702 1662.458541 1657.108063 2 0.0 2.0 1648.868523 1660.884308 1653.750338 3 0.0 3.0 1618.676555 1633.696287 1624.778824 4 0.0 4.0 1619.720071 1637.743748 1627.042793 5 0.0 5.0 1619.720071 1637.743748 1627.042793 6 0.0 6.0 1601.914727 1625.946297 1611.678357 ... 223 14.0 13.0 1430.080724 1514.333692 1464.318768 224 14.0 14.0 1430.080724 1514.333692 1464.318768 [225 rows x 5 columns]
该数据是每次训练时取不同p和q计算对应的AIC、BIC和HQIC的值,可通过该数据分析不同训练的结果。
best p and q +----+---+---------------+---------------+---------------+ | p | q | aic | bic | hqic | +----+---+---------------+---------------+---------------+ | 14 | 1 | 1467.49034744 | 1467.49034744 | 1467.49034744 | +----+---+---------------+---------------+---------------+
ARMA最优模型下的p和q的值,满足BIC最小的原则。
步骤7 模型训练和效果评估。通过最优的ARMA模型对训练集做时间序列内的预测,并与原始时间序列数据做对比,输入预测值与实际值的时间序列对比趋势图以及RMSE输出预测结果。
图4-14 ARMA预测结果与实际结果对比
从对比图中发现,预测值与实际值的数据对比差异不大,并且趋势分布能比较好地还原实际数据分布规律。
步骤8 模型预测应用。这里设置了预测起止时间分别是1991-07-28、1991-08-02,调用predict_data做预测分析。得到结果如下:
-----------predict data---------- 1991-07-28 183.093636 1991-07-29 125.896794 1991-07-30 135.165396 1991-07-31 143.513278 1991-08-01 111.555943 1991-08-02 113.530398 Freq: D, dtype: float64
图4-15 ARMA预测结果与实际结果合成
上面输出的数据表格和趋势图,显示了未来预测的结果和趋势,整个图像趋势没有明显的异常,较为符合实际情况。
上述过程中,主要需要考虑的关键点是:
如何做数据平稳性处理。
如何通过ACF和PACF图做p和q阶数评估(当然采用AIC、BIC是一种自动化的方法)。
在做数据平稳性处理之后,注意还原数据,否则预测数据将难以应用。
ARMA模型在做最优模型训练后,可直接返回模型对象给其他程序调用,而无需重复训练。
代码实操小结:在本节的代码中,主要使用了statsmodels的相关库和方法做时间序列的分析处理和预测应用,知识点如下:
通过def函数式的方法定义不同的功能模块实现不同的功能,函数中包含参数、功能和返回信息。
为函数写Doc String。
使用prettytable库中的add_row、field_names等方法创建格式化表格并输出。
通过for循环结合多条件语句做循环处理。
使用Numpy的log、exp、sum等方法做基本数据处理。
使用statsmodels库里面的plot_acf、plot_pacf做acf和pacf图形检验分析,adfuller、acorr_ljungbox做adf检验和随机性检验。
使用Matplolib做折线图输出,并设置不同的线条样式。
使用ACI、BIC等方法做最优ARMA模型p和q阶数的选择。
使用自定义方法计算RMSE。
使用ARMA的fit方法做模型训练,predict方法做时间序列预测。
使用lambda表达式实现基本功能处理。
使用pandas.datetime中的strptime将字符串转换为日期格式。
通过Pandas的read_csv方法读取数据文件并指定分隔符、索引列和日期解析功能对象。
使用Pandas中的describe方法输出数据框的基本概述信息。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论