Monetary Policy Statement Review - South Africa March 2024

Author

Kudzanai Tsunga

Overview

The monetary policy statement motivated for leaving the repo rate unchanged at 8.25% and this was motivated by services inflation, inflation expectations, and the global trend. It noted an acceleration in services inflation, which was at its highest level since 2019, indicated that services, rather than goods, as being the main inflation driver. This phenomenon has become a global trend and inflation expectations can contribute to elevated underlying inflation rates, as there is little room to absorb shocks when expectations remain high. Despite low unemployment rates, major central banks are expected to adjust rates cautiously. Emerging markets, previously hiking rates, now see reductions, yet rates remain above inflation. Thus, the return to the target inflation rate of 4.5% has been slow. Recent inflation numbers show a delay in reaching the target, with headline inflation reaching 5.6% in February, closer to the top of the target range. South Africa’s, economic performance was below expectations due to supply-side issues, notably electricity shortages and transport constraints, but forecasts suggest modest growth acceleration as supply constraints ease, particularly regarding electricity shortages.

Sentiment

The monetary policy committee (MPC) has put the repo rate on hold citing services inflation, global inflation trends, tidytext shows a moderately positive statement given the choice of words used in the statement. The AFINN index of the statement had an overall positive score of 5 and the summary table showed emphasis on growth as a positive and risk for the negative words.

library(tidytext)
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.0     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tibble)

# summarise the statement and create tibble 
summary_text <- readLines("za_mpc_27032024")
Warning in readLines("za_mpc_27032024"): incomplete final line found on
'za_mpc_27032024'
summary_text  <- tribble(~summary, summary_text)

tokenized_summary <- summary_text %>% unnest_tokens(word, summary)
afinn_lexicon <- get_sentiments("afinn")

# Join the tokenized summary with the AFINN lexicon
scored_summary <- tokenized_summary %>% inner_join(afinn_lexicon, by = "word")

# Create a custom color palette
pos_ <- "green"
neg_ <- "red"


# Filter out words with neutral sentiment (score = 0)
filtered_summary <- scored_summary %>% filter(value != 0)

total_score <- scored_summary %>% summarise(total_sentiment = sum(value))
cat("Sentiment Score for the Statement:", total_score$total_sentiment, "\n")
Sentiment Score for the Statement: 5 
ggplot(data = filtered_summary) +
  geom_col(mapping = aes(word, value, fill = ifelse(value > 0, pos_, neg_))) +
  theme_bw() +
  labs(x = "Words", y = "Sentiment Score") +
  scale_fill_manual(values = c(pos_, neg_)) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        legend.position = "none") + 
  coord_cartesian(clip = "off")

The word summary and wordcloud show inflation being mentioned 26 times with the next frequent word with 8 mentions being rates. This complements the concerns of services and food inflation as significant concern for the MPC. The bigram summary showed inflation being mentioned the most with food (3 times), and twice mentioned with: headline, expectations, and core. The bgram chart summarises the top bigrams (given in Table 2) and the direction of relationship.

library("tm") #load library
Loading required package: NLP

Attaching package: 'NLP'
The following object is masked from 'package:ggplot2':

    annotate
library("SnowballC") #load library
library("wordcloud") #load library
Loading required package: RColorBrewer
library(readr)

text_data <- read_lines("za_mpc_27032024")
docs <- Corpus(VectorSource(text_data))

docs <- tm_map(docs, removeWords, stopwords("english"))
Warning in tm_map.SimpleCorpus(docs, removeWords, stopwords("english")):
transformation drops documents
docs <- tm_map(docs, removePunctuation)
Warning in tm_map.SimpleCorpus(docs, removePunctuation): transformation drops
documents
docs <- tm_map(docs, removeWords, c(" “The", "The", "also", "this", "the", "This"))
Warning in tm_map.SimpleCorpus(docs, removeWords, c(" “The", "The", "also", :
transformation drops documents
dtm <- TermDocumentMatrix(docs)
m <- as.matrix(dtm)
v <- sort(rowSums(m),decreasing = TRUE)
d <- data.frame(word = names(v),freq = v)

library(knitr)
top10 <- head(d, 10) 
kable(top10, caption = "Table 1. Top 10 Words")        
Table 1. Top 10 Words
word freq
inflation inflation 26
rates rates 8
year year 8
rate rate 6
expectations expectations 5
growth growth 5
policy policy 5
target target 5
expected expected 4
food food 4
library(wordcloud)
set.seed(1234)
wordcloud(words = d$word, freq = d$freq, min.freq = 3,
          max.words = 50, random.order = FALSE, rot.per = -0.5, 
          colors = brewer.pal(8, "Dark2"),
          scale = c(5, 0.5)) 

# word network and conclusion 

library(dplyr)
library(tidytext)
library(janeaustenr)
library(tidyr)
library(igraph)

Attaching package: 'igraph'
The following objects are masked from 'package:lubridate':

    %--%, union
The following objects are masked from 'package:dplyr':

    as_data_frame, groups, union
The following objects are masked from 'package:purrr':

    compose, simplify
The following object is masked from 'package:tidyr':

    crossing
The following object is masked from 'package:tibble':

    as_data_frame
The following objects are masked from 'package:stats':

    decompose, spectrum
The following object is masked from 'package:base':

    union
library(ggraph)


text <- read_csv("za_mpc_27032024.csv")
Rows: 1 Columns: 2
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): mpc_issue, statement

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
mpc_bigrams <- text %>% 
  unnest_tokens(bigram, statement, token = "ngrams", n = 2)

bigrams_separated <- mpc_bigrams %>%
  separate(bigram, c("word1", "word2"), sep = " ")

bigrams_filtered <- bigrams_separated %>%
  filter(!word1 %in% stop_words$word) %>%
  filter(!word2 %in% stop_words$word)

bigram_counts <- bigrams_filtered %>% 
  count(word1, word2, sort = TRUE)

bigrams_united <- bigrams_filtered %>%
  unite(bigram, word1, word2, sep = " ")


bigram_tf_idf <- bigrams_united %>%
  count(mpc_issue, bigram) %>%
  bind_tf_idf(bigram, mpc_issue, n) %>%
  arrange(desc(tf_idf))

bigram_top10 <- bigrams_filtered %>% 
  count(word1, word2, sort = TRUE) %>% 
  slice(1:10)

library(knitr)
kable(bigram_top10, caption = "Table 2. Top 10 Bigrams") 
Table 2. Top 10 Bigrams
word1 word2 n
food inflation 3
south africa 3
4.5 objective 2
central banks 2
core inflation 2
exchange rate 2
headline inflation 2
inflation expectations 2
policy rate 2
repo rate 2
bigram_graph <- bigram_counts %>%
  filter(n >= 2) %>%
  graph_from_data_frame()

set.seed(2020)

a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

ggraph(bigram_graph, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = TRUE,
                 arrow = a, end_cap = circle(0.01, 'inches')) +
  geom_node_point(color = "lightblue", size = 4) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1) +
  theme_void()