Introducción a eurolig

Breve introducción a la nueva versión de eurolig para analizar datos jugada a jugada y de localización de tiros para la Euroliga.

eurolig proporciona un kit de herramientas para obtener y analizar datos de la Euroliga con R. El paquete está diseñado principalmente para trabajar con dos tipos de datos de baloncesto: datos jugada a jugada y datos de localización de tiros. A pesar de que la primera temporada de la Euroliga fue en el 2000, los datos jugada a jugada y de localización de tiros solo están disponibles desde la temporada 2007/2008. Este artículo introduce la nueva version del paquete (0.4.0) y muestra cómo puede ser usado para realizar algunos análisis sencillos.

Cambios en la nueva versión

Si ya habías usado o conocías eurolig de mi entrada anterior, te darás cuenta de que hay bastantes cambios y nuevas funcionalidades. La versión anterior de eurolig (0.0.0.900) estaba aún muy cruda y en su fase más experimental. Para la nueva versión (0.4.0) he añadido mucha más funcionalidad y documentación básica.

Los cambios más importantes son:

  • camelCase para nombres de funciones (en lugar de snake_case).

  • He eliminado la función plot_heatmap() para producir heatmaps de los patrones de asistencias.

  • Los data frames de datos jugada a jugada contienen variables distintas.

Aunque hay bastantes cambios no he mantenido funciones viejas en esta nueva versión porque creo que no hay mucha gente usando el paquete. Código antiguo seguramente no funcionará con esta nueva versión. No obstante, adaptar el código antiguo para la nueva versión debería de ser bastante sencillo.

Paquetes necesarios

Necesitarás instalar el paquete eurolig desde su repositorio en GitHub:

# install.packages("devtools")
devtools::install_github("solmos/eurolig")

Además de eurolig, los siguientes paquetes son necesarios para reproducir esta entrada:

library(eurolig)
library(dplyr)
library(ggplot2)

Obtener los datos

Conjuntos de datos

Varios conjuntos de datos van incluídos en el paquete. Puedes ver una lista con:

data(package = "eurolig")

Los conjuntos de datos samplepbp y sampleshots contienen datos ejemplo de datos jugada a jugada y de localización de tiro, respectivamente.

Un conjunto de datos bastante útil es gameresults. Contiene todos los resultados de todos los partidos disputados en la Euroliga desde la temporada 2001/2002 hasta la temporada 2018/2019. Como podrás ver en la siguiente sección, este conjunto de datos puede ser útil para encontrar los partidos para los que quieres obtener datos.

Finalmente, si necesitas encontrar nombres o códigos de equipos, el conjunto de datos teaminfo puede ser útil.

Funciones extract

Funciones que obtienen datos a traves de la API de la web de la Euroliga empiezan con el verbo extract. Necesitarás estar conectado a internet para que estas funciones funcionen.

Las principales funciones para obtener datos son:

  • extractPbp() para datos jugada a jugada.

  • extractShots() para datos de localización de tiros.

Estas funciones solo pueden obtener datos de un sólo partido. Esto quiere decir que si necesitas conseguir datos para varios partidos deberás iterar la función para todos los partidos de interés. Nótese, sin embargo, que el archivo robot.txt de la Euroliga pide que se esperen 15 segundos (!) entre cada petición. Ten esto en cuenta al realizar un gran número de peticiones.

Cada partido se identifica con una combinación única de temporada y código de partido. Para indicar a las funciones extract de qué partido queremos obtener datos necesitamos introducir como argumentos el código y la temporada correspondiente.

Vamos a encontrar, por ejemplo, los partidos de la temporada 2018/2019 con el mayor número de puntos totales:

games <- gameresults %>% 
  filter(season == 2018) %>% 
  mutate(total_points = points_home + points_away) %>% 
  arrange(desc(total_points))

head(games)
## # A tibble: 6 x 14
##   season phase round_name team_home points_home team_away points_away
##    <int> <chr> <chr>      <chr>           <int> <chr>           <int>
## 1   2018 RS    Round 21   Herbalif…         104 AX Arman…         106
## 2   2018 RS    Round 16   AX Arman…         111 Buducnos…          94
## 3   2018 RS    Round 1    Real Mad…         109 Darussaf…          93
## 4   2018 RS    Round 4    Herbalif…          91 CSKA Mos…         106
## 5   2018 RS    Round 8    CSKA Mos…          99 Zalgiris…          97
## 6   2018 RS    Round 25   CSKA Mos…         101 AX Arman…          95
## # … with 7 more variables: game_code <int>, date <chr>, round_code <int>,
## #   game_url <chr>, team_code_home <chr>, team_code_away <chr>,
## #   total_points <int>

Podemos comprobar que el partido con mayor puntuación fue el disputado entre el Herbalife Gran Canaria y el AX Armani Exchange Olimpia Milan con 210 puntos totales. La temporada y código que identifican este partido son:

games$season[1]
## [1] 2018
games$game_code[1]
## [1] 168

Podemos obtener datos de localización de tiro y de jugada a jugada para este partido introduciendo estos valores como argumentos en extractShots() y extractPbp():

game_shots <- extractShots(game_code = 168, season = 2018)
game_pbp <- extractPbp(168, 2018)

Si quisiésemos encontrar partidos para la temporada actual (no incluida en gameresults) tenemos dos opciones: mirar el código del partido en la url del partido o usar extractResults().

Análisis de datos jugada a jugada

Los datos jugada a jugada contienen mucha información que no está en las estadísticas del boxscore tradicional. En el análisis que sigue voy a mostrar cómo utilizar eurolig para encontrar la siguiente información a partir de los datos jugada a jugada:

  • Mas-Menos (+/-) para uno o más jugadores.

  • Estadísticas On/Off para uno o más jugadores.

  • Patrones de asistencias dentro de un equipo.

Para estos análisis voy a utilizar el conjunto de datos samplepbp que contiene los datos jugada a jugada de los cuatro partidos de la Final Four de la Euroliga 2018/2019:

data("samplepbp")
glimpse(samplepbp)
## Observations: 2,121
## Variables: 29
## $ season         <int> 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2…
## $ game_code      <int> 257, 257, 257, 257, 257, 257, 257, 257, 257, 257,…
## $ play_number    <int> 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 1…
## $ team_code      <chr> NA, "ULK", "IST", "ULK", "IST", "IST", "ULK", "IS…
## $ player_name    <chr> NA, "DUVERIOGLU, AHMET", "DUNSTON, BRYANT", "MUHA…
## $ play_type      <chr> "BP", "TPOFF", "TPOFF", "2FGM", "2FGA", "RBLK", "…
## $ time_remaining <chr> "10:00", "09:59", "09:59", "09:42", "09:19", "09:…
## $ quarter        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ points_home    <dbl> 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5, 5…
## $ points_away    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2…
## $ play_info      <chr> "Begin Period", "", "", "Two Pointer (1/1 -  2 pt…
## $ seconds        <dbl> 0, 1, 1, 18, 41, 41, 44, 45, 47, 56, 56, 67, 68, …
## $ home_team      <chr> "Fenerbahce Beko Istanbul", "Fenerbahce Beko Ista…
## $ away_team      <chr> "Anadolu Efes Istanbul", "Anadolu Efes Istanbul",…
## $ home           <lgl> NA, TRUE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE,…
## $ team_name      <chr> NA, "Fenerbahce Beko Istanbul", "Anadolu Efes Ist…
## $ last_ft        <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …
## $ and1           <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, …
## $ home_player1   <chr> "GREEN, ERICK", "GREEN, ERICK", "GREEN, ERICK", "…
## $ home_player2   <chr> "MELLI, NICOLO", "MELLI, NICOLO", "MELLI, NICOLO"…
## $ home_player3   <chr> "GUDURIC, MARKO", "GUDURIC, MARKO", "GUDURIC, MAR…
## $ home_player4   <chr> "MUHAMMED, ALI", "MUHAMMED, ALI", "MUHAMMED, ALI"…
## $ home_player5   <chr> "DUVERIOGLU, AHMET", "DUVERIOGLU, AHMET", "DUVERI…
## $ away_player1   <chr> "LARKIN, SHANE", "LARKIN, SHANE", "LARKIN, SHANE"…
## $ away_player2   <chr> "MOERMAN, ADRIEN", "MOERMAN, ADRIEN", "MOERMAN, A…
## $ away_player3   <chr> "MICIC, VASILIJE", "MICIC, VASILIJE", "MICIC, VAS…
## $ away_player4   <chr> "DUNSTON, BRYANT", "DUNSTON, BRYANT", "DUNSTON, B…
## $ away_player5   <chr> "SIMON, KRUNOSLAV", "SIMON, KRUNOSLAV", "SIMON, K…
## $ lineups        <chr> "GREEN, ERICK - MELLI, NICOLO - GUDURIC, MARKO - …

Más-Menos

El más-menos (+/-) mide la diferencia entre los puntos anotados por el equipo y los puntos que permite al equipo rival en el tiempo que uno o más jugadores del equipo están en la pista. getPlusMinus() utiliza los datos jugada a jugada para uno o más partidos y calcula el +/- en cada partido de los jugadores especificados.

Aunque esta estadística está siendo muy usada últimamente, es importante recalcar que el +/- crudo es muy inestable y depende por completo del contexto. Por otro lado, es la pieza clave para construir otras estadísticas más avanzadas como RAPM o RPM.

Dicho esto, vamos a averiguar, por ejemplo, cuál fue el +/- de Sergio Rodriguez en los dos partidos que disputó en la Final Four 2018/2019:

chacho_pm <- getPlusMinus(pbp = samplepbp, players = "RODRIGUEZ, SERGIO")
# Select only a few columns so that data frame fits in the document
chacho_pm %>% 
  select(game_code, team_code_opp, poss, poss_opp, plus_minus)
## # A tibble: 2 x 5
##   game_code team_code_opp  poss poss_opp plus_minus
##       <int> <chr>         <dbl>    <dbl>      <dbl>
## 1       258 MAD              44       42          8
## 2       260 IST              24       25        -10

Nótese que también es posible calcular el más-menos para combinaciones de jugadores introduciendo un vector de caracteres con los nombres de los jugadores como argumento en players.

Estadísticas On/Off

Las estadísticas on/off de un jugador o una combinación de jugadores miden las estadísticas del equipo y del rival cuando ese jugador o jugadores están en pista y cuando están en el banquillo.

Puedes usar getOnOffStats() para averiguar las estadísticas on/off. Por ejemplo, podemos comparar cómo rindió el Real Madrid cuando Rudy y Ayón estaban en pista y cómo rindió cuando ambos estaban en el banquillo en los dos partidos de la Final Four 2018/2019.

getOnOffStats(pbp = samplepbp, players = c("FERNANDEZ, RUDY", "AYON, GUSTAVO"))
## # A tibble: 8 x 28
##   season game_code players on    type  team_code home   fg2a  fg2m fg2_pct
##    <int>     <int> <chr>   <lgl> <chr> <chr>     <lgl> <int> <int>   <dbl>
## 1   2018       258 FERNAN… TRUE  defe… CSK       TRUE      5     1   0.2  
## 2   2018       258 FERNAN… TRUE  offe… MAD       FALSE     3     2   0.667
## 3   2018       259 FERNAN… TRUE  offe… MAD       FALSE    20    14   0.7  
## 4   2018       259 FERNAN… TRUE  defe… ULK       TRUE     15     7   0.467
## 5   2018       258 FERNAN… FALSE defe… CSK       TRUE     33    16   0.485
## 6   2018       258 FERNAN… FALSE offe… MAD       FALSE    43    22   0.512
## 7   2018       259 FERNAN… FALSE offe… MAD       FALSE    15     9   0.6  
## 8   2018       259 FERNAN… FALSE defe… ULK       TRUE     17     9   0.529
## # … with 18 more variables: fg3a <int>, fg3m <int>, fg3_pct <dbl>,
## #   fga <int>, fgm <int>, fg_pct <dbl>, fta <int>, ftm <int>,
## #   ft_pct <dbl>, orb <int>, drb <int>, tov <int>, ast <int>, stl <int>,
## #   cpf <int>, blk <int>, pts <dbl>, poss <dbl>

Nótese que getOnOffStats() genera 4 filas por partido que corresponden a:

  • Estadísticas del equipo cuando los jugadores están en pista juntos.

  • Estadísitcas del equipo rival cuando los jugadores están en pista juntos.

  • Estadísticas del equipo cuando los jugadores están en el banquillo.

  • Estadísticas del equipo rival cuando los jugadores están en el banquillo.

Patrones de asistencias

Los datos jugada a jugada nos permiten averiguar quién asiste a quién en canastas asistidas. La función getAssists() genera un data frame con el pasador y el tirador (junto con más variables) de todas las canastas asistidas en los datos jugada a jugada.

Voy a usar getAssists() y un poco de manipulación de datos para hayar las asistencias más comunes en el CSK de Moscú en los dos partidos de la Final Four 2018/2019:

assists_csk <- getAssists(pbp = samplepbp, team = "CSK")
assists_csk %>% 
  count(passer, shooter) %>% 
  arrange(desc(n))
## # A tibble: 25 x 3
##    passer            shooter              n
##    <chr>             <chr>            <int>
##  1 CLYBURN, WILL     HIGGINS, CORY        2
##  2 DE COLO, NANDO    HACKETT, DANIEL      2
##  3 HACKETT, DANIEL   HUNTER, OTHELLO      2
##  4 RODRIGUEZ, SERGIO HINES, KYLE          2
##  5 CLYBURN, WILL     DE COLO, NANDO       1
##  6 DE COLO, NANDO    CLYBURN, WILL        1
##  7 DE COLO, NANDO    HUNTER, OTHELLO      1
##  8 DE COLO, NANDO    KURBANOV, NIKITA     1
##  9 DE COLO, NANDO    PETERS, ALEC         1
## 10 HACKETT, DANIEL   CLYBURN, WILL        1
## # … with 15 more rows

Análisis de datos de localización de tiros

Los datos de localización de tiros indican las coordenadas x-y de todos los tiros hechos durante un partido. Estos datos pueden ser útiles para, por ejemplo, identificar tendencias en ataque de un jugador o equipo, mostrar qué lugares de la pista un jugador es más efectivo o analizar qué tipo de tiros permite un equipo al rival.

Para el siguiente análisis voy a usar el conjunto de datos sampleshots que contiene los datos de localización de tiros de los cuatro partidos de la Final Four 2018/2019.

glimpse(sampleshots)
## Observations: 490
## Variables: 25
## $ season        <dbl> 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018, 20…
## $ game_code     <int> 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, …
## $ num_anot      <int> 5, 6, 10, 14, 16, 18, 21, 25, 27, 30, 31, 33, 34, …
## $ team_code     <chr> "ULK", "IST", "IST", "ULK", "IST", "ULK", "IST", "…
## $ player_id     <chr> "P001324", "P003048", "P003048", "P005159", "P0018…
## $ player_name   <chr> "MUHAMMED, ALI", "DUNSTON, BRYANT", "DUNSTON, BRYA…
## $ action_id     <chr> "2FGM", "2FGA", "2FGM", "3FGM", "2FGA", "2FGA", "2…
## $ action        <chr> "Two Pointer", "Missed Two Pointer", "Two Pointer"…
## $ points        <int> 2, 0, 2, 3, 0, 0, 0, 3, 0, 3, 0, 3, 3, 2, 2, 2, 0,…
## $ coord_x       <dbl> 1.1428571, -0.3163265, -0.1836735, -5.9489796, 1.9…
## $ coord_y       <dbl> 2.279082, 2.717857, 2.207653, 6.177041, 2.462755, …
## $ zone          <chr> "C", "B", "B", "H", "C", "C", "B", "H", "G", "I", …
## $ fastbreak     <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
## $ second_chance <lgl> FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FA…
## $ off_turnover  <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, T…
## $ minute        <int> 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6,…
## $ console       <chr> "09:42", "09:19", "09:13", "08:53", "08:29", "08:0…
## $ points_a      <int> 2, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 10, 12, …
## $ points_b      <int> 0, 0, 2, 2, 2, 2, 2, 5, 5, 8, 8, 11, 11, 13, 15, 1…
## $ utc           <chr> "20190517160232", "20190517160256", "2019051716030…
## $ make          <lgl> TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE…
## $ quarter       <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ seconds       <dbl> 18, 41, 47, 67, 91, 117, 130, 146, 176, 185, 203, …
## $ team_code_a   <chr> "ULK", "ULK", "ULK", "ULK", "ULK", "ULK", "ULK", "…
## $ team_code_b   <chr> "IST", "IST", "IST", "IST", "IST", "IST", "IST", "…

Puedes comprobar que además de las coordenadas x-y, coord_x y coord_y, hay variables que dan información contextual como el jugador que realizó el tiro, el tiempo que quedaba en el marcador cuando el tiro ocurrió o si el tiro se hizo tras un rebote ofensivo. Estas variables pueden ayudar a filtrar tiros de características específicas.

La función plotShotChart() permite mostrar gráficamente el lugar de la pista en el que se realizaron los tiros, coloreados según se metieron (verde) o se fallaron (rojo). El objeto resultante es un objeto ggplot que podemos modificar con funciones de ggplot2:

plotShotchart(sampleshots) +
  labs(title = "Euroleague 2018/2019 Final Four") +
  theme(legend.position = "bottom")

Avatar
Sergio Olmos Pardo
Estadístico | Científico de datos

Relacionado

comments powered by Disqus