Code for Simple Exponential Smoothing and Weighted Moving Average.

Single Exponential Smoothing

__single[source]

__single(ts, alpha=0.1)

df = simulate_data.pandas_time_series()

ts = df['time_series'].to_numpy()
for a in (0.2, 0.4):
    df[f'ss_{a}'] = __single(ts=ts, alpha=a)

fig = df.plot(
    backend='plotly',
    title=f'Single Smoothing',
)

fig.update_layout(template="plotly_dark",)

Double Exponential Smoothing

__double[source]

__double(ts, alpha=0.1, beta=0.1, s_0=None, b_0=None, m=1)

df = simulate_data.pandas_time_series()

ts = df['time_series'].to_numpy()
for a, b in ((0.25, 0.25), (0.6, 0.6)):

    df[f'ds_{a}_{b}'] = __double(
        ts=ts,
        alpha=a,
        beta=b,
    )

fig = df.plot(
    backend='plotly',
    title=f'Double Smoothing',
)

fig.update_layout(template="plotly_dark",)

Triple Exponential Smoothing

Holt-Winters Method

holt_winters[source]

holt_winters(ts, l=1, alpha=0.1, beta=0.1, gamma=0.1, s_0=None, b_0=None, m=1)

num_points = 35
m = 1
l = 4
interval = np.linspace(
    0,
    l * np.pi,
    num=num_points + m,
)

level = 3
season = (30 / 1) * np.sin(interval[:-m])
trend = np.vectorize(lambda x: (3 / 1) * x)(interval[:-m])
noise = (4 / 1) * np.random.random((num_points,))
d = level + season + trend + noise

alpha1, alpha2 = 0.5, 0.5
beta1, beta2 = 0.15, 0.2
gamma1 = 0.15

time_series = np.append(d, [np.nan for _ in range(m)])

holtwinters1 = holt_winters(
    ts=d,
    l=l,
    alpha=alpha1,
    beta=beta1,
    gamma=gamma1,
    m=m,
)

df = pd.DataFrame(
    {
        'time_series': time_series,
        f'a_{alpha1}\n b_{beta1}\n g_{gamma1}': holtwinters1,
        # f'alph_{alpha2}_bet_{beta2}': smoothing2,
    },
    index=interval,
)
df.index = interval

fig = df.plot(backend='plotly', title=f'Forecast m={m} time-steps ahead')

fig.update_layout(template="plotly_dark",)

...

Dataframes and Figures

Generates a Time Series Dataframe and a Figure Object

The Values of The Time Series are Simulated

Includes Forecasting with Moving Averages

__smoothing[source]

__smoothing(*args, kind=None, df=None, ts_col=None, **kwargs)

Forescasts with Exponential Smoothing


Parameters

kind : str , default 'simple'. It can be:

 'simple' : Single Smoothing.
 'double' : Double Smoothing.

df : DataFrame, default None. If df is None a dataframe with simulated data is generated.

ts_col : str, default None. The column name with the time series. Set to 'time_series' when df is None. *args : int. Each value represent a Moving Average Forecast and its corresponding window size **kwargs : passed to __pandas_time_series


Returns

tuple: First element is the Pandas Dataframe and the second is ploty's figure object

SINGLE[source]

SINGLE(*args, df=None, ts_col=None, **kwargs)

Forescasts with Single Smoothing


Parameters

df : DataFrame, default None. If df is None a dataframe with simulated data is generated. ts_col : str, default 'time_series'. The column name with the time series. Ignored if df is None. *args : int. Each value represent a Single Smoothing parameter

**kwargs : passed to __pandas_time_series

Returns

tuple: First element is the Pandas Dataframe and the second is ploty's fig object

_, fig = SINGLE(.2, .6)
fig.show()
df = pd.read_csv(
    '../data/Electric_Production.csv',
    index_col='DATE',
    parse_dates=['DATE'],
)
ts_col = 'Electric Production'
df.columns = [ts_col]
_, fig = SINGLE(
    .25,
    .65,
    df=df,
    ts_col=ts_col,
)
fig.update_layout(
    autosize=False,
    width=1100,
    height=450,
)
fig.update_traces(line=dict(width=0.8))
fig.show()

DOUBLE[source]

DOUBLE(*args, df=None, ts_col=None, **kwargs)

Forescasts with Double Smoothing


Parameters

df : DataFrame, default None. If df is None a dataframe with simulated data is generated. ts_col : str, default 'time_series'. The column name with the time series. Ignored if df is None. *args : Each pair reprents the two parameters of the double smoothing

**kwargs : passed to __pandas_time_series

Returns

tuple: First element is the Pandas Dataframe and the second is ploty's fig object

_, fig = DOUBLE(
    [.25, .5],
    [.6, .7],
)

fig.show()
df = pd.read_csv(
    '../data/Electric_Production.csv',
    index_col='DATE',
    parse_dates=['DATE'],
)
ts_col = 'Electric Production'
df.columns = [ts_col]
_, fig = DOUBLE(
    [.25, .5],
    [.4, .6],
    df=df,
    ts_col=ts_col,
)
fig.update_layout(
    autosize=False,
    width=1100,
    height=450,
)
fig.update_traces(line=dict(width=0.8))
fig.show()

Further Tests

Test with statsmodels library

smoothing_level = 0.15
smoothing_trend = 0.15

own_col = 'home_made_holt'

dff = pd.read_csv(
    '../data/Electric_Production.csv',
    index_col='DATE',
    parse_dates=['DATE'],
)
#dff = dff.resample('D').mean()
dff.index = pd.date_range('2020-01-01', periods=len(dff), freq='D')
ts_col = 'Electric Production'
dff.columns = [ts_col]
dff, _ = DOUBLE(
    [smoothing_level, smoothing_trend],
    df=dff,
    ts_col=ts_col,
)
dff.columns = [ts_col, own_col]

stats_holt = Holt(
    dff[[ts_col]],
    initialization_method="estimated",
).fit(
    smoothing_level=smoothing_level,
    smoothing_trend=smoothing_trend,
    optimized=False,
)
sm_holt = 'sm_holt'
dff[sm_holt] = stats_holt.fittedvalues
dff['sm_level'] = stats_holt.level
dff['sm_trend'] = stats_holt.trend

dff['delta'] = dff[sm_holt] - dff[own_col]
 
assert all(dff.delta[55:] < 10**(-5))
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-175-92fbf51a7ee3> in <module>
----> 1 assert all(dff.delta[55:] < 10**(-5))

AssertionError: 
fig = dff[['delta']].plot(backend='plotly',)
fig.update_layout(template="plotly_dark",)
fig.show()
fig = dff[[ts_col, own_col, sm_holt]].plot(backend='plotly',)
fig.update_layout(template="plotly_dark",)
fig.show()
datafile = '../data/superstore_sales.csv'

dff = pd.read_csv(
    datafile,
    index_col='Order Date',
    dtype={
        'Row ID': str,
        'Order ID': str,
    },
    parse_dates=['Order Date', 'Ship Date'],
)

dff.columns = ['_'.join(x.lower().split(' ')) for x in dff.columns]

region = 'Atlantic'
product_category = 'Furniture'
customer_segment = 'Consumer'

query = ' and '.join([
    f"region=='{region}'",
    f"product_category=='{product_category}'",
    f"customer_segment=='{customer_segment}'",
])

dff = dff.query(query)
dff = dff.resample('2W').sum()
ts_col = 'sales'

dff = dff[[ts_col]]

#dff.index = pd.date_range('2020-01-01', periods=len(dff), freq='D')

smoothing_level = 0.5
smoothing_trend = 0.5

own_col = 'home_made_holt'

dff, _ = DOUBLE(
    [smoothing_level, smoothing_trend],
    df=dff,
    ts_col=ts_col,
)


dff.columns = [ts_col, own_col]

fig.update_layout(
    autosize=False,
    width=1100,
    height=450,
)

stats_holt = Holt(dff[[ts_col]], initialization_method="estimated").fit(
    smoothing_level=smoothing_level,
    smoothing_trend=smoothing_trend,
    optimized=False,
)
sm_holt = 'sm_holt'
dff[sm_holt] = stats_holt.fittedvalues
dff['sm_level'] = stats_holt.level
dff['sm_trend'] = stats_holt.trend

dff['delta'] = dff[sm_holt] - dff[own_col]
fig = dff[['delta']].plot(backend='plotly',)
fig.update_layout(template="plotly_dark",)
fig.show()