Archives par étiquette : données

Visualisations des données : exemple avec un questionnaire de bien-être

Dans une publication précédente je vous avais présenté une solution pour recueillir des résultats d’un questionnaire en ligne de bien-être type Hooper, puis d’importer les résultats dans le logiciel RStudio.

Aujourd’hui je vous présente quelques unes de possibilités pour visualiser des résultats afin de faire une interprétation graphique et une analyse. Collecter, traiter et analyser des données est utile que si le destinataire final interprète rapidement le rapport ou le tableau de bord. Si personne ne le lit tout cela ne sert à rien.

Note 1 : je rappelle avant toute chose que l’exemple est avec un questionnaire hooper, mais libre à vous d’utiliser le questionnaire ou les données de votre choix. 

Note 2 : je rappelle enfin que l’analyse et les conclusions sont dépendantes du type de données et du contexte. 

Le système de graphique de base sur RStudio est simple et pratique. Mais le package ggplot2 est généralement plus utilisé car il propose plus d’options. 

Nous allons donc commencer par installer le package et créer une colonne de score total additionnant toutes les valeurs des réponses (sommeil,fatigue,stress et courbatures).

 
#Installation et chargement package ggplot2
install.packages("ggplot2")
library(ggplot2)

# Création colonne score total
data$total<-data$Sommeil+data$Fatigue+data$Stress+data$Courbatures

Le premier graphique que nous allons faire est une courbe du score total sur la durée des données. Comme nous représentons l’évolution du score dans le temps c’est en fait une série temporelle avec geom_line() .

 
ggplot(data, aes(x=Date, y=total,group = 1)) +
  geom_line() + 
  xlab("")

Le graphique de base n’est très lisible. Pour chaque jour il présente les valeurs de toutes l’équipe. Nous allons changer de thème avec theme_classic().

 
ggplot(data, aes(x=as.Date(Date), y=total,group = 1)) +
  geom_line() + 
  xlab("")  +  theme_classic()

Le graphique est plus visible mais toujours peu parlant. Essayons avec une repésentation en boites avec geom_boxplot().

 
ggplot(data, aes(x=as.factor(Date), y=total)) +
  geom_boxplot() + 
  xlab("")+theme_classic()

La représentation est plus intéressante car elle donne des indications de tendance centrale, de dispersion et de valeurs anormales.

Essayons à présent de représenter l’évolution du score total dans le temps, mais cette fois-ci avec un graphique pour chaque athlète avec facet_wrap().

 
ggplot(data, aes(x=Date, y=total, group = 1)) +
  geom_line() + 
  xlab("")+  theme_classic()+facet_wrap(~Athlete)

Cette visualisation est parlante car elle renseigne sur l’évolution dans le temps pour chaque athlète. Il est possible de faire le même chose en diagramme en barres avec geom_bar().

 
ggplot(data, aes(x=Date, y=total, group = 1)) +
  geom_bar(stat = 'identity') + 
  xlab("")+  theme_classic()+facet_wrap(~Athlete)

Cependant, avons-le, l'interprétation n’est pas très instinctive et rapide. Nous allons mettre des couleurs en conditionnant la couleur à la valeur de score total avec “fill=”.

 
ggplot(data, aes(x=Date, y=total, fill=total)) +
  geom_bar(stat = 'identity') + 
  xlab("")+theme_classic()+facet_wrap(~Athlete)

Déjà mieux ! Nous pouvons ensuite choisir les couleurs pour être toujours plus parlant visuellement avec scale_y_continuous() et scale_fill_gradient2().

 
ggplot(data, aes(x=Date, y=total, fill=total)) +
  geom_bar(stat = 'identity') + 
  xlab("")+
  theme_classic()+facet_wrap(~Athlete)+
  scale_y_continuous(breaks=seq(0,28,14))+
  scale_fill_gradient2(low='blue', mid='green', high='red', midpoint = 14, name='Total')

Le graphique est bien plus visuel. Le contraste de couleurs permet de rapidement distinguer les valeurs hautes ou basses. Notez qu’il est également possible de représenter le score total par date.

 
ggplot(data, aes(x=Athlete, y=total, fill=total)) +
  geom_bar(stat = 'identity') + 
  xlab("")+
  theme_classic()+facet_wrap(~Date)+
  scale_y_continuous(breaks=seq(0,28,14))+
  scale_fill_gradient2(low='blue', mid='green', high='red', midpoint = 14, name='Total')

Ou bien encore de le faire une variable en particulier. La fatigue par exemple.

 
ggplot(data, aes(x=Date, y=Fatigue, fill=Fatigue)) +
  geom_bar(stat = 'identity') + 
  xlab("")+
  theme_classic()+facet_wrap(~Athlete)+
  scale_y_continuous(breaks=seq(0,7,1))+
  scale_fill_gradient2(low='blue', mid='green', high='red', midpoint = 3.5, name='Fatigue')

La représentation dépend de la question, des données et du destinataire. Mais vous avez à présent une brève idée des possibilités.

Maintenant la question est que ce passe t-il avec l’athlète A ?

Estimer les progrès d’un joueur

Estimer les progrès d’un joueur de sports collectif est un sujet complexe. Il mêle l’objectif avec le subjectif. Rien de remplacera l’avis de l’entraîneur pour savoir qui doit être sur le terrain. Cependant aujourd’hui je vais vous présenter une solution avec des données objectives.

Nous allons prendre l’exemple de la NBA. 2 raisons : beaucoup de statistiques sont disponibles et il y a un trophée chaque saison pour le joueur avec la plus forte progression (en comparaison à la saison précédente).

Nous allons utiliser des statistiques ascendantes (statistiques individuelles pour expliquer la réussite collective) pour 2 joueurs (Spencer Dinwiddie et Brandon Ingram), à la date du 27 janvier 2020. Le script en langage R est disponible sur mon Github (MIP).

 install.packages("dplyr")
install.packages("ggplot2")
install.packages("reshape2")

library(dplyr) #Load readxl package
library(ggplot2) #Load readxl package
library(reshape2) #Load readxl package
library(readr)

# chargement données 2019-2020
brk20 <- read_csv("brk20.csv")
View(brk20)
brk19 <- read_csv("brk19.csv")
View(brk19)
no20 <- read_csv("no20.csv")
View(no20)
lal19 <- read_csv("lal19.csv")
View(lal19)

Nous allons charger les données et les packages nécessaires. Puis visualiser les données brutes.


Pour débuter nous allons corriger le nom de joueurs via une fonction qui séparer la colonnes en 2 parties et conserver uniquement la 1ère.

 ## Extraction nom
first = function(str){
    str = str[[1]]
    parts = strsplit(str,'\\\\')[[1]]
    first_part = parts[1]
    if(length(parts) >= 2)
      print(sprintf(' - Il y a plusieurs parties dans "%s", ne gardons que %s.',paste(parts,collapse=""),first_part))  
    return(first_part)
  }

# Correctif nom joueur
brk20['X2'] = apply(brk20['X2'], 1, first) 
brk19['X2'] = apply(brk19['X2'], 1, first) 
no20['X2'] = apply(no20['X2'], 1, first) 
lal19['X2'] = apply(lal19['X2'], 1, first) 

Puis, pour les statistiques variables selon le nombre de minutes jouées nous voulons obtenir un ratio par minute. Ceci pour éviter de donner un avantage au joueur dont les minutes auraient largement augmentées d’une saison sur l’autre. L’impact sur l’équipe pendant les minutes jouées sont importantes.

Pour cela nous allons créer une fonction qui prend 2 variables : x (la liste des statistiques à modifier) et y la variable de minutes jouées.

 # Fonction calcul stat par minutes
per_min = function(x,y){
  return(x/y)
}


# liste stats à corriger
list<-c("3P","3PA","2P","2PA","FT","FTA","ORB","DRB","TRB","AST","STL","BLK","TOV","PF","PTS")

# Application fonction à liste variables
brk20[list]<-per_min(brk20[list],brk20$MP)
brk19[list]<-per_min(brk19[list],brk19$MP)
no20[list]<-per_min(no20[list],no20$MP)
lal19[list]<-per_min(lal19[list],lal19$MP)

Normaliser les statistiques via un calcul du score Z afin d’obtenir l’impact du joueur dans son équipe. L’objectifs est d’éviter de comparer sans norme. Mais de comparer la place, statistiquement, du joueur dans son équipe. 

Puis faire un peu de nettoyage.

 # Normalisation
brk20[c(3:28)] <- scale(brk20[c(3:28)])
brk19[c(3:28)] <- scale(brk19[c(3:28)])
no20[c(3:28)] <- scale(no20[c(3:28)])
lal19[c(3:28)] <- scale(lal19[c(3:28)])

# Conservation Dinwiddie
Dinwiddie20<-brk20[brk20$X2=="Spencer Dinwiddie",]
Dinwiddie20<-Dinwiddie20[2:28]

Dinwiddie19<-brk19[brk19$X2=="Spencer Dinwiddie",]
Dinwiddie19<-Dinwiddie19[2:28]

# Conservation Ingram
Ingram20<-no20[no20$X2=="Brandon Ingram",]
Ingram20<-Ingram20[2:28]

Ingram19<-lal19[lal19$X2=="Brandon Ingram",]
Ingram19<-Ingram19[2:28]


# Retrait age 
Dinwiddie20 <- Dinwiddie20[-2]
Dinwiddie19 <- Dinwiddie19[-2]
Ingram20 <- Ingram20[-2]
Ingram19 <- Ingram19[-2]

Les données sont actuellements encore sur un tableau de données par saison et par joueurs, mais également sur plusieurs colonnes. Nous allons à présent changer d’un format large vers un format long, puis les regrouper sur un seul tableau de de données par joueurs. Ceci afin de pouvoir calculer les différences entre les 2 saisons. Nous voulons comparer les joueurs à lui même.

# Changement format données
Dinwiddie20<-melt(Dinwiddie20,  id="X2")
Dinwiddie19<-melt(Dinwiddie19,  id="X2")
Ingram20<-melt(Ingram20,  id="X2")
Ingram19<-melt(Ingram19,  id="X2")

Enfin, nous allons calculer la différences entre les 2 saisons. Nous avons à présent un tableau de données par joueurs. Nous allons tout regrouper sur un seul tableau pour finalement mettre cette différences en graphique.

#Merge
Dinwiddie<-merge(Dinwiddie19,Dinwiddie20,by=c("X2","variable"))
Dinwiddie$diff<-Dinwiddie$value.y-Dinwiddie$value.x
Ingram<-merge(Ingram19,Ingram20,by=c("X2","variable"))
Ingram$diff<-Ingram$value.y-Ingram$value.x

#Jointure
Dinwiddie_Ingram<-rbind(Dinwiddie,Ingram)

#Graphique comparaison des 2 joueurs
ggplot(Dinwiddie_Ingram,aes(x = Dinwiddie_Ingram$variable, y = Dinwiddie_Ingram$diff, fill = X2))+
  geom_bar(stat="identity", position=position_dodge())+coord_flip()+ 
  xlab("Stat en relation avec l'équipe")+ylab("Différence 2019-2020")+
  theme_classic()+ theme(legend.text = element_text(colour="blue", size=10, 
                                                    face="bold"),legend.position="top")+
  labs(fill = "Joueur")



Si la version visuelle ne vous convient pas, il est également possible de faire la somme ou la moyenne de ces différences d’une saison sur l’autre. 

# Somme et moyenne diff

sum(Dinwiddie_Ingram$diff[Dinwiddie_Ingram$X2=="Spencer Dinwiddie"]) #12.39
sum(Dinwiddie_Ingram$diff[Dinwiddie_Ingram$X2=="Brandon Ingram"]) #10.61

mean(Dinwiddie_Ingram$diff[Dinwiddie_Ingram$X2=="Spencer Dinwiddie"]) #0.4956364
mean(Dinwiddie_Ingram$diff[Dinwiddie_Ingram$X2=="Brandon Ingram"]) #0.4244306

Spencer Dinwiddie = 12.39 / 0.49

Brandon Ingram = 10.61 / 0.42

Résumons

Dans notre cas présent les deux joueurs semblent avoir un impact plus important sur leur équipes respectives cette saison. Cependant Spencer Dinwiddie semble avoir su rentabiliser son temps de jeu. En tout cas mieux de Brandon Ingram. Spencer Dinwiddie semble parfaitement exploiter la situation de son équipe et de son temps de jeu accru.

Rugby – Plaquages offensifs en TOP 14

Lors de la 12ème journée du TOP14 les commentateurs ont utilisés la statistiques des “plaquages offensifs par match” concernant l’équipe du Stade Français Paris. Une statistique “positive” dans leurs propos. Le Stade Français Paris étant, avant la 12ème journée, l’équipe réalisant le plus de “plaquages offensifs  par match”. Au regard du classement du Stade Français Paris à cet instant de la saison, derniers, j’ai trouvé l’utilisation curieuse.

Je vous propose donc d’explorer la question des “plaquages offensifs réussis” en rugby, Top14 plus précisément.

Au rugby le “plaquage offensif réussi” est implique de saisir son adversaire pour lui faire perdre ses appuis et le repousser, le reculer. On regagne du terrain sur son adversaire. C’est un classique des sports dit de “gagne terrain” comme le rugby et le football américain.

Les données utilisées ont été obtenues sur le site de la Ligue Nationale de Rugby. Il s’agit dans notre cas du classement (avec les points marqués et encaissés) ainsi que des statistiques défensives pour les saisons 2014-2015 à 2019-2020 (hors saison 2017-2018 pour lesquelles il n’y a pas de statistiques disponibles….). L’analyse va être faites en langage R.

Pour chacune des saisons les données sont sur 2 fichiers. Nous allons pour commencer par regrouper les données sur une seule table de données par saison puis toutes les regrouper sur une table 2015 à 2020. Nous au passage caractériser chaque saison par l’année lors de laquelle se déroule la finale. Exemple : Saison 2019-2020 = 2020. Ceci nous aidera dans la suite de l’analyse. Les tables finales sont disponibles sur mon Github.

# Installation packages necessaires
install.packages("ggplot2")
library(ggplot2)

# Importation data frame
top14_class2015 <- read_csv("data/top14_class2015.csv")
top14_def2015 <- read_csv("data/top14_def2015.csv")

top14_class2016 <- read_csv("data/top14_class2016.csv")
top14_def2016 <- read_csv("data/top14_def2016.csv")

top14_class2017 <- read_csv("data/top14_class2017.csv")
top14_def2017 <- read_csv("data/top14_def2017.csv")

top14_class2019 <- read_csv("data/top14_class2019.csv")
top14_def2019 <- read_csv("data/top14_def2019.csv")

top14_class2020 <- read_csv("data/top14_class2020.csv")
top14_def2020 <- read_csv("data/top14_def2020.csv")

# Création de data frame pour chaque saison

## Renommage pour fusion
names(top14_class2015)[2]<-"Clubs"
names(top14_class2016)[2]<-"Clubs"
names(top14_class2017)[2]<-"Clubs"
names(top14_class2019)[2]<-"Clubs"
names(top14_class2020)[2]<-"Clubs"

## Regroupement des 2 fichiers pour chaque saisons

bilan2015<-merge(top14_class2015,top14_def2015, by = "Clubs")
bilan2016<-merge(top14_class2016,top14_def2016, by = "Clubs")
bilan2017<-merge(top14_class2017,top14_def2017, by = "Clubs")
bilan2019<-merge(top14_class2019,top14_def2019, by = "Clubs")
bilan2020<-merge(top14_class2020,top14_def2020, by = "Clubs")

## Création variable saison pour chaque table 
bilan2015$saison<-c("2015")
bilan2016$saison<-c("2016")
bilan2017$saison<-c("2017")
bilan2019$saison<-c("2019")
bilan2020$saison<-c("2020")

# Regroupement des toutes les saisons dans une table unique

bilan15_20<-do.call("rbind",list(bilan2015,bilan2016, bilan2017, bilan2019, bilan2020))

Saison 2019-2020

Nous allons explorer graphiquement la relation entre le nombre de plaquage par rencontre avec le nombre de points encaissés par rencontre puis le classement. Nous allons pour cela créer les deux variables puis faire les graphiques.

# Création variable plaq_p_m et pts_e_p_m

bilan2020$plaq_p_m<-bilan2020$`Plaquages offensifs réussis`/bilan2020$Matchs
bilan2020$pts_e_p_m<-bilan2020$Pts.E/bilan2020$J.

# Graphiques plaquages / pts encaissés
ggplot(bilan2020, aes(x=plaq_p_m, y=pts_e_p_m)) +
  geom_point() + # Show dots
  geom_label(label= bilan2020$Clubs,nudge_x = 0.25, nudge_y = 0.25, 
    check_overlap = T )+
  ggtitle("Saison 2019-2020 : plaquages offensifs et points encaissés (après 12 journées)")+
  xlab ("Nombre de plaquage offensif par rencontre")+
  ylab("Nombre de points encaissés par rencontre")

# Graphiques plaquages / Classement
ggplot(bilan2020, aes(x=plaq_p_m, y=Rang)) +
  geom_point() + # Show dots
  geom_label(label= bilan2020$Clubs,nudge_x = 0.25, nudge_y = 0.25, 
             check_overlap = T )+
  ggtitle("Saison 2019-2020 : plaquages offensifs et points encaissés (après 12 journées)")+
  xlab ("Nombre de plaquage offensif par rencontre")+
  ylab("Classement")

On constate qu’il n’y a pas de relation entre le nombre de plaquage par rencontre et le nombre de points encaissés par rencontre ou le classement. Le Stade Français Paris étant l’exemple le plus flagrant. L’équipe est une des équipes du TOP 14 avec les plus de plaquages offensifs par rencontre, mais l’une des dernières au classement et au nombre de points encaissés.

Période 2015-2020

Pour aller plus loin nous allons explorer graphiquement les mêmes relations mais sur la période des saisons 2014-2015 à 2019-2020.

# Période 2015-2020

# Création variable plaq_p_m et pts_e_p_m

bilan15_20$plaq_p_m<-bilan15_20$`Plaquages offensifs réussis`/bilan15_20$Matchs
bilan15_20$pts_e_p_m<-bilan15_20$Pts.E/bilan15_20$J.

# Graphiques plaquages / pts encaissés
ggplot(bilan15_20, aes(x=plaq_p_m, y=pts_e_p_m)) +
  geom_point(color="black",
             fill="orange",
             shape=22,
             alpha=0.5,
             size=6,
             stroke = 1) +
  ggtitle("Période 2015-2020 : plaquages offensifs et points encaissés")+
  xlab ("Nombre de plaquage offensif par rencontre")+
  ylab("Nombre de points encaissés par rencontre")

# Graphiques plaquages / Classement
ggplot(bilan15_20, aes(x=plaq_p_m, y=Rang)) +
  geom_point(color="black",
             fill="orange",
             shape=22,
             alpha=0.5,
             size=6,
             stroke = 1)+
  ggtitle("Période 2015-2020 : plaquages offensifs et points encaissés")+
  xlab ("Nombre de plaquage offensif par rencontre")+
  ylab("Classement")

Période 2015-2020 : évolution plaquages offensifs

La statistiques des plaquages offensifs ne semble pas être indicateur de qualité pour de performance. Cependant lorsque l’on regarde son évolution depuis plusieurs saison on ne peut que faire le constat d’une augmentation du nombre de plaquages offensifs par match de TOP 14.

# Graphiques plaquages OFF par saison 
ggplot( bilan15_20, aes(x=bilan15_20$saison,y=bilan15_20$plaq_p_m , fill=bilan15_20$saison)) +
  geom_boxplot() +
  ggtitle("Période 2015-2020 : plaquages offensifs par match") +
  xlab("Saison") + ylab("Nombre de plaques offensifs par match")+
  theme(    legend.position="none")

Période 2015-2020 : évolution points marqués et encaissés

Cependant, sur la même période, il est possible d’observer un certaines constance dans le nombres de points marqués et encaissés.

# Création variables nombres de points marqués par match 
bilan15_20$pts_m_p_m<-bilan15_20$Pts.M/bilan15_20$J.

# Graphiques nombre de points encaissés par match 
ggplot( bilan15_20, aes(x=bilan15_20$saison,y=bilan15_20$pts_e_p_m , fill=bilan15_20$saison)) +
  geom_boxplot() +
  ggtitle("Période 2015-2020 : points encaissés par match") +
  xlab("Saison") + ylab("Nombre de points encaissés par match")+
    theme(    legend.position="none")

# Graphiques nombre de points marqués par match 
ggplot( bilan15_20, aes(x=bilan15_20$saison,y=bilan15_20$pts_m_p_m , fill=bilan15_20$saison)) +
  geom_boxplot() +
  ggtitle("Période 2015-2020 : points marqués par match") +
  xlab("Saison") + ylab("Nombre de points marqués par match")+
  theme(    legend.position="none")

Résumons

La statistique des plaquages offensifs ne semble pas être indicateur de qualité pour de performance. Réaliser plus de plaquages offensifs n’est pas associé à une diminution du nombre points encaissés, ni à un meilleur classement.

Les plaquages offensifs semblent être plus nombreux depuis quelques saisons. Il serait intéressant d’étudier une modification des organisations et techniques défensives utilisées par les équipes. Peut-être une tendance actuelle vers la défense inversée ? Un refus du statut de défenseur et l’objectif de récupérer des possessions lors des rucks suivants le plaquage offensif ? Cette statistique (rucks gagnés suite plaquage offensif) n’est malheureusement pas disponible sur le site de la LNR.

Cette évolution du nombre de plaquage offensif ne semble pas s’associer à une modifications du nombre de points marqués et encaissés par match. Les équipes semblent avoir trouver des solutions si cette tendance, si celle leur avait posé problème.

En bref...

Commentateurs et entraîneurs devraient hésiter à utiliser les plaquages offensifs comme argument de qualité ou d’efficacité 

Les entraîneurs peuvent probablement utiliser cette statistique pour apprécier si l’organisation collective défensive et technique correspondent aux attentes et ont les effets désirés. 

Valeur de la possession et classement au basket en PROA

Au basketball l’efficacité d’une équipe est souvent exprimée en nombre de point par 100 possessions. Ex 115 points / 100 possessions. C’est une valeur indirectement obtenue par la Valeur de la Possession d’Hollinger (En anglais Value Of Possession – VOP). 

La VOP est également utilisée pour le calcul de l’évaluation du joueur (En anglais Player Efficiency Rating – PER).

A sa façon le VOP est une mesure de l’efficacité offensive d’une équipe. En utilisant cette statistique on part du principe que le basketball est un jeu simple et qu’il faut être le plus efficace possible à chaque possession de la rencontre, et que le plus efficace gagne la rencontre.

L’objectif dans cette analyse est de répondre à la question suivante : la VOP est-elle corrélée au classement final en PROA ?

 Pour cela nous allons utiliser les données des 5 dernières saisons (2014 à 2019) et utiliser du code en R.

Les données

Les données des 5 dernières saison de PROA ont été obtenues sur le site basketball-reference.com et sont disponibles sur mon Github.

Explorons les tableaux de données.

head(proa1415)
names(proa1415)

Pour chacune des 5 saisons de PRO nous avons les 18 équipes composants le championnat et 24 variables. Ces 24 variables sont les statistiques sur la saison.

Calcul de la VOP pour chacunes des saisons

Pour ce calcul nous aurons besoin des points (PTS), des tirs tentés (FGA), des rebonds offensifs (ORB), balles perdues (TOV) et des lancers francs (FTA). 

La VOP se calcul alors de la manière suivante :

VOP = PTS / (FGA-ORB+TOV+0.44*FTA) 

Note : Hollinger utilise un coefficient de 0.44 par lancé francs pour tenir compte du fait que les joueurs n’obtiennent pas 2 lancers francs à chaque fois en NBA. Il conviendrait de déterminer un coefficient propre à la PROA.

# VOP saison 1415
proa1415$POS<-proa1415$FGA-proa1415$ORB+proa1415$TOV+(.44*proa1415$FTA)
proa1415$VOP<-proa1415$PTS/proa1415$POS

# VOP saison 1516
proa1516$POS<-proa1516$FGA-proa1516$ORB+proa1516$TOV+(.44*proa1516$FTA)
proa1516$VOP<-proa1516$PTS/proa1516$POS

# VOP saison 1617
proa1617$POS<-proa1617$FGA-proa1617$ORB+proa1617$TOV+(.44*proa1617$FTA)
proa1617$VOP<-proa1617$PTS/proa1617$POS

# VOP saison 1718
proa1718$POS<-proa1718$FGA-proa1718$ORB+proa1718$TOV+(.44*proa1718$FTA)
proa1718$VOP<-proa1718$PTS/proa1718$POS

# VOP saison 1819
proa1819$POS<-proa1819$FGA-proa1819$ORB+proa1819$TOV+(.44*proa1819$FTA)
proa1819$VOP<-proa1819$PTS/proa1819$POS

Tous les tableaux de données correspondants à une saison ont donc à présent 2 nouvelles colonnes : POS (nombre de possessions sur la saison) et VOP.

Nous allons à présent regrouper les données de VOP dans un seul tableau et comparer les 5 dernières saisons dans un graphique.

# Rangement des données  
Saison1415 = c(VOP1419$proa1415.VOP)
Saison1516 = c(VOP1419$proa1516.VOP)
Saison1617 = c(VOP1419$proa1617.VOP)
Saison1718 = c(VOP1419$proa1718.VOP)
Saison1819 = c(VOP1419$proa1819.VOP)
VOP1419 = data.frame(cbind(Saison1415,Saison1516,Saison1617,Saison1718,Saison1819))
VOP1419<-stack(VOP1419)
names(VOP1419)[names(VOP1419) == "ind"] <- "Saison"
names(VOP1419)[names(VOP1419) == "values"] <- "VOP"

# graphique VOP par saison
boxplot(VOP1419$VOP~VOP1419$Saison,main="VOP par saison", xlab = "Saison", ylab= "VOP")
abline(h=median(proa1415$VOP),col = "red")

Si l’on prend la saison 14-15 comme référence, il semblerait que la VOP soit en augmentation. Indiquant des équipes inscrivants de plus en plus de points par possession (pour le plus grand plaisir des supporters).

Note : Cependant hormis entre les saisons 18-19 et 16-17, il n’y a pas de différences statistiquement significative. Mais nous n’entrerons pas dans les détails ici.

Corrélation en VOP et classement

Nous allons utiliser les variables du classement (Rk) et la VOP (VOP) pour répondre à notre question initiale. Nous allons tout d’abord regrouper tous les tableaux dans un seul. 

# regroupement data frame dans une seule
VOP_RK <- merge(merge(merge(merge(
  proa1415,
  proa1516, all = TRUE),
  proa1617, all = TRUE),
  proa1718, all = TRUE),
  proa1819, all = TRUE)
head(VOP_RK)

Puis ensuite explorer graphiquement cette relation.

# graphique VOP - classement
plot(x=VOP_RK$VOP, y=VOP_RK$Rk,main="Correlation VOP et Classement final PROA saisons 14-19",xlab = "VOP", ylab= "Classement")
abline(lm(VOP_RK$Rk~VOP_RK$VOP),col = "red")

La droite en rouge nous indique la relation entre le classement et la VOP. Cette relation est concrètement négative. Mais positive pour nous. En effet le classement le plus élevé est représenté par la valeur 1. Donc pour nous lorsque le VOP augmente il semblerait que le classement se rapproche du plus haut, c’est à dire 1.

Cependant on ne peut qu'observer également la dispersion des observations. De ce fait nous allons calculer le coefficient de corrélation. Pour apprécier sa valeur nous allons utiliser une échelle proposée par Will G Hopkins : 0.0-0.1 = trivial, très petit, sans substance, minuscule, pratiquement nul / 0.1-0.3 = petit, faible, mineur / 0.3-0.5 = modéré, moyen / 0.5-0.7 = grand, haut, majeur / 0.7-0.9 = très grand, très haut, énorme / 0.9-1 = presque, pratiquement ou presque: parfait, distinct, infini.

# correlation VOP et classement
summary(lm(VOP_RK$Rk~VOP_RK$VOP))

Nous obtenons un R² de 0.46. C’est à dire une corrélation modérée entre la VOP et le classement.

Conclusion

A partir des données des saisons 2014 à 2019 nous avons constaté qu’il y a avait une corrélation entre la valeur de la possession et le classement en PROA. Cette corrélation est modérée.

Malgré le fait que la VOP (ou bien encore le nombre de points marqués par 100 possession) soit une statistique souvent mise en avant au basketball elle est loin de permettre d’expliquer le résultat d’une équipe pendant la saison. 

Références :

  • Hollingern J.- Pro Basketball Forecast 2004-05
  • Stephen M. Shea, Christopher E. Baker - Basketball Analytics 2013
  • A Scale of Magnitudes for Effect Statistics - Will G Hopkins

Suivi de l’état de forme à partir du CMJ – Tout est-il sous contrôle ?

Le saut en contre mouvement ( ou CMJ – counter movement jump – en anglais) est probablement une des mesures les plus utilisées pour suivre les effets de l’entraînement sur la capacité à s’entraîner ou à être performant. Cette une mesure qui ne concerne que le système nerveux et la capacité musculaire et ne saurait représenter l’ensemble des capacités nécessaire à la performance dans le sport (cardiovasculaire, technique, tactique,motivation,etc). 

Cependant c’est une mesure rapide, pratique, reproductible et de ce fait très étudiée.

L’objectif de cet article est de présenter le suivi des performances lors d’un CMJ pour 11 sujets pratiquants le MMA, pendant 5 mois.

Nous allons retenir uniquement le pic de vélocité qui semble être un marqueur plus précis de la fatigue neuromusculaire.

Pour suivre cette évolution sur 5 mois nous allons utiliser un procédé basé sur la Maîtrise Statistique de Processus (MSP), aussi dénommée Statistical Process Control (SPC) en anglais. Ce procédé permet d’identifier les valeurs “anormales” ou qui vont au delà de valeurs prédéfinies.

Nous utiliserons pour chaque cas étudié le langage R pour une analyse graphique à partir la moyenne et l’écart-type. Toutes les données sont disponibles sur mon Github.

1/ Les données

Tout d’abord nous allons charger le package ggplot2 d’exporation graphique, puis explorer les données.

library(ggplot2)
head(cmj)
table(cmj$ID)
table(cmj$Date)

En examinant les données nous constatons que pour chaque sujet (ID) les résultats sont présentés par date. Pour chaque date nous avons 3 sauts (Saut1, Saut2, Saut3). Les sujets 1/7/8 sont ceux pour lesquels nous avons le plus de données (10 ou 11). Nous constatons également que la régularité n’est pas optimales. Les 11 sujets sont loin d’avoir participés à toutes les prises de mesures. Mais dans le cas présent, présentation du procédé MSP/SPC pour un suivi du CMJ, cela ne sera pas handicapant.

2/ Exemples

Nous allons prendre pour 1er exemple le sujet 8 car il présente le plus de données (avec le sujet 1).  Nous allons isoler toutes les données du sujet 8 puis calculer la moyenne des 3 sauts pour chacune des dates.

Nous avons besoin de la moyenne et de l’écart-type de l’ensemble des observations du sujet. 

Le procédé MSP/SPC utilise des seuils hauts et bas se basant sur l’écart-type. J’attire l’attention du lecteur que les seuils sont ici calculés sur la base de 1 et 2 écarts-types. Libre à chacun de définir les seuils selon sa volonté et ses besoins. Ces seuils s’obtiennent en ajoutant ou soustrayant 1 et 2 écarts-types à la moyenne.

# Isoler les données sur sujet 8 
Sujet8<-cmj[cmj$ID=='8',] 

# Calcul de la moyenne pour chaque date 
Sujet8$moyenne<-round(with(Sujet8,(Saut1+Saut2+Saut3)/3),2) 

# Calcul de la moyenne et écart-type pour l'ensemble des observations 
Sujet8_sauts<-c(Sujet8$Saut1, Sujet8$Saut2 ,Sujet8$Saut3) 
Sujet8_moyenne<-mean(Sujet8_sauts) 
Sujet8_sd<-sd(Sujet8_sauts) 

# Détermination des seuils haut et bas 
sd1_haut<-Sujet8_moyenne+(1*Sujet8_sd) 
sd1_bas<-Sujet8_moyenne-(1*Sujet8_sd) 
sd2_haut<-Sujet8_moyenne+(2*Sujet8_sd) 
sd2_bas<-Sujet8_moyenne-(2*Sujet8_sd) 

Nous pouvons alors faire le graphique avec la moyenne (trait en rouge continu), les seuils 1 écart-type (trait pointillé noir) et les seuils 2 écarts-types (trait pointillé rouge).

ggplot(Sujet8, aes(x = Date, y = moyenne)) +
  geom_point(color = "blue") +
  theme_classic()+ geom_hline(aes(yintercept = mean(Sujet8_sauts)), color = "red")+
  geom_hline(aes(yintercept = sd1_haut), linetype = "dashed")+
  geom_hline(aes(yintercept = sd1_bas), linetype = "dashed")+geom_line()+
  geom_hline(aes(yintercept = sd2_haut), linetype = "dashed", color = "red")+
  geom_hline(aes(yintercept = sd2_bas), linetype = "dashed", color = "red")+
  ggtitle("Sujet 8 - Evolution Pic de vitesse lors d'un CMJ") + xlab("Date")+ylab("Vitesse (m/s)")

Le procédé MSP/SPC se base sur une suite ou un fréquence de valeur au delà des différents seuils. Par exemple, si les valeurs restent entre les seuils 1 supérieur et inférieur, les réponses au processus d’entraînement sont considérées comme normales. Les différentes performances et la variation de l’état de forme du sportif font partie de processus d’adaptations à la charge d’entraînement. Ce que l’on cherche à savoir est si ce processus est sous contrôle (adaptations/réponses désirées) ou non (surentrainement/mauvaise adaptation/désentrainement)  

Le sujet 8 présente 2 valeurs inférieures au seuil 1. Mais elles ne sont pas consécutives. Elles ne semblent pas indiquer de mal-adaptations du sujet au processus d’entraînement ou d’effets indésirables de celui-ci.

Nous pouvons procéder à la même analyse avec les sujets 1 et 7 pour finalement présenter les 3 graphiques.


# Sujet8
ggplot(Sujet8, aes(x = Date, y = moyenne)) +
  geom_point(color = "blue") +
  theme_classic()+ geom_hline(aes(yintercept = mean(Sujet8_sauts)), color = "red")+
  geom_hline(aes(yintercept = sd1_haut), linetype = "dashed")+
  geom_hline(aes(yintercept = sd1_bas), linetype = "dashed")+geom_line()+
  geom_hline(aes(yintercept = sd2_haut), linetype = "dashed", color = "red")+
  geom_hline(aes(yintercept = sd2_bas), linetype = "dashed", color = "red")+
  ggtitle("Sujet 8 - Evolution Pic de vitesse lors d'un CMJ") + xlab("Date")+ylab("Vitesse (m/s)")

# Sujet 1
Sujet1<-cmj[cmj$ID=='1',]

Sujet1$moyenne<-round(with(Sujet1,(Saut1+Saut2+Saut3)/3),2)

Sujet1_saut<-c(Sujet1$Saut1, Sujet1$Saut2 ,Sujet1$Saut3  )
Sujet1_saut
mean(Sujet1_saut)
sd(Sujet1_saut)
sd1_haut<-mean(Sujet1_saut)+(1*sd(Sujet1_saut))
sd1_bas<-mean(Sujet1_saut)-(1*sd(Sujet1_saut))
sd2_haut<-mean(Sujet1_saut)+(2*sd(Sujet1_saut))
sd2_bas<-mean(Sujet1_saut)-(2*sd(Sujet1_saut))

ggplot(Sujet1, aes(x = Date, y = moyenne)) +
  geom_point(color = "blue") +
  theme_classic()+ geom_hline(aes(yintercept = mean(Sujet1_saut)), color = "red")+
  geom_hline(aes(yintercept = sd1_haut), linetype = "dashed")+
  geom_hline(aes(yintercept = sd1_bas), linetype = "dashed")+
  geom_hline(aes(yintercept = sd2_haut), linetype = "dashed", color = "red")+
  geom_hline(aes(yintercept = sd2_bas), linetype = "dashed", color = "red")+
  ggtitle("Sujet 1 - Evolution Pic de vitesse lors d'un CMJ") + xlab("Date")+ylab("Vitesse (m/s)")+
  geom_line()



# Sujet 7
Sujet7<-cmj[cmj$ID=='7',]

Sujet7$moyenne<-round(with(Sujet7,(Saut1+Saut2+Saut3)/3),2)

Sujet7_saut<-c(Sujet7$Saut1, Sujet7$Saut2 ,Sujet7$Saut3  )
Sujet7_saut
mean(Sujet7_saut)
sd(Sujet7_saut)
sd1_haut<-mean(Sujet7_saut)+(1*sd(Sujet7_saut))
sd1_bas<-mean(Sujet7_saut)-(1*sd(Sujet7_saut))
sd2_haut<-mean(Sujet7_saut)+(2*sd(Sujet7_saut))
sd2_bas<-mean(Sujet7_saut)-(2*sd(Sujet7_saut))

ggplot(Sujet7, aes(x = Date, y = moyenne)) +
  geom_point(color = "blue") +
  theme_classic()+ geom_hline(aes(yintercept = mean(Sujet7_saut)), color = "red")+
  geom_hline(aes(yintercept = sd1_haut), linetype = "dashed")+
  geom_hline(aes(yintercept = sd1_bas), linetype = "dashed")+
  geom_hline(aes(yintercept = sd2_haut), linetype = "dashed", color = "red")+
  geom_hline(aes(yintercept = sd2_bas), linetype = "dashed", color = "red")+geom_line()+
  ggtitle("Sujet 7 - Evolution Pic de vitesse lors d'un CMJ") + xlab("Date")+ylab("Vitesse (m/s)")




Nous constatons que pour les 3 sujets présentés ici le processus d'entraînement semble sous contrôle. 

3/ Conclusion

Un procédé basé sur la Maîtrise Statistique de Processus (MSP), aussi dénommée Statistical Process Control (SPC), est une méthode simple pour suivre la réponse du sportif au processus d'entraînement. Nous avons vu ici le cas du pic de vitesse lors d’un CMJ mais d’autres données peuvent sans problème être utilisées. 

Volontairement, le cas des tendances des courbes n’a pas été abordé. Il fera certainement l’objet d’un autre billet sur ce blog.

 

 

Réferences :

- Bertrand Mathieu, Alexis Peeters , Julien Piscione , Mathieu Lacome - Usefulness of typical tests of short-duration maximal effort used to assess players readiness to perform - SPSR - 2017 | Nov | 3 | v1

- Taylor KL. Monitoring neuromuscular fatigue in high performance athletes. Edith Cowan University; 2012.

- Kennedy, R., & Drake, D. (2018). Improving the Signal-To-Noise Ratio When Monitoring Countermovement Jump Performance: Signal-to-noise ratio of CMJ performance. Journal of Strength and Conditioning Research.

- William Sands, Marco Cardinale, Jeni McNeal, Steven Murray, Christopher Sole, Jacob Reed, Nikos Apostolopoulos, and Michael Stone - Recommendations for Measurement and Management of an Elite Athlete (2019)

La dynamique de la charge d’entrainement hebdomadaire correspond-t-elle à celle souhaitée par l’entraîneur ?

“Sans données, vous êtes juste quelqu’un avec une opinion” – W. Edwards Deming.

L’objectif de cet article est d’explorer la dynamique de la charge d’entrainement afin de permettre à l’entraîneur d’ajuster ses séances si nécessaire.

Les entraîneurs ont toujours de fortes opinions sur le processus d’entrainement et comment l’organiser. L’expérience de terrain à permis de construire les principes fondamentaux de la méthodologie de l’entrainement. Dans cet objectif, le suivi de la charge d’entrainement revêt un intérêt particulier. Il permet de définir et d’observer la dynamique de la difficulté de entrainement. Les entraîneurs ont ainsi leurs préférences : linéaire progressif, linéaire dégressif, ondulatoire/alternée/high-low, pour donner quelques exemples. La charge interne et la charge externe sont généralement utilisées afin d’apprécier cette dynamique.

Pour ceux qui ne sont pas familiers avec ces termes, la plupart du temps, la charge d’entraînement externe sont des tâches que l’athlète réalise lors de son entraînement (cadence de course / cyclisme, charges / intensités, séries, données GPS, etc.), tandis que la charge interne correspond à la réaction des athlètes à l’entraînement (FC, évaluation de la perception de l’effort perçu à la séance, compte rendu subjectif de bien-être, etc.).

Cependant il existe parfois des différences entre les prévisions de l’entraîneur et la réaction du sportif. Sans suivi de la charge interne, il est impossible de savoir comment le sportif ressent la semaine (ou la durée d’observation). C’est sur la base de la charge interne que l’entraîneur peut plus facilement ajuster, si nécessaire, ses entraînements et donc la charge externe.

Des données

Prenons un cas concret. Il a été demandé une combattante MMA de compléter un question de perception de l’effort (RPE) à la fin de tous les entraînements pendant 4 semaines. Les données sont disponibles sur mon Github. La charge d’entrainement (UA) = durée séance (min) x score RPE.

Sur la base de ces réponses, la question est la suivante : la dynamique de la charge d’entrainement hebdomadaire correspond-t-elle à celle souhaitée par l’entraîneur ?

Que cela veut-il bien dire ?

Sur la base de cette présentation graphique nous pouvons informer l’entraîneur que :

1/ Il semble y avoir alternance entre jours de forte charge d’entrainement (lundi et jeudi) et les jours de charge d’entrainement plus faible. Si c’est l’objectif, il serait possible d’explorer une augmentation de cette charge le lundi par différents moyens.

2/ Si l’objectif est d’avoir une dynamique linéaire (progressive ou régressive) dans la semaine, il semble nécessaire de réorganiser le planning hebdomadaire afin d’obtenir la dynamique souhaitée.

Les données et le code en langage R utilisés sont disponibles sur mon Github.

Durée du combat au MMA

La durée de l’effort est une donnée importante dans la préparation à un sport. C’est une information qui intéresse particulièrement des entraîneurs. On ne se prépare pas à un effort de 5 minutes comme à un effort de 15 minutes. Dans les sports de combat cela implique notamment la mise en place de stratégie technique, mais également de gestion de l’effort (pacing en anglais). L’Institut de la Performance de l’UFC (UFCPI) à publié en 2018 une étude nous éclairant sur ces notions. Ainsi, le durée moyenne d’un combat à l’UFC est de 10 minutes 43 seconde. De plus, ils ont constaté une relation entre la catégorie de poids et la durée du combat. Les catégories de poids plus légères présentant une durée moyenne de combat plus importante, tandis que les catégories de poids les plus lourdes présentent elles une durée moyenne de combat plus courte.
Cependant, plusieurs études sur ce type de sujet dans différents sports nous laisse à penser que le niveau de compétition est un facteur important sur certaines données d’efforts. Par exemple, au football un niveau de jeu plus élevé n’est pas nécessairement associé à une durée de jeu réel ou une distance parcourue plus importantes pendant la rencontre. Sans oublier la variabilité selon de poste joué.
La question qu’un entraîneur est en droit de se poser est donc : la durée type d’un combat de MMA est elle la même, peut importe le niveau ?
A partir des données de l’European BeatDown (EBD), sur 6 événements, je vous propose d’explorer la question grâce à du code en R.

Obtenir des données
Les données ont été obtenues grâce au site Tapology et sont disponibles sont mon github. Ils y a 83 observations pour lesquelles nous avons les variables suivantes : EDB, durée, résultat et catégorie. Nous aurons besoin du package ggplot2 pour la visualisation des données.

library(ggplot2)
head(EBD)

Distribution
Tout d’abord, observons la distribution de la fin du combat pour choisir les statistiques les plus appropriées.

ggplot(EBD, aes(x=Durée)) + 
geom_histogram(binwidth = 100) + 
theme_classic() + 
ggtitle ("EBD - Fin du combat")

Nous pouvons observer que de part sa distribution asymétrique la médiane et la distance interquartile seront des informations plus utiles que la moyenne qui sera certainement sur évaluée en raison du très grand nombre de combat d’une durée de 15 minutes (note: +1 min pour être précis).

Durée type du combat

ggplot(data=EBD, aes( y = Durée)) +  geom_boxplot()+ theme_classic() + ggtitle("EBD - Durée du combat")
quantile(EBD$Durée)
IQR(EBD$Durée)
median(EBD$Durée)

Nous obtenons une médiane à 7 minutes 07 (soit 3 minutes de moins que l’UFC) et une distance interquartile de 711 secondes (près de 11 minutes). Autrement dit, 50% des combats de l’EBD se terminent entre la 3ème et la 15 minutes. Notons également que très peu d’observations sont visibles à cette valeur sur l’histogramme précédent. Ces informations étant très peu précises et inutilisables par les entraîneurs, cherchons à présent les mêmes informations par catégories de poids.

Durée type du combat selon la catégorie

ggplot(data=EBD, aes(x = CATEGORIE, y = Durée)) +  geom_boxplot(aes(fill=CATEGORIE))+ theme_classic() + ggtitle("Durée du combat selon catégorie")+coord_flip()  +
  xlab("M/F:Catégorie(lbs)") + ylab("Durée (min:sec)") + geom_jitter(shape=2, position=position_jitter(0.2)) + theme(legend.text = element_text(size = 13),
   

Résumons
L’objectif de cette analyse était de déterminer sur les informations de l’UFC sur la durée du combat étaient utilisable pour d’autre niveau de compétition. Pour la durée moyenne, la réponse est non (sous réserve que l’UFC ait utilisée la médiane et non la moyenne). Les combats semblent plus long à l’UFC. Concernant la tendance à une relation entre la catégorie de poids et la durée du combat, la réponse est plutôt oui. Les observations des données de l’EBD semblent confirmer les tendances observées à l’UFC. Les catégories de poids plus légères semblent avoir des durées de combat plus importantes. Cependant, les entraîneurs seraient avisés de prendre en compte la durée type (50% des observations – distance interquartiles) selon la catégorie de poids pour construire une stratégie. Celles-ci étant particulièrement grande en relation avec la durée maximale du combat ( 3 x 5 min).

Références :

A Cross-Sectional Analysis & Projection of the UFC Athlete