Archives par étiquette : basket-ball

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.

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