Création d'un graphique linéaire moyen animé animé dans R. Récupération de données via l'API NBA

Nous continuons d'analyser les données de basket-ball en utilisant R.


Contrairement à l' article précédent, qui était exclusivement de nature divertissante, les graphiques qui seront construits dans cet article peuvent être intéressants du point de vue de l'analyse du jeu d'équipe de la campagne de la saison.


Et nous allons construire des graphiques de moyenne mobile pour trois types de notes d'équipe NBA: attaquant, défensif et net (c'est-à-dire la différence entre les deux premiers). En bref à leur sujet. Les notes offensives et défensives sont le nombre de points marqués / manqués par une équipe pour 100 possessions. Note NET - c'est leur différence pour cent possessions. Toute personne intéressée à en savoir plus à leur sujet peut lire le glossaire sur le basket-référence . Il existe une formule de calcul, que j'ai également implémentée à l'aide de R, mais je n'ai pas encore publié d'article à ce sujet.


Je vais également expliquer pourquoi je vais construire le graphique de la moyenne mobile. Dans chaque match individuel, la proportion d'aléatoire est trop élevée, les indicateurs passent de 70 à 150, ce qui rend l'analyse des données inutile, et le graphique lui-même ressemble plus à un cardiogramme. Si nous prenons la moyenne cumulative, nous obtenons alors un autre extrême: le calendrier est similaire aux fluctuations amorties et les matchs à la fin de la saison, lorsqu'ils sont ajoutés aux 70-75 matchs déjà organisés, n'affectent pratiquement pas l'indicateur global. En gros, ils ne sont "pas visibles". La moyenne mobile dans ce cas est le moyen de sortir de l'impasse. D'une part, l'influence du hasard diminue, d'autre part, il n'y a pas d'accumulation excessive de résultats. Dans les statistiques de basket-ball, ils font généralement une moyenne mobile de 10 matchs.


Bibliothèques utilisées


library(httr) library(jsonlite) library(tidyverse) library(lubridate) library(zoo) library(ggthemes) library(gganimate) 

Récupération de données à l'aide de l'API NBA


La dernière fois, j'ai récupéré des données à l'aide de l'extension NBA Data Retriever . Cette fois, j'utiliserai l'API NBA pour charger directement les données requises dans R.


Nous découvrons d'abord où obtenir ces données. Pour ce faire, ouvrez la page dont nous avons besoin sur stats.nba.com et accédez aux outils de développement. Ouvrez ensuite Réseau -> XHR et appuyez sur F5. Dans la liste qui apparaît, nous trouvons un fichier avec un nom similaire au nom de la page. Nous avons besoin de lui. Après vous être assuré que vous avez sélectionné le bon fichier, copiez son adresse dans R. Dans les images, il ressemble à ceci.


ouvrez le fichier souhaité



le fichier devrait ressembler à ceci



copie à l'adresse R



Maintenant, allons travailler dans R Studio . Pour obtenir les informations dont nous avons besoin, nous utilisons la fonction GET du package http . Cependant, pour que la demande soit exécutée correctement (cela peut être vérifié par la fonction status_code , elle doit être 200), vous devez ajouter des en-têtes pour déterminer les paramètres de travail de la transaction HTTP


 ##Adding headers request_headers <- c( "accept-encoding" = "gzip, deflate, sdch", "accept-language" = "en-US,en;q=0.8", "cache-control" = "no-cache", "connection" = "keep-alive", "host" = "stats.nba.com", "pragma" = "no-cache", "upgrade-insecure-requests" = "1", "user-agent" = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9" ) #Getting a response request <- GET(adv_box_team, add_headers(request_headers)) 

Nous obtenons une réponse comme celle-ci:



Mais alors que les données dont nous avons besoin ne sont pas visibles. Pour les obtenir, nous extrayons d'abord le content la demande par le content de la fonction dans un fichier json, puis le convertissons en une liste avec une fonction du package jsonlite avec le nom parlant de fromJSON


 boxscore_data <- fromJSON(content(request, as = "text")) 

En conséquence, nous obtenons une liste qui contient déjà toutes les informations dont nous avons besoin, puis nous la mettons simplement sous la forme nécessaire pour le travail.


Préparation des données


Pour ce faire, créez un tableau de données au lieu d'une liste, puis ajoutez des en-têtes de colonne.


 #Convert to tibble data and assigning column names table <- tbl_df(data.frame(boxscore_data$resultSets$rowSet[[1]], stringsAsFactors = FALSE)) names(table) <- toupper(boxscore_data$resultSets$headers[[1]]) 

toupper est une fonction qui remplace tous les caractères par des majuscules. Après cela, nous devrions obtenir un tableau avec 2460 lignes et 46 colonnes. En principe, vous pouvez travailler avec le tableau sous cette forme, mais il est préférable d'exclure les informations inutiles, pour un travail plus pratique et plus rapide.


 ##Select the columns you want to analyze rating <- table %>% select(TEAM_ID, TEAM_ABBREVIATION, TEAM_NAME, GAME_ID, GAME_DATE, MATCHUP, WL, E_OFF_RATING, E_DEF_RATING, E_NET_RATING) 

Si vous regardez le tableau source, vous pouvez voir deux types de même classement: «normal» et avec le préfixe E. Sans entrer dans les détails, le classement E prend en compte le rythme du jeu, il est donc plus précis. Nous le prenons.


Ensuite, je veux simplifier les noms des évaluations. Ils devront être introduits dans les arguments de la fonction et il est préférable d'utiliser la notation plus familière à un large éventail d'utilisateurs: ORTG, DRTG, NRTG. Ici, vous pouvez "vous perdre" en écrivant une expression régulière et en la remplaçant par str_replace , mais les écrire est toujours un plaisir et ici nous pouvons nous en passer parfaitement. Il suffit d'extraire les 3, 7, 9 et 12 caractères des noms actuels, de les combiner et de remplacer les noms de colonnes par le vecteur de caractères résultant. Tout cela se fait en utilisant les fonctions du package stringr : str_sub et str_c (un analogue de la base paste0 ).


 ## Renaming columns with E_OFF_RATING on ORTG rating1 <- rating %>% rename_at(vars(starts_with("E_")), list(~str_c(str_sub(., start = 3, end = 3), str_sub(., start = 7, end = 7), str_sub(., start = 9, end = 9), str_sub(., start = 12, end = 12)))) 

at dans les fonctions de package dplyr a la même propriété que la construction dt[, lapply(.SD, func), .SDols = col1] dans le package data.table : l'action est appliquée à plusieurs colonnes en même temps. Ici, nous sélectionnons toutes les colonnes dont le nom commence par "E_".


En conséquence, nous obtenons une telle table, avec laquelle nous continuerons à travailler avec:


TEAM_IDTEAM_ABBREVIATIONTEAM_NAMEGAME_IDGAME_DATEMATCHUPWlORTGDRTGNRTG
1610612749MilMilwaukee dollars00218012262019-04-10T00: 00: 00MIL vs OkcL102,4116,8-14,4
1610612766ChaFrelons Charlotte00218012222019-04-10T00: 00: 00CHA vs. ORLL121,4130,1-8,6
1610612758SACRois de Sacramento00218012302019-04-10T00: 00: 00SAC @ PORL129,7136,4-6,8
1610612748MIAMiami Heat00218012212019-04-10T00: 00: 00MIA @ BKNL84,2103,6-19,4
1610612750MINLoups de bois du Minnesota00218012282019-04-10T00: 00: 00MIN @ DENL98,3103,7-5,4

La fonction rolling_offnet_rating_nba pour tracer et animer une moyenne mobile.


Encore une fois, comme la dernière fois, créons une fonction pour effectuer des changements minimes dans les calculs.


La fonction rolling_offnet_rating_nba à ceci:


 rolling_offnet_rating_nba <- function(table, name, variable, col1 = col1, col2 = col2) 

table est le nom de la table de données,
nom - l'abréviation de l'équipe pour laquelle les graphiques seront réalisés ("BOS", "LAL", etc.).
variable - la cote qui sera calculée (voici deux options, ORTG ou NRTG, pour la cote de protection j'ai fait une fonction distincte)
col1 et col2 - couleur de ligne à une valeur supérieure / inférieure à la moyenne.


La plupart des fonctions dplyr utilisent une évaluation non standard (NSE ). Il s'agit d'un terme général signifiant que leur évaluation diffère de l'évaluation habituelle en R. Cela nous permet de simplifier l'écriture de code et de travailler avec des bases de données SQL, mais le moins est que nous ne pouvons pas remplacer la valeur par un objet équivalent défini ailleurs.


Dplyr utilise l' évaluation Tidy . Par conséquent, il est nécessaire d'utiliser des outils spéciaux (fonctions de citation, opérateur !!) pour résoudre les problèmes rencontrés lors de la programmation. Vous pouvez en savoir plus à ce sujet ici , et voir ici .


Le code suivant prend le nom de l'argument de fonction et écrit l'expression qui lui a été présentée. (Pour comprendre comment enquo et ses fonctions similaires fonctionnent, il est utile d'imprimer la sortie de cette fonction)


 ##Return the entered value in the function argument in the type quosure quo_rating <- enquo(variable) quo_col1 <- enquo(col1) quo_col2 <- enquo(col2) 

Ensuite, nous changeons le format de données de certaines colonnes: nous faisons du GAME_DATE du caractère une colonne au format Date, et nous rendons les colonnes de notation numériques. Parce que nous appliquons la fonction as.numeric à trois colonnes, puis mutate_at utilisé à la place de mutate . Et nous trions tout par ordre croissant de date.


 ##Changing the data type of multiple columns test1 <- table %>% mutate(GAME_DATE = as.Date(ymd_hms(GAME_DATE))) %>% mutate_at(vars(ORTG:NRTG), list(~as.numeric)) %>% arrange(GAME_DATE) 

Et puis nous calculons la moyenne mobile de 10 matchs de l'équipe dont nous avons besoin. Pour ce faire, utilisez la fonction rollmeanr du package zoo . r à la fin du nom signifie que le résultat doit être aligné à droite. Pour les neuf premiers matchs de la saison, une moyenne mobile de 10 matches est tout simplement impossible à calculer, nous laissons donc ces champs inchangés en les remplissant en NA à l'aide de l'argument fill. na.omit supprime du tableau les lignes dans lesquelles ces NA se produisent.


 ##The calculation of the moving average team <- test1 %>% filter(TEAM_ABBREVIATION == "DAL") %>% mutate(RATING = rollmeanr(ORTG, k = 10, fill= NA)) %>% na.omit(test1) 

La table d'équipe ressemble à ceci:


TEAM_IDTEAM_ABBREVIATIONTEAM_NAMEGAME_IDGAME_DATEMATCHUPWlORTGDRTGNRTGÉvaluation
1610612742DALDallas mavericks00218001502018-11-06DAL contre ÉtaitW116,899,217,6105,51
1610612742DALDallas mavericks00218001602018-11-07DAL @ UTAL98,5112,0-13,6104,92
1610612742DALDallas mavericks00218001812018-11-10DAL contre OkcW115,0101,113,9104,13
1610612742DALDallas mavericks00218001932018-11-12DAL @ CHIW98,391,07.3103.03
1610612742DALDallas mavericks00218002102018-11-14DAL contre UTAW117,365,851,6105,34

En principe, nous avons déjà reçu les informations dont nous avons besoin. À l'aide de deux lignes de code, vous pouvez créer un graphique linéaire. Mais la ligne noire sur fond blanc présente peu d'intérêt d'un point de vue à la fois esthétique et informatif. Une autre partie du "corps de fonction" corrige cela.


Pour commencer, nous ajoutons les données sur la valeur moyenne, 10e et 21e (dixième à partir du bas), ainsi que la date 10 du match par équipe (c'est-à-dire la première pour laquelle la moyenne mobile est calculée et qui est restée dans le tableau d'équipe après avoir supprimé les lignes de NA) .


 ##The average, 10 and 21 ratings in the entire League. average <- league %>% mutate(average = mean(!! quo_rating)) %>% select(average) %>% unique() %>% .$average top10 <- league %>% arrange(desc(!! quo_rating)) %>% select(!! quo_rating) %>% slice(10) top10 <- top10[[1]] bottom10 <- league %>% arrange(desc(!! quo_rating)) %>% select(!! quo_rating) %>% slice(21) bottom10 <- bottom10[[1]] ##Getting the date of the first rollaverage data <- team %>% select(GAME_DATE) %>% arrange(GAME_DATE) data <- data[[1,1]] 

À partir des fonctions précédemment inutilisées, la fonction slice apparaît ici, qui sélectionne les lignes par leur numéro de série.


Ensuite, nous sélectionnons 2 couleurs et leur nom. Les données, comme la dernière fois, sont table_color de la table table_color . Le nom sera utilisé dans l'en-tête du graphique pour expliquer laquelle des couleurs correspond à des valeurs inférieures à la moyenne et laquelle est supérieure.


 ##Getting color and color_name selected color color1 <- table_color %>% filter(TEAM_ABBREVIATION == name) %>% select(!! quo_col1) color1 <- color1[[1]] color2 <- table_color %>% filter(TEAM_ABBREVIATION == name) %>% select(!! quo_col2) color2 <- color2[[1]] name1 <- paste0("name_", quo_name(quo_col1)) name2 <- paste0("name_", quo_name(quo_col2)) name_color1 <- table_color %>% filter(TEAM_ABBREVIATION == name) %>% select(name1) name_color1 <- name_color1[[1]] name_color2 <- table_color %>% filter(TEAM_ABBREVIATION == name) %>% select(name2) name_color2 <- name_color2[[1]] 

Les arguments de la fonction sont par défaut col1 et col2, ce sont les première et deuxième couleurs des commandes. Dans la plupart des cas (plus précisément dans 26), ces valeurs n'ont pas besoin d'être modifiées, cependant, pour quatre équipes, la couleur suivante doit être utilisée dans leur palette de couleurs. À Dallas et au Minnesota, les première et deuxième couleurs sont trop similaires, tandis qu'à Milwaukee et Brooklyn, elles ne sont pas visibles sur fond blanc. À la fois cela, et un autre complique la lecture du calendrier, il vaut donc la peine d'utiliser l'argument col2 = col3 pour eux.


Ensuite, nous obtenons la note maximale pour l'équipe. Nous aurons besoin de cette valeur pour organiser le texte avec la valeur de notation sur le graphique. Je veux faire attention à la dernière ligne de code. Il se trouve que les fonctions ont parfaitement tracé les graphiques dans 89 des 90 cas, mais lors de la construction d'un indice de protection, Milwaukee a donné une erreur. Il s'est avéré que la valeur nominale maximale à Milwaukee est atteinte deux fois et ggplot2 commence naturellement à jurer que l'esthétique devrait être, dans notre cas, 1 ou 73. Par conséquent, nous avons besoin d'une seule valeur nominale maximale.


 ##The maximum value of the rating max <- team %>% filter(RATING == max(RATING)) %>% select(RATING) max <- max[[1]] 

Construire un graphe statique dans ggplot2


 ##Building and save a static chart Sys.setlocale("LC_ALL", "C") gg <- ggplot(team, aes(GAME_DATE, RATING)) + geom_hline(yintercept = c(top10, bottom10), col = c("red", "blue")) + annotate(geom = "text", x = as.Date(data) + 2, y = top10 - 0.2, label = "TOP 10", col = "red") + annotate(geom = "text", x = as.Date(data) + 2, y = bottom10 + 0.2, label = "BOTTOM 10", col = "blue") + geom_line(size = 2, col = if_else(team$RATING > average, color1, color2)) + theme_tufte() + labs(title = paste0(team$TEAM_NAME, " 10-Game Rolling ", quo_name(quo_rating)), subtitle = paste0(paste0(name_color1, " - above average ", quo_name(quo_rating)), "\n", paste0(name_color2, " - below average ", quo_name(quo_rating))), caption = "Source: BBall Index Data & Tools\nTelegram: @NBAatlantic, twitter: @vshufinskiy") theme(plot.title = element_text(size = 12, hjust = 0.5), plot.caption = element_text(size = 10), plot.subtitle = element_text(size = 9)) ggsave(paste0(unique(team$TEAM_NAME), quo_name(quo_rating), ".jpeg"), gg, width = 8, units = "in") 

Parmi les nouveautés ici, l'utilisation de la fonction if_else pour changer la couleur de la ligne selon que la note moyenne de la ligue est supérieure ou inférieure, ainsi que la première ligne qui modifie les paramètres régionaux. Ceci est fait de sorte que les abréviations des noms des mois le long de l'axe X soient écrites en anglais.


Animation d'une moyenne mobile de 10 correspondances.


Dans la construction de l'animation, j'ai ajouté plusieurs lotions qui ne sont pas possibles dans la version statique. Tout d'abord, la date de changement (similaire à la façon dont l'année a changé dans le dernier article), ainsi que la valeur de la notation à un moment donné. Il change également de couleur selon qu'il est supérieur ou inférieur à la moyenne.


 ##Building animations anim <- gg + theme(plot.title = element_text(hjust = 0.5, size = 25), plot.subtitle = element_text(size = 15), plot.caption = element_text(size = 15), axis.text = element_text(size = 15), axis.title = element_text(size = 18)) + geom_text(aes(x = as.Date(data), y = max + 0.5), label = paste0(quo_name(quo_rating)," ", round(team$RATING, digits = 1)), size = 6, col = if_else(team$RATING > average, color1, color2)) + transition_reveal(GAME_DATE) + labs(title = paste0(team$TEAM_NAME, " 10-Game Rolling ", quo_name(quo_rating)), subtitle = paste0(paste0(name_color1, " - above average ",quo_name(quo_rating)), "\n", paste0(name_color2, " - below average ",quo_name(quo_rating)), "\n", "Date: {frame_along}"), caption = paste0("Source: stats.nba.com\nTelegram: @NBAatlantic, twitter: @vshufinskiy")) 

Résultat



Sur le graphique, il est assez évident que Dallas a plongé dans la seconde moitié de février-mars. L'explication est très simple: c'est à ce moment de la saison que les Mavericks ont échangé 4 joueurs sur 5 dans leurs cinq premiers et le principal atout entrant, le letton Kristaps Porzingis, n'a pas joué pendant une minute en raison de la rupture des ligaments croisés.


Ici, je ne vais pas me plonger dans la composante sportive, donc si quelqu'un est intéressé à voir les 89 autres graphiques de la saison 2018-19, alors vous êtes les bienvenus sur mon blog sur sports.ru , où j'ai l'intention d'écrire un article avec un aperçu des plus intéressants d'entre eux ou dans mon télégramme canal sur la NBA, où je vais tous les poster.


Dépôt GitHub

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


All Articles