Doppelgängers: Encontrando jugadores similares

En StatsBomb, nos comprometemos a proporcionar a nuestros clientes las herramientas que necesitan para triunfar. Nuestra plataforma de análisis StatsBomb IQ está diseñada por y para profesionales del fútbol y cuenta con una serie de características esenciales para los que trabajan en el scouting y/o la dirección deportiva.

Además, estamos trabajando en una actualización de la sección de scouting de IQ que tendrá en cuenta los comentarios que hemos recibido de nuestros usuarios finales.

Una de las herramientas ya incluidas en IQ es una que representa un buen punto de partida para cualquier trabajo de scouting: Similar Player Search.

El concepto es sencillo: se elige un jugador como objeto de la búsqueda y el algoritmo produce una lista de jugadores con perfiles estadísticos similares, clasificados en una escala de 0 a 100, siendo 100 una coincidencia exacta. El objeto puede ser un jugador de renombre, un potencial fichaje o incluso uno de los jugadores del propio equipo para el que se necesita una reserva o un futuro reemplazo.

Existen varios filtros que se pueden aplicar para genera una lista más adaptada a las necesidades del usuario final:

  • Género
  • Posición
  • Minutos jugados
  • Edad
  • Competición: una selección abierta o por región o nivel de competición.
  • Temporada

Asimismo, existe la posibilidad de ajustar la importancia asignada a cada dato. Por ejemplo, si un equipo está buscando un jugador capaz de duplicar la producción creativa de uno de los mejores centrocampistas de la división pero no le importa que el jugador duplique la producción defensiva de dicho centrocampista, se puede dar más importancia al primer dato en el análisis de similitud.

Los resultados pueden funcionar como un comienzo ideal para un proceso más amplio de scouting. Los datos de StatsBomb cubren más de 80 competiciones y cerca de 50,000 jugadores del mundo, así que existen muchas oportunidades de encontrar algunos nombres menos conocidos. Esto es particularmente valioso para los equipos que manejan presupuestos más ajustados.

Es importante tener en cuenta que el hecho de aproximar la producción estadística de un jugador no necesariamente indica que existen similitudes en habilidad, potencial, físico o incluso estilo de juego. Decir que un determinado jugador coincide con la producción estadística de Zlatan Ibrahimovic, por ejemplo, sólo significa que el jugador realiza muchos tiros, gana muchos duelos aéreos y mantiene bien el balón. No dice nada de sus otras características técnicas o físicas. Lo más conveniente es pensar en el objeto de la búsqueda como un avatar para un determinado perfil estadístico.

Por ejemplo, dame un jugador con producción similar a la de Sadio Mane.

o de Alejandro Gómez...

o de Toni Kroos.

Las posibilidades son infinitas.


¿Quiere saber más sobre las posibilidades de StatsBomb IQ? Un miembro de nuestro equipo de expertos le puede demostrar todas las ventajas de StatsBomb IQ y de los datos de StatsBomb. Contacte con nosotros >>

Cómo hacer más con StatsBomb Data en R: Mapas de tiros, xG asistido y más

Hace un par de meses, añadimos los datos del Barcelona de la temporada 2019-20 a nuestro repositorio de la carrera completa de Lionel Messi y la gente ya ha empezado a hacer cosas interesantes con ellos. Por ejemplo, Eoin O’Brien ha creado una página interactiva en la que puedes ver los redes de pases del Barça en cada partido de la temporada.

Ahora, vamos a enseñar algunas cosas que podéis hacer con estos datos en R, un programa y lenguaje de programación que solemos utilizar en nuestro trabajo de análisis. Ya hemos escrito una introducción al uso de StatsBomb Data en R y recomendamos que la leéis antes de intentar hacer las cosas incluidas en este artículo, que tiene como base un artículo en inglés por nuestro analista Euan Dewar.

Podéis hacer estas cosas con tanto los datos de Messi como los de las otras competiciones que ofrecemos de manera gratuita, incluyendo los últimos mundiales tanto masculino como femenino, la temporada de liga del mítico Arsenal de la 2003-04 y las ligas femeninas de Inglaterra y los Estados Unidos. Podéis encontrar una lista completa de las competiciones aquí.

Importar los datos

Primero, tenemos que importar los datos del Barcelona de la 2019-20:

library(tidyverse)
library(StatsBombR)

Comp <- FreeCompetitions() %>%
filter(competition_id==11 & season_name=="2019/2020")

Matches <- FreeMatches(Comp)

StatsBombData <- StatsBombFreeEvents(MatchesDF = Matches, Parallel = T)

StatsBombData = allclean(StatsBombData)

events = StatsBombData

Para crear visualizaciones es útil tener los nombres cortos de los jugadores en vez de sus nombres completos con segundo apellido, etc... Este código crea otra columna con este detalle ('player.nickname'):

lineups <- StatsBombFreeLineups(MatchesDF = Matches, Parallel = T)
lineups <- cleanlineups(lineups)
lineups <- lineups %>% mutate(player_nickname = ifelse(is.na(player_nickname), player_name, player_nickname))
lineups <- lineups %>% select(player.id = player_id, player.nickname = player_nickname) %>%
group_by(player.id) %>% slice(1) %>% ungroup()

events <- events %>% left_join(lineups)

Mapas de tiros

Bueno, ahora podemos crear un mapa de tiros de un jugador.

shots = events %>%
filter(type.name=="Shot" & (shot.type.name!="Penalty" | is.na(shot.type.name)) & player.nickname=="Luis Suárez") %>% #1 mutate(shot.body_part_ESP.name = recode (shot.body_part.name, "Right Foot" = "Pie derecho", "Left Foot" = "Pie izquierdo", "Head" = "Cabeza")) #2

shotmapxgcolors <- c("#192780", "#2a5d9f", "#40a7d0", "#87cdcf", "#e7f8e6", "#f4ef95", "#FDE960", "#FCDC5F", "#F5B94D", "#F0983E", "#ED8A37", "#E66424", "#D54F1B", "#DC2608", "#BF0000", "#7F0000", "#5F0000") #3

ggplot() +
annotate("rect",xmin = 0, xmax = 120, ymin = 0, ymax = 80, fill = NA, colour = "black", size = 0.6) +
annotate("rect",xmin = 0, xmax = 60, ymin = 0, ymax = 80, fill = NA, colour = "black", size = 0.6) +
annotate("rect",xmin = 18, xmax = 0, ymin = 18, ymax = 62, fill = NA, colour = "black", size = 0.6) +
annotate("rect",xmin = 102, xmax = 120, ymin = 18, ymax = 62, fill = NA, colour = "black", size = 0.6) +
annotate("rect",xmin = 0, xmax = 6, ymin = 30, ymax = 50, fill = NA, colour = "black", size = 0.6) +
annotate("rect",xmin = 120, xmax = 114, ymin = 30, ymax = 50, fill = NA, colour = "black", size = 0.6) +
annotate("rect",xmin = 120, xmax = 120.5, ymin =36, ymax = 44, fill = NA, colour = "black", size = 0.6) +
annotate("rect",xmin = 0, xmax = -0.5, ymin =36, ymax = 44, fill = NA, colour = "black", size = 0.6) +
annotate("segment", x = 60, xend = 60, y = -0.5, yend = 80.5, colour = "black", size = 0.6)+
annotate("segment", x = 0, xend = 0, y = 0, yend = 80, colour = "black", size = 0.6)+
annotate("segment", x = 120, xend = 120, y = 0, yend = 80, colour = "black", size = 0.6)+
theme(rect = element_blank(),
line = element_blank()) +
annotate("point", x = 108 , y = 40, colour = "black", size = 1.05) +
annotate("path", colour = "black", size = 0.6, x=60+10*cos(seq(0,2*pi,length.out=2000)), y=40+10*sin(seq(0,2*pi,length.out=2000)))+
annotate("point", x = 60 , y = 40, colour = "black", size = 1.05) +
annotate("path", x=12+10*cos(seq(-0.3*pi,0.3*pi,length.out=30)), size = 0.6, y=40+10*sin(seq(-0.3*pi,0.3*pi,length.out=30)), col="black") +
annotate("path", x=107.84-10*cos(seq(-0.3*pi,0.3*pi,length.out=30)), size = 0.6, y=40-10*sin(seq(-0.3*pi,0.3*pi,length.out=30)), col="black") +
geom_point(data = shots, aes(x = location.x, y = location.y, fill = shot.statsbomb_xg, shape = shot.body_part_ESP.name), size = 6, alpha = 0.8) + #4
theme(axis.text.x=element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
plot.caption=element_text(size=13,family="Source Sans Pro", hjust=0.5, vjust=0.5),
plot.subtitle = element_text(size = 18, family="Source Sans Pro", hjust = 0.5),
axis.text.y=element_blank(), legend.position = "top",
legend.title=element_text(size=18,family="Source Sans Pro"),
legend.text=element_text(size=16,family="Source Sans Pro"),
legend.margin = margin(c(15, 30, -60, 10)), legend.key.size = unit(1.5, "cm"),
legend.key.width = unit(0.95, "cm"),
plot.title = element_text(margin = margin(r = 10, b = 10), face="bold",size = 26, family="Source Sans Pro", colour = "black", hjust = 0.5),
legend.direction = "horizontal",

axis.ticks=element_blank(),
aspect.ratio = c(65/100),
plot.background = element_rect(fill = "white"),
strip.text.x = element_text(size=13,family="Source Sans Pro")) +
labs(title = "Luis Suárez, Mapa de tiros", subtitle = "La Liga, 2019-20") + #5
scale_fill_gradientn(colours = shotmapxgcolors, limit = c(0,0.8), oob=scales::squish, name = "Valor de xG") + #6
scale_shape_manual(values = c("Cabeza" = 21, "Pie derecho" = 23, "Pie izquierdo" = 24), name ="") + #7
guides(fill = guide_colourbar(title.position = "top"),
shape = guide_legend(override.aes = list(size = 5, fill = "black"))) + #8
coord_flip(xlim = c(85, 125)) #9

1. Un filtro básico para eliminar los penaltis y elegir el jugador que quieras.
2. Para asignar los nombres castellanos a las partes del cuerpo.
3. Los colores para los valores de xG.
4. Empezamos a trazar los tiros con geom_point. Elegimos el valor de xG como la fill y la parte del cuerpo como la shape (forma) de los puntos. Se pueden cambiarlos por otros valores, como el tipo de asistencia, el resultado del tiro (gol, a puerta, bloqueado...), etc...
5. Para poner el título y subtítulo a la visualización.
6. Los parámetros para la fill de los tiros. En el parámetro colours, hacemos referencia a los colores que elegimos antes.
7. Para elegir las formas para cada parte del cuerpo. Los números son los números asignados a las formas estándares de ggplot. Una lista aquí. Las formas de 21 en adelante son los que tienen colores interiores (controlados por fill).
8. Con guides() podemos ajustar la forma, el color y otras cosas de la leyenda. En este ejemplo, estamos cambiando la posición del título de la fill para situarlo por encima de la leyenda. Asimismo, estamos cambiando el tamaño y color de las formas.
9. coord_flip() cambia los ejes para mostrar el campo de juego de forma vertical. Con xlim podemos poner un límite al eje x para mostrar sólo una parte del campo.

El resultado será esta visualización:

xG + xG Asistido

No existe una columna para los valores de xG asistido en nuestros datos pero es algo que podemos generar si conectamos el valor de xG de un tiro a la pase que creó la ocasión usando la función join.

Aquí está el código:

xGA = events %>%
filter(type.name=="Shot") %>% #1
select(shot.key_pass_id, xGA = shot.statsbomb_xg) #2

shot_assists = left_join(events, xGA, by = c("id" = "shot.key_pass_id")) %>% #3
select(team.name, player.nickname, player.id, type.name, pass.shot_assist, pass.goal_assist, xGA ) %>%
filter(pass.shot_assist==TRUE | pass.goal_assist==TRUE) #4

1. Filtrando a los tiros, los únicos eventos que tienen valores de xG.
2. Con Select() podemos elegir las columnas que queremos incluir en el Data Frame. Elegimos la columna 'shot.key_pass_id', una variable de los tiros que da la ID de la pase que creó el tiro. Asimismo, renombramos la columna 'shot.statsbomb_xg' a 'xGA' para que ya tenga el nombre correcto cuando la juntamos con las pases.
3. left_join() es una función para combinar las columnas de dos Data Frames diferentes. En este ejemplo, estamos juntado nuestro Data Frame inicial (‘events’) y el que hemos creado (‘xGA’). La clave es la parte by = c(“id” = “shot.key+pass_id) que junta los dos Data Frames cuando la columna de ID en ‘events’ coincide con la columna ‘shot.key_pass_id’ en ‘xGA’. Ahora, la columna ‘xGA’ muestra el valor de xG de cada pase clave y asistencia.
4. Filtrando los datos a las pases claves y asistencias. El resultado debería ser algo así:

Todo bien hasta ahora. ¿Pero qué tenemos que hacer para crear un gráfico de estos datos? Por ejemplo, un ranking de jugadores que incluye tanto el xG como el xGA.

player_xGA = shot_assists %>%
group_by(player.nickname, player.id, team.name) %>%
summarise(xGA = sum(xGA, na.rm = TRUE)) #1

player_xG = events %>%
filter(type.name=="Shot") %>%
filter(shot.type.name!="Penalty" | is.na(shot.type.name)) %>%
group_by(player.nickname, player.id, team.name) %>%
summarise(xG = sum(shot.statsbomb_xg, na.rm = TRUE)) %>%
left_join(player_xGA) %>%
mutate(xG_xGA = sum(xG+xGA, na.rm =TRUE) ) #2

player_minutes = get.minutesplayed(events)

player_minutes = player_minutes %>%
group_by(player.id) %>%
summarise(minutes = sum(MinutesPlayed)) #3

player_xG_xGA = left_join(player_xG, player_minutes) %>%
mutate(nineties = minutes/90,
xG_90 = round(xG/nineties, 2),
xGA_90 = round(xGA/nineties,2),
xG_xGA90 = round(xG_xGA/nineties,2) ) #4

chart = player_xG_xGA %>%
filter(minutes>=900)

chart<-chart %>%
select(1, 9:10)%>%
pivot_longer(-player.nickname, names_to = "variable", values_to = "value") %>%
filter(variable=="xG_90" | variable=="xGA_90") #6

1. Agrupando los datos por jugador y calculando sus totales de xGA para la temporada.
2. Eliminando los penaltis, calculando los totales de xG para cada jugador y sumando el xG y xGA para crear una nueva columna con la suma de los dos.
3. Una función para importar los minutos disputados de cada jugador.
4. Juntando el xG y xGA a los minutos y calculando las cifras por cada 90 minutos en el campo.
5. Aquí creamos un Data Frame para el gráfico, con un filtro para incluir sólo a jugadores con 900 o más minutos disputados.
6. La función pivot_longer() aplana los datos para crear filas individuales para cada variable y valor relacionada a un jugador. Es más fácil visualizarlo:

Tenemos los datos preparados para crear el gráfico.

ggplot(chart, aes(x =reorder(player.nickname, value), y = value, fill=fct_rev(variable))) + #1
geom_bar(stat="identity", colour="white")+
labs(title = "xG + xG Asistido", subtitle = "Barcelona, La Liga, 2019-20", x="", y="Por cada 90 minutos", caption ="Mínimo de 900 minutos jugados")+
theme(axis.text.y = element_text(size=14, color="#333333", family="Source Sans Pro"),
axis.title = element_text(size=14, color="#333333", family="Source Sans Pro"),
axis.text.x = element_text(size=14, color="#333333", family="Source Sans Pro"),
axis.ticks = element_blank(),
panel.background = element_rect(fill = "white", colour = "white"),
plot.background = element_rect(fill = "white", colour ="white"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),

plot.title=element_text(size=24, color="#333333", family="Source Sans Pro" , face="bold"),
plot.subtitle=element_text(size=18, color="#333333", family="Source Sans Pro", face="bold"),
plot.caption=element_text(color="#333333", family="Source Sans Pro", size =10),

text=element_text(family="Source Sans Pro"),
legend.title=element_blank(),
legend.text = element_text(size=14, color="#333333", family="Source Sans Pro"),
legend.position = "bottom") + #2
scale_fill_manual(values=c("#3371AC", "#DC2228"), labels = c( "xGA","xG")) + #3
scale_y_continuous(expand = c(0, 0), limits= c(0,max(chart$value) + 0.5)) + #4
coord_flip()+ #5
guides(fill = guide_legend(reverse = TRUE)) #6

1. Usamos reorder() para poner los jugadores en orden de su suma de xG y xGA. Hemos puesto el ‘variable’ como la fill de la barra. Así podemos poner los dos datos juntos en la misma barra con un color distinto para cada uno de los dos.
2. La función labs() controla el título, el subtítulo y las etiquetas de los ejes. La función theme() controla la tipografía, el color del fondo y cosas así.
3. Aquí estamos eligiendo los colores para cada dato (xG = rojo; xGA = azul) y creando la etiqueta.
4. Poniendo un límite al eje y. En este caso hemos usado el valor máximo + 0.3.
5. Cambiando los ejes para tener un diagrama de barras horizontal en vez de vertical.
6. Tenemos que invertir la leyenda para que muestre los datos en el mismo orden que el diagrama. El resultado final será esta visualización:

Existen muchas variaciones que podéis hacer con un diagrama de barras así: presiones, dividido entre campo propio y campo contrario; incursiones al área, dividido entre pases y conducciones; pases, dividido entre los dos pies. Nuestra especificación de datos incluye los nombres de todos los variables que podéis utilizar.


Ahora es el turno de vosotros. Podéis compartir sus resultados con nosotros: @statsbombes en Twitter. Cabe mencionar que además del nuestro paquete de R, tenemos uno de Python, StatsBombPy. Podéis encontrar todos los detalles en nuestro Github.