Web à Québec 2019 – Jour 2 et 3

Une deuxième journée où j’ai assisté à moins de conférences, car je devais présenter la mienne! Mais un jour 3 fantastique avec des conférenciers de calibre mondial vraiment inspirants.

Martin Boucher et Marie-Anne Courchesne

Quelles sont les retombées concrètes de la stratégie de communication numérique du Québec?

On entre dans la modernité

Une conférence qui début avec une grande question: comment passer de 800 sites distincts à un seul site unifié? La réponse… Un à la fois! À l’aide d’une approche itérative, la petite équipe web de 20 personnes du gouvernement du Québec a développé la première version de quebec.ca, en six mois, entièrement à l’interne. Pour un site v1 avec 500 pages de contenu, c’est tout un accomplissement pour notre cher gouvernement qu’on croit souvent bien lent! L’arme secrète? Une organisation du travail selon les principes agile.

Prochaine étape? Les 798 autres sites qui sont encore indépendants!

Faites un tour sur le nouveau site du gouvernement: https://www.quebec.ca/

Hugues Foltz

L’arrivée massive des technologies de rupture à l’ère du 4.0

Qu’est-ce qu’une innovation de rupture? Il s’agit d’une innovation qui a le pouvoir de renverser complètement les plus grands joueurs d’une industrie. Les exemples amenés sont multiples avec les classiques Tesla, Amazon, Uber et AirBnB, mais aussi d’autres moins connus comme Doctolib, en France, qui casse les barrières entre un patient et son médecin, ou Lemonade qui chambarde la vitesse à laquelle on peut reçevoir un paiement d’assurance.

Outre ces innovations, cette conférence a regardé très loin dans le futur, imaginant une société ou les humains peuvent se recentrer sur ce qu’ils font de mieux: la créativité, l’empathie et la gestion du flou. Selon lui, les emplois débilitants et répétitifs seront automatisés sans aucun doute, d’ici 10 à 20 ans.

Visitez le site de Vooban: https://vooban.com/

Colin Megil

AI & the future of the political party

Une toute nouvelle façon de réfléchir à la manière d’engager les citoyens dans le processus politique: voici la solution offerte par la plate-forme pol.is. À l’aide d’une stratégie d’apprentissage machine, il est maintenant possible de compresser les opinions qualitatives de milliers de personnes en un résumé qui est possible à comprendre pour nos élus. Déjà implanté à Taiwan, pol.is fait maintenant l’objet d’essais avec le gouvernement du Canada!

Visitez le site de pol.is: https://pol.is/

Simo Ahava

There’s data everywhere!

Simo, véritable superstar du web analytique et de GTM en particulier, nous a présenté sa vision d’une équipe d’analytiques qui est en mesure d’offrir de hautes performances: une équipe avec des membres ayant une expertise dans l’utilisation des outils, d’autres membres avec une expertise du marché dans lequel évolue l’entreprise, et une grande maturité de gens et de processus. C’est uniquement lorsque ces trois éléments se rejoignent dans une équipe que celle-ci travaillera ensemble à atteindre leurs buts partagés. Et la meilleure manière d’organiser le travail? Avec la méthode Scrum, même si celle-ci peut être malaisante au début!

Points bonis accordés pour la montée de lait contre le concept un peu arrièré d’une session dans Google Analytics.

Visitez le site de Simo Ahava: https://www.simoahava.com/

Tim Wilson

Statistics Intuition for the Marketer (and Why It Matters)

Probablement ma conférence chouchou des trois journées, Tim a réussi en 45 minutes à expliquer les principes fondamentaux de l’analyse statistique, en plus de présenter l’ANOVA, les analyses post-hoc, la régression linéaire et les séries temporelles. À mon avis, il s’agit d’un tour de force d’être en mesure d’expliquer ces méthodes d’une manière aussi claire et concise. Mais, outre ses explications limpides, il a même proposé un exemple d’application réelle de ces trois méthodes d’analyse statistique à des données web.

À mon avis, les connaissances en statistiques sont une lacune généralisée en marketing numérique, que ce soit au niveau des pratiques ou des outils. Espérons que cette présentation amorçe un petit vent de changement dans l’industrie!

Le profil LinkedIn de Tim Wilson: https://www.linkedin.com/in/tgwilson/

Dina Zielinski

A future for digital data in nature’s oldest storage device

ÇA c’est du Big Data!

Saviez-vous qu’il était possible de stocker des données numériques dans de l’ADN? Moi non plus! Avec une toute première étude sur le sujet en 1988, ce n’est que depuis 2007 que des recherches sérieuses sont entreprises sur cette méthode originale d’entreposer des données. Loin d’être prête à une utilisation par les consommateurs à cause de son prix élevé, 9000$US pour encoder et décoder 2Mo de données, une utilisation future pourrait être l’archivage des données les plus primordiales de l’humanité en raison de sa durée de vie de plus de 1000 ans dans des conditions d’archivage simples, et de sa densité d’information inégalée.

Visitez le profil LinkedIn de Dina Zielinski: https://www.linkedin.com/in/dinazielinski/

Jean-François Renaud

Budget optimal en marketing numérique : utiliser les données

On revient sur terre après la conférence sur le stockage des données dans l’ADN avec un sujet tout ce qu’il y a de plus plate (le principal intéressé l’a qualifié comme tel) mais tellement important: combien de dollars dépenser en marketing numérique. À l’aide d’une nouvelle méthode dévelopée chez Adviso, le Delta Opportunity Index (DOI), il est maintenant possible d’ajuster les budgets publicitaires en fonction de la demande pour les produits et services offerts. Pourquoi vendre des motoneiges uniquement lors d’une seule campagne en novembre, alors qu’il y a de l’intérêt pour celles-ci à des niveaux différents toute l’année? On comprends à quel point Jean-François adore le always-on et n’aime pas vraiment les campagnes. Mais à voir le succès qu’il obtient pour ses clients, on comprends qu’il a un peu raison!

Visitez le profil LinkedIn de Jean-François: https://www.linkedin.com/in/jeanfrancoisrenaud/

Lisez l’article scientifique sur la méthodologie DOI:
https://lp.adviso.ca/doi-waq2019

Rémy Savard

Gatsby – Performances irréelles, sécurité accrue et meilleure expérience de développement Web

Je ne suis pas développeur web, mais cette conférence a piqué ma curiosité car elle présente une des nombreuses technologies permettant de déployer un site sans serveur, hébergé uniquement sur des CDN, ce qui les rends tellements rapides que ça en est effectivement un peu irréel. Ce que j’ai retenu de cette présentation est qu’il est présentement possible d’utiliser cette technologie tout en conservant le CMS auquel nous sommes habitués, comme WordPress, Magento ou Shopify. Considérant la performance hallucinante qu’il est possible d’atteindre, ça vaut la peine de faire un peu plus de recherches, particulièrement pour les sites e-commerce où chaque seconde de chargement diminue le taux de conversion. Pour voir un site e-commerce de plus de 50 000 pages utilisant cette technologie, jetez un oeil ici, et attachez votre tuque avec du duct tape avant:
https://www.danielwellington.com/

Pour le profil LinkedIn de Rémy Savard: https://www.linkedin.com/in/remysavard

Vitaly Friedman

Bringing Personality Back To The Web

En 2019, les sites web modernes n’ont plus de personalité. Enfermés dans les codes stylistiques créés par le responsive et les frameworks de type bootstrap, et la diminution de la friction à tout prix, nous avons perdu ce qui était original, surprenant et mémorable du web d’autrefois. Il nous dis même qu’il s’ennuie du Flash Player! Toutefois, tout n’est pas perdu, car plusieurs exemples de sites webs modernes mémorables sont mis de l’avant:

http://dada-data.net – On ne comprends pas tout à fait l’objectif du site, mais c’est une véritable oeuvre d’art numérique.

https://www.sbs.com.au/theboat/ – Une bande dessinée numérique absolument fabuleuse.

https://www.bloomberg.com/features/elon-musk-goals/ – Ce qui arrive quand on donne 100% de la liberté créative aux designers.

J’ai trouvé cette présentation absolument superbe pour conclure une congrès sur le numérique, car elle était complètement éclatée et donnait envie de faire les choses différement! Et ne s’agit-il pas la de la raison d’être d’un congrès? Apprendre de nouvelles choses, et faire les choses mieux, mais aussi différentes des autres!

Visitez le site de Vitaly Friedman, Smashing Magazine:https://www.smashingmagazine.com/

Web à Québec – Jour 1

Je vous offre un aperçu des conférences auxquelles j’ai eu la chance d’assister lors de la première journée du Web à Québec!

Aaron Draplin – Draplin Design Co

Une créativité complètement éclatée.

Le fondateur de Draplin Design Co, Aaron Draplin, nous as parlé de sa vie et de la manière qu’il s’y est pris afin d’atteindre le style de vie qu’il désire d’une manière très éloquente.

Son style de design éclaté, très pop art, a été utilisé dans des projets du gouvernement des États-Unis, par la campagne de Bernie Sanders et par la Nasa.

Jetez un oeil au site web de Draplin Design Co.

Ross Simmonds – Content isn’t King anymore

Quelques exemples de plate-formes de distribution.

Nous avons eu droit à une démonstration expliquant pourquoi le contenu n’est plus roi, la distribution l’est! Des changements d’algorithmes continuels aux featured snippets qui gardent les visiteurs dans l’écosystème de Google, Ross démontre qu’il est risqué de garder tous ses oeufs dans le même panier et qu’il est pertinent de diversifier sa distribution. Il dit ainsi que Content isn’t King anymore, distribution is. Il suggère de trouver la communauté intéressée par notre sujet, de créer un contenu pertinent pour elle, et de la diffuser dans cette communauté.

Visitez le site de Ross Simmods: https://rosssimmonds.com/

Tin Kadoic – AirBnB

Designing designers time

C’est occupé, un designer.

Le titre de “designer” prends un tout autre sens de nos jours, et est plus large que celui de designer graphique, web, ou UI/UX. Tin Kadoic nous explique qu’un designer doit prendre en main sa carrière comme il designerait un produit, et qu’il doit être intentionel dans les aptitudes qu’il apprends. Il faut apprendre les éléments qui sont pertinents pour se rendre la où l’on veut aller.

Il as aussi fait un détour par sa vision d’un bon gestionnaire, en expliquant la différence entre un “Talker-Talker”, un “Talker-Maker” et un “Maker-Maker”. Il dit que les meilleurs gestionnaires sont les meilleurs contributeurs qui sont forcés de devenir gestionnaires afin d’arriver à leur fins, donc des “Makers-Makers” qui deviennent “Talker-Makers”. Il n’avait pas beaucoup d’éloges pour les “Talkers-Talkers”, toutefois!

Visitez le site web de Tin Kadoic: https://blackduke.com/

Daria Hvizdalova

AI != Robotics: Backend Enablement

Une conférence fascinante par cette ingénieure venue tout droit de la République Tchèque, elle a fait la différence entre l’automatisation des processus humains (Robotic Process Automation – RPA), possible entièrement par software, et l’automatisation industrielle, alliant software et hardware afin de remplacer l’humain dans des tâches trop dangereuses ou dans lesquelles l’humain est inneficient. Surprenament, les processus d’automatisation industrielle sont généralement de niveau 1, c’est à dire que les robots sont uniquements programmés afin de définir une tâche très spécifique, sans capacité d’adaptation ou de capteurs leur permettant de changer leur comportement en fonction de l’environnement. Elle a fait un mention des cobots, ces robots cohabitants avec les humains dans un cadre industriel, mais que ceux-ci n’étaient pas encore très efficaces.

Elle a aussi présenté les différences entre un narrow AI, capable de faire une seule tâche (ex. jouer aux échecs), et un broad AI, capable de raisonner et d’apprendre par lui-même. Nous sommes encore loin du broad AI!

Visitez la page LinkedIn de Daria: https://www.linkedin.com/in/dariahvizdalova

Lea Pica

The PICA Protocol™: Your Prescription for Healthy, Actionable Data Storytelling

Il ne faut pas juste vomir des buzzwords

Lea Pica est une sommitée dans le monde du data storytelling. Elle nous as présenté son protocole de détox des visualisations en entreprise à travers son protocole PICA: Purpose, Insights, Context, Aesthetics. Pour avoir de l’impact, une visualisation doit avoir un but précis, répondre à une question d’affaires. Elle doit amener un insight, un angle qui n’est pas visible immédiatement à partir des données. Celle-ci doit être analysée dans son contexte, c’est-à-dire qu’il faut vérifier s’il existe d’autres sources de données qui pourraient confirmer ou infirmer ce qui est présenté. Enfin, elle doit être présenté selon des critères d’esthétismes bien précis, afin que le message ne soit pas perdu dans le medium.

Visitez le site web de Lea Pica:http://leapica.com/

Préparation des données de l’article sur le tableau de bord automatisé

Jean-Philippe Allard

6 avril, 2019

Provenance des données

Pour la boutique en ligne, j’ai utilisé les données de l’année 2017 provenant du Google Merchandise Store, que j’ai manuellement exportées en fichiers csv. Une meilleure méthode serait d’utiliser une des nombreuses librairies permettant d’accéder au Analytics Reporting API. Toutefois, le demo account de Google Analytics ne permet pas l’accès par API, donc tous les fichiers requis ont été exportés à la mitaine.

Pour la boutique physique, j’ai créé un fichier d’exemple en modifiant extensivement des données d’un marchand du Royaume-Uni. Les données originales sont disponibles sur Kaggle.

J’ai aussi créé une page Kaggle pour les fichiers finaux de cette page.

Début du projet

Nous avons tout d’abord besoin des librairies suivantes:

  • tidyverse, une librairie de librairies contenant tout ce qu’on a besoin pour le data wrangling.
  • readr, une librairie plus intuitive et rapide pour lire les fichiers csv.
#Loading Packages
library(tidyverse)
library(readr)

La première étape est d’importer les données. Ici à partir d’un fichier local, mais il est aussi possible de faire des requêtes http à un fichier sur un serveur, ou des GET requests à un API avec la librairie httr. Par défaut, read_csv ne va pas parser des colonnes contenant des lettres en tant que colonne de type numérique, afin d’éviter de perdre des données. Il faut ainsi le demander formellement pour enlever les signes de $ ou de %.

#Importer les données
Online_original <- read_csv("All_Transactions_SKU.csv", 
    skip = 6)
KEY_TransactionID_Date <- read_csv("KEY_TransactionID_Date.csv")
KEY_SKU_ProductName <- read_csv("KEY_SKU_ProductName_GOOGLE.csv", 
    skip = 6)
KEY_SKU_Category <- read_csv("KEY_SKU_Category_Google.csv", 
    skip = 6)
#Parser les colonnes avec des $ ou des %
Online_original$"Product Revenue" <- parse_number(Online_original$"Product Revenue")
Online_original$"Avg. Price" <- parse_number(Online_original$"Avg. Price")

KEY_SKU_Category$"Product Revenue" <- parse_number(KEY_SKU_Category$"Product Revenue")
KEY_SKU_Category$"Avg. Price" <- parse_number(KEY_SKU_Category$"Avg. Price")
KEY_SKU_Category$"Product Refund Amount" <- parse_number(KEY_SKU_Category$"Product Refund Amount")
KEY_SKU_Category$"Basket-to-Detail Rate" <- parse_number(KEY_SKU_Category$"Basket-to-Detail Rate")
KEY_SKU_Category$"Buy-to-Detail Rate" <- parse_number(KEY_SKU_Category$"Buy-to-Detail Rate")

KEY_SKU_ProductName$"Product Revenue" <- parse_number(KEY_SKU_ProductName$"Product Revenue")
KEY_SKU_ProductName$"Avg. Price" <- parse_number(KEY_SKU_ProductName$"Avg. Price")
KEY_SKU_ProductName$"Product Refund Amount" <- parse_number(KEY_SKU_ProductName$"Product Refund Amount")
KEY_SKU_ProductName$"Basket-to-Detail Rate" <- parse_number(KEY_SKU_ProductName$"Basket-to-Detail Rate")
KEY_SKU_ProductName$"Buy-to-Detail Rate" <- parse_number(KEY_SKU_ProductName$"Buy-to-Detail Rate")

KEY_TransactionID_Date$"Revenue" <- parse_number(KEY_TransactionID_Date$"Revenue")
KEY_TransactionID_Date$"Tax" <- parse_number(KEY_TransactionID_Date$"Tax")
KEY_TransactionID_Date$"Delivery" <- parse_number(KEY_TransactionID_Date$"Delivery")
KEY_TransactionID_Date$"Refund Amount" <- parse_number(KEY_TransactionID_Date$"Refund Amount")

Enfin, nous allons filtrer les données des différents fichiers importés afin de conserver un seul type de catégories (il y en avait trois qui se dupliquaients), des Transaction ID uniques pour les dates et des Product SKU uniques pour les informations sur les produits. Puis, nous allons joindre toutes les tables en une seule table contenant toutes les informations, et enfin, retirer les informations qui ne sont pas pertinentes.

#Filtrer les données de la table catégorie pour conserver uniquement un type de catégorie
KEY_SKU_Category <- filter(KEY_SKU_Category, 
                          `Product Category (Enhanced E-commerce)` == "Accessories"|
                          `Product Category (Enhanced E-commerce)` == "Android"|
                          `Product Category (Enhanced E-commerce)` == "Apparel"|
                          `Product Category (Enhanced E-commerce)` == "Backpacks"|
                          `Product Category (Enhanced E-commerce)` == "Bags"|
                          `Product Category (Enhanced E-commerce)` == "Bottles"|
                          `Product Category (Enhanced E-commerce)` == "Drinkware"|
                          `Product Category (Enhanced E-commerce)` == "Fun"|
                          `Product Category (Enhanced E-commerce)` == "Gift Cards"|
                          `Product Category (Enhanced E-commerce)` == "Google"|
                          `Product Category (Enhanced E-commerce)` == "Headgear"|
                          `Product Category (Enhanced E-commerce)` == "Housewares"|
                          `Product Category (Enhanced E-commerce)` == "Lifestyle"|
                          `Product Category (Enhanced E-commerce)` == "More Bags"|
                          `Product Category (Enhanced E-commerce)` == "Nest"|
                          `Product Category (Enhanced E-commerce)` == "Nest-Canada"|
                          `Product Category (Enhanced E-commerce)` == "Nest-USA"|
                          `Product Category (Enhanced E-commerce)` == "Notebooks & Journals"|
                          `Product Category (Enhanced E-commerce)` == "Office"|
                          `Product Category (Enhanced E-commerce)` == "Waze"
                          )

#Enlever les duplicatas de KEY_TransactionID_Date (Transaction ID 45426 et 33673, probablement des modifications manuelles à des commandes eronnées)
KEY_TransactionID_Date <- distinct(KEY_TransactionID_Date, `Transaction ID`, .keep_all = TRUE)

#Enlever les duplicatas de KEY_SKU_ProductName
KEY_SKU_ProductName <- distinct(KEY_SKU_ProductName, `Product SKU`, .keep_all = TRUE)

#Enlever les duplicatas de KEY_SKU_Category
KEY_SKU_Category <- distinct(KEY_SKU_Category, `Product SKU`, .keep_all = TRUE)

#Joindre les données de toutes les tables
Online_joined <- left_join(Online_original, KEY_TransactionID_Date, by = "Transaction ID")
Online_joined <- left_join(Online_joined, KEY_SKU_ProductName, by = "Product SKU")
Online_joined <- left_join(Online_joined, KEY_SKU_Category, by = "Product SKU")

#Conserver uniquement les colonnes pertinentes et les réorganiser
Online <- select(Online_joined, "Transaction ID", "Date", "Product SKU", "Product", "Product Category (Enhanced E-commerce)", "Quantity.x", "Avg. Price.x", "Revenue", "Tax", "Delivery" )

#Renommer les colonnes avec un indicateur de doublon maintenant qu'il n'est plus nécéssaire
colnames(Online)[colnames(Online)=="Quantity.x"] <- "Quantity"
colnames(Online)[colnames(Online)=="Avg. Price.x"] <- "Avg. Price"

Voila notre fichier d’exemple de transactions d’une boutique en ligne fin prêt!

Création du fichier de magasin physique

Considérant que je rends publiques les données de cette exemple, je ne peux utiliser les données privées sur lesquelles je fais normalement ce genre de travail. J’ai donc décidé d’utiliser un fichier provenant de Kaggle, et de l’ajuster à la situation présente.

Nous allons tout d’abord lire notre fichier original “UK Retailer” provenant de Kaggle: https://www.kaggle.com/carrie1/ecommerce-data

#Lire le fichier original
Retail_original <- read_csv("UK retailer data.csv")
#Retirer les retours
Retail_clean <- Retail_original %>% filter(Quantity > 0)

Maintenant, nous allons modifier les dates d’achat afin d’obtenir uniquement des dates du 1ier janvier 2018 au 31 décembre 2018, comme dans notre fichier e-commerce

#Nous chargeons la librairie lubridate qui permet de mieux jouer avec les dates
library(lubridate)
#Convertir la colone de type caractères en type date, en enlevant l'information d'heure
Retail_clean$InvoiceDate <- as.Date(Retail_clean$InvoiceDate, format = "%m/%d/%Y")

#Ajouter 1 mois puis 6 ans, et retirer 1 journée à toutes les dates
Retail_clean$InvoiceDate <- Retail_clean$InvoiceDate %m+% months(1)
Retail_clean$InvoiceDate <- Retail_clean$InvoiceDate %m+% years(6)
Retail_clean$InvoiceDate <- Retail_clean$InvoiceDate %m-% days(1)

#Retirer tout ce qui n'est pas en 2017
Retail_clean <- Retail_clean %>% filter(InvoiceDate < "2018-01-01" & InvoiceDate > "2016-12-31")

Maintenant, nous allons simuler les quantités achetées et les stocker en attendant de les appliquer sur notre fichier.

#Visualiser les quantités achetées sur la boutique en ligne en retirant les outliers
freqs <- Online %>% group_by(Quantity) %>% tally()

#Enlever les outliers
freqs <- freqs %>% filter(Quantity <= 10)

#Visualiser
ggplot(data = freqs, aes(x = Quantity, y = n)) + 
  geom_point() +
  xlim(0, 10)
#Nous allons maintenant créer des chiffres aléatoires de quantité selon la distribution de la boutique en ligne pour nos données de magasin physique 

den <- density(Online$Quantity, na.rm = TRUE)

den_qty <- round(sample(Online$Quantity, 505882, replace=TRUE) + rnorm(505882, 0, den$bw), digits = 0)

den_qty <- as.data.frame(den_qty)

#Il faut remplacer les NA créés par l'arrondissement par 1...
den_qty <- den_qty %>% mutate(den_qty = if_else(is.na(den_qty), 1, den_qty))

#Voyons voir si la distribution semble similaire à l'original...
freqs2 <- den_qty %>% group_by(den_qty) %>% tally()

#Enlever les outliers
freqs2 <- freqs2 %>% filter(den_qty <= 10)

ggplot(data = freqs2, aes(x = den_qty, y = n)) + 
  geom_point() +
  xlim(0, 10)
#Ça semble le cas! 

Nous allons maintenant joindre les données pour notre fichier fictif en conservant uniquement les colonnes Date – Stock Code et Invoice Number, et en utilisant nos données précédentes pour les quantités achetées. On simule un fichier avec très peu d’informations, que nous enrichirons dans l’exemple.

#Création du fichier
Retail <- data.frame(InvoiceNo = Retail_clean$InvoiceNo, InvoiceDate = Retail_clean$InvoiceDate, StockCode = Retail_clean$StockCode, Quantity = den_qty$den_qty, stringsAsFactors = FALSE)

Enfin, nous allons réduire le nombre de SKU dans le fichier Retail afin qu’il soit égal au nombre de SKUS du fichier Online, ce qui nous permettra de créer la clé entre les deux fichiers dans l’exemple qui suivra.

#Compter le nombre de SKUs dans le fichier Online
nrow(as.data.frame(unique(Online$`Product SKU`)))
## [1] 1178
#1178 SKUs uniques. 

#Compter le nombre de SKUs dans le fichier Retail
nrow(as.data.frame(unique(Retail$StockCode)))
## [1] 3931
#3931 SKUs uniques. C'est trop!

#Il faut diminuer de près du trois quart le nombre de SKUs du fichier Retail, mais on ne veut pas trop diminuer le nombre de transactions. Nous allons donc commencer par un clean-up des SKUs qui ont des variations à l'aide d'une lettre pour conserver uniquement des SKUs numérique.

Retail$StockCode <- str_replace(Retail$StockCode, "[a-zA-Z]\\b$", "")

#Combien de SKUs maintenant? 
nrow(as.data.frame(unique(Retail$StockCode)))
## [1] 3309
#3309 SKUs. Encore trop. 

#Enlevons les SKUs qui sont des erreurs (BANKCHARGES, POS, etc.) en retirant aussi la transaction au complet. 

Retail <- Retail %>% filter(str_detect(StockCode, "\\d\\d\\d\\d\\d"))

#On peut maintenant parser la colonne comme numérique car il n'y a plus de lettres

Retail$StockCode <- parse_number(Retail$StockCode)

#Combien de SKUs maintenant? 
nrow(as.data.frame(unique(Retail$StockCode)))
## [1] 3288
#3290 SKUs. Nous allons retirer des commandes de façon arbitraire pour atteindre notre objectif (il faut se rappeler que c'est uniquement un fichier d'exemple!)

#Créons un tableau avec nos SKUs uniques, afin de voir quels sont les 1178 premiers SKUS
unique_retail_SKUS <- as.data.frame(sort(unique(Retail$StockCode)))

#Les 1178 premiers SKUs sont situés de 10002 à 22314. Nous allons exclure toutes les lignes comportant des achats de SKUs à l'extérieur de cette plage. 

Retail <- Retail %>% filter(StockCode <= 22314)

#Combien de SKUs maintenant? 
nrow(as.data.frame(unique(Retail$StockCode)))
## [1] 1178
#1178 SKUs. Nous avons atteint notre objectif! 

Voila! Nos deux fichiers sont maintenants prêts pour être utilisés dans l’exemple. Visitez le ici: Exemple de tableau de bord automatisé avec R et Google Compute Engine

Exemple de tableau de bord automatisé

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!