由于数据传输过程、采样及记录过程中发生数据失真或丢失,研究现象本身由于受各种偶然非正常的因素影响而形成缺失值和离群点等情况出现在序列中,对于这样的时间序列,在建立时间序列模型前,需要对序列进行预处理。
除此之外,数据处理也是机器学习的重要环节,算法人员花50%以上的时间来对数据进行处理是再正常不过的场景。于不同的业务背景,不同类型的数据,不同的算法,数据如果不处理好,就无法正确的应用计算算法来得到想要的结果。数据预处理的重要性不言而喻。
在计量实证中,面板数据和时间序列数据是最常运用的两种数据类型,本文讨论时间序列数据预处理的处理方式,由于时间序列中蕴含着“时序”特征,与一般系列数据的常规预处理方式不同,需要加以区别。
1. 通用场景下时间序列数据预处理
1.1 缺失值
时间序列缺失值的处理是预处理中的一个重要环节,处理不当,就会累积大量的错误,造成较大误差.关于缺失值的处理,常见的方法有两种,一种是直接丢弃含缺失数据的记录;另一种是用新值替代缺失数据实际中,后者的处理方式更常用,因为前者对数据分析而言是很大的浪费。
1.1.1删除所在行
当缺失值的个数只占整体很小一部分的时候,且时间序列较长,可直接删除缺失值。但是如果缺失值占比上升,则不建议采用此种方式。
import numpy as np
import pandas as pd
data = pd.read_csv('data.csv',encoding='GBK')
# 将空值形式的缺失值转换成可识别的类型
data = data.replace(' ', np.NaN)
print(data.columns)#['id', 'label', 'a', 'b', 'c', 'd']
#将每列中缺失值的个数统计出来
null_all = data.isnull().sum()
#id 0
#label 0
#a 7
#b 3
#c 3
#d 8
#查看a列有缺失值的数据
a_null = data[pd.isnull(data['a'])]
#a列缺失占比
a_ratio = len(data[pd.isnull(data['a'])])/len(data) #0.0007
#丢弃缺失值,将存在缺失值的行丢失
new_drop = data.dropna(axis=0)
print(new_drop.shape)#(9981,6)
#丢弃某几列有缺失值的行
new_drop2 = data.dropna(axis=0, subset=['a','b'])
print(new_drop2.shape)#(9990,6)
1.1.2统计方法填充:
(1)统计量填充:时间序列缺失值常用序列均值、中位数等统计量来进行填充,但具体使用何种统计量需要根据时间序列分布特征决定,可参考下表可考虑用该序列中已观测序列值的均值替代。当数据具有明显的趋势时,这种方法都可能在分析中引入偏差,表现不佳。适用数据类型:无趋势&无季节性数据。
#均值填充
data['a'] = data['a'].fillna(data['a'].mean())
#中位数填充
data['a'] = data['a'].fillna(data['a'].median())
#众数填充
data['a'] = data['a'].fillna(stats.mode(data['a'])[0][0])
(2)邻域or常数值替代
最近邻域替代法,即设t时刻的序列值缺失,用已观测到t-1时刻的序列值或者t+1时刻的序列值来替代t时刻的序列。当数据具有明显的趋势时,这种方法都可能在分析中引入偏差,表现不佳。适用数据类型:无趋势&无季节性数据。
#临域替代
#用前一个数据进行填充
data['a'] = data['a'].fillna(method='pad')
#用后一个数据进行填充
data['a'] = data['a'].fillna(method='bfill')
(3)常见统计模型填充
即通过一些建模方法获得缺失处的预测值,常见的有回归和样条模型法。回归模型包括一元线性回归和多元线性回归,即根据观测序列,构造出回归模型所需的自变量和因变量,从而得到自变量与因变量之间的关系,并得到缺失处的预测值。样条法是通过对已观测序列值建立样条模型,如三阶样条、光滑样条等,从而预测出缺失值。
该方法“理论上”提供了缺失数据的良好估计。然而,它有几个缺点可能比优点还值得关注。首先,因为替换值是根据其他变量预测的,他们倾向于“过好”地组合在一起,因此标准差会被缩小。我们还必须假设回归用到的变量之间存在线性关系——而实际上他们之间可能并不存在这样的关系。
适用数据类型:有趋势&无季节性数据
以下以线性回归和三阶样条为例:
处理前数据:
from matplotlib.pyplot import figure
import matplotlib.pyplot as plt
figure(figsize=(12, 5), dpi=80, linewidth=10)
plt.plot(passenger['Date'], passenger['Passengers'])
plt.title('Air Passengers Raw Data with Missing Values')
plt.xlabel('Years', fontsize=14)
plt.ylabel('Number of Passengers', fontsize=14)
plt.show()
两种处理方法
passenger[‘Linear’] = passenger[‘Passengers’].interpolate(method=’linear’)
passenger[‘Spline order 3’] = passenger[‘Passengers’].interpolate(method=’spline’, order=3)
methods = ['Linear', 'Spline order 3']
from matplotlib.pyplot import figure
import matplotlib.pyplot as plt
for method in methods:
figure(figsize=(12, 4), dpi=80, linewidth=10)
plt.plot(passenger["Date"], passenger[method])
plt.title('Air Passengers Imputation using: ' + types)
plt.xlabel("Years", fontsize=14)
plt.ylabel("Number of Passengers", fontsize=14)
plt.show()
处理后:
(4)具有季节性特征的时间序列处理方式见 2
(5)常用机器学习填充方式见 3
1.2离群值(异常值)检测
离群点(outlier)是指和其他观测点偏离非常大的数据点,离群点是异常的数据点,但是不一定是错误的数据点。确定离群点对于数据分析会带来不利的影响,比如,增大错误方差、影响预测和影响正态性。若检测出离群值,可以直接剔除或是采用其他值填充,但离群值并不一定需要剔除或是替代,需要根据具体建模的要求以及数据的现实情况来决定。
(1)3准则
在质量管理统计中,通常把远离标准差3倍距离以上的数据点视为离群点,也就是说,把Z-score大于3的数据点视作离群点,Python实现如下:
import numpy as np
import pandas as pd
def detect_outliers(data,threshold=3):
mean_d = np.mean(data)
std_d = np.std(data)
outliers = []
for y in data_d:
z_score= (y - mean_d)/std_d
if np.abs(z_score) > threshold:
outliers.append(y)
return outliers
(2)孤立森林
顾名思义,孤立森林是一种基于决策树的异常检测机器学习算法。它通过使用决策树的分区隔离给定特征集上的数据点来工作。在孤立森林中,递归地随机分割数据集,直到所有的样本点都是孤立的。在这种随机分割的策略下,通常只需要极少的分割次数就可以使得异常点被孤立。换句话说,那些密度很高的簇是需要被切割很多次才能被孤立,但是那些密度很低的点很容易就可以被孤立。一维、二维示意图如下:
from sklearn.ensemble import IsolationForest
X = [[-1.1], [0.2], [10.1], [0.3], [2.5], [1.7], [2.2], [1.7]]
clf = IsolationForest()
clf.fit(X)
# 输出decision_function的结果:大于0表示正样本的可信度大于负样本,否则可信度小于负样本。
# 在这个demo中,第三个数字是异常点的可能性最大,其次是第一个数字
scores = clf.decision_function(X)
"""
输出的结果是:
[-0.10780172 0.08482445 -0.2449808 0.08846511 0.04413297 0.11703052
0.0870313 0.11703052]
"""
print(scores)
(3)K-means 聚类
是一种无监督机器学习算法,经常用于检测时间序列数据中的异常值。该算法查看数据集中的数据点,并将相似的数据点分组为 K 个聚类。通过测量数据点到其最近质心的距离来区分异常。如果距离大于某个阈值,则将该数据点标记为异常。K-Means 算法使用欧式距离进行比较。Python案例分析如下:
data_zs = 1.0*(data - data.mean())/data.std() #数据标准化
from sklearn.cluster import KMeans
k=3 # 聚类的类别
iteration = 500 #聚类最大循环次数
model = KMeans(n_clusters = k, n_jobs = 4, max_iter = iteration) #分为k类,并发数4
model.fit(data_zs) #开始聚类
#标准化数据及其类别
r = pd.concat([data_zs, pd.Series(model.labels_, index = data.index)], axis = 1) #每个样本对应的类别
r.columns = list(data.columns) + [u'聚类类别'] #重命名表头
norm = []
for i in range(k): #逐一处理
norm_tmp = r[['R', 'F', 'M']][r['聚类类别'] == i]-model.cluster_centers_[i]
norm_tmp = norm_tmp.apply(np.linalg.norm, axis = 1) #求出绝对距离
norm.append(norm_tmp/norm_tmp.median()) #求相对距离并添加
norm = pd.concat(norm) #合并
threshold = 2 #离散点阈值
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
norm[norm <= threshold].plot(style = 'go') #正常点
discrete_points = norm[norm > threshold] #离群点
discrete_points.plot(style = 'ro')
for i in range(len(discrete_points)): #离群点做标记
id = discrete_points.index[i]
n = discrete_points.iloc[i]
plt.annotate('(%s, %0.2f)'%(id, n), xy = (id, n), xytext = (id, n))
plt.xlabel(u'编号')
plt.ylabel(u'相对距离')
离群点用红色进行标记:
2. 时间序列建模场景
2.1 具有季节性特征的数据填充
季节性调整 + 上述常规填充方法,适用数据类型:有趋势&有季节性数据
获取季节因子,去除时间序列的季节性,采用以上合适的填充方法,再乘相应的季节因子
#可以分别获得趋势、季节性和随机性
trend = decomposition.trend
seasonal = decomposition.seasonal
residual = decomposition.resid
2.2 时间序列建模预处理
2.2.1 平稳性和纯随机性检验
拿到一个观察值序列之后,首先要对它的平稳性和纯随机性进行检验,这两个重要的检验称为序列的预处理。根据检验的结果可以将序列分为不同的类型,对不同类型的序列我们会采用不同的分析方法。
时间序列需要进行平稳性检验,原因是很多模型的假设条件为平稳or随机序列,若时序不平稳或者不随机,则无法满足前提条件。
import numpy as np
from matplotlib import pyplot as plt
np.random.seed(123)
y = np.random.standard_normal(size=100)
for i in range(1, len(y)):
y[i] = 1 + 0.1*i + y[i]
plt.figure(figsize=(12, 6))
plt.plot(y)
plt.show()
from arch.unitroot import ADF
adf = ADF(y)
# print(adf.pvalue)
print(adf.summary().as_text())
adf = ADF(y)
adf.trend = 'ct'
print(adf.summary().as_text())
相关结果解读参考具体的DF或者ADF检验原理,结合统计量的具体结果。
2.2.2时间序列平稳化
(1)n阶差分:一般通过差分可以去除时序的趋势项。
以下以一阶差分为例:
df['first_difference'] = df.riders - df.riders.shift(1) #也可以使diff()
test_stationarity(df.first_difference.dropna(inplace=False))
(2)季节性差分
若依然不平稳,可以通过季节性差分消除序列的季节性
df['seasonal_difference'] = df.riders - df.riders.shift(12)
test_stationarity(df.seasonal_difference.dropna(inplace=False))
(3)取对数
df.riders_log= df.riders.apply(lambda x: np.log(x))
test_stationarity(df.riders_log)
以上方法可酌情叠加使用,需要根据具体情况调整
3. 机器学习场景
3.1 基于机器学习方法的数据填充
能够用于数据插补的机器学习方法有很多,比如Random Forest和KNN,但在这里我们讨论KNN方法,因为它被广泛应用。在本方法中,我们根据某种距离度量选择近邻居的数量),以及距离度量。KNN既可以预测离散属性(k近邻中最常见的值)也可以预测连续属性(k近邻的均值)。
#KNN
from fancyimpute import KNN
fill_knn = KNN(k=3).fit_transform(data)
data = pd.DataFrame(fill_knn)
print(data.head())
#out
0 1 2 3 4 5
0 111.0 0.0 2.0 360.0 4.000000 1.0
1 112.0 1.0 9.0 1080.0 3.000000 1.0
2 113.0 1.0 9.0 1080.0 2.000000 1.0
3 114.0 0.0 1.0 360.0 *3.862873 *1.0
4 115.0 0.0 1.0 270.0 5.000000 1.0
#随机森林
from sklearn.ensemble import RandomForestRegressor
#提取已有的数据特征
process_df = data.ix[:, [1, 2, 3, 4, 5]]
# 分成已知该特征和未知该特征两部分
known = process_df[process_df.c.notnull()].as_matrix()
uknown = process_df[process_df.c.isnull()].as_matrix()
# X为特征属性值
X = known[:, 1:3]
# print(X[0:10])
# Y为结果标签
y = known[:, 0]
print(y)
# 训练模型
rf = RandomForestRegressor(random_state=0, n_estimators=200, max_depth=3, n_jobs=-1)
rf.fit(X, y)
# 预测缺失值
predicted = rf.predict(uknown[:, 1:3])
print(predicted)
#将预测值填补原缺失值
data.loc[(data.c.isnull()), 'c'] = predicted
print(data[0:10])
3.2 训练集和测试集的划分
在完成缺失值、异常值等处理后,机器学习模型需要对数据进行训练集、测试集划分。
简单代码实现:
import numpy as np
def split_train_test(data,test_ratio):
#设置随机数种子,保证每次生成的结果都是一样的
np.random.seed(42)
#permutation随机生成0-len(data)随机序列
shuffled_indices = np.random.permutation(len(data))
#test_ratio为测试集所占的半分比
test_set_size = int(len(data)) * test_ratio
test_indices = shuffled_indices[:test_ratio]
train_indices = shuffled_indices[test_set_size:]
#iloc选择参数序列中所对应的行
return data.iloc[train_indices],data.iloc[test_indices]
#测试
train_set,test_set = split_train_test(data,0.2)
print(len(train_set), "train +", len(test_set), "test")
通过sklearn实现:
from sklearn.model_selection import train_test_split
#data:需要进行分割的数据集
#random_state:设置随机种子,保证每次运行生成相同的随机数
#test_size:将数据分割成训练集的比例
train_set, test_set = train_test_split(data, test_size=0.2, random_state=42)
更多机器学习填充预测方法可以参考Github资源:【 https://github.com/xinychen/transdim 】复制括号内的链接在浏览器打开,作者用代码实现了当前最主流的时间序列缺失值填充算法和预测算法。
转自:“社科学术汇”微信公众号
如有侵权,请联系本站删除!