[1] "HOLA"
Pre-introducción a R
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.
¡No hace falta instalar R!
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 ni RStudio instalados en tu computadora.
Programar en R consiste en aplicar funciones a objetos:
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
:
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()
:
[1] "hola" "hello" "olá" "hallo"
[1] 1 100 4 55 10
Una vez concatenados los objetos, los pasamos a la función que deseemos:
[1] "HOLA" "HELLO" "OLÁ" "HALLO"
[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:
[1] "HOLA" "HELLO" "OLÁ" "HALLO"
[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
- Concatena los objetos.
- 2
- Los convierte en mayúsculas.
- 3
- Los ordena.
[1] "HALLO" "HELLO" "HOLA" "OLÁ"
- 1
- Concatena los objetos.
- 2
- Les saca raíz cuadrada.
- 3
- Los ordena.
- 4
- Escoge el mayor.
[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.
Comentarios directo en el código
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:
[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:
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.
La documentación de las funciones es esencial
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:
[1] 10 11 12 13 14 15 16 17 18 19 20
[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, es conveniente abordar sólo un argumento por línea:
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:
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()
:
El segundo paso es cargarlo usando la función library()
:
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.
Manipulación de datos
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.
El origen de los datos
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()
:
Si queremos imprimirlos, usamos la función print()
:
# 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:
# 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, n = 10
). O sea, bien podríamos combinar ambas funciones para lograr que R imprima todas las filas:
# 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
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 los 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 originalmente) y asume que el Deportivo Saprissa lo ganará; este es el escenario hipotético al que se refiere el Gráfico A visto al principio, y recordemos que nuestro objetivo es reproducir ese Gráfico A.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; esta es la historia que el Gráfico A comunica.
Por cierto, si necesitamos contar las columnas de data
en lugar de las filas, la función ncol()
hará el trabajo:
La función dim()
devuelve el número de filas y de columnas, en ese orden:
Podemos salvar el resultado anterior con el ya conocido operador <-
, por ejemplo, asignando ese objeto al nombre dimensiones_originales
.
Recordemos que la asignación hecha mediante <-
crea el objeto en la memoria de nuestra computadora para que lo podamos usar repetidamente, pero no lo imprime:
Hay dos opciones para imprimir el objeto:
[1] 22 4
[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:
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
.
Vamos a imprimir unas cuantas filas para recordar cómo se ve. Las funciones head()
y tail()
imprimen las primeras y las últimas filas, respectivamente:
# 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
# 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
A efectos de visualizar estos datos, 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 mediante las funciones pivot_longer()
y mutate()
, ambas del paquete tidyverse
(de los paquetes dplyr
y tidyr
, en realidad, dos de los varios paquetes que están incorporados en tidyverse
).
Vamos a analizar la transformación paso por paso:
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")
)
)
- 1
-
Asigna el objeto (el objeto que resulta de todo este bloque de código) al nombre
df
. - 2
- Lo convierte de ancho a largo (es decir, colapsamos columnas).
- 3
- Indica las columnas que dejarán de serlo (es decir, las que vamos a colapsar en una única columna).
- 4
- Indica el nombre de la (nueva) columna a la que irán los nombres de las columnas anteriores.
- 5
- Indica el nombre de la (nueva) columna a la que irán los valores de las columnas anteriores.
- 6
-
Convierte en factor la (nueva) columna
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 y almacenarlos en un objeto distinto: df
.
data
y df
tienen ahora estructuras distintas.
Los datos originales hay que conservarlos
Es siempre aconsejable que conservés una copia intacta de los datos tal cual eran originalmente, 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.
Los datos en df
tienen esta forma (mucha atención a las columnas equipo
y titulos
, columnas que no existen en data
):
# 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 que data
.
Las dimensiones originales eran estas:
Las nuevas dimensiones son estas:
En otras palabras, 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 existe una columna que indica el número de titulos
que cada equipo acumuló al finalizar cada torneo
.
Tidy data
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 técnica y pormenorizada la podés leer 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:
# 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 no indicamos nada después de la coma, R asumirá que queremos todas las columnas:
# 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
Si no indicamos nada antes de la coma, R asumirá que queremos todas las filas:
## Todas las filas, columna 1
## Pero R no me las va a imprimir todas
## Me va a imprimir sólo 10 filas
## Porque son un buen poco de filas D:
## Pero creo que 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 es básicamente como suministrarle a R las coordenadas exactas para seleccionar una casilla específica:
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í:
La indexación habilita manipulaciones muy útiles. Podemos, por ejemplo, introducir cambios.
Digamos que el torneo Clausura 2024 no lo gana 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 asumimos que el Deportivo Saprissa ganará el torneo Clausura 2024:
En este instante, df_heredia
es una copia de df
, así que no hay ninguna sorpresa si el Deportivo Saprissa sigue apareciendo con 40 títulos:
# A tibble: 1 × 3
torneo equipo titulos
<chr> <fct> <dbl>
1 2024 Clausura Saprissa 40
Vamos a bajarlo a 39 títulos usando la indexación:
# A tibble: 1 × 3
torneo equipo titulos
<chr> <fct> <dbl>
1 2024 Clausura Saprissa 39
Listo. Lo siguiente es sumarle un título más al Club Sport Herediano, que actualmente aparece con 29:
Vamos a subirlo a 30 títulos usando la indexación:
# A tibble: 1 × 3
torneo equipo titulos
<chr> <fct> <dbl>
1 2024 Clausura CSH 30
Ahora tenemos dos data sets aptos para nuestro ejercicio de visualización de datos:
df
es el data set para reproducir el Gráfico A visto al principio, el que asume que el Deportivo Saprissa obtiene el torneo Clausura 2024.df_heredia
es el data set para reproducir el Gráfico B visto al principio, el que asume que el Club Sport Herediano obtiene el torneo Clausura 2024.
La función subset()
nos ayudará a realizar unas verificaciones finales. Básicamente, esta función sirve para filtrar los datos.
df
asume que el torneo Clausura 2024 será el título 40 del Deportivo Saprissa:
# 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 será el título 30 del Club Sport Herediano:
# 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
Job done!
Con estos data sets debidamente producidos podemos pasar a la fase final y más interesante de este ejercicio: la visualización de datos.
Visualización de datos
R es excelente para visualizar datos. El paquete más popular para visualización de datos en R 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 vamos a ir construyendo por capas. Pronto veremos qué significa construir visualizaciones “por capas”.
El operador
+
nos permite ir agregando las capas. Los operadores|>
(aquí lo hemos utilizado antes) y+
hacen básicamente lo mismo, peroggplot2
sólo acepta+
.
Manos a la obra. Es hora de reproducir el Gráfico A que vimos al principio del post.
Vamos a crear un gráfico y se lo vamos a asignar al nombre plot9
. Utilizando las funciones ggplot()
y aes()
, producimos la primera capa a la que le iremos agregando otras capas hasta nuestro producto final:
1plot9 <- df |>
ggplot(aes(
2 x = factor(torneo, levels = unique(torneo)),
3 y = titulos,
4 group = equipo,
5 color = equipo
))
plot9
- 1
-
Indica los datos a visualizar, o sea,
df
. - 2
- Indica la variable que corresponde al eje X (requirió una transformación que no es necesario profundizar).
- 3
- Indica la variable que corresponde al eje Y.
- 4
- Indica la variable que más adelante usaremos para agrupar.
- 5
- Indica la variable que más adelante usaremos para colorear.
plot9
parece una visualización completamente vacía, pero si ponemos atención notaremos que los ejes X y Y ya 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)
plot8
- 1
-
Crea un gráfico lineal (el argumento
linewidth
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:
- 1
- Indica dónde se ubica la línea horizontal respecto al eje Y.
- 2
- Indica el color de la línea.
- 3
- Indica el tipo de línea.
plot7
instala 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"
)
plot6
- 1
- Indica el tipo de etiqueta.
- 2
- Indica el texto.
- 3
- Indica dónde se ubica la etiqueta respecto al eje X.
- 4
- Indica dónde se ubica la etiqueta respecto al eje Y.
- 5
- Indica el color de los bordes de la etiqueta.
- 6
- Indica el color del relleno de la etiqueta.
- 7
- Indica el énfasis del texto (en negrita).
plot6
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 requieren atención, plot6
tiene un defecto importante: 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. Con 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")
)
plot5
- 1
- Establece los colores icónicos de cada equipo.
plot5
consigue que las líneas sean de un color u otro según cuál sea el equipo. Para elegir los colores con exactitud, 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 de aquellos colores que definen su identidad cromática. En el caso del Deportivo Saprissa, por ejemplo, su tradicional morado vinotinto tiene el código de color "#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"
)
plot4
- 1
- Establece la marca (es decir, el número de títulos) que acumula el Deportivo Saprissa en el último torneo de la serie.
- 2
- Establece la marca (es decir, el número de títulos) que acumula la Liga Deportiva Alajuelense en el último torneo de la serie.
- 3
- Establece la marca (es decir, el número de títulos) que acumula el Club Sport Herediano en el último torneo de la serie.
- 4
- Determina los límites del eje Y.
- 5
- Determina la posición del eje Y, que trasladamos a la derecha.
plot4
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")
)
plot3
- 1
- Indica las marcas del eje X, que limitamos a dos únicamente.
- 2
-
Modifica los nombres de las marcas del eje X, que no son exactamente iguales a como aparecen en el data set
df
.
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:
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 = ""
)
plot2
- 1
- Establece el título.
- 2
- Establece el subtítulo.
- 3
- Establece la nota al pie.
- 4
- Establece el título del eje X (lo dejamos vacío).
- 5
- Establece el título del eje Y (lo dejamos vacío).
plot2
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
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")
)
plot
- 1
- Retoca el tamaño y el énfasis del título.
- 2
- Retoca el tamaño del subtítulo.
- 3
- Retoca el tamaño de la nota al pie.
- 4
- Retoca el tamaño y el énfasis de las marcas del eje X.
- 5
- Retoca el tamaño y el énfasis de las marcas del eje Y.
- 6
- Traslada la tabla de leyendas a la izquierda.
- 7
- Desaparece el título de la tabla de leyendas.
- 8
- Retoca el tamaño del texto de la tabla de leyendas.
- 9
- Establece la fuente para todo el gráfico.
plot
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
.
Buenas prácticas
Algunas buenas prácticas de visualización de datos que es importante resaltar:
- Escoger el tipo de visualización correcto según la historia que encierran los datos. 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.
- Limitar los elementos de la visualización a sólo los estrictamente necesarios. 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. La incluí sólo para ilustrar que añadir etiquetas es posible.
- Incluir títulos y subtítulos que permitan a la visualización explicarse por sí sola. 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.
- Explotar la psicología del color siempre que sea posible. Cada equipo es representado por uno de sus colores icónicos. Son sus colores exactos. Lo podemos garantizar porque tuvimos el cuidado de buscar los códigos de color respectivos.
- Evitar la sobrecarga de texto y la desproporción en los ejes X y Y. 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.
- Procurar que el orden de la tabla de leyendas sea consistente con lo que la visualización transmite. 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).
- Ajustar el texto para que las personas puedan leerlo sin esfuerzo. 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.
- Preservar el balance de la visualización. 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 la coloco a la izquierda para compensar.
- Buscar la crítica. 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.
Práctica
Hemos utilizado el data set df
para reproducir el Gráfico A visto al principio del post. También habíamos creado el data set df_heredia
, con base en el cual es posible reproducir el Gráfico B.
- Crear un gráfico lineal con base en
df_heredia
, tan parecido como sea posible al Gráfico B.
Cite my blog posts as follows:
Alvarado-Mena, Edwin. (Year, Month Date). Title. AlvaradoCSS. URL.
Any strong opinions about this post?
Please let me know! I take your feedback very seriously.
Photo by Click and Boo on Unsplash