Le Marketing de contenu amène-t-il du trafic sur mon site Web ? Logiciel R

Evénements significatifs et dates des articles en 2014

Et voilà la question à 1 MD de $ que tout Web Marketeur et dans ce cas, tout rédacteur Web se pose : Est-ce que mon travail sert à quelque chose ?

Plus particulièrement, nous allons nous intéresser à la contribution au trafic d’un site Web, notamment en SEO, qu’apporte la création d’articles et la mise en œuvre d’actions Web Marketing connexes .

On entend par actions connexes des actions du type :

  • distribution du contenu sur les réseaux sociaux
  • récupération de liens entrants suite à la publication de l’article
  • emailing pour annoncer l’article
  • Eventuellement publicité Adwords, FaceBook, Instagram…
  • ….

Avec la console Google Analytics

Une première approche pour répondre à notre question sur la contribution d’un article serait de le vérifier dans Google Analytics.

Rem : Comme dans d’autres articles précédents nous illustrerons notre propos avec des données issues du site de l’association Networking Morbihan.

Pour avoir une idée de la contribution d’un article sur une longue période, nous vous conseillons d’étudier un article ancien.

Dans notre cas, l’article « rentrée-2011 » a été publié le 1er septembre 2011. C’est l’un des premiers articles dont la date est certaine. Notre période d’étude va du 1 juillet 2011 au 31 décembre 2018 soit 7 ans 1/2.

Pour avoir une vue du trafic d’une seule page , allez sur Comportement   -> Contenus du site -> Toutes les Pages, puis cliquez sur une page ou un article ancien qui vous intéresse.

Trafic article  "rentrée 2011"
Trafic article « rentrée 2011 »

Comme on le voit sur le graphique, cet article a été vu essentiellement
au moment ou il a été publié. Il faut dire qu’il a bénéficié au moment de la
publication :

  • D’une mise en évidence sur la page d’accueil du site.
  • D’une communication via email.
  • De la création d’un événement dans Viadéo.
  • De la diffusion sur FaceBook.
  • De la diffusion sur Twitter.
  • De liens externes créés par les membres

Notons toutefois, que l’on ne peut pas évaluer dans Google Analytics si le trafic supplémentaire constaté est significatif par rapport au trafic « normal ou de base » du site, ou au trafic attendu du site si nous n’avions pas publié l’article et les articles précédents. 

Il n’est pas possible non plus de comparer plusieurs articles ou encore d’avoir une information synthétique sur tous les articles.

Avec R :

C’est pourquoi il est intéressant de travailler avec des outils et langages statistiques tiers comme R ou Python. Dans cet article nous traiterons le problème avec R.

La question précise que l’on va se poser ici est : « Est ce que, après avoir publié un article, j’obtiens des pics de trafic significatifs le jour même ou les jours suivants ? »

Pour avoir une idée de cela de façon visuelle, nous allons ajouter les dates de publications de nos articles à la courbe des pages vues et des événements significatifs réalisée précédemment :

Evénements significatifs depuis 2011
Evénements significatifs depuis 2011

De quoi aurons-nous besoin ?

Logiciel R

Bien que je l’ai indiqué partout précédemment je vous redonne l’information : afin de pouvoir tester le code source de cette démonstration nous vous invitons à télécharger Le Logiciel R sur ce site https://cran.r-project.org/, ainsi que l’environnement de développement RStudio ici : https://www.rstudio.com/products/rstudio/download/.

Jeu de données

Vous pouvez soit tester le code source avec nos données que vous pouvez récupérer ici en ce qui concerne les données de trafic récupérées de Google Analytics puis nettoyées. Et ici en ce qui concerne les dates des articles publiés sur notre site.

Soit, si vous souhaitez utiliser vos données, nous vous conseillons de suivre les étapes décrites dans les articles précédents suivants :

Code Source

Vous pouvez copier/coller les bouts de code dans les zones de code ou sinon tout télécharger depuis notre Github : https://github.com/Anakeyn/ArticlesPubDateVSSignTrafficR.

Récupération des données de pages vues :

Les données sont récupérées à partir du fichier dfPageViews.csv et mises en forme pour avoir un enregistrement par jour. Les « anomalies » i.e. les jours avec un trafic significativement important sont aussi repérées.

##########################################################################
# Auteur : Pierre Rouarch 2019
# Vérification que les actions Marketing menées sont en rapport  avec le 
# trafic significatif détecté. 
# Pour illustrer notre propos nous utiliserons les données de 
# l'association Networking-Morbihan 
##########################################################################
#Packages et bibliothèques utiles (décommenter si besoin)
##########################################################################
#install.packages("lubridate")  #si vous ne l'avez pas
#install.packages("tseries")
#install.packages("devtools")
#devtools::install_github("twitter/AnomalyDetection")  #pour anomalyDetection de Twitter
#install.packages("XML")
#install.packages("stringi")
#install.packages("BSDA")
#install.packages("BBmisc")
#install.packages("stringi")
#install.packages("FactoMineR")
#install.packages("factoextra")
#install.packages("rcorr")

#install.packages("lubridate")  #si vous ne l'avez pas
library (lubridate) #pour yday
#library(tseries) #pour ts
library(AnomalyDetection) #pour anomalydetectionVec
library(XML) # pour xmlParse
#library(stringi) #pour stri_replace_all_fixed(x, " ", "")
#library(BSDA)  #pour SIGN.test 
#library(BBmisc) #pour which.first
#install.packages("stringi")
library(stringi) #pour stri_detect
#library(ggfortify)  #pour ploter autoplot type ggplot
#install.packages("tidyverse")  #si vous ne l'avez pas #pour gggplot2, dplyr, tidyr, readr, purr, tibble, stringr, forcats 
#install.packages("forecast") #pour ma
#Chargement des bibliothèques utiles
library(tidyverse) #pour gggplot2, dplyr, tidyr, readr, purr, tibble, stringr, forcats 
library(forecast)  #pour  arima, ma, tsclean


##########################################################################
# Récupération du Jeu de données nettoyé
##########################################################################
dfPageViews <- read.csv("dfPageViews.csv", header=TRUE, sep=";") 
#str(dfPageViews) #verif
dfPageViews$date <- as.Date(dfPageViews$date,format="%Y-%m-%d")
#str(dfPageViews) #verif

#Données par jour
dfDatePV <- as.data.frame(dfPageViews$date)
colnames(dfDatePV)[1] <- "date"
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y")) %>%               #creation de la variable year
  mutate(dayOfYear = yday(date))                  #creation de la variable dayOfYear



#Sauvegarde de daily_data au besoin
#str(daily_data) #verif
write.csv2(daily_data, file = "DailyDataClean.csv",  row.names=FALSE) #sauvegarde en csv avec ;


##########################################################################
# Détections des événements significatifs - Anomaly Detection
##########################################################################
#help(AnomalyDetectionVec) #pour voir la doc
#recherche des anomalies sur la variable Pageviews  
res <- AnomalyDetectionVec(daily_data[,2], max_anoms=0.05, 
                           direction='both', alpha=0.05, plot=FALSE, period=30)
nrow(res$anoms) #98

daily_data$index <- as.integer(as.character(rownames(daily_data))) #pour etre raccord avec res$anoms.
#il faut que l'on récupère cnt_ma30 de daily_data pour afficher les anomalies sur la courbe
myAnoms <- left_join(res$anoms, daily_data, by="index")
#Minimum de pages vues pour un événement significatif depuis 2011.
min(myAnoms$Pageviews)
#89

Récupération des articles :

En fait, ce qui nous intéresse est de récupérer la date de publication de tous les articles publiés qui nous intéressent.

Si vous utilisez un CMS (Content Management System), vous devez pouvoir faire cela assez facilement.

Dans notre cas, nous utilisons WordPress. WordPress dispose d’un outil d’export qui permet de récupérer des fichiers .xml d’articles.

Un petit bémol toutefois : l’outil d’export ne permet pas de sélectionner les catégories d’articles qui nous intéressent en une seule fois. Nous seront donc obligés de faire plusieurs exports et de récupérer des données dans 6 fichiers .xml.

Comme on l’a vu précédemment vous pouvez récupérer les 6 fichiers .xml sur notre github : https://github.com/Anakeyn/ArticlesPubDateVSSignTrafficR/raw/master/WP.articles.zip

Dézipper l’archive et placez les fichiers dans le répertoire de votre programme R.

################################################################################
# Récupération des Articles par categories. Les catégories qui nous intéressent 
# sont celles pour lesquelles les administrateurs ont créé des articles qu'ils 
# souhaitaient mettre en avant :   
# "A la une", 
# "Actualités", 
# "Les Autres Rendez-vous",
# "Networking Apéro", 
# "Networking Conseil"
# "Networking Cotravail", 
# L'export de WordPress ne permet pas d'exporter un choix de catégories, on est 
# obligé de faire catégorie par catégorie
################################################################################
#Categorie A La une
xmlNW56Articles <- xmlParse("NW56.WP.ALaUne.xml")
links <- setNames(xmlToDataFrame(nodes = getNodeSet(xmlNW56Articles, "//item//link"), stringsAsFactors = FALSE), "link")
pubDates <- setNames(xmlToDataFrame(nodes=getNodeSet(xmlNW56Articles, "//item//pubDate"), stringsAsFactors = FALSE), "pubDate")
dfNW56AlaUne <- cbind(pubDates, links)
#Categorie "Actualités",
xmlNW56Articles <- xmlParse("NW56.WP.Actualites.xml")
links <- setNames(xmlToDataFrame(nodes = getNodeSet(xmlNW56Articles, "//item//link"), stringsAsFactors = FALSE), "link")
pubDates <- setNames(xmlToDataFrame(nodes=getNodeSet(xmlNW56Articles, "//item//pubDate"), stringsAsFactors = FALSE), "pubDate")
dfNW56Actualites <- cbind(pubDates, links)
#Categorie ""Les Autres Rendez-vous",
xmlNW56Articles <- xmlParse("NW56.WP.LesAutresRDV.xml")
links <- setNames(xmlToDataFrame(nodes = getNodeSet(xmlNW56Articles, "//item//link"), stringsAsFactors = FALSE), "link")
pubDates <- setNames(xmlToDataFrame(nodes=getNodeSet(xmlNW56Articles, "//item//pubDate"), stringsAsFactors = FALSE), "pubDate")
dfNW56LesAutresRDV <- cbind(pubDates, links)
#Categorie "Networking Apéro",
xmlNW56Articles <- xmlParse("NW56.WP.NetworkingApero.xml")
links <- setNames(xmlToDataFrame(nodes = getNodeSet(xmlNW56Articles, "//item//link"), stringsAsFactors = FALSE), "link")
pubDates <- setNames(xmlToDataFrame(nodes=getNodeSet(xmlNW56Articles, "//item//pubDate"), stringsAsFactors = FALSE), "pubDate")
dfNW56NetworkingApero <- cbind(pubDates, links)
#Categorie "Networking Conseil",
xmlNW56Articles <- xmlParse("NW56.WP.NetworkingConseil.xml")
links <- setNames(xmlToDataFrame(nodes = getNodeSet(xmlNW56Articles, "//item//link"), stringsAsFactors = FALSE), "link")
pubDates <- setNames(xmlToDataFrame(nodes=getNodeSet(xmlNW56Articles, "//item//pubDate"), stringsAsFactors = FALSE), "pubDate")
dfNW56NetworkingConseil <- cbind(pubDates, links)
#Categorie "Networking Cotravail",
xmlNW56Articles <- xmlParse("NW56.WP.NetworkingCotravail.xml")
links <- setNames(xmlToDataFrame(nodes = getNodeSet(xmlNW56Articles, "//item//link"), stringsAsFactors = FALSE), "link")
pubDates <- setNames(xmlToDataFrame(nodes=getNodeSet(xmlNW56Articles, "//item//pubDate"), stringsAsFactors = FALSE), "pubDate")
dfNW56NetworkingCotravail <- cbind(pubDates, links)

#Groupement de toutes les catégories
dfNW56Articles <- rbind(dfNW56AlaUne, dfNW56Actualites, dfNW56LesAutresRDV, 
                        dfNW56NetworkingApero,dfNW56NetworkingConseil, dfNW56NetworkingCotravail )

#verif
nrow(dfNW56Articles)
dfNW56Articles <- unique(dfNW56Articles)  #pour éviter les pages dans plusieurs catégories
nrow(dfNW56Articles) #verif - 104 Articles 
str(dfNW56Articles)  #pb : les dates sont dans un format anglophone et en chaines de caractères
#et non pas dans un format standard universel du genre "2017-10-19" ou en datetime

Problème de date :

Nous avons besoin de la date pour merger le jeu de données des articles avec les dates et le jeu de données du trafic journalier.

Le problème est que la date dans les articles est sous la forme anglophone « Tue, 04 Nov 2014 12:23:53 +0000 » et en chaîne de caractère, et ne peut pas être décodée si votre ordinateur est en « locale » France.

On va devoir passer en anglais le temps du décodage.

##############################################################################
#transformation de pubDate en une date exploitable
#Attention si vous êtes déjà en anglais commenter cette ligne
Sys.getlocale()
#Attention si vous êtes déjà en anglais commenter cette ligne :
#on passe en anglais
Sys.setlocale("LC_ALL","English")  #pour pouvoir traduire les dates
#Mon, 20 Jun 2011 13:18:18 +0000
dfNW56Articles$dateString <- as.character(as.Date(substr(dfNW56Articles$pubDate, 1, 16), format = "%a, %d %b %Y"))
#Attention si vous souhaitez rester en anglais commenter cette ligne :
Sys.setlocale("LC_ALL","French")  #pour revenir en français

Merge des articles et des données de trafic dans myArticle

######## mettre aussi une DateString aussi dans daily_data pour faire le join #############
daily_data$dateString  <- as.character(daily_data$date)

myArticles <- inner_join(dfNW56Articles, daily_data, by="dateString")
myArticles <- myArticles[order(myArticles$index),] 
#Verifs
str(myArticles) #95
#View(myArticles)
#Si besoin
write.csv2(myArticles, file = "myArticles.csv",  row.names=FALSE)
#Date et/ou numéro de jour devrait suffire 
myArticlesDate <- myArticles[,c("date","index")]
#Eventuellement 
colnames(myArticlesDate) <-c("date","NumDay") 
write.csv2(myArticlesDate, file = "myArticlesDate.csv",  row.names=FALSE)

Graphique des événements vs date de publication depuis 2011.

Sur ce graphique on compare les événement significatifs détectés (voir article précédent) aux dates de publications des articles.

##########################################################################
# Graphiques des anomalies (événements) et dates de publication  
##########################################################################
#sur toute la période
ggplot() + 
  geom_line(data=daily_data, aes(index, Pageviews), color='grey') + 
  geom_point(data=res$anoms , aes(index, anoms), color='red') +
  geom_point(data=myArticles , aes(index, Pageviews), color='blue', shape = 3, size=3) +
  xlab("Numéro de Jour") +
  ylab("Nbre pages vues / jour") +
  labs(title = paste(nrow(res$anoms),"événements (ronds rouges) pour", nrow(myArticles),"articles (croix bleues) : "),
       subtitle = "Les articles des dernières années ne semblent pas suivis d'effets",
       caption = "Evénements significatifs et publications des articles depuis 2011")

#sauvegarde du dernier ggplot :
ggsave(filename = "Events-Articles-s2011.jpeg",  dpi="print") 

Evénements significatifs et dates de publication des articles
Evénements significatifs et dates de publication des articles sur toute la période

Comme on le voit sur la courbe, vers les années 2017 2018 (numéros de jours importants) les actions ne sont plus très efficaces. Pour les autres périodes, ce n’est pas très lisible. C’est pourquoi nous allons diviser le graphique par années.

Graphiques par années

##########################################################################
# Graphiques des événements et dates de publication  par années
##########################################################################

#Affichage pour 2011
myYear = "2011"
ggplot() + 
  geom_line(data=daily_data[which(daily_data$year == myYear), ] , aes(dayOfYear, Pageviews), color='grey') + 
  geom_point(data=myAnoms[which(myAnoms$year == myYear), ]  , aes(dayOfYear, anoms), color='red', shape=1, size=3 ) +
  geom_point(data=myArticles[which(myArticles$year == myYear),] , aes(dayOfYear, Pageviews), color='blue',shape = 3, size=3) +
  xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour") +
  ggtitle(label = paste("Evénements significatifs (ronds rouges) et \n publications des articles (croix bleues) en ", myYear))
ggsave(filename = stri_replace_all_fixed(paste("Events-Articles-",myYear,".jpeg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot.

#Affichage pour 2012
myYear = "2012"
ggplot() + 
  geom_line(data=daily_data[which(daily_data$year == myYear), ] , aes(dayOfYear, Pageviews), color='grey') + 
  geom_point(data=myAnoms[which(myAnoms$year == myYear), ]  , aes(dayOfYear, anoms), color='red', shape=1, size=3 ) +
  geom_point(data=myArticles[which(myArticles$year == myYear),] , aes(dayOfYear, Pageviews), color='blue',shape = 3, size=3) +
  xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour") +
  ggtitle(label = paste("Evénements significatifs (ronds rouges) et \n publications des articles (croix bleues) en ", myYear))

ggsave(filename = stri_replace_all_fixed(paste("Events-Articles-",myYear,".jpeg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot.

#Affichage pour 2013
myYear = "2013"
ggplot() + 
  geom_line(data=daily_data[which(daily_data$year == myYear), ] , aes(dayOfYear, Pageviews), color='grey') + 
  geom_point(data=myAnoms[which(myAnoms$year == myYear), ]  , aes(dayOfYear, anoms), color='red', shape=1, size=3 ) +
  geom_point(data=myArticles[which(myArticles$year == myYear),] , aes(dayOfYear, Pageviews), color='blue',shape = 3, size=3) +
  xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour") +
  ggtitle(label = paste("Evénements significatifs (ronds rouges) et \n publications des articles (croix bleues) en ", myYear))
ggsave(filename = stri_replace_all_fixed(paste("Events-Articles-",myYear,".jpeg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot.

#Affichage pour 2014
myYear = "2014"
ggplot() + 
  geom_line(data=daily_data[which(daily_data$year == myYear), ] , aes(dayOfYear, Pageviews), color='grey') + 
  geom_point(data=myAnoms[which(myAnoms$year == myYear), ]  , aes(dayOfYear, anoms), color='red', shape=1, size=3 ) +
  geom_point(data=myArticles[which(myArticles$year == myYear),] , aes(dayOfYear, Pageviews), color='blue',shape = 3, size=3) +
  ggtitle(label = paste("Evénements significatifs et publications des articles en ", myYear))
xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour") +
  ggtitle(label = paste("Evénements significatifs (ronds rouges) et \n publications des articles (croix bleues) en ", myYear))

#Affichage pour 2015
myYear = "2015"
ggplot() + 
  geom_line(data=daily_data[which(daily_data$year == myYear), ] , aes(dayOfYear, Pageviews), color='grey') + 
  geom_point(data=myAnoms[which(myAnoms$year == myYear), ]  , aes(dayOfYear, anoms), color='red', shape=1, size=3 ) +
  geom_point(data=myArticles[which(myArticles$year == myYear),] , aes(dayOfYear, Pageviews), color='blue',shape = 3, size=3) +
  xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour") +
  ggtitle(label = paste("Evénements significatifs (ronds rouges) et \n publications des articles (croix bleues) en ", myYear))

ggsave(filename = stri_replace_all_fixed(paste("Events-Articles-",myYear,".jpeg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot.


#Affichage pour 2016
myYear = "2016"
ggplot() + 
  geom_line(data=daily_data[which(daily_data$year == myYear), ] , aes(dayOfYear, Pageviews), color='grey') + 
  geom_point(data=myAnoms[which(myAnoms$year == myYear), ]  , aes(dayOfYear, anoms), color='red', shape=1, size=3 ) +
  geom_point(data=myArticles[which(myArticles$year == myYear),] , aes(dayOfYear, Pageviews), color='blue',shape = 3, size=3) +
  xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour") +
  ggtitle(label = paste("Evénements significatifs (ronds rouges) et \n publications des articles (croix bleues) en ", myYear))

ggsave(filename = stri_replace_all_fixed(paste("Events-Articles-",myYear,".jpeg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot.


#Affichage pour 2017
myYear = "2017"
ggplot() + 
  geom_line(data=daily_data[which(daily_data$year == myYear), ] , aes(dayOfYear, Pageviews), color='grey') + 
  geom_point(data=myAnoms[which(myAnoms$year == myYear), ]  , aes(dayOfYear, anoms), color='red', shape=1, size=3 ) +
  geom_point(data=myArticles[which(myArticles$year == myYear),] , aes(dayOfYear, Pageviews), color='blue',shape = 3, size=3) +
  xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour") +
  ggtitle(label = paste("Evénements significatifs (ronds rouges) et \n publications des articles (croix bleues) en ", myYear))

ggsave(filename = stri_replace_all_fixed(paste("Events-Articles-",myYear,".jpeg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot.


#Affichage pour 2018
myYear = "2018"
ggplot() + 
  geom_line(data=daily_data[which(daily_data$year == myYear), ] , aes(dayOfYear, Pageviews), color='grey') + 
  geom_point(data=myAnoms[which(myAnoms$year == myYear), ]  , aes(dayOfYear, anoms), color='red', shape=1, size=3 ) +
  geom_point(data=myArticles[which(myArticles$year == myYear),] , aes(dayOfYear, Pageviews), color='blue',shape = 3, size=3) +
  xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour") +
  ggtitle(label = paste("Evénements significatifs (ronds rouges) et \n publications des articles (croix bleues) en ", myYear))

ggsave(filename = stri_replace_all_fixed(paste("Events-Articles-",myYear,".jpeg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot.

##########################################################################
# MERCI pour votre attention !
##########################################################################

Pour 2011 (à partir de juillet)

Evénements significatifs et dates des articles en 2011
Evénements significatifs et dates des articles en 2011

Pour 2012

Evénements significatifs et dates des articles en 2012
Evénements significatifs et dates des articles en 2012

Pour 2013

Evénements significatifs et dates des articles en 2013
Evénements significatifs et dates des articles en 2013

Pour 2014

Evénements significatifs et dates des articles en 2014
Evénements significatifs et dates des articles en 2014

PoUR 2015

Evénements significatifs et dates des articles en 2015Evénements significatifs et dates des articles en 2015
Evénements significatifs et dates des articles en 2015

Pour 2016

Evénements significatifs et dates des articles en 2016
Evénements significatifs et dates des articles en 2016

Pour 2017

Evénements significatifs et dates des articles en 2017
Evénements significatifs et dates des articles en 2017

Pour 2018

Evénements significatifs et dates des articles en 2018
Evénements significatifs et dates des articles en 2018

La visualisation par années confirme ce que l’on constatait sur le graphique global. Il semble que parfois la publication soit suivie d’effets, parfois non. Ceci nécessiterait une étude plus approfondie des différents cas.

Dans un prochain article nous nous intéresserons plutôt à la « durée de vie » des articles sur un site Web.

Merci pour vos témoignages et suggestions en commentaires

A bientôt,

Pierre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.