Quelle est la durée de vie de mes articles sur mon site ? R – I

Distribution PV articles marketing mois 1

Dans cet article nous allons nous intéresser à la « durée de vie » de nos articles sur un site Web.

Autrement dit, la durée où, en moyenne, mes articles « apportent » du trafic sur mon site. Nous l’exprimerons ici en nombre de mois.

Cette problématique peut être intéressante, par exemple, si l’on veut comparer l’efficacité de différents rédacteurs, de différentes thématiques ou encore de différents sites …

Cet article étant un peu long il sera divisé en deux parties.

Comment allons nous procéder ?

Logiciel R :

Dans cet article nous allons utiliser R. Dans un prochain article nous procéderons avec Python.

Si vous ne l’avez pas encore fait, téléchargez le Logiciel R sur le site du CRAN 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

Comme précédemment, nous utiliserons les données du site de Networking Morbihan. On partira des données de trafic récupérées de Google Analytics et nettoyées et de la liste des « articles marketing »*.

Si vous souhaitez utiliser vos données, suivez les étapes décrites dans les articles précédents suivants, pour bien comprendre la démarche et créer vos propres fichiers :

* Pour le trafic « articles marketing » on considèrera 3 types de pages vues liées aux articles marketing, de plus en plus restrictives :

  • AM (Articles Marketing)  :  il s’agit les pages vues liées aux articles comme les pages vues des articles eux-mêmes et les pages vues des autres pages dont la page d’entrée serait une page « d’article marketing ».  ».  Rem : Le « trafic de base » correspond au reste des pages vues.
  • DM (Direct Marketing) : il s’agit du trafic des pages (Articles Marketing ou non) dont l’entrée s’est faite via une page article marketing.
  • UM (Unique Marketing) : il s’agit du trafic des pages articles marketing dont l’entrée s’est faite par la même page article marketing.

Code Source :

Vous pouvez récupérer les différents bouts de code ci-dessous ou récupérer tout le code sur notre Github à l’adresse https://github.com/Anakeyn/ArticlesLifetimeR.

Chargement des bibliothèques et récupération des données :

##########################################################################
# Auteur : Pierre Rouarch 2019 - Licence GPL 3
# ArticleLifetimeR
# Duree de vie des articles "Marketing"
# Pour illustrer notre propos nous utiliserons le jeu de données de 
# l'association Networking-Morbihan 
.
##########################################################################
#Packages et bibliothèques utiles (décommenter au 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é des pages et des articles
##########################################################################
dfPageViews <- read.csv("dfPageViews.csv", header=TRUE, sep=";") 
#str(dfPageViews) #verif
#transformation de la date en date 🙂
dfPageViews$date <- as.Date(dfPageViews$date,format="%Y-%m-%d")
#str(dfPageViews) #verif
str(dfPageViews) #72821 obs
dfPageViews$index <- 1:nrow(dfPageViews)  #création d'un pour retrouver les "articles marketing" 
#ensuite
#pour les articles
myArticles <- read.csv("myArticles.csv", header=TRUE, sep=";") 
#transformation de la date en date 🙂
myArticles$date <- as.Date(myArticles$date,format="%Y-%m-%d")
#str(myArticles) #verif

Calcul du « trafic de base »

Il peut être intéressant de déterminer au préalable le « trafic de base » : dfBasePageViews. On crée aussi un jeu de données du trafic de base par jour : BaseDaily_data

##########################################################################
# Calcul du "trafic de base" ie hors "articles marketing" 
# On va supprimer toutes les pages vues correspondantes aux articles 
# "Marketing" ainsi que toutes les pages vues dont l'entrée s'est faite 
# par un article "Marketing". on comparera  ensuite au traffic 
# Articles Marketing.
##########################################################################
#récupere les chemins des pages pour les comparer dans dfPageViews
myArticles$pagePath <- str_split_fixed(myArticles$link, "https://www.networking-morbihan.com", 2)[,2]
patternArticlesToRemove <- unique(myArticles$pagePath)

#Pour les pages de base on enleve les pagePath de nos articles
indexPagePathToRemove <- -grep(pattern = paste(patternArticlesToRemove, collapse="|"), dfPageViews$pagePath)
dfBasePageViews <- dfPageViews[indexPagePathToRemove,]
#puis on enleve les landingPagePath de nos articles
indexLandingPagePathToRemove <- -grep(pattern = paste(patternArticlesToRemove, collapse="|"), dfBasePageViews$landingPagePath)
dfBasePageViews <- dfBasePageViews[indexLandingPagePathToRemove,]
str(dfBasePageViews) #37614 obs.

#pour la visualisation des pages "de base" (voir plus bas)
dfBaseDatePV <- as.data.frame(dfBasePageViews$date)
colnames(dfBaseDatePV)[1] <- "date"
BaseDaily_data <- dfBaseDatePV  %>%                      
  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

Trafic « articles marketing »

Il suffit de soustraire le trafic de base au trafic global. Créons aussi un jeu de données pour le trafic par jour.

###########################################################
# Détermination du trafic des pages articles marketing
###########################################################
#le trafic des pages vues des pages articles est le reste 
dfAMPageViews <- anti_join(x = dfPageViews, y = dfBasePageViews, by = "index")
str(dfAMPageViews) 

#Calcul des pages vues "articles marketing" par jour

dfAMDatePV <- as.data.frame(dfAMPageViews$date)
colnames(dfAMDatePV)[1] <- "date"
AMDaily_data <- dfAMDatePV  %>%                      
  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


Remarque : il reste 35207 observations pour les pages « Articles Marketing »

Comparatif trafic de base vs trafic articles marketing

Par curiosité, l’idée est ici de voir si les deux trafics sont corrélés. On pourra essayer de le voir graphiquement ou bien de calculer le Tau de Kendall (car ici la distribution n’est pas normale – je vous passe le détail 🙂 ).

Le Tau de Kendall se situe entre -1 et 1. Une valeur proche de 0 indique qu’il n’y a pas de corrélation. Une valeur positive indique une corrélation positive, une valeur négative une corrélation négative. Plus on s’approche de -1 ou 1, plus les variables sont corrélées.

##########################################################################
# Comparons le trafic "de base" par rapport au trafic "articles marketing"
############################################################################

#Qui est le plus grand ?
nrow(AMDaily_data)  #2478
nrow(BaseDaily_data) #2502
#merge pour être sur la même taille. (le plus grand)
BaseDaily_AMDaily_data <- merge(x = BaseDaily_data, y = AMDaily_data, by = "date", all.x = TRUE)
str(BaseDaily_AMDaily_data)
#Est-ce que l'on a des na ?
sum(is.na(BaseDaily_AMDaily_data$Pageviews.x)) #0 na
sum(is.na(BaseDaily_AMDaily_data$Pageviews.y)) #180 na

#NA en 0
BaseDaily_AMDaily_data[is.na(BaseDaily_AMDaily_data$Pageviews.y),"Pageviews.y"] <- 0
#recalculate cnt_ma30.y  avec les 0
BaseDaily_AMDaily_data$cnt_ma30.y = ma(BaseDaily_AMDaily_data$Pageviews.y, order=30)


#Comparons le traffic de base vs "Articles MArketing" - Pas très lisible sur toute la période
ggplot() + 
  geom_line(data=BaseDaily_AMDaily_data, aes(date, Pageviews.x), color="blue") + 
  geom_line(data = BaseDaily_AMDaily_data, aes(date, Pageviews.y), color="red") + 
  scale_x_date('year')  + 
  ylab("pages vues") +
  ggtitle(label = "PV depuis 2011 - bleu : pages articles marketing - Rouge : pages de base")
#Pas très lisible
ggsave(filename = "PV-s2011-base-vs-mkt.jpg",  dpi="print") #sauvegarde du dernier ggplot.



#Corrélation de Kendall #tau 0.3814596  pas terrible
cor.test(BaseDaily_AMDaily_data$Pageviews.x, BaseDaily_AMDaily_data$Pageviews.y, 
         method="kendall", use = "complete.obs")

Comparatif trafic de base vs trafic articles marketing
Comparatif trafic de base vs trafic articles marketing

Le graphique n’est pas très lisible. On peut juste voir que le trafic de base semble plus régulier. Il pourrait être intéressant de diviser le graphique en année, mais ce n’est pas le propos de cet article. Si vous êtes curieux allez-y !

Le Tau de Kendall donne une valeur de 0,39 ce qui indique une corrélation positive, ce à quoi on pouvait s’attendre, mais cette valeur n’est pas non plus énorme. Essayons en moyenne mobile :

Comparatif en moyenne mobile

#################################################################################
#Et en moyenne mobile sur 30 jours.
ggplot() + 
  geom_line(data=BaseDaily_AMDaily_data, aes(date, cnt_ma30.x), color="blue") + 
  geom_line(data=BaseDaily_AMDaily_data, aes(date, cnt_ma30.y), color="red") + 
  scale_x_date('year')  + 
  ylab("moyenne mobile 30 j - pages vues") +
  ggtitle(label = "Moy.Mob. 30 j. - PV depuis 2011 - bleu : pages articles marketing \n Rouge : pages de base")

ggsave(filename = "PV-s2011-base-vs-mkt-mm30.jpg",  dpi="print") #sauvegarde du dernier ggplot.


#Corrélation de Kendall #tau : 0.5426576  
cor.test(BaseDaily_AMDaily_data$cnt_ma30.x, BaseDaily_AMDaily_data$cnt_ma30.y, 
         method="kendall", use = "complete.obs")


#Sauvegarde éventuelle 
write.csv2(BaseDaily_AMDaily_data, file = "BaseDaily_AMDaily_data.csv",  row.names=FALSE)


Comparatif trafic de base vs trafic articles marketing en moyenne mobile.
Comparatif trafic de base vs trafic articles marketing en moyenne mobile.

C’est légèrement plus lisible, il y a des moments ou les courbes se suivent de près. Le Tau de Kendall est de 0,54 ce qui indique une corrélation positive un peu meilleure que précédemment.

A ce stade on constate qu’il y a une corrélation positive du trafic de base et du trafic articles marketing, ce qui parait somme toute logique, mais c’est mieux de le démontrer :-). Revenons à nos moutons …

Distribution du trafic des articles marketing à x mois

L’idée est ici de voir, au bout d’une période donnée (1,2 ,10 mois…), quels sont les trafics mensuels de mes différentes pages articles . Statistiquement, il s’agit d’afficher la distribution du trafic pour mes articles.

Pour faire cela nous avons créé une fonction que l’on pourra appeler pour chaque mois (cette fonction sera aussi utilisée pour le trafic Direct Marketing et pour le trafic Unique Marketing ) :

############################################################################
# Fonction pour récupérer une période sur un numéro de période et 
# un nombre de jour 
############################################################################

getMyDistribution <- function(myPageViews, myArticles, myNumPeriode, myNbrOfDays=30, myLastDate, myTestType="AM") {
#'myPageViews = une dataframe de Pages vues à tester avec les variables au minimum
#' date : date YYYY-MM-DD - date de la visite sur la page
#' landinfPagePath : chr path de la page d'entrée sur le site ex "/rentree-2011"
#  PagePath : chr path de la page visitée sur le site site ex "/rentree-2011"
#'myArticles = une dataframe de Pages vues que l'on souhaite investiguer et qui sont 
#'parmi les précédentes avec les variables au minimum  
#' date : date YYYY-MM-DD - date de la visite sur la page
#' PagePath : chr - path de la page visitée sur le site site ex "/rentree-2011"
#'myNumPeriode : integer Numéro de période par exemple 1 si c'est la première période
#'myNbrOfDays : int - nombre de jours pour la période 30 par défaut
#'myLastDate : date YYYY-MM-DD - date limite à investiguer.
#'myTestType="AM" : chr - "AM" test du landingPagePath ou pagePath sinon test du pagePath seul. 
  
 dfThisPeriodPV <- data.frame(ThisPeriodPV=double())  #pour sauvegarder la distribution 
 for (i in (1:nrow(myArticles))) {
    Link <- myArticles[i,"pagePath"] #lien i /
    #cat(paste(myArticles[i,"date"],"\n"))
    Date1 <- myArticles[i,"date"]+((myNumPeriode-1)*myNbrOfDays)
    Date2 <- Date1+myNbrOfDays
    if (myTestType == "AM") {
      myPV <- myPageViews[which( myPageViews$pagePath == Link | myPageViews$landingPagePath == Link ), ]
      } else  {
        myPV <- dfDMPageViews[which( myPageViews$pagePath == Link  ), ]
      }
    #MArketing
    myPVPeriode <- myPV[myPV$date %in% Date1:Date2,]
    myPVPeriode <- nrow(myPVPeriode)
    
    if (Date1 > myLastDate) { 
      myPVPeriode <- NA  #pour éviter d'avoir des 0 
    }
    dfThisPeriodPV[i, "ThisPeriodPV"] <- myPVPeriode  
 }
 return(dfThisPeriodPV)
}
#/getMyDistribution

Pour le Mois 1

############################################################################
# Pour le mois 1
############################################################################
myMonthNumber <- 1
dfAMThisMonthPV <- getMyDistribution(myPageViews=dfAMPageViews, 
                                       myArticles=myArticles, 
                                       myNumPeriode=myMonthNumber, 
                                       myNbrOfDays=30,
                                       myLastDate=dfAMPageViews[nrow(dfAMPageViews),"date"],
                                       myTestType="AM")

#test de normalité
resST <- shapiro.test(dfAMThisMonthPV$ThisPeriodPV)
resST$p.value #0.0006370982  normalité rejetée

ggplot(data=dfAMThisMonthPV, aes(x=ThisPeriodPV)) + 
  geom_histogram() +
  xlab("Nombre de vues") +
  ylab("Décompte") +
  labs(title = paste("la distribution est très étirée et ne présente pas de normalité.\n p Valeur =", format(round(resST$p.value, 5), scientific = FALSE)," << 0.05"),
  subtitle = "le décompte le plus important se fait pour les pages à 0 vues \n mais il y a aussi des pages avec plus de 600 vues.",
  caption = paste("Distribution du nombre de pages vues Articles Marketing", myMonthNumber, "mois après la parution"))
##################################################################
#  which.max(dfAMThisMonthPV$ThisPeriodPV)  #page la plus vues
ggsave(filename = stri_replace_all_fixed(paste("Dist-PV-AM-Mois-",myMonthNumber,".jpg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot en fonction du mois

Distribution PV articles marketing mois 1
Distribution PV articles marketing mois 1

Pour le mois 2

############################################################################
# Pour le mois 2
############################################################################
myMonthNumber <- 2
dfAMThisMonthPV <- getMyDistribution(myPageViews=dfAMPageViews, 
                                     myArticles=myArticles, 
                                     myNumPeriode=myMonthNumber, 
                                     myNbrOfDays=30,
                                     myLastDate=dfAMPageViews[nrow(dfAMPageViews),"date"],
                                     myTestType="AM")

#test de normalité
resST <- shapiro.test(dfAMThisMonthPV$ThisPeriodPV)
resST$p.value

ggplot(data=dfAMThisMonthPV, aes(x=ThisPeriodPV)) + 
  geom_histogram() +
  xlab("Nombre de vues") +
  ylab("Décompte") +
  #labs standard
  labs(title = paste("Le deuxième mois la distribution s'est resserrée.\n p Valeur =", format(round(resST$p.value, 12), scientific = FALSE)," << 0.05"),
       subtitle = "Il n'y a pas de pages au delà de 200 vues.",
       caption = paste("Distribution du nombre de pages vues Articles Marketing", myMonthNumber, "mois après la parution"))
##################################################################
#  which.max(dfAMThisMonthPV$ThisPeriodPV)
ggsave(filename = stri_replace_all_fixed(paste("Dist-PV-AM-Mois-",myMonthNumber,".jpg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot en fonction du mois

Distribution PV articles marketing mois 2
Distribution PV articles marketing mois 2

Pour le mois 10

############################################################################
# Pour le mois 10
############################################################################
myMonthNumber <- 10
dfAMThisMonthPV <- getMyDistribution(myPageViews=dfAMPageViews, 
                                     myArticles=myArticles, 
                                     myNumPeriode=myMonthNumber, 
                                     myNbrOfDays=30,
                                     myLastDate=dfAMPageViews[nrow(dfAMPageViews),"date"],
                                     myTestType="AM")

#test de normalité
resST <- shapiro.test(dfAMThisMonthPV$ThisPeriodPV)
resST$p.value

ggplot(data=dfAMThisMonthPV, aes(x=ThisPeriodPV)) + 
  geom_histogram() +
  xlab("Nombre de vues") +
  ylab("Décompte") +
  #labs standard
  labs(title = paste("Dès le dixième mois on s'approche d'un équilibre.\n p Valeur =", format(round(resST$p.value, 16), scientific = FALSE)," << 0.05"),
       subtitle = "Les pages à 0 vues sont pratiquement aussi nombreuses que celles \n à plusieurs vues",
       caption = paste("Distribution du nombre de pages vues Articles Marketing", myMonthNumber, "mois après la parution"))
##################################################################
#  which.max(dfAMThisMonthPV$ThisPeriodPV)
ggsave(filename = stri_replace_all_fixed(paste("Dist-PV-AM-Mois-",myMonthNumber,".jpg"), " ", ""),  dpi="print") #sauvegarde du dernier ggplot en fonction du mois
 

Distribution PV articles marketing mois 10
Distribution PV articles marketing mois 10

Pour le mois 40

############################################################################
# Pour le mois 40
############################################################################
myMonthNumber <- 40
dfAMThisMonthPV <- getMyDistribution(myPageViews=dfAMPageViews, 
                                     myArticles=myArticles, 
                                     myNumPeriode=myMonthNumber, 
                                     myNbrOfDays=30,
                                     myLastDate=dfAMPageViews[nrow(dfAMPageViews),"date"],
                                     myTestType="AM")

#test de normalité
resST <- shapiro.test(dfAMThisMonthPV$ThisPeriodPV)
resST$p.value

ggplot(data=dfAMThisMonthPV, aes(x=ThisPeriodPV)) + 
  geom_histogram() +
  xlab("Nombre de vues") +
  ylab("Décompte") +
  #labs standard
  labs(title = paste("A 40 mois la distribution est très resserrée.\n p Valeur =", format(round(resST$p.value, 13), scientific = FALSE)," << 0.05."),
       subtitle = "Les pages à 0 vues sont majoritaires.",
       caption = paste("Distribution du nombre de pages vues Articles Marketing", myMonthNumber, "mois après la parution"))
##################################################################
#sauvegarde du dernier ggplot en fonction du mois
ggsave(filename = stri_replace_all_fixed(paste("Dist-PV-AM-Mois-",myMonthNumber,".jpg"), " ", ""),  dpi="print") 

Distribution PV articles marketing mois 40
Distribution PV articles marketing mois 40

Dans notre cas, on constate que, assez rapidement, vers le mois 10, les pages articles marketing semblent ne plus apporter de trafic.

On vérifiera cela statistiquement dans la suite de l’article.

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.