Po autokoreácii a heteroskedasticite rezíduí je multikolinearita tretím závažným porušením predpokladov použitia metódy najmenších štvorcov. Tu sa okrem iného predpokadá, že matica \(\mathbf X\) je tvorená lineárne nezávislými riadkami a tiež stĺpcami, čo zabezpečí regularitu matice \(\mathbf X^T\mathbf X\) a teda možnosť jej inverzie. Tá sa používa pri odhadoch regresných koeficientov. V praxi sa ale môže stať, že vzniká "takmer singulárna matica \(\mathbf X^T\mathbf X\) ", t.j. matica \(\mathbf X\) je tvorená "približne" lineárne závislými stĺpcami, t.j. existuje taká ich lineárna kombinácia, v ktorej
\[ x_{il} = \alpha_0 + \alpha_1 x_{i1} + \dots + \alpha_{l-1} x_{i,(l-1)} + \alpha_{l+1} x_{i,(l+1)} + \alpha_k x_{i,k} + \nu_i \]
Tu \(\nu_i\) sú rádovo menšie čísla , než regresory \(x_{.}\), t.j. \((\forall i)(\nu_i << x_{.,i})\). V tomto prípade je inverzná matica \((\mathbf X^T\mathbf X)^{-1}\) veľmi nestabilná a obsahuje na hlavnej diagonále veľmi veľké hodnoty. Táto matica sa používa pri výpočtoch \[\hat \beta = (\mathbf X^T\mathbf X)^{-1} \mathbf X^T \mathbf y\] a tiež \[\text{std}(\beta_i) = \sqrt{\sigma^2 (\mathbf X^T \mathbf X)^{-1}_{ii}}.\] To spôsobuje nestabilitu odhadovaných regresných koeficientov a ich nadhodnotené rozptyly.
Tento problém nazývame problémom multikolinearity.
Multikolinearita patrí medzi najčastejšie problémy viacnásobnej
lineárnej regresie.
Je dôležité jasne rozlišovať dva fakty:
V tejto časti načítame súbor nhlplayoffs.csv, popíšeme premenné a definujeme východiskový regresný model. Ako závislá premenná použijeme wins (počet víťazstiev v playoff sérii) a ako vysvetľujúce premenné zvolíme goals_scored, goals_against a games. Tieto premenné sú vzájomne úzko prepojené (goals_scored a goals_against určujú goal_differential), čo vytvára priestor pre multikolinearitu — cieľom cvičenia je práve skúmať a riešiť ju.
# knitr options
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)
# balíčky
if (!requireNamespace("car", quietly = TRUE)) install.packages("car")
library(car)
## Loading required package: carData
if (!requireNamespace("psych", quietly = TRUE)) install.packages("psych")
library(psych)
##
## Attaching package: 'psych'
## The following object is masked from 'package:car':
##
## logit
# načítanie dát
nhl <- read.csv("nhlplayoffs.csv", dec = ".", sep = ",", header = TRUE, stringsAsFactors = FALSE)
# kontrola
dim(nhl)
## [1] 1009 13
str(nhl)
## 'data.frame': 1009 obs. of 13 variables:
## $ rank : int 1 2 3 4 5 6 7 8 9 10 ...
## $ team : chr "Colorado Avalanche" "Tampa Bay Lightning" "New York Rangers" "Edmonton Oilers" ...
## $ year : int 2022 2022 2022 2022 2022 2022 2022 2022 2022 2022 ...
## $ games : int 20 23 20 16 14 12 12 10 7 7 ...
## $ wins : int 16 14 10 8 7 6 5 4 3 3 ...
## $ losses : int 4 9 10 8 7 6 7 6 4 4 ...
## $ ties : int 0 0 0 0 0 0 0 0 0 0 ...
## $ shootout_wins : int 5 1 1 1 1 1 1 2 0 1 ...
## $ shootout_losses : int 1 2 2 2 0 1 1 0 0 0 ...
## $ win_loss_percentage: num 0.8 0.609 0.5 0.5 0.5 0.5 0.417 0.4 0.429 0.429 ...
## $ goals_scored : int 85 67 62 65 37 40 35 23 20 17 ...
## $ goals_against : int 55 61 58 59 40 38 39 32 24 27 ...
## $ goal_differential : int 30 6 4 6 -3 2 -4 -9 -4 -10 ...
summary(nhl[c("games","wins","losses","goals_scored","goals_against","goal_differential")])
## games wins losses goals_scored
## Min. : 2.000 Min. : 0.000 Min. : 0.000 Min. : 0.00
## 1st Qu.: 5.000 1st Qu.: 1.000 1st Qu.: 4.000 1st Qu.:11.00
## Median : 7.000 Median : 3.000 Median : 4.000 Median :20.00
## Mean : 9.364 Mean : 4.657 Mean : 4.657 Mean :26.63
## 3rd Qu.:12.000 3rd Qu.: 7.000 3rd Qu.: 6.000 3rd Qu.:37.00
## Max. :27.000 Max. :18.000 Max. :12.000 Max. :98.00
## goals_against goal_differential
## Min. : 0.00 Min. :-27
## 1st Qu.:16.00 1st Qu.: -6
## Median :22.00 Median : -2
## Mean :26.63 Mean : 0
## 3rd Qu.:35.00 3rd Qu.: 3
## Max. :91.00 Max. : 49
# vyber dát pre analýzu (odstránime identifikačné polia ako team, year ak sú nepotrebné)
nhl2 <- nhl[, c("wins","games","goals_scored","goals_against","goal_differential")]
head(nhl2)
## wins games goals_scored goals_against goal_differential
## 1 16 20 85 55 30
## 2 14 23 67 61 6
## 3 10 20 62 58 4
## 4 8 16 65 59 6
## 5 7 14 37 40 -3
## 6 6 12 40 38 2
Základný model (východiskový) odhadneme takto:
model_base <- lm(wins ~ goals_scored + goals_against + games, data = nhl2)
summary(model_base)
##
## Call:
## lm(formula = wins ~ goals_scored + goals_against + games, data = nhl2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.3909 -0.4314 -0.0324 0.3591 3.1894
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.001460 0.050968 -19.65 <2e-16 ***
## goals_scored 0.113663 0.003562 31.91 <2e-16 ***
## goals_against -0.117244 0.003657 -32.06 <2e-16 ***
## games 0.614489 0.011831 51.94 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.7105 on 1005 degrees of freedom
## Multiple R-squared: 0.9727, Adjusted R-squared: 0.9727
## F-statistic: 1.195e+04 on 3 and 1005 DF, p-value: < 2.2e-16
# uložíme si koeficienty a štandardné chyby pre neskoršie porovnanie
coef_base <- summary(model_base)$coefficients
coef_base
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.0014605 0.050967749 -19.64891 5.395933e-73
## goals_scored 0.1136630 0.003562468 31.90569 7.548804e-155
## goals_against -0.1172439 0.003656798 -32.06190 6.331893e-156
## games 0.6144894 0.011830852 51.93957 7.517920e-287
Skontrolujeme korelácie medzi vysvetľujúcimi premennými — vysoké korelácie naznačujú multikolinearitu.
# korelačná matica (Pearson)
preds <- nhl2[, c("goals_scored","goals_against","games","goal_differential")]
cor_matrix <- cor(preds, use = "pairwise.complete.obs")
cor_matrix
## goals_scored goals_against games goal_differential
## goals_scored 1.0000000 0.9088340 0.9401903 0.7229132
## goals_against 0.9088340 1.0000000 0.8948069 0.3687775
## games 0.9401903 0.8948069 1.0000000 0.6128774
## goal_differential 0.7229132 0.3687775 0.6128774 1.0000000
# doplnkový prehľad pomocou psych::pairs.panels
pairs.panels(preds)
Vypočítame Variance Inflation Factors (VIF) pre základný model. Hodnoty VIF > 5 (alebo 10 podľa prísnejšieho pravidla) indikujú problém.
vif_base <- vif(model_base)
vif_base
## goals_scored goals_against games
## 10.735066 6.249835 9.372463
# zoradíme podľa veľkosti
sort(vif_base, decreasing = TRUE)
## goals_scored games goals_against
## 10.735066 9.372463 6.249835
VIF > 10 = vážna multikolinearita
Premenná games je veľmi úzko naviazaná na výkonnosť tímu (viac zápasov → viac príležitostí skórovať → viac inkasovaných gólov).
Condition number (číslo kondície) poskytuje doplňujúce informácie o multikolinearite.
# použijeme singular value decomposition na maticu návrhu (bez interceptu)
X <- model.matrix(~ goals_scored + goals_against + games, data = nhl2)
svdvals <- svd(scale(X, center = TRUE, scale = FALSE))$d
condition_number <- max(svdvals) / min(svdvals)
condition_number
## [1] Inf
# alternatívne: kappa
kappa(X)
## [1] 63.39702
0–15 → OK
15–30 → stredná multikolinearita
30+ → silná
Hodnota 63.39702 znamená, že multikolinearita je extrémna podľa tohto metrického pohľadu.
Ukážeme tri bežné prístupy: vynechanie premennej, škálovanie premenných a iná úprava premennej, ktorá zachová interpretovateľnosť.
V jednej verzii modelu vynecháme premennú goals_against (predpoklad: časť informácie je zachytená v goals_scored a games/alebo goal_differential). Odhadneme model a porovnáme koeficienty a VIF.
model_drop <- lm(wins ~ goals_scored + games, data = nhl2)
summary(model_drop)
##
## Call:
## lm(formula = wins ~ goals_scored + games, data = nhl2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3.2522 -0.5349 -0.1333 0.4437 4.4068
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.764826 0.064062 -27.55 <2e-16 ***
## goals_scored 0.062935 0.004537 13.87 <2e-16 ***
## games 0.506837 0.016127 31.43 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.01 on 1006 degrees of freedom
## Multiple R-squared: 0.9448, Adjusted R-squared: 0.9447
## F-statistic: 8617 on 2 and 1006 DF, p-value: < 2.2e-16
vif(model_drop)
## goals_scored games
## 8.617555 8.617555
# porovnanie koeficientov
cbind(base = coef_base[,1], drop = summary(model_drop)$coefficients[,1])
## base drop
## (Intercept) -1.0014605 -1.76482591
## goals_scored 0.1136630 0.06293465
## goals_against -0.1172439 0.50683662
## games 0.6144894 -1.76482591
Škálovanie (štandardizácia na strednú hodnotu 0 a smerodajnú odchýlku 1) zlepší numerickú stabilitu a môže ovplyvniť VIF. Pozor: štandardizované koeficienty menia jednotky interpretácie.
nhl_scaled <- nhl2
nhl_scaled[c("goals_scored","goals_against","games")] <- scale(nhl_scaled[c("goals_scored","goals_against","games")])
model_scaled <- lm(wins ~ goals_scored + goals_against + games, data = nhl_scaled)
summary(model_scaled)
##
## Call:
## lm(formula = wins ~ goals_scored + goals_against + games, data = nhl_scaled)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.3909 -0.4314 -0.0324 0.3591 3.1894
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.65709 0.02237 208.20 <2e-16 ***
## goals_scored 2.33943 0.07332 31.91 <2e-16 ***
## goals_against -1.79376 0.05595 -32.06 <2e-16 ***
## games 3.55848 0.06851 51.94 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.7105 on 1005 degrees of freedom
## Multiple R-squared: 0.9727, Adjusted R-squared: 0.9727
## F-statistic: 1.195e+04 on 3 and 1005 DF, p-value: < 2.2e-16
vif(model_scaled)
## goals_scored goals_against games
## 10.735066 6.249835 9.372463
# condition number pre škálovaných premenných
Xs <- model.matrix(~ goals_scored + goals_against + games, data = nhl_scaled)
svdvals_s <- svd(scale(Xs, center = TRUE, scale = FALSE))$d
max(svdvals_s)/min(svdvals_s)
## [1] Inf
Aby sme zachovali interpretovateľnosť a zároveň odstránili redundanciu, môžeme namiesto dvoch úzko spätých premenných (goals_scored a goals_against) použiť jedine premennú goal_differential (ktorá má priamu interpretáciu: rozdiel skóre). Odhadneme model s touto úpravou a vyhodnotíme VIF a kondíciu.
model_diff <- lm(wins ~ goal_differential + games, data = nhl2)
summary(model_diff)
##
## Call:
## lm(formula = wins ~ goal_differential + games, data = nhl2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.4040 -0.4155 -0.0419 0.3646 3.1843
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.001938 0.050962 -19.66 <2e-16 ***
## goal_differential 0.115369 0.003066 37.63 <2e-16 ***
## games 0.604356 0.004890 123.58 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.7105 on 1006 degrees of freedom
## Multiple R-squared: 0.9727, Adjusted R-squared: 0.9727
## F-statistic: 1.793e+04 on 2 and 1006 DF, p-value: < 2.2e-16
vif(model_diff)
## goal_differential games
## 1.601585 1.601585
# condition number
Xdiff <- model.matrix(~ goal_differential + games, data = nhl2)
kappa(Xdiff)
## [1] 33.03597
V závere stručne zhrnieme pozorovania: ktoré premenné spôsobovali multikolinearitu, aké boli VIF a condition number pre jednotlivé modely, a ktorá z navrhnutých úprav sa javí ako najvhodnejšia z hľadiska interpretovateľnosti a stability odhadu.
# vytiahneme dôležité čísla pre stručné zhrnutie:
res <- list(
vif_base = vif_base,
cond_base = condition_number,
vif_drop = vif(model_drop),
cond_drop = kappa(model.matrix(~ goals_scored + games, data = nhl2)),
vif_scaled = vif(model_scaled),
cond_scaled = max(svdvals_s)/min(svdvals_s),
vif_diff = vif(model_diff),
cond_diff = kappa(Xdiff)
)
res
## $vif_base
## goals_scored goals_against games
## 10.735066 6.249835 9.372463
##
## $cond_base
## [1] Inf
##
## $vif_drop
## goals_scored games
## 8.617555 8.617555
##
## $cond_drop
## [1] 66.56644
##
## $vif_scaled
## goals_scored goals_against games
## 10.735066 6.249835 9.372463
##
## $cond_scaled
## [1] Inf
##
## $vif_diff
## goal_differential games
## 1.601585 1.601585
##
## $cond_diff
## [1] 33.03597
V tejto časti pridáme odhad ridge regresie, ktorá rieši multikolinearitu penalizáciou veľkých koeficientov.
# balíček glmnet
if (!requireNamespace("glmnet", quietly = TRUE)) install.packages("glmnet")
library(glmnet)
# príprava dát pre glmnet
X <- as.matrix(nhl2[, c("goals_scored","goals_against","games")])
y <- nhl2$wins
# ridge regresia: alpha = 0
ridge_model <- glmnet(X, y, alpha = 0)
# výber lambda pomocou cross-validation
cv_ridge <- cv.glmnet(X, y, alpha = 0)
cv_ridge$lambda.min
## [1] 0.4150871
# koeficienty pri optimálnom lambda
coef(cv_ridge, s = "lambda.min")
## 4 x 1 sparse Matrix of class "dgCMatrix"
## lambda.min
## (Intercept) -1.13204326
## goals_scored 0.09151874
## goals_against -0.02484388
## games 0.42862101
# vizualizácia
plot(cv_ridge)
Koeficienty sú menšie a stabilnejšie než v OLS.
Zaujímavé je, že ridge „vrátil” logicky očakávanú znamienkovú štruktúru:
goals_scored → pozitívny vplyv na wins,
goals_against → negatívny vplyv na wins,
To je znak, že OLS bol skreslený bezpenalizačnou multikolinearitou.
Premenná games zostáva najsilnejším prediktorom (+0.61). # PCA PCA pomáha odstrániť multikolinearitu transformáciou pôvodných vysvetľujúcich premenných na nové, navzájom nekorelované komponenty.
# vyber vysvetľujúcich premenných
preds <- nhl2[, c("goals_scored","goals_against","games")]
# PCA s centráciou a škálovaním
pca_model <- prcomp(preds, center = TRUE, scale. = TRUE)
summary(pca_model)
## Importance of components:
## PC1 PC2 PC3
## Standard deviation 1.6821 0.33484 0.24194
## Proportion of Variance 0.9431 0.03737 0.01951
## Cumulative Proportion 0.9431 0.98049 1.00000
# komponenty
pca_model$rotation
## PC1 PC2 PC3
## goals_scored -0.5814985 0.2911150 -0.7596786
## goals_against -0.5719086 -0.8103922 0.1272204
## games -0.5786019 0.5084452 0.6377330
# skóre pozorovaní
head(pca_model$x)
## PC1 PC2 PC3
## [1,] -3.772238 0.25676943 -0.7471384
## [2,] -3.787724 -0.05223809 0.2975026
## [3,] -3.234574 -0.22745052 0.1267274
## [4,] -2.957054 -0.58918663 -0.4161882
## [5,] -1.255911 -0.15440741 0.2390351
## [6,] -1.066077 -0.18163680 -0.1085755
# vizualizácia variancie
plot(pca_model, type = "l")
Takmer celá informácia (95.5 %) je v PC1 → znamená to, že premenné sú
extrémne redundantné.
PC2 a PC3 neobsahujú výraznú dodatočnú informáciu.
Pozitívne a vysoké hodnoty pri Goals Scored aj Goals Against znamenajú:
PC1 meria celkovú úroveň ofenzívy/defenzívy, teda „koľko sa v sérii skóruje na oboch stranách“.
Ide o spoločný signál, ktorý vysvetľuje väčšinu variancie.
PC2 zachytáva rozdiel medzi strelenými a inkasovanými gólmi, teda kvalitu tímu.