La base de datos seleccionada en la Actividad 112 corresponde a las estadísticas individuales de los jugadores de la Premier League (primera división del fútbol inglés). Cada fila es un jugador y cada columna un atributo personal o una estadística acumulada de su carrera en la liga: club, posición, nacionalidad, edad, partidos jugados, goles, pases, tarjetas, entre otras.
# Importación de datos.
# Si el Excel está en la misma carpeta del .Rmd se usa directamente;
# si no, se busca automáticamente en las carpetas habituales del usuario.
archivo <- "premierleague_probabilidad.xlsx"
if (!file.exists(archivo)) {
# Carpeta personal real (en Windows ~ apunta a Documentos, no al perfil)
perfil <- c(path.expand("~"), Sys.getenv("USERPROFILE"), dirname(path.expand("~")))
perfil <- unique(perfil[perfil != ""])
subcarpetas <- c("Downloads", "Descargas", "Desktop", "Escritorio",
"Documents", "Documentos")
carpetas <- c(
getwd(),
dirname(getwd()),
perfil,
as.vector(outer(perfil, subcarpetas, file.path)),
as.vector(outer(file.path(perfil, "OneDrive"), subcarpetas, file.path))
)
encontrados <- character(0)
for (d in unique(carpetas)) {
if (dir.exists(d)) {
encontrados <- c(encontrados,
list.files(d, pattern = "premierleague.*\\.xlsx$",
full.names = TRUE, ignore.case = TRUE))
}
}
if (length(encontrados) == 0) {
stop("No se encontró el archivo premierleague_probabilidad.xlsx. ",
"Guárdalo en la misma carpeta que este .Rmd.")
}
archivo <- encontrados[1]
}
pl <- read_excel(archivo)
basename(archivo) # archivo utilizado## [1] "premierleague_probabilidad.xlsx"
## [1] 612 59
El archivo original trae filas en blanco al final de la hoja y nombres de columna con espacios. Antes de analizar se eliminan los registros vacíos y se renombran las variables de interés al español.
pl <- pl %>%
filter(!is.na(Name), !is.na(Club), !is.na(Position)) %>% # quita filas vacías
rename(
jugador = Name,
club = Club,
posicion = Position,
nacionalidad = Nationality,
edad = Age,
partidos = Appearances,
goles = Goals,
asistencias = Assists,
pases = Passes,
amarillas = `Yellow cards`
) %>%
mutate(
club = gsub("-", " ", club), # nombres legibles
posicion = recode(posicion,
"Goalkeeper" = "Portero",
"Defender" = "Defensa",
"Midfielder" = "Mediocampista",
"Forward" = "Delantero"),
posicion = factor(posicion,
levels = c("Portero", "Defensa", "Mediocampista", "Delantero"))
)
n <- nrow(pl)
n # tamaño final de la muestra## [1] 571
Después de la depuración se trabaja con 571 jugadores.
| Variable | Tipo | Escala de medición |
|---|---|---|
posicion |
Cualitativa | Nominal (4 categorías) |
club |
Cualitativa | Nominal (20 categorías) |
edad |
Cuantitativa discreta | Razón (años cumplidos) |
partidos |
Cuantitativa discreta | Razón (conteo de partidos) |
Para los indicadores de forma y la moda se definen tres funciones sencillas, ya que R base no las incluye.
# Moda: valor (o valores) con mayor frecuencia
moda <- function(x) {
x <- x[!is.na(x)]
tab <- table(x)
as.numeric(names(tab)[tab == max(tab)])
}
# Coeficiente de asimetría de Fisher
asimetria <- function(x) {
x <- x[!is.na(x)]
mean((x - mean(x))^3) / (sd(x) * sqrt((length(x) - 1) / length(x)))^3
}
# Curtosis en exceso (0 = distribución mesocúrtica)
curtosis <- function(x) {
x <- x[!is.na(x)]
mean((x - mean(x))^4) / (sd(x) * sqrt((length(x) - 1) / length(x)))^4 - 3
}fa <- table(pl$posicion) # frecuencia absoluta
fr <- prop.table(fa) * 100 # frecuencia relativa (%)
tabla_pos <- data.frame(
Posicion = names(fa),
Frec_absoluta = as.numeric(fa),
Frec_relativa = round(as.numeric(fr), 2),
Frec_abs_acumulada = cumsum(as.numeric(fa)),
Frec_rel_acumulada = round(cumsum(as.numeric(fr)), 2)
)
kable(tabla_pos,
col.names = c("Posición", "ni", "hi (%)", "Ni", "Hi (%)"),
caption = "Tabla 1. Distribución de frecuencias de la posición de juego")| Posición | ni | hi (%) | Ni | Hi (%) |
|---|---|---|---|---|
| Portero | 69 | 12.08 | 69 | 12.08 |
| Defensa | 194 | 33.98 | 263 | 46.06 |
| Mediocampista | 198 | 34.68 | 461 | 80.74 |
| Delantero | 110 | 19.26 | 571 | 100.00 |
ggplot(pl, aes(x = posicion)) +
geom_bar(fill = azul, width = 0.65) +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.4, size = 3.5) +
labs(title = "Jugadores por posición de juego",
x = NULL, y = "Número de jugadores") +
theme_minimal(base_size = 12) +
theme(panel.grid.major.x = element_blank())La moda de la variable es Mediocampista, con 198 jugadores (34.7% del total). La distribución no es uniforme: mediocampistas y defensas concentran cerca de 68.7% de las plantillas, mientras que los porteros son apenas el 12.1%. Este resultado es coherente con la estructura táctica del fútbol: cada equipo alinea un solo portero pero cuatro o cinco jugadores por línea en defensa y medio campo, y mantiene además menos porteros suplentes en la nómina.
Al tratarse de una variable nominal no tiene sentido calcular media ni mediana; el único indicador de tendencia central válido es la moda.
fa_club <- sort(table(pl$club), decreasing = TRUE)
fr_club <- prop.table(fa_club) * 100
tabla_club <- data.frame(
Club = names(fa_club),
Frec_absoluta = as.numeric(fa_club),
Frec_relativa = round(as.numeric(fr_club), 2)
)
kable(tabla_club,
col.names = c("Club", "ni", "hi (%)"),
caption = "Tabla 2. Número de jugadores registrados por club")| Club | ni | hi (%) |
|---|---|---|
| Liverpool | 34 | 5.95 |
| Brighton and Hove Albion | 33 | 5.78 |
| Tottenham Hotspur | 32 | 5.60 |
| Crystal Palace | 31 | 5.43 |
| Everton | 31 | 5.43 |
| Leicester City | 31 | 5.43 |
| Manchester United | 31 | 5.43 |
| Arsenal | 30 | 5.25 |
| Newcastle United | 30 | 5.25 |
| West Ham United | 29 | 5.08 |
| Aston Villa | 28 | 4.90 |
| Sheffield United | 28 | 4.90 |
| West Bromwich Albion | 28 | 4.90 |
| Chelsea | 27 | 4.73 |
| Manchester City | 27 | 4.73 |
| Leeds United | 26 | 4.55 |
| Fulham | 25 | 4.38 |
| Burnley | 24 | 4.20 |
| Southampton | 24 | 4.20 |
| Wolverhampton Wanderers | 22 | 3.85 |
orden_club <- names(sort(table(pl$club))) # de menor a mayor plantilla
ggplot(pl, aes(y = factor(club, levels = orden_club))) +
geom_bar(fill = azul, width = 0.7) +
labs(title = "Tamaño de la plantilla registrada por club",
x = "Número de jugadores", y = NULL) +
theme_minimal(base_size = 11) +
theme(panel.grid.major.y = element_blank())La base cubre los 20 clubes de la temporada. El club con mayor número de jugadores registrados es Liverpool (34 jugadores) y el de menor número es Wolverhampton Wanderers (22). El rango es estrecho —apenas 12 jugadores de diferencia entre el máximo y el mínimo—, de modo que la distribución es prácticamente equilibrada: ningún club está sobrerrepresentado y las comparaciones posteriores entre equipos no quedan sesgadas por el tamaño de la muestra.
Cruzar las dos variables cualitativas permite verificar que la composición por posición se mantenga estable entre clubes.
tabla_doble <- table(pl$club, pl$posicion)
kable(round(prop.table(tabla_doble, margin = 1) * 100, 1),
caption = "Tabla 3. Composición porcentual de la plantilla por club (% por fila)")| Portero | Defensa | Mediocampista | Delantero | |
|---|---|---|---|---|
| Arsenal | 10.0 | 33.3 | 33.3 | 23.3 |
| Aston Villa | 17.9 | 32.1 | 35.7 | 14.3 |
| Brighton and Hove Albion | 12.1 | 30.3 | 36.4 | 21.2 |
| Burnley | 12.5 | 41.7 | 29.2 | 16.7 |
| Chelsea | 7.4 | 40.7 | 37.0 | 14.8 |
| Crystal Palace | 9.7 | 38.7 | 32.3 | 19.4 |
| Everton | 9.7 | 25.8 | 32.3 | 32.3 |
| Fulham | 12.0 | 32.0 | 40.0 | 16.0 |
| Leeds United | 7.7 | 34.6 | 38.5 | 19.2 |
| Leicester City | 9.7 | 32.3 | 41.9 | 16.1 |
| Liverpool | 14.7 | 26.5 | 35.3 | 23.5 |
| Manchester City | 11.1 | 33.3 | 33.3 | 22.2 |
| Manchester United | 12.9 | 41.9 | 32.3 | 12.9 |
| Newcastle United | 13.3 | 30.0 | 40.0 | 16.7 |
| Sheffield United | 14.3 | 39.3 | 32.1 | 14.3 |
| Southampton | 16.7 | 33.3 | 33.3 | 16.7 |
| Tottenham Hotspur | 15.6 | 31.2 | 34.4 | 18.8 |
| West Bromwich Albion | 10.7 | 35.7 | 32.1 | 21.4 |
| West Ham United | 13.8 | 34.5 | 31.0 | 20.7 |
| Wolverhampton Wanderers | 9.1 | 36.4 | 31.8 | 22.7 |
Los porcentajes por fila confirman el patrón general: en casi todos los clubes los porteros representan entre el 10% y el 15% de la plantilla y las líneas de defensa y mediocampo superan conjuntamente el 60%.
edad <- pl$edad
ind_edad <- data.frame(
Indicador = c("n", "Media", "Mediana", "Moda", "Desviación estándar",
"Varianza", "Coef. de variación (%)", "Mínimo", "Máximo", "Rango",
"Q1", "Q3", "Rango intercuartílico", "P10", "P90",
"Asimetría (Fisher)", "Curtosis (exceso)"),
Valor = round(c(
sum(!is.na(edad)), mean(edad, na.rm = TRUE), median(edad, na.rm = TRUE),
moda(edad)[1], sd(edad, na.rm = TRUE), var(edad, na.rm = TRUE),
100 * sd(edad, na.rm = TRUE) / mean(edad, na.rm = TRUE),
min(edad, na.rm = TRUE), max(edad, na.rm = TRUE),
diff(range(edad, na.rm = TRUE)),
quantile(edad, 0.25, na.rm = TRUE), quantile(edad, 0.75, na.rm = TRUE),
IQR(edad, na.rm = TRUE),
quantile(edad, 0.10, na.rm = TRUE), quantile(edad, 0.90, na.rm = TRUE),
asimetria(edad), curtosis(edad)
), 2)
)
kable(ind_edad, caption = "Tabla 4. Indicadores descriptivos de la edad (años)")| Indicador | Valor |
|---|---|
| n | 570.00 |
| Media | 25.79 |
| Mediana | 26.00 |
| Moda | 27.00 |
| Desviación estándar | 4.38 |
| Varianza | 19.16 |
| Coef. de variación (%) | 16.97 |
| Mínimo | 17.00 |
| Máximo | 38.00 |
| Rango | 21.00 |
| Q1 | 22.00 |
| Q3 | 29.00 |
| Rango intercuartílico | 7.00 |
| P10 | 20.00 |
| P90 | 32.00 |
| Asimetría (Fisher) | 0.15 |
| Curtosis (exceso) | -0.69 |
pl <- pl %>%
mutate(edadR = cut(edad,
breaks = c(16, 20, 24, 28, 32, Inf),
labels = c("17 a 20 años", "21 a 24 años", "25 a 28 años",
"29 a 32 años", "33 años o más")))
fa_e <- table(pl$edadR)
fr_e <- prop.table(fa_e) * 100
tabla_edad <- data.frame(
Intervalo = names(fa_e),
ni = as.numeric(fa_e),
hi = round(as.numeric(fr_e), 2),
Ni = cumsum(as.numeric(fa_e)),
Hi = round(cumsum(as.numeric(fr_e)), 2)
)
kable(tabla_edad,
col.names = c("Intervalo de edad", "ni", "hi (%)", "Ni", "Hi (%)"),
caption = "Tabla 5. Distribución de frecuencias de la edad agrupada")| Intervalo de edad | ni | hi (%) | Ni | Hi (%) |
|---|---|---|---|---|
| 17 a 20 años | 82 | 14.39 | 82 | 14.39 |
| 21 a 24 años | 153 | 26.84 | 235 | 41.23 |
| 25 a 28 años | 169 | 29.65 | 404 | 70.88 |
| 29 a 32 años | 128 | 22.46 | 532 | 93.33 |
| 33 años o más | 38 | 6.67 | 570 | 100.00 |
ggplot(pl, aes(x = edad)) +
geom_histogram(binwidth = 2, fill = azul, color = "white") +
geom_vline(xintercept = mean(edad, na.rm = TRUE),
linetype = "dashed", color = "#C0392B") +
annotate("text", x = mean(edad, na.rm = TRUE) + 0.4, y = Inf, vjust = 1.8,
label = "Media", color = "#C0392B", size = 3.5, hjust = 0) +
labs(title = "Distribución de la edad de los jugadores",
x = "Edad (años)", y = "Frecuencia") +
theme_minimal(base_size = 12)ggplot(pl, aes(x = edad)) +
geom_boxplot(fill = azul, alpha = 0.6, outlier.color = "#C0392B") +
labs(title = "Diagrama de caja de la edad", x = "Edad (años)") +
theme_minimal(base_size = 12) +
theme(axis.text.y = element_blank())La edad promedio de los jugadores es de 25.8 años y la mediana es de 26 años. La cercanía entre ambas medidas, junto con un coeficiente de asimetría de 0.15 —muy próximo a cero—, indica una distribución casi simétrica. La curtosis en exceso (-0.69) es negativa, es decir, la distribución es platicúrtica: más aplanada que la normal, con las edades repartidas de forma relativamente pareja en lugar de concentrarse en un solo pico.
La dispersión es baja: con una desviación estándar de 4.38 años, el coeficiente de variación es de apenas 17%, lo que confirma que se trata de un grupo homogéneo en edad. El 50% central de los jugadores se ubica entre los 22 y los 29 años (rango intercuartílico de 7 años), y el 90% tiene 32 años o menos. Los valores extremos —17 años el más joven y 38 el mayor— reflejan la convivencia entre juveniles recién promovidos y veteranos al final de su carrera.
part <- pl$partidos
ind_part <- data.frame(
Indicador = c("n", "Media", "Mediana", "Moda", "Desviación estándar",
"Varianza", "Coef. de variación (%)", "Mínimo", "Máximo", "Rango",
"Q1", "Q3", "Rango intercuartílico", "P10", "P90",
"Asimetría (Fisher)", "Curtosis (exceso)"),
Valor = round(c(
sum(!is.na(part)), mean(part, na.rm = TRUE), median(part, na.rm = TRUE),
moda(part)[1], sd(part, na.rm = TRUE), var(part, na.rm = TRUE),
100 * sd(part, na.rm = TRUE) / mean(part, na.rm = TRUE),
min(part, na.rm = TRUE), max(part, na.rm = TRUE),
diff(range(part, na.rm = TRUE)),
quantile(part, 0.25, na.rm = TRUE), quantile(part, 0.75, na.rm = TRUE),
IQR(part, na.rm = TRUE),
quantile(part, 0.10, na.rm = TRUE), quantile(part, 0.90, na.rm = TRUE),
asimetria(part), curtosis(part)
), 2)
)
kable(ind_part, caption = "Tabla 6. Indicadores descriptivos de los partidos jugados")| Indicador | Valor |
|---|---|
| n | 571.00 |
| Media | 68.82 |
| Mediana | 36.00 |
| Moda | 0.00 |
| Desviación estándar | 81.80 |
| Varianza | 6690.83 |
| Coef. de variación (%) | 118.85 |
| Mínimo | 0.00 |
| Máximo | 539.00 |
| Rango | 539.00 |
| Q1 | 3.00 |
| Q3 | 108.00 |
| Rango intercuartílico | 105.00 |
| P10 | 0.00 |
| P90 | 190.00 |
| Asimetría (Fisher) | 1.62 |
| Curtosis (exceso) | 2.95 |
pl <- pl %>%
mutate(partidosR = cut(partidos,
breaks = c(-1, 0, 25, 50, 100, 200, Inf),
labels = c("Sin debutar (0)", "1 a 25", "26 a 50",
"51 a 100", "101 a 200", "Más de 200")))
fa_p <- table(pl$partidosR)
fr_p <- prop.table(fa_p) * 100
tabla_part <- data.frame(
Intervalo = names(fa_p),
ni = as.numeric(fa_p),
hi = round(as.numeric(fr_p), 2),
Ni = cumsum(as.numeric(fa_p)),
Hi = round(cumsum(as.numeric(fr_p)), 2)
)
kable(tabla_part,
col.names = c("Partidos jugados", "ni", "hi (%)", "Ni", "Hi (%)"),
caption = "Tabla 7. Distribución de frecuencias de los partidos jugados")| Partidos jugados | ni | hi (%) | Ni | Hi (%) |
|---|---|---|---|---|
| Sin debutar (0) | 90 | 15.76 | 90 | 15.76 |
| 1 a 25 | 139 | 24.34 | 229 | 40.11 |
| 26 a 50 | 103 | 18.04 | 332 | 58.14 |
| 51 a 100 | 85 | 14.89 | 417 | 73.03 |
| 101 a 200 | 106 | 18.56 | 523 | 91.59 |
| Más de 200 | 48 | 8.41 | 571 | 100.00 |
ggplot(pl, aes(x = partidos)) +
geom_histogram(binwidth = 25, fill = azul, color = "white", boundary = 0) +
labs(title = "Distribución de los partidos jugados",
x = "Partidos jugados en la Premier League", y = "Frecuencia") +
theme_minimal(base_size = 12)ggplot(pl, aes(x = partidos)) +
geom_boxplot(fill = azul, alpha = 0.6, outlier.color = "#C0392B") +
labs(title = "Diagrama de caja de los partidos jugados", x = "Partidos") +
theme_minimal(base_size = 12) +
theme(axis.text.y = element_blank())El comportamiento de esta variable es opuesto al de la edad. La media (68.8 partidos) casi duplica a la mediana (36 partidos) y la moda es 0, señal inequívoca de una distribución fuertemente asimétrica hacia la derecha: el coeficiente de asimetría es 1.62 y la curtosis en exceso 2.95, es decir, una distribución leptocúrtica con cola larga y valores atípicos.
El coeficiente de variación alcanza el 118.9%, muy superior al 100%, lo que revela una heterogeneidad extrema: conviven jugadores que aún no debutan con futbolistas que superan los 539 partidos. El 25% de los jugadores acumula 3 partidos o menos, mientras que el 10% más experimentado supera los 190.
Implicación metodológica: con esta forma de distribución la media no es una medida representativa. Para resumir la experiencia típica de un jugador conviene usar la mediana y el rango intercuartílico, que no se ven arrastrados por los valores extremos. En la edad, en cambio, la media sí es un buen resumen porque la distribución es simétrica y homogénea.
Al cruzar las variables cualitativas con las cuantitativas aparecen diferencias que el análisis univariado no muestra.
resumen <- pl %>%
group_by(posicion) %>%
summarise(
n = n(),
Edad_media = round(mean(edad, na.rm = TRUE), 1),
Edad_mediana= median(edad, na.rm = TRUE),
Part_media = round(mean(partidos, na.rm = TRUE), 1),
Part_mediana= median(partidos, na.rm = TRUE),
Goles_media = round(mean(goles, na.rm = TRUE), 1),
Goles_max = max(goles, na.rm = TRUE),
.groups = "drop"
)
kable(resumen,
col.names = c("Posición", "n", "Edad media", "Edad mediana",
"Partidos media", "Partidos mediana", "Goles media", "Goles máx."),
caption = "Tabla 8. Indicadores por posición de juego")| Posición | n | Edad media | Edad mediana | Partidos media | Partidos mediana | Goles media | Goles máx. |
|---|---|---|---|---|---|---|---|
| Portero | 69 | 28.2 | 28 | 51.8 | 7.0 | 0.0 | 0 |
| Defensa | 194 | 25.6 | 26 | 67.3 | 37.5 | 2.7 | 27 |
| Mediocampista | 198 | 25.4 | 25 | 71.5 | 40.0 | 6.6 | 61 |
| Delantero | 110 | 25.4 | 25 | 77.4 | 39.5 | 21.5 | 180 |
ggplot(pl, aes(x = posicion, y = edad)) +
geom_boxplot(fill = azul, alpha = 0.6, outlier.color = "#C0392B") +
labs(title = "Edad según la posición de juego",
x = NULL, y = "Edad (años)") +
theme_minimal(base_size = 12) +
theme(panel.grid.major.x = element_blank())Los porteros son claramente el grupo de mayor edad: su media (28.2 años) supera en cerca de 2.7 años a la del resto de posiciones, que son prácticamente indistinguibles entre sí (entre 25.4 y 25.6 años). Esto coincide con la idea de que la portería es una posición donde la experiencia pesa más que el rendimiento físico y donde las carreras se extienden por más tiempo.
En goles, la diferencia es la esperada por el rol táctico: los delanteros promedian 21.5 goles frente a 2.7 de los defensas, y los porteros registran 0.
Las dos variables cualitativas se comportan de forma distinta. La posición presenta una distribución claramente desigual, con moda en Mediocampista (34.7%), mientras que el club se reparte de manera casi uniforme entre los 20 equipos. En ambos casos la moda es el único indicador de tendencia central aplicable por tratarse de escalas nominales.
La edad es una variable homogénea y casi simétrica. Media (25.8) y mediana (26) coinciden, la asimetría es cercana a cero y el coeficiente de variación (17%) es bajo: la media resume bien al conjunto.
Los partidos jugados son una variable muy heterogénea y asimétrica. Con un coeficiente de variación del 118.9% y una asimetría de 1.62, la media sobreestima la experiencia del jugador típico; la mediana y el rango intercuartílico son las medidas adecuadas.
La elección del indicador depende de la forma de la distribución. Este es el aprendizaje central de la unidad: calcular la media sin revisar antes la simetría y la dispersión puede llevar a conclusiones equivocadas sobre los mismos datos.
Limitaciones. El análisis es exclusivamente descriptivo: describe y resume lo observado en esta base, pero no permite establecer relaciones causales ni generalizar a otras ligas o temporadas. Además, las estadísticas son acumuladas de carrera, por lo que favorecen a los jugadores con más años en la liga.
Base de datos: estadísticas de jugadores de la Premier League (Actividad 112). Procesamiento: R y RStudio.