W niniejszym projekcie wykorzystuję model Bayesian Auto-Regressive (zwany dalej BAR), aby przewidywać wartości inflacji w Polsce na najbliższe miesiące.
Korzystam ze sharmonizowanej inflacji konsumenckiej HICP dostępnej na stronie Eurostatu dla lat 1997-2023 (https://ec.europa.eu/eurostat/databrowser/view/prc_hicp_manr/default/table?lang=en). Najpierw na podstawie danych z lat 1997-2022 wyliczę prognozę dla 11 miesięcy roku 2023 i porównam ją z wartościami faktycznymi. Następnie, korzystając z tych samych paramterów wyliczę przewidywania inflacji dla kilku kolejnych miesięcy.
Kody, z których korzystam do szacowania modelu BAR w większości bazują na slajdach i kodach opracowanych przez doktora Andrzeja Kocięckiego z Uniwersytetu Warszawskiego (https://scholar.google.com/citations?user=svf8CzoAAAAJ&hl=pl&oi=ao). Kody odpowiadające za graficzną część niniejszej pracy są wyłącznie mojego autorstwa (https://www.linkedin.com/in/tymoteusz-metrak/).
library(tidyverse) # łatwiejsza praca z danymi
library(lubridate) # pracowanie z datami
library(ggthemes) # bardziej estetyczne wykresy
library(dygraphs) # interaktywe wykresy
library(xts) # dla klasy "xts" koniecznej do interaktywnych wykresów
inflation <- read.csv("prc_hicp_manr_page_linear_march_2024.csv")
Poland <- inflation[inflation$geo=="PL", 7:8]
Poland$TIME_PERIOD <- ym(Poland$TIME_PERIOD) # zmiana wyrazów na typ "data"
Pierwszy rzut oka na dane - zbuduję wykres, aby zobaczyć inflację w rzeczonym okresie. Najpierw tworzę ramkę danych z zapisanymi wartościami inflacji, datą i miejscem na oszacowania, które później obliczę.
df <- data.frame(
c(rep(NA, length(Poland[,2]))), # wartości NA, potem włożę tu przewidywania
c(Poland[,2]), # prawdziwe wartości
c(Poland[,1])) # daty
names(df) <- c("Fitted", "Actual", "Date")
Następnie tworzę poglądowy wykres.
ggplot(data=df, aes(x=Date)) +
geom_line(aes(y=Actual, colour = "Inflacja r/r"), linewidth=1) +
theme_gdocs() +
theme(legend.title = element_blank())+
ylab("Inflacja HICP (w %)") +
xlab("Rok") +
labs(title="Inflacja w Polsce 1997-2023",
subtitle = "styczeń 1997 - luty 2024")
Wyświetlę teraz wykres interkatywny.
Poland_xts <- xts(Poland[c("OBS_VALUE")], order.by = Poland$TIME_PERIOD)
dygraph(Poland_xts$OBS_VALUE,
main = "Wykres interaktywny inflacji w Polsce 1997-2023",
xlab="Rok",
ylab="HICP (w %)") %>%
dyRangeSelector(height = 60)
Poniższa część pracy to model, który na podstawie danych 1997-2022 przewidzi inflację HICP w 11 miesiącach 2023 roku. Następnie poównam wartości przewidywane z wartościami rzeczywistymi, aby wykazać użyteczność modelu BAR do przewidywania inflacji i ogólniej - szeregów czasowych.
yy <- as.matrix(Poland[1:312,2]) # wykorzystam dane 1997-2022 i będę przewidywał na 2023 rok
T.obs <- length(yy) ## "T" -> długość powyższego wektora
Ustalam 12 wartości opóźnionych, które odpowiadają całemu roku poprzedzającmu dany miesiąc. Horyzont prognozy ustalam na 12 miesięcy 2023 roku.
p <- 12 ## liczba opóźnień (lags)
h <- 12 ## horyzont prognozy
y <- cbind(yy[(p+1):T.obs])
x <- matrix(NA,T.obs-p,p+1)
x[,1] <- 1
for (i in 1:p) {
x[,i+1] <- yy[(p+1-i):(T.obs-i)]
}
Potrzebuję otrzymać reszty z modelu regresji liniowej szacowanej metodą najmniejszych kwadratów. Użyję potem te reszty do obliczenia prawdopodobieństwa a posteriori dla sigma^2.
beta.hat = solve(crossprod(x),crossprod(x,y))
## Ekwiwalent znanego wzoru = solve(t(x)%*%x)%*%t(x)%*%y
# Reszty
z <- y - x%*%beta.hat
Definuję tutaj odpowiedni rozkład a priori. To w tym miejscu definiuję jaki tym modelu BAR wykorzystam. Może to być BAR(1), BAR(2), albo BAR(1) z dodatkowym opóźnieniem odpowiadającym za sezonowość (“seasonality”). To na takie ostatnie rozwiązanie się zdecyduję. Do szacowania wykorzystam wartość z ostatniego okresu i sprzed roku (gdyż inflacja podlega wahaniom sezonowym, np. z powodu wzrostu konsumpcji w okresie świątecznym czy spadku bezrobocia latem).
Parametr “shrinkage” ustalam na poziomie 0,15, chociaż może być to zmieniane w zależności od potrzeb.
beta.bar = matrix(0,p+1,1)
beta.bar[2] <- 1 ## random walk prior
beta.bar[13] <- 1 ## seasonality prior
# Parameters poniżej odpowiadające za "shrinkage"
lambda.bar <- 0.15 ## Shrinkage dla wszystkich bet poza stałą
lambda.bar.const <- 1 ## Shrinkage (a właściwie jego brak) dla stałej
Tworzę macierz złożoną z samych zer i wartości lambda na diagonali.
Sigma.bar <- matrix(0,p+1,p+1)
# Pierwsza wartość równa lambda.bar.const (dla stałej)
Sigma.bar[1,1] <- lambda.bar.const
# Pozostałe wartości zwyczajnie na diagonali
for (i in 1:p) {
Sigma.bar[i+1,i+1] <- lambda.bar/i
}
Sigma.tilde <- crossprod(x) + solve(Sigma.bar)
inv.Sigma.tilde <- solve(Sigma.tilde)
beta.tilde <- inv.Sigma.tilde%*%( crossprod(x)%*%beta.hat + solve(Sigma.bar)%*%beta.bar)
Poniżej są parametry dla prawdopodobieństwa brzegowego sigma^2. Są to parametry z funkcji gęstości odwrotnego gamma (inverted-gamma probability density function).
alpha.sigma2 <- 0.5*(T.obs-p)
lambda.sigma2 <- 0.5*(crossprod(z) + crossprod(x%*%beta.hat) + t(beta.bar)%*%solve(Sigma.bar)%*%beta.bar - t(beta.tilde)%*%Sigma.tilde%*%beta.tilde )
Większa liczba iteracji (powtórzeń) wydłuży proces szacowania, ale powinna zwiększyć dokładność estymacji. Ja ustalam liczbę powtórzeń algorytmu na 300 000, ale już przy 100 000 można otrzymać wiarygodne oszacowania.
Ponadto, w kroku tym tworzę miejsce na przechowywanie wartości, które zaraz oszacuję.
n.iter <- 300000
sigma2.all <- matrix(NA,n.iter,1)
sigma2.gibbs.all <- matrix(NA,n.iter,1)
beta.all <- matrix(NA,n.iter,p+1)
forecast.all <- matrix(NA,n.iter,h)
W próbkowaniu wykorzystam tzw. właściwość Choleskiego.
beta <- beta.bar # inicjalizacja
for (i in 1:n.iter) {
# próbkowanie z rozkładu odwrotnego gamma (rozkład brzegowy a posteriori sigma^2)
sigma2 <- 1/rgamma(1, shape = alpha.sigma2, rate = lambda.sigma2)
# próbkowanie z wielowymiarowego rozkładu normalnego z wykorzystaniem właściwości Choleskiego
beta <- beta.tilde + sqrt(sigma2)*t(chol(inv.Sigma.tilde))%*%rnorm(p+1)
# próbkowanie z rozkładu prognozy
forecast <- matrix(NA,1,h)
x.last <- x[T.obs-p,2:(p+1)] # ostatni rząd macierzy x bez 1
for (j in 1:h) {
y.mean <- cbind(1,rbind(x.last))%*%beta
obs.for <- y.mean + sqrt(sigma2)*rnorm(1) # próbkowanie z prawdopodobieństwa dla przyszłej obserwacji
forecast[j] <- obs.for
x.last <- cbind(obs.for,rbind(x.last))
x.last <- x.last[1:p]
}
sigma2.all[i] <- sigma2
beta.all[i,] <- t(beta)
forecast.all[i,] <- forecast
}
Wspaniale! Próbkowanie trwało kilka minut i zostało zakończone. Otrzymałem już wyniki, teraz należy je odpowiednio zapisać.
W macierzy forecast.all mam wszystkie wartości próbkowania. Macierz ta ma 300 000 wierszy - po jednym dla każdego powtorzenia algorytmu próbkowania. Posiada też 12 kolumn, odpowiadających 12 okresom przewidywania inflacji. Dla każdej kolumny wyliczam medianę, dolne 5% i górne 5% (czyli 95%).
sr.quant <- matrix(NA,h,3)
for (i in 1:h) {
sr.quant[i,] <- quantile(forecast.all[,i], probs = c(0.05,0.5,0.95))
}
Teraz, do stworzonej na początku tabeli z wartościami prawdziwymi dodam przewidywania (mediana) wartościami inflacji dla 12 miesięcy 2023 roku. Pozwoli mi to na wyliczenie błędu standardowego - MAPE (mean absolute percentage error) oraz przedstawienie przewidywań na wykresie.
df$Fitted <- c(rep(NA, 312), sr.quant[,2], NA, NA) # NA values and predictions
Tutaj wyliczam tzw. “Średni absolutny błąd procentowy”.
MAPE <- filter(df, year(Date)>2022 & year(Date)<2024) %>%
summarise(MAPE=mean(abs(Actual-Fitted)/Actual))
MAPE <- MAPE[1,1]
print(MAPE)
## [1] 0.0533613
Jak widać, zadany model produkuje niezbyt duży błąd standardowy na przestrzeni 12 miesięcy 2023 roku i można go uznać za satysfakcjonujący. Zaakceptuję zatem wcześniej zdefiniowany rozkład a priori, liczbę opóźnień (lags) oraz shrinkage. Można jednak manipulować tymi wartościami w zależności od potrzeb (i nadziei na zmniejszenie błędu prognozy).
W tym miejscu utworzę tabelę z przedziałem ufności na poziomie 90%.
posterior.interval <- cbind.data.frame(
sr.quant[,1],
sr.quant[,3],
subset(df, year(Date)>2022 & year(Date)<2024)$Date)
names(posterior.interval) <- c("LL", "UL", "Date")
Teraz połączę przedziałów wiarygodności z medianą przewidywań.
credible_df <- left_join(df, posterior.interval, by="Date")
Najpierw stowrzę wykres porównujący inflację w całym badanym wykresie z prognozą na ostatnie miesiące 2023 roku.
ggplot(data=credible_df, aes(x=Date)) +
geom_line(aes(y=Actual, colour = "Prawdziwa"), linewidth=1) +
geom_line(aes(y=Fitted, colour = "Przewidywana"), linewidth=1, linetype=2) +
theme_gdocs() +
theme(legend.title = element_blank()) +
ylab("") +
xlab("") +
geom_vline(xintercept=as.numeric(as.Date("2022-12-01")), linetype=2) +
geom_ribbon(aes(ymin=LL, ymax=UL), fill="grey", alpha=0.5) +
labs(title=paste0("Przewidywana vs. prawdziwa inflacja w Polsce"),
subtitle="1997 - 2023",
caption=paste0("Mean Absolute Percentage Error = ", round(100*MAPE,2), "%","\nAutor: Tymoteusz Mętrak"),
x="Data",
y="Punkty procentowe (%)")
## Warning: Removed 314 rows containing missing values (`geom_line()`).
Teraz stworzę wykres wyłącznie dla miesięcy, dla których mogę porównać wartości przewidywane z prawdziwymi.
credible_df_new <- credible_df[313:324,]
ggplot(data=credible_df_new, aes(x=Date)) +
geom_line(aes(y=Actual, colour = "Prawdziwa"), linewidth=1) +
geom_line(aes(y=Fitted, colour = "Przewidywana"), linewidth=1, linetype=2) +
theme_gdocs() +
theme(legend.title = element_blank()) +
ylab("") +
xlab("") +
geom_ribbon(aes(ymin=LL, ymax=UL), fill="grey", alpha=0.5) +
labs(title=paste0("Przewidywana vs. prawdziwa inflacja w Polsce"),
subtitle="Styczeń - grudzień 2023",
caption=paste0("Mean Absolute Percentage Error = ", round(100*MAPE,2), "%","\nAutor: Tymoteusz Mętrak"),
x="Data",
y="Punkty procentowe (%)")
Zbuduję teraz wykres z “suwakiem”.
credible_df_xts <- xts(credible_df[,-3], order.by = credible_df$Date)
credible_df_xts[312,1]=credible_df_xts[312,2]
credible_df_xts[312,3]=credible_df_xts[312,2]
credible_df_xts[312,4]=credible_df_xts[312,2]
dygraph(credible_df_xts,
main = "Wykres interaktywny inflacji w Polsce 1997-2023",
xlab="Rok",
ylab="HICP (w %)") %>%
dySeries("Actual", label = "Prawdziwa") %>%
dySeries(c("LL", "Fitted", "UL"), label = "Przewidywana") %>%
dyRangeSelector(height = 70)
W tej sekcji dokonam przewidywań inflacji na kolejny rok. Skorzystam ze sprawdzonych powyżej parametrów, które zagwarantowały niski błąd standardowy.
yy2 <- as.matrix(Poland[,2]) # wykorzystam wszystkie dane 1997-luty 2024
T.obs2 <- length(yy2)
Ustalam 12 wartości opóźnionych, które odpowiadają całemu roku poprzedzającmu dany miesiąc. Horyzont prognozy ustalam na 12 miesięcy.
p2 <- 12 # liczba opoźnień
h2 <- 12 # horyznot prognozy
y2 <- cbind(yy2[(p2+1):T.obs2])
x2 <- matrix(NA,T.obs2-p2,p2+1)
x2[,1] <- 1
for (i in 1:p2) {
x2[,i+1] <- yy2[(p2+1-i):(T.obs2-i)]
}
beta.hat2 = solve(crossprod(x2),crossprod(x2,y2))
# Reszty
z2 <- y2 - x2%*%beta.hat2
Definuję tutaj identyczny rozkład a priori jak wcześniej. Parametr “shrinkage” ustalam na poziomie 0,15, chociaż jak wspomniałem, może być to zmieniane w zależności od potrzeb.
beta.bar2 = matrix(0,p2+1,1)
beta.bar2[2] <- 1 ## random walk prior
beta.bar2[13] <- 1 ## seasonality prior
# "Shrinkage"
lambda.bar2 <- 0.15
lambda.bar.const2 <- 1
Sigma.bar2 <- matrix(0,p2+1,p2+1)
Sigma.bar2[1,1] <- lambda.bar.const2
for (i in 1:p2) {
Sigma.bar2[i+1,i+1] <- lambda.bar2/i
}
Sigma.tilde2 <- crossprod(x2) + solve(Sigma.bar2)
inv.Sigma.tilde2 <- solve(Sigma.tilde2)
beta.tilde2 <- inv.Sigma.tilde2%*%( crossprod(x2)%*%beta.hat2 + solve(Sigma.bar2)%*%beta.bar2)
alpha.sigma22 <- 0.5*(T.obs2-p2)
lambda.sigma22 <- 0.5*(crossprod(z2) + crossprod(x2%*%beta.hat2) + t(beta.bar2)%*%solve(Sigma.bar2)%*%beta.bar2 - t(beta.tilde2)%*%Sigma.tilde2%*%beta.tilde2 )
Ustalam liczbę powtórzeń algorytmu tak jak wcześniej na 300 000.
Ponadto, w kroku tym tworzę miejsce na przechowywanie wartości, które zaraz oszacuję.
n.iter2 <- 300000
sigma2.all2 <- matrix(NA,n.iter2,1)
beta.all2 <- matrix(NA,n.iter2,p2+1)
forecast.all2 <- matrix(NA,n.iter2,h2)
beta2 <- beta.bar2
for (i in 1:n.iter2) {
sigma22 <- 1/rgamma(1, shape = alpha.sigma22, rate = lambda.sigma22)
beta2 <- beta.tilde2 + sqrt(sigma22)*t(chol(inv.Sigma.tilde2))%*%rnorm(p+1)
forecast2 <- matrix(NA,1,h2)
x.last2 <- x2[T.obs2-p2,2:(p2+1)]
for (j in 1:h2) {
y.mean2 <- cbind(1,rbind(x.last2))%*%beta2
obs.for2 <- y.mean2 + sqrt(sigma22)*rnorm(1)
forecast2[j] <- obs.for2
x.last2 <- cbind(obs.for2,rbind(x.last2))
x.last2 <- x.last2[1:p2]
}
sigma2.all2[i] <- sigma22
beta.all2[i,] <- t(beta2)
forecast.all2[i,] <- forecast2
}
Próbkowanie zostało zakończone. Otrzymałem już wyniki, teraz należy je odpowiednio zapisać. Wyliczam medianę, dolne 5% i górne 5% (czyli 95%).
sr.quant2 <- matrix(NA,h2,3)
for (i in 1:h2) {
sr.quant2[i,] <- quantile(forecast.all2[,i], probs = c(0.05,0.5,0.95))
}
Przygotuję ramkę danych z wartościami przewidywanymi
dates <- seq(as.Date("2024/03/01"), as.Date("2025/02/01"), by = "month")
df2 <- data.frame(sr.quant2[,2],dates)
names(df2) <- c("Fitted", "Date")
W tym miejscu utworzę tabelę z przedziałem ufności na poziomie 90%.
posterior.interval2 <- cbind.data.frame(
sr.quant2[,1],
sr.quant2[,3],
df2$Date)
names(posterior.interval2) <- c("LL", "UL", "Date")
credible_df2 <- left_join(df2, posterior.interval2, by="Date")
Najpierw wykres z inflacją przewidywaną na przyszłość.
ggplot(data=credible_df2, aes(x=Date)) +
geom_line(aes(y=Fitted, colour = "Przewidywana"), linewidth=1, linetype=2) +
theme_gdocs() +
theme(legend.title = element_blank()) +
ylab("") +
xlab("") +
geom_ribbon(aes(ymin=LL, ymax=UL), fill="grey", alpha=0.5) +
labs(title="Przewidywana inflacja w Polsce",
subtitle="marzec 2024 - luty 2025",
caption="Autor: Tymoteusz Mętrak",
x="Data",
y="Punkty procentowe (%)")
Stworzę kolejne wykresy. Do tego potrzebuję zbudować odpowiednie tabele.
dates2 <- seq(as.Date("1997/01/01"), as.Date("2025/02/01"), by = "month")
df3 <- data.frame(
c(rep(NA, 326), sr.quant2[,2]),
c(Poland$OBS_VALUE, rep(NA, 12)),
dates2)
names(df3) <- c("Fitted", "Actual","Date")
df3 <- left_join(df3, posterior.interval2, by="Date")
Teraz mogę stworzyć wykres z wartościami prawdziwymi oraz przewidywanymi.
ggplot(data=df3, aes(x=Date)) +
geom_line(aes(y=Actual, colour = "Prawdziwa"), linewidth=1) +
geom_line(aes(y=Fitted, colour = "Przewidywana"), linewidth=1, linetype=2) +
theme_gdocs() +
theme(legend.title = element_blank()) +
ylab("") +
xlab("") +
geom_vline(xintercept=as.numeric(as.Date("2024-02-01")), linetype=2) +
geom_ribbon(aes(ymin=LL, ymax=UL), fill="grey", alpha=0.5) +
labs(title=paste0("Inflacja w Polsce i jej przewidywanie"),
subtitle="styczeń 1997 - luty 2025",
caption="Autor: Tymoteusz Mętrak",
x="Data",
y="Punkty procentowe (%)")
## Warning: Removed 12 rows containing missing values (`geom_line()`).
## Warning: Removed 326 rows containing missing values (`geom_line()`).
Finalnie, stworzę jeszcze ostatni wykres - wykres interaktywny.
df3_xts <- xts(df3[,-3], order.by = df3$Date)
df3_xts[326,1] <- df3_xts[326,2]
df3_xts[326,3] <- df3_xts[326,2]
df3_xts[326,4] <- df3_xts[326,2]
dygraph(df3_xts,
main = "Wykres interaktywny inflacji w Polsce 1997-2025",
xlab="Rok",
ylab="HICP (w %)") %>%
dySeries("Actual", label = "Prawdziwa") %>%
dySeries(c("LL", "Fitted", "UL"), label = "Przewidywana") %>%
dyRangeSelector(height = 70)