Зображення, згенероване за допомогою ШІ, на тему XGBoost
Однією з найбільш поширених проблем у роздрібній торгівлі сьогодні є обробка задач часового ряду. Ці задачі стосуються прогнозування та передбачення майбутніх продажів на основі детального аналізу та інтерпретації патернів, що виникають із історичних даних про продажі. Рітейлерам потрібно використовувати таку інформацію для прийняття кращих рішень щодо управління запасами, маркетингових стратегій і бізнес-планування загалом. Ефективне вирішення таких проблем з часом допомагає компаніям передбачати тенденції на ринку, реагувати на зміни у поведінці споживачів та зберігати конкурентну перевагу в постійно змінюваному ринку.
У сьогоднішньому блозі ми прагнемо спростити процес побудови моделі прогнозування, використовуючи дані часового ряду, за допомогою XGBoost — потужного алгоритму машинного навчання, який добре зарекомендував себе у різних завданнях передбачення, включаючи прогнозування часового ряду. Давайте ближче подивимося, як розробити модель прогнозування за допомогою цих даних.
Інформація про набір даних
Набір даних містить щоденні продажі продуктів у 136 різних магазинах з 2022-05-08 до 2024-05-31, тобто близько 2 років даних. Вхідні дані складаються з близько 100 тис. рядків і наступних колонок:
date
: Дата продажу (у форматі DDMONYYYY, наприклад, 01JAN2023).store_id
: Унікальний ідентифікатор для кожного магазину.product_id
: Унікальний ідентифікатор для кожного продукту.units_sold
: Кількість проданих одиниць.promoYorN
: Чи була активна акція в цей день (1 — так, 0 — ні).value_sales
: Вартість продажів у цей день.
Нижче наводиться перші 10 рядків вхідних даних для кращого розуміння:
1. Попередня обробка даних
Ми детально проаналізуємо дані, щоб виявити патерни, яких вони дотримуються, і перевіримо наявність аномальних значень, які можуть вказувати на помилки, наприклад, неправильні продажі для конкретних дат. Давайте побудуємо графік тренду для загальних продажів продукту в магазині.
Як видно, ми маємо дуже високі показники продажів в листопаді 2022 року, тому проведемо виявлення та видалення аномальних значень.
Що таке виявлення та видалення аномальних значень?
Виявлення та видалення аномальних значень є важливим етапом попередньої обробки як для аналізу часового ряду, так і для інших застосувань машинного навчання. Аномальне значення — це точка даних, що суттєво відрізняється від решти набору даних, що може спотворити аналіз і зробити модель некоректною. Аномалії в даних часового ряду можуть виникнути через помилки введення даних, несподівані події на ринку або аномалії в поведінці споживачів. Виявлення та корекція таких аномалій забезпечують цілісність і надійність моделей прогнозування. Існує кілька способів видалення таких точок даних, наприклад: IQR (міжквартильний діапазон), MAD (медіанна абсолютна девіація), Z-score або ж ви можете просто видалити їх вручну. Для нашого випадку ми використовуємо метод медіанної абсолютної девіації (MAD).
def handle_outliers(data, column, threshold=3.5):
median = data[column].median()
mad = np.median(np.abs(data[column] - median))
lower_bound = median - threshold * mad
upper_bound = median + threshold * mad
data[column] = np.where(data[column] < lower_bound, lower_bound, data[column])
data[column] = np.where(data[column] > upper_bound, upper_bound, data[column])
return data
Після застосування цього методу давайте побудуємо графік знову.
Тепер ви можете побачити, що видалення аномальних значень успішно завершено, і значення знаходяться в очікуваному діапазоні. Це покращить точність прогнозування моделі.
2. Обробка відсутніх даних
Однією з найбільш поширених проблем у будь-якому проекті аналізу даних і машинного навчання є відсутність даних.
Правильне управління відсутніми даними може забезпечити точність, надійність та стабільність ваших моделей. Це всеосяжний посібник, який охоплює різні типи відсутніх даних, їх можливі причини, методи їх управління та кращі практики, що застосовуються під час Попереднього аналізу даних (EDA) / Попередньої обробки даних.
Стратегії множинної імпутації:
- Заповнення NA: Застосовує значення за замовчуванням, використовуючи статистичні методи (середнє, медіана, мода).
- Логіка імпутації: Відсутні записи були систематично виявлені та оброблені. Наприклад, ми використали
SimpleImputer(strategy='mean')
для заміни відсутніх значень на середнє значення для кожної колонки, забезпечуючи узгодженість даних і мінімізуючи упередження моделі.
Ми використовуємо Логіку імпутації для заповнення відсутніх значень за допомогою стратегії середнього значення.
from sklearn.impute import SimpleImputer
def handle_missing_data(df):
df['date'] = pd.to_datetime(df['date'])
# Визначаємо необхідний діапазон дат
start_date = pd.to_datetime('2022-05-08')
end_date = pd.to_datetime('2024-05-31')
# Створюємо повний діапазон дат
all_dates = pd.date_range(start=start_date, end=end_date, freq='D')
# Отримуємо унікальні комбінації product_id та store_id
unique_combinations = df[['product_id', 'store_id']].drop_duplicates()
# Створюємо новий DataFrame з усіма комбінаціями та датами
all_data = pd.DataFrame(
index=pd.MultiIndex.from_product(
[unique_combinations['product_id'], unique_combinations['store_id'], all_dates],
names=['product_id', 'store_id', 'date']
)
).reset_index()
df = pd.merge(all_data, df, on=['product_id', 'store_id', 'date'], how='left')
df.loc[df.units_sold.isnull(), 'promoYorN'] = 0
# Імпутуємо відсутні значення за допомогою SimpleImputer
imputer = SimpleImputer(strategy='mean')
df['units_sold'] = imputer.fit_transform(df[['units_sold']])
# Видаляємо дублікати
df = df.drop_duplicates(subset=['product_id', 'store_id', 'date'], keep='first')
return df
3. 𝗙𝗲𝗮𝘁𝘂𝗿𝗲 𝗘𝗻𝗴𝗶𝗻𝗲𝗲𝗿𝗶𝗻𝗴
Інженерія ознак (Feature Engineering) є важливою в моделюванні часового ряду, оскільки вона включає вибір і трансформацію сирих даних у значущі ознаки для можливого покращення точності прогностичних статистичних моделей. У цьому контексті значення інженерії ознак неможливо переоцінити.
Завдяки ретельному вибору та налаштуванню релевантних ознак статистичні моделі отримують можливість краще виявляти та фіксувати підлягаючі взаємозв'язки і патерни в даних, що суттєво покращує можливості прогнозування. Крім того, інженерія ознак дозволяє вбудовувати знання галузі та інтуїтивні оцінки, щоб модель могла використовувати людську інтуїцію для максимізації своєї ефективності. Нижче наведені кілька ознак, які ми вибрали для нашого набору даних.
3.1 Ознаки свят
Щоб врахувати вплив державних та національних свят на дані, ми включили спеціальний набір даних про свята. Це включає бінарні індикатори, такі як isStateHoliday
, що позначає, чи є конкретний день державним святом. Таким чином, модель може вчитися і коригувати варіації в поведінці чи патернах продажів, що зазвичай відбуваються під час свят.
# Додаємо флаги свят і подій (Свята США на 2022, 2023, 2024 роки)
holidays = [
'2022-01-01', '2022-01-17', '2022-02-21', '2022-05-30', '2022-07-04', '2022-09-05', '2022-11-11', '2022-11-24', '2022-12-25',
'2023-01-01', '2023-01-16', '2023-02-20', '2023-05-29', '2023-07-04', '2023-09-04', '2023-11-10', '2023-11-23', '2023-12-25',
'2024-01-01', '2024-01-15', '2024-02-19', '2024-05-27', '2024-07-04', '2024-09-02', '2024-11-11', '2024-11-28', '2024-12-25'
]
df['is_holiday'] = df['DIM2_ELEM_ID'].isin(pd.to_datetime(holidays)).astype(int)
3.2. Ознаки, що базуються на часі
Ознаки, що базуються на часі, такі як день тижня, місяць року, сезонність та інші часові патерни можуть бути цінними для прогнозування.
Наприклад, якщо певні продукти мають вищий середній рівень продажів на вихідних, включення дня тижня як ознаки може покращити точність прогностичної моделі.
# Перетворення дати в datetime і виділення ознак
df['date'] = pd.to_datetime(df['date'],format="%d-%b-%Y")
df['day_of_week'] = df['date'].dt.dayofweek
df['month'] = df['date'].dt.month
df['year'] = df['date'].dt.year
df['week_of_year'] = df['date'].dt.isocalendar().week
df['day_of_month'] = df['date'].dt.day
df['is_weekend'] = df['day_of_week'].apply(lambda x: 1 if x >= 5 else 0)
df['quarter'] = df['date'].dt.quarter
3.3 Ознаки затримки та ковзні вікна
Щоб зафіксувати тимчасові залежності та тренди з часом, ми додали ознаки затримки, такі як Lag7, _Lag14_ і Lag21_. Ці ознаки представляють значення через 7, 14 і 21 день відповідно. Включення таких затримок дозволяє моделі отримувати інформацію про недавню історичну поведінку, що є критично важливим для точних прогнозів на основі попередніх трендів.
df = df.sort_values(by=['store_id', 'product_id', 'time'])
df['lag_7_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].shift(7)
df['lag_14_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].shift(14)
df['lag_21_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].shift(21)
3.4 Статистика ковзного вікна
Цей процес передбачає підсумовування даних часового ряду в рамках ковзного вікна. Це дозволяє зменшити шум і підкреслити основні тренди. Використання ковзних вікон може виявити патерни, які можуть бути прихованими в необроблених даних.
# Генерація ковзних середніх та статистики
df['rolling_7_mean_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].transform(lambda x: x.rolling(window=7, min_periods=1).mean())
df['rolling_14_mean_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].transform(lambda x: x.rolling(window=14, min_periods=1).mean())
3.5 Ознаки, специфічні для магазину (Планується для майбутніх версій)
Хоча ці ознаки ще не реалізовані в поточній версії, ми визнаємо їхню важливість для покращення ефективності моделі. Серед цих запланованих ознак:
Дані про погоду: Ця ознака допоможе пояснити коливання в продажах або поведінці користувачів, що обумовлені зовнішніми факторами.
Місцеві події: Це можуть бути такі події, як Чорна п’ятниця, фестивалі в селищі або громадські заходи, які також впливають на патерни даних.
Інформація про конкурентів: Дані про акції або діяльність конкурентів можуть допомогти пояснити зміни на ринку.
Включивши ці ознаки в майбутніх версіях, ми зможемо отримати більш повну картину факторів, що впливають на наші дані, тим самим покращуючи прогностичні можливості моделі.
4.6 Специфічні ознаки для даних
Як ви могли помітити на початку, наші вхідні дані містять колонку promoYorN. Вона вказує на те, чи був продукт в акції в цей день, що значно впливає на його продажі. Ми включимо це як одну з ознак у нашу модель.
Навчання моделі за допомогою вищеописаних ознак
Тепер настав час тренувати модель і перевірити її ефективність за допомогою різних параметрів, таких як RMSE, MAE тощо.
Ми розділимо дані на навчальну та тестову вибірки. Навчальна вибірка буде використовуватися для побудови та тренування моделі, в той час як тестова вибірка служитиме для генерації прогнозів та оцінки ефективності моделі після її створення. Такий підхід забезпечить правильне навчання моделі та точну оцінку її ефективності.
Ми будемо використовувати метод traintestsplit для XGBRegressor.
df['product_id'] = df['product_id'].astype('int64')
df['store_id'] = df['store_id'].astype('int64')
df['units_sold'] = df['units_sold'].astype('int64')
df['promoYorN'] = df['promoYorN'].astype('int64')
df = handle_outliers(df, 'units_sold')
df = handle_missing_data(df)
# Перетворення дати в datetime і виділення ознак
df['date'] = pd.to_datetime(df['date'],format="%d-%b-%Y")
df['day_of_week'] = df['date'].dt.dayofweek
df['month'] = df['date'].dt.month
df['year'] = df['date'].dt.year
df['week_of_year'] = df['date'].dt.isocalendar().week
df['day_of_month'] = df['date'].dt.day
df['is_weekend'] = df['day_of_week'].apply(lambda x: 1 if x >= 5 else 0)
df['quarter'] = df['date'].dt.quarter
# Кодування категоріальних ознак
label_encoder = LabelEncoder()
df['promoYorN_rolling'] = label_encoder.fit_transform(df['promoYorN'])
# Генерація ознак затримки, згрупованих за store_id та product_id
df = df.sort_values(by=['store_id', 'product_id', 'date'])
df['lag_7_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].shift(7)
df['lag_14_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].shift(14)
df['lag_21_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].shift(21)
# Генерація ковзних середніх та статистики
df['rolling_7_mean_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].transform(lambda x: x.rolling(window=7, min_periods=1).mean())
df['rolling_14_mean_units_sold'] = df.groupby(['store_id', 'product_id'])['units_sold'].transform(lambda x: x.rolling(window=14, min_periods=1).mean())
# Заповнення пропущених значень
df.fillna(0, inplace=True)
X = df.drop(columns=['units_sold', 'date','product_id'])
y = df['units_sold']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
xgb_model = xgb.XGBRegressor(
subsample=0.8,
n_estimators=400,
max_depth=10,
learning_rate=0.05,
gamma=0,
colsample_bytree=0.8,
random_state=42
)
xgb_model.fit(X_train, y_train)
Після цього ви отримаєте свою модель і тепер можете прогнозувати майбутні продажі. Але зачекайте, давайте перевіримо, наскільки добре працює наша модель і які ознаки вона найбільше враховує під час навчання. Спочатку протестуємо точність моделі за допомогою RMSE та MAE. Значення RMSE: 7.6798 MAE: 5.4541 — це в межах прийнятного діапазону для нашого випадку.
y_pred = xgb_model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
mae = mean_absolute_error(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")
print(f"MAE: {mae:.4f}")
Тепер давайте побудуємо графік важливості ознак і краще зрозуміємо, що модель вважає найважливішим.
fi = pd.DataFrame(data=xgb_model.feature_importances_, index=xgb_model.feature_names_in_, columns=['importance'])
fi.sort_values('importance').plot(kind='barh', title='Feature Importance')
import matplotlib.pyplot as plt
plt.show()
Графік важливості ознак
Модель надає найбільшу важливість ознакам isweekend_ та rolling7meanunitsold. Тепер давайте розглянемо передбачувані значення для майбутніх дат.
При побудові графіка тренду прогнозованих продажів за останні 4 місяці ми бачимо прогнози моделі на тестових даних, порівнюючи прогнозовані значення продажів з фактичними значеннями. Як показано на графіку, прогнозовані продажі для тестових даних точно збігаються з кривою фактичних значень продажів, що показує, як точно модель навчилася на тренувальних даних.
Висновок:
Ми візуалізуємо ефективність моделі на тестових даних, порівнюючи прогнозовані продажі з фактичними значеннями продажів. Графік показує, що прогнозовані продажі точно збігаються з фактичними продажами, що вказує на ефективність моделі в навчанні на тренувальних даних.
Мінімальні відхилення між прогнозованими та фактичними значеннями свідчать про те, що модель ефективно навчилася на тренувальних даних, успішно виявляючи та відтворюючи ключові фактори, які впливають на результативність продажів.
Дякую за увагу 🙂
Перекладено з: Leveraging XGBoost for Accurate Retail Time Series Predictions