Análisis Exploratorio de los jugadores de FIFA 20 (Parte I)

En esta serie de posts, voy a llevar a cabo un análisis exploratorio o EDA (Exploratory Data Analysis en inglés) sobre los jugadores y equipos del famoso videojuego FIFA 20. Intentaré hacer que cada post sea breve y actualizar la serie cada poco tiempo! *Edit: aquí tenéis la parte 2

La idea es ver la cantidad de jugadores que hay, encontrar variables relacionadas, desglose por países, mejores jugadores y más cosas que se vayan ocurriendo o me sugiráis!

*Spoiler*
Voy a meter visualizaciones potentes, con el código incluido, para que podáis replicarlo vosotros en la tarea que estéis haciendo.

Portada Fifa 20

Importamos los datos y vemos por encima qué tenemos

Hay miles de repositorios donde encontrar los datos de los jugadores del videojuego, y también miles de sitios con datos estadisticos reales de los jugadores. Yo he optado por utilizar los datos de este kernel de Kaggle que he encontrado, tiene datos de los jugadores desde el FIFA15 hasta el de este añoo, y también datos de los equipos.

# Primero vamos a imoprtar las librerias necesarias
import pandas as pd
import numpy as np
import operator 
import seaborn as sns
from math import pi
import matplotlib.pyplot as plt
''' Cargamos los datos de kaggle. 
El enlace de estos dataset es https://www.kaggle.com/stefanoleone992/fifa-20-complete-player-dataset
Yo voy a usar los datos de Fifa 20
'''
jugadores = pd.read_csv('/content/players_20.csv')
print(jugadores.shape)
jugadores.head()

Tenemos 18.278 jugadores únicos y cada uno tiene 104 variables. Si vemos un ejemplo de la cabecera del DataFrame vemos esto:

Muestra del dataset sobre el que estamos haciendo el an?lisis exploratorio

Correlaciones de todas las variables numéricas

Lo primero que vamos a hacer es comprobar la correlación entre todas las variables numéricas del dataset para ver si hay variables redundantes. Este análisis también nos servirá para investigar si hay relaciones que a primera vista no parecen naturales.

df_num = jugadores.select_dtypes(include=np.number)
print(df_num.shape)

De primeras vemos que tenemos 61 variables numéricas, que son una barbaridad. Así que lo que vamos a hacer es mostrar las 15 variables más correladas entre sí (en variable absoluto)

# Sacamos el valor absoluto
corr_mini = df_num.corr().abs()
# Llenamos la diagonal de NAN para que no cuenten a la hora de sacar los máximos
np.fill_diagonal(corr_mini.values, np.nan)
# Sacamos parejas y las ordenamos
pairs = corr_mini.unstack()
sorted_pairs = pairs.sort_values(ascending=False)
# Transformamos los indices en una lista ordenada.
correladas = []
for pair in sorted_pairs.index:
  correladas.append(pair[0])
  correladas.append(pair[1])
# Eliminamos los duplicados y nos quedamos con las 15 primeras
print(list(dict.fromkeys(correladas))[:15])

[‘goalkeeping_reflexes’, ‘gk_reflexes’, ‘goalkeeping_kicking’, ‘gk_kicking’, ‘gk_diving’, ‘goalkeeping_diving’, ‘gk_handling’, ‘goalkeeping_handling’, ‘goalkeeping_positioning’, ‘gk_positioning’, ‘power_shot_power’, ‘release_clause_eur’, ‘value_eur’, ‘skill_dribbling’, ‘dribbling’]

Como vemos, las de portero están repetidas… En total son 5 duplicadas, así que vamo a crear la lista de las 20 más correladas y dropeamos las que contengan el substring ‘goalkeeping_‘.

 # Eliminamos las que tienen 'goalkeeping_'
correladas = [ x for x in correladas if "goalkeeping_" not in x ]
# Eliminamos las duplicadas
correladas = list(dict.fromkeys(correladas))[:15]
print(correladas)

[‘gk_reflexes’, ‘gk_kicking’, ‘gk_diving’, ‘gk_handling’, ‘gk_positioning’, ‘power_shot_power’, ‘release_clause_eur’, ‘value_eur’, ‘skill_dribbling’, ‘dribbling’, ‘defending_sliding_tackle’, ‘defending_standing_tackle’, ‘defending’, ‘movement_sprint_speed’, ‘pace’]

Bien, ahora vamos a sacar la matriz de correlación y la representamos en un heatmap:

corr = df_num[correladas].corr()
mask = np.zeros_like(corr)
mask[np.triu_indices_from(mask)] = True
with sns.axes_style("white"):
  sns.heatmap(corr, mask=mask, square=True, vmin=-1, vmax=1)
Matriz de autocorrelaci?n. Clave en el an?lisis exploratorio

Sorprendentemente, las variables de portero están correlacionadas directamente con las variables de pace, defending, dibbling!!

Visualización de correlación con un grafo de redes

Vamos a hacer una visualización más potente para averiguar las relaciones de correlación entre las diferentes variables: una representación en forma de red, en la que las variables conectadas quiere decir que tienen una relación, cuanto más cerca entre ellas será una relación más fuerte:

links = corr.stack().reset_index()
links.columns = ['var1', 'var2','valor']
 
# Nos quedamos con las variables relacionadas con m?s de 0.8 y quitamos 
# la autocorrelación:  (cor(A,A)=1)
links_filtered=links.loc[(links['valor'] > 0.8) &
                         (links['var1'] != links['var2'])]
 
# Build your graph
G=nx.from_pandas_edgelist(links_filtered, 'var1', 'var2')
nx.from_pandas_edgelist
# Plot the network:
plt.figure(3,figsize=(25,25)) 
nx.draw(G, with_labels=True, node_color='orange', node_size=400,
        edge_color='black', linewidths=1, font_size=10)

En la imagen anterior no se consigue ver demasiado bien, así que vamos a hacer una visualización más potente con la ayuda de la librería bokeh :

Bokeh Plot

El código para realizarlo es un poco complicado, así que tampoco os paréis mucho a estudiarlo, pero aquí os va:

from bokeh.plotting import figure 
from bokeh.io import output_file, show
from bokeh.models import (BoxZoomTool, Circle, HoverTool,
                          MultiLine, Plot, Range1d, ResetTool,)
from bokeh.palettes import Spectral4
from bokeh.models.graphs import from_networkx
# Asignamos el nombre del nodo a cada nodo
for node in G.nodes:
  G.nodes[node]['var'] = node
# Para hacerlo con Bokeh
plot = Plot(plot_width=600, plot_height=600,
            x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))
plot.title.text = "Graph Interaction Demonstration"
node_hover_tool = HoverTool(tooltips=[('Variable', '@var')])
plot.add_tools(node_hover_tool, BoxZoomTool(), ResetTool())
graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0))
graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0])
graph_renderer.edge_renderer.glyph = MultiLine(line_alpha=0.8, line_width=1)
plot.renderers.append(graph_renderer)
output_file("red_interactiva.html")
show(plot)

Es interesante ver cómo todas las variables de valor del jugador están relacionadas directamente con el overall del jugador, y ésta a su vez relacionada altamente con los atributos de portero.
Esto bien podría ser porque los porteros sólo tienen estos atributos desarrollados, mientras que las otras características son bastante malas.

Vemos también como hay un clúster de variables relacionadas con el moviemiento, que aparece muy cerca de las variables de físico y fuerza.
Otro clúster que aparece, son las variables de defensa, que con toda la razón están relacionadas entre sí.
Se puede ver también que las variables de disparo y regate se relacionan con el overall a través de las variables de portero, siendo la conexión entre estos dos clústers la de ‘gk_kicking’ con la de ‘power_shot_power’.

Analizamos las nacionalidades más representadas en el videojuego

Vamos a analizar ahora cuáles son las las 8 nacionalidades con más jugadores. Para ello, buscamos en la columna de ‘nacionality’ del dataset y contamos valores diferentes.

También, vamos a hacer una visualización en un barplot con Bokeh, para poder ver visualmente cómo están repartidos los jugadores.

Nacionalidades de los jugadores

# Las 8 nacionalidades mas comunes de los jugadores que aparecen en el fifa
nacionalidades = jugadores.nationality.value_counts()
nacionalidades.sort_values(ascending=False, inplace=True)
# Nos quedamos con las 8 primeras solo
y = list(nacionalidades.iloc[0:8].values)
x = list(nacionalidades.iloc[0:8].index)
nacionalidades = x
counts = y
source = ColumnDataSource(data=dict(nacionalidades=nacionalidades,
                                    counts=counts))
p = figure(x_range=nacionalidades, toolbar_location=None,
           title="Nacionalidades", tools="hover", 
           tooltips = 'Pais: @nacionalidades')
p.vbar(x='nacionalidades', top='counts', width=0.9, source=source,
       legend_field="nacionalidades", line_color='white',
       fill_color=factor_cmap('nacionalidades', palette=Spectral8,
                              factors=nacionalidades))
p.xgrid.grid_line_color = None
p.y_range.start = 0
p.y_range.end = 2500
output_file("Barplot de Nacionalidades.html")
show(p)
Bokeh Plot

Aquí tenemos las 8 nacionalidades más representadas del juego. Como era de esperar, el país más representado es Inglaterra, ya que es el único país del juego que tiene 4 ligas diferentes.

Posiciones de los jugadores

Vamos a ver la distribución de jugadores por posición que tiene cada país. Para ello, vamos a categorizar las posiciones de los jugadores en 4: portero, defensa, centrocampista y delantero:

# Inicializacion de las variables necesarias
posicion1 = []
posicion2 = []
posicion3 = []
posicion_frecuente = []
# Diccionario de la categorizacion de cada posicion
categorizacion_pos = {
    'delantero': ['CF', 'RW', 'RWB', 'ST'],
    'centrocampista': ['CAM', 'CDM','CM', 'LM', 'LW', 'RM'],
    'defensa': ['CB', 'LB', 'LWB', 'RB','RWB'],
    'portero': ['GK']
    }
for index, row in jugadores.iterrows():
# Diccionario para llevar la cuenta de cuantas veces aparece una categoria    
    pos_preferida = {
        'delantero': 0,
        'centrocampista': 0,
        'defensa': 0,
        'portero': 0}
# la columna de player_positions puede tener mas de una posicion, asi que lo
# partimos por ',' y creamos una lista de posiciones.    
    posiciones = row.player_positions.split(',')
    pos1 = posiciones[0]
    # hacemos un try except, por si acaso algun jugador solo tiene una posicion, 
    # como suele suceder con los porteros.
    try:
        pos2 = posiciones[1]
    except IndexError: 
        pos2 = ''
    try:
        pos3 = posiciones[2]
    except IndexError: 
        pos3 = ''
    
    mejores3 = [pos1, pos2, pos3]
    for key in categorizacion_pos.keys():
        for pos in mejores3:
            if pos in categorizacion_pos[key]:
                if key == 'portero': # Si es portero, asignamos esa
                    pos_preferida[key] = 99
                else:
                    pos_preferida[key] = 1 + pos_preferida[key]
                

    posicion_frecuente.append(max(pos_preferida.items(),
                                  key=operator.itemgetter(1))[0])
  
jugadores['posicion_frecuente'] = posicion_frecuente

Bien, ahora tenemos asignada la posición frecuente de cada jugador, pasamos a crear un barplot con las posiciones apiladas para cada país.

output_file("Apilado.html")
posiciones = list(jugadores['posicion_frecuente'].unique())
data = {'nacionalidades' : nacionalidades}
for pos in posiciones:
  contador = list()
  porcentajes = list()
  for pais in nacionalidades:
    cuenta = len(jugadores.loc[(jugadores['posicion_frecuente'] == pos) &
                               (jugadores['nationality'] == pais), :])
    porcentaje = np.round(100*cuenta/len(jugadores.loc[jugadores['nationality'] == pais]), 0)
    contador.append(cuenta)
    porcentajes.append(porcentaje)
  data[pos] = contador
  data[pos + '_porcentaje'] = porcentajes

p = figure(x_range=nacionalidades, plot_height=250, 
           title="Cantidad de jugadores por posicion por pais",
           toolbar_location=None)
renderers = p.vbar_stack(posiciones, x='nacionalidades', width=0.9,
                         color=Spectral4, source=data, legend_label=posiciones)
#Creamos el 'diccionario' para pasarlo a la herramienta de hover de Bokeh 
#para que muestre la informaci?n que nosotros queremos.
for r in renderers:
    posicion = r.name
    hover = HoverTool(tooltips=[
        ("Cantidad", "@%s" % posicion),
        ("Porcentaje", "@" + str(posicion + '_porcentaje'))
    ], renderers=[r])
    p.add_tools(hover)
p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.axis.minor_tick_line_color = None
p.outline_line_color = None
show(p)
Bokeh Plot

Parece interesante ver cómo estos 8 países tienen una distribución de posiciones bastante parecida, con lo que a simple vista esto va en contra de que países como Brasil o Argentina están llenos de delanteros y países como Italia los defensas son lo más común.

En el siguiente post continuamos este análisis exploratorio

Como este post se nos está alargando ya bastante, lo continuaremos en la siguiente entrada, para que no se os canse la vista de leer.