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:
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:
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 .
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){ ... }
où 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"