Несбывшиеся прогнозы
Рассмотрим кейс работы модели предсказания потребления в условиях имеющихся резервов. Такая ситуация может возникнуть, например, при разработке оптимальной закупочной стратегии обеспечения филиалов крупной организации необходимым инвентарем.
Предположим, что работающая модель машинного обучения у нас уже имеется, основной задачей является учет резервов и их корректировка после очередной поставки. Создадим демонстрационный набор прогнозов (pred_ser), определяемый парой букв (фактически идентификаторы филиалов):
import pandas as pd import numpy as np import string np.random.seed(1) letters = np.array(list(string.ascii_uppercase))
ids = pd.DataFrame(np.concatenate([np.random.choice(letters, size=10).reshape(-1,1), np.random.choice(letters, size=10).reshape(-1,1)], axis=1)) pred_df = pd.concat([ids, pd.Series(np.random.randint(0,100, size=10), name='pred')], axis=1) pred_df
также зададим резервы (stock_ser) из части индексов прогнозных значений и новых:
stock_ser = pd.Series(np.random.randint(100, size=5), index=pd.MultiIndex.from_tuples(pred_df[[0,1]].sample(5).apply(lambda x: tuple(x), axis=1))) stock_ser = pd.concat([stock_ser,pd.Series([11], index=[('L','M')])]) stock_ser = pd.concat([stock_ser,pd.Series([13], index=[('A','A')])]) stock_ser
Логика работы реализована в двух функциях refresh_preds_stocks и add_stocks, которые отвечают за обновление прогнозов с учетом остатков и корректировку резервов с учетом их вычета из прогнозов:
def add_stocks(stock_ser, add_ser): combo_index = stock_ser.index.union(add_ser.index) stock_update = stock_ser.reindex(combo_index, fill_value=0) + add_ser.reindex(combo_index, fill_value=0) stock_update = stock_update.map(lambda x: x if x>0 else None) stock_update.dropna(inplace=True) return stock_update def refresh_preds_stocks(pred_col, stock_col): pred_ser = pred_col.copy() stock_ser = stock_col.copy() stock_ser.index.set_names(pred_ser.index.names, inplace=True) update_stock = add_stocks(stock_ser,-pred_ser) pred_sub_stock_ser = (pred_ser - stock_ser).reindex(pred_ser.index) pred_ser.mask(pred_sub_stock_ser.notnull(),pred_sub_stock_ser, inplace=True ) pred_ser = pred_ser.map(lambda x: max(x,0)) return pred_ser, update_stock
В add_stocks мы добавляем к резервам столбец и возвращаем обновленные результаты. Успешная реализация требует аккуратной переиндексации столбцов для учета отсутствующих в обоюдных списках индексов (с заменой пустых значений на 0) и удаления значений меньше или равных нулю. В refresh_preds_stocks логика схожая только обновление прогнозов происходит путем замены только тех значений, в которых присутствует остаток (путем их вычета).
Получим обновленные прогнозы и сравним с первоначальными:
pred_new, stock_new = refresh_preds_stocks(pred_ser, stock_ser) pred_df.merge(pred_new.reset_index().rename(columns={'pred':'pred_new'}))
Функционал add_stocks намерено не включен в состав refresh_preds_stocks, так как он может понадобиться в последующем. Допустим, если поставщики товара в филиал округляют порции поставок до неких значений (например, иначе не продаются или штучно не включаются в упаковку) и остатки нужно корректировать на величину этих "добавок" (фактически разность между округленными и первоначальными прогнозами). Условно добавим к остаткам прогнозы и посмотрим сработает ли наша функция корректно:
add_stocks(stock_new, pred_ser)