Моделі прогнозування часових рядів у Python: що обрати для курсової чи диплому

💡 Усі статті, обговорення, новини про Python — в одному місці. Приєднуйтесь до Python спільноти!

Якщо ви читаєте цю статтю, то скоріш за все вам потрібно зробити проект з прогнозування. Може це курсова з аналізу продажів, може диплом з передбачення курсу криптовалюти, а може магістерська з прогнозування енергоспоживання. Неважливо що саме — підходи будуть схожі, і я хочу розповісти про них простою мовою, без зайвого академічного пафосу.

Я сам пройшов через це ще в університеті, потім допомагав друзям, а зараз це частина моєї роботи. Тому знаю типові помилки і знаю що реально працює.

Спочатку трохи теорії (коротко, обіцяю)

Часовий ряд — це просто послідовність значень у часі. Температура щогодини, ціна акцій щодня, кількість замовлень щомісяця. Здавалось би, що тут складного — візьми попередні значення і екстраполюй. Але ні, все цікавіше.

У будь-якому часовому ряді є кілька компонентів. Тренд — це загальний напрямок руху, вгору чи вниз. Сезонність — регулярні коливання, як продажі морозива влітку. І шум — випадкові коливання, які ніяк не передбачиш.

Задача моделі — розділити ці компоненти і зрозуміти, який тренд і яка сезонність будуть у майбутньому. Шум, зрозуміло, передбачити неможливо, тому ідеального прогнозу не буває.

ARIMA — стара школа, яка все ще тримається

ARIMA розшифровується як AutoRegressive Integrated Moving Average. Звучить страшно, але ідея проста. Модель дивиться на попередні значення ряду і на попередні помилки прогнозу, і на основі цього робить новий прогноз.

Головна перевага ARIMA — вона працює на невеликих даних. Якщо у вас 200-300 спостережень, це ваш вибір. Нейронки на таких обсягах просто не навчаться.

Ось як це виглядає в коді. Спочатку імпортуємо бібліотеки:

import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
from sklearn.metrics import mean_absolute_error
import numpy as np

Завантажуємо дані і ділимо на train/test:

df = pd.read_csv('sales_data.csv', parse_dates=['date'], index_col='date')
train = df[:-30]
test = df[-30:]

Створюємо модель. Параметри (5,1,2) означають: дивимось на 5 попередніх значень, робимо одне диференціювання для видалення тренду, враховуємо 2 попередні помилки:

model = ARIMA(train['sales'], order=(5, 1, 2))
fitted_model = model.fit()
forecast = fitted_model.forecast(steps=30)

Як обрати ці параметри? Можна мучитись з ACF і PACF графіками, а можна просто використати auto_arima з бібліотеки pmdarima — вона сама підбере оптимальні значення:

from pmdarima import auto_arima
auto_model = auto_arima(train['sales'], seasonal=True, m=7, suppress_warnings=True)
print(auto_model.summary())

Чесно кажучи, для курсової цього достатньо. Показали що знаєте ARIMA, підібрали параметри, порахували похибку — викладач задоволений.

Prophet — коли потрібно швидко і красиво

Prophet зробили в Facebook (тепер Meta) для внутрішнього бізнес-прогнозування. Потім викотили у відкритий доступ, і студенти по всьому світу сказали «дякую».

Чому Prophet такий популярний? Бо він вимагає мінімум зусиль. Даєш йому дві колонки — дату і значення — і він сам розбирається з трендом, сезонністю, святами. І ще й графіки малює гарні, які можна прямо в презентацію вставляти.

Код максимально простий:

from prophet import Prophet
import pandas as pd

df = pd.read_csv('sales_data.csv')
df = df.rename(columns={'date': 'ds', 'sales': 'y'})

model = Prophet(yearly_seasonality=True, weekly_seasonality=True)
model.fit(df[:-30])

future = model.make_future_dataframe(periods=30)
forecast = model.predict(future)

fig = model.plot(forecast)

І все! Серйозно, це весь код для базового прогнозу.

Крута фіча Prophet — можна додати свята. Модель зрозуміє, що 8 березня чи Новий рік впливають на продажі:

ua_holidays = pd.DataFrame({
    'holiday': 'ukrainian_holidays',
    'ds': pd.to_datetime(['2025-01-01', '2025-01-07', '2025-03-08', '2025-08-24', '2025-12-25']),
    'lower_window': -1,
    'upper_window': 1
})
model = Prophet(holidays=ua_holidays)

Мінус Prophet — він не дуже любить високочастотні дані. Якщо у вас погодинні або похвилинні спостереження, краще дивитись в бік інших рішень.

XGBoost — коли є купа додаткових факторів

ARIMA і Prophet працюють тільки з самим часовим рядом. А що робити, якщо у вас є додаткова інформація? Наприклад, ви прогнозуєте продажі, і знаєте коли були рекламні кампанії, яка була погода, чи був це вихідний день.

Тут на сцену виходить XGBoost і подібні моделі градієнтного бустингу. Вони не розуміють концепцію «часового ряду» — для них це просто таблиця з колонками. Тому ваша задача — створити правильні ознаки.

Ось типовий набір ознак для часового ряду:

def create_features(df):
    df = df.copy()
    
    # Базові часові ознаки
    df['dayofweek'] = df.index.dayofweek
    df['month'] = df.index.month
    df['is_weekend'] = df['dayofweek'].isin([5, 6]).astype(int)
    
    # Лагові ознаки — попередні значення
    df['lag_1'] = df['sales'].shift(1)
    df['lag_7'] = df['sales'].shift(7)
    df['lag_30'] = df['sales'].shift(30)
    
    # Ковзні середні
    df['rolling_mean_7'] = df['sales'].shift(1).rolling(7).mean()
    df['rolling_mean_30'] = df['sales'].shift(1).rolling(30).mean()
    
    return df

Зверніть увагу на shift(1) перед rolling — це важливо! Без нього ви випадково «підглянете» в майбутнє і отримаєте нереалістично хороші результати на тесті, а потім модель буде працювати погано на реальних даних.

Далі стандартний XGBoost:

from xgboost import XGBRegressor

df = create_features(df)
df = df.dropna()

feature_cols = ['dayofweek', 'month', 'is_weekend', 'lag_1', 'lag_7', 'lag_30', 'rolling_mean_7', 'rolling_mean_30']
X = df[feature_cols]
y = df['sales']

X_train, X_test = X[:-30], X[-30:]
y_train, y_test = y[:-30], y[-30:]

model = XGBRegressor(n_estimators=500, max_depth=6, learning_rate=0.05)
model.fit(X_train, y_train)
predictions = model.predict(X_test)

Бонус XGBoost — можна подивитись важливість ознак. Це дуже корисно для розділу «аналіз результатів» у дипломі:

importance = pd.DataFrame({
    'feature': feature_cols,
    'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
print(importance)

До речі, є ще LightGBM — це по суті швидша версія XGBoost. На великих датасетах працює в рази швидше, а точність часто навіть краща. Синтаксис майже ідентичний:

import lightgbm as lgb
model = lgb.LGBMRegressor(n_estimators=500, max_depth=6, learning_rate=0.05)
model.fit(X_train, y_train)

Якщо ваш датасет більше 50 тисяч рядків — однозначно беріть LightGBM.

Ensemble — комбінуємо моделі для кращого результату

Окремо хочу сказати про ensemble підхід. Ідея проста: кожна модель має свої сильні і слабкі сторони. ARIMA добре ловить тренд, Prophet — сезонність, XGBoost — вплив зовнішніх факторів. Якщо їх скомбінувати, результат часто кращий ніж у будь-якої окремої моделі.

Найпростіший варіант — просто усереднити прогнози:

ensemble_pred = (pred_arima + pred_prophet + pred_xgb) / 3

Трохи розумніший варіант — зважене усереднення. Даємо більшу вагу моделі, яка краще показала себе на валідації:

# Припустимо XGBoost мав найменшу помилку
weights = [0.25, 0.25, 0.5]
ensemble_pred = weights[0]*pred_arima + weights[1]*pred_prophet + weights[2]*pred_xgb

Ще крутіший варіант — stacking. Тренуємо окрему модель, яка вчиться комбінувати прогнози базових моделей:

from sklearn.linear_model import Ridge

stacking_features = pd.DataFrame({
    'arima': pred_arima_validation,
    'prophet': pred_prophet_validation,
    'xgb': pred_xgb_validation
})

meta_model = Ridge()
meta_model.fit(stacking_features, y_validation)

Для магістерської stacking — це майже обов’язкова тема. Комісія любить коли показуєш щось складніше за просте усереднення.

LSTM — нейронки для тих, хто хоче вразити комісію

LSTM (Long Short-Term Memory) — це тип рекурентної нейронної мережі, який вміє запам’ятовувати довгострокові залежності. Звучить круто, і для магістерської це справді може бути плюсом.

Але є нюанси. LSTM потребує багато даних — мінімум кілька тисяч спостережень, краще десятки тисяч. На типових студентських датасетах з 500 рядків LSTM буде працювати гірше за Prophet.

Якщо все ж хочете спробувати, ось базовий приклад:

import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from sklearn.preprocessing import MinMaxScaler

# Нормалізація даних — для нейронок це обов'язково
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(df[['sales']])

# Створюємо послідовності: кожен приклад — це 60 попередніх днів
def create_sequences(data, seq_length):
    X, y = [], []
    for i in range(len(data) - seq_length):
        X.append(data[i:i+seq_length])
        y.append(data[i+seq_length])
    return np.array(X), np.array(y)

X, y = create_sequences(scaled_data, 60)

# Архітектура мережі
model = Sequential([
    LSTM(64, return_sequences=True, input_shape=(60, 1)),
    Dropout(0.2),
    LSTM(32),
    Dropout(0.2),
    Dense(1)
])

model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=100, batch_size=32, validation_split=0.1)

Головне з LSTM — не забудьте зробити inverse_transform для результатів, щоб повернути їх до оригінального масштабу.

Що обрати для вашого проекту?

Якщо коротко:

Курсова робота — Prophet. Швидко, просто, гарні графіки. Додайте порівняння з наївним прогнозом (просто взяти минуле значення) і ARIMA — цього достатньо.

Бакалаврський диплом — Prophet + ARIMA + XGBoost. Покажіть, що вмієте працювати з різними підходами, порівняйте їх на ваших даних.

Магістерська — все вищеперелічене плюс LSTM. Додайте ensemble (комбінацію моделей), аналіз важливості ознак, можливо ablation study.

Як оцінювати якість прогнозу

Це важливо! Багато студентів будують модель, але забувають нормально оцінити результат.

MAE (Mean Absolute Error) — середня абсолютна помилка. Якщо MAE = 100, це означає що в середньому модель помиляється на 100 одиниць. Інтуїтивно зрозуміла метрика.

RMSE (Root Mean Squared Error) — корінь середньоквадратичної помилки. Більше «штрафує» за великі помилки. Якщо є викиди в даних, RMSE буде сильно вищим за MAE.

MAPE (Mean Absolute Percentage Error) — помилка у відсотках. Корисно коли порівнюєте різні датасети або коли значення сильно відрізняються за масштабом.

from sklearn.metrics import mean_absolute_error, mean_squared_error

mae = mean_absolute_error(y_test, predictions)
rmse = np.sqrt(mean_squared_error(y_test, predictions))
mape = np.mean(np.abs((y_test - predictions) / y_test)) * 100

print(f'MAE: {mae:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'MAPE: {mape:.2f}%')

І обов’язково побудуйте графік «прогноз vs реальність» — це перше що дивиться викладач.

import matplotlib.pyplot as plt

plt.figure(figsize=(12, 6))
plt.plot(y_test.index, y_test.values, label='Реальні значення', color='blue')
plt.plot(y_test.index, predictions, label='Прогноз', color='red', linestyle='--')
plt.xlabel('Дата')
plt.ylabel('Значення')
plt.title('Порівняння прогнозу з реальними даними')
plt.legend()
plt.savefig('forecast_comparison.png', dpi=150)
plt.show()

Такий графік одразу показує де модель справляється, а де провалюється. Плюс виглядає професійно в дипломі.

Крос-валідація для часових рядів

Окрема тема — як правильно валідувати модель. Звичайний train_test_split тут не підходить, бо він перемішує дані випадково. А в часових рядах порядок важливий!

Правильний підхід — TimeSeriesSplit. Він створює кілька «вікон» для валідації, кожне наступне більше за попереднє:

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)

scores = []
for train_idx, test_idx in tscv.split(X):
    X_train_cv, X_test_cv = X.iloc[train_idx], X.iloc[test_idx]
    y_train_cv, y_test_cv = y.iloc[train_idx], y.iloc[test_idx]
    
    model = XGBRegressor(n_estimators=100, max_depth=4)
    model.fit(X_train_cv, y_train_cv)
    
    pred = model.predict(X_test_cv)
    score = mean_absolute_error(y_test_cv, pred)
    scores.append(score)

print(f'Середня MAE по фолдах: {np.mean(scores):.2f} +/- {np.std(scores):.2f}')

Якщо стандартне відхилення велике — модель нестабільна, потрібно спрощувати або збирати більше даних.

Робота з пропусками в даних

Реальні дані майже завжди мають пропуски. Сервер впав, датчик зламався, забули записати — причин багато. Як з цим боротися?

Найпростіше — інтерполяція:

df['sales'] = df['sales'].interpolate(method='linear')

Для даних з сезонністю краще працює сезонна інтерполяція:

df['sales'] = df['sales'].interpolate(method='time')

Prophet взагалі сам справляється з пропусками — просто не включайте ці рядки в датафрейм, і він розбереться.

А от для ARIMA пропуски — це проблема. Або заповнюйте їх заздалегідь, або використовуйте спеціальні версії типу ARIMAX.

Типові помилки, яких краще уникати

Data leakage — випадкове використання майбутньої інформації при навчанні. Типовий приклад: забули зробити shift() при створенні ковзного середнього.

Неправильний split — для часових рядів не можна робити випадковий train/test split! Тільки хронологічний: спочатку навчання на старих даних, потім тест на нових.

Занадто оптимістичні результати — якщо ваша модель показує MAPE 0.5%, це підозріло. Перевірте чи немає витоку даних.

Ігнорування baseline — завжди порівнюйте з наївним прогнозом. Якщо ваша складна модель ледве перемагає просте «завтра буде як сьогодні», то щось не так.

На закінчення

Прогнозування — це не магія. Це послідовний процес: зрозуміти дані, обрати підходящу модель, налаштувати її, оцінити результат, покращити. І так по колу.

Не бійтесь експериментувати. Спробуйте кілька моделей, порівняйте їх, зробіть висновки. Саме це хочуть бачити викладачі — не ідеальний результат, а розуміння процесу.

Успіхів з вашим проектом!

---

Якщо стаття була корисною і ви хочете заглибитись в тему — пишіть в коментарях, розкажу більше про конкретні аспекти.

👍ПодобаєтьсяСподобалось8
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Для бакалаврської чи магістерської зараз навряд вистачить просто прогнозу на датасеті класичними ML-моделями чи LSTM, чи я помиляюся? Ще варті уваги трансформери, pre-trained моделі, не забути про fine-tuning. Можна піти в сторону MLOps, показати якусь інтеграцію у продакшн. Або продемонструвати, що це готовий продукт, він має бізнес-цінність і реально може використовуватися :)

Для бакалаврської чи магістерської зараз навряд вистачит

Вистачить з головою.

У всіх цих підходах ми мовчазно вважаємо, що майбутнє буде схожим на минуле. Але хіба це завжди виконується?
ІМХО, щоб більш-менш прогнозувати майбутнє, треба дивитися в бік каузальних моделей (типу Bayesian Belief Networks, тощо).

Дякую але треба брати до уваги стаціонарність ряду.

Чи пробували передбачати курси акцій, крипти і т.д.?

По акціям є готовий софт, ринок передбачуваний. По крипті є також готовий софт, але доларів 500 втратив, ринок непередбачуваний, можливо, треба щось поміняти у подходах.

Оце ви дуже топову тему створили, дякую, якраз розбираюсь із цим в роботі.
Тема дуже актуальна. Буду вдячний, якщо напишете щось ще по цьому.

Із того, що не згадано в статті але обіцяє бути дуже багатообіцяючим це Time Series Foundation Models. Великі моделі які вже натреновано, але які ви можете донавчити під свої дані, або й використовувати як є.

Обов’язково ознайомлюсь, я так сказати в самому початку шляху.

Підписатись на коментарі