时间序列平滑方法之移动平均、一次指数平滑、二次指数平滑、三次指数平滑的python细节实现...

时间序列平滑方法之移动平均、一次指数平滑、二次指数平滑、三次指数平滑的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

推荐阅读:

我的2022届互联网校招分享

我的2021总结

浅谈算法岗和开发岗的区别

互联网校招研发薪资汇总

公众号:AI蜗牛车

保持谦逊、保持自律、保持进步

9cc7ec25479b33cff8852a7f9059e6b3.jpeg

发送【蜗牛】获取一份《手把手AI项目》(AI蜗牛车著)

发送【1222】获取一份不错的leetcode刷题笔记

发送【AI四大名著】获取四本经典AI电子书

猜你喜欢

转载自blog.csdn.net/qq_33431368/article/details/134901683