Все на себе испытали, насколько важно точно прогнозировать спрос. Представьте себе: вы идете в местный продуктовый магазин, чтобы купить яблоки. Как узнать, сколько яблок купить?

Не задумываясь, вы, скорее всего, просто покупаете такое же количество яблок, которое съели за неделю до этого. Или вы быстро посчитаете в уме и подсчитаете среднее количество яблок, которые вы ели каждую неделю в течение последних нескольких недель, и купите это количество.

В бизнесе создание точного прогноза важнее, чем выбор фруктов в продуктовом магазине. Способность прогнозировать спрос на продажи имеет решающее значение для эффективной работы бизнеса.

Давайте рассмотрим сценарий для дистрибьютора, продающего персональную электронику. Если у них слишком много запасов, они вложили капитал в товары, которые могли бы потратить на другие способы, чтобы помочь бизнесу (например, на рекламу или найм дополнительного персонала). Если у них недостаточно запасов, продажи сокращаются, поскольку клиенты не могут сразу получить свой продукт.

Давайте подробнее рассмотрим некоторые реальные данные от дистрибьютора магазина электроники и посмотрим, как мы можем создать модель машинного обучения для прогнозирования потребительского спроса.

Источник необработанных данных

Вот источник данных, который мы будем использовать для этого проекта:

https://www.kaggle.com/datasets/mkechinov/ecommerce-purchase-history-from-electronics-store?select=kz.csv

Вы захотите загрузить этот CSV-файл в свой локальный рабочий каталог. Не пытайтесь открыть его! Он содержит слишком много данных, чтобы Excel мог их отобразить.

Используя Jupyter Notebook и Python, давайте посмотрим, сколько строк в этом наборе:

import pandas as pd

sales = pd.read_csv('kz.csv')

rows_of_data = len(sales.index)
print("There are " + str(rows_of_data) + " rows of data.")
This data set contains transaction data for large home appliances and personal electronics product sales between April 2020 through November 2020. 

There are 2633521 rows of data. Да, почти 3 миллиона строк! Максимальное количество строк, которые может загрузить Excel, составляет чуть более 1 миллиона. Даже если бы мы были ниже порога Excel, производительность была бы низкой, а анализ воспроизводимых данных производительности занял бы вечность.

Немного предыстории этих данных. Этот набор содержит данные о транзакциях по продаже крупной бытовой техники и личной электроники в период с апреля 2020 года по ноябрь 2020 года.

Каждая строка в наборе данных представляет собой «событие». Это означает, что одной и той же транзакции продаж может соответствовать несколько строк.

Подведение итогов данных и очистки

Посмотрим, какие столбцы входят в этот набор:

for col_name in sales.columns:
    print(col_name)

sales.head()

Это возвращает следующие имена столбцов: event_time, order_id, product_id, category_id, category_code, brand, price, user_id

Еще кое-что, что было бы полезно знать, это относительное количество уникальных идентификаторов заказов по сравнению с уникальными пользователями:

unique_orders = len(pd.unique(sales['order_id']))
unique_users = len(pd.unique(sales['user_id']))

print("There are " + str(unique_orders) + " unique order IDs.")
print("There are " + str(unique_users) + " unique user IDs.")

There are 1435266 unique order IDs и There are 98263 unique user IDs.

Итак, из 2,6 млн продаж приходится 1,4 млн уникальных заказов и чуть менее 0,1 млн уникальных пользователей. Это говорит нам о том, что есть много повторных покупателей.

Чтобы очистить набор данных, давайте отбросим все строки из sales DataFrame, которые содержат пропущенные значения. Давайте также создадим 2 новых столбца, один для event_date и один для event_time.

sales.dropna(inplace=True)
sales.rename(columns={'event_time': 'event_datetime'}, inplace=True)

event_date = pd.to_datetime(sales['event_datetime']).dt.date
event_time = pd.to_datetime(sales['event_datetime']).dt.time

sales.insert(1, 'event_date', event_date, allow_duplicates=True)
sales.insert(2, 'event_time', event_time, allow_duplicates=True)

sales['event_date'] = pd.to_datetime(sales['event_date'])

Давайте проверим и посмотрим, сколько строк мы удалили в ходе этого процесса:

cleaned_total_orders = len(sales.order_id)
print("After cleaning the data, there are now " + str(cleaned_total_orders) + \
      " total orders.")

percent_decrease = 100 * (1 - (cleaned_total_orders/total_orders))
percent_formatted = "{0:.2f}".format(percent_decrease)
print("This is a " + str(percent_formatted) + "% decrease in dataset size.")

After cleaning the data, there are now 420718 total orders.
This is a 84.02% decrease in dataset size.

Отличный! У нас все еще есть много точек данных, чтобы выполнить реальный анализ данных и сделать полезные прогнозы. Теперь самое интересное… визуализация данных.

Исследовательский анализ данных

Во-первых, давайте отобразим некоторую описательную статистику и создадим несколько групп, чтобы увидеть распределение количества проданных товаров по цене.

import seaborn as sns
from matplotlib import pyplot as plt
%matplotlib inline

sales['price'].describe().apply("{0:.0f}".format)

bins = [0, 75, 200, 500, 12000]
price_labels = ['0-75', '76-200', '201-500', '501+']
sales['price_bin'] = pd.cut(sales['price'], bins=bins, labels=price_labels, \
                            include_lowest=True)

bar_plot = sns.countplot(data=sales, x=sales['price_bin']);
bar_plot.set(xlabel='Price Bins', ylabel='Count', title='Count of Sold Items within Price Range');

count 420718, mean 254, std 321, min 0, 25% 39, 50% 139, 75% 347, max 11574, Name: price, dtype: object

На графике выше видно, что большинство продуктов (по подсчету) продано менее чем за 75 долларов (около 150 000 транзакций). Тем не менее, есть также много крупных транзакций (около 60 000 транзакций на сумму более 500 долларов США). Давайте углубимся еще немного (сосредоточившись на продажах менее 2000 долларов):

fig, ax = plt.subplots()
sales['price'] = sales[sales['price'] < 2000]['price']
plt.hist(sales['price'], bins=20)

# Labels
ax.set(xlabel='Price Bins USD', ylabel='Count', title='Count of Sold Items within Price Range')

# The vertial "mean" line
ax.axvline(x=sales['price'].mean(), color='red', label='Mean', \
              linestyle='--', linewidth=2)

# Legend
plt.legend(bbox_to_anchor=(1, 1), loc='upper left')

plt.show()

Давайте посмотрим, как дела обстоят с течением времени. Для этого мы построим общий объем продаж с течением времени:

_2020_df = sales[sales['event_date'] >= '2020/01/01']
sales.sort_values(by = ['event_date'], ascending=True, inplace=True)

# Error handling
pd.options.mode.chained_assignment = None

_2020_df['month'] = _2020_df['event_date'].dt.month
import calendar
_2020_df['month'] = _2020_df['month'].apply(lambda x: calendar.month_abbr[x])

grouped_by_month_df = _2020_df.groupby('month')['price'].sum()
months_in_order = ['Jan','Feb','Mar', 'Apr', 'May', 'Jun', \
                   'Jul', 'Aug', 'Sep', 'Oct', 'Nov']
df_sorted = grouped_by_month_df.reindex(months_in_order, axis=0)

f = plt.figure()
plt.ticklabel_format(style = 'plain')
plt.xlabel('Months')
plt.ylabel('Total Sales per Month [$]')
plt.title('Sales [$] per Month')
df_sorted.plot(kind='bar', ax=f.gca());
plt.show();

Похоже, бизнес идет хорошо! Продажи во втором полугодии явно превысили продажи в первом полугодии. Давайте углубимся еще немного и посмотрим на ежедневные продажи за тот же период времени. Для этого создадим линейный график:

import matplotlib.dates as mdates
import datetime as dt     

grouped_by_date_df = _2020_df.groupby([_2020_df['event_date']])['price'].sum()

#f = plt.figure()
plt.ticklabel_format(style = 'plain')
ax = grouped_by_date_df.plot(kind='line', x='event_date', y='price');
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
plt.gcf().autofmt_xdate()

plt.xlabel('Date')
plt.ylabel('Total Sales per Day [$]')
plt.title('Sales [$] per Day')

plt.show();

Глядя на линейный график, мы видим массу волатильности во второй половине года. Некоторые дни имеют высокий спрос, а другие имеют относительно низкий спрос. Это может создать серьезную нагрузку на специалистов по планированию спроса и цепочки поставок.

Чтобы помочь этим специалистам по планированию цепочки поставок, давайте создадим модель, которая будет прогнозировать спрос на следующий день на основе прошлых продаж.

Модель машинного обучения (прогноз продаж)

В нашей модели мы попытаемся использовать данные о продажах за предыдущие 3 дня, чтобы спрогнозировать продажи на следующий день. Во-первых, нам нужно создать новые кадры данных для представления исторических данных о продажах:

reset_df = grouped_by_date_df.reset_index()
reset_df['one_day_back'] = reset_df['price'].shift(+1)
reset_df['two_days_back'] = reset_df['price'].shift(+2)
reset_df['three_days_back'] = reset_df['price'].shift(+3)
reset_df = reset_df.dropna()

from sklearn.linear_model import LinearRegression
x1, x2, x3, y = reset_df['one_day_back'], reset_df['two_days_back'], reset_df['three_days_back'], reset_df['price']
x1, x2, x3, y = np.array(x1), np.array(x2), np.array(x3), np.array(y)
x1, x2, x3, y = x1.reshape(-1,1), x2.reshape(-1,1), x3.reshape(-1,1), y.reshape(-1,1)
final_x = np.concatenate((x1, x2, x3), axis=1)
# 'final_x' is your array of input variables
# There are 321 rows of data

X_train, X_test, y_train, y_test = final_x[:256], final_x[257:], y[:256], y[257:]
model = LinearRegression().fit(final_x, y)

Здесь мы использовали библиотеку sklearn для создания линейной регрессии, которая будет предсказывать будущие продажи.

Вот и все! Теперь давайте оценим, насколько хороша наша модель:

r_sq = model.score(final_x, y)
r_sq_rounded = round(r_sq, 2)
coef = np.round(model.coef_, 3)

print('The R^2 coefficient is ' + str(r_sq_rounded))
print('The model coefficient for x1 is ' + str(coef[0,0]))
print('The model coefficient for x2 is ' + str(coef[0,1]))
print('The model coefficient for x3 is ' + str(coef[0,2]))

The R² coefficient is 0.74
The model coefficient for x1 is 0.59
The model coefficient for x2 is 0.096
The model coefficient for x3 is 0.221

Эти результаты говорят нам о том, что 74% изменчивости выпуска (продажи за текущий день) объясняются нашими независимыми переменными (данными о продажах за последние 3 дня). Коэффициенты модели показывают, что продажи предыдущего дня лучше всего предсказывают продажи текущего дня.

Проще говоря, мы можем точно предсказать сегодняшние продажи, если знаем продажи за последние 3 дня.

Давайте посмотрим, насколько хороша наша модель для данных о продажах, начиная с 17 сентября:

pred = model.predict(X_test)

plt.plot(pred, label='Prediction')
plt.plot(y_test, label='Actual Sales')

# Formatting
plt.ticklabel_format(style = 'plain')

# Labels and stuff
plt.xlabel('Days past Sept 17th')
plt.ylabel('Sales per Day [$]')
plt.title('Sales [$] per Day')
plt.legend(loc="upper left")

plt.show();

Что хорошо в приведенном выше графике прогноза, так это то, что кривая «прогноза», по-видимому, реагирует на увеличение/уменьшение объема продаж. Хотя на кривой фактического объема продаж имеется много резких скачков, прогнозная кривая хорошо отслеживает фактический объем продаж.

Выводы и следующие шаги

Вот и все! Если вы хотите пойти дальше, вы можете создать модель для определенных категорий продуктов. Например:

grouped_by_cat_df = _2020_df.groupby('category_code')['price'].sum().reset_index()
grouped_by_cat_df = grouped_by_cat_df.sort_values(by=['price'], ascending=False)

f = plt.figure()
plt.ticklabel_format(style = 'plain')
plt.xlabel('Total Sales [$] per Category')
plt.ylabel('Categories')
plt.title('Sales [$] per Category')

#x = topsklearncategories['category_code']
#y = topsklearncategories['price']

x = grouped_by_cat_df['category_code'].iloc[0:10]
y = grouped_by_cat_df['price'].iloc[0:10]

plt.barh(x, y)
plt.xticks(rotation=90)

plt.show();

Наиболее выгодной моделью прогнозирования будет продажа смартфонов и ноутбуков. Вот некоторые другие вопросы, которые стоит изучить и изучить:

  • Какова прибыль от каждой продажи?
  • Что изменилось в бизнесе (дополнительный маркетинг, новый бизнес B2B и т. д.), что привело к скачку продаж в мае?
  • Дистрибьютору следует подумать о том, чтобы пойти ва-банк на продажи смартфонов и ноутбуков. Может ли компания сделать эти продукты большей частью своего бизнеса?

Если у вас есть какие-либо вопросы, вы можете связаться со мной по адресу [email protected]. Свяжитесь со мной в LinkedIn и ознакомьтесь с моим репозиторием GitHub для этого урока!