Presentación

Esta publicación tiene dos objetivos, uno personal y otro general. El objetivo personal es poder tener un apunte en limpio sobre el uso de funciones personalizadas con R, pues estas permiten automatizar tareas comunes de una manera más eficiente que copiar y pegar. El segundo objetivo, el general, es simplemente compartir con todo aquel que guste de analizar datos y tampoco esté familiarizado con el uso de funciones

No asumo que mi código sea el más eficiente o esté libre de errores, así que cualquier corrección o comentario házmelo saber por este medio o a través de mi twitter ecodiegoale.

Si te interesa profundizar sobre la programación de funciones, te recomiendo leer R for Data Science de H. Wickham y G. Grolemund, disponible en este enlace.

Además, nunca está de más revisar stackoverflow, github, rpubs o preguntarle a ChatGPT.

Aviso: Si algún link no abre con click izquierdo, trata con el derecho y abrir en otra ventana o pestaña.

Contenido

En este blog se hará uso de la Jordà-Schularick-Taylor Macrohistory Database (JSTMD) de una manera muy sencilla utilizando principalmente la librería de tidyverse. Lo que haremos será calcular la inflación y tasa de interés real de largo plazo para todos los países disponibles y escribir una función que automatice la elaboración de las gráficas de las variables calculadas. La inspiración de este blog surgió tras la lectura del borrador del próximo libro de Santiago Capraro, Carlo Panico y LD Torres, Inequality and Stagnation: A monetary interpretation, sin duda un gran libro de macroeconomía heterodoxa.

Para más información sobre la JSTMD revisar el siguiente enlace.

Preparación

Lo primero será definir tu directorio de trabajo y cargar las librerías necesarias para el análisis.

#setwd("C:/Users/tu carpeta")

rm(list = ls()) #para borrar todos los objetos en la memoria de la consola
options(scipen=999) #para desactivar la notación científica

#Si no cuentas con los paquetes, usa el comando install.packages("ejemplo")

library(tidyverse)
library(haven)
library(scales)
library(ggtext)

Conforme avancemos en el blog haré mención de cuando estemos usando las librerías cargadas.

Base de datos: Cómo descargar la JSTMD

Hay varias formas de cargar una base de datos en tu ambiente de trabajo, la más común es descargar el archivo y guardarlo en el mismo directorio donde está tu script y posteriormente llamarla de alguna forma.

En este blog no lo haré así. Lo que haré será bajar directamente de su página la JSTMD y cargarla.

Vamos paso a paso. Primero, entramos al sitio y copiamos el enlace que proporcionan para bajar la base desde STATA. Lo sé, estamos usando R, pero igual funciona (¡viva el software abierto!).

url <- "https://www.macrohistory.net/app/download/9834512469/JSTdatasetR6.dta?t=1687773948"

Ahora, descargo el archivo desde la URL:

download.file(url, "datos.dta", mode = "wb")
#wb se refiere a "write binary". Este modo es comúnmente utilizado al descargar archivos binarios, como datos de Stata, para asegurarse de que los datos se manejen correctamente en binario.

Lo que hizo esta función fue descargar el archivo directamente desde la URL que proporcionamos y guardarlo con el nombre de datos.dta en el directorio de trabajo donde estamos trabajando. La extensión .dta es porque son datos para STATA.

Si abres la carpeta donde guardaste tu script encontrarás el archivo datos.dta.

Ya con el archivo descargado y almacenado en la misma carpeta, sigue cargarlo a R.

Para conseguirlo usaremos una función de la librería haven.

datos <- haven::read_dta("datos.dta")

Vamos a dar una mirada rápida a la estructura de nuestros datos:

glimpse(datos)
## Rows: 2,718
## Columns: 59
## $ year                     <dbl> 1870, 1871, 1872, 1873, 1874, 1875, 1876, 187…
## $ country                  <chr> "Australia", "Australia", "Australia", "Austr…
## $ iso                      <chr> "AUS", "AUS", "AUS", "AUS", "AUS", "AUS", "AU…
## $ ifs                      <dbl> 193, 193, 193, 193, 193, 193, 193, 193, 193, …
## $ pop                      <dbl> 1775, 1675, 1722, 1769, 1822, 1874, 1929, 199…
## $ rgdpmad                  <dbl> 3273.239, 3298.507, 3553.426, 3823.629, 3834.…
## $ rgdpbarro                <dbl> 13.83616, 13.93686, 15.04425, 16.21944, 16.26…
## $ rconsbarro               <dbl> 21.44973, 19.93080, 21.08501, 23.25491, 23.45…
## $ gdp                      <dbl> 208.78, 211.56, 227.40, 266.54, 287.58, 300.7…
## $ iy                       <dbl> 0.1092656, 0.1045791, 0.1304380, 0.1249862, 0…
## $ cpi                      <dbl> 2.708333, 2.666667, 2.541667, 2.541667, 2.666…
## $ ca                       <dbl> -6.1475940, 5.2607740, 7.8676360, -11.0478328…
## $ imports                  <dbl> 36, 34, 38, 49, 49, 50, 48, 52, 52, 48, 46, 5…
## $ exports                  <dbl> 37, 46, 53, 50, 54, 52, 51, 51, 50, 44, 59, 5…
## $ narrowm                  <dbl> 23.3, 27.2, 36.2, 38.6, 37.9, 37.5, 38.2, 40.…
## $ money                    <dbl> 54.3, 59.5, 68.5, 73.7, 79.3, 88.5, 95.8, 105…
## $ stir                     <dbl> 4.88, 4.60, 4.60, 4.40, 4.50, 4.60, 4.60, 4.5…
## $ ltrate                   <dbl> 4.911817, 4.844633, 4.737350, 4.671958, 4.653…
## $ hpnom                    <dbl> 0.4922526, 0.4698775, 0.4847942, 0.4698775, 0…
## $ unemp                    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ wage                     <dbl> 0.4481183, 0.4394170, 0.4524690, 0.4916250, 0…
## $ debtgdp                  <dbl> 0.172568, 0.191799, 0.154920, 0.142692, 0.194…
## $ revenue                  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ expenditure              <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ xrusd                    <dbl> 0.3669461, 0.3691464, 0.3692388, 0.3624048, 0…
## $ tloans                   <dbl> 54.792, 53.748, 55.822, 65.380, 71.478, 79.95…
## $ tmort                    <dbl> 1.680, 1.766, 1.470, 1.364, 1.434, 1.564, 1.8…
## $ thh                      <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ tbus                     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ bdebt                    <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ lev                      <dbl> 25.01666, 23.98300, 22.44565, 21.00500, 20.97…
## $ ltd                      <dbl> 119.83204, 107.03788, 94.90955, 101.70810, 10…
## $ noncore                  <dbl> 32.92877, 30.21366, 26.86041, 28.64502, 28.29…
## $ crisisJST                <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ crisisJST_old            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ peg                      <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ peg_strict               <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ peg_type                 <chr> "PEG", "PEG", "PEG", "PEG", "PEG", "PEG", "PE…
## $ peg_base                 <chr> "GBR", "GBR", "GBR", "GBR", "GBR", "GBR", "GB…
## $ JSTtrilemmaIV            <dbl> NA, -0.42, 1.27, 0.59, -1.08, -0.51, -1.01, 0…
## $ eq_tr                    <dbl> -0.004903895, 0.110192969, 0.176749304, 0.151…
## $ housing_tr               <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ bond_tr                  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ bill_rate                <dbl> 0.0488, 0.0460, 0.0460, 0.0440, 0.0450, 0.046…
## $ rent_ipolated            <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ housing_capgain_ipolated <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ housing_capgain          <dbl> NA, -0.045456301, 0.031746607, -0.030769771, …
## $ housing_rent_rtn         <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ housing_rent_yd          <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ eq_capgain               <dbl> -0.07004543, 0.04165363, 0.10894547, 0.083086…
## $ eq_dp                    <dbl> 0.07141703, 0.06546638, 0.06299735, 0.0644841…
## $ eq_capgain_interp        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ eq_tr_interp             <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ eq_dp_interp             <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ bond_rate                <dbl> 0.04911817, 0.04844633, 0.04737350, 0.0467195…
## $ eq_div_rtn               <dbl> 0.06641459, 0.06819329, 0.06986062, 0.0698419…
## $ capital_tr               <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ risky_tr                 <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ safe_tr                  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…

Es un dataframe de 2,718 observaciones y 59 variables. Algunas variables tienen nombres muy evidentes, otras no. Para conocer a detalle te recomiendo leer la documentación. Para acceder a ella podemos descargarla de la misma manera que la base de datos.

url <- "https://www.macrohistory.net/app/download/9834516169/JST_documentationR6.pdf?t=1676279836"
download.file(url, "documentacion.pdf", mode = "wb")

Ahora, si revisas la carpeta de tus documentos encontrarás el archivo PDF de la documentación, donde se aclara qué es cada variable y sus fuentes.

#Voy a borrar el archivo .dta porque ya no lo usaremos. También borraré el objeto "url". 
file.remove("datos.dta")
## [1] TRUE
rm("url")

Exploración de la base de datos

Ya hemos dado un primer vistazo a nuestra base utilizando la función glimpse().

Ahora veremos el periodo que abarca la base y los países incluidos:

unique(datos$country)
##  [1] "Australia"   "Belgium"     "Canada"      "Switzerland" "Germany"    
##  [6] "Denmark"     "Spain"       "Finland"     "France"      "UK"         
## [11] "Ireland"     "Italy"       "Japan"       "Netherlands" "Norway"     
## [16] "Portugal"    "Sweden"      "USA"

La base incluye a 18 países (desarrollados).

unique(datos$year)
##   [1] 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884
##  [16] 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899
##  [31] 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914
##  [46] 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929
##  [61] 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944
##  [76] 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959
##  [91] 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974
## [106] 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989
## [121] 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004
## [136] 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019
## [151] 2020

Para el periodo de 1870-2020 con frecuencia anual.

Variables de interés

Como dije al inicio de este blog, el objetivo es calcular la tasa de interés real de largo plazo y la inflación.

La inflación no es más que la tasa de crecimiento de los precios. En este caso, los precios están expresados como un índice de precios al consumidor donde 1990=100. Podemos expresar la inflación de la siguiente manera:

\(\pi_{it}= \frac{P_{it}}{P_{it-1}}-1\)

La expresión anterior no es más que una tasa de crecimiento. Donde \(\pi_{it}\) es la inflación del país i en el periodo t, \(P_{it}\) el nivel o índice de precios del país i en el periodo t y \(P_{it-1}\) es el mismo índice pero del periodo inmediato anterior.

En la JSTMD el índice de precios viene codificado por la variable cpi.

Ahora, la definición de la tasa de interés real es un poco más complicada que la de la inflación.

La definición clásica está dada por la Ecuación de Fisher, la cual se expresa de la siguiente manera:

\(r_{t}= i_t-\pi^E_t\)

Donde \(r_t\) es la tasa de interés real, \(i_t\) la tasa de interés nominal y \(\pi^E_t\) la tasa de inflación esperada.

This equation says that the real interest rate is simply the nominal interest adjusted for expected inflation. It is the real interest rate that is most important for investment and saving decisions, as it represents the true cost of borrowing (and the true return on saving).” (Carlin and Soskice, 2024, p. 29).

Lo anterior nos dice que necesitaríamos contar con un indicador de inflación esperada, que refleje las expectativas de las empresas sobre la inflación futura. Estas expectativas son las que influyen en las decisiones de inversión de las firmas, y, como resultado, impactan en los niveles de producción y empleo.

Lamentablemente, la JSTMD no cuenta con un indicador parecido, nosotros hemos calculado la inflación observada, no la esperada, sin embargo y dado que no es el objetivo de esta entrada, omitiremos este detalle y calcularemos la tasa de interés real como la diferencia entre la tasa de interés de largo plazo (ltrate) y la inflación simplemente. Para introducirse en la discusión de las cuestiones de la tasa de interés real recomiendo el libro Macroeconomics. Institutions, Instability, and Inequality de Wendy Carlin y David Soskice. También recomiendo este hilo de twitter (nunca diré X) de Jamel Sandoval, donde se ofrecen elementos más precisos sobre este tema.

No te recomiendo buscar el libro de Carlin y Soskice en Library Genesis donde, si bien solo se encuentra la edición anterior, está de manera gratuita.

Aclaradas las cuestiones teóricas, proseguimos.

datos <- datos %>%
  group_by(country) %>%
  mutate(
    inf = ((cpi / lag(cpi)) -1)*100,
    lt_r = ltrate - inf
  ) 

El código anterior utiliza algunos verbos y funciones comunes de tidyverse. Primero usamos group_by(), esta función agrupa el dataframe por la columna country. Esto significa que las operaciones subsiguientes se realizarán por cada grupo definido por valores únicos en la columna country (por los países). Luego, mutate() se utiliza para crear o modificar columnas en el dataframe. Se crea una nueva columna llamada inf. Esta columna calcula la tasa de inflación para cada observación en el grupo. La fórmula toma el índice de precios al consumidor observado (cpi) , lo divide por el valor del cpi en la observación anterior (utilizando lag()) y le resta 1. Luego, se multiplica por 100 para expresar la tasa como un porcentaje. Por último, se crea otra nueva columna llamada lt_r. Esta columna calcula la tasa de interés real a largo plazo. Resta la tasa de inflación calculada anteriormente de la tasa de interés nominal de largo plazo (ltrate).

Ahora, una nota importante. En la documentación se aclara que la variable ltrate viene expresada en términos porcentuales, es decir, ya viene multiplicada por 100, sin embargo, personalmente yo prefiero trabajar con las variables en nivel, no en porcentaje, por lo que dividiré entre 100 las variables que usaremos.

datos <- datos %>%
  mutate(
  inf =  inf/100,
  lt_r = lt_r/100
  ) %>%
  ungroup()

Una vez hecho lo anterior, siempre recomiendo observar cómo lucen nuestras variables:

head(datos %>%
  group_by(country) %>%
  select(year, country, inf) %>%
  filter(year > 1979) %>%
  pivot_wider(names_from = year,
              values_from = inf))
## # A tibble: 6 × 42
## # Groups:   country [6]
##   country     `1980` `1981` `1982` `1983` `1984` `1985`   `1986`  `1987` `1988`
##   <chr>        <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>    <dbl>   <dbl>  <dbl>
## 1 Australia   0.101  0.0969 0.111  0.101  0.0395 0.0674  0.0908  0.0849  0.0723
## 2 Belgium     0.0665 0.0763 0.0872 0.0767 0.0634 0.0487  0.0130  0.0155  0.0116
## 3 Canada      0.102  0.125  0.108  0.0582 0.0434 0.0395  0.0417  0.0436  0.0402
## 4 Switzerland 0.0402 0.0649 0.0566 0.0297 0.0292 0.0344  0.00752 0.0145  0.0186
## 5 Germany     0.0542 0.0633 0.0527 0.0328 0.0241 0.0218 -0.00125 0.00242 0.0127
## 6 Denmark     0.123  0.117  0.101  0.0691 0.0629 0.0468  0.0368  0.0399  0.0456
## # ℹ 32 more variables: `1989` <dbl>, `1990` <dbl>, `1991` <dbl>, `1992` <dbl>,
## #   `1993` <dbl>, `1994` <dbl>, `1995` <dbl>, `1996` <dbl>, `1997` <dbl>,
## #   `1998` <dbl>, `1999` <dbl>, `2000` <dbl>, `2001` <dbl>, `2002` <dbl>,
## #   `2003` <dbl>, `2004` <dbl>, `2005` <dbl>, `2006` <dbl>, `2007` <dbl>,
## #   `2008` <dbl>, `2009` <dbl>, `2010` <dbl>, `2011` <dbl>, `2012` <dbl>,
## #   `2013` <dbl>, `2014` <dbl>, `2015` <dbl>, `2016` <dbl>, `2017` <dbl>,
## #   `2018` <dbl>, `2019` <dbl>, `2020` <dbl>

El código anterior acomoda nuestra base de datos de la siguiente manera, en las columnas tenemos los años y en las filas los países, la variable observada es la inflación que calculamos. Para más detalle del código anterior te recomiendo copiar y pegarlo en ChatGPT y pedirle que te explique línea por línea lo que acabo de hacer. Puedes darle un poco de contexto antes y especificarle que estoy usando R o tidyverse.

Ahora calculo un cuadro resumen con la inflación y tasa de interés de largo plazo promedio para el periodo 1980-2020.

datos %>%
  group_by(country) %>%
  filter(year > 1979) %>%
  summarise(
    inf.avrg = (mean(inf, na.rm = TRUE))*100,
    lt_r.avrg = (mean(lt_r, na.rm = TRUE))*100,
  )
## # A tibble: 18 × 3
##    country     inf.avrg lt_r.avrg
##    <chr>          <dbl>     <dbl>
##  1 Australia      3.97       3.59
##  2 Belgium        2.69       3.19
##  3 Canada         3.08       3.52
##  4 Denmark        2.93       3.63
##  5 Finland        3.13       3.21
##  6 France         2.96       3.05
##  7 Germany        2.07       2.66
##  8 Ireland        3.74       3.26
##  9 Italy          4.61       3.36
## 10 Japan          0.965      1.95
## 11 Netherlands    2.23       2.77
## 12 Norway         3.69       2.66
## 13 Portugal       6.62       2.29
## 14 Spain          4.54       3.09
## 15 Sweden         3.38       3.07
## 16 Switzerland    1.59       1.43
## 17 UK             3.40       2.90
## 18 USA            3.00       2.90

Gráfica de Estados Unidos y Canadá

En la sección anterior calculamos las variables que nos interesan, además estimamos unas medias simples. Ahora lo que queremos es graficar y observar su evolución en el tiempo. Vamos paso a paso.

Definición del tema

En ggplot2, un tema se refiere a un conjunto de características visuales que determinan el aspecto y la apariencia general de un gráfico. Los temas son una forma de personalizar el estilo de tus gráficos y hacer que se vean de la manera que desees. Puedes aplicar un tema a todo tu gráfico o modificar elementos específicos del tema según sea necesario. También puedes usar temas predefinidos. A continuación yo definiré mis propios visuales. Para más información puedes revisar mis blogs sobre ggplot2 aquí y aquí. Igualmente te recomiendo que copies y pegues mi tema en una conversación con ChatGPT y le pidas te explique cada elemento. Por ahora, únicamente me detendré a explicar plot.subtitle().

La forma en que utilizo plot.subtitle() y ggtext está basada e inspirada en el código de Albert Rapp.

El objetivo es mejorar la presentación de gráficos al reemplazar una leyenda grande con colores en las palabras del título.

theme_jstd <- theme(panel.background = element_blank(),
      panel.border = element_blank(),
      legend.position = "none",
      axis.line.x = element_blank(),
      axis.line = element_line(
        colour = "black", 
        linewidth= 0.8),
      axis.ticks = element_blank(),
      axis.text.y = element_text(
        colour = "black",
        size = 10),
      axis.text.x = element_text(
        colour = "black",
        size = 10),
      axis.title.y = element_blank(),
      axis.title.x = element_text(size = 10),
      legend.key = element_blank(), #quita el fondo gris de las leyendas
      panel.grid.major.y =element_line(
        color = '#D7D6D6',
        linewidth = 0.8),
      panel.grid.minor.y = element_line(
        color = "#D7D6D6",
        linewidth = 0.1,
        linetype = 'dashed'),
      panel.grid.major.x = element_blank(),
      plot.title = element_text(
        size = 14, hjust = 0,
        face = "bold", colour = "black"),
      plot.caption = element_text(
        size = 8, hjust = 0,
        colour = "black"),
      plot.subtitle = ggtext::element_markdown(
        size = 12, hjust = 0,
        face = "bold", colour = "black"))

Graficación

Lo primero y más común para graficar la evolución de nuestras variables para un solo país es crear un dataframe filtrado a partir del original, donde la condición sea el país que queremos mostrar.

datos_usa <- datos %>%
  filter(year > 1969 & country == "USA")

Hemos filtrado nuestro dataframe para que solo tenga datos de Estados Unidos a partir de 1970.

Luego, sigue graficarlo:

usa <- ggplot(datos_usa, aes(x = year)) +
  geom_hline(yintercept = 0, linewidth = 0.8) +
  geom_line(aes(y = inf, color = "Inflación"), size = 1) +
  geom_line(aes(y = lt_r, color = "Tasa Larga"), size = 1) +
  labs(title = "USA",
       caption = "Authored by @ecodiegoale with information from JSTDatasetR6, 2023.",
       subtitle = "<span style = 'color:#FF9800;'>Inflation</span> and <span style = 'color:#B80000;'>long-term real interest rate</span>",
       x = " ") +
  scale_color_manual(" ",
    values = c("Inflación" = "#FF9800", 
               "Tasa Larga" = "#B80000"))+ 
  scale_y_continuous(label = percent)+ #librería scales
  theme_jstd
usa

Reitero, no me detendré en los detalles de cómo graficar, eso lo pueden revisar en mis Apuntes de ggplot2 ya citados o en cualquier manual de R. Lo único que destacaré es lo siguiente:

  • Para este gráfico hemos usado el dataframe filtrado datos_usa.

  • En scale_y_continuous() cuando aplicamos label = percent estamos ajustando la escala del eje Y para que sea continua y formateando las etiquetas en formato de porcentaje. Por ejemplo, si tienes datos en el eje Y que van de 0 a 1, la escala se ajustará para mostrar este rango y las etiquetas se formatearán como porcentajes (por ejemplo, 0%, 25%, 50%, 75%, 100%). Es por esto que dividí anteriormente nuestras variables entre 100.

  • El parámetro subtitle dentro de la función labs() en ggplot2 se utiliza para establecer el subtítulo del gráfico. En el código que usé, el subtítulo se está personalizando utilizando formato de texto HTML y colores definidos en scale_color_manual().

  • Se utiliza la etiqueta HTML con el atributo de estilo (style). El atributo color se establece en #FF9800, que es un código de color naranja. Esto significa que la palabra “Inflation” en el subtítulo se mostrará con un color naranja. De manera análoga se hace para “long-term real interest rate”, que se mostrará con un color rojo oscuro.

  • En resumen, el subtítulo utiliza colores diferentes (naranja y rojo oscuro) para resaltar visualmente las partes específicas del texto. Estos colores están definidos en scale_color_manual() y se aplican directamente en el formato HTML del subtítulo para crear un efecto visual personalizado y coherente con la paleta de colores del gráfico. Para más detalle revisar el código ya citado de Albert Rapp.

Si quieres guardar tus gráficos en buena calidad puedes hacerlo en un formato PNG o TIFF de la siguiente manera:

ggsave("usa.png", plot = usa, width = 14, height = 10, units = "cm")
ggsave("usa.tiff", plot = usa, width = 14, height = 10, units = "cm", dpi = 600)

Ahora lo mismo para Canadá.

datos_can <- datos %>%
  filter(year > 1969 & country == "Canada")

ggplot(datos_can, aes(x = year)) +
  geom_hline(yintercept = 0, linewidth = 0.8) +
  geom_line(aes(y = inf, color = "Inflación"), size = 1) +
  geom_line(aes(y = lt_r, color = "Tasa Larga"), size = 1) +
  labs(title = "Canada",
       caption = "Authored by @ecodiegoale with information from JSTDatasetR6, 2023.",
       subtitle = "<span style = 'color:#FF9800;'>Inflation</span> and <span style = 'color:#B80000;'>long-term real interest rate</span>",
       x = " ") +
  scale_color_manual(" ",
                     values = c("Inflación" = "#FF9800", 
                                "Tasa Larga" = "#B80000"))+ 
  scale_y_continuous(label = percent)+
  theme_jstd

Una función personalizada

Hemos hecho dos gráficos para dos países distintos, para conseguirlo tuvimos que crear dos nuevos dataframes a partir de nuestra base original por medio de filtros. Ahora, si quisiéramos graficar para los 18 países disponibles en la base tendríamos que realizar esta operación 16 veces más. Copiar y pegar código no suena muy eficiente o rápido. Además, al hacer esto nos exponemos a equivocarnos, pues podríamos copiar y pegar y no reemplazar apropiadamente los parámetros necesarios. Otro inconveniente es la creación de 16 dataframes más, donde cada uno contenga la misma información pero para países diferentes.

Es aquí cuando entran en juego las funciones personalizadas.

Como dije en la introducción, estas permiten automatizar tareas comunes de una manera más eficiente que copiar y pegar. Otra ventaja es que a medida que cambian los requisitos, solo necesitas actualizar el código en un lugar en lugar de en muchos, es decir, si hay cambios en los requisitos del código, solo necesitas realizar actualizaciones en la definición de la función en lugar de tener que buscar y actualizar múltiples instancias del mismo código copiado en varios lugares.

Aclaro, no soy programador, soy economista, así que mis funciones pueden no ser las mejores o más optimizadas.

No siendo un programador, escribir funciones puede demandar tiempo y esfuerzo. Antes de embarcarte en la tarea de crear una función, es recomendable evaluar su utilidad en situaciones específicas. Por ejemplo, para nuestro caso, si la base de datos solo contuviera información para 3 países, escribir una función podría ser más tardado que solo hacer 3 gráficos. No obstante, aprender a escribir funciones puede ser beneficioso, especialmente cuando te enfrentas a grandes conjuntos de datos y tareas analíticas que tienden a volverse repetitivas. Mi intención no es enseñarte a programar funciones de manera avanzada, ya que yo mismo sigo aprendiendo. Más bien, mi objetivo es mostrar de manera práctica cómo utilizar funciones para aplicarlas de inmediato en situaciones concretas.

Recomiendo mucho R for Data Science de H. Wickham y G. Grolemund, disponible en este enlace.

La estructura básica de una función en R es la siguiente:

nombre_funcion <- function(argumento1, argumento2, ...) {
  # Cuerpo de la función
  # Aquí se realiza el procesamiento o las operaciones deseadas
  
  # Puedes utilizar los argumentos dentro del cuerpo de la función
  
  # Al final, la función devuelve un resultado opcionalmente usando return()
  return(resultado)
}

Viendo lo anterior, deducimos que la operación deseada será la filtración de nuestra base por país y su graficación. Recordemos el código anterior e identifiquemos los argumentos que tendrían que cambiar para cada país y los que se mantienen:

Primero, se filtra el dataframe usando la variable country.

datos_usa <- datos %>%
  filter(year > 1969 & country == "USA")

Tendríamos que cambiar “USA” por cada uno de los elementos que se muestran a continuación.

unique(datos$country)
##  [1] "Australia"   "Belgium"     "Canada"      "Switzerland" "Germany"    
##  [6] "Denmark"     "Spain"       "Finland"     "France"      "UK"         
## [11] "Ireland"     "Italy"       "Japan"       "Netherlands" "Norway"     
## [16] "Portugal"    "Sweden"      "USA"

Entonces, nuestra función tendrá dos argumentos, la base de datos empleada y el país filtrado.

Ahora, en cuanto al gráfico, este tendría que cambiar el dataframe que usa y !el título!:

usa <- ggplot(datos_usa, aes(x = year)) +
  geom_hline(yintercept = 0, linewidth = 0.8) +
  geom_line(aes(y = inf, color = "Inflación"), size = 1) +
  geom_line(aes(y = lt_r, color = "Tasa Larga"), size = 1) +
  labs(title = "USA",
       caption = "Authored by @ecodiegoale with information from JSTDatasetR6, 2023.",
       subtitle = "<span style = 'color:#FF9800;'>Inflation</span> and <span style = 'color:#B80000;'>long-term real interest rate</span>",
       x = " ") +
  scale_color_manual(" ",
    values = c("Inflación" = "#FF9800", 
               "Tasa Larga" = "#B80000"))+ 
  scale_y_continuous(label = percent)+ #librería scales
  theme_jstd

Si nos fijamos bien, en labs() está escrito el título de manera fija. Tenemos que encontrar una manera que el título cambie conforme lo hace la información del gráfico. Es decir, si se grafica la información filtrada para Alemania, el título debe cambiar.

Lo demás no es necesario cambiar, por ejemplo, los años (year) y los valores del eje Y (inf y lt_r), no cambian, pues tienen el mismo nombre siempre, solo cambian sus valores conforme el país.

La función queda como sigue:

generar_graficos <- function(base_datos, individuo) {
  #Rutina del gráfico
  ggplot(data = base_datos %>%
           filter(country == individuo & year > 1969),
         aes(x = year)) +
    geom_hline(yintercept = 0, linewidth = 0.8) +
    geom_line(aes(y = inf, color = "Inflación"), size = 1) +
    geom_line(aes(y = lt_r, color = "Tasa Larga"), size = 1) +
    
    #Asignar título al gráfico conforme al país
    labs(title = paste0(base_datos$country[base_datos$country == individuo][1]),
         caption = "Authored by @ecodiegoale with information from JSTDatasetR6, 2023.",
         subtitle = "<span style = 'color:#FF9800;'>Inflation</span> and <span style = 'color:#B80000;'>long-term real interest rate</span>",
         x = " ") +
    scale_y_continuous(label = percent) +
    scale_color_manual(" ",values = c("Inflación" = "#FF9800", 
                                  "Tasa Larga" = "#B80000"))+
    theme_jstd
  
}

El nombre de mi función es generar_graficos. El primer argumento es la base de datos que usamos y el segundo es el país filtrado, yo lo llamé individuo. Luego, iniciamos con la graficación usando la función ggplot() como de costumbre, pero observa que en data = ya no usamos datos_usa o datos_can o cualquier base filtrada individualmente por país, usamos un nombre genérico base_datos. Después, a esa base genérica le aplicamos un filtro por medio de una pipa %>%, le decimos que esa base genérica tiene una columna llamada country la cual debe ser igual (==) a algún individuo. El resto del gráfico sigue como lo hicimos para Estados Unidos.

Vamos a deternos en esta línea:

#labs(title = paste0(base_datos$country[base_datos$country == individuo][1]),

Esta línea de código utiliza la función paste0() y la indexación de vectores para construir el título de nuestro gráfico en ggplot2.

  • La función paste0() se utiliza para concatenar los elementos proporcionados sin ningún separador. En este caso, se utiliza para concatenar el nombre del país seleccionado al título del gráfico.

  • base_datos$country[base_datos$country == individuo]: Esto crea un subconjunto de la columna country en el dataframe base_datos donde el valor de country es igual a la variable individuo. En otras palabras, selecciona todas las filas en las que el país coincide con el valor de individuo.

Esto luciría más o menos así cuando individuo es igual a Alemania, por poner un ejemplo:

datos$country[datos$country == "Germany"]
##   [1] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##   [8] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [15] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [22] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [29] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [36] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [43] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [50] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [57] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [64] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [71] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [78] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [85] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [92] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
##  [99] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
## [106] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
## [113] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
## [120] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
## [127] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
## [134] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
## [141] "Germany" "Germany" "Germany" "Germany" "Germany" "Germany" "Germany"
## [148] "Germany" "Germany" "Germany" "Germany"
  • [1]: Esto selecciona el primer elemento del subconjunto creado en el paso anterior. Si hay más de una fila que cumple con la condición, esto seleccionará solo el primer país encontrado.
datos$country[datos$country == "Germany"][1]
## [1] "Germany"

En resumen, cuando en el gráfico se filtra información para un país, en el título se concatena el primer elemento de la columna country cuando sus valores son iguales al país que se usó para filtrar. Si se filtra para Alemania (individuo == “Germany”), en el título se pega el primer elemento de la columna country (“Germany”)

Una vez construida la función, hay que generar los vectores que contengan los argumentos que necesita para funcionar:

#Generar un vector con los valores únicos de la variable country
individuos <- unique(datos$country)
#este es el segundo argumento de la función que creamos, el primero es la base de datos
individuos 
##  [1] "Australia"   "Belgium"     "Canada"      "Switzerland" "Germany"    
##  [6] "Denmark"     "Spain"       "Finland"     "France"      "UK"         
## [11] "Ireland"     "Italy"       "Japan"       "Netherlands" "Norway"     
## [16] "Portugal"    "Sweden"      "USA"

Ahora necesitamos aplicar la función que creamos a cada elemento de la lista individuos. Para eso usaremos la función lapply(), la cual es una función en R que se utiliza para aplicar una función (generar_graficos) a cada elemento de una lista (o vector) y devuelve una lista con los resultados.

Vamos a hacer un ejemplo básico de la función lapply() antes de usarla en nuestro análisis. Supongamos que tienes una lista de números y quieres calcular el cuadrado de cada número usando lapply:

# Creamos una lista que contenga números (nosotros ya tenemos una lista, pero esta contiene gráficos)
numeros <- list(1, 2, 3, 4, 5)

# Definimos una función que calcule el cuadrado de un número
calcular_cuadrado <- function(x) {
  return(x^2)
}

# Ahora aplicamos la función a cada elemento de la lista usando lapply

resultados_cuadrados <- lapply(numeros, calcular_cuadrado)

# Vemos los resultados
print(resultados_cuadrados)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 4
## 
## [[3]]
## [1] 9
## 
## [[4]]
## [1] 16
## 
## [[5]]
## [1] 25

Ahora, retomemos nuestro análisis. El resultado de nuestra función es una lista de gráficos, donde cada elemento de la lista corresponde al resultado de aplicar la función generar_graficos() a un elemento de la lista individuos.

lista_graficos <- lapply(individuos, function(x) {
  generar_graficos(datos, x)
})

Procedemos a observar el contenido de nuestra lista. En teoría, debería ser una lista con 18 elementos (18 gráficos de los países).

#View(lista_graficos)

Con la línea anterior se te desplegará una ventana que contenga los elementos y detalles de tu lista. Cada elemento está enumerado del 1 al 18. Si bien ya logramos lo que queríamos, podemos hacer que esta lista sea más cómoda de utilizar. En vez de números, sería mejor que cada elemento tenga de nombre el país del gráfico que almacena.

lista_graficos <- setNames(lista_graficos, individuos)

Ahora, si quisiéramos ver únicamente el gráfico de Dinamarca habría que extraerlo de la lista. Esto se hace de la siguiente manera:

lista_graficos["Denmark"]
## $Denmark

Puedes extraer cada uno de los elementos únicamente cambiando el nombre, sin embargo, hacer esto 18 veces tampoco es muy eficiente.

Lo que haré a continuación será un bucle for, en un próximo blog tal vez me explaye más sobre esto, por ahora con ver el siguiente código basta:

output_dir <- "inflacion" #nombre de la carpeta donde almacenaré los gráficos

# Iterar a través de los gráficos y guardarlos en el directorio
for (nombre in names(lista_graficos)) {
  filename <- file.path(output_dir, paste0(nombre, ".png"))
  ggsave(filename, plot = lista_graficos[[nombre]], width = 14, height = 10, units = "cm")
}

El resultado final será que en tu carpeta donde está guardado tu script aparacerá otra carpeta llamada “inflacion”, la cual contendrá los 18 gráficos en formato PNG con sus respectivos nombres:

En esta entrada hemos visto cómo manipular datos con tidyverse, especialmente datos macroeconómicos, procuré no dejar de lado la teoría económica y mostrar comandos útiles para bajar y cargar bases de datos y archivos desde internet directamente al ambiente de R. El principal objetivo fue mostrar cómo visualizar datos y automatizar su graficación por medio de funciones. Como dije anteriormente, no siendo programador esta tarea puede ser desafiante, pero puede ser beneficiosa y vale la pena aprenderla, siempre con un objetivo práctivo en mente y un problema a resolver, de nada sirve aprender algo de lo que no podamos servirnos.

Y recuerden, cualquiera puede programar.