Jean-Philippe Allard
6 avril 2019
Bienvenue sur cet article avec les dĂ©tails techniques de ma confĂ©rence du Web Ă QuĂ©bec . Nous allons coder ensemble afin dâĂ©viter Ă Bob de passer des heures Ă chaque mois Ă faire les mĂȘmes opĂ©rations dans Excel, afin de le libĂ©rer Ă faire des activitĂ©s plus productives.
Contexte de lâexemple de code
Cet exemple est basĂ© sur les donnĂ©es rĂ©elles du Google Merchandise Store Demo Account de 2017 ainsi que dâun fichier lourdement modifiĂ© provenant de Kaggle . Toutes les manipulations pour crĂ©er ces fichiers sont dĂ©taillĂ©es dans mon autre article sur la prĂ©paration des donnĂ©es dans R . Malheureusement, confidentialitĂ© oblige, câĂ©tait la seule maniĂšre de crĂ©er un exemple accessible au public, car il y a trĂšs peu de donnĂ©es transactionnelles rĂ©elles disponibles au public.
Rappel du mandat
Bob est un analyste dâaffaire trĂšs travaillant. Outre ses activitĂ©s rĂ©guliĂšres, il passe une journĂ©e par mois Ă recueillir des donnĂ©es, les nettoyer, les analyser et les mettre en page, afin de faire une prĂ©sentation de 15 minutes aux dirigeants de lâentreprise pour laquelle il travaille, sur la performance de leur boutique en ligne et de leur boutique physique.
Cela fait deux ans quâil prĂ©sente les mĂȘmes donnĂ©es de la mĂȘme façon, ce qui implique quâil a passĂ© 192 heures (24 jours de 8h) Ă refaire les mĂȘmes tĂąches rĂ©pĂ©titives afin de prĂ©senter des donnĂ©es durant 6 heures (24 prĂ©sentations de 15 minutes). Bob pense quâil doit y avoir un meilleur moyen dâutiliser son temps. Il a raison!
Solution
Bob dĂ©cide quâil va apprendre Ă utiliser le langage de programmation R sur Datacamp . Il planifie apprendre ce langage 1h par jour en se rĂ©veillant 1h plus tĂŽt chaque matin. Il se dit quâen un an au maximum, avec 250h de pratique (1h/jour, 5 jours par semaine, 50 semaines), il sera capable dâutiliser ce langage pour libĂ©rer son temps. Et il a raison.
Voici ce quâil peut faire aprĂšs 250h de pratique. Nous sommes le 1ier janvier 2018 et Bob dĂ©cide dâutiliser le temps quâil mettait auparavant Ă produire son rapport Ă la main, afin de crĂ©er des visualisations selon les meilleurs principes de la communication des donnĂ©es.
Importation et nettoyage des données
#Bob commence par charger les librairies qu'il a besoin.
#Librairie readr pour charger ses fichiers rapidement et facilement.
library(readr)
#Librairie tidyverse, une librairie de librairies, permettant de mieux travailler avec le tidy data.
library(tidyverse)
#Librairie lubridate qui permet de facilement travailler avec les dates.
library(lubridate)
#Librairie ggthemes, qui offre des préconfigurations de style de tableaux
library(ggthemes)
#Librairie permettant de contrĂŽler l'affichage des chiffres sur une visualisation
library(scales)
#Librairie pour gérer les fontes
library(extrafont)
#Bob va aussi déterminer la localisation des dates, pour affichage en français.
Sys.setlocale("LC_TIME", "French")
Voici les cours Datacamp pour ces différentes librairies.
readr
tidyverse
lubridate
Maintenant, Bob lit ses fichiers et les transforme en objet dans R. Dans un contexte de production, ceux-ci seraient chargés via API ou par un connecteur de données. Mais pour garder cet exemple simple, Bob lit un fichier local sur son disque dur.
Si vous voulez avoir les fichiers de cet exemple, ils sont disponibles sur Kaggle.
#Lecture des fichiers
Online <- read_csv("Online.csv", col_types = cols(Date = col_date(format = "%Y%m%d")))
Retail <- read_csv("Retail.csv", col_types = cols(InvoiceDate = col_date(format = "%Y-%m-%d")))
#Bob vérifie la structure des fichiers afin de mieux orienter ses prochaines manipulations
glimpse(Online)
## Observations: 54,144
## Variables: 10
## $ `Transaction ID` <dbl> 48497, 48496, 48495, ...
## $ Date <date> 2017-12-31, 2017-12-...
## $ `Product SKU` <chr> "GGOENEBQ079099", "GG...
## $ Product <chr> "NestÂź Protect Smoke ...
## $ `Product Category (Enhanced E-commerce)` <chr> "Nest-USA", "Nest-USA...
## $ Quantity <dbl> 4, 5, 1, 1, 1, 3, 1, ...
## $ `Avg. Price` <dbl> 80.52, 80.52, 151.88,...
## $ Revenue <dbl> 316.00, 395.00, 149.0...
## $ Tax <dbl> 34.44, 33.14, 12.06, ...
## $ Delivery <dbl> 19.99, 6.50, 6.50, 6....
glimpse(Retail)
## Observations: 181,247
## Variables: 4
## $ InvoiceNo <dbl> 536598, 536598, 536598, 536599, 536599, 536600, 53...
## $ InvoiceDate <date> 2017-01-01, 2017-01-01, 2017-01-01, 2017-01-01, 2...
## $ StockCode <dbl> 21421, 21422, 22178, 20749, 21056, 21730, 21871, 2...
## $ Quantity <dbl> 1, 2, 26, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 60, 2, 1, ...
Bob remarque que son fichier Retail, montrant les ventes des magasins physiques, nâest pas trĂšs complet par rapport Ă son fichier Online. Il aimerait rajouter lâinformation sur les produits, leur prix de vente et leur catĂ©gorie Ă son fichier Retail. Mais les codes de produit sont aussi diffĂ©rents! Il va donc falloir crĂ©er une âclĂ©â entre les âProduct SKUâ du fichier online et le âStockCodeâ du fichier retail.
Bob cherche donc Ă trouver une liste de StockCode – Nom de produit, mais malheureusement, son entreprise nâa jamais cru bon de crĂ©er cette liste (Ă se demander comment elle est toujours en affaires!). Comme Bob doit travailler avec ces contraintes, cela implique quâil doit crĂ©er un fichier de âclĂ©â faisant le lien entre le SKU et le StockCode. Bob demande donc Ă son stagiaire, GĂ©rard, de crĂ©er la concordance entre les 1179 Product SKU et les 1179 StockCodes. GĂ©rard, aprĂšs avoir sombrĂ© dans lâalcoolisme, lui livre le fichier suivant:
KEY_SKU <- read_csv("KEY_SKU.csv")
glimpse(KEY_SKU)
## Observations: 1,178
## Variables: 2
## $ `Product SKU` <chr> "GGOENEBQ079099", "GGOENEBQ079199", "GGOENEBQ084...
## $ StockCode <dbl> 21421, 21422, 22178, 20749, 21056, 21730, 21871,...
Il sâagit de la concordance entre chaque StockCode et son Product SKU. Avec cette concordance, il devient possible de prendre les informations du fichier Online et de les appliquer au fichier Retail.
Il faut dâabord crĂ©er un fichier avec la liste des informations voulues par SKU, câest-Ă -dire le nom du produit, sa catĂ©gorie, et son prix.
#On commence par créer un vecteur logique des SKUS dupliqués, qu'on rajoute comme une nouvelle colonne dans notre fichier Online.
Online$dups <- duplicated(Online$`Product SKU`)
#On conserve uniquement les lignes non dupliquées
Product_list <- Online %>% filter(dups == FALSE)
#On conserve seulement l'information pertinente
Product_list <- Product_list[c("Product SKU", "Product", "Product Category (Enhanced E-commerce)", "Avg. Price")]
#On rajoute le stock code Ă notre liste de produits
Product_list <- left_join(KEY_SKU, Product_list, by = "Product SKU")
#Notre liste de produits avec toutes les informations est prĂȘte!
glimpse(Product_list)
## Observations: 1,178
## Variables: 5
## $ `Product SKU` <chr> "GGOENEBQ079099", "GG...
## $ StockCode <dbl> 21421, 21422, 22178, ...
## $ Product <chr> "NestÂź Protect Smoke ...
## $ `Product Category (Enhanced E-commerce)` <chr> "Nest-USA", "Nest-USA...
## $ `Avg. Price` <dbl> 80.52, 80.52, 151.88,...
#On peut enlever la colonne dups de notre fichier Online
Online$dups <- NULL
#Et ensuite, rajouter toute l'information Ă notre fichier Retail
Retail <- merge(Retail, Product_list, by = "StockCode", all.x = TRUE)
Retail <- arrange(Retail, InvoiceDate, InvoiceNo)
#Nous pouvons calculer le reste de l'information manquante: les revenus et les taxes. Nous calculons par ligne d'abord:
perLine <- Retail %>% mutate(RevenuePerLine = Quantity * `Avg. Price`, TaxPerLine = RevenuePerLine * 0.014975)
#Puis nous groupons les lignes par numéro de commande:
perInvoiceNo <- perLine %>% group_by(InvoiceNo) %>% summarise(Revenue = sum(RevenuePerLine), Tax = sum(TaxPerLine))
#Puis nous joignons ces informations à nos données
Retail <- merge(Retail, perInvoiceNo, all.x = TRUE)
#Arrondissons le montant des taxes
Retail$Tax <- round(Retail$Tax, digits = 2)
#Et ajoutons des frais de livraison de 0
Retail <- Retail %>% mutate(Delivery = 0)
#On peut mĂȘme enlever le StockCode et travailler uniquement avec le Product SKU
Retail$StockCode <- NULL
#Notre fichier Retail possĂšde maintenant toutes les informations disponibles dans notre fichier Online!
glimpse(Online)
## Observations: 54,144
## Variables: 10
## $ `Transaction ID` <dbl> 48497, 48496, 48495, ...
## $ Date <date> 2017-12-31, 2017-12-...
## $ `Product SKU` <chr> "GGOENEBQ079099", "GG...
## $ Product <chr> "NestÂź Protect Smoke ...
## $ `Product Category (Enhanced E-commerce)` <chr> "Nest-USA", "Nest-USA...
## $ Quantity <dbl> 4, 5, 1, 1, 1, 3, 1, ...
## $ `Avg. Price` <dbl> 80.52, 80.52, 151.88,...
## $ Revenue <dbl> 316.00, 395.00, 149.0...
## $ Tax <dbl> 34.44, 33.14, 12.06, ...
## $ Delivery <dbl> 19.99, 6.50, 6.50, 6....
glimpse(Retail)
## Observations: 181,247
## Variables: 10
## $ InvoiceNo <dbl> 536598, 536598, 53659...
## $ InvoiceDate <date> 2017-01-01, 2017-01-...
## $ Quantity <dbl> 1, 2, 26, 2, 2, 2, 1,...
## $ `Product SKU` <chr> "GGOENEBQ079099", "GG...
## $ Product <chr> "NestÂź Protect Smoke ...
## $ `Product Category (Enhanced E-commerce)` <chr> "Nest-USA", "Nest-USA...
## $ `Avg. Price` <dbl> 80.52, 80.52, 151.88,...
## $ Revenue <dbl> 4190.44, 4190.44, 419...
## $ Tax <dbl> 62.75, 62.75, 62.75, ...
## $ Delivery <dbl> 0, 0, 0, 0, 0, 0, 0, ...
DerniÚre étape avant de faire nos analyses et nos visualisations: joindre les deux tableaux ensemble.
#Il faut d'abord renommer les colonnes pour qu'elles possĂšdent le mĂȘme nom d'un cĂŽtĂ© comme de l'autre. Ăa nous simplifiera la vie.
Retail <- Retail %>% rename("Transaction ID" = "InvoiceNo", "Date" = "InvoiceDate")
#On rajoute un colonne catégorielle à chaque tableau
Retail$Channel <- "Retail"
Online$Channel <- "Online"
#On joint
Full <- bind_rows(Online, Retail)
#Notre tableau complet est prĂȘt!
Analyse des données
Maintenant que toutes nos données sont regroupées, il est trÚs facile de faire des statistiques descriptives en utilisant les fonctions de base de R ainsi que les fonctions group_by(), filter() et summarize() de dplyr.
#Exemple: On peut calculer le revenu total annuel par Canal de vente (Online ou Retail)
Full_unique <- Full %>%
filter(duplicated(Full$`Transaction ID`) == FALSE)
Revenue_by_channel <- Full_unique %>%
group_by(Channel) %>%
summarize(sum(Revenue))
Revenue_by_channel
## # A tibble: 2 x 2
## Channel `sum(Revenue)`
## <chr> <dbl>
## 1 Online 4743705.
## 2 Retail 19019265.
Rappelons les données que nous voulons obtenir et présenter pour notre rapport:
Revenus par catégorie par mois
Croissance par catégorie MoM
Estimation des ventes pour le mois suivant
ScĂ©narios dâestimation des ventes en fonction du budget marketing
Nous allons produire et visualiser chacune de ces données. Débutons par créer un sommaire des revenus par canal, par catégorie, et par mois.
Revenus par mois par catégorie
#Créer la colonne "Mois-Année" qui va agir à titre de facteur de groupement
Full_unique$Mois_An <- format(as.Date(Full_unique$Date), "%Y-%m")
#Changer les vecteurs catégoriels en facteurs
Full_unique$`Product Category (Enhanced E-commerce)` <- as.factor(Full_unique$`Product Category (Enhanced E-commerce)`)
Full_unique$Mois_An <- as.factor(Full_unique$Mois_An)
Full_unique$Channel <- as.factor(Full_unique$Channel)
#Créer le sommaire des ventes par canal, mois et catégorie.
Full_summary <- Full_unique %>%
group_by(Channel, Mois_An, `Product Category (Enhanced E-commerce)`, .drop = FALSE) %>%
summarize(sum(Revenue))
#On remplace les ventes non catégorisées par une catégorie "Autres"
Full_summary$`Product Category (Enhanced E-commerce)` <- as.character(Full_summary$`Product Category (Enhanced E-commerce)`)
Full_summary$`Product Category (Enhanced E-commerce)` <- Full_summary$`Product Category (Enhanced E-commerce)` %>% replace_na("Other")
Full_summary$`Product Category (Enhanced E-commerce)` <- as.factor(Full_summary$`Product Category (Enhanced E-commerce)`)
Maintenant que nous avons notre sommaire de ventes par canal, par catégorie et par mois, nous pouvons commençer à créer nos visualisations.
#Initialisation de la date. Dans un cadre réel, nous utiliserions Sys.Date(), mais ici, nous utilisons une date fixe pour notre exemple.
sysdate <- as.Date(ymd_hms("2018-01-01 9:00:00"))
#Visualisation des ventes du dernier mois, par catégorie.
ggplot(data = filter(Full_summary, Mois_An == format(as.Date(sysdate %m+% months(-1)), "%Y-%m")), aes(x = reorder(`Product Category (Enhanced E-commerce)`, `sum(Revenue)`), y = `sum(Revenue)`, fill = Channel)) +
geom_col(position = "stack") +
coord_flip() +
theme_tufte() +
theme(legend.position = c(0.8, 0.15)) +
scale_fill_manual(values = c("#2c7bb6", "#abd9e9")) +
scale_y_continuous(labels = dollar) +
theme(axis.title.x = element_text(family = "Arial", colour="#484848", size=12),
axis.text.x = element_text(family = "Arial", colour="#484848", size=12),
axis.title.y = element_blank(),
axis.text.y = element_text(family = "Arial", colour="#484848", size=12),
legend.title = element_text(family = "Arial", colour="#484848", size=10),
legend.text = element_text(family = "Arial", colour="#484848", size=9),
plot.title = element_text(family = "Arial", colour="#484848", size=16),
plot.subtitle = element_text(family = "Arial", colour="#484848", size=10)) +
labs(y = "Revenus (En $)",
title = "Ventes par catégorie, par canal",
subtitle = (paste("Mois de", format(as.Date(sysdate %m+% months(-1)), "%B %Y"), sep = " ")),
fill = "Canal")
Maintenant, nous désirons obtenir la croissance des ventes par catégorie pour le dernier mois complet, comparé au mois précédent.
#Calcul de la croissance des ventes du dernier mois (Décembre) par rapport au mois précédent (Novembre)
Summary_current <- Full_summary %>% filter(Mois_An == format(as.Date(sysdate %m+% months(-1)), "%Y-%m"))
Summary_previous <- Full_summary %>% filter(Mois_An == format(as.Date(sysdate %m+% months(-2)), "%Y-%m"))
Summary_current$Growth <- (Summary_previous$`sum(Revenue)` - Summary_current$`sum(Revenue)`) / Summary_previous$`sum(Revenue)` * 100
#
Summary_current[is.na(Summary_current)] <- NA
Summary_current$Growth[is.infinite(Summary_current$Growth)] <- NA
#Visualisation de la croissance des ventes du dernier mois, en ligne, par catégorie.
ggplot(data = filter(Summary_current, Channel == "Online" & !is.na(Growth)), aes(x = reorder(`Product Category (Enhanced E-commerce)`, Growth), y = Growth, fill = Growth > 0)) +
geom_col() +
coord_flip(ylim = c(-100, 100)) +
theme_tufte() +
theme(axis.title.x = element_text(family = "Arial", colour="#484848", size=12),
axis.text.x = element_text(family = "Arial", colour="#484848", size=12),
axis.title.y = element_blank(),
axis.text.y = element_text(family = "Arial", colour="#484848", size=12),
plot.title = element_text(family = "Arial", colour="#484848", size=16),
plot.subtitle = element_text(family = "Arial", colour="#484848", size=10),
legend.position = "none"
) +
labs(y = "Variation MoM (en %)",
title = "Variation des ventes mensuelles en ligne",
subtitle = (paste("Mois de", format(as.Date(sysdate %m+% months(-1)), "%B %Y"), "par rapport Ă ", format(as.Date(sysdate %m+% months(-2)), "%B %Y"), sep = " "))) +
scale_fill_manual(values=c("FALSE"= "#fdae61", "TRUE" = "#abd9e9"))
#Visualisation de la croissance des ventes du dernier mois, en magasin, par catégorie.
ggplot(data = filter(Summary_current, Channel == "Retail" & !is.na(Growth)), aes(x = reorder(`Product Category (Enhanced E-commerce)`, Growth), y = Growth, fill = Growth > 0)) +
geom_col()+
coord_flip(ylim = c(-100, 100)) +
theme_tufte() +
theme(axis.title.x = element_text(family = "Arial", colour="#484848", size=12),
axis.text.x = element_text(family = "Arial", colour="#484848", size=12),
axis.title.y = element_blank(),
axis.text.y = element_text(family = "Arial", colour="#484848", size=12),
plot.title = element_text(family = "Arial", colour="#484848", size=16),
plot.subtitle = element_text(family = "Arial", colour="#484848", size=10),
legend.position = "none"
) +
labs(y = "Variation MoM (en %)",
title = "Variation des ventes mensuelles en magasin",
subtitle = (paste("Mois de", format(as.Date(sysdate %m+% months(-1)), "%B %Y"), "par rapport Ă ", format(as.Date(sysdate %m+% months(-2)), "%B %Y"), sep = " "))) +
scale_fill_manual(values=c("FALSE"= "#fdae61", "TRUE" = "#abd9e9"))
Maintenant que nous connaissons nos ventes et notre croissance par catĂ©gorie, nous aimerions faires des prĂ©visions pour le mois prochain afin dâĂ©tablir un objectif de ventes.
Si vous voulez apprendre Ă faire des modĂšles de sĂ©ries temporelles, voici l’excellent cours sur Datacamp: Forecasting using R par Rob J. Hyndman , le crĂ©ateur de la librairie forecast.
#Chargeons la librairie forecast qui permet de faire des modÚles prédictifs de série temporelle, et la librairie xts qui permet de créer des objets de type séries de dates plus facilement
library(forecast)
library(xts)
#Créons les revenus journaliers totaux, incluant les ventes en ligne et en magasin
Revenue_daily <- Full_unique %>% group_by(Date) %>% summarize(sum(Revenue))
#Et une série de dates pour construire notre objet xts
dates <- seq(as.Date("2017-01-01"), length = 365, by = "days")
#Convertissons cet objet en format xts
Revenue_daily <- xts(Revenue_daily$`sum(Revenue)`, order.by = dates)
#Créons notre modÚle prédictif de maniÚre automatique
model <- auto.arima(Revenue_daily, stepwise = FALSE)
#Faisons une prédiction pour le nombre de jours dans le mois actuel
fcast <- forecast(model, h = days_in_month(sysdate))
#On jette un oeil rapide à la prédiction et aux valeurs prédites
plot(fcast)
#Et aux prévisions moyennes
fcast$mean
## Time Series:
## Start = 366
## End = 396
## Frequency = 1
## [1] 100409.10 95617.85 82661.92 95286.51 98492.79 85303.40 91042.45
## [8] 98923.06 88758.51 88421.40 97584.02 91933.86 87538.74 95362.89
## [15] 94152.09 88053.42 93077.02 95182.81 89395.81 91298.83 95155.73
## [22] 90978.02 90295.77 94414.55 92341.67 90062.89 93365.82 93227.23
## [29] 90410.92 92360.72 93573.34
#On crée notre tableau de données à visualiser
df <- data.frame(Bas = sum(fcast$lower[,1]), Moyen = sum(fcast$mean), `ĂlevĂ©` = sum(fcast$upper[,1]))
df_long <- gather(df, key = "Estimation", value = "value")
#On ajuste l'affichage des chiffres en $
dollar <- dollar_format(accuracy = 1, big.mark = " ", prefix = "", suffix = "$")
#On crée notre visualisation
ggplot(data = df_long, aes(x = Estimation, y = value, fill = Estimation, label = dollar(value))) +
geom_bar(stat="identity", position = "dodge") +
geom_text(hjust = 1.1, size = 5, color = "#ffffff") +
coord_flip() +
theme_tufte() +
scale_y_continuous(labels = dollar) +
scale_x_discrete(limits = c("ĂlevĂ©", "Moyen", "Bas")) +
scale_fill_manual(values = c("#abd9e9", "#abd9e9", "#2c7bb6")) +
labs(title = (paste("Estimation des ventes pour le mois de", format(as.Date(sysdate), "%B %Y"), sep = " ")),
subtitle = "Calculé à l'aide d'un modÚle ARIMA automatique") +
theme(
axis.title.x = element_blank(),
axis.text.x = element_text(family = "Arial", colour="#484848", size=12),
axis.title.y = element_blank(),
axis.text.y = element_text(family = c("Arial", "Arial Black", "Arial"), colour="#484848", size=12),
plot.title = element_text(family = "Arial", colour="#484848", size=16),
plot.subtitle = element_text(family = "Arial", colour="#484848", size=10),
legend.position = "none"
)
En utilisant uniquement un modĂšle de sĂ©rie temporelles, il y aura toujours une marge dâerreur assez importante. Toutefois, il est aussi possible de crĂ©er plutĂŽt un modĂšle de rĂ©gression linĂ©aire avec des erreurs ARIMA. Comme nous dĂ©sirons conaitre lâeffet de la publicitĂ© sur nos ventes, nous allons crĂ©er ce type de modĂšle avec des donnĂ©es sur le budget publicitaire online et offline, et ensuite faire des prĂ©dictions par scĂ©narios.
#Commençons par importer un fichier avec les dépenses marketing par jour pour l'année 2017.
Marketing_spend <- read_csv("Marketing_Spend.csv",
col_types = cols(`Offline Spend` = col_double(),
`Online Spend` = col_double(), X1 = col_date(format = "%Y-%m-%d")))
#On crée trois modÚles en ajoutant les budgets marketing comme régresseurs
model_spend_both <- auto.arima(Revenue_daily, stepwise = FALSE, xreg = cbind(Marketing_spend$`Online Spend`, Marketing_spend$`Offline Spend`))
model_spend_online <- auto.arima(Revenue_daily, stepwise = FALSE, xreg = Marketing_spend$`Online Spend`)
model_spend_offline <- auto.arima(Revenue_daily, stepwise = FALSE, xreg = Marketing_spend$`Offline Spend`)
#On observe le "fit" des modĂšles
summary(model_spend_both)
## Series: Revenue_daily
## Regression with ARIMA(3,0,0) errors
##
## Coefficients:
## ar1 ar2 ar3 intercept xreg1 xreg2
## -0.0879 -0.0553 0.0715 -24577.857 43.1001 2.6479
## s.e. 0.0523 0.0524 0.0529 2346.622 0.9302 0.7613
##
## sigma^2 estimated as 190771248: log likelihood=-3994.55
## AIC=8003.11 AICc=8003.42 BIC=8030.41
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 8.370002 13698 11354.57 -12.31825 34.27963 0.3066989
## ACF1
## Training set -0.002404769
summary(model_spend_online)
## Series: Revenue_daily
## Regression with ARIMA(3,0,0) errors
##
## Coefficients:
## ar1 ar2 ar3 intercept xreg
## -0.0579 -0.0164 0.1133 -18797.216 44.0179
## s.e. 0.0527 0.0524 0.0531 1899.972 0.9144
##
## sigma^2 estimated as 195942090: log likelihood=-3999.95
## AIC=8011.9 AICc=8012.13 BIC=8035.3
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 8.96493 13901.72 11577.88 -13.89588 34.22336 0.3127307
## ACF1
## Training set -0.008030993
summary(model_spend_offline)
## Series: Revenue_daily
## Regression with ARIMA(0,1,4) errors
##
## Coefficients:
## ma1 ma2 ma3 ma4 xreg
## -0.9442 -0.1772 0.0047 0.1510 14.0375
## s.e. 0.0517 0.0723 0.0749 0.0534 1.6692
##
## sigma^2 estimated as 1.224e+09: log likelihood=-4323.92
## AIC=8659.84 AICc=8660.07 BIC=8683.22
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 356.6522 34692.86 26926.26 -69.75817 96.22966 0.7273065
## ACF1
## Training set 0.005368986
Le modĂšle incluant les deux rĂ©gresseurs est supĂ©rieur (AIC plus faible). On remarque aussi que le budget de marketing offline a peu dâeffet sur les ventes, avec un coefficient de seulement 2.64, comparĂ© Ă un coefficient de 43.10 pour le budget de marketing online. Une autre chose intĂ©ressante est que le AIC du modĂšle incluant les rĂ©gresseurs est beaucoup plus faible que celui nâincluant pas les rĂ©gresseurs, ce qui indique que nous devrions ĂȘtre en mesure de faire des prĂ©dictions plus prĂ©cises.
Créons maintenant nos prédictions qui incluent le budget marketing prévu.
#Il faut d'abord créer des tableaux avec des scénarios d'augmentation de budgets en fonction des budgets du mois précédent.
Budget_same <- Marketing_spend %>%
filter(X1 <= (ceiling_date(as.Date(sysdate %m+% months(-1)), "month") - days(1)) & X1 >= as.Date(sysdate %m+% months(-1)))
Budget_higher_online <- Marketing_spend %>%
filter(X1 <= (ceiling_date(as.Date(sysdate %m+% months(-1)), "month") - days(1)) & X1 >= as.Date(sysdate %m+% months(-1))) %>%
mutate(`Online Spend` = `Online Spend` * 1.25)
Budget_higher_offline <- Marketing_spend %>%
filter(X1 <= (ceiling_date(as.Date(sysdate %m+% months(-1)), "month") - days(1)) & X1 >= as.Date(sysdate %m+% months(-1))) %>%
mutate(`Offline Spend` = `Offline Spend` * 1.25)
#On peut maintenant créer trois prédictions avec nos trois scénarios
fcast_same <- forecast(model_spend_both, xreg = cbind(Budget_same$`Online Spend`, Budget_same$`Offline Spend`), h = days_in_month(sysdate))
fcast_higher_online <- forecast(model_spend_both, xreg = cbind(Budget_higher_online$`Online Spend`, Budget_higher_online$`Offline Spend`), h = days_in_month(sysdate))
fcast_higher_offline <- forecast(model_spend_both, xreg = cbind(Budget_higher_offline$`Online Spend`, Budget_higher_offline$`Offline Spend`), h = days_in_month(sysdate))
#On jette un oeil à nos prédictions. Laquelle semble la plus intéressante?
plot(fcast_same)
plot(fcast_higher_online)
plot(fcast_higher_offline)
Maintenant que nous avons nos prédictions avec nos trois scénarios, nous allons visualiser celles-ci. En premier lieu, nous allons refaire notre estimation précédente des ventes du mois avec notre nouveau modÚle plus précis.
#On crée notre tableau de données à visualiser
df_same <- data.frame(Bas = sum(fcast_same$lower[,1]), Moyen = sum(fcast_same$mean), `ĂlevĂ©` = sum(fcast_same$upper[,1]))
df_long_same <- gather(df_same, key = "Estimation", value = "value")
#On crée notre visualisation
ggplot(data = df_long_same, aes(x = Estimation, y = value, fill = Estimation, label = dollar(value))) +
geom_bar(stat="identity", position = "dodge") +
geom_text(hjust = 1.1, size = 5, color = "#ffffff") +
coord_flip() +
theme_tufte() +
scale_y_continuous(labels = dollar) +
scale_x_discrete(limits = c("ĂlevĂ©", "Moyen", "Bas")) +
scale_fill_manual(values = c("#abd9e9", "#abd9e9", "#2c7bb6")) +
labs(title = (paste("Estimation des ventes pour le mois de", format(as.Date(sysdate), "%B %Y"), sep = " ")),
subtitle = "Budget de marketing identique au mois précédent",
caption = "Régression linéaire du budget marketing sur les ventes avec erreurs ARIMA\nNiveau de confiance 80%") +
theme(
axis.title.x = element_blank(),
axis.text.x = element_text(family = "Arial", colour="#484848", size=12),
axis.title.y = element_blank(),
axis.text.y = element_text(family = c("Arial", "Arial Black", "Arial"), colour="#484848", size=12),
plot.title = element_text(family = "Arial", colour="#484848", size=16),
plot.subtitle = element_text(family = "Arial", colour="#484848", size=10),
plot.caption = element_text(family = "Arial", colour="#484848", size=8),
legend.position = "none"
)
DĂ©jĂ beaucoup plus prĂ©cis! Maintenant, nous allons crĂ©er une visualisation qu dĂ©montre les trois scĂ©narios dans un seul graphique, afin quâil soit Ă©vident de la dĂ©cision Ă prendre.
#On crée notre tableau de données à visualiser
df_budget <- data.frame(Budget = c("Identique", "25% de plus en ligne", "25% de plus hors ligne"),
Moyen = c(sum(fcast_same$mean), sum(fcast_higher_online$mean), sum(fcast_higher_offline$mean)))
#On crée notre visualisation
ggplot(data = df_budget, aes(x = Budget, y = Moyen, fill = Budget, label = dollar(Moyen))) +
geom_bar(stat="identity", position = "dodge") +
geom_text(hjust = 1.1, size = 5, color = "#ffffff") +
coord_flip() +
theme_tufte() +
scale_y_continuous(labels = dollar) +
scale_fill_manual(values = c("#2c7bb6", "#abd9e9", "#abd9e9")) +
scale_x_discrete(limits = c("25% de plus en ligne", "25% de plus hors ligne", "Identique")) +
labs(title = (paste("Estimation des ventes pour le mois de", format(as.Date(sysdate), "%B %Y"), sep = " ")),
subtitle = "En fonction d'une augmentation du budget marketing en ligne ou hors ligne",
caption = "Prédictions calculées à partir de la moyenne du modÚle de régression\n linéaire du budget marketing sur les ventes avec erreurs ARIMA") +
theme(
axis.title.x = element_blank(),
axis.text.x = element_text(family = "Arial", colour="#484848", size=12),
axis.title.y = element_blank(),
axis.text.y = element_text(family = c("Arial Black", "Arial", "Arial"), colour="#484848", size=12),
plot.title = element_text(family = "Arial", colour="#484848", size=16),
plot.subtitle = element_text(family = "Arial", colour="#484848", size=10),
plot.caption = element_text(family = "Arial", colour="#484848", size=8),
legend.position = "none"
)
DĂ©marrage de lâinstance Google Compute Engine
Maintenant que nous avons tous les rapports que nous désirons envoyer chaque mois, il nous reste seulement deux étapes. La premiÚre est de créer une instance de RStudio dans Google Compute Engine, et la deuxiÚme est de créer un script dans cette instance qui enverra notre rapport chaque mois. Commençons par créer notre instance de RStudio dans Google Compute Engine avec la librairie googleComputeEngineR.
La premiĂšre Ă©tape est de crĂ©er un compte Google Compute Engine . Il faut ajouter une carte de crĂ©dit, mais lâutilisation dâun serveur f1-micro est gratuit. Câest amplement assez performant pour la tĂąche quâon va lui demander une fois par mois.
Ensuite, il faut crĂ©er notre projet, puis aller chercher les dĂ©tails de notre âService Accountâ en format JSON: API Manager -> Credentials -> Create credentials -> Service account key -> Key type = JSON. On ajoute ce fichier au dossier de travail de notre projet. Ă noter que câest ce fichier JSON qui contient les informations dâaccĂšs au compte GCE: il convient donc de bien le protĂ©ger.
Ensuite, il faut créer un fichier .Renviron dans notre dossier de travail (avec le bloc-note) qui sera chargé automatiquement lorsque la librairie sera activée. Ce fichier doit contenir les éléments suivants:
GCE_AUTH_FILE = "C:/My First Project-168c949fa5ac.json" #L'emplaçement du fichier JSON créé précédemment.
GCE_DEFAULT_PROJECT_ID = "mineral-highway-236314" #Le project ID du "My First Projet" créé par Google lors de la création du compte, ou un autre projet si vous préférez.
GCE_DEFAULT_ZONE = "us-east1-b" #L'emplaçement du serveur, qui devrait ĂȘtre celui-ci si vous vous connectez Ă partir de la cote est.
Ensuite, le code pour créer la nouvelle instance GCE est excessivement simple.
library(googleComputeEngineR)
vm <- gce_vm(template = "rstudio",
name = "autoemailer", #Nom qu'on désire donner à l'instance
username = "waq", #Nom d'utilisateur pour RStudio
password = "qwertyiscool", #Mot de passe pour RStudio
predefined_type = "n1-standard-1") #Type de serveur
Le f1-micro est le seul serveur gratuit, mais j’ai plutĂŽt utilisĂ© un n1-standard-1 Ă environ 25$/mois car les librairies utilisĂ©es pour l’exemple dĂ©passaient la capacitĂ© de mĂ©moire du petit f1-micro.
PrĂ©paration de lâinstance de machine virtuelle
Il faut dâabord installer les librairies qui ne sont pas incluses dans lâimage de base. Mais avant cela, il faut installer java ainsi que rJava via le terminal de notre instance (Linux Debian 8) car ils sont une dĂ©pendance de la librairie mailR que nous utiliserons pour lâenvoi de courriel.
sudo apt-get install default-jdk
sudo apt-get install r-cran-rjava
sudo R CMD javareconf
Maintenant, nous pouvons installer les librairies:
install.packages("cronR")
install.packages("ggthemes")
install.packages("extrafont")
install.packages("miniUI")
install.packages("forecast")
install.packages("xts")
install.packages("mailR")
install.packages("rJava")
install.packages("shiny")
install.packages("shinyFiles")
Et les activer:
library(rJava)
library(cronR)
library(miniUI)
library(mailR)
library(readr)
library(tidyverse)
library(lubridate)
library(ggthemes)
library(scales)
library(extrafont)
library(forecast)
library(xts)
library(shiny)
library(shinyFiles)
Ensuite, si on veut que les dates soient formattĂ©es correctement, il faut crĂ©er la locale fr_FR.UTF-8, encore une fois dans le terminal, avec lâapplication Ă cet effet:
sudo dpkg-reconfigure locales
Et créer un fichier .Renviron avec les locales qui seront chargées au start-up de R:
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="fr_FR.UTF-8"
LC_NUMERIC="fr_FR.UTF-8"
LC_TIME="fr_FR.UTF-8"
LC_COLLATE="fr_FR.UTF-8"
LC_MONETARY="fr_FR.UTF-8"
LC_MESSAGES="fr_FR.UTF-8"
LC_PAPER="fr_FR.UTF-8"
LC_NAME="fr_FR.UTF-8"
LC_ADDRESS="fr_FR.UTF-8"
LC_TELEPHONE="fr_FR.UTF-8"
LC_MEASUREMENT="fr_FR.UTF-8"
LC_IDENTIFICATION="fr_FR.UTF-8"
LC_ALL=fr_FR.UTF-8
Et enfin, installer et activer le service cron dans le terminal
sudo apt-get install cron
sudo cron start
CrĂ©ation du script dâenvoi de courriel automatique
Jâai dâabord créé une version abrĂ©gĂ©e du fichier actuel en format R Markdown, contenant uniquement les graphiques ainsi quâune courte explication de ceux-ci. Jâai tĂ©lĂ©versĂ© ce fichier sur mon instance GCE directement via RStudio. Ensuite, il faut tĂ©lĂ©verser tous les documents que nous avons utilisĂ©s jusquâici. Le plus simple est de les copier via un fichier .zip qui sera automatiquement dĂ©compressĂ© Ă lâarrivĂ©e.
Une fois que câest fait, voici le script Ă utiliser:
#Créer le rapport en html à partir du fichier .rmd
rapport <- rmarkdown::render("Rapport_Mensuel.Rmd")
#Envoyer le courriel
mailR::send.mail(from = "[email protected] ",
to = "[email protected] ",
subject = "Rapport de performance du mois",
body = "Rapport_Mensuel.html",
html = TRUE,
inline = TRUE,
smtp = list(host.name = "smtp.gmail.com", port = 465, user.name = "[email protected] ", passwd = "xxxxxxxx", ssl = TRUE),
authenticate = TRUE,
send = TRUE)
La toute derniĂšre Ă©tape est de sĂ©lectionner ce script dans lâonglet Tools â> Addins â> cronR afin de sĂ©lectionner la frĂ©quence dĂ©sirĂ©e!
Voila, vous reçevrez maintenant vos rapports dans votre boite courriel, toujours mis à jour, sans lever le petit doigt!