¿Cómo ha cambiado la ACB? — los Four Factors, temporada a temporada¶
basketballonpampers · informe de análisis de datos
En síntesis. Diez temporadas de Liga Endesa (2016/17 – 2025/26, ~3.100 partidos) dibujan una ACB más rápida y más anotadora: los puntos por equipo suben de 79,8 a 88,2 (+8,4), empujados por más ritmo (74,3 → 77,8 posesiones) y más triple (40 → 42% de los tiros) — las tres tendencias sólidas (r ≈ 0,8). Los cuatro factores se mueven poco y a mejor: eFG sube leve (52,8 → 54,4), las pérdidas bajan (17,4 → 15,7), rebote y asistencias planos. Y un cambio reciente y brusco: el tiro libre se dispara en las dos últimas temporadas (FTr 30,6 → 35,4, casi todo en 2024-2026). El baloncesto de la élite española va hacia más posesiones, más tiro exterior y más eficiencia — y, últimamente, más viajes a la línea.
La pregunta¶
¿Ha cambiado cómo se juega en la ACB a lo largo de una década? Se miran los Four Factors y el contexto de estilo (ritmo, dieta de triple, asistencias, anotación) temporada a temporada, y se comprueba con una recta de tendencia cuáles suben o bajan de verdad y cuáles son ruido.
Cómo lo medimos (honesto)¶
- Nivel LIGA, una fila por temporada: cada factor se calcula sobre los totales de todos los equipos de la temporada (no promediando equipos).
- Ventana 2016/17–2025/26 (10 temporadas). Nota de datos: el adaptador ACB de 2016-2019 repartía mal jugadores dentro del partido; se corrigió el 08-09-2026 y los totales de equipo ya cuadran con el marcador oficial. Este estudio es a nivel liga por diseño (una fila por temporada) y sus cifras no cambiaron con la corrección: un tiro mal asignado a un equipo se contaba igual en la suma total.
- Posesiones = estimación de Oliver (FGA − OREB + TOV + 0,44·FTA), consistente en todas las temporadas → el TOV% y el ritmo son comparables año a año.
- Tendencia = recta por mínimos cuadrados + correlación con el año. Se marca "sólida" solo si |r| ≳ 0,6; el resto, plana.
- Descriptivo: se describe la evolución, no se predice la siguiente temporada.
1. Los datos, temporada a temporada¶
import warnings; warnings.filterwarnings("ignore")
from pathlib import Path
import numpy as np, pandas as pd
import matplotlib.pyplot as plt
BG="#0c0c0e"; PANEL="#151517"; INK="#ededf0"; MUTED="#8a8a93"; ACCENT="#e8772e"
GRID="#2a2a2e"; POS="#3fb27f"; NEG="#d8584f"; BLUE="#4f93c7"
plt.rcParams.update({
"figure.facecolor": BG, "axes.facecolor": BG, "savefig.facecolor": BG,
"axes.edgecolor": GRID, "axes.labelcolor": INK, "text.color": INK,
"xtick.color": MUTED, "ytick.color": MUTED, "grid.color": GRID,
"axes.grid": True, "grid.alpha": 0.30, "grid.linewidth": 0.6,
"figure.figsize": (9, 5), "figure.dpi": 110,
"font.size": 11, "axes.titlesize": 12.5, "axes.titleweight": "bold",
"axes.spines.top": False, "axes.spines.right": False,
})
for cand in [Path("../datasets-acb"), Path("analisis/datasets-acb"), Path("../../analisis/datasets-acb")]:
if cand.exists():
BASE = cand; break
be = pd.read_csv(BASE/"bronze"/"bronze_boxscore_equipo.csv")
for c in ["pts","fga","fgm","p3m","p3a","p1a","ro","rd","to","assist"]:
be[c] = pd.to_numeric(be[c], errors="coerce")
g = be.groupby("temporada").agg(
n=("id_partido","nunique"), fgm=("fgm","sum"), fga=("fga","sum"), p3m=("p3m","sum"),
p3a=("p3a","sum"), p1a=("p1a","sum"), ro=("ro","sum"), rd=("rd","sum"),
to=("to","sum"), ast=("assist","sum"), pts=("pts","sum")).reset_index()
g["pos"] = g.fga - g.ro + g.to + 0.44*g.p1a
tab = pd.DataFrame({
"temporada": [f"{t}/{str(t+1)[2:]}" for t in g.temporada],
"año": g.temporada,
"eFG": ((g.fgm + 0.5*g.p3m)/g.fga*100).round(1),
"TOV%": (g.to/g.pos*100).round(1),
"OREB%": (g.ro/(g.ro+g.rd)*100).round(1),
"FTr": (g.p1a/g.fga*100).round(1),
"3PA%": (g.p3a/g.fga*100).round(1),
"AST%": (g.ast/g.fgm*100).round(1),
"ritmo": (g.pos/(g.n*2)).round(1),
"pts_pg": (g.pts/(g.n*2)).round(1),
})
print(tab.drop(columns="año").to_string(index=False))
temporada eFG TOV% OREB% FTr 3PA% AST% ritmo pts_pg 2016/17 52.8 17.4 28.7 30.6 40.1 55.7 74.3 79.8 2017/18 52.9 15.9 28.4 31.0 40.1 57.3 74.4 81.1 2018/19 54.0 16.3 28.7 29.5 41.6 56.2 74.2 81.8 2019/20 53.1 16.9 28.8 29.1 40.9 55.6 75.6 81.8 2020/21 54.2 17.2 28.8 29.0 39.8 55.3 75.4 82.7 2021/22 53.1 16.6 30.1 29.1 40.0 54.8 74.5 81.6 2022/23 52.8 16.6 28.5 29.9 41.0 57.3 75.5 81.6 2023/24 53.5 16.2 28.7 31.4 41.4 58.4 75.5 83.2 2024/25 54.4 15.7 29.2 32.0 43.4 58.6 76.5 86.2 2025/26 54.4 15.7 29.4 35.4 42.4 56.6 77.8 88.2
# tendencia: pendiente por año y correlación con el año
METR = ["eFG","TOV%","OREB%","FTr","3PA%","AST%","ritmo","pts_pg"]
yr = tab["año"].values
filas = []
for m in METR:
v = tab[m].values.astype(float)
b1, b0 = np.polyfit(yr, v, 1); r = np.corrcoef(yr, v)[0,1]
tend = "SUBE" if r > 0.6 else ("BAJA" if r < -0.6 else "plana")
filas.append([m, round(v[0],1), round(v[-1],1), round(v[-1]-v[0],1), round(b1,2), round(r,2), tend])
trend = pd.DataFrame(filas, columns=["métrica","2016/17","2025/26","Δ","pend/año","r(año)","tendencia"])
print(trend.to_string(index=False))
métrica 2016/17 2025/26 Δ pend/año r(año) tendencia
eFG 52.8 54.4 1.6 0.12 0.56 plana
TOV% 17.4 15.7 -1.7 -0.11 -0.57 plana
OREB% 28.7 29.4 0.7 0.07 0.44 plana
FTr 30.6 35.4 4.8 0.38 0.58 plana
3PA% 40.1 42.4 2.3 0.26 0.68 SUBE
AST% 55.7 56.6 0.9 0.20 0.46 plana
ritmo 74.3 77.8 3.5 0.31 0.84 SUBE
pts_pg 79.8 88.2 8.4 0.71 0.85 SUBE
Lectura del cuadro. Tres métricas suben con claridad (r ≈ 0,7-0,8): puntos, ritmo y % de triple. Los cuatro factores se mueven poco (|r| ≈ 0,5): eFG algo arriba, pérdidas algo abajo. El tiro libre tiene el mayor salto (+4,8) pero no es una subida sostenida (r 0,58): se concentra en las dos últimas temporadas.
2. Los cuatro factores en el tiempo¶
fig, axes = plt.subplots(2, 2, figsize=(11, 7)); axes = axes.ravel()
FF = [("eFG","Tiro (eFG%)",POS), ("TOV%","Pérdidas (TOV%)",NEG),
("OREB%","Rebote of. (OREB%)",BLUE), ("FTr","Tiro libre (FTr)",ACCENT)]
for ax,(m,tit,col) in zip(axes, FF):
v = tab[m].values.astype(float)
ax.plot(yr, v, "-o", color=col, lw=2, ms=5)
b1,b0 = np.polyfit(yr, v, 1); ax.plot(yr, b0+b1*yr, "--", color=MUTED, lw=1.2)
r = np.corrcoef(yr, v)[0,1]
ax.set_title(f"{tit} · Δ {v[-1]-v[0]:+.1f} (r={r:+.2f})", color=INK, fontsize=11)
ax.set_xticks(yr[::2]); ax.set_xticklabels([f"{t%100:02d}" for t in yr[::2]])
fig.suptitle("Los Four Factors de la ACB, 2016/17–2025/26 (nivel liga)", color=INK, fontweight="bold")
plt.tight_layout(); plt.show()
Lectura. El eFG sube despacio (mejor selección y tiro); las pérdidas bajan (equipos más cuidadosos con el balón); el rebote ofensivo apenas se mueve; el tiro libre salta al final. Los factores son estables: la ACB no ha cambiado qué la decide, sino el volumen con que se juega.
3. La ACB moderna: más rápida, más triple, más puntos¶
Las tres tendencias sólidas, indexadas a 2016/17 = 100 para verlas juntas.
fig, ax = plt.subplots(figsize=(9.4, 5))
for m,lab,col in [("pts_pg","Puntos por equipo",ACCENT), ("ritmo","Ritmo (posesiones)",BLUE),
("3PA%","% de tiros de 3",POS)]:
v = tab[m].values.astype(float); idx = v / v[0] * 100
ax.plot(yr, idx, "-o", color=col, lw=2.2, ms=5, label=f"{lab} ({v[0]:.0f}→{v[-1]:.0f})")
ax.axhline(100, color=MUTED, lw=0.8, ls=":")
ax.set_xticks(yr); ax.set_xticklabels([f"{t%100:02d}/{(t+1)%100:02d}" for t in yr], rotation=45, fontsize=9)
ax.set_ylabel("índice (2016/17 = 100)")
ax.set_title("La modernización de la ACB · índice base 2016/17", color=INK)
ax.legend(facecolor=PANEL, edgecolor=GRID, labelcolor=INK)
plt.tight_layout(); plt.show()
Lectura. Las tres crecen de forma sostenida y se aceleran en las dos últimas temporadas: más posesiones + más triples ⇒ más puntos. Es el mismo movimiento que en el resto del baloncesto de élite, medido aquí sobre datos ACB reales.
4. El salto reciente del tiro libre¶
El cambio más brusco no es una tendencia de década, sino de los dos últimos años.
fig, ax = plt.subplots(figsize=(9.2, 4.6))
v = tab["FTr"].values.astype(float)
cols = [ACCENT if t >= 2024 else BLUE for t in yr]
ax.bar([f"{t%100:02d}/{(t+1)%100:02d}" for t in yr], v, color=cols, edgecolor=BG)
for i,val in enumerate(v):
ax.text(i, val+0.2, f"{val:.1f}", ha="center", color=INK, fontsize=9, fontweight="bold")
ax.set_ylabel("FTr (tiros libres intentados / tiros de campo × 100)")
ax.set_title("Tiro libre por temporada · el salto de 2024-2026 (naranja)", color=INK)
ax.set_ylim(0, max(v)*1.15)
plt.setp(ax.get_xticklabels(), rotation=45, fontsize=9)
plt.tight_layout(); plt.show()
print("FTr media 2016/17-2023/24:", round(v[:-2].mean(),1), "| últimas dos:", round(v[-2:].mean(),1))
FTr media 2016/17-2023/24: 30.0 | últimas dos: 33.7
Lectura. Ocho temporadas estables en torno a 30 y un salto a 32-35 en las dos últimas. Un cambio así de brusco y localizado suele venir de reglas o criterio arbitral (más faltas pitadas), no de un cambio gradual de estilo. Es una hipótesis a confirmar con la normativa de esas temporadas — el dato solo dice qué pasó, no por qué.
5. Conclusiones¶
- La ACB se ha vuelto más rápida y anotadora: +8,4 puntos por equipo en una década (79,8 → 88,2), empujados por más ritmo (+3,5 posesiones) y más triple (40 → 42%). Son las tendencias sólidas (r ≈ 0,8).
- Los cuatro factores se mueven poco y a mejor: eFG algo arriba (52,8 → 54,4), pérdidas algo abajo (17,4 → 15,7); rebote y asistencias, planos. Qué decide el partido no ha cambiado; sí el volumen.
- El tiro libre da un salto reciente (2024-2026: 30 → 35), probablemente por reglas/arbitraje — a confirmar con la normativa.
Cautelas (honestidad)¶
- Nivel liga por diseño: describe la liga, no equipos concretos.
- Posesiones estimadas (Oliver), consistentes entre temporadas → válidas para comparar, no como conteo exacto.
- Descriptivo: describe la evolución; no predice la próxima temporada.
- El porqué del salto del tiro libre queda como hipótesis (reglas/arbitraje), no como conclusión.