# Spustite raz v konzole, iba ak tieto baliky chybaju.
install.packages(c("broom", "knitr", "kableExtra", "ggplot2", "dplyr",
"tidyr", "tseries", "car"))Econometrics in R - cvičenie 5
Základná diagnostika modelu: ceny bývania na Slovensku
Pokračujem s databázou Eurostatu House price index (prc_hpi_a), index cien bývania, 2015 = 100.
library(broom)
library(knitr)
library(kableExtra)
library(ggplot2)
library(dplyr)
library(tidyr)Príprava databázy
Rovnako ako v minulom cvičení načítam súbor udaje/ceny_byvania_eu.csv (ak chýba, stiahne sa z Eurostatu) a vytvorím tabuľku, kde jeden riadok je jeden rok.
cesta <- "udaje/ceny_byvania_eu.csv"
if (!file.exists(cesta)) {
url <- paste0(
"https://ec.europa.eu/eurostat/api/dissemination/sdmx/2.1/data/prc_hpi_a/",
"A.TOTAL+DW_NEW+DW_EXST.I15_A_AVG.SK+CZ+HU+PL+AT+DE+EU27_2020/",
"?format=SDMX-CSV&startPeriod=2010"
)
dir.create("udaje", showWarnings = FALSE)
write.table(read.csv(url), cesta, sep = ";", dec = ".", row.names = FALSE)
}
udaje <- read.csv2(cesta, sep = ";", dec = ".",
na.strings = c("", "NA"), stringsAsFactors = FALSE)
udaje <- udaje[udaje$purchase == "TOTAL", c("geo", "TIME_PERIOD", "OBS_VALUE")]
udaje.roky <- as.data.frame(pivot_wider(udaje, names_from = geo,
values_from = OBS_VALUE))
udaje.roky <- udaje.roky[order(udaje.roky$TIME_PERIOD), ]
names(udaje.roky)[names(udaje.roky) == "TIME_PERIOD"] <- "Rok"
names(udaje.roky)[names(udaje.roky) == "EU27_2020"] <- "EU"
udaje.roky <- na.omit(udaje.roky[, c("Rok", "SK", "CZ", "EU")])
rownames(udaje.roky) <- udaje.roky$Rok # v grafoch sa zobrazia roky
head(udaje.roky) Rok SK CZ EU
2010 2010 97.06 95.2 100.26
2011 2011 95.60 95.3 100.88
2012 2012 92.94 93.9 98.76
2013 2013 93.69 93.9 97.06
2014 2014 94.96 96.2 97.75
2015 2015 100.00 100.0 100.00
Uloženie workspace, čistenie workspace
- Aby som nemusel v každom cvičení znova načítavať a upravovať databázu, uložím si celý obsah pamäte (workspace) do súboru:
save.image("pamat.Rdata")- Mazanie niektorých objektov z pamäte:
pokus1 <- 6
pokus2 <- "text" # tieto dva objekty chcem vymazat
rm(list = c("pokus1", "pokus2"))
ls() # co zostalo v pamati[1] "cesta" "ciarka" "pformat" "udaje" "udaje.roky"
- Čistenie celej pamäte:
rm(list = ls())
ls()character(0)
- Opätovné načítanie uloženého workspace:
load("pamat.Rdata")
ls()[1] "cesta" "ciarka" "pformat" "udaje" "udaje.roky"
Na začiatku ďalších cvičení teda stačí load("pamat.Rdata") a na konci dokumentu save.image("pamat.Rdata"). Nepotrebné objekty budem priebežne mazať príkazom rm().
Lineárna regresia
Odhadujem model z minulého cvičenia:
\[ SK_t = \beta_0 + \beta_1 CZ_t + \beta_2 EU_t + u_t \]
kde \(SK_t\), \(CZ_t\) a \(EU_t\) sú indexy cien bývania na Slovensku, v Česku a priemer EÚ v roku \(t\).
model <- lm(SK ~ CZ + EU, data = udaje.roky)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("Variable", "Estimate", "Std. Error", "t value",
"p-value", "Significance"),
caption = "Regresné koeficienty"
) |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| Variable | Estimate | Std. Error | t value | p-value | Significance |
|---|---|---|---|---|---|
| (Intercept) | -42.7681 | 33.6774 | -1.2699 | 0.2264 | |
| CZ | 0.1638 | 0.2413 | 0.6788 | 0.5091 | |
| EU | 1.2439 | 0.5690 | 2.1860 | 0.0477 | * |
Interpretácia výsledkov
Stĺpce tabuľky: Estimate je odhad koeficientu \(\hat\beta_j\), Std. Error jeho štandardná chyba (neistota odhadu), t value je testovacia štatistika \(t_j = \hat\beta_j / SE(\hat\beta_j)\) a p-value je p-hodnota t-testu.
t-test pre každý koeficient testuje
\[ H_0:\beta_j=0 \qquad H_1:\beta_j\neq 0. \]
Ak je p-hodnota menšia ako 0,05, zamietam \(H_0\) a koeficient považujem za štatisticky významný. Pri platnosti \(H_0\) má štatistika t-rozdelenie s \(n-k\) = 13 stupňami voľnosti.
Koeficient pri CZ: Ak je index v Česku vyšší o 1 bod (pri rovnakom indexe EÚ), index na Slovensku je v priemere vyšší o 0,164 bodu. Koeficient nie je štatisticky významný na 5 % hladine (p = 0,509).
Koeficient pri EU: Ak je index EÚ vyšší o 1 bod (pri rovnakom indexe v Česku), index na Slovensku je v priemere vyšší o 1,244 bodu. Koeficient je štatisticky významný na 5 % hladine (p = 0,048).
Absolútny člen (Intercept): hodnota indexu SK, ak by indexy CZ aj EÚ boli nulové. Má len technický význam (priesečník s osou).
Pozor: Všetky tri indexy rastú v čase, takže spolu silno súvisia (multikolinearita). Štandardné chyby sú preto väčšie a koeficienty môžu vyjsť nevýznamné alebo s nečakaným znamienkom, aj keď model ako celok funguje dobre.
Kvalita vyrovnania modelu
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 = "Model summary"
) |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| R.squared | Adj.R.squared | Sigma | F Stat. | p.value | df | logLik | AIC | BIC | df.residual | N |
|---|---|---|---|---|---|---|---|---|---|---|
| 0.9946 | 0.9938 | 2.9474 | 1207.462 | 0 | 2 | -38.3368 | 84.6737 | 87.764 | 13 | 16 |
Koeficient determinácie \(R^2 = 1 - RSS/TSS\) = 0,995. Model vysvetľuje približne 99,5 % variability indexu cien na Slovensku.
Upravený \(\bar R^2\) = 0,994 zohľadňuje počet premenných. Je trochu nižší ako \(R^2\), lebo mám len 16 pozorovaní.
Sigma \(\hat\sigma = \sqrt{RSS/(n-k)}\) = 2,95: skutočný index sa od modelu typicky odchyľuje asi o 2,9 bodu indexu.
F-test overuje, či sú vysvetľujúce premenné spoločne významné:
\[ H_0:\beta_1=\beta_2=0 \qquad H_1: \text{aspoň jeden } \beta_j \neq 0 \]
\[ F=\frac{R^2/(k-1)}{(1-R^2)/(n-k)} = 1207,5, \qquad p < 0{,}001. \]
Keďže p < 0,05, zamietam nulovú hypotézu: model ako celok je štatisticky významný.
df = 2 (počet vysvetľujúcich premenných), df.residual = \(n-k\) = 13, N = 16 rokov.
AIC a BIC použijem neskôr pri porovnaní modelov (nižšia hodnota = lepší model). logLik samostatne neinterpretujem.
Overenie vzťahu medzi RSS (deviance) a sigmou:
deviance(model)[1] 112.9353
sigma(model)[1] 2.947428
sqrt(deviance(model) / df.residual(model))[1] 2.947428
Grafická analýza kvality modelu
par(mfrow = c(2, 2))
plot(model)par(mfrow = c(1, 1))V grafoch sú pozorovania označené rokmi. Čo sledujem v jednotlivých grafoch:
- Residuals vs Fitted: rezíduá by mali kolísať okolo nuly bez tvaru. Zakrivená červená čiara naznačuje nelinearitu. Pri časovom rade si všímam aj to, či rezíduá susedných rokov nemajú rovnaké znamienko (to by naznačovalo autokoreláciu).
- Normal Q-Q: body by mali ležať blízko priamky. Odchýlky na koncoch znamenajú ťažšie chvosty alebo odľahlé hodnoty.
- Scale-Location: ak je červená čiara približne vodorovná, rozptyl rezíduí je konštantný (homoskedasticita). Lievik naznačuje heteroskedasticitu.
- Residuals vs Leverage: body za prerušovanými čiarami Cookovej vzdialenosti (0,5 a 1) by výrazne ovplyvňovali odhad.
Konkrétne pre môj model: najvyššiu leverage má rok 2020 (h = 0,387; priemer je p/n = 0,188) a najväčšiu Cookovu vzdialenosť rok 2025 (D = 0,863). Žiadne pozorovanie neprekračuje hranicu 1, takže žiadny rok neovplyvňuje odhad neprimerane.
Test normality rezíduí
Rezíduá \(\mathbf e = (\mathbf I - \mathbf H)\mathbf u\) sú lineárnou kombináciou náhodných zložiek, preto normalitu \(\mathbf u\) testujem nepriamo cez rezíduá.
Jarqueov–Berov test
- \(H_0\): rezíduá sú normálne rozdelené.
- \(H_1\): rezíduá nie sú normálne rozdelené.
\[ JB = \frac{n}{6}\left(S^2+\frac{(K-3)^2}{4}\right) \overset{a}{\sim} \chi^2(2) \]
jb_test <- tseries::jarque.bera.test(residuals(model))
tidy(jb_test) |>
select(statistic, p.value, method) |>
kable(digits = 4, col.names = c("JB statistic", "p-value", "Test"),
caption = "Jarque-Bera test normality rezíduí") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| JB statistic | p-value | Test |
|---|---|---|
| 0.501 | 0.7784 | Jarque Bera Test |
p-hodnota (0,778) je väčšia ako 0,05, preto hypotézu o normalite rezíduí nezamietam. Treba však pamätať, že JB test je asymptotický a pri iba 16 pozorovaniach má malú silu, preto ho dopĺňam Q-Q grafom.
Test odľahlých pozorovaní – outlierTest()
Test využíva externe študentizované rezíduá \(t_i = e_i / (\hat\sigma_{(i)}\sqrt{1-h_{ii}})\) a Bonferroniho korekciu \(p_{Bonf} = \min(1, n \cdot p_i)\).
- \(H_0\): pozorovanie nie je odľahlé.
- \(H_1\): pozorovanie je odľahlé.
outlier_test <- car::outlierTest(model)
outlier_table <- data.frame(
Observation = names(outlier_test$rstudent),
rstudent = unname(outlier_test$rstudent),
unadjusted.p = unname(outlier_test$p),
bonf.p = unname(outlier_test$bonf.p)
)
outlier_table |>
kable(digits = 4,
col.names = c("Rok", "Studentized residual",
"Unadjusted p-value", "Bonferroni p-value"),
caption = "Test odľahlých pozorovaní (Bonferroni)") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| Rok | Studentized residual | Unadjusted p-value | Bonferroni p-value |
|---|---|---|---|
| 2025 | 2.7792 | 0.0167 | 0.2668 |
Najextrémnejšie je pozorovanie z roku 2025. Bonferroniho p-hodnota je 0,267, teda väčšia ako 0,05: v modeli nemám štatisticky významne odľahlé pozorovanie.
Úprava modelu
Podobne ako v predlohe skúsim logaritmickú transformáciu vysvetľujúcich premenných:
\[ SK_t = \beta_0 + \beta_1 \ln(CZ_t) + \beta_2 \ln(EU_t) + u_t \]
Interpretácia: ak sa index v Česku zvýši o 1 %, index na Slovensku sa zmení približne o \(\beta_1/100\) bodu.
model2 <- lm(SK ~ log(CZ) + log(EU), data = udaje.roky)
tidy(model2) |>
kable(digits = 4,
col.names = c("Variable", "Estimate", "Std. Error", "t value", "Pr(>|t|)"),
caption = "Model s logaritmami") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| Variable | Estimate | Std. Error | t value | Pr(>|t|) |
|---|---|---|---|---|
| (Intercept) | -832.9038 | 217.4374 | -3.8305 | 0.0021 |
| log(CZ) | 0.2143 | 49.8285 | 0.0043 | 0.9966 |
| log(EU) | 201.6146 | 96.6306 | 2.0864 | 0.0572 |
Grafická diagnostika upraveného modelu
par(mfrow = c(2, 2))
plot(model2)par(mfrow = c(1, 1))Test normality a odľahlých hodnôt upraveného modelu
jb_test2 <- tseries::jarque.bera.test(residuals(model2))
tidy(jb_test2) |>
select(statistic, p.value, method) |>
kable(digits = 4, col.names = c("JB statistic", "p-value", "Test")) |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| JB statistic | p-value | Test |
|---|---|---|
| 4.463 | 0.1074 | Jarque Bera Test |
car::outlierTest(model2) rstudent unadjusted p-value Bonferroni p
2025 5.105662 0.00025935 0.0041497
Malé úlohy
Iná modifikácia modelu
V minulom cvičení som videl, že rast cien sa v čase zrýchľoval (rezíduá trendu mali tvar písmena U). Skúsim preto kvadratický trend:
\[ SK_t = \beta_0 + \beta_1 Rok_t + \beta_2 Rok_t^2 + u_t \]
udaje.roky$t <- udaje.roky$Rok - 2010 # cas od roku 2010 (0, 1, 2, ...)
model3 <- lm(SK ~ t + I(t^2), data = udaje.roky)
tidy(model3) |>
kable(digits = 4,
col.names = c("Variable", "Estimate", "Std. Error", "t value", "Pr(>|t|)"),
caption = "Model s kvadratickým trendom") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| Variable | Estimate | Std. Error | t value | Pr(>|t|) |
|---|---|---|---|---|
| (Intercept) | 93.2335 | 3.2838 | 28.3922 | 0.000 |
| t | -0.7352 | 1.0158 | -0.7237 | 0.482 |
| I(t^2) | 0.5481 | 0.0653 | 8.3913 | 0.000 |
Rok som posunul tak, aby začínal nulou (t = Rok - 2010). Výsledky sa tým nemenia, len sa čísla v R lepšie počítajú (rok 2025² je veľmi veľké číslo).
Porovnanie modelov pomocou AIC
Všetky tri modely majú rovnakú vysvetľovanú premennú (SK) aj rovnaké pozorovania, preto ich môžem porovnať pomocou AIC (a BIC).
porovnanie <- data.frame(
Model = c("SK ~ CZ + EU", "SK ~ log(CZ) + log(EU)", "SK ~ t + t^2"),
R2 = c(glance(model)$r.squared, glance(model2)$r.squared, glance(model3)$r.squared),
Adj.R2 = c(glance(model)$adj.r.squared, glance(model2)$adj.r.squared,
glance(model3)$adj.r.squared),
AIC = c(AIC(model), AIC(model2), AIC(model3)),
BIC = c(BIC(model), BIC(model2), BIC(model3)),
JB.p = c(jb_test$p.value, jb_test2$p.value,
tseries::jarque.bera.test(residuals(model3))$p.value)
)
porovnanie |>
kable(digits = 3, caption = "Porovnanie modelov") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| Model | R2 | Adj.R2 | AIC | BIC | JB.p |
|---|---|---|---|---|---|
| SK ~ CZ + EU | 0.995 | 0.994 | 84.674 | 87.764 | 0.778 |
| SK ~ log(CZ) + log(EU) | 0.990 | 0.988 | 95.436 | 98.527 | 0.107 |
| SK ~ t + t^2 | 0.985 | 0.983 | 101.180 | 104.270 | 0.204 |
Podľa AIC je najlepší model SK ~ CZ + EU (AIC = 84,67). Pôvodný a logaritmický model sa líšia o 10,76 bodu AIC, teda logaritmická transformácia model nezlepšila.
par(mfrow = c(2, 2))
plot(model3)par(mfrow = c(1, 1))Záver
Odhadol som viacnásobnú lineárnu regresiu indexu cien bývania na Slovensku a urobil základnú diagnostiku: t-testy, F-test, \(R^2\), grafickú analýzu rezíduí, Jarqueov–Berov test a test odľahlých hodnôt. Model vysvetľuje veľkú časť variability, ale treba si uvedomiť dve obmedzenia:
- Mám len 16 pozorovaní, takže testy majú malú silu.
- Ide o časové rady, ktoré spolu rastú. Vysoké \(R^2\) preto môže byť čiastočne spôsobené spoločným trendom. V ďalších cvičeniach by bolo vhodné otestovať aj autokoreláciu rezíduí (napr. Durbin-Watsonov test).
Na konci uložím workspace, aby som ho mohol nabudúce načítať príkazom load("pamat.Rdata").
save.image("pamat.Rdata")
rm(list = ls())