时间序列平滑方法之移动平均、一次指数平滑、二次指数平滑、三次指数平滑的python细节实现
一、平滑方法
常用的平滑方法有
移动平均
一次指数平滑、二次指数平滑、三次指数平滑
具体理论比较好懂,可以参考:
https://zhuanlan.zhihu.com/p/441373033
https://zhuanlan.zhihu.com/p/78848809
二、python实现
以下写法都是流式的方式来写的,就是来一个数据处理一个那种流式数据
2.1 移动平均
class MovAvgSmoothing(object):
def __init__(self, window_size=7):
self.window_size = window_size
self.data_queue = []
def update(self, data):
if len(self.data_queue) == self.window_size:
del self.data_queue[0]
self.data_queue.append(data)
return sum(self.data_queue) / len(self.data_queue)
2.2 指数平滑
2.2.1 一次指数平滑
class ExpSmoothing(object):
def __init__(self, alpha=0.9):
self.alpha = alpha
self.prev_smooth = None
def update(self, data):
if self.prev_smooth is None:
self.prev_smooth = data
return data
else:
smooth = self.alpha * data + (1 - self.alpha) * self.prev_smooth
self.prev_smooth = smooth
return smooth
2.2.2 二次指数平滑
"""
Holt指数平滑,方法中包含一个预测方程和两个平滑方程(水平平滑方程+趋势预测方程)
趋势部分又可分为加性趋势和乘性趋势
对于较大时间步长的预测,趋势可能不会无限延长,就需要抑制这种趋势,加性趋势和乘性趋势的抑制分别对应加性抑制(抑制线性趋势)、乘性抑制(抑制指数趋势
"""
class SmoothMode(Enum):
Addition = "add mode"
Multiplication = "multiply mode"
class DoubleExpSmoothing(object):
def __init__(self, alpha=0.5, beta=0.5, mode=SmoothMode.Addition):
self.alpha = alpha
self.beta = beta
self.prev_smooth = None
self.prev_trend = None
self.mode = mode
def update(self, data):
if self.prev_smooth is None:
self.prev_smooth = data
return data
elif self.prev_trend is None:
smooth = None
if self.mode == SmoothMode.Addition:
self.prev_trend = data - self.prev_smooth
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth + self.prev_trend)
elif self.mode == SmoothMode.Multiplication:
self.prev_trend = data / self.prev_smooth
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth * self.prev_trend)
self.prev_smooth = smooth
return smooth
else:
trend = None
smooth = None
if self.mode == SmoothMode.Addition:
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth + self.prev_trend)
trend = self.beta * (smooth - self.prev_smooth) + (1 - self.beta) * self.prev_trend
elif self.mode == SmoothMode.Multiplication:
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth * self.prev_trend)
trend = self.beta * (smooth / self.prev_smooth) + (1 - self.beta) * self.prev_trend
self.prev_smooth = smooth
self.prev_trend = trend
return smooth
2.2.3 三次指数平滑
"""
Holt-Winters加法模型(Holt-Winters指数平滑)
方法中包含一个预测方程和三个平滑方程 (一个用于水平,一个用于趋势,一个用于季节性分量)
当季节变化在时间序列中大致保持不变时,通常选择加法模型
"""
class SmoothMode(Enum):
Addition = "add mode"
Multiplication = "multiply mode"
class TripleExpSmoothing(object):
def __init__(self, alpha=0.5, beta=0.5, gamma=0.5, m=12, mode=SmoothMode.Addition):
self.alpha = alpha
self.beta = beta
self.gamma = gamma
self.m = m
self.prev_smooth = None
self.prev_trend = None
self.seasonal = [0] * m
self.mode = mode
def update(self, data):
if self.prev_smooth is None:
self.prev_smooth = data
return data
elif self.prev_trend is None:
trend, smooth = None, None
if self.mode == SmoothMode.Addition:
trend = data - self.prev_smooth
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth + trend)
elif self.mode == SmoothMode.Multiplication:
trend = data / self.prev_smooth
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth + trend)
self.prev_smooth = smooth
self.prev_trend = trend
return smooth
else:
season, trend, smooth = None, None, None
pre_m_season = self.seasonal[len(self.seasonal) - self.m]
if self.mode == SmoothMode.Addition:
smooth = self.alpha * (data - pre_m_season) + (1 - self.alpha) * (self.prev_smooth + self.prev_trend)
trend = self.beta * (smooth - self.prev_smooth) + (1 - self.beta) * self.prev_trend
season = self.gamma * (data - self.prev_smooth - self.prev_trend) + (1 - self.gamma) * pre_m_season
elif self.mode == SmoothMode.Multiplication:
smooth = self.alpha * (data / pre_m_season) + (1 - self.alpha) * (self.prev_smooth + self.prev_trend)
trend = self.beta * (smooth - self.prev_smooth) + (1 - self.beta) * self.prev_trend
season = self.gamma * (data / (self.prev_smooth + self.prev_trend)) + (1 - self.gamma) * pre_m_season
self.seasonal.append(season)
self.season_garbage_collection()
self.prev_smooth, self.prev_trend = smooth, trend
return smooth
# 60s 一个点,一天1440,周期如果是一周则是1440*7
def season_garbage_collection(self):
if len(self.seasonal) >= 1440 * 7:
self.seasonal = self.seasonal[-1440 * 7:]
三、统一管理
可以用一个类取动态灵活的管理这些平滑方法,再根据使用的调度去选择用哪个
class MovSmoothManager(object):
def __init__(self, m):
self.avg_smooth = MovAvgSmoothing(window_size=3)
self.exp_smooth = ExpSmoothing(alpha=0.75)
self.double_exp_smooth_add = DoubleExpSmoothing(alpha=0.75, beta=0.75)
self.double_exp_smooth_multiply = DoubleExpSmoothing(alpha=0.75, beta=0.75, mode=SmoothMode.Multiplication)
self.triple_exp_smooth_add = TripleExpSmoothing(alpha=0.75, beta=0.75, gamma=0.75, m=m)
self.triple_exp_smooth_multiply = TripleExpSmoothing(alpha=0.75, beta=0.75, gamma=0.75, m=m, mode=SmoothMode.Multiplication)
def update_data(self, fea_pd: pd.DataFrame, fea_cols):
smooth_pd = fea_pd.copy(deep=True)
smooth_fea = ['avgSmooth', 'expSmooth', 'doubleExpSmoothAdd', 'doubleExpSmoothMultiply',
'tripleExpSmoothAdd', 'tripleExpSmoothMultiply']
smooth_data = {}
for fea_col in fea_cols:
smooth_pd['avgSmooth'] = smooth_pd[fea_col].apply(lambda x: self.avg_smooth.update(x))
smooth_pd['expSmooth'] = smooth_pd[fea_col].apply(lambda x: self.exp_smooth.update(x))
smooth_pd['doubleExpSmoothAdd'] = smooth_pd[fea_col].apply(lambda x: self.double_exp_smooth_add.update(x))
smooth_pd['doubleExpSmoothMultiply'] = smooth_pd[fea_col].apply(lambda x: self.double_exp_smooth_multiply.update(x))
smooth_pd['tripleExpSmoothAdd'] = smooth_pd[fea_col].apply(lambda x: self.triple_exp_smooth_add.update(x))
smooth_pd['tripleExpSmoothMultiply'] = smooth_pd[fea_col].apply(lambda x: self.triple_exp_smooth_multiply.update(x))
smooth_data[fea_col] = smooth_pd[smooth_fea]
return smooth_data
四、总代码
from enum import Enum
import pandas as pd
class SmoothMode(Enum):
Addition = "add mode"
Multiplication = "multiply mode"
class MovAvgSmoothing(object):
def __init__(self, window_size=7):
self.window_size = window_size
self.data_queue = []
def update(self, data):
if len(self.data_queue) == self.window_size:
del self.data_queue[0]
self.data_queue.append(data)
return sum(self.data_queue) / len(self.data_queue)
class ExpSmoothing(object):
def __init__(self, alpha=0.9):
self.alpha = alpha
self.prev_smooth = None
def update(self, data):
if self.prev_smooth is None:
self.prev_smooth = data
return data
else:
smooth = self.alpha * data + (1 - self.alpha) * self.prev_smooth
self.prev_smooth = smooth
return smooth
"""
Holt指数平滑,方法中包含一个预测方程和两个平滑方程(水平平滑方程+趋势预测方程)
趋势部分又可分为加性趋势和乘性趋势
对于较大时间步长的预测,趋势可能不会无限延长,就需要抑制这种趋势,加性趋势和乘性趋势的抑制分别对应加性抑制(抑制线性趋势)、乘性抑制(抑制指数趋势
"""
class DoubleExpSmoothing(object):
def __init__(self, alpha=0.5, beta=0.5, mode=SmoothMode.Addition):
self.alpha = alpha
self.beta = beta
self.prev_smooth = None
self.prev_trend = None
self.mode = mode
def update(self, data):
if self.prev_smooth is None:
self.prev_smooth = data
return data
elif self.prev_trend is None:
smooth = None
if self.mode == SmoothMode.Addition:
self.prev_trend = data - self.prev_smooth
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth + self.prev_trend)
elif self.mode == SmoothMode.Multiplication:
self.prev_trend = data / self.prev_smooth
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth * self.prev_trend)
self.prev_smooth = smooth
return smooth
else:
trend = None
smooth = None
if self.mode == SmoothMode.Addition:
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth + self.prev_trend)
trend = self.beta * (smooth - self.prev_smooth) + (1 - self.beta) * self.prev_trend
elif self.mode == SmoothMode.Multiplication:
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth * self.prev_trend)
trend = self.beta * (smooth / self.prev_smooth) + (1 - self.beta) * self.prev_trend
self.prev_smooth = smooth
self.prev_trend = trend
return smooth
"""
Holt-Winters加法模型(Holt-Winters指数平滑)
方法中包含一个预测方程和三个平滑方程 (一个用于水平,一个用于趋势,一个用于季节性分量)
当季节变化在时间序列中大致保持不变时,通常选择加法模型
"""
class TripleExpSmoothing(object):
def __init__(self, alpha=0.5, beta=0.5, gamma=0.5, m=12, mode=SmoothMode.Addition):
self.alpha = alpha
self.beta = beta
self.gamma = gamma
self.m = m
self.prev_smooth = None
self.prev_trend = None
self.seasonal = [0] * m
self.mode = mode
def update(self, data):
if self.prev_smooth is None:
self.prev_smooth = data
return data
elif self.prev_trend is None:
trend, smooth = None, None
if self.mode == SmoothMode.Addition:
trend = data - self.prev_smooth
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth + trend)
elif self.mode == SmoothMode.Multiplication:
trend = data / self.prev_smooth
smooth = self.alpha * data + (1 - self.alpha) * (self.prev_smooth + trend)
self.prev_smooth = smooth
self.prev_trend = trend
return smooth
else:
season, trend, smooth = None, None, None
pre_m_season = self.seasonal[len(self.seasonal) - self.m]
if self.mode == SmoothMode.Addition:
smooth = self.alpha * (data - pre_m_season) + (1 - self.alpha) * (self.prev_smooth + self.prev_trend)
trend = self.beta * (smooth - self.prev_smooth) + (1 - self.beta) * self.prev_trend
season = self.gamma * (data - self.prev_smooth - self.prev_trend) + (1 - self.gamma) * pre_m_season
elif self.mode == SmoothMode.Multiplication:
smooth = self.alpha * (data / pre_m_season) + (1 - self.alpha) * (self.prev_smooth + self.prev_trend)
trend = self.beta * (smooth - self.prev_smooth) + (1 - self.beta) * self.prev_trend
season = self.gamma * (data / (self.prev_smooth + self.prev_trend)) + (1 - self.gamma) * pre_m_season
self.seasonal.append(season)
self.season_garbage_collection()
self.prev_smooth, self.prev_trend = smooth, trend
return smooth
# 60s 一个点,一天1440,周期如果是一周则是1440*7
def season_garbage_collection(self):
if len(self.seasonal) >= 1440 * 7:
self.seasonal = self.seasonal[-1440 * 7:]
class MovSmoothManager(object):
def __init__(self, m):
self.avg_smooth = MovAvgSmoothing(window_size=3)
self.exp_smooth = ExpSmoothing(alpha=0.75)
self.double_exp_smooth_add = DoubleExpSmoothing(alpha=0.75, beta=0.75)
self.double_exp_smooth_multiply = DoubleExpSmoothing(alpha=0.75, beta=0.75, mode=SmoothMode.Multiplication)
self.triple_exp_smooth_add = TripleExpSmoothing(alpha=0.75, beta=0.75, gamma=0.75, m=m)
self.triple_exp_smooth_multiply = TripleExpSmoothing(alpha=0.75, beta=0.75, gamma=0.75, m=m, mode=SmoothMode.Multiplication)
def update_data(self, fea_pd: pd.DataFrame, fea_cols):
smooth_pd = fea_pd.copy(deep=True)
smooth_fea = ['avgSmooth', 'expSmooth', 'doubleExpSmoothAdd', 'doubleExpSmoothMultiply',
'tripleExpSmoothAdd', 'tripleExpSmoothMultiply']
smooth_data = {}
for fea_col in fea_cols:
smooth_pd['avgSmooth'] = smooth_pd[fea_col].apply(lambda x: self.avg_smooth.update(x))
smooth_pd['expSmooth'] = smooth_pd[fea_col].apply(lambda x: self.exp_smooth.update(x))
smooth_pd['doubleExpSmoothAdd'] = smooth_pd[fea_col].apply(lambda x: self.double_exp_smooth_add.update(x))
smooth_pd['doubleExpSmoothMultiply'] = smooth_pd[fea_col].apply(lambda x: self.double_exp_smooth_multiply.update(x))
smooth_pd['tripleExpSmoothAdd'] = smooth_pd[fea_col].apply(lambda x: self.triple_exp_smooth_add.update(x))
smooth_pd['tripleExpSmoothMultiply'] = smooth_pd[fea_col].apply(lambda x: self.triple_exp_smooth_multiply.update(x))
smooth_data[fea_col] = smooth_pd[smooth_fea]
return smooth_data
推荐阅读:
公众号:AI蜗牛车
保持谦逊、保持自律、保持进步
发送【蜗牛】获取一份《手把手AI项目》(AI蜗牛车著)
发送【1222】获取一份不错的leetcode刷题笔记
发送【AI四大名著】获取四本经典AI电子书