toupper("hola")[1] "HOLA"
Clausura 2024, el título que la Liga Deportiva Alajuelense no puede perder
En este ejercicio vamos a presentar todo el código necesario para reproducir el Gráfico A visto arriba.
Para empezar, haremos una pre-introducción a R. Esta pre-introducción es ideal para personas que nunca antes han programado.
Podés programar en línea. Sólo tenés que crear una cuenta en Posit Cloud para conseguir acceso a RStudio. Podrás entonces realizar este ejercicio de programación aunque no tengás R instalado en tu computadora.
Programar en R consiste en aplicar funciones a objetos:
toupper("hola")[1] "HOLA"
Arriba, la función toupper() la aplicamos sobre el objeto "hola" y lo convertimos en "HOLA".
La función sqrt() la aplicamos sobre el objeto 4 y obtenemos su raíz cuadrada, 2:
sqrt(4)[1] 2
Eso es todo: en R, programar consiste en aplicar funciones a objetos.
En los ejemplos anteriores aplicamos funciones a un objeto nada más. También podemos aplicar funciones a varios objetos simultáneamente.
Para aplicar funciones a varios objetos al mismo tiempo, primero concatenamos todos los objetos mediante la función c():
c("hola", "hello", "olá", "hallo")[1] "hola" "hello" "olá" "hallo"
c(1, 100, 4, 55, 10)[1] 1 100 4 55 10
Una vez concatenados los objetos, los pasamos a la función que deseemos:
toupper(c("hola", "hello", "olá", "hallo"))[1] "HOLA" "HELLO" "OLÁ" "HALLO"
sqrt(c(1, 100, 4, 55, 10))[1] 1.000000 10.000000 2.000000 7.416198 3.162278
En estos ejemplos lo que hemos hecho es anidar dos funciones; así, por ejemplo, la función c() quedó anidada dentro de la función toupper().
Las funciones anidadas R las implementa de adentro hacia afuera, de modo que el output de c() se convierte en el input de toupper().
Anidar funciones no es lo mejor si buscamos que nuestro código se lea fácilmente. Con el operador |> podemos conseguir los mismos resultados y a la vez logramos que el código sea más claro:
c("hola", "hello", "olá", "hallo") |>
toupper()[1] "HOLA" "HELLO" "OLÁ" "HALLO"
c(1, 100, 4, 55, 10) |>
sqrt()[1] 1.000000 10.000000 2.000000 7.416198 3.162278
El operador |> clarifica cuál función está tomando como input el output de cuál función. Por supuesto, la utilidad del operador |> es mayor cuantas más funciones estén involucradas en una misma ejecución:
[1] "HALLO" "HELLO" "HOLA" "OLÁ"
[1] 10
Recapitulemos:
En R hay funciones y hay objetos.
Programar en R consiste en aplicar funciones a objetos.
Lo siguiente es aprender que las funciones tienen argumentos.
R nos permite jugar con los argumentos de las funciones. Modificando sus argumentos podemos adaptar las funciones a nuestras necesidades inmediatas.
Todo lo que escribás después de # R lo omitirá. De esa manera podés añadir comentarios al código. Dejar abundancia de comentarios en nuestro código es siempre una buena idea.
Por ejemplo, R dispone de una función para crear secuencias de números:
seq(
from = 10, # Empieza en este número
to = 20 # Termina en este número
) [1] 10 11 12 13 14 15 16 17 18 19 20
La función seq() tiene dos argumentos, from y to, y esos argumentos los podemos manipular para crear cualquier secuencia de números:
En from especificamos con cuál número empieza la secuencia.
En to especificamos con cuál número termina la secuencia.
Es curioso que no es necesario indicar los nombres de los argumentos:
seq(10, 20) [1] 10 11 12 13 14 15 16 17 18 19 20
En el ejemplo anterior no escribimos from = ni tampoco to =. Tan sólo indicamos 10 y 20, sin decir explícitamente cuál correspondía a from y cuál a to.
A falta de indicaciones explícitas, R distribuye los argumentos de acuerdo con el orden asignado por defecto.
El orden los argumentos lo podés saber consultando la documentación de las funciones.
Para consultar la documentación de la función seq() basta con ejecutar el shortcut ?seq(). Inmediatamente se desplagará la pestaña Help: en la sección Usage vas a ver el orden asignado por defecto a los argumentos, y en la sección siguiente, Arguments, vas a encontrar una descripción de cada un argumento.
Una de las grandes fortalezas de R es precisamente la excelente calidad de su documentación.
Si a los argumentos los definimos explícitamente, los podemos acomodar en cualquier orden:
seq(from = 10, to = 20) [1] 10 11 12 13 14 15 16 17 18 19 20
seq(to = 20, from = 10) [1] 10 11 12 13 14 15 16 17 18 19 20
Las líneas de código demasiado largas no son recomendables porque complican su lectura. Por lo general, abordaremos sólo un argumento por línea:
seq(
from = 1,
to = 21,
by = 3
)[1] 1 4 7 10 13 16 19
R permite quebrar las líneas a la altura de los paréntesis y de las comas, y así lo haremos frecuentemente a lo largo de este ejercicio.
Por cierto, el operador : también crea secuencias de números. Lo presentamos desde ya porque lo vamos a necesitar más adelante:
55:60[1] 55 56 57 58 59 60
R trae consigo un montón de funciones. Ya hemos puesto en práctica unas cuantas: toupper(), sqrt(), c(), sort(), seq().
Una de las fortalezas de R es que podemos instalar paquetes con más funciones. La oferta de paquetes que existe es realmente extraordinaria. Hay paquetes para programar casi cualquier cosa.
Para este ejercicio vamos a instalar el paquete tidyverse, el cual es, en realidad, una colección de paquetes. Al instalar tidyverse estamos instalando varios paquetes al mismo tiempo, entre otros:
dplyr: paquete especializado en manipulación de datos.
ggplot2: paquete especializado en visualización de datos.
El primer paso es instalar tidyverse usando la función install.packages():
install.packages("tidyverse")El segundo paso es cargarlo usando la función library():
library(tidyverse)Ambos pasos (instalar y cargar el paquete) son indispensables, no importa el paquete en cuestión.
Aunque hayamos instalado un paquete en nuestra computadora previamente, si no lo hemos cargado en la sesión de R actual no será posible utilizar sus funciones.
¡Listo! Esta pre-introducción a R es suficiente para que cualquier persona sin experiencia previa en programación pueda encarar con éxito las próximas secciones.
Entramos en materia, ahora sí. Primero vamos a producir los datos que necesitamos para este ejercicio.
Los datos corresponden a los torneos disputados en la liga profesional de fútbol masculino de Costa Rica, desde el torneo Invierno 2013 hasta el torneo Clausura 2024:
data <- tribble(
~torneo, ~Saprissa, ~LDA, ~CSH,
"2013 Invierno", 29, 29, 23, # LDA gana 29
"2014 Verano", 30, 29, 23, # Saprissa gana 30
"2014 Invierno", 31, 29, 23, # Saprissa gana 31
"2015 Verano", 31, 29, 24, # CSH gana 24
"2015 Invierno", 32, 29, 24, # Saprissa gana 32
"2016 Verano", 32, 29, 25, # CSH gana 25
"2016 Invierno", 33, 29, 25, # Saprissa gana 33
"2017 Verano", 33, 29, 26, # CSH gana 26
"2017 Apertura", 33, 29, 26, # Ninguno gana
"2018 Clausura", 34, 29, 26, # Saprissa gana 34
"2018 Apertura", 34, 29, 27, # CSH gana 27
"2019 Clausura", 34, 29, 27, # Ninguno gana
"2019 Apertura", 34, 29, 28, # CSH gana 28
"2020 Clausura", 35, 29, 28, # Saprissa gana 35
"2020 Apertura", 35, 30, 28, # LDA gana 30
"2021 Clausura", 36, 30, 28, # Saprissa gana 36
"2021 Apertura", 36, 30, 29, # CSH gana 29
"2022 Clausura", 36, 30, 29, # Ninguno gana
"2022 Apertura", 37, 30, 29, # Saprissa gana 37
"2023 Clausura", 38, 30, 29, # Saprissa gana 38
"2023 Apertura", 39, 30, 29, # Saprissa gana 39
"2024 Clausura", 40, 30, 29 # Saprissa gana 40
)El código anterior produce los datos que vamos a visualizar en la próxima sección.
Lo usual es que tengás que cargar los datos (típicamente nos los dan almacenados, digamos, en un spreadsheet como las conocidas hojas de cálculo de Microsoft Excel). Por mera conveniencia, para este ejercicio los datos los produje manualmente, valiéndome de la función tribble() para tabularlos. Los datos los obtuve de Wikipedia.
No vamos a profundizar en cómo opera la función tribble(). Lo importante es notar que el operador <- toma el output de dicha función (los datos) y lo asigna a un nombre: data.
A partir del instante en que ejecutamos ese bloque de código el objeto data ha sido creado en la memoria de nuestra computadora. En adelante, podremos manipular los datos con tan sólo llamarlos por su nombre: data.
Por ejemplo, si queremos saber cuántas filas tiene data, le aplicamos la función nrow():
nrow(data)[1] 22
Si queremos imprimirlos, usamos la función print():
print(data)# A tibble: 22 × 4
torneo Saprissa LDA CSH
<chr> <dbl> <dbl> <dbl>
1 2013 Invierno 29 29 23
2 2014 Verano 30 29 23
3 2014 Invierno 31 29 23
4 2015 Verano 31 29 24
5 2015 Invierno 32 29 24
6 2016 Verano 32 29 25
7 2016 Invierno 33 29 25
8 2017 Verano 33 29 26
9 2017 Apertura 33 29 26
10 2018 Clausura 34 29 26
# ℹ 12 more rows
La función print() imprime apenas diez filas. Esta función tiene un argumento, n, que podemos modificar para que imprima un número específico de filas:
print(data, n = 3)# A tibble: 22 × 4
torneo Saprissa LDA CSH
<chr> <dbl> <dbl> <dbl>
1 2013 Invierno 29 29 23
2 2014 Verano 30 29 23
3 2014 Invierno 31 29 23
# ℹ 19 more rows
Tal como acabamos de observar, la función nrow() señala cuántas filas tiene el objeto data y la función print() imprime unas cuantas filas (por defecto)… O sea, bien podríamos combinarlas y hacer que R imprima todas las filas:
print(data, n = nrow(data))# A tibble: 22 × 4
torneo Saprissa LDA CSH
<chr> <dbl> <dbl> <dbl>
1 2013 Invierno 29 29 23
2 2014 Verano 30 29 23
3 2014 Invierno 31 29 23
4 2015 Verano 31 29 24
5 2015 Invierno 32 29 24
6 2016 Verano 32 29 25
7 2016 Invierno 33 29 25
8 2017 Verano 33 29 26
9 2017 Apertura 33 29 26
10 2018 Clausura 34 29 26
11 2018 Apertura 34 29 27
12 2019 Clausura 34 29 27
13 2019 Apertura 34 29 28
14 2020 Clausura 35 29 28
15 2020 Apertura 35 30 28
16 2021 Clausura 36 30 28
17 2021 Apertura 36 30 29
18 2022 Clausura 36 30 29
19 2022 Apertura 37 30 29
20 2023 Clausura 38 30 29
21 2023 Apertura 39 30 29
22 2024 Clausura 40 30 29
Una vez vistas todas las filas, es oportuno introducir tres aclaraciones sobre data, el data set que imprimimos justo arriba:
data recoge cuántos títulos lleva acumulados cada uno de tres equipos seleccionados (Deportivo Saprissa, Liga Deportiva Alajuelense y Club Sport Herediano) al finalizar el torneo respectivo; cada fila es un torneo distinto.
data incluye el torneo Clausura 2024 (no habrá iniciado al momento de publicarse este ejercicio) y asume que el Deportivo Saprissa lo ganará; este es el escenario hipotético al que se refiere el Gráfico A; nuestra tarea es reproducir el Gráfico A, precisamente.
data esconde una historia: la historia de cuántos títulos de ventaja sacaría el Deportivo Saprissa sobre la Liga Deportiva Alajuelense si ganase el Clausura 2024 (acaba de ganar el Apertura 2023) y cómo la ventaja del primero sobre el segundo se abrió escandalosamente en cuestión de una década.
Por cierto, si debemos contar las columnas de data en lugar de las filas, la función ncol() hará el trabajo:
ncol(data)[1] 4
La función dim() devuelve el número de filas y de columnas, en ese orden:
dim(data)[1] 22 4
Podemos salvar el resultado anterior con el ya conocido operador <-, por ejemplo, asignando ese objeto al nombre dimensiones_originales.
Recordemos que la asignación crea el objeto en la memoria de nuestra computadora para que lo podamos usar repetidamente, pero no lo imprime:
## Lo asignamos
dimensiones_originales <- dim(data)## Lo imprimimos, opción A
dimensiones_originales[1] 22 4
## Lo imprimimos, opción B
print(dimensiones_originales)[1] 22 4
Asignar un objeto a un nombre y al mismo tiempo imprimirlo es posible si encapsulamos todo el código entre paréntesis:
## Lo asignamos y lo imprimimos
(dimensiones_originales <- dim(data))[1] 22 4
Lista la producción de los datos. Ahora vamos a modificarlos un poco.
Sobre todo, necesitamos llevar a cabo un cambio importante respecto a la estructura misma de los datos.
data actualmente tiene una columna Saprissa, una columna LDA y una columna CSH. Imprimamos unas cuantas filas para recordar cómo se ve.
Las funciones head() y tail() imprimen las primeras y las últimas filas, respectivamente:
## Las primeras filas
head(data)# A tibble: 6 × 4
torneo Saprissa LDA CSH
<chr> <dbl> <dbl> <dbl>
1 2013 Invierno 29 29 23
2 2014 Verano 30 29 23
3 2014 Invierno 31 29 23
4 2015 Verano 31 29 24
5 2015 Invierno 32 29 24
6 2016 Verano 32 29 25
## Las últimas filas
tail(data)# A tibble: 6 × 4
torneo Saprissa LDA CSH
<chr> <dbl> <dbl> <dbl>
1 2021 Apertura 36 30 29
2 2022 Clausura 36 30 29
3 2022 Apertura 37 30 29
4 2023 Clausura 38 30 29
5 2023 Apertura 39 30 29
6 2024 Clausura 40 30 29
En lugar de tener esas tres columnas así, separadas por equipos, necesitamos colapsarlas en una única columna equipo, de la cual Saprissa, LDA y CSH sean sus tres posibles valores.
Dicha transformación la conseguiremos gracias a las funciones pivot_longer() y mutate(), ambas del paquete tidyverse (del paquete dplyr, en realidad, uno de los varios paquetes que vienen incorporados en tidyverse):
1df <- data |>
2 pivot_longer(
3 cols = c("Saprissa", "LDA", "CSH"),
4 names_to = "equipo",
5 values_to = "titulos"
) |>
mutate(
6 equipo = factor(
equipo,
levels = c("Saprissa", "LDA", "CSH")
)
)df.
equipo, un paso necesario para especificar el orden de los equipos (Saprissa > LDA > CSH) y del que no hará falta que profundicemos mucho más.
Lo que acabamos de hacer es transformar los datos: data y df tienen ahora estructuras distintas.
Es siempre aconsejable que conservés una copia intacta de los datos tal cual eran originalmente: data. De ahí que el resultado de la transformación a la que sometí a data lo asigné al nombre df.
Los data sets data y df son diferentes:
data son los datos originales, que voy a conservar por si más adelante necesito verlos y recordar cómo eran.
df son los datos que estoy manipulando y que seguiré manipulando.
Ahora los datos tienen esta forma (mucha atención a las columnas equipo y titulos, columnas que antes no existían):
print(df, n = nrow(df))# A tibble: 66 × 3
torneo equipo titulos
<chr> <fct> <dbl>
1 2013 Invierno Saprissa 29
2 2013 Invierno LDA 29
3 2013 Invierno CSH 23
4 2014 Verano Saprissa 30
5 2014 Verano LDA 29
6 2014 Verano CSH 23
7 2014 Invierno Saprissa 31
8 2014 Invierno LDA 29
9 2014 Invierno CSH 23
10 2015 Verano Saprissa 31
11 2015 Verano LDA 29
12 2015 Verano CSH 24
13 2015 Invierno Saprissa 32
14 2015 Invierno LDA 29
15 2015 Invierno CSH 24
16 2016 Verano Saprissa 32
17 2016 Verano LDA 29
18 2016 Verano CSH 25
19 2016 Invierno Saprissa 33
20 2016 Invierno LDA 29
21 2016 Invierno CSH 25
22 2017 Verano Saprissa 33
23 2017 Verano LDA 29
24 2017 Verano CSH 26
25 2017 Apertura Saprissa 33
26 2017 Apertura LDA 29
27 2017 Apertura CSH 26
28 2018 Clausura Saprissa 34
29 2018 Clausura LDA 29
30 2018 Clausura CSH 26
31 2018 Apertura Saprissa 34
32 2018 Apertura LDA 29
33 2018 Apertura CSH 27
34 2019 Clausura Saprissa 34
35 2019 Clausura LDA 29
36 2019 Clausura CSH 27
37 2019 Apertura Saprissa 34
38 2019 Apertura LDA 29
39 2019 Apertura CSH 28
40 2020 Clausura Saprissa 35
41 2020 Clausura LDA 29
42 2020 Clausura CSH 28
43 2020 Apertura Saprissa 35
44 2020 Apertura LDA 30
45 2020 Apertura CSH 28
46 2021 Clausura Saprissa 36
47 2021 Clausura LDA 30
48 2021 Clausura CSH 28
49 2021 Apertura Saprissa 36
50 2021 Apertura LDA 30
51 2021 Apertura CSH 29
52 2022 Clausura Saprissa 36
53 2022 Clausura LDA 30
54 2022 Clausura CSH 29
55 2022 Apertura Saprissa 37
56 2022 Apertura LDA 30
57 2022 Apertura CSH 29
58 2023 Clausura Saprissa 38
59 2023 Clausura LDA 30
60 2023 Clausura CSH 29
61 2023 Apertura Saprissa 39
62 2023 Apertura LDA 30
63 2023 Apertura CSH 29
64 2024 Clausura Saprissa 40
65 2024 Clausura LDA 30
66 2024 Clausura CSH 29
Esto es una transformación. La estructura de los datos ha cambiado: df tiene menos columnas y muchas más filas.
Las dimensiones originales eran estas:
dimensiones_originales # filas y columnas, en ese orden[1] 22 4
Las nuevas dimensiones son estas:
dim(df) # filas y columnas, en ese orden[1] 66 3
La transformación consistió en pasar de un data set ancho (data: 22 filas y 4 columnas) a uno largo (df: 66 filas y 3 columnas).
Además de una nueva columna equipo en lugar de las tres columnas Saprissa, LDA, CSH, ahora hay una columna que indica el número de titulos que cada equipo acumuló al finalizar cada torneo.
Por razones que no podré elaborar aquí, la estructura de los datos que hemos consolidado hasta este punto es la que más nos conviene a efectos de visualizarlos. La explicación pormenorizada está aquí.
df asume que Saprissa ganará el título Clausura 2024. Podemos explorar esa región del data set por medio del operador [], que nos permite hacer selecciones de filas y columnas con tan sólo especificar sus índices.
Cuando usamos el operador [], tal como vamos a mirar en el siguiente ejemplo, antes de la coma establecemos los índices de las filas que nos interesa seleccionar; después de la coma, los índices de las columnas:
## Filas de la 61 a la 63, columnas de la 1 a la 3
df[61:63, 1:3]# A tibble: 3 × 3
torneo equipo titulos
<chr> <fct> <dbl>
1 2023 Apertura Saprissa 39
2 2023 Apertura LDA 30
3 2023 Apertura CSH 29
Si dejamos un espacio vacío antes de la coma, R asumirá que queremos todas las filas; si lo dejamos después de la coma, asumirá que queremos todas las columnas:
## Filas de la 1 a la 3, todas las columnas
df[1:3, ]# A tibble: 3 × 3
torneo equipo titulos
<chr> <fct> <dbl>
1 2013 Invierno Saprissa 29
2 2013 Invierno LDA 29
3 2013 Invierno CSH 23
## Todas las filas, columna 1
## Pero R no me las va a imprimir todas
## Porque son un buen poco de filas D:
## Pero ahí se entiende el punto :D
df[, 1] # A tibble: 66 × 1
torneo
<chr>
1 2013 Invierno
2 2013 Invierno
3 2013 Invierno
4 2014 Verano
5 2014 Verano
6 2014 Verano
7 2014 Invierno
8 2014 Invierno
9 2014 Invierno
10 2015 Verano
# ℹ 56 more rows
Si especificamos sólo una fila y sólo una columna, esto sería básicamente como suministrarle a R las coordenadas exactas para seleccionar una casilla en específico.
Indexar habilita manipulaciones muy útiles. La casilla del título 40 del Deportivo Saprissa (en nuestro caso hipótetico, la que asumimos que ganará en el Clausura 2024) se extrae así:
df[64, 3]# A tibble: 1 × 1
titulos
<dbl>
1 40
Ese mismo código podemos aprovecharlo para introducir cambios. Digamos que el torneo Clausura 2024 no lo va a ganar el Deportivo Saprissa (quedaría entonces en 39 títulos) sino el Club Sport Herediano (pasaría a 30 títulos).
El operador [] nos da una mano para llevar a cabo los dos cambios anteriores. Para empezar, vamos a crear un data set diferente, df_heredia, de manera tal que df lo mantendremos como el data set en el que el Deportivo Saprissa gana el torneo Clausura 2024:
df_heredia <- df # Creamos df_heredia
df_heredia[64, 3] # Saprissa tiene 40, porque así está en df# A tibble: 1 × 1
titulos
<dbl>
1 40
df_heredia[64, 3] <- 39 # Lo bajamos a 39, porque esto es df_heredia
df_heredia[64, 3] # Verificamos que sean 39 ahora# A tibble: 1 × 1
titulos
<dbl>
1 39
df_heredia[66, 3] # CSH tiene 29, porque así está en df# A tibble: 1 × 1
titulos
<dbl>
1 29
df_heredia[66, 3] <- 30 # Lo subimos a 30, porque esto es df_heredia
df_heredia[66, 3] # Verificamos que sean 30 ahora# A tibble: 1 × 1
titulos
<dbl>
1 30
Job done! Ahora tenemos dos data sets aptos para nuestro ejercicio de visualización de datos. La función subset() nos asiste para emprender unas verificaciones finales.
df asume que el torneo Clausura 2024 lo ganará el Deportivo Saprissa:
subset(df, torneo == "2024 Clausura")# A tibble: 3 × 3
torneo equipo titulos
<chr> <fct> <dbl>
1 2024 Clausura Saprissa 40
2 2024 Clausura LDA 30
3 2024 Clausura CSH 29
df_heredia asume que el torneo Clausura 2024 lo ganará el Club Sport Herediano:
subset(df_heredia, torneo == "2024 Clausura")# A tibble: 3 × 3
torneo equipo titulos
<chr> <fct> <dbl>
1 2024 Clausura Saprissa 39
2 2024 Clausura LDA 30
3 2024 Clausura CSH 30
Con estos data sets debidamente preparados podemos pasar a la fase final y más interesante de este ejercicio: la visualización de datos.
R es excelente para visualizar datos. El paquete rey de la visualización de datos se llama ggplot2. Lo instalamos y cargamos al instalar y cargar tidyverse.
Hay dos características de ggplot2 que debemos conocer desde el principio:
Las visualizaciones las iremos construyendo por capas. Pronto veremos qué significa construir visualizaciones por capas.
El operador + nos permite ir agregando las capas; en cuanto su funcionamiento, los operadores |> (aquí lo hemos utilizado antes) y + hacen básicamente lo mismo, pero ggplot2 sólo acepta +.
Manos a la obra.
Vamos a crear un gráfico y se lo vamos a asignar al nombre plot9. Utilizando las funciones ggplot() y aes(), producimos esta primera capa:
1plot9 <- df |>
ggplot(aes(
2 x = factor(torneo, levels = unique(torneo)),
3 y = titulos,
4 group = equipo,
5 color = equipo
))
plot9df.
plot9 parece una visualización completamente vacía, pero si ponemos atención notaremos que los ejes X y Y están ocupados. De hecho, esta capa define todas las variables que van a dar sentido a la visualización en su conjunto.
Siguiente capa: plot8.
plot8 parte de plot9 y le agrega una capa en la que determinamos el tipo de gráfico que vamos a producir. En este caso, implementamos la función geom_line() pues nuestro objetivo es crear un gráfico lineal:
plot8 <- plot9 +
1 geom_line(linewidth = 2)
plot8linewidth determina el grosor de la línea).
plot8 ya no se ve vacío. Es un gráfico lineal, exactamente el tipo de gráfico que resulta adecuado para comunicar datos que evolucionan en el tiempo (eje X).
Son tres líneas porque así lo habíamos establecido en los argumentos group y color de la capa anterior: ambos los asociamos con la variable equipo, que abarca tres categorías.
Siguiente capa: plot7.
plot7 parte de plot8 y le añade una capa que dibuja una línea horizontal mediante la función geom_hline(). Esta es la misma línea horizontal, verde y punteada que observamos en el Gráfico A:
plot7 pone en su lugar la línea horizontal, verde y punteada. Falta, sin embargo, la etiqueta de texto que también observamos en el Gráfico A.
Siguiente capa: plot6.
plot6 parte de plot7 y le suma la etiqueta de texto, para lo cual implementamos la función annotate():
plot6 <- plot7 +
annotate(
1 geom = "label",
2 label = " La 40 ",
3 x = 19,
4 y = 40,
5 color = "#216839",
6 fill = "#ebf6ef",
7 fontface = "bold"
)
plot6plot6 cumple la misión de instalar en su lugar la etiqueta " La 40 ". Pero aún nos queda mucho por hacer y mejorar.
Entre otras cosas que requiere atención, plot6 mantiene un defecto: los colores de las líneas son los colores genéricos de ggplot2, colores que no transmiten todo lo que podrían comunicar colores mejor escogidos.
Siguiente capa: plot5.
plot5 parte de plot6 e introduce la importancia de la psicología del color. Implementando la función scale_color_manual() modificaremos los colores de modo muy intencional para propiciar ciertas conexiones mentales:
plot5 <- plot6 +
scale_color_manual(
1 values = c("#7b113d", "#cf1f25", "#ffc20f")
)
plot5plot5 logra que las líneas sean de un color u otro según cuál sea el equipo. Para elegir los colores, en este sitio web cargamos una imagen oficial tomada de las redes sociales de cada equipo y empleamos el extractor para identificar los códigos exactos de aquellos colores que constituyen su identidad cromática; en el caso del Deportivo Saprissa, por ejemplo, el tradicional morado vinotinto corresponde al código "#7b113d".
plot5 aprovecha al máximo la psicología del color. Ahora bien, los ejes son todavía muy mejorables.
Siguiente capa: plot4.
plot4 parte de plot5 y lo dedicamos a arreglar el eje Y por medio de la función scale_y_continuous(), que nos permite efectuar precisas modificaciones:
plot4 <- plot5 +
scale_y_continuous(
breaks = c(
1 pull(df[64, 3]),
2 pull(df[65, 3]),
3 pull(df[66, 3])
),
4 limits = c(23, 40),
5 position = "right"
)
plot4plot4 ordena el eje Y, en efecto, pero el eje X continúa siendo un desastre. Es más, el eje X ni siquiera se puede leer pues las marcas están todas superpuestas.
Siguiente capa: plot3.
plot3 parte de plot2 y ordena el eje X mediante la función scale_x_discrete(), que nos permite atacar el evidente problema de saturación (ni siquiera hace falta dejar una marca para cada torneo):
plot3 <- plot4 +
scale_x_discrete(
1 breaks = c("2014 Verano", "2024 Clausura"),
2 labels = c("2014 \nVerano", "2024 \nClausura")
)
plot3df.
plot3 limita las marcas a aquellas que corresponden a los torneos Verano 2014 y Clausura 2024, ya que esos son los dos torneos que fijan la década en la cual la Liga Deportiva Alajuelense experimentó su debable deportiva.
Es un notable paso hacia adelante, pero los títulos de los ejes son aún mejorables; además, al gráfico le falta título y subtítulo.
Siguiente capa: plot2.
plot2 parte de plot3 y arregla los títulos del gráfico, tanto los generales como los de los ejes. Para que no se nos haga demasiado bulto en el código, los textos definámoslos por adelantado:
titulo <- "(A) Si el Deportivo Saprissa gana el Clausura 2024"
subtitulo <- "Conseguiría el título 40, diez más que la LDA"
nota <- 'Fuente: "Vas a aprender visualización de datos y programación en R con este gráfico futbolero"'Ahora sí, podemos efectuar los cambios gracias a la función labs():
plot2 <- plot3 +
labs(
1 title = titulo,
2 subtitle = subtitulo,
3 caption = nota,
4 x = "",
5 y = ""
)
plot2plot2 se empieza a mirar bastante aceptable. Ahora bien, el fondo gris del gráfico es genérico y muy feo. Lo vamos a cambiar.
Siguiente capa: plot1.
plot1 parte de plot2 y retoca el aspecto general del gráfico; lo hacemos escogiendo alguna estética de las que ggplot2 dispone, como theme_classic():
plot1 <- plot2 +
theme_classic()
plot1plot1 elimina el fondo gris, entre otros retoques estéticos menores pero que todos juntos armonizan muy gratamente el aspecto general del gráfico.
No podríamos hablar de un producto final, sin embargo, mientras no incrementemos el tamaño de la letra.
Siguiente capa: plot.
plot parte de plot1 y desarrolla una amplia gama de mejoras: vamos a hacer más grande el texto, cambiar la fuente, llevar la tabla de las leyendas al lado izquierdo, entre otros arreglos que la función theme() en conjunto con la función element_text() hacen realidad:
plot <- plot1 +
theme(
1 plot.title = element_text(size = 22, face = "bold"),
2 plot.subtitle = element_text(size = 20),
3 plot.caption = element_text(size = 14),
4 axis.text.x = element_text(size = 12, face = "bold"),
5 axis.text.y = element_text(size = 12, face = "bold"),
6 legend.position = "left",
7 legend.title = element_blank(),
8 legend.text = element_text(size = 18),
9 text = element_text(family = "sans")
)
plotplot es el producto final: una copia exacta del Gráfico A. Hemos ilustrado así el proceso completo de cómo crear una visualización efectiva utilizando el paquete ggplot2.
😎
Algunas buenas prácticas de visualización de datos que es importante resaltar:
Nuestros datos refieren a la evolución de un evento (la obtención de títulos) a lo largo del tiempo. Por ende, escogimos un gráfico lineal que desarrolla la variable temporal a lo largo del eje X.
Si al gráfico le metíamos mucha cosa, lo arruinábamos. Particularmente los ejes los diseñamos de modo muy minimalista, ambos con unas poquitas marcas apenas. La etiqueta del título 40 probablemente no era necesaria, salvo para ilustrar que añadir etiquetas es posible.
El gráfico tiene un título y un subtítulo que se refuerzan mutuamente. Damos por un hecho que el público meta está familiarizado con el fútbol de Costa Rica. Si no fuera el caso, ajustes adicionales serían necesarios.
Cada equipo es representado por uno de sus colores icónicos. Son sus colores exactos; lo son porque tuvimos el cuidado de buscar los códigos de color respectivos.
El gráfico ni siquiera tiene títulos para los ejes. Confiamos en que el título, el subtítulo y, sobre todo, las marcas en cada eje bastan para saber de qué se trata cada uno. Los límites de los ejes no alargan ni achican la visualización.
En este caso, la tabla de leyendas está ordenada de un modo consistente con el orden mismo que refleja el gráfico; la ubicamos a la izquierda para que dicha conexión sea aún más cercana a las líneas de cada equipo (el eje Y no queda en medio).
Es frecuente topar con visualizaciones ilegibles debido a que el tamaño de la letra es inadecuado. Un ajuste así de elemental puede hacer toda la diferencia.
Los distintos elementos hay que repartirlos a lo largo y ancho del gráfico de la forma más balanceada posible: si el eje Y cae a la derecha, como en este caso, la tabla de leyendas compensa a la izquierda.
Las visualizaciones efectivas requieren elaboración y reelaboración, y extrema atención al detalle. Si a este mismo gráfico lo volvemos a revisar la próxima semana con plena seguridad le encontraremos numerosas oportunidades de mejora.
Por hoy, así queda.
Hemos utilizado el data set df para reproducir el Gráfico A. También habíamos creado el data set df_heredia, con base en el cual es posible reproducir el Gráfico B.
df_heredia, tan parecido como sea posible al Gráfico B.Edwin Alvarado-Mena. PhD Student & Data Science Ambassador at the University of Arizona, USA. Programming literacy advocate and Certified Carpentries Instructor, he is also pursuing the Master’s degree in Data Science and the Computational Social Science Graduate Certificate.