---
title: "Zadanie 5 – Základná diagnostika modelu"
subtitle: "Kvalita regresného modelu ceny ropy Brent"
author: "Norbert Hanáček"
lang: sk
format:
html:
toc: true
number-sections: true
code-fold: true
code-tools: true
embed-resources: true
execute:
echo: true
warning: false
message: false
---
# Nadväznosť na zadanie 4
V zadaní 4 sme na údajoch z databázy [Global Oil Prices & Geopolitical Events (2010–2026)](https://www.kaggle.com/datasets/kavyadhyani/global-oil-prices-and-geopolitical-events) (Kaggle, autor Kavya Dhyani, licencia CC BY 4.0) odhadli dva modely:
- **jednoduchý model** `model1`: cena Brent vysvetlená iba indexom geopolitického rizika GPR,
- **viacnásobný model** `model`: cena Brent vysvetlená indexmi GPR, VIX a DXY.
V zadaní 4 sme koeficienty iba interpretovali a zámerne sme sa nevenovali štatistickému testovaniu. V tomto zadaní na tento model nadviažeme a posúdime jeho **kvalitu ako celku**:
1. **Vysvetľovacia schopnosť modelu:** `R.squared`, `Adj.R.squared`.
2. **Štatistická významnosť a presnosť modelu:** `Sigma`, `F Stat.`, `p.value`, `df`, `df.residual`.
3. **Grafická analýza kvality modelu:** diagnostické grafy rezíduí.
```{r}
#| label: baliky
library(broom)
library(knitr)
library(kableExtra)
library(ggplot2)
library(dplyr)
# Pomocná funkcia zo zadania 4: číslo so slovenskou desatinnou čiarkou.
sk_cislo <- function(x, digits = 3) {
formatC(x, format = "f", digits = digits, decimal.mark = ",", big.mark = " ")
}
```
# Príprava databázy
Údaje pripravíme **rovnakým postupom ako v zadaní 4**, aby sme pracovali s tou istou vzorkou a s tým istým modelom. Súbor `oil_geopolitics_dataset_2010_2026.csv` môže byť v podpriečinku `udaje` alebo priamo vedľa dokumentu.
```{r}
#| label: nacitanie
cesta <- "udaje/oil_geopolitics_dataset_2010_2026.csv"
if (!file.exists(cesta)) cesta <- "oil_geopolitics_dataset_2010_2026.csv"
if (!file.exists(cesta)) {
stop("Vložte CSV do podpriečinka udaje alebo vedľa QMD dokumentu.")
}
ropa <- read.csv(cesta, sep = ",", dec = ".",
na.strings = c("", "NA"),
check.names = FALSE, stringsAsFactors = FALSE)
ropa$date <- as.Date(ropa$date)
# Rovnaké premenné ako v zadaní 4.
udaje <- ropa[, c("date", "brent_price", "gpr_index", "vix", "dxy_index")]
# Rovnaké ošetrenie chýbajúcich hodnôt ako v zadaní 4:
# riadky bez ceny Brent vynecháme, vysvetľujúce premenné doplníme mediánom.
udaje <- udaje[!is.na(udaje$brent_price), ]
for (premenna in c("gpr_index", "vix", "dxy_index")) {
chyba <- is.na(udaje[[premenna]])
udaje[[premenna]][chyba] <- median(udaje[[premenna]], na.rm = TRUE)
}
cat("Počet dní vo vzorke:", nrow(udaje), "\n")
cat("Obdobie:", format(min(udaje$date)), "až", format(max(udaje$date)), "\n")
```
# Lineárna regresia
Odhadujeme ten istý viacnásobný model ako v zadaní 4:
$$
BrentPrice_t = \beta_0 + \beta_1 GPR_t + \beta_2 VIX_t + \beta_3 DXY_t + u_t
$$
kde $t$ označuje obchodný deň. Pre porovnanie odhadneme znova aj jednoduchý model `model1` so samotným indexom GPR.
```{r}
#| label: odhad-modelov
model1 <- lm(brent_price ~ gpr_index, data = udaje)
model <- lm(brent_price ~ gpr_index + vix + dxy_index, data = udaje)
```
V zadaní 4 sme zobrazili iba odhady koeficientov. Teraz pridáme aj štandardné chyby, t-štatistiky, p-hodnoty a hviezdičky významnosti:
```{r}
#| label: koeficienty
tidy(model) |>
mutate(
Significance = case_when(
p.value < 0.001 ~ "***",
p.value < 0.01 ~ "**",
p.value < 0.05 ~ "*",
p.value < 0.10 ~ ".",
TRUE ~ ""
)
) |>
kable(
digits = 4,
col.names = c("Premenná", "Odhad", "Std. Error", "t value",
"p-value", "Významnosť"),
caption = "Regresné koeficienty modelu ceny Brent"
) |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
```
Všetky tri vysvetľujúce premenné sú pri tejto veľkej vzorke štatisticky významné. Koeficient pri GPR je kladný: o jeden bod vyšší index geopolitického rizika sa pri nezmenených VIX a DXY spája s cenou Brent vyššou približne o `r sk_cislo(coef(model)["gpr_index"], 3)` USD/barel. Koeficient pri DXY je záporný: silnejší dolár sa spája s nižšou cenou ropy vyjadrenou v USD, čo zodpovedá ekonomickej intuícii.
# Kvalita vyrovnania modelu
Súhrnné charakteristiky modelu ako celku získame funkciou `glance()`. Tabuľku zobrazíme celú (bez `deviance`), ale v ďalších častiach sa sústredíme iba na prvé dve skupiny charakteristík.
```{r}
#| label: glance
glance(model) |>
select(-deviance) |>
kable(
digits = 4,
col.names = c("R.squared", "Adj.R.squared", "Sigma", "F Stat.",
"p.value", "df", "logLik", "AIC", "BIC",
"df.residual", "N"),
caption = "Súhrnné charakteristiky modelu (glance)"
) |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
# Hodnoty si uložíme pre ďalšie výpočty a text.
g <- glance(model)
g1 <- glance(model1)
n <- nobs(model)
k <- length(coef(model)) # počet koeficientov vrátane konštanty
```
## Vysvetľovacia schopnosť modelu
### Koeficient determinácie – R.squared
Koeficient determinácie vyjadruje, akú časť variability ceny ropy Brent vysvetľuje regresný model:
$$
R^2 = 1 - \frac{RSS}{TSS},
\qquad RSS = \sum_{t=1}^{n} e_t^2,
\qquad TSS = \sum_{t=1}^{n} (y_t - \bar y)^2 .
$$
Hodnotu si overíme aj ručným výpočtom:
```{r}
#| label: r2-rucne
y <- udaje$brent_price
RSS <- sum(residuals(model)^2)
TSS <- sum((y - mean(y))^2)
R2_rucne <- 1 - RSS / TSS
R2_adj_rucne <- 1 - (1 - R2_rucne) * (n - 1) / (n - k)
data.frame(
Ukazovatel = c("R²", "Adjusted R²"),
Rucny_vypocet = c(R2_rucne, R2_adj_rucne),
glance = c(g$r.squared, g$adj.r.squared)
) |>
kable(digits = 6, col.names = c("Ukazovateľ", "Ručný výpočet", "glance()"),
caption = "Overenie R² a upraveného R²") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
```
**Interpretácia:** Model vysvetľuje približne **`r sk_cislo(100 * g$r.squared, 1)` %** variability dennej ceny ropy Brent v rokoch 2010–2026. Zvyšných približne `r sk_cislo(100 * (1 - g$r.squared), 1)` % variability zostáva nevysvetlených – ide o vplyv faktorov, ktoré model neobsahuje (napr. ponuka OPEC+, dopyt Číny, zásoby ropy, pandémia COVID-19).
### Upravený koeficient determinácie – Adj.R.squared
$$
\bar R^2 = 1 - (1 - R^2)\,\frac{n-1}{n-k}
$$
kde $k$ je počet odhadovaných koeficientov vrátane konštanty (u nás $k = `r k`$).
**Interpretácia:** Upravený koeficient determinácie má hodnotu `r sk_cislo(g$adj.r.squared, 4)` a je takmer zhodný s $R^2$ = `r sk_cislo(g$r.squared, 4)`. Je to preto, že máme veľmi veľa pozorovaní ($n = `r n`$) a iba tri vysvetľujúce premenné, takže penalizácia za počet premenných je zanedbateľná.
### Porovnanie s jednoduchým modelom zo zadania 4
Upravený koeficient determinácie je vhodný práve na porovnanie modelov s rôznym počtom premenných, ktoré majú rovnakú vysvetľovanú premennú a sú odhadnuté na rovnakých pozorovaniach – presne ako naše dva modely zo zadania 4.
```{r}
#| label: porovnanie-modelov
porovnanie_modelov <- data.frame(
Model = c("model1: Brent ~ GPR", "model: Brent ~ GPR + VIX + DXY"),
R2 = c(g1$r.squared, g$r.squared),
R2_adj = c(g1$adj.r.squared, g$adj.r.squared),
Sigma = c(g1$sigma, g$sigma),
F_stat = c(g1$statistic, g$statistic),
df = c(g1$df, g$df),
df_res = c(g1$df.residual, g$df.residual)
)
porovnanie_modelov |>
kable(digits = 4,
col.names = c("Model", "R²", "Adj. R²", "Sigma", "F Stat.",
"df", "df.residual"),
caption = "Porovnanie jednoduchého a viacnásobného modelu") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
```
Samotný index geopolitického rizika vysvetľuje iba približne `r sk_cislo(100 * g1$r.squared, 2)` % variability ceny Brent. Po pridaní VIX a DXY vzrástol upravený koeficient determinácie na `r sk_cislo(100 * g$adj.r.squared, 1)` %. Rast $\bar R^2$ potvrdzuje, že pridané premenné model skutočne zlepšili a nejde iba o mechanický nárast $R^2$. Cena ropy teda súvisí oveľa silnejšie s makroekonomickým prostredím (najmä silou dolára) ako so samotným geopolitickým rizikom.
> Vysoká hodnota $R^2$ sama osebe nezaručuje správnu špecifikáciu modelu ani kauzálny vzťah medzi premennými.
## Štatistická významnosť a presnosť modelu
### Reziduálna štandardná chyba – Sigma
$$
\hat\sigma = \sqrt{\frac{RSS}{n-k}}
$$
**Interpretácia:** $\hat\sigma$ = `r sk_cislo(g$sigma, 2)` USD/barel. Typická odchýlka skutočnej ceny Brent od ceny predikovanej modelom je teda približne `r sk_cislo(g$sigma, 1)` USD za barel. Pri priemernej cene `r sk_cislo(mean(y), 2)` USD/barel to predstavuje približne `r sk_cislo(100 * g$sigma / mean(y), 1)` % priemernej ceny – model je teda na presnú predikciu dennej ceny ropy pomerne nepresný. V porovnaní s jednoduchým modelom ($\hat\sigma$ = `r sk_cislo(g1$sigma, 2)` USD/barel) sa však chyba znížila.
### F-štatistika – F Stat.
F-test overuje spoločnú významnosť všetkých vysvetľujúcich premenných. Testujeme hypotézy:
$$
H_0: \beta_1 = \beta_2 = \beta_3 = 0
$$
$$
H_1: \text{aspoň jeden z koeficientov } \beta_1, \beta_2, \beta_3 \neq 0
$$
Testovacia štatistika:
$$
F = \frac{R^2/(k-1)}{(1-R^2)/(n-k)} \sim F(k-1,\; n-k)
$$
### P-hodnota, df a df.residual
- **df** = $k - 1$ = `r g$df` – počet vysvetľujúcich premenných (GPR, VIX, DXY); stupne voľnosti v čitateli F-testu.
- **df.residual** = $n - k$ = `r n` − `r k` = `r g$df.residual` – reziduálne stupne voľnosti; používajú sa pri výpočte $\hat\sigma$, t-testov aj F-testu.
- **p.value** – p-hodnota celkového F-testu.
Všetky tieto hodnoty si overíme ručným výpočtom a doplníme aj kritickú hodnotu F-rozdelenia na hladine 5 %:
```{r}
#| label: f-test-rucne
df1 <- k - 1
df2 <- n - k
sigma_rucne <- sqrt(RSS / df2)
F_rucne <- (R2_rucne / df1) / ((1 - R2_rucne) / df2)
p_rucne <- pf(F_rucne, df1, df2, lower.tail = FALSE)
F_krit <- qf(0.95, df1, df2)
data.frame(
Ukazovatel = c("Sigma", "F Stat.", "p.value", "df", "df.residual",
"Kritická hodnota F (α = 0,05)"),
Rucny_vypocet = c(sigma_rucne, F_rucne, p_rucne, df1, df2, F_krit),
glance = c(g$sigma, g$statistic, g$p.value, g$df, g$df.residual, NA)
) |>
kable(digits = 4, col.names = c("Ukazovateľ", "Ručný výpočet", "glance()"),
caption = "Overenie charakteristík presnosti a významnosti modelu") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
```
**Interpretácia:** F-štatistika má hodnotu `r sk_cislo(g$statistic, 2)`, čo je mnohonásobne viac ako kritická hodnota `r sk_cislo(F_krit, 2)`. P-hodnota je prakticky nulová (R ju zobrazuje ako 0, lebo je menšia ako najmenšie zobraziteľné číslo). **Nulovú hypotézu zamietame** – aspoň jedna z premenných GPR, VIX, DXY má štatisticky významný vplyv na cenu Brent, model ako celok je štatisticky významný.
Aj jednoduchý `model1` je štatisticky významný (F = `r sk_cislo(g1$statistic, 2)`, p = `r format(signif(g1$p.value, 3), decimal.mark = ",")`), hoci vysvetľuje menej ako 1 % variability. Ide o dôležité poučenie: **štatistická významnosť neznamená vysokú vysvetľovaciu schopnosť**. Pri tisícoch pozorovaní vychádzajú významné aj veľmi slabé vzťahy.
::: {.callout-warning}
P-hodnoty t-testov aj F-testu platia za klasických predpokladov (nezávislosť a normalita náhodných chýb, konštantný rozptyl). Pri denných časových radoch sú tieto predpoklady často porušené, čo ukáže grafická analýza nižšie. Významnosť preto treba brať s rezervou.
:::
# Grafická analýza kvality modelu
## Štyri diagnostické grafy
Ak hovoríme o vlastnostiach modelu ako celku, pozrime sa najskôr na štandardné diagnostické grafy funkcie `plot()`. Červená LOWESS krivka vzniká lokálnym vyhladzovaním pomocou váženej metódy najmenších štvorcov.
```{r}
#| label: diagplots
#| fig-cap: "Diagnostické grafy regresného modelu ceny Brent"
#| fig-width: 10
#| fig-height: 8
# Nastaviť rozloženie 2 x 2
par(mfrow = c(2, 2))
# Vykresliť všetky 4 diagnostické grafy modelu
plot(model)
# Resetovať layout
par(mfrow = c(1, 1))
```
### Residuals vs Fitted
- Os X: vyrovnané (predikované) hodnoty ceny Brent, os Y: rezíduá $e_t = y_t - \hat y_t$.
- Rezíduá síce kolíšu okolo nuly, ale **netvoria náhodné mračno** – body vytvárajú zreteľné pásy a „hady“. Je to typický znak časového radu: susedné dni majú takmer rovnakú cenu aj rovnaké hodnoty premenných, preto ležia v grafe tesne vedľa seba.
- Červená LOWESS krivka nie je vodorovná, čo naznačuje **nelinearitu** – lineárny tvar modelu nezachytáva vzťah úplne.
- Rozptyl rezíduí nie je pri všetkých vyrovnaných hodnotách rovnaký, môže ísť o **heteroskedasticitu**.
### Q-Q plot
- Os X: teoretické kvantily normálneho rozdelenia, os Y: štandardizované rezíduá.
- V strednej časti body ležia blízko priamky, na **koncoch sa však od nej odchyľujú**. Rozdelenie rezíduí preto nie je presne normálne.
- Pri `r n` pozorovaniach je mierna nenormalita menej vážna (vďaka centrálnej limitnej vete), vážnejším problémom je závislosť rezíduí v čase.
### Scale-Location
- Os X: vyrovnané hodnoty, os Y: druhá odmocnina absolútnych štandardizovaných rezíduí.
- Ak by bol rozptyl konštantný, červená čiara by bola vodorovná. Jej **zakrivenie naznačuje, že rozptyl rezíduí sa mení** s úrovňou predikovanej ceny (heteroskedasticita).
### Residuals vs Leverage
- Os X: leverage $h_{tt}$ (pákový účinok), os Y: štandardizované rezíduá; prerušované krivky sú hranice Cookovej vzdialenosti.
- Priemerná leverage je $k/n$ = `r sk_cislo(k / n, 5)`, maximálna `r sk_cislo(max(hatvalues(model)), 4)` – žiadne pozorovanie nemá extrémny pákový účinok.
- Najväčšia Cookova vzdialenosť je `r sk_cislo(max(cooks.distance(model)), 4)`, čo je hlboko pod orientačnou hranicou 0,5. **Žiadny jednotlivý deň neprimerane neovplyvňuje** odhad koeficientov.
- Najväčšie štandardizované rezíduá patria dňom z obdobia `r format(udaje$date[which.max(abs(rstandard(model)))], "%m/%Y")` – po ruskej invázii na Ukrajinu bola cena Brent výrazne vyššia, než by predikoval model.
## Doplnkové grafy pre časové rady
Keďže naše údaje sú denný časový rad, štandardné grafy doplníme o dva grafy v čase, ktoré problém modelu ukážu najzreteľnejšie.
### Skutočná a predikovaná cena v čase
```{r}
#| label: fig-skutocnost-predikcia
#| fig-cap: "Skutočná cena ropy Brent a cena predikovaná modelom"
#| fig-width: 9
#| fig-height: 4.5
graf_udaje <- data.frame(
date = udaje$date,
Skutocnost = udaje$brent_price,
Predikcia = fitted(model),
Reziduum = residuals(model)
)
ggplot(graf_udaje, aes(x = date)) +
geom_line(aes(y = Skutocnost, colour = "Skutočná cena"), linewidth = 0.5) +
geom_line(aes(y = Predikcia, colour = "Predikcia modelu"), linewidth = 0.5) +
scale_colour_manual(values = c("Skutočná cena" = "darkblue",
"Predikcia modelu" = "firebrick")) +
labs(x = "Rok", y = "Cena Brent (USD/barel)", colour = NULL) +
theme_minimal(base_size = 12) +
theme(legend.position = "top")
```
Model zachytáva hlavné vlny ceny ropy (napr. prepad koncom roka 2014 a vrchol na jar 2022), ale pri dlhších obdobiach sa od skutočnosti výrazne vzďaľuje: v rokoch 2015–2017 a počas pandémie v roku 2020 predikuje vyššiu cenu, než aká bola, a v rokoch 2011–2014 a 2022–2024 naopak nižšiu.
### Rezíduá v čase
```{r}
#| label: fig-reziduum-cas
#| fig-cap: "Rezíduá modelu v čase"
#| fig-width: 9
#| fig-height: 4.5
ggplot(graf_udaje, aes(x = date, y = Reziduum)) +
geom_hline(yintercept = 0, colour = "grey40") +
geom_hline(yintercept = c(-2, 2) * sigma(model),
colour = "firebrick", linetype = "dashed") +
geom_line(colour = "steelblue", linewidth = 0.4) +
labs(x = "Rok", y = "Rezíduum (USD/barel)") +
theme_minimal(base_size = 12)
# Korelácia rezíduí dvoch po sebe idúcich dní
autokor <- cor(residuals(model)[-1], residuals(model)[-n])
```
Prerušované čiary vyznačujú hranicu $\pm 2\hat\sigma$. Rezíduá **nie sú náhodne rozhádzané okolo nuly**, ale tvoria dlhé obdobia, keď model systematicky podhodnocuje (napr. 2012–2013, 2022–2023) alebo nadhodnocuje (2015–2017, 2020) cenu. Korelácia rezíduí dvoch po sebe idúcich dní je `r sk_cislo(autokor, 3)`, teda takmer 1.
Ide o **silnú autokoreláciu rezíduí**, ktorá porušuje predpoklad nezávislosti náhodných chýb. Dôsledkom je, že štandardné chyby koeficientov sú pravdepodobne podhodnotené a p-hodnoty z tabuliek vyššie vychádzajú „optimistickejšie“, než v skutočnosti sú.
# Záver
- **Vysvetľovacia schopnosť:** Viacnásobný model vysvetľuje približne `r sk_cislo(100 * g$r.squared, 1)` % variability ceny Brent ($\bar R^2$ = `r sk_cislo(g$adj.r.squared, 4)`), zatiaľ čo samotný index GPR zo zadania 4 iba `r sk_cislo(100 * g1$r.squared, 2)` %. Pridanie VIX a DXY model výrazne zlepšilo.
- **Významnosť a presnosť:** Model je ako celok štatisticky významný (F = `r sk_cislo(g$statistic, 2)`, df = `r g$df` a `r g$df.residual`, p ≈ 0). Typická chyba predikcie je však približne `r sk_cislo(g$sigma, 1)` USD/barel.
- **Grafická analýza:** Žiadne pozorovanie nie je neprimerane vplyvné, ale rezíduá vykazujú nelinearitu, meniaci sa rozptyl, odchýlky od normality a najmä silnú autokoreláciu v čase.
Model preto považujeme za užitočný **opisný** model súvislosti ceny ropy s makroekonomickým prostredím, nie za spoľahlivý prognostický model. Riešením by mohlo byť modelovanie denných výnosov (`brent_return`) namiesto cien, prípadne zahrnutie oneskorených hodnôt (`brent_lag_1`), ktoré databáza obsahuje.
Obsah pamäte si uložíme, aby sme ho mohli v ďalšom zadaní načítať príkazom `load()`:
```{r}
#| label: ulozenie
save.image("pamat_ropa.Rdata")
```