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