Continuamos analizando datos de baloncesto usando R.
A diferencia del artículo anterior, que fue puramente entretenido, los gráficos que se construirán en este artículo pueden ser interesantes desde el punto de vista de analizar el juego del equipo de la campaña de la temporada.
Y construiremos gráficos de promedio móvil para tres tipos de clasificación de equipos de la NBA: ataque, defensa y clasificación neta (es decir, la diferencia entre los dos primeros). En pocas palabras sobre ellos. Las clasificaciones de ataque y defensa son el número de puntos anotados / perdidos por un equipo por 100 posesiones. Calificación neta: esta es su diferencia para cien posesiones. Cualquier persona interesada en aprender más sobre ellos puede leer el glosario sobre referencias de baloncesto . Hay una fórmula de cálculo que también implementé usando R, pero aún no he publicado un artículo al respecto.
También explicaré por qué construiré el gráfico de la media móvil. En cada coincidencia individual, la proporción de aleatoriedad es demasiado alta, los indicadores saltan de 70 a 150, lo que hace que el análisis de datos sea inútil, y el gráfico en sí es más como un cardiograma. Si tomamos el promedio acumulativo, obtenemos otro extremo: el cronograma es similar a las fluctuaciones amortiguadas, y los juegos al final de la temporada, cuando se suman a los partidos ya celebrados 70-75, prácticamente no afectan el indicador general. En términos generales, "no son visibles". La media móvil en este caso es la salida del estancamiento. Por un lado, la influencia del azar disminuye, por otro lado, no hay una acumulación excesiva de resultados. En las estadísticas de baloncesto, generalmente hacen un promedio móvil de 10 partidos.
Bibliotecas utilizadas
library(httr) library(jsonlite) library(tidyverse) library(lubridate) library(zoo) library(ggthemes) library(gganimate)
Recuperando Datos Usando la API NBA
La última vez, recuperé datos utilizando la extensión NBA Data Retriever . Esta vez usaré la API de la NBA para cargar directamente los datos requeridos en R.
Primero descubrimos de dónde obtener estos datos. Para hacer esto, abra la página que necesitamos en stats.nba.com y vaya a las herramientas para desarrolladores. Luego abra Red -> XHR y presione F5. En la lista que aparece, encontramos un archivo con un nombre similar al nombre de la página. Lo necesitamos a el. Después de asegurarse de haber seleccionado el archivo correcto, copie su dirección en R. En las imágenes se ve así.
abre el archivo deseado

el archivo debería verse así

copiar a la dirección R

Ahora vamos a trabajar en R Studio . Para obtener la información que necesitamos, utilizamos la función GET
del paquete http
. Sin embargo, para que la solicitud se ejecute correctamente (esto puede verificarse mediante la función status_code
, debe ser 200), debe agregar encabezados para determinar los parámetros de funcionamiento de la transacción 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))
Recibimos una respuesta como esta:

Pero mientras los datos que necesitamos no son visibles. Para obtenerlos, primero extraemos el content
la solicitud por el content
la función en un archivo json, y luego lo convertimos en una lista con una función del paquete jsonlite
con el nombre parlante de fromJSON
boxscore_data <- fromJSON(content(request, as = "text"))
Como resultado, obtenemos una lista que ya contiene toda la información que necesitamos y luego simplemente la traemos al formulario que se necesita para trabajar.
Preparación de datos
Para hacer esto, cree una tabla de datos en lugar de una lista y luego agregue encabezados de columna.
#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
es una función que reemplaza todos los caracteres con mayúsculas. Después de eso, deberíamos obtener una tabla con 2460 filas y 46 columnas. En principio, puede trabajar con la tabla de esta forma, pero es mejor excluir información innecesaria, para un trabajo más conveniente y rápido.
##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 observa la tabla de origen, puede ver dos tipos de la misma calificación: "normal" y con el prefijo E. Sin entrar en detalles, la calificación E tiene en cuenta el ritmo del juego, por lo tanto, es más preciso. Lo tomamos
A continuación, quiero simplificar los nombres de las calificaciones. Deberán introducirse en los argumentos de la función y es mejor usar la notación más familiar para una amplia gama de usuarios: ORTG, DRTG, NRTG. Aquí puede "confundirse" con escribir una expresión regular y reemplazar con str_replace
, pero escribirlas sigue siendo un placer y aquí podemos prescindir de ellas perfectamente. Solo necesitamos extraer los 3, 7, 9 y 12 caracteres de los nombres actuales, combinarlos y reemplazar los nombres de columna con el vector de caracteres resultante. Todo esto se hace utilizando las funciones del paquete str_sub
: str_sub
y str_c
(un análogo 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))))
en las funciones del paquete dplyr
tiene la misma propiedad que la construcción dt[, lapply(.SD, func), .SDols = col1]
en el paquete data.table
: la acción se aplica a varias columnas al mismo tiempo. Aquí seleccionamos todas las columnas cuyo nombre comienza con "E_".
Como resultado, obtenemos dicha tabla, con la que continuaremos trabajando con:
La función rolling_offnet_rating_nba para trazar y animar una media móvil.
Nuevamente, como la última vez, creemos una función para hacer cambios mínimos en los cálculos.
La función rolling_offnet_rating_nba
así:
rolling_offnet_rating_nba <- function(table, name, variable, col1 = col1, col2 = col2)
tabla es el nombre de la tabla de datos,
nombre : la abreviatura del equipo para el que se realizarán los gráficos ("BOS", "LAL", etc.).
variable : la calificación que se calculará (aquí hay dos opciones, ORTG o NRTG, para la calificación de protección hice una función separada)
col1 y col2 : color de línea en un valor superior / inferior al promedio.
La mayoría de dplyr
funciones de dplyr
usan evaluación no estándar (NSE ). Este es un término general que significa que su evaluación difiere de la evaluación habitual en R. Esto nos permite simplificar la escritura de código y trabajar con bases de datos SQL, pero la desventaja es que no podemos reemplazar el valor con un objeto equivalente definido en otro lugar.
Dplyr utiliza la evaluación ordenada . Por lo tanto, es necesario usar herramientas especiales (funciones de citas, operador !!) para resolver los problemas encontrados durante la programación. Puede leer más sobre esto aquí , y ver aquí .
El siguiente código toma el nombre del argumento de la función y escribe la expresión que se le presentó. (Para comprender cómo funcionan enquo
y sus funciones similares, es útil imprimir el resultado de esta función)
##Return the entered value in the function argument in the type quosure quo_rating <- enquo(variable) quo_col1 <- enquo(col1) quo_col2 <- enquo(col2)
A continuación, cambiamos el formato de datos de algunas columnas: hacemos que GAME_DATE del personaje sea una columna en el formato Fecha, y hacemos que las columnas de calificación sean numéricas. Porque aplicamos la función as.numeric
a tres columnas, luego mutate_at
usa mutate_at
lugar de mutate
. Y clasificamos todo en orden ascendente de fecha.
##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)
Y luego calculamos el promedio móvil de 10 partidos del equipo que necesitamos. Para hacer esto, use la función rollmeanr
del paquete zoo
. r al final del nombre significa que el resultado debe estar alineado a la derecha. Para los primeros nueve juegos de la temporada, un promedio móvil de 10 partidos es simplemente imposible de calcular, por lo que dejamos estos campos sin cambios al llenarlos en NA utilizando el argumento de relleno. na.omit
elimina de la tabla las filas en las que ocurren estos NA.
##The calculation of the moving average team <- test1 %>% filter(TEAM_ABBREVIATION == "DAL") %>% mutate(RATING = rollmeanr(ORTG, k = 10, fill= NA)) %>% na.omit(test1)
La mesa del equipo se ve así:
En principio, ya hemos recibido la información que necesitamos. Usando dos líneas de código, puede construir un gráfico lineal. Pero la línea negra sobre un fondo blanco es de poco interés tanto desde el punto de vista estético como informativo. Una parte adicional del "cuerpo de funciones" corrige esto.
Para comenzar, agregamos los datos sobre el valor de calificación promedio, 10 y 21 (décimo desde abajo), así como la fecha 10 del partido del equipo (es decir, el primero para el que se calcula el promedio móvil y que permaneció en la tabla del equipo después de eliminar las líneas 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]]
De las funciones no utilizadas anteriormente, la función de slice
aparece aquí, que selecciona las filas por su número de serie.
A continuación, seleccionamos 2 colores y su nombre. Los datos, como la última vez, se table_color
de la tabla table_color
. El nombre se usará en el encabezado de la tabla para explicar cuál de los colores corresponde a valores por debajo del promedio y cuál es más alto.
##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]]
Los argumentos de la función predeterminan a col1 y col2, estos son los primeros y segundos colores de los comandos. En la mayoría de los casos (más precisamente en 26), estos valores no necesitan ser cambiados, sin embargo, para cuatro equipos, se debe usar el siguiente color en su paleta de colores. En Dallas y Minnesota, el primer y segundo color son demasiado similares, mientras que en Milwaukee y Brooklyn no son visibles sobre un fondo blanco. Tanto eso como otro complican la lectura del programa, por lo tanto, vale la pena usar el argumento col2 = col3 para ellos.
Luego obtenemos la calificación máxima para el equipo. Necesitaremos este valor para organizar el texto con el valor de calificación en el gráfico. Quiero prestar atención a la última línea de código. Dio la casualidad de que las funciones trazaron gráficos perfectamente en 89 de los 90 casos, pero al construir una calificación de protección, Milwaukee dio un error. Resultó que el valor máximo de calificación en Milwaukee se alcanza dos veces y ggplot2
naturalmente comienza a jurar que la estética debería ser, en nuestro caso, 1 o 73. Por lo tanto, necesitamos un solo valor máximo de calificación.
##The maximum value of the rating max <- team %>% filter(RATING == max(RATING)) %>% select(RATING) max <- max[[1]]
Construyendo un gráfico estático en 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")
De lo nuevo aquí, el uso de la función if_else
para cambiar el color de la línea dependiendo de si la calificación promedio de la Liga es mayor o menor, así como la primera línea que cambia la configuración regional. Esto se hace para que las abreviaturas de los nombres de los meses a lo largo del eje X se escriban en inglés.

Animación de una media móvil de 10 partidos.
En la construcción de la animación, agregué varias lociones que no son posibles en la versión estática. En primer lugar, la fecha de cambio (similar a cómo cambió el año en el último artículo), así como el valor de la calificación en un momento determinado. También cambia de color dependiendo de si está por encima o por debajo del promedio.
##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"))
Resultado

En el gráfico, es bastante obvio que Dallas bajó en la segunda mitad de febrero-marzo. La explicación para esto es muy simple: fue en este punto de la temporada que los Mavericks intercambiaron a 4 de 5 jugadores en sus cinco iniciales, y el principal activo entrante, el letón Kristaps Porzingis, no jugó ni un minuto debido a la ruptura de los ligamentos cruzados.
Aquí no profundizaré en el componente deportivo, por lo que si alguien está interesado en ver las 89 listas restantes de la temporada 2018-19, entonces soy bienvenido a mi blog en sports.ru , donde planeo escribir un artículo con una descripción general de los más interesantes o en mi Telegram canal sobre la NBA, donde los voy a publicar a todos.
Repositorio de GitHub