Libraries and theme and NBER recession dates

# Libraries
library(tidyquant)
library(tidyverse)
library(broom)
library(knitr)
library(psych)
library(data.table)
library(janitor)
library(magrittr)
library(sandwich)
library(car)
library(slider)
library(lmtest)


theme <- 
  theme(
    plot.title = element_text(size = 30, lineheight = .9, face = "bold", hjust = .5, vjust = .75, family = "serif"
    ),
    plot.subtitle = element_text(hjust = .5, vjust = 1, family = "serif", size = 20),
    plot.caption = element_text(hjust = .5, family = "serif", size = 15),
    panel.background = element_rect(fill = "white"),
    axis.title = element_text(family = "serif", size = 20),
    axis.text = element_text(size = 20, family = "serif"),
    axis.line.y = element_line(color = "black"),
    axis.line.x = element_line(color = "black"),
    strip.text = element_text(size = 15, family = "serif", face = "bold"),
    strip.background.x = element_rect(fill = "lightblue"))

recessions_df =
  read.table(
    textConnection(
      "begin, end
1857-06-01, 1858-12-01
1860-10-01, 1861-06-01
1865-04-01, 1867-12-01
1869-06-01, 1870-12-01
1873-10-01, 1879-03-01
1882-03-01, 1885-05-01
1887-03-01, 1888-04-01
1890-07-01, 1891-05-01
1893-01-01, 1894-06-01
1895-12-01, 1897-06-01
1899-06-01, 1900-12-01
1902-09-01, 1904-08-01
1907-05-01, 1908-06-01
1910-01-01, 1912-01-01
1913-01-01, 1914-12-01
1918-08-01, 1919-03-01
1920-01-01, 1921-07-01
1923-05-01, 1924-07-01
1926-10-01, 1927-11-01
1929-08-01, 1933-03-01
1937-05-01, 1938-06-01
1945-02-01, 1945-10-01
1948-11-01, 1949-10-01
1953-07-01, 1954-05-01
1957-08-01, 1958-04-01
1960-04-01, 1961-02-01
1969-12-01, 1970-11-01
1973-11-01, 1975-03-01
1980-01-01, 1980-07-01
1981-07-01, 1982-11-01
1990-07-01, 1991-03-01
2001-03-01, 2001-11-01
2007-12-01, 2009-06-01
2020-04-01, 2020-06-01"
    ),
sep = ',',
colClasses = c('Date', 'Date'),
header = TRUE
  )

Ex. 1: real spot price of gold

cpi <- 
  c("CPIAUCSL") %>% 
  tq_get(get = "economic.data", from = "1978-12-01") %>% 
  select(date, cpi = price) 

gold <- 
  read_csv("gold.csv") %>% 
  mutate(date = date(mdy_hm(date)),
         date = floor_date(date, "month")) %>% 
  group_by(date) %>% 
  filter(row_number() == n()) %>% 
  ungroup 

gold %>% 
  left_join(cpi, by = "date") %>% 
  mutate(real_gold = 100*(gold/cpi)) %>% 
  drop_na %>% 
  ggplot(aes(x = date, y = real_gold)) +
  geom_line() +
  geom_text(aes(label = paste0("$", round(real_gold, 1))), data = data.table::last, vjust = -1) +
  geom_point(data = data.table::last, color = "gold", size = 4) +
  theme +
  labs(title = "Real spot price of gold", 
       caption = "Note: Chart shows spot price of gold deflated using the Consumer Price Index level.\nSource: World Gold Council, FRED",
       subtitle = "1979 - March 2024",
       x = NULL, 
       y = "Real gold spot price")

Dataframe for other exhibits

 median <- 
  read_csv("median_may.csv") %>% 
  mutate(date = mdy(date),
         median = median_pce/100) %>% 
  select(date, median)

gold <- 
read_csv("gold.csv") %>% 
  mutate(date = date(mdy_hm(date)),
         date = floor_date(date, "month")) %>% 
  group_by(date) %>% 
  filter(row_number() == n()) %>% 
  ungroup %>% 
  mutate(gold = (gold/lag(gold, 1)) - 1)

core_pce <- 
  c("PCEPILFE") %>% 
  tq_get(get = "economic.data", from = "1978-12-01") %>% 
  select(date, core_pce = price) %>% 
  mutate(core_pce = (core_pce/lag(core_pce, 1)) - 1)

pce <- 
    c("PCEPI") %>% 
  tq_get(get = "economic.data", from = "1978-12-01") %>% 
  select(date, pce = price) %>% 
  mutate(headline_pce = (pce/lag(pce, 1)) - 1) %>% 
  select(date, headline_pce)

cpi <- 
    c("CPIAUCSL") %>% 
  tq_get(get = "economic.data", from = "1978-12-01") %>% 
  select(date, headline_cpi = price) %>% 
  mutate(headline_cpi = (headline_cpi/lag(headline_cpi, 1)) - 1) %>% 
  select(date, headline_cpi)

core_cpi <- 
    c("CPIAUCSL") %>% 
  tq_get(get = "economic.data", from = "1978-12-01") %>% 
  select(date, core_cpi = price) %>% 
  mutate(core_cpi = (core_cpi/lag(core_cpi, 1)) - 1) %>% 
  select(date, core_cpi)


df <- 
gold %>% 
  left_join(median, by = "date") %>% 
  left_join(core_pce, by = "date") %>% 
  left_join(pce, by = "date") %>% 
  left_join(core_cpi, by = "date") %>% 
  left_join(cpi, by = "date")
  drop_na
## function (data, ...) 
## {
##     check_dots_unnamed()
##     UseMethod("drop_na")
## }
## <bytecode: 0x00000272c8ad03a0>
## <environment: namespace:tidyr>

Corr and regression (gold ~ headline_pce)

df %>% 
  #cor.test(~headline_pce + gold, data = .)
  lm(gold ~ headline_pce, data = .) %>% summary
## 
## Call:
## lm(formula = gold ~ headline_pce, data = .)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.246275 -0.029965 -0.001953  0.026138  0.255919 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)  
## (Intercept)   0.00149    0.00315   0.473   0.6364  
## headline_pce  1.71595    0.92126   1.863   0.0631 .
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.05218 on 542 degrees of freedom
##   (2 observations deleted due to missingness)
## Multiple R-squared:  0.00636,    Adjusted R-squared:  0.004527 
## F-statistic: 3.469 on 1 and 542 DF,  p-value: 0.06306
  #coeftest(vcov = vcovHAC)

Ex. 2: Scatterplot of headline_pce vs. gold

df %>% 
  ggplot(aes(x = headline_pce, y = gold)) +
  geom_point(shape = 22, fill = "gold4") +
  theme +
  geom_smooth(method = "lm", se = T) +
  labs(x = "Headline PCE deflator m/m % change", y = "Gold spot price m/m % change",
       title = "Headline PCE vs. gold", 
       subtitle = "Monthly changes, 1979 to March 2024",
       caption = "Note: Chart shows pairs of monthly changes in headline PCE inflation and gold spot price with linear regression best fit line\n and shaded standard error values.\nSource: FRED, World Gold Council")

Robustness checks

df %>% 
  select(date, gold, headline_cpi) %>% 
  cor.test(~ gold + headline_cpi, data = .)
## 
##  Pearson's product-moment correlation
## 
## data:  gold and headline_cpi
## t = 2.3598, df = 542, p-value = 0.01864
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.01692268 0.18335785
## sample estimates:
##       cor 
## 0.1008458
  #lm(gold ~ headline_cpi, data = .) %>% 
  summary
## standardGeneric for "summary" defined from package "base"
## 
## function (object, ...) 
## standardGeneric("summary")
## <environment: 0x00000272ca44f160>
## Methods may be defined for arguments: object
## Use  showMethods(summary)  for currently available ones.

Ex. 3: Rolling regression - headline inflation

df <- 
  df %>% 
  mutate(index = row_number())
  
      
# assign window length in index units (months in this example) 

    window <-   36
      
# initialize vector of length of periods over which regressions will be run (= total length minus window length)
      
      results_vector <- 
        tibble(values = rep(0, length(df$index) - window),
               errors = rep(0, length(df$index) - window)) # getting values for total less observations lost due to window length
    
    
    for(i in 1:length(results_vector$values)){
      
      mod <- 
        df %>% 
        filter(between(index, first(index) + i - 1, first(index) + window + i - 1)) %>% 
        lm(gold ~ headline_pce, data = .) %>% 
        coeftest
      
      results_vector[i,1] <- mod[2, 1] #extract coefficient
      results_vector[i, 2] <- mod[2, 2] #extract SE
      }
        
      
      results_vector %>% 
        mutate(date = df$date[df$index > window]) %>% # Add date column by subsetting original date vector 
        ggplot() +
        geom_ribbon(aes(x = date, ymin = values - 2*errors, ymax = values + 2*errors), fill = "gold") +
        geom_line(aes(x = date, y = values)) +
        geom_rect(aes(xmin = begin, xmax = end, ymin = -Inf, ymax = +Inf), 
        fill = "azure3", data = recessions_df %>%  filter(year(begin) > 1978), alpha = .5) +
        geom_hline(yintercept = 0, linetype = 2) +
        theme +
        labs(title = "Gold 36-month rolling headline-inflation beta",
             subtitle = "1979 - March 2024",
             caption = "Note: Chart shows the coefficient estimated from a regression of monthly change in gold spot price\non monthly change on median PCE inflation with 2-standard-error band.\n NBER recession dates shaded in gray.\nSource: World Gold Council, FRED, NBER",
             x = NULL, 
             y = "Gold spot price inflation beta")

      lm(results_vector$values ~ lag(results_vector$values, 1)) %>% summary
## 
## Call:
## lm(formula = results_vector$values ~ lag(results_vector$values, 
##     1))
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -7.5467 -0.5846 -0.0406  0.4921  7.7910 
## 
## Coefficients:
##                               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                    0.07652    0.07044   1.086    0.278    
## lag(results_vector$values, 1)  0.94701    0.01431  66.175   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.497 on 507 degrees of freedom
##   (1 observation deleted due to missingness)
## Multiple R-squared:  0.8962, Adjusted R-squared:  0.896 
## F-statistic:  4379 on 1 and 507 DF,  p-value: < 2.2e-16

Ex. 4: Scatterplot of median_pce vs. gold

df %>% 
  #cor.test(~headline_pce + gold, data = .)
  lm(gold ~ median, data = .) %>% summary
## 
## Call:
## lm(formula = gold ~ median, data = .)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.241890 -0.030169 -0.003169  0.026965  0.255444 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.001119   0.004700  -0.238    0.812
## median       2.451858   1.518184   1.615    0.107
## 
## Residual standard error: 0.05225 on 541 degrees of freedom
##   (3 observations deleted due to missingness)
## Multiple R-squared:  0.004798,   Adjusted R-squared:  0.002958 
## F-statistic: 2.608 on 1 and 541 DF,  p-value: 0.1069
  #coeftest(vcov = vcovHAC
  
df %>% 
  ggplot(aes(x = median, y = gold)) +
  geom_point(shape = 22, fill = "gold4") +
  theme +
  geom_smooth(method = "lm", se = T) +
  labs(x = "Median PCE deflator m/m % change", y = "Gold spot price m/m % change",
       title = "Median PCE vs. gold", 
       subtitle = "Monthly changes, 1979 to March 2024",
       caption = "Note: Chart shows pairs of monthly changes in median PCE inflation and gold spot price with linear regression best fit line\n and shaded standard error values,\nSource: Federal Reserve Bank of Cleveland, World Gold Council")

Gold and headline shocks regression

df %>% 
  mutate(shocks = headline_pce - median) %>% 
  lm(gold ~ shocks, data = .) %>% summary
## 
## Call:
## lm(formula = gold ~ shocks, data = .)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.236766 -0.029492 -0.003176  0.025979  0.266340 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)   
## (Intercept) 0.006053   0.002281   2.653  0.00821 **
## shocks      1.588524   1.293873   1.228  0.22008   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.0523 on 541 degrees of freedom
##   (3 observations deleted due to missingness)
## Multiple R-squared:  0.002778,   Adjusted R-squared:  0.0009351 
## F-statistic: 1.507 on 1 and 541 DF,  p-value: 0.2201

Ex. 5: rolling regression (gold ~ median_pce)

df <- 
  df %>% 
  mutate(index = row_number())
  
      
  window <-   36
      

      results_vector1 <- 
        tibble(values = rep(0, length(df$index) - window),
               errors = rep(0, length(df$index) - window)) 
    
    
    for(i in 1:length(results_vector$values)){
      
      mod <- 
        df %>% 
        filter(between(index, first(index) + i - 1, first(index) + window + i - 1)) %>% 
        lm(gold ~ median, data = .) %>% 
        coeftest
      
      results_vector1[i,1] <- mod[2, 1] #extract coefficient
      results_vector1[i, 2] <- mod[2, 2] #extract SE
      }
        
      
      results_vector1 %>% 
        mutate(date = df$date[df$index > window]) %>% # Add date column by subsetting original date vector 
        ggplot() +
        geom_ribbon(aes(x = date, ymin = values - 2*errors, ymax = values + 2*errors), fill = "gold") +
        geom_rect(aes(xmin = begin, xmax = end, ymin = -Inf, ymax = +Inf), 
        fill = "azure3", data = recessions_df %>%  filter(year(begin) > 1978), alpha = .5) +
        geom_line(aes(x = date, y = values)) +
        geom_hline(yintercept = 0, linetype = 2) +
        theme +
        labs(title = "Gold 36-month rolling median-inflation beta",
             subtitle = "1979 - March 2024",
             caption = "Note: Chart shows the coefficient estimated from a regression of monthly change in gold spot price\non monthly change on median PCE inflation with 2-standard-error band.\nNBER recession dates shaded in gray.\nSource: World Gold Council, Federal Reserve Bank of Cleveland, NBER",
             x = NULL, 
             y = "Gold spot price inflation beta")

Autocorrelation and vol comparisons of headline and median pce betas

      # Autocorr

      lm(results_vector1$values ~ lag(results_vector$values, 1)) %>% summary
## 
## Call:
## lm(formula = results_vector1$values ~ lag(results_vector$values, 
##     1))
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -48.001  -7.093   0.978   7.941  30.133 
## 
## Coefficients:
##                               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                    -1.5176     0.6264  -2.423   0.0158 *  
## lag(results_vector$values, 1)   0.7630     0.1273   5.996 3.85e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 13.31 on 507 degrees of freedom
##   (1 observation deleted due to missingness)
## Multiple R-squared:  0.06621,    Adjusted R-squared:  0.06437 
## F-statistic: 35.95 on 1 and 507 DF,  p-value: 3.847e-09
      # Est SD

      sd(results_vector1$values)
## [1] 13.79907
      sd(results_vector$values)
## [1] 4.639094

Other test: gold and economic slack

gold <- 
read_csv("gold.csv") %>% 
  mutate(date = date(mdy_hm(date)),
         date = floor_date(date, "quarter")) %>% 
  group_by(date) %>% 
  filter(row_number() == n()) %>% 
  ungroup

rgdp <- 
  c("GDPC1") %>% 
  tq_get(get = "economic.data", from = "1978-01-01") %>% 
  select(date, gdp = price)

potgdp <- 
  c("GDPPOT") %>% 
  tq_get(get = "economic.data", from = "1978-01-01") %>% 
  select(date, gdppot = price)


gold %>% 
  left_join(rgdp, by = "date") %>% 
  left_join(potgdp, by = "date") %>% 
  mutate(gap = gdp - gdppot,
         gap = (gap / lag(gap, 1)) - 1,
         gold_chg = (gold / lag(gold, 1)) - 1) %>% 
  lm(gold_chg ~ gap, data = .) %>% 
  summary
## 
## Call:
## lm(formula = gold_chg ~ gap, data = .)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.27082 -0.05213 -0.00889  0.04904  0.41525 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)  
## (Intercept) 0.0165524  0.0063788   2.595   0.0102 *
## gap         0.0010275  0.0009705   1.059   0.2911  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.08568 on 179 degrees of freedom
##   (2 observations deleted due to missingness)
## Multiple R-squared:  0.006224,   Adjusted R-squared:  0.0006718 
## F-statistic: 1.121 on 1 and 179 DF,  p-value: 0.2911