Applicazioni pratiche di machine learning/Previsione di vendite future

Indice del libro

ESEMPIO 1 modifica

Caricamento librerie modifica

import pandas as pd 
from prophet import Prophet
import matplotlib.pyplot as plt

Parte 1 : Dati e obiettivo modifica

Nel dataset train.csv scaricabile da qui : https://www.kaggle.com/competitions/demand-forecasting-kernels-only/data si hanno 5 anni di dati sulle vendite di 50 articoli in 10 negozi diversi e si vogliono prevedere 3 mesi di vendite future per ciascuno dei 50 articoli nei diversi negozi, utilizzando la libreria Prophet di Facebook.

I dati a disposizione sono :

  • date - Data della vendita.
  • store - ID Negozio
  • item - ID Articolo
  • sales - Numero di articoli venduti in quel particolare negozio e in quella particolare data.

Il dataset test.csv dovrà essere riempito con le previsioni di vendite dei 3 mesi successivi.

Caricamento dati:

train= pd.read_csv('train.csv')
test=pd.read_csv('test.csv')
train.head()
date 			store 	item 	sales
0 	2013-01-01 	1 	1 	13
1 	2013-01-02 	1 	1 	11
2 	2013-01-03 	1 	1 	14
3 	2013-01-04 	1 	1 	13
4 	2013-01-05 	1 	1 	10


Parte 2 : Esplorazione dati modifica

train['date'] = pd.to_datetime(train['date'])

# Aggiunta delle colonne 'month' e 'year'
train['month'] = train['date'].dt.month
train['year'] = train['date'].dt.year

# Stampa del range delle date presenti nel dataset
print(train['date'].min(), train['date'].max())
2013-01-01 2017-12-31 

# Iterazione su tutti i negozi presenti nel dataset per visualizzare in ciascuno gli ID degli articoli:
for s in train['store'].unique():
    df = train.loc[train['store'] == s]
    print(f'store = {s}')
    print(df['item'].unique())
store = 1
[ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
49 50]
store = 2
[ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
49 50]
... ...
store = 9
[ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
49 50]
store = 10
[ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
49 50]

L'articolo più venduto è l'ID 15 nel negozio 2 infatti :

# Raggruppamento delle vendite per negozio, prodotto, anno e mese dal più venduto in poi
train.groupby(['store', 'item', 'year', 'month'])['sales'].sum().reset_index().sort_values(by='sales', ascending=False)
	store 	item 	year 	month 	sales
3894 	2 	15 	2017 	7 	5109
4074 	2 	18 	2017 	7 	5014
4674 	2 	28 	2017 	7 	4999
4662 	2 	28 	2016 	7 	4944
3774 	2 	13 	2017 	7 	4903
... 	... 	... 	... 	... 	...
18251 	7 	5 	2013 	12 	239
12240 	5 	5 	2013 	1 	239
18241 	7 	5 	2013 	2 	236
15240 	6 	5 	2013 	1 	234
18240 	7 	5 	2013 	1 	211

Il negozio con più vendite è il 2 infatti :

# Somma delle vendite per negozio da quello con più vendite
train.groupby('store')['sales'].sum().reset_index().sort_values(by='sales', ascending=False)
	store 	sales
1 	2 	6120128
7 	8 	5856169
2 	3 	5435144
9 	10 	5360158
8 	9 	5025976
3 	4 	5012639
0 	1 	4315603
4 	5 	3631016
5 	6 	3627670
6 	7 	3320009


Parte 3 : Modellizzazione e Previsione modifica

# Aggiunta di una colonna 'ds' contenente le date delle vendite nel formato corretto
train['ds'] = pd.to_datetime(train['date'], format='%Y-%m-%d')

# Creazione di una funzione per prevedere le vendite per un determinato prodotto in un determinato negozio
def predict_sales(product_id, store_id):
    # Creazione di un dataframe contenente solo le vendite per il prodotto e il negozio specificati
    product_sales = train[(train['store'] == store_id) & (train['item'] == product_id)]
    # Selezione delle colonne 'ds' e 'y' per Prophet
    prophet_sales = product_sales[['ds', 'sales']].rename(columns={'sales': 'y'})
    # Creazione di un modello Prophet
    model = Prophet(yearly_seasonality=True, weekly_seasonality=True)
    # Addestramento del modello
    model.fit(prophet_sales)
    # Creazione di un dataframe contenente le date dei prossimi 3 mesi
    future_dates = model.make_future_dataframe(periods=90)
    # Generazione delle previsioni per i prossimi 3 mesi
    forecast = model.predict(future_dates)
    # Restituzione del dataframe delle previsioni
    return forecast[['ds', 'yhat']].tail(90)

# Creazione di una lista di prodotti e negozi per i quali effettuare le previsioni
products = train['item'].unique() 
stores = train['store'].unique() 

# Previsione delle vendite per ogni combinazione di prodotto e negozio e riempimento del dataset di test con le previsioni
for product_id in products:
    for store_id in stores:
        # Previsione delle vendite
        sales_forecast = predict_sales(product_id, store_id)
        # Riempimento del dataset di test con le previsioni
        test.loc[(test['store'] == store_id) & (test['item'] == product_id), 'sales'] = sales_forecast['yhat'].values

# Salvataggio del dataset di test con le previsioni
test.to_csv('test_with_predictions.csv', index=False)

Il dataset test riempito con le previsioni è il seguente :

	id 	date 		store 	item 	sales
0 	0 	2018-01-01 	1 	1 	12.984464
1 	1 	2018-01-02 	1 	1 	15.639726
2 	2 	2018-01-03 	1 	1 	16.282882
3 	3 	2018-01-04 	1 	1 	16.943880
4 	4 	2018-01-05 	1 	1 	18.493129
... 	... 	... 	... 	... 	...
44995 	44995 	2018-03-27 	10 	50 	75.398605
44996 	44996 	2018-03-28 	10 	50 	76.734359
44997 	44997 	2018-03-29 	10 	50 	81.777542
44998 	44998 	2018-03-30 	10 	50 	86.899711
44999 	44999 	2018-03-31 	10 	50 	91.900675

ESEMPIO 2 modifica

Caricamento librerie modifica

 library(dplyr)
 library(gbm)
 library(ggplot2)

Parte 1: Dati modifica

Avendo un set di dati temporali composto da vendite giornaliere,gentilmente fornito da una delle più grandi società di software russe - 1C Azienda (http://1c.ru/eng/title.htm) è possibile prevedere quali saranno le vendite future dell'azienda . Nel dataset sales_train ci sono 2.935.849 registrazioni giornaliere con 11 variabili a cui ne verranno aggiunte altre nei prossimi mesi. In tutto ci sono 60 negozi, 22170 articoli e 84 categorie.

Descrizioni dei files:

  • sales_train.csv : Dati storici giornalieri da gennaio 2013 a ottobre 2015.
  • items.csv - informazioni supplementari su articoli/prodotti.
  • item_categories.csv - informazioni supplementari su categorie di articoli.
  • shops.csv - informazioni supplementari sui negozi.

Variabili contenute nei datasets:

  • ID : un ID che rappresenta un negozio o un articolo all'interno del set di dati
  • shop_id : identificatore univoco di un negozio
  • item_id : identificatore univoco di un prodotto
  • item_category_id - identificatore univoco della categoria di articoli
  • item_cnt_day - numero di prodotti venduti. Si vuole predire un valore mensile di questo dato
  • item_price - prezzo corrente di un articolo
  • data - data nel formato gg/mm/aaaa
  • date_block_num - un numero di mese consecutivo, utilizzato per comodità. Gennaio 2013 è 0, febbraio 2013 è 1, ...,Ottobre 2015 è il 33
  • item_name : nome dell'articolo
  • name_shop : nome del negozio
  • item_category_name - nome della categoria dell'articolo

Caricamento dei dati:

 sales_train <- read.csv("sales_train_v2.csv")
 items2 <-read.csv("items.csv")
 items <- read.csv("items-translated.csv")
 items <- cbind(items,item_category_id=items2$item_category_id)
 shops <- read.csv("shops-translated.csv")
 categories <- read.csv("item_categories-translated.csv")
 items <- merge(items,categories, by =c("item_category_id"), all.x  = TRUE)
 sales_train <- merge(sales_train,items, by =c("item_id"), all.x = TRUE)
 sales_train <- merge(sales_train,shops, by =c("shop_id"), all.x = TRUE)

Creazione di nuove variabili nel dataset :

#Creazione delle variabili: week_day, month , week_year
 sales_train$date <- as.Date(sales_train$date,tryFormats=c("%d.%m.%Y"))
 p <- as.POSIXlt(sales_train$date)
 sales_train <- cbind(sales_train,week_day=p$wday +1)
 sales_train <- cbind(sales_train,month=p$mon +1)
 week_year<-as.numeric(strftime(p, format = "%V"))
 sales_train <- cbind(sales_train,week_year)

 '''#Creazione della variabile: Media delle vendite di ogni articolo per ogni mese
 df1<-sales_train %>%'''
 group_by(date_block_num,item_id) %>%
 summarise(date_item_avg=mean(item_cnt_day))
 sales_train <- merge(sales_train, as.data.frame(df1),
 by=c("date_block_num","item_id"), all.x = TRUE)

 '''#Creazione della variabile: Media delle vendite per categoria per ogni mese'''
 df1<-sales_train %>%
 group_by(date_block_num,shop_id, item_category_id) %>%
 summarise(date_shop_cat_avg=mean(item_cnt_day))
 
 sales_train <- merge(sales_train, as.data.frame(df1),
 by=c("date_block_num","shop_id","item_category_id"), all.x =TRUE)

 '''#Creazione variabile da predire item_cnt_month (vendite totali mensili)'''
 df1<-sales_train %>%
 group_by(date_block_num,week_year,week_day,shop_id,item_category_
 id,item_id,item_price, date_item_avg, date_shop_cat_avg) %>%
 summarise(item_cnt_month=sum(item_cnt_day))
 sales_train <- merge(sales_train, as.data.frame(df1),
 by=c("date_block_num","week_year","week_day","shop_id","item_category_id","item_id","item_price",     "date_item_avg","date_shop_cat_avg"), all.x = TRUE)

E' possibile migliorare il modello aggiungendo nuove variabili e/o modificando il tuning degli iperparametri (DA FARE)

Parte 2: Domanda di ricerca modifica

Si vogliono predire le vendite totali per ogni prodotto e negozio nel prossimo mese disponibile (novembre 2015)

Parte 3: Esplorazione dei dati modifica

  sales_train %>%
  group_by(item_category_name_translated) %>%
  summarise(total = sum( item_cnt_day)) %>%
  filter(total>10000) %>%
  mutate(item_category_name_translated=reorder(item_category_name_translated,total))  %>%
   ggplot(aes(item_category_name_translated,total,fill=item_category_name_translated)) +
  geom_bar(stat="identity")+
  coord_flip()+
  scale_y_continuous(breaks=seq(0,650000,80000))+
  theme(legend.position = "none") +
  xlab("Category") +
  ylab("Total sales") +
  ggtitle("Categories with total sales > 10000", subtitle = "from 2013/01 to 2015/09")
 
Categorie con più di 10000 prodotti venduti da gennaio 2013 a settembre 2015
 
Mesi con più vendite da gennaio 2013 a settembre 2015
 
Giorni della settimana con più vendite da gennaio 2013 a settembre 2015
 
Settimane dell'anno con più vendite da gennaio 2013 a settembre 2015

Parte 4: Modellizzazione modifica

Cancellazione degli outliers: Nelle variabili prezzo item_price e numero dei prodotti venduti item_cnt_day ci sono valori anomali, troppo grandi e rari che occorre cancellare :

 sales_train %>%
 ggplot(aes(item_id,item_price))+
 geom_boxplot()
 
 sales_train %>%
 ggplot(aes(item_id,item_cnt_day))+
 geom_boxplot()
 
 sales_train <- sales_train[-which(sales_train$item_price>10000) ,]
 sales_train <- sales_train[-which.max(sales_train$item_cnt_day) ,]
 max(sales_train$item_price)
 max(sales_train$item_cnt_day)
 
Outlier item_price
 
Outlier item_cnt_day

I dati per creare il modello previsionale sono quelli anteriori a ottobre 2015 e costituiscono il cosiddetto training set (date_block_num <33), mentre quelli relativi a ottobre 2015 (date_block_num = 33) costituiscono il validation set .

 sales_validation <-
 sales_train[which(sales_train$date_block_num==33),]
 sales_train <- sales_train[which(sales_train$date_block_num<33),]

Per prevedere le vendite mensili totali a ottobre 2015 il pacchetto gbm adotta la strategia del gradiente stocastico, una piccola ma importante modifica dell'algoritmo di base.

 gbm_model = gbm(item_cnt_month ~ shop_id + item_id +
 item_category_id + date_block_num + item_price + week_day +
 week_year + date_item_avg + date_shop_cat_avg , data=sales_train,
 shrinkage = 0.01,
 distribution = "gaussian",
 n.trees = 1000,
 interaction.depth = 5,
 bag.fraction = 0.5,
 train.fraction = 0.8,
 # cv.folds = 5,
 n.cores = -1,
 verbose = T)

Parte 5: Previsione modifica

Dopo aver ottenuto il modello, è possibile prevedere le vendite totali mensili sia sul training set che sul validation set (ottobre 2015) e si può calcolare l'errore quadratico medio (RMSE) per valutare la correttezza della previsione.

 p1 = predict(gbm_model,newdata = sales_train, n.trees = 1000)
 print(paste("RMSE_train=",sqrt((1/nrow(sales_train))*sum((p1-
 sales_train$item_cnt_month)^2))))
 ## [1] "RMSE_train= 1.74599536656872"
 p1 = predict(gbm_model,newdata = sales_validation, n.trees =
 1000)
 rmse <-sqrt((1/nrow(sales_validation))*sum((p1-
 sales_validation$item_cnt_month)^2))
 print(paste("RMSE_validation=",rmse))
 ## [1] "RMSE_test= 3.70520797224124"

Inoltre è possibile rispondere alla domanda di ricerca prevedendo le vendite mensili sul testing set (November 2015):

 p1 = predict(gbm_model,newdata = sales_test, n.trees = 1000)
 sub2 = data.frame(ID = sales_test$ID,item_cnt_month =
 write.csv(sub2, "submission.csv", row.names = F)