پروژه ای در آنالیز و پیش بینی سری زمانی با استفاده از پایتون
آنالیز سری زمانی، روش های آنالیز داده های سری زمانی برای استخراج آمار معنادار و سایر ویژگیهای را دربر می گیرد. پیش بینی سری زمانی، بکارگیری یک مدل برای پیش بینی مقادیر آینده براساس مقادیر پیشتر مشاهده شده است.
سری زمانی(Time series)، به میزان گسترده ای برای داده های ناایستا(non-stationary) مانند اقتصاد، آب و هوا، قیمت سهام و خرده فروشی مورد استفاده قرار میگیرد. در این پست رویکردهای مختلف پیش بینی سری زمانی مربوط به یک فروشگاه خرده فروشی را شرح خواهیم داد. بیایید شروع کنیم!
داده
داده های فروش یک فروشگاه بزرگ را بکار می بریم که از این قسمت قابل دانلود هستند.
import warnings
import itertools
import numpy as np
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")
plt.style.use('fivethirtyeight')
import pandas as pd
import statsmodels.api as sm
import matplotlib
matplotlib.rcParams['axes.labelsize'] = 14
matplotlib.rcParams['xtick.labelsize'] = 12
matplotlib.rcParams['ytick.labelsize'] = 12
matplotlib.rcParams['text.color'] = 'k'
دسته های مختلفی در داده های فروش این فروشگاه بزرگ وجود دارد، ما، کار خود را با آنالیز و پیش بینی سری زمانی فروش مبلمان آغاز می کنیم.
df = pd.read_excel("Superstore.xls")
furniture = df.loc[df['Category'] == 'Furniture']
furniture.head()
یک داده خوب در مورد فروش مبلمان در بازه زمانی 4 ساله را در اختیار داریم.
furniture['Order Date'].min(), furniture['Order Date'].max()
پیش پردازش داده ها
این مرحله، حذف ستونهایی که نیاز نداریم، بررسی مقادیر از دست رفته، تجمیع دادگان فروش براساس تاریخ و موارد دیگر را دربر می گیرد.
cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']
furniture.drop(cols, axis=1, inplace=True)
furniture = furniture.sort_values('Order Date')
furniture.isnull().sum()
furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()
furniture.head()
شاخص گذاری با استفاده از داده های سری زمانی
furniture = furniture.set_index('Order Date')
furniture.index
کار با داده های تاریخ و زمان فعلی می تواند دشوار و پیچیده باشد، لذا، به جای آن، از میانگین مقدار فروش روزانه ی آن در ماه استفاده می کنیم و آغاز هر ماه را به عنوان برچسب زمان بکار می بریم.
y = furniture['Sales'].resample('MS').mean()
نگاهی اجمالی به داده های فروش مبلمان سال 2017
y['2017':]
مصورسازی داده های سری زمانی فروش مبلمان
y.plot(figsize=(15, 6))
plt.show()
تعدادی الگوی قابل تمایز در نمودار زمانی ترسیم شده نمایان می گردد. سری زمانی مصور شده، دارای الگوی فصلی است. مثلا فروش، همواره در آغاز سال، پایین و در پایان سال، بالا است. یک روند رو به بالا در طول هر سال به همراه دو ماه فروش کم در میانه ی سال مشاهده می شود.
همچنین، این امکان وجود دارد که داده های خود را با استفاده از یک روش موسوم به تجزیه سری زمانی مصور سازیم که تجزیه سری زمانی به سه مولفه متمایز: روند، فصلی بودن و نویز را میسر می سازد.
from pylab import rcParams
rcParams['figure.figsize'] = 18, 8
decomposition = sm.tsa.seasonal_decompose(y, model='additive')
fig = decomposition.plot()
plt.show()
نمودار قسمت بالا به وضوح نشان می دهد که فروش مبلمان، بی ثبات است و درضمن، فصلی بودن آن مشهود میباشد.
پیش بینی سری زمانی با استفاده از ARIMA
ما برآنیم که یکی از متداول ترین روش های پیش بینی سری زمانی، مشهور به ARIMA را بکار گیریم که مخفف میانگین متحرک یکپارچه خودهمبسته است.
مدل های ARIMA، با علامت ARIMA(p, d, q)
نشان داده می شوند. این سه پارامتر برای فصلی بودن، روند و نویز درنظر گرفته می شوند.
p = d = q = range(0, 2)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]
print('Examples of parameter combinations for Seasonal ARIMA...')
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[1]))
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[2]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[3]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[4]))
این مرحله، مرحله انتخاب پارامتر برای مدل سری زمانی ARIMA فروش مبلمان است. در این مرحله با استفاده از یک «جستجوی شبکه ای(Grid Search)» سعی در یافتن مجموعه بهینه ای از پارامترهایی که بهترین عملکرد را برای مدل ما نتیجه می دهد، داریم.
for param in pdq:
for param_seasonal in seasonal_pdq:
try:
mod = sm.tsa.statespace.SARIMAX(y,
order=param,
seasonal_order=param_seasonal,
enforce_stationarity=False,
enforce_invertibility=False)
results = mod.fit()
print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
except:
continue
خروجی بالا نشان می دهد که SARIMAX(1, 1, 1)x(1, 1, 0, 12)
، کمترین مقدار AIC به میزان ۲۹۷.۷۸ را نتیجه می دهد. لذا، آن را به عنوان بهترین گزینه درنظر می گیریم.
برازاندن مدل ARIMA
mod = sm.tsa.statespace.SARIMAX(y,
order=(1, 1, 1),
seasonal_order=(1, 1, 0, 12),
enforce_stationarity=False,
enforce_invertibility=False)
results = mod.fit()
print(results.summary().tables[1])
باید همواره باید امکانات عیب شناسی(diagnostics) مدل را به منظور تحقیق و بررسی هر نوع رفتار غیرمعمول اجرا کنیم.
results.plot_diagnostics(figsize=(16, 8))
plt.show()
مدل ما کامل و بی عیب نیست، امکانات عیب شناسی مدل ما نشان می دهد که باقی مانده های(residuals) مدل، دارای توزیع نزدیک به توزیع نرمال هستند.
ارزیابی پیش بینی ها
فروش پیش بینی شده را با فروش واقعی سری زمانی مقایسه می کنیم و پیش بینی های بازه زمانی 1/1/2017 تا پایان داده ها را مشخص می نماییم تا میزان دقت پیش بینی های خود را دریابیم.
pred = results.get_prediction(start=pd.to_datetime('2017-01-01'), dynamic=False)
pred_ci = pred.conf_int()
ax = y['2014':].plot(label='observed')
pred.predicted_mean.plot(ax=ax, label='One-step ahead Forecast', alpha=.7, figsize=(14, 7))
ax.fill_between(pred_ci.index,
pred_ci.iloc[:, 0],
pred_ci.iloc[:, 1], color='k', alpha=.2)
ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')
plt.legend()
plt.show()
نمودار خطی، مقادیر مشاهده شده را در مقایسه با مقادیر پیش بینی شده نشان می دهد. در مجموع پیش بینی های ما با مقادیر واقعی بسیار همخوانی دارد که نشانگر یک روند رو به بالاست که از ابتدای سال آغاز می شود و نزدیک به انتهای سال، فصلی است.
y_forecasted = pred.predicted_mean
y_truth = y['2017-01-01':]
mse = ((y_forecasted - y_truth) ** 2).mean()
print('The Mean Squared Error of our forecasts is {}'.format(round(mse, 2)))
print('The Root Mean Squared Error of our forecasts is {}'.format(round(np.sqrt(mse), 2)))
خطای میانگین مربعات (MSE) یک برآوردگر در آمار، میانگین مربعات خطاها، یعنی میانگین مربع تفاوت میان مقادیر مشاهده شده (واقعی) و آنچه که برآورد می شود را می سنجد. MSE، یک معیار برای سنجش کیفیت یک برآوردگر است که همواره غیرمنفی است و هرچه MSE کوچک تر باشد، یافتن خط بهترین برازش، نزدیک تر (دقیق تر) می شود.
خطای ریشه میانگین مربعات (RMSE) به ما می گوید که مدل مان قادر به پیش بینی میانگین فروش روزانه مبلمان در مجموعه داده های آزمایشی با خطایی معادل ۱۵۱.۶۴ واحد با داده واقعی است.
فروش روزانه مبلمان ما، از حدود 400 تا بیش از 1200 را دربر می گیرد. این مدل، بنظر من، تاکنون مدل نسبتا خوبی بوده است.
پیش بینی و مصور سازی آنها
pred_uc = results.get_forecast(steps=100)
pred_ci = pred_uc.conf_int()
ax = y.plot(label='observed', figsize=(14, 7))
pred_uc.predicted_mean.plot(ax=ax, label='Forecast')
ax.fill_between(pred_ci.index,
pred_ci.iloc[:, 0],
pred_ci.iloc[:, 1], color='k', alpha=.25)
ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')
plt.legend()
plt.show()
جدول ما، فصلی بودن فروش مبلمان را به وضوح نشان می دهد. چنانچه با آینده بسیار دورتر، دست به پیش بینی بزنیم، طبیعی است که اطمینان کمتری به مقادیر پیش بینی شده داشته باشیم. فاصله های اطمینان ایجاد شده (نمایش به عنوان بازه خاکستری) این امر را بیان می کند، که با آینده بسیار دورتر، بزرگ تر می شود.
آنالیز سری زمانی مبلمان در قسمت بالا، کنجکاوی بنده را در رابطه با دیگر دسته ها و چگونگی مقایسه آن ها با یکدیگر در طول زمان برانگیخت. لذا برآنیم تا سری زمانی مبلمان و تجهیزات اداری را مقایسه نماییم.
مقایسه سری زمانی مبلمان با سری زمانی تجهیزات اداری
با توجه به داده ها تعداد فروش تجهیزات اداری طی این سالها نسبت به مبلمان بسیار بیشتر بوده است.
furniture = df.loc[df['Category'] == 'Furniture']
office = df.loc[df['Category'] == 'Office Supplies']
furniture.shape, office.shape
کاوش داده ها
ما می خواهیم فروش دو دسته را در همین دوره زمانی مقایسه کنیم. این امر به معنای ترکیب دو محموعه داده در یک مجموعه و ترسیم نمودار سری زمانی این دو مجموعه در یک نمودار است.
cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']
furniture.drop(cols, axis=1, inplace=True)
office.drop(cols, axis=1, inplace=True)
furniture = furniture.sort_values('Order Date')
office = office.sort_values('Order Date')
furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()
office = office.groupby('Order Date')['Sales'].sum().reset_index()
furniture = furniture.set_index('Order Date')
office = office.set_index('Order Date')
y_furniture = furniture['Sales'].resample('MS').mean()
y_office = office['Sales'].resample('MS').mean()
furniture = pd.DataFrame({'Order Date':y_furniture.index, 'Sales':y_furniture.values})
office = pd.DataFrame({'Order Date': y_office.index, 'Sales': y_office.values})
store = furniture.merge(office, how='inner', on='Order Date')
store.rename(columns={'Sales_x': 'furniture_sales', 'Sales_y': 'office_sales'}, inplace=True)
store.head()
plt.figure(figsize=(20, 8))
plt.plot(store['Order Date'], store['furniture_sales'], 'b-', label = 'furniture')
plt.plot(store['Order Date'], store['office_sales'], 'r-', label = 'office supplies')
plt.xlabel('Date'); plt.ylabel('Sales'); plt.title('Sales of Furniture and Office Supplies')
plt.legend()
مشاهده می کنیم که فروش مبلمان و تجهیزات اداری دارای یک الگوی فصلی مشابه است. اوایل سال، فصلِ فروش کم برای هر دو دسته است. این طور بنظر می رسد که زمان تابستان برای تجهیزات اداری نیز زمان کسادی است، درضمن، میانگین فروش روزانه مبلمان در اکثر ماه ها از میانگین فروش روزانه تجهیزات اداری بیشتر است. این امر طبیعی است، چراکه مقدار مبلمان نسبت به مقدار تجهیزات اداری بایستی بسیار بیشتر باشد. میانگین فروش روزانه تجهیزات اداری، هر از گاهی از مبلمان پیشی می گیرد. بیایید مشخص کنیم برای اولین بار چه زمانی فروش تجهیزات اداری از فروش مبلمان پیش افتاد.
first_date = store.ix[np.min(list(np.where(store['office_sales'] > store['furniture_sales'])[0])), 'Order Date']
print("Office supplies first time produced higher sales than furniture is {}.".format(first_date.date()))
مدل سازی سری زمانی با استفاده از Prophet
ابزار پیش بینی Prophet که توسط فیسبوک در سال 2017 منتشر شد، برای آنالیز سری زمانی طراحی شده است که دارای الگوهایی در بازه های زمانی مختلف مانند سالانه، هفتگی یا روزانه هستند. این ابزار قابلیت های پیشرفته ای برای مدل سازی تاثیرات تعطیلات بر سری زمانی برخوردار است. بنابراین در این قسمت میخواهیم از Prophet استفاده کنیم.
from fbprophet import Prophet
furniture = furniture.rename(columns={'Order Date': 'ds', 'Sales': 'y'})
furniture_model = Prophet(interval_width=0.95)
furniture_model.fit(furniture)
office = office.rename(columns={'Order Date': 'ds', 'Sales': 'y'})
office_model = Prophet(interval_width=0.95)
office_model.fit(office)
furniture_forecast = furniture_model.make_future_dataframe(periods=36, freq='MS')
furniture_forecast = furniture_model.predict(furniture_forecast)
office_forecast = office_model.make_future_dataframe(periods=36, freq='MS')
office_forecast = office_model.predict(office_forecast)
plt.figure(figsize=(18, 6))
furniture_model.plot(furniture_forecast, xlabel = 'Date', ylabel = 'Sales')
plt.title('Furniture Sales')
plt.figure(figsize=(18, 6))
office_model.plot(office_forecast, xlabel = 'Date', ylabel = 'Sales')
plt.title('Office Supplies Sales');
مقایسه پیش بینی ها
ما، پیشتر، پیش بینی هایی برای سه سال آینده ی این دو دسته انجام داده ایم. حال با کنار هم گذاشتن آن ها، پیش بینی های انجام شده را مورد مقایسه قرار می دهیم.
furniture_names = ['furniture_%s' % column for column in furniture_forecast.columns]
office_names = ['office_%s' % column for column in office_forecast.columns]
merge_furniture_forecast = furniture_forecast.copy()
merge_office_forecast = office_forecast.copy()
merge_furniture_forecast.columns = furniture_names
merge_office_forecast.columns = office_names
forecast = pd.merge(merge_furniture_forecast, merge_office_forecast, how = 'inner', left_on = 'furniture_ds', right_on = 'office_ds')
forecast = forecast.rename(columns={'furniture_ds': 'Date'}).drop('office_ds', axis=1)
forecast.head()
مصورسازی روند و پیش بینی
plt.figure(figsize=(10, 7))
plt.plot(forecast['Date'], forecast['furniture_trend'], 'b-')
plt.plot(forecast['Date'], forecast['office_trend'], 'r-')
plt.legend(); plt.xlabel('Date'); plt.ylabel('Sales')
plt.title('Furniture vs. Office Supplies Sales Trend')
plt.figure(figsize=(10, 7))
plt.plot(forecast['Date'], forecast['furniture_yhat'], 'b-')
plt.plot(forecast['Date'], forecast['office_yhat'], 'r-')
plt.legend(); plt.xlabel('Date'); plt.ylabel('Sales')
plt.title('Furniture vs. Office Supplies Estimate')
روندها و الگوها
ما اکنون می توانین مدل های Prophet را برای بررسی روندهای مختلف این دو دسته در داده ها مورد استفاده قرار دهیم.
furniture_model.plot_components(furniture_forecast)
office_model.plot_components(office_forecast)
خوب است بدانید که فروش هم مبلمان و هم تجهیزات اداری در طول زمان، افزایش خطی داشته است و این رشد را ادامه خواهد داشت، هرچند رشد تجهیزات اداری اندکی سریع تر بنظر می آید.
آوریل، بدترین ماه برای مبلمان و فوریه، بدترین ماه برای تجهیزات اداری است. دسامبر، بهترین ماه برای مبلمان و اکتبر، بهترین ماه برای تجهیزات اداری است.
آنالیزهای سری زمانی بسیاری همچون پیش بینی با استفاده از محدودیت های عدم قطعیت (uncertainty bounds)، نقطه تغییر (change point) و تشخیص آنومالی و پیش بینی سری زمانی با استفاده از منبع داده خارجی وجود دارد. ما تنها شروع کننده بودیم!
سلام
آیا در زمینه پیش بینی ارز دیجیتالی بیت کوین با سری های زمانی روشی را می شناسید که دقت و حساسیت بالایی داشته باشد چه خودتان زحمت کشیده باشد چه ارجاع دهید.
سپاس بابت پاسخگویی
سلام
چون در رابطه با قیمت ارزهای دیجیتال عوامی بسیار زیادی درگیر هستند معمولا روش های آماری و یا شبکه های عمیق به تنهایی خیلی دقت بالایی ندارند.