|
1 | | -"""Módulo para visualización de datos.""" |
| 1 | +"""Módulo de visualización de pases del Manchester United.""" |
| 2 | +from pathlib import Path |
| 3 | + |
2 | 4 | import matplotlib.pyplot as plt |
| 5 | +import matplotlib.ticker as mticker |
3 | 6 | import pandas as pd |
| 7 | + |
4 | 8 | try: |
5 | 9 | import seaborn as sns |
| 10 | + sns.set_theme(style="whitegrid", palette="muted") |
6 | 11 | HAS_SEABORN = True |
7 | 12 | except ImportError: |
8 | 13 | HAS_SEABORN = False |
9 | 14 |
|
10 | | -def plot_top_passers(df: pd.DataFrame, metric: str = 'Cmp', top_n: int = 10, out_path: str = 'grafico_pases_united.png'): |
11 | | - """Genera un gráfico de barras de los top N jugadores según una métrica.""" |
| 15 | +_RED = "#c0392b" |
| 16 | +_DARK = "#2c3e50" |
| 17 | + |
| 18 | + |
| 19 | +def _guardar(fig: plt.Figure, out_path: str) -> str: |
| 20 | + Path(out_path).parent.mkdir(parents=True, exist_ok=True) |
| 21 | + fig.savefig(out_path, dpi=150, bbox_inches="tight") |
| 22 | + plt.close(fig) |
| 23 | + print(f" ✅ Guardado: {out_path}") |
| 24 | + return out_path |
| 25 | + |
| 26 | + |
| 27 | +def plot_top_passers( |
| 28 | + df: pd.DataFrame, |
| 29 | + metric: str = "Cmp", |
| 30 | + top_n: int = 10, |
| 31 | + out_path: str = "grafico_pases_united.png", |
| 32 | +) -> str: |
| 33 | + """ |
| 34 | + Gráfico de barras horizontales con los top N jugadores según una métrica. |
| 35 | +
|
| 36 | + Args: |
| 37 | + df: DataFrame limpio de pases. |
| 38 | + metric: Columna a usar como métrica (ej. 'Cmp', 'Prog', 'xA'). |
| 39 | + top_n: Número de jugadores a mostrar. |
| 40 | + out_path: Ruta de salida del gráfico. |
| 41 | +
|
| 42 | + Returns: |
| 43 | + Ruta donde se guardó el gráfico. |
| 44 | + """ |
12 | 45 | if metric not in df.columns: |
13 | | - raise KeyError(f'La columna {metric} no existe en los datos.') |
14 | | - |
15 | | - # Ordenar y seleccionar top |
16 | | - df_top = df.sort_values(metric, ascending=False).head(top_n) |
17 | | - |
18 | | - plt.figure(figsize=(10, 6)) |
19 | | - |
| 46 | + raise KeyError(f"La columna '{metric}' no existe en los datos.") |
| 47 | + if "Player" not in df.columns: |
| 48 | + raise KeyError("El DataFrame no tiene columna 'Player'.") |
| 49 | + |
| 50 | + df_top = df.dropna(subset=[metric]).sort_values(metric, ascending=False).head(top_n) |
| 51 | + |
| 52 | + fig, ax = plt.subplots(figsize=(10, 6)) |
| 53 | + |
| 54 | + colores = [_RED if i == 0 else _DARK for i in range(len(df_top))] |
| 55 | + |
20 | 56 | if HAS_SEABORN: |
21 | | - sns.set_theme(style="whitegrid") |
22 | | - sns.barplot(x=metric, y='Player', data=df_top, palette='viridis') |
| 57 | + sns.barplot(x=metric, y="Player", data=df_top, palette=colores, ax=ax) |
23 | 58 | else: |
24 | | - # Fallback a matplotlib puro |
25 | | - plt.barh(df_top['Player'][::-1], df_top[metric][::-1]) |
26 | | - |
27 | | - plt.xlabel(metric) |
28 | | - plt.title(f'Top {top_n} jugadores por {metric}') |
29 | | - plt.tight_layout() |
30 | | - plt.savefig(out_path) |
31 | | - plt.close() |
32 | | - return out_path |
| 59 | + ax.barh(df_top["Player"][::-1], df_top[metric][::-1], color=colores[::-1]) |
| 60 | + |
| 61 | + ax.set_xlabel(metric, fontsize=11) |
| 62 | + ax.set_ylabel("") |
| 63 | + ax.set_title(f"Top {top_n} jugadores — {metric} | Man Utd 2024/25", fontweight="bold") |
| 64 | + ax.axvline(df_top[metric].mean(), color="gray", linestyle="--", linewidth=0.8, |
| 65 | + label=f"Media: {df_top[metric].mean():.1f}") |
| 66 | + ax.legend(fontsize=8) |
| 67 | + |
| 68 | + return _guardar(fig, out_path) |
| 69 | + |
| 70 | + |
| 71 | +def plot_prog_ratio_scatter( |
| 72 | + df: pd.DataFrame, |
| 73 | + out_path: str = "analisis_mediocampo_united.png", |
| 74 | +) -> str: |
| 75 | + """ |
| 76 | + Scatter: Pases completados (volumen) vs Prog_Ratio (eficiencia progresiva). |
| 77 | +
|
| 78 | + Args: |
| 79 | + df: DataFrame con columnas Cmp y Prog_Ratio. |
| 80 | + out_path: Ruta de salida. |
| 81 | +
|
| 82 | + Returns: |
| 83 | + Ruta donde se guardó el gráfico. |
| 84 | + """ |
| 85 | + if "Prog_Ratio" not in df.columns or "Cmp" not in df.columns: |
| 86 | + raise KeyError("Se necesitan columnas 'Prog_Ratio' y 'Cmp'.") |
| 87 | + |
| 88 | + fig, ax = plt.subplots(figsize=(10, 7)) |
| 89 | + |
| 90 | + ax.scatter(df["Cmp"], df["Prog_Ratio"], color=_RED, s=100, edgecolors=_DARK, |
| 91 | + linewidth=0.6, zorder=3) |
| 92 | + |
| 93 | + if "Player" in df.columns: |
| 94 | + for _, row in df.iterrows(): |
| 95 | + ax.annotate(row["Player"], xy=(row["Cmp"], row["Prog_Ratio"]), |
| 96 | + xytext=(4, 3), textcoords="offset points", fontsize=8) |
| 97 | + |
| 98 | + ax.axhline(df["Prog_Ratio"].mean(), color="gray", linestyle="--", linewidth=0.8, |
| 99 | + label=f"Media Prog_Ratio: {df['Prog_Ratio'].mean():.3f}") |
| 100 | + ax.axvline(df["Cmp"].mean(), color="gray", linestyle=":", linewidth=0.8, |
| 101 | + label=f"Media Cmp: {df['Cmp'].mean():.0f}") |
| 102 | + |
| 103 | + ax.set_xlabel("Pases Completados (Cmp)", fontsize=11) |
| 104 | + ax.set_ylabel("Ratio de Pases Progresivos (Prog/Cmp)", fontsize=11) |
| 105 | + ax.set_title("Volumen vs Eficiencia Progresiva — Mediocampistas Man Utd", fontweight="bold") |
| 106 | + ax.legend(fontsize=8) |
| 107 | + ax.grid(True, alpha=0.2) |
| 108 | + |
| 109 | + return _guardar(fig, out_path) |
0 commit comments