Clustering: Clasificando a los jugadores actuales
Qué es el "Clustering", y necesidad cubierta
Como hemos apuntado en la introducción, el "clustering" o clusterización consiste en agrupar un conjunto de elementos, en este caso jugadores, en una serie de "clusters" (grupos), según su nivel de similitud, en base a los parámetros que se le indica al algoritmo que realiza la clasificación.
La necesidad nace a raiz de constatar que, en el año 2024, los perfiles de los jugadores actuales en el baloncesto moderno son distintos a la clasificación que se realizaba antaño, que distribuía a los jugadores en base, escolta, alero, ala-pívot y pívot. A partir de eso, se han hecho diferentes estudios de cómo se podrían agrupar a los jugadores para definir un rol más ajustado a la realidad actual, y a la vez más específico, que pueda ayudar al mismo tiempo a directores técnicos a la hora de fichar a alguien según las necesidades del equipo.
Algoritmo a utilizar y estadísticas que se han considerado
Para realizar esta agrupación, se ha utilizado el algoritmo conocido como "K-Means", que es un algoritmo de clusterización que agrupa los elementos en k grupos, basándose en una serie de características.
Características de los jugadores
Como características de los jugadores, se han considerado los siguientes parámetros, todos ellos obtenidos a través del "box score":
- Altura
- Ratio asistencias/pérdidas
- Rebotes por minuto
- Frecuencia de triples
- Asistencias por posesión
- Puntos por posesión
- Minutos por partido
- Porcentaje real de tiro
Elegir el nº idóneo de clusters
Existen varios métodos para encontrar el número idóneo de clusters en los que agrupar los jugadores:
- Método del Codo: el más bajo es el mejor
- Método de la Silueta: el más alto es el mejor
- Método de brecha: el más alto es el mejor
En este caso, hemos elegido el Método del Codo para encontrar el nº idóneo de clusters. En caso de querer profundizar más en los tres métodos, os recomendamos este artículo.
Realizando pruebas a partir de 5 clusters, cuando hemos llegado a 8 y 9 clusters no hemos notado una diferencia significativa, con lo cual hemos determinado que lo idóneo es agrupar a los jugadores en 8 clusters.
Desarrollo del script en Python
Una vez determinado el nº de clusters, hemos llevado a cabo el desarrollo del algoritmo que genera los clusters, utilizando el lenguaje de programación Python.
Compartimos con vosotros el código fuente que realiza la consulta en la base de datos, genera los clusters mediante el algoritmo K-Means, y posteriormente exporta los datos a un archivo CSV, donde se almacena el nombre del jugador, el equipo al que pertenece, el nº de cluster al que ha sido asignado, y el valor que tiene el jugador en cada uno de los 8 parámetros estadísticos que se han tenido en cuenta.
from sklearn.cluster import KMeans
from pymongo import MongoClient
# Constants and Variables
numClusters = 8
competition = 'acb'
players = []
client = # Connection data to MongoDB database
db = client.get_database('statscenter')
playersDB = db.players
playersData = playersDB.find({'competition': competition}).sort([('team', 1), ('lastName', 1), ('firstName', 1)])
# Fit data
for playerData in playersData:
player = {
'name': u'{}, {} ({})'.format(playerData['lastName'], playerData['firstName'], playerData['team'])
}
# 1) Ratio AST/TOV
if playerData['tov'] == 0:
continue
else:
player['ast-tov'] = round(float(playerData['ast']) / float(playerData['tov']), 3)
# 2) REB/MIN
if playerData['min'] == 0:
continue
else:
player['reb-min'] = round(float(playerData['reb']) / float(playerData['min']), 3)
# 3) 3P%
if playerData['fga'] == 0:
continue
else:
player['3p%'] = round(float(playerData['3pa']) / float(playerData['fga']), 3)
# 4) AST%
if (playerData['fga'] + 0.44 * playerData['fta'] + playerData['tov']) == 0:
continue
else:
player['ast%'] = round(playerData['ast'] * 100 / (playerData['fga'] + 0.44 * playerData['fta'] + playerData['tov']), 2)
# 5) PTS%
if (playerData['fga'] + 0.44 * playerData['fta'] + playerData['tov']) == 0:
continue
else:
player['pts%'] = round(playerData['pts'] * 100 / (playerData['fga'] + 0.44 * playerData['fta'] + playerData['tov']), 2)
# 6) MIN/G
if playerData['gp'] == 0:
continue
else:
player['min-g'] = round(float(playerData['min']) / float(playerData['gp']), 3)
# 7) TS%
player['ts%'] = playerData['ts%']
# 8) Height
if 'height' in playerData:
player['height'] = playerData['height']
players.append(player)
# Generate clusters
playersDF = pd.DataFrame(players)
kmeans = KMeans(n_clusters=numClusters)
y = kmeans.fit_predict(playersDF[['ast-tov', 'reb-min', '3p%', 'ast%', 'pts%', 'min-g', 'ts%', 'height']])
playersDF['cluster'] = y
# Generate CSV
header = ['name', 'cluster', 'height', 'ast-tov', 'reb-min', '3p%', 'ast%', 'pts%', 'min-g', 'ts%']
playersDF.to_csv('acbClusteredPlayers.csv', columns=header, encoding='utf-8', index=False, float_format='%.3f')
Clusters resultantes
Una vez ejecutado el generador de clusters, obtenemos un archivo CSV con los jugadores clasificados en clusters, y lo que hacemos es marcar con un fondo de color distinto cada valor de cada característica, para que luego visualmente nos sea más sencillo determinar qué características cumplen los jugadores de un mismo cluster.
En este caso, utilizamos una paleta de 5 colores: verde, lima, amarillo, naranja y rojo. Y por cada característica, dividimos el rango entre 5 y ponemos en el color que le corresponda el fondo de la cela de ese valor. Por ejemplo, en el caso de la altura, tenemos que la mínima es 178 y la máxima 221, con lo cual, la diferencia entre el máximo y el mínimo es de 44. Dividimos ese 44 en 5 intervalos iguales, y tenemos que de 178 a 186 es color rojo (jugadores más bajos), de 187 a 195 color naranja (jugadores algo bajos), de 196 a 203 color amarillo (jugadores de altura media), de 204 a 212 lima (jugadores un poco altos), y de 213 a 221 verde (jugadores más altos). Lo mismo hacemos con el resto de las 8 características que definen cada jugador.
Luego miramos en qué coinciden todos los jugadores del cluster 0, por ejemplo, y de esta forma sabemos qué características tienen.
De este análisis, concluimos que salen los siguientes clusters de jugadores:
1. Primer cluster
- Muy buena anotación
- Muy buen % de tiro
- Bajo % de asistencias
- Juegan minutos
- Jugadores tipo: Shermadini, Tavares, Sastre, Susinskas
2. Segundo cluster
- Altura baja
- Buena ratio de asistencias/pérdidas
- Bajo % de rebote
- Frecuencia media de triples
- Buen % de asistencias
- Media/buena anotación
- Medio/bueno % de tiro
- Jugadores tipo: Sergio Rodríguez, Ricky Rubio, Marcelinho Huertas, Facundo Campazzo
3. Tercer cluster
- Baja ratio de asistencias/pérdidas
- Bajo % de asistencias
- Media/buena anotación
- Medio/bueno % de tiro
- Buena altura
- Jugadores tipo: Víctor Claver, Yves Pons, Xavier López-Arostegui, Mario Hezonja
4. Cuarto cluster
- Medio/bueno % de tiro
- Baja altura
- Bajo % de rebote
- Bajo % de asistencias
- Media/buena anotación
- Jugadores tipo: Sergio Llull, Jaime Fernández, Andrés Feliz, Nico Laprovittola
5. Quinto cluster
- Bajo % de asistencias
- Buena anotación
- Buen % de tiro
- Baja ratio asistencias/pérdidas
- Jugadores tipo: Willy Hernangómez, Gaby Deck, Matt Costello, Dylan Osetkowski
6. Sexto cluster
- Baja anotación
- Bajo % de tiro
- Baja ratio asistencias/pérdidas
- Bajo % de rebote
- Pocos minutos
- Jugadores tipo: Pol Figueras, Chema González, Álex Moreno, Wilhelm Falk
7. Séptimo cluster
- Baja anotación
- Bajo % de tiro
- Baja % de asistencias
- Pocos minutos
- Jugadores tipo: Nacho Llovet, David Jelinek, Maxi Fjellerup, Jordan Sakho
8. Octavo cluster
- Medios/pocos minutos
- Baja altura
- Bajo % de rebote
- % de asistencias medio
- Baja anotación
- Bajo % de tiro
- Jugadores tipo: Guillem Jou, Janis Strelnieks, Carlos Alocén, Lucas Langarita
* Las características en negrita son aquellas en las que la coincidencia entre los jugadores del mismo cluster es mayor.