Visualisation du nombre de victoires pour les équipes NBA à l'aide de graphiques à barres animées en R

Tout d'abord, quelques informations générales. Je m'appelle Vladislav et ma connaissance de R a eu lieu en août de l'année dernière. J'ai décidé d'apprendre un langage de programmation en raison d'une nature appliquée. Depuis mon enfance, j'aimais tenir des statistiques sportives. Avec l'âge, ce passe-temps s'est transformé en une envie d'analyser ces chiffres d'une manière ou d'une autre et, sur la base d'une analyse des données, de donner, si possible, des pensées intelligentes. Le problème est que ces dernières années, le sport a été balayé par une vague de données, des dizaines de sociétés rivalisent entre elles, essayant de calculer, décrire et enfoncer dans le neurone toute action d'un joueur de football, joueur de basket-ball, joueur de baseball sur le terrain. Et Excel n'est pas catégoriquement adapté à l'analyse. J'ai donc décidé d'étudier R pour que l'analyse la plus simple ne prenne pas une demi-journée. Déjà au cours de l'étude, un intérêt pour la programmation en tant que telle s'est ajouté, mais ce sont déjà les paroles.


Je veux remarquer immédiatement qu'une grande partie de ce que j'écrirai à l'avenir déjà été dans les simpsons était sur Habr dans l'article Nous créons des histogrammes animés en utilisant R. Cet article, à son tour, est une traduction de l'article Créer des graphiques à barres animés en utilisant R à partir de Medium. Par conséquent, afin de différer d'une manière ou d'une autre des articles ci-dessus, je vais essayer de décrire plus complètement ce que je fais, ainsi que les moments qui ne figurent pas dans l'article d'origine. Par exemple, pour remplir les colonnes, j'ai utilisé les couleurs des commandes NBA, pas la palette ggplot2 standard, mais dans le traitement des données du package data.table , pas dplyr . J'ai fait tout ce travail en fonction, alors maintenant il suffit d'écrire simplement le nom de l'équipe et les années pour lesquelles il faut compter le nombre de victoires.


Les données


Pour construire le calendrier, j'ai utilisé des données sur le nombre de victoires pour chacune des 30 équipes NBA au cours des 15 dernières saisons. Ils ont été collectés sur stats.nba.com à l'aide de l'extension NBA Data Retriever , qui, grâce à l'utilisation de l'API NBA, produit des fichiers csv avec les statistiques nécessaires. Voici les détails complets de mon projet sur Github .


Bibliothèques utilisées


 library(data.table) library(tidyverse) library(gganimate) 

Pour le traitement des données, j'utilise data.table (simplement parce que j'ai rencontré ce package auparavant). Je télécharge également un ensemble de packages tidyverse , et non un ggplot2 séparé afin de ne pas vous inquiéter, si lors de l'analyse une idée apparaît qui nécessite un chargement supplémentaire d'un package à partir de cet ensemble. Dans ce cas particulier, ggplot2 peut être supprimé, les autres packages définis ne sont pas impliqués. Eh bien, gganimate met les graphiques en mouvement.


Travailler avec des données


Vous devez d'abord mettre les données en ordre. Fondamentalement, pour construire des graphiques, nous avons besoin de 2 des 79 colonnes d'un tableau avec des données brutes. Vous pouvez d'abord sélectionner les colonnes nécessaires, vous pouvez d'abord remplacer certaines valeurs. Je suis allé dans la deuxième voie.


La table dans data.table a la forme dt[i, j, by] , où by est "responsable" du regroupement des éléments. Je vais regrouper par la colonne TeamName. Et il y a un hic. Cette colonne affiche les noms des équipes: Lakers, Celtics, Heat, etc. Mais au cours de la période sous revue (à partir de la saison 2004/05), plusieurs équipes ont changé de nom: les New Orleans Hornets sont devenus New Orleans Pelicans, Charlotte Bobcats a renvoyé le nom historique Charlotte Hornets et Seattle Supersonics est devenu Oklahoma City Thunder. Cela peut être source de confusion. Les conversions suivantes permettent d'éviter cela:


 table1 <- table[TeamCity == "New Orleans" & TeamName == "Hornets", TeamName := "Pelicans"][ TeamCity == "New Orleans/Oklahoma City" & TeamName == "Hornets", TeamName := "Pelicans"][ TeamName == "Bobcats", TeamName := "Hornets"][ TeamName == "SuperSonics", TeamName := "Thunder"] 

Pour cette période, les modifications sont minimes, mais si vous le développez, il deviendra très difficile de regrouper par TeamName et vous devrez utiliser une colonne plus fiable. Dans ces données, il s'agit de TeamID.


Pour commencer, nous nous débarrassons des informations "supplémentaires", ne laissant que les colonnes dont nous avons besoin pour travailler:


 table1 <- table1[ , .(TeamName, WINS)] 

Dans data.table la construction. () data.table la fonction de list . Une option plus "classique" pour sélectionner des colonnes est table1 <- table1[, c("TeamName", "WINS")] . Après cela, le tableau prend la forme suivante:


Nom d'équipeGAGNE
Soleils62
Chaleur59
Éperons59
Pistons54

Pour une animation pour chaque saison séparément, cela suffit, mais pour calculer le nombre total de victoires pour une période sélectionnée, vous devez calculer le total cumulé des victoires.


table1 <- table1[, CumWins := cumsum(WINS), by = "TeamName"]


En utilisant la fonction cumsum nous obtenons les chiffres dont nous avons besoin. L'utilisation de: = au lieu de = vous permet d'ajouter une nouvelle colonne au tableau; je ne la remplace pas par la même colonne CumWins. by = "TeamName" regroupe les données par le nom de l'équipe et le montant cumulé est by = "TeamName" séparément pour chacune des 30 équipes.


Ensuite, j'ajoute une colonne avec l'année de début de chaque saison. La saison NBA s'étend d'octobre à mai, elle se décompose donc en deux années civiles. Dans la désignation de la saison, l'année de son début, c'est-à-dire Saison: 2018 sur le graphique est la saison 2018/19 en réalité.


Le tableau d'origine contient ces données. La colonne SeasonID montre un chiffre sous la forme de 2 (l'année du début de la saison), par exemple 22004. Vous pouvez supprimer les deux premiers à l'aide du package stringr ou des fonctions R de base, mais je suis allé un peu différemment. Il s'est avéré que j'utilise d'abord cette colonne pour indiquer les saisons requises, puis supprime et crée à nouveau une colonne avec des dates. Actions supplémentaires.


Je l'ai fait comme suit:


table1 <- table1[,year := rep(seq(2004, 2018), each = length(unique(table1$TeamName)))]


J'ai eu la chance que, pour la période sélectionnée, le nombre d'équipes de la NBA n'ait pas changé, j'ai donc simplement répété les chiffres de 2004 à 2018 30 fois. Encore une fois, si vous remontez dans l'histoire, cette méthode ne sera pas pratique en raison du fait que le nombre d'équipes dans chaque saison sera différent, il est donc préférable d'utiliser l'option avec effacement de la colonne SeasonID.


Ajoutez ensuite la colonne cumrank.


table1 <- table1[, cumrank := frank(-CumWins, ties.method = "random"), by = "year"]


Il représente le classement des équipes de chaque saison par le nombre de victoires et sera utilisé comme valeurs de l'axe X. frank un analogue de data.table plus rapide du data.table de base, moins signifie le classement par ordre décroissant (cela peut également être fait en utilisant l'argument decreasing = TRUE . quel ordre les équipes avec le même nombre de victoires iront, donc ties.method = "random" . Eh bien, tout cela est regroupé en un an.


Et la dernière conversion de table ajoute la colonne value_rel .


table1 <- table1[, value_rel := CumWins/CumWins[cumrank==1], by = "year"]


Cette colonne considère le rapport entre le nombre de victoires de chaque équipe et l'indicateur le plus élevé de l'année. Pour la meilleure équipe, cet indicateur est 1, pour le reste il l'est moins, selon le succès de la saison.


Après tous les ajouts, le tableau a la forme suivante:


Nom d'équipeGAGNECumwinsannéecumrankvaleur_rel
Éperons5959200430,9516129
Éperons63122200511,0000000
Éperons58180200620,9729730
Éperons56236200711,0000000

Une seule équipe est présentée dans le tableau pour illustrer la cumulativité. Toutes ces actions se font, comme dans le changement de nom, avec une cascade de crochets


 table1 <- table1[ ,.(TeamName, WINS)][ , CumWins := cumsum(WINS), by = "TeamName"][ ,year := rep(seq(2004, 2018), each = length(unique(table1$TeamName)))][ , cumrank := frank(-CumWins, ties.method = "random"), by = "year"][ , value_rel := CumWins/CumWins[cumrank==1], by = "year"] 

Changez le remplissage des colonnes de la norme aux couleurs des équipes.


Vous pouvez immédiatement procéder à la construction de graphiques, mais il y a, comme il me semble, un point important: la couleur des colonnes du graphique. Vous pouvez quitter la palette ggplot2 standard, mais c'est une mauvaise option. Tout d'abord, il me semble qu'elle est laide. Et deuxièmement, il est difficile de trouver une équipe sur la carte. Pour les fans de la NBA, chaque équipe est associée à une couleur spécifique: Boston est verte, Chicago est rouge, Sacramento est violette, etc. Par conséquent, l'utilisation de la couleur de la commande dans les colonnes de remplissage permet de l'identifier plus rapidement, malgré l'abondance de bleu et de rouge.


Pour ce faire, créez une table table_color avec le nom de la commande et sa couleur principale. Couleurs prises sur teamcolorcodes.com .


Nom d'équipeTEAM_color
Hawks# E03A3E
Celtics# 007A33
Filets# 000000

Avec la table des couleurs, vous devez effectuer une autre manipulation. Parce que lorsque des facteurs de traçage sont utilisés, l'ordre des équipes change. Le premier sur la liste sera Philadelphie 76, en tant que propriétaire unique du nom "numérique", puis selon l'alphabet. Nous devons donc organiser les couleurs dans le même ordre, puis extraire le vecteur les contenant de la table. Je l'ai fait comme suit:


  table_color <- table_color[order(TeamName)] cols <- table_color[, "TEAM_color"] 

Tracer


Nous construisons vraiment un seul graphique, qui contient les 450 (15 saisons * 30 équipes) indicateurs de victoires, puis le "divisons" par la variable nécessaire (dans notre cas, par années) en utilisant les fonctions du package gganimate .


 gg <- ggplot(table1, aes(cumrank, group = TeamName, fill = as.factor(TeamName), color = as.factor(TeamName))) + geom_tile(aes(y = CumWins/2, height = CumWins, width = 0.7), color = NA, alpha = 0.8) 

Tout d'abord, nous créons un graphique en utilisant la fonction ggplot . Dans l'argument aes , spécifiez comment les variables du tableau seront affichées sur le graphique. Nous les regroupons par TeamName, le fill et la color seront responsables de la couleur des colonnes.


Les vraies colonnes l'appellent n'est pas entièrement vrai. En utilisant geom_tile nous "divisons" les données du graphique en rectangles. Voici un exemple de graphique de ce type:

On peut voir comment le graphique est "divisé" en carrés (ils sont obtenus à partir de rectangles utilisant la couche coord_equal() ), trois dans chaque colonne. Mais grâce à la width argument inférieure à un, notre mosaïque prend la forme de colonnes.


  geom_text(aes(y = 0, label = paste(TeamName, " ")), vjust = 0.2, hjust = 1, size = 6) + geom_text(aes(y = CumWins, label = paste0(" ",round(CumWins))), hjust = 0, size = 7) + coord_flip(clip = "off", expand = FALSE) + scale_fill_manual(values = cols) + scale_color_manual(values = cols) + scale_y_continuous(labels = scales::comma) + scale_x_reverse() + guides(color = FALSE, fill = FALSE) + 

Ensuite, j'ajoute deux signatures à l'aide de geom_text : le nom de l'équipe et le nombre de victoires. coord_flip permute les axes, scale_fill_manual et scale_color_manual changent la couleur des colonnes, scale_x_reverse "agrandit" l'axe X. Notez que nous prenons les couleurs du vecteur cols créé précédemment.


La couche de theme spécifie les options de réglage de l'affichage du graphique. Ici, il est indiqué comment les en-têtes et les étiquettes des axes doivent être affichés (en aucun cas ce que element_blank nous dit sur le côté droit de l'égalité). Nous supprimons la légende, l'arrière-plan, le cadre et les lignes de la grille le long de l'axe Y. plot.title arguments plot.title , plot.subtitle , plot.caption nous définissons les options d'affichage pour le titre, le sous-titre et la signature du graphique. Pour plus de détails sur la signification de tous les paramètres, voir gglot2


 theme(axis.line=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), axis.ticks=element_blank(), axis.title.x=element_blank(), axis.title.y=element_blank(), legend.position="none", panel.background=element_blank(), panel.border=element_blank(), panel.grid.major=element_blank(), panel.grid.minor=element_blank(), panel.grid.major.x = element_line( size=.1, color="grey" ), panel.grid.minor.x = element_line( size=.1, color="grey" ), plot.title=element_text(size=25, hjust=0.5, face="bold", colour="black", vjust=-1), plot.subtitle = element_text(size = 15), plot.caption =element_text(size=15, hjust=0.5, color="black"), plot.background=element_blank(), plot.margin = margin(2,2, 2, 4, "cm")) 

Créer une animation


Je ne m'attarderai pas sur l'utilisation de la fonction transition_states , cette partie est identique à ma publication précédente sur Habré. Quant aux labs il crée le titre, le sous-titre et la signature du graphique. L'utilisation de {closest_state} vous permet d'afficher chaque année spécifique sur le graphique, les colonnes dont nous voyons actuellement.


  anim <- gg + transition_states(year, transition_length = 4, state_length = 1) + view_follow(fixed_x = TRUE) + labs(title = "Cumulative Wins by teams in seasons", subtitle = "Season: {closest_state}", caption = "Telegram: @NBAatlantic, Twitter: @vshufiskiy\n Data sourse: stats.nba.com") 

Fonction nba_cumulative_wins pour créer des graphiques.


L'écriture de fonctions simplifie et accélère le processus d'obtention du résultat si vous devez utiliser le code plus d'une fois. Habituellement, une fonction dans R a la forme suivante:


 _ <- function( ) { _ } 

Vous devez d'abord comprendre quels paramètres vous souhaitez modifier à l'aide de la fonction, ses arguments en dépendent. Le premier argument est le nom de la table de données en cours d'entrée. Cela vous permet de le renommer si un tel désir survient, sans rien changer dans la fonction elle-même. Je veux également que n'importe quel nombre de commandes soit affiché sur le graphique: de une (ce qui est inutile) à 30 (il n'y en a tout simplement plus). Je souhaite également pouvoir prendre en compte toutes les périodes au cours de ces 15 années pour lesquelles j'ai des données. Tout cela est implémenté sous cette forme de fonction:


 nba_cumulative_wins <- function(table, elements, first_season, last_season){ ... } 

table est le nom de la table avec les données d'entrée,
elements - les noms des équipes qui devraient être affichées sur le graphique
first_season - la première saison à afficher sur le graphique
last_season - la dernière saison à afficher sur le graphique.


Si l'argument est très souvent utilisé avec une valeur spécifique, vous pouvez le définir par défaut. Ensuite, si elle est omise parmi les arguments de la fonction, alors cette valeur sera substituée. Par exemple, si vous vous inscrivez


nba_cumulative_wins <- function(table, elements, first_season, last_season = 2018)


les horaires seront ensuite établis jusqu'à la saison 2018/19, sauf indication contraire.


Utilisation d' elements arguments, first_season , last_season


En utilisant l'argument elements , nous pouvons spécifier le nom des équipes que nous voulons voir sur le graphique. C'est très pratique quand il y a 2 ou 3 équipes de ce type, mais si nous voulons afficher la ligue entière, nous devrons écrire les elements = c() et le nom des 30 équipes entre parenthèses.


J'ai donc décidé de "diviser" les valeurs d'entrée de l'argument elements en plusieurs groupes.
La fonction nba_cumulative_wins peut créer des graphiques pour des équipes individuelles, des divisions, des conférences ou la NBA dans son ensemble. Pour cela, j'ai utilisé la construction suivante:


  select_teams <- unique(table1$TeamName) select_div <- unique(table1$Division) select_conf <- unique(table1$Conference) select_nba <- "NBA" table1 <- if(elements %in% select_teams){ table1[TeamName %in% elements] } else if (elements %in% select_div){ table1[Division %in% elements] } else if(elements %in% select_conf){ table1[Conference %in% elements] } else if(elements == "NBA"){ table1 } else { NULL } 

select_ vecteurs symboliques select_ contiennent les noms des 30 équipes, 6 divisions, 2 conférences et la NBA, et la fonction unique ne laisse qu'un seul nom unique, au lieu de 15 (par le nombre d'années dans les données).


Ensuite, en utilisant la if...else , l'argument elements entré est vérifié pour appartenir à l'une des classes ( %in% utilisé pour déterminer si l'élément appartient au vecteur), et la table de données est modifiée en conséquence. Maintenant, si je veux voir les résultats des équipes jouant dans la division Sud-Ouest à la place


elements = c("Mavericks", "Spurs", "Rockets", "Grillies", "Pelicans")


entrez simplement


elements = "Southwest" , ce qui est beaucoup plus rapide et plus pratique.


En raison de la possibilité de choisir des saisons, le travail avec les dates change également. Au tout début, la ligne est ajoutée:


 table1 <- table1[SeasonID >= as.numeric(paste(2, first_season, sep = "")) & SeasonID <= as.numeric(paste(2, last_season, sep = ""))] 

Je ne laisse donc dans le tableau que les lignes qui tombent dans l'intervalle de temps choisi. Le code de création de la colonne year change également. Maintenant, cela ressemble à ceci:


 table1 <- table1[ ,year := rep(seq(first_season, last_season), each = length(unique(table1$TeamName)))] 

Dans le cadre du regroupement des éléments, la procédure d'obtention des couleurs souhaitées est compliquée. Le fait est que dans la table table_color seuls les noms des commandes. Par conséquent, nous devons «déployer» nos contractions en arrière. Pour ce faire, utilisez à nouveau la construction if...else .


  elements1 <- if (elements == "NBA"){ c("Hawks", "Celtics", "Nets", "Hornets", "Bulls", "Cavaliers", "Mavericks", "Nuggets", "Pistons", "Warriors", "Rockets", "Pacers", "Clippers", "Lakers", "Grizzlies", "Heat", "Bucks", "Timberwolves", "Pelicans", "Knicks", "Thunder", "Magic", "76ers", "Suns", "Trail Blazers","Kings", "Spurs", "Raptors", "Jazz", "Wizards") } else if (elements == "West") { c("Mavericks","Nuggets", "Warriors", "Rockets", "Clippers", "Lakers", "Grizzlies","Timberwolves", "Pelicans", "Thunder", "Suns", "Trail Blazers","Kings", "Spurs", "Jazz") } else if (elements == "East") { c("Hawks", "Celtics", "Nets", "Hornets", "Bulls", "Cavaliers","Pistons", "Pacers", "Heat", "Bucks", "Knicks", "Magic", "76ers", "Raptors", "Wizards") } else if (elements == "Pacific") { c("Warriors", "Clippers", "Lakers", "Suns", "Kings") } else if (elements == "Southeast") { c("Magic", "Hornets", "Heat", "Hawks", "Wizards") } else if (elements == "Southwest") { c("Mavericks", "Grizzlies", "Pelicans", "Rockets", "Spurs") } else if (elements == "Central") { c("Bucks", "Pacers", "Pistons", "Bulls", "Cavaliers") } else if (elements == "Atlantic") { c("Knicks", "Nets", "Celtics", "Raptors", "76ers") } else if (elements == "Northwest") { c("Nuggets", "Trail Blazers", "Jazz", "Thunder", "Suns") } else { elements } 

Ensuite, créez une table avec les noms des commandes dont nous avons besoin, connectez cette table à table_color utilisant la fonction dplyr package dplyr . inner_join inclut uniquement les cas qui correspondent dans les deux tables.


  table_elements1 <- data.table(TeamName = elements1) table_color <- table_color[order(TeamName)] inner_table_color <- inner_join(table_color, table_elements1) cols <- inner_table_color[, "TEAM_color"] 

La fonction change l'orthographe du titre et du sous-titre. Ils prennent ce look:


 anim <- gg + transition_states(year, transition_length = 4, state_length = 1) + view_follow(fixed_x = TRUE) + labs(title = paste("Cumulative Wins by teams in seasons", first_season, "-", last_season, sep = " "), subtitle = paste(if (elements %in% select_div ){ paste(elements, "Division", sep = " ") } else if (elements %in% select_conf ){ paste("Conference", elements, sep = " ") }, "Season: {closest_state}", sep = " "), caption = "Telegram: @NBAatlantic, Twitter: @vshufiskiy\nData sourse: stats.nba.com") 

Rendu


De plus, tout cela est visualisé.


 animate(anim, nframes = (last_season - first_season + 1) * (length(unique(table1$TeamName)) + 20), fps = 20, width = 1200, height = 1000, renderer = gifski_renderer(paste(elements[1], "cumwins.gif", sep = "_"))) 

J'ai sélectionné le nombre en nframes empiriquement, de sorte qu'en fonction du nombre de commandes sélectionnées, la vitesse augmente / diminue.


Graphique



J'espère que mon message est intéressant. Code de projet Github .


Si vous êtes intéressé par la composante sportive de ces visualisations, vous pouvez visiter mon blog sur sports.ru "Des deux côtés de l'Atlantique"

Source: https://habr.com/ru/post/fr458904/


All Articles