Resumen

Analizamos 510 temporadas de equipos de la MLB entre 2000 y 2016 para responder una pregunta sencilla: ¿los equipos que gastan más en salarios ganan más partidos?

1. La pregunta

Cada temporada se habla de los equipos que “compran” campeonatos. Pero, ¿qué dicen los datos? Aquí medimos la relación entre lo que cada equipo paga a sus jugadores y los partidos que gana en la temporada regular.

2. Datos y fuentes

3. Metodología

Usamos una regresión lineal simple:

\[ \text{Victorias}_{i,t} = a + b \times \text{Salario relativo}_{i,t} + e_{i,t} \]

donde i es el equipo y t la temporada.

¿Por qué “salario relativo” y no el salario en dólares? Porque los salarios suben cada año: gastar 100 millones en 2005 no es lo mismo que en 2016. Por eso dividimos el gasto de cada equipo entre el promedio de la liga en esa misma temporada:

Así, el coeficiente b se lee como: cuántas victorias más gana un equipo cuando pasa de gastar el promedio a gastar el doble.

4. Análisis en R

Paso 1: Preparación de los datos

salarios <- read.csv("Salaries.csv", fileEncoding = "UTF-8-BOM")
equipos  <- read.csv("Teams.csv",    fileEncoding = "UTF-8-BOM")

str(salarios)
## 'data.frame':    26428 obs. of  5 variables:
##  $ yearID  : int  2004 2007 2008 2009 2010 2011 2012 1986 1987 1988 ...
##  $ teamID  : chr  "SFN" "CHA" "BOS" "SEA" ...
##  $ lgID    : chr  "NL" "AL" "AL" "AL" ...
##  $ playerID: chr  "aardsda01" "aardsda01" "aardsda01" "aardsda01" ...
##  $ salary  : int  300000 387500 403250 419000 2750000 4500000 500000 600000 625000 675000 ...

Paso 2: Pre-proceso

# Quitamos salarios en cero (registros sin pago)
salarios <- salarios[salarios$salary > 0, ]

# En 2016 la tabla de salarios usa otros códigos de equipo que la tabla de equipos.
# Los cambiamos para que coincidan.
codigos <- c(CHC = "CHN", CHW = "CHA", KCR = "KCA", LAD = "LAN", NYM = "NYN", NYY = "NYA",
             SDP = "SDN", SFG = "SFN", STL = "SLN", TBR = "TBA", WSN = "WAS")
cambiar <- salarios$teamID %in% names(codigos)
salarios$teamID[cambiar] <- codigos[salarios$teamID[cambiar]]

# Sumamos los salarios de todos los jugadores: gasto total por equipo y temporada
payroll <- aggregate(salary ~ yearID + teamID, data = salarios, FUN = sum)
names(payroll)[3] <- "payroll"

# Nos quedamos con las temporadas 2000-2016 y las columnas que usamos
equipos <- equipos[equipos$yearID >= 2000 & equipos$yearID <= 2016,
                   c("yearID", "teamID", "name", "W", "L")]

# Unimos victorias con salarios
datos <- merge(equipos, payroll, by = c("yearID", "teamID"))

# Salario relativo: gasto del equipo / promedio de la liga en esa temporada
prom <- aggregate(payroll ~ yearID, data = datos, FUN = mean)
names(prom)[2] <- "prom_liga"
datos <- merge(datos, prom, by = "yearID")
datos$sal_rel      <- datos$payroll / datos$prom_liga
datos$payroll_mill <- datos$payroll / 1e6

# Revisión: deben ser 30 equipos x 17 temporadas = 510 filas
nrow(datos)
## [1] 510
summary(datos[, c("W", "payroll_mill", "sal_rel")])
##        W           payroll_mill       sal_rel      
##  Min.   : 43.00   Min.   : 14.67   Min.   :0.1769  
##  1st Qu.: 72.00   1st Qu.: 59.07   1st Qu.:0.7174  
##  Median : 82.00   Median : 80.41   Median :0.9360  
##  Mean   : 80.96   Mean   : 86.50   Mean   :1.0000  
##  3rd Qu.: 90.00   3rd Qu.:104.09   3rd Qu.:1.2077  
##  Max.   :116.00   Max.   :231.98   Max.   :2.8552

Paso 3: Análisis

modelo <- lm(W ~ sal_rel, data = datos)
summary(modelo)
## 
## Call:
## lm(formula = W ~ sal_rel, data = datos)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -34.506  -7.397   0.329   7.517  33.420 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   69.696      1.235  56.419   <2e-16 ***
## sal_rel       11.269      1.146   9.835   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 10.43 on 508 degrees of freedom
## Multiple R-squared:   0.16,  Adjusted R-squared:  0.1583 
## F-statistic: 96.73 on 1 and 508 DF,  p-value: < 2.2e-16
b  <- coef(modelo)[2]
r2 <- summary(modelo)$r.squared
correlacion <- cor(datos$W, datos$sal_rel)
plot(datos$sal_rel, datos$W,
     pch = 19, col = rgb(13, 132, 54, 110, maxColorValue = 255),
     xlab = "Salario relativo (1 = promedio de la liga)",
     ylab = "Victorias en la temporada",
     main = "Salarios vs. victorias en la MLB, 2000-2016")
abline(modelo, col = "#E23D2E", lwd = 3, lty = 2)
abline(v = 1, col = "gray60", lty = 3)

Paso 4: Post-proceso

Guardamos los residuos del modelo. Un residuo positivo significa que el equipo ganó más de lo que su salario predecía. Uno negativo, que ganó menos.

datos$esperadas <- round(fitted(modelo), 1)
datos$residuo   <- round(resid(modelo), 1)
datos$payroll_mill <- round(datos$payroll_mill, 1)
datos$sal_rel      <- round(datos$sal_rel, 2)
cols <- c("name", "yearID", "payroll_mill", "sal_rel", "W", "esperadas", "residuo")

# Los más eficientes: ganaron mucho más de lo que su salario predecía
eficientes <- head(datos[order(-datos$residuo), cols], 5)

# Gastaron más que el promedio y ganaron mucho menos de lo esperado
caros <- datos[datos$sal_rel > 1, ]
derrochadores <- head(caros[order(caros$residuo), cols], 5)

# Gráfica para Instagram (1080 x 1080) y datos limpios
png("grafica_salarios_victorias.png", width = 1080, height = 1080, res = 150)
plot(datos$sal_rel, datos$W, pch = 19,
     col = rgb(13, 132, 54, 110, maxColorValue = 255),
     xlab = "Salario relativo (1 = promedio)", ylab = "Victorias",
     main = "Salarios vs. victorias, MLB 2000-2016")
abline(modelo, col = "#E23D2E", lwd = 3, lty = 2)
dev.off()
## png 
##   2
write.csv(datos, "datos_salarios_victorias.csv", row.names = FALSE)

5. Resultados

Ecuación estimada

\[ \widehat{\text{Victorias}} = 69.70 + 11.27 \times \text{Salario relativo} \]

Cómo se lee: un equipo que gasta igual que el promedio de la liga (salario relativo = 1) gana, según el modelo, unas 81 victorias. Por cada vez que su gasto sube en una unidad (por ejemplo, de gastar el promedio a gastar el doble), gana en promedio 11.3 partidos más.

Indicador Valor
Temporadas analizadas 510
Victorias extra al gastar el doble del promedio (b) 11.3
Victorias extra por cada 10 % más de gasto 1.1
R² (parte explicada por el salario) 16 %
Correlación 0.4

Lectura sencilla: gastar más sí ayuda a ganar, y la relación es estadísticamente significativa. Pero el salario solo explica cerca del 16 % de las diferencias en victorias. Hay equipos que ganan mucho gastando poco, y otros que gastan mucho y no ganan.

Los más eficientes

Ganaron muchas más victorias de las que su salario predecía.

Equipo Temporada Salarios (millones US$) Salario relativo Victorias Esperadas Diferencia
Seattle Mariners 2001 74.7 1.14 116 82.6 33.4
Oakland Athletics 2002 40.0 0.59 103 76.4 26.6
Oakland Athletics 2001 33.8 0.52 102 75.5 26.5
Tampa Bay Rays 2008 43.8 0.49 97 75.2 21.8
St. Louis Cardinals 2004 83.2 1.21 105 83.3 21.7

Gastaron mucho, ganaron poco

Gastaron más que el promedio de la liga y ganaron muchas menos victorias de las esperadas.

Equipo Temporada Salarios (millones US$) Salario relativo Victorias Esperadas Diferencia
Arizona Diamondbacks 2004 69.8 1.01 51 81.1 -30.1
Seattle Mariners 2008 117.7 1.31 61 84.5 -23.5
New York Mets 2003 116.9 1.65 66 88.3 -22.3
Boston Red Sox 2012 173.2 1.77 69 89.7 -20.7
Minnesota Twins 2011 112.7 1.21 63 83.4 -20.4

6. Limitaciones

7. Cómo reproducir este estudio

  1. Descarga la base de datos en formato CSV desde sabr.org/lahman-database.
  2. Copia los archivos Salaries.csv y Teams.csv en la misma carpeta que este archivo .Rmd.
  3. Abre el archivo en RStudio y presiona Knit.

Referencias


Hecho por Sports Economics · Sports economics made simple.