knitr::opts_chunk$set(
echo = TRUE,
message = FALSE,
warning = FALSE
)Cvičenie 5 – Základná diagnostika regresného modelu
Moja vysnívaná kariéra: Dátový vedec
1 Úvod
V tomto cvičení pokračujem v analýze svojej databázy Latest Data Science Salaries.
Moja hlavná téma je Moja vysnívaná kariéra: Dátový vedec.
V predchádzajúcom cvičení som vytvoril lineárny regresný model, ktorým som skúmal výšku platu pracovnej pozície Data Scientist.
V tomto cvičení sa zameriam najmä na:
- uloženie a obnovenie workspace,
- interpretáciu regresných koeficientov a t-testov,
- F-test celého regresného modelu,
- koeficient determinácie (R^2),
- grafickú analýzu rezíduí,
- test normality rezíduí,
- identifikáciu odľahlých a vplyvných pozorovaní,
- modifikáciu pôvodného regresného modelu.
2 Príprava databázy
Databázu mám uloženú v priečinku údaje.
cesta <- "údaje/Latest_Data_Science_Salaries.csv"
file.exists(cesta)[1] TRUE
Výsledkom by mala byť hodnota TRUE.
2.1 Načítanie údajov
udaje <- read.csv(
cesta,
sep = ",",
dec = ".",
na.strings = c("", "NA"),
check.names = FALSE,
stringsAsFactors = FALSE
)
head(udaje) Job Title Employment Type Experience Level Expertise Level Salary
1 Data Engineer Full-Time Senior Expert 210000
2 Data Engineer Full-Time Senior Expert 165000
3 Data Engineer Full-Time Senior Expert 185900
4 Data Engineer Full-Time Senior Expert 129300
5 Data Scientist Full-Time Senior Expert 140000
6 Data Scientist Full-Time Senior Expert 126000
Salary Currency Company Location Salary in USD Employee Residence
1 United States Dollar United States 210000 United States
2 United States Dollar United States 165000 United States
3 United States Dollar United States 185900 United States
4 United States Dollar United States 129300 United States
5 United States Dollar United States 140000 United States
6 United States Dollar United States 126000 United States
Company Size Year
1 Medium 2023
2 Medium 2023
3 Medium 2023
4 Medium 2023
5 Medium 2023
6 Medium 2023
Názvy premenných upravím do formátu vhodného pre R.
names(udaje) <- make.names(
trimws(
names(udaje)
),
unique = TRUE
)
names(udaje) [1] "Job.Title" "Employment.Type" "Experience.Level"
[4] "Expertise.Level" "Salary" "Salary.Currency"
[7] "Company.Location" "Salary.in.USD" "Employee.Residence"
[10] "Company.Size" "Year"
2.2 Výber pracovnej pozície Data Scientist
Keďže mojou vysnívanou kariérou je pozícia Data Scientist, vyberiem iba tieto záznamy.
data_scientist <- udaje[
udaje$Job.Title == "Data Scientist",
]Vyberiem premenné potrebné pre regresnú analýzu.
data_scientist <- data_scientist[, c(
"Salary.in.USD",
"Year",
"Experience.Level",
"Company.Size"
)]Odstránim prípadné neúplné riadky.
data_scientist <- data_scientist[
complete.cases(data_scientist),
]Počet použitých pozorovaní:
nrow(data_scientist)[1] 635
2.3 Úprava premenných
Úroveň skúseností nastavím ako faktor.
Ako základnú kategóriu použijem Entry.
data_scientist$Experience.Level <- factor(
data_scientist$Experience.Level,
levels = c(
"Entry",
"Mid",
"Senior",
"Executive"
)
)Veľkosť firmy nastavím ako faktor.
Ako základnú kategóriu použijem Small.
data_scientist$Company.Size <- factor(
data_scientist$Company.Size,
levels = c(
"Small",
"Medium",
"Large"
)
)Pre jednoduchšiu interpretáciu roku vytvorím premennú, ktorá vyjadruje počet rokov od roku 2020.
data_scientist$Rok.od.2020 <-
data_scientist$Year - 2020Skontrolujem výslednú databázu.
head(data_scientist) Salary.in.USD Year Experience.Level Company.Size Rok.od.2020
5 140000 2023 Senior Medium 3
6 126000 2023 Senior Medium 3
7 170000 2023 Senior Medium 3
8 130000 2023 Senior Medium 3
17 185900 2023 Senior Medium 3
18 129300 2023 Senior Medium 3
str(data_scientist)'data.frame': 635 obs. of 5 variables:
$ Salary.in.USD : int 140000 126000 170000 130000 185900 129300 225000 156400 61520 49216 ...
$ Year : int 2023 2023 2023 2023 2023 2023 2023 2023 2023 2023 ...
$ Experience.Level: Factor w/ 4 levels "Entry","Mid",..: 3 3 3 3 3 3 3 3 2 2 ...
$ Company.Size : Factor w/ 3 levels "Small","Medium",..: 2 2 2 2 2 2 2 2 2 2 ...
$ Rok.od.2020 : num 3 3 3 3 3 3 3 3 3 3 ...
3 Ukladanie a obnovovanie workspace
Workspace predstavuje objekty, ktoré máme aktuálne uložené v pracovnej pamäti R.
Ich zoznam zobrazím príkazom:
ls()[1] "cesta" "data_scientist" "udaje"
3.1 Uloženie workspace
Celý aktuálny workspace uložím do súboru:
save.image(
"pamat_Cvicenie5.RData"
)Takto môžem uložiť databázu aj ostatné vytvorené objekty a v ďalšom cvičení ich nemusím vytvárať úplne od začiatku.
3.2 Mazanie vybraných objektov
Vytvorím dva skúšobné objekty.
aaa <- 6
bbb <- "text"
aaa[1] 6
bbb[1] "text"
Vymažem iba tieto dva objekty.
rm(
list = c(
"aaa",
"bbb"
)
)
ls()[1] "cesta" "data_scientist" "udaje"
3.3 Vyčistenie celého workspace
Pred úplným vyčistením ešte raz uložím aktuálnu pamäť.
save.image(
"pamat_Cvicenie5.RData"
)Teraz môžem odstrániť všetky objekty.
rm(
list = ls()
)
ls()character(0)
Workspace je teraz prázdny.
3.4 Opätovné načítanie workspace
Načítam späť uložené objekty.
load(
"pamat_Cvicenie5.RData"
)
ls()[1] "cesta" "data_scientist" "udaje"
Takýmto spôsobom môžem prenášať svoje pracovné prostredie medzi jednotlivými cvičeniami.
4 Lineárna regresia
Pokračujem s regresným modelom, v ktorom vysvetľujem plat Data Scientist pomocou:
- roku,
- úrovne skúseností,
- veľkosti firmy.
Model má tvar:
\[ Salary_i = \beta_0 + \beta_1 Rok_i + \beta_2 Experience_i + \beta_3 CompanySize_i + u_i \]
Model odhadnem pomocou funkcie lm().
model <- lm(
Salary.in.USD ~
Rok.od.2020 +
Experience.Level +
Company.Size,
data = data_scientist
)
summary(model)
Call:
lm(formula = Salary.in.USD ~ Rok.od.2020 + Experience.Level +
Company.Size, data = data_scientist)
Residuals:
Min 1Q Median 3Q Max
-135659 -35648 -3527 30791 290271
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 32197 11879 2.710 0.006904 **
Rok.od.2020 11301 3344 3.379 0.000771 ***
Experience.LevelMid 16348 8152 2.005 0.045359 *
Experience.LevelSenior 77673 7751 10.022 < 2e-16 ***
Experience.LevelExecutive 94384 14391 6.559 1.13e-10 ***
Company.SizeMedium 30753 11129 2.763 0.005889 **
Company.SizeLarge 11859 11557 1.026 0.305211
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 53430 on 628 degrees of freedom
Multiple R-squared: 0.3627, Adjusted R-squared: 0.3566
F-statistic: 59.57 on 6 and 628 DF, p-value: < 2.2e-16
5 Regresné koeficienty a t-testy
Výsledky jednotlivých regresných koeficientov získam zo summary(model).
coef_mat <- summary(model)$coefficients
coef_mat Estimate Std. Error t value Pr(>|t|)
(Intercept) 32196.96 11879.013 2.710407 6.903661e-03
Rok.od.2020 11301.33 3344.190 3.379392 7.714495e-04
Experience.LevelMid 16348.02 8152.397 2.005302 4.535937e-02
Experience.LevelSenior 77672.94 7750.556 10.021596 4.988176e-22
Experience.LevelExecutive 94383.80 14390.562 6.558729 1.134515e-10
Company.SizeMedium 30753.02 11128.988 2.763326 5.889235e-03
Company.SizeLarge 11859.18 11556.846 1.026160 3.052112e-01
Vytvorím prehľadnú tabuľku.
coef_tab <- data.frame(
Premenna = rownames(coef_mat),
Estimate = coef_mat[, 1],
Std.Error = coef_mat[, 2],
t.value = coef_mat[, 3],
p.value = coef_mat[, 4],
row.names = NULL
)Pridám označenie štatistickej významnosti.
coef_tab$Significance <- ifelse(
coef_tab$p.value < 0.001,
"***",
ifelse(
coef_tab$p.value < 0.01,
"**",
ifelse(
coef_tab$p.value < 0.05,
"*",
ifelse(
coef_tab$p.value < 0.10,
".",
""
)
)
)
)
knitr::kable(
coef_tab,
digits = 4,
row.names = FALSE,
col.names = c(
"Premenná",
"Odhad",
"Štandardná chyba",
"t hodnota",
"p-hodnota",
"Významnosť"
),
caption = "Regresné koeficienty a výsledky t-testov"
)| Premenná | Odhad | Štandardná chyba | t hodnota | p-hodnota | Významnosť |
|---|---|---|---|---|---|
| (Intercept) | 32196.96 | 11879.013 | 2.7104 | 0.0069 | ** |
| Rok.od.2020 | 11301.33 | 3344.190 | 3.3794 | 0.0008 | *** |
| Experience.LevelMid | 16348.02 | 8152.397 | 2.0053 | 0.0454 | * |
| Experience.LevelSenior | 77672.94 | 7750.556 | 10.0216 | 0.0000 | *** |
| Experience.LevelExecutive | 94383.80 | 14390.562 | 6.5587 | 0.0000 | *** |
| Company.SizeMedium | 30753.02 | 11128.988 | 2.7633 | 0.0059 | ** |
| Company.SizeLarge | 11859.18 | 11556.846 | 1.0262 | 0.3052 |
6 Interpretácia t-testu
Pri každom regresnom koeficiente testujeme:
\[ H_0:\beta_j=0 \]
oproti:
\[ H_1:\beta_j\neq0. \]
Testovacia štatistika je:
\[ t_j = \frac{\hat{\beta}_j} {SE(\hat{\beta}_j)}. \]
Ak je p-hodnota menšia ako 0,05, nulovú hypotézu na 5 % hladine významnosti zamietame.
To znamená, že daný regresný koeficient považujeme za štatisticky významný.
6.1 Interpretácia roku
Koeficient pri premennej Rok.od.2020 je:
coef(model)["Rok.od.2020"]Rok.od.2020
11301.33
Jeho p-hodnota je:
summary(model)$coefficients[
"Rok.od.2020",
4
][1] 0.0007714495
Pri rovnakých skúsenostiach a rovnakej veľkosti firmy sa zvýšenie roku o jednu jednotku spája so zmenou predikovaného platu približne o:
1.130133^{4} USD.
Koeficient pri roku je štatisticky významný na 5 % hladine významnosti.
6.2 Interpretácia Senior úrovne
coef(model)["Experience.LevelSenior"]Experience.LevelSenior
77672.94
Senior Data Scientist má pri rovnakom roku a rovnakej veľkosti firmy predikovaný plat približne o
7.767294^{4} USD
vyšší oproti základnej kategórii Entry.
P-hodnota:
summary(model)$coefficients[
"Experience.LevelSenior",
4
][1] 4.988176e-22
Tento rozdiel je štatisticky významný.
6.3 Interpretácia Mid úrovne
Mid Data Scientist má oproti Entry Data Scientist pri ostatných nezmenených podmienkach rozdiel v predikovanom plate:
coef(model)["Experience.LevelMid"]Experience.LevelMid
16348.02
P-hodnota:
summary(model)$coefficients[
"Experience.LevelMid",
4
][1] 0.04535937
Koeficient je štatisticky významný na 5 % hladine významnosti.
6.4 Interpretácia Executive úrovne
coef(model)["Experience.LevelExecutive"]Experience.LevelExecutive
94383.8
Executive Data Scientist má oproti Entry úrovni pri ostatných nezmenených premenných predikovaný plat vyšší približne o
9.43838^{4} USD.
Koeficient je štatisticky významný.
7 Označenie štatistickej významnosti
Pri výsledkoch používam tradičné označenie:
| Symbol | p-hodnota | Interpretácia |
|---|---|---|
*** |
p < 0.001 | veľmi silná štatistická významnosť |
** |
p < 0.01 | významné na 1 % hladine |
* |
p < 0.05 | významné na 5 % hladine |
. |
p < 0.10 | významné na 10 % hladine |
| bez symbolu | p >= 0.10 | štatisticky nevýznamné |
8 Kvalita vyrovnania modelu
Kvalitu regresného modelu posúdim pomocou:
- koeficientu determinácie (R^2),
- upraveného (R^2),
- reziduálnej štandardnej chyby,
- F-testu,
- AIC a BIC.
Najskôr získam potrebné hodnoty.
model_summary <- summary(model)
f_stat <- model_summary$fstatistic
F_hodnota <- unname(
f_stat[1]
)
df1 <- unname(
f_stat[2]
)
df2 <- unname(
f_stat[3]
)
F_p_value <- pf(
F_hodnota,
df1,
df2,
lower.tail = FALSE
)Vytvorím tabuľku.
suhrn_modelu <- data.frame(
R.squared =
model_summary$r.squared,
Adj.R.squared =
model_summary$adj.r.squared,
Sigma =
model_summary$sigma,
F.stat =
F_hodnota,
F.p.value =
F_p_value,
df =
df1,
df.residual =
df.residual(model),
N =
nobs(model),
AIC =
AIC(model),
BIC =
BIC(model),
Deviance =
deviance(model)
)
knitr::kable(
suhrn_modelu,
digits = 4,
row.names = FALSE,
caption = "Súhrnné charakteristiky regresného modelu"
)| R.squared | Adj.R.squared | Sigma | F.stat | F.p.value | df | df.residual | N | AIC | BIC | Deviance |
|---|---|---|---|---|---|---|---|---|---|---|
| 0.3627 | 0.3566 | 53427.47 | 59.5651 | 0 | 6 | 628 | 635 | 15636.34 | 15671.96 | 1.792623e+12 |
9 Koeficient determinácie R²
Koeficient determinácie má tvar:
\[ R^2 = 1 - \frac{RSS}{TSS}. \]
Kde:
\[ RSS = \sum_{i=1}^{n} (y_i-\hat y_i)^2 \]
a:
\[ TSS = \sum_{i=1}^{n} (y_i-\bar y)^2. \]
V mojom modeli je:
model_summary$r.squared[1] 0.3626892
Teda model vysvetľuje približne:
round(
model_summary$r.squared * 100,
2
)[1] 36.27
percent variability platov Data Scientist v použitej vzorke.
Hodnota (R^2) je približne 0.363, teda model zachytáva približne 36.3 % variability platu.
To neznamená, že rovnaké percento predikcií je správnych. (R^2) meria vysvetlenú variabilitu vysvetľovanej premennej.
10 Manuálne overenie R²
Najskôr vypočítam reziduálny súčet štvorcov.
RSS <- sum(
residuals(model)^2
)
RSS[1] 1.792623e+12
Celkový súčet štvorcov:
TSS <- sum(
(
data_scientist$Salary.in.USD -
mean(data_scientist$Salary.in.USD)
)^2
)
TSS[1] 2.812792e+12
Manuálne vypočítané (R^2):
R2_manual <- 1 -
RSS / TSS
R2_manual[1] 0.3626892
Porovnanie:
c(
R2_z_modelu =
model_summary$r.squared,
R2_manual =
R2_manual
)R2_z_modelu R2_manual
0.3626892 0.3626892
Výsledky by mali byť rovnaké.
11 Upravený koeficient determinácie
Upravený koeficient determinácie zohľadňuje aj počet vysvetľujúcich premenných.
model_summary$adj.r.squared[1] 0.3566002
Na rozdiel od obyčajného (R^2) nemusí po pridaní ďalšej premennej vždy rásť.
Je preto vhodnejší pri porovnávaní modelov s rôznym počtom vysvetľujúcich premenných.
12 Reziduálna štandardná chyba
sigma(model)[1] 53427.47
Reziduálna štandardná chyba vyjadruje typickú veľkosť odchýlky medzi skutočným a modelom predikovaným platom.
Má rovnakú jednotku ako vysvetľovaná premenná, teda v našom prípade USD.
Overím jej výpočet.
sqrt(
deviance(model) /
df.residual(model)
)[1] 53427.47
Táto hodnota by mala byť rovnaká ako výsledok sigma(model).
13 F-test celého modelu
F-test skúma spoločnú štatistickú významnosť vysvetľujúcich premenných.
Testujeme:
\[ H_0: \beta_1 = \beta_2 = \dots = \beta_k = 0 \]
oproti:
\[ H_1: \text{aspoň jeden smernicový koeficient nie je nulový}. \]
F-štatistika:
F_hodnota[1] 59.56508
P-hodnota:
F_p_value[1] 2.438836e-58
Keďže p-hodnota je 2.44e-58, na 5 % hladine významnosti
zamietame nulovú hypotézu.
To znamená, že
regresný model je ako celok štatisticky významný.
Je dôležité rozlišovať:
- t-test hodnotí jednotlivý regresný koeficient,
- F-test hodnotí model ako celok.
14 Grafická analýza kvality modelu
Pri diagnostike lineárneho regresného modelu môžem použiť príkaz:
plot(model)
Ten vytvorí štyri základné diagnostické grafy.
par(
mfrow = c(
2,
2
)
)
plot(model)par(
mfrow = c(
1,
1
)
)15 Interpretácia diagnostických grafov
15.1 Residuals vs Fitted
Prvý graf porovnáva rezíduá s predikovanými hodnotami.
V ideálnom prípade by mali byť rezíduá náhodne rozptýlené okolo nuly a nemali by vytvárať výrazný systematický tvar.
Červená vyhladená čiara by mala byť približne horizontálna.
Výrazné zakrivenie by mohlo naznačovať, že lineárny model nezachytáva celý tvar vzťahu medzi premennými.
Ak by sa rozptyl rezíduí smerom doprava výrazne zväčšoval alebo zmenšoval, mohlo by ísť o heteroskedasticitu.
15.2 Normal Q-Q
Druhý graf slúži na vizuálne posúdenie normality rezíduí.
Ak sú rezíduá približne normálne rozdelené, body by mali ležať približne na diagonálnej čiare.
Výrazné odchýlky najmä na koncoch grafu naznačujú problém s normalitou alebo existenciu extrémnych hodnôt.
Normalitu následne overím aj formálnym Jarqueovým-Berovým testom.
15.3 Scale-Location
Tretí graf skúma stabilitu rozptylu rezíduí.
Ideálne by mali byť body rozptýlené približne rovnomerne a červená čiara by mala byť približne horizontálna.
Výrazný rast alebo pokles môže naznačovať heteroskedasticitu.
15.4 Residuals vs Leverage
Posledný graf spája veľkosť rezíduí a leverage jednotlivých pozorovaní.
Leverage ukazuje, ako neobvyklé je pozorovanie vzhľadom na hodnoty vysvetľujúcich premenných.
Cookova vzdialenosť zároveň pomáha identifikovať pozorovania, ktoré môžu výraznejšie ovplyvňovať výsledné regresné koeficienty.
Vysoká leverage sama osebe neznamená, že je dané pozorovanie chybné.
16 Test normality rezíduí
Na formálne posúdenie normality použijem Jarqueov-Berov test.
Testujeme:
\[ H_0: \text{rezíduá majú normálne rozdelenie} \]
oproti:
\[ H_1: \text{rezíduá nemajú normálne rozdelenie}. \]
Jarqueov-Berov test využíva šikmosť a špicatosť rezíduí.
Testovacia štatistika má tvar:
\[ JB = \frac{n}{6} \left( S^2 + \frac{(K-3)^2}{4} \right). \]
16.1 Výpočet Jarqueovho-Berovho testu
Najskôr získam rezíduá.
res <- residuals(model)
n_res <- length(res)Vypočítam centrované rezíduá.
res_c <- res -
mean(res)Druhý, tretí a štvrtý moment:
m2 <- mean(
res_c^2
)
m3 <- mean(
res_c^3
)
m4 <- mean(
res_c^4
)Šikmosť:
S <- m3 /
(
m2^(3 / 2)
)
S[1] 0.6184228
Špicatosť:
K <- m4 /
(
m2^2
)
K[1] 4.504448
Jarqueova-Berova štatistika:
JB <- n_res / 6 *
(
S^2 +
(
(K - 3)^2 / 4
)
)
JB[1] 100.3605
P-hodnota:
JB_p <- pchisq(
JB,
df = 2,
lower.tail = FALSE
)
JB_p[1] 1.61066e-22
Výsledok zobrazím v tabuľke.
JB_tabulka <- data.frame(
JB_statistika = JB,
p_hodnota = JB_p,
Skewness = S,
Kurtosis = K
)
knitr::kable(
JB_tabulka,
digits = 4,
row.names = FALSE,
caption = "Jarqueov-Berov test normality rezíduí"
)| JB_statistika | p_hodnota | Skewness | Kurtosis |
|---|---|---|---|
| 100.3605 | 0 | 0.6184 | 4.5044 |
17 Interpretácia Jarqueovho-Berovho testu
Rozhodovacie pravidlo je:
- ak (p < 0.05), zamietame normalitu rezíduí,
- ak (p ), normalitu nezamietame.
V mojom modeli je p-hodnota:
1.61e-22.
Preto na 5 % hladine významnosti
zamietam nulovú hypotézu o normálnom rozdelení rezíduí.
Formálny test je vhodné posudzovať spolu s Q-Q grafom.
18 Test odľahlých pozorovaní
Odľahlé pozorovania môžem skúmať pomocou externe studentizovaných rezíduí.
Použijem:
rstud <- rstudent(model)Externe studentizované rezíduum má tvar:
\[ t_i = \frac{e_i} {\hat{\sigma}_{(i)} \sqrt{1-h_{ii}}}. \]
Pre každé pozorovanie vypočítam obojstrannú p-hodnotu.
df_outlier <-
df.residual(model) - 1
p_unadjusted <- 2 *
pt(
abs(rstud),
df = df_outlier,
lower.tail = FALSE
)Keďže naraz testujem veľa pozorovaní, použijem Bonferroniho korekciu.
p_bonferroni <- pmin(
1,
nobs(model) *
p_unadjusted
)Vytvorím tabuľku.
model_data <- model.frame(model)
outlier_table <- data.frame(
Pozorovanie =
rownames(model_data),
Plat_USD =
model_data$Salary.in.USD,
Studentizovane_reziduum =
rstud,
p_hodnota =
p_unadjusted,
Bonferroni_p =
p_bonferroni,
row.names = NULL
)Zoradím ju podľa Bonferroniho p-hodnoty.
outlier_table <- outlier_table[
order(
outlier_table$Bonferroni_p
),
]
knitr::kable(
head(
outlier_table,
10
),
digits = 5,
row.names = FALSE,
caption = "Najvýraznejšie pozorovania podľa testu odľahlosti"
)| Pozorovanie | Plat_USD | Studentizovane_reziduum | p_hodnota | Bonferroni_p |
|---|---|---|---|---|
| 3296 | 412000 | 5.64894 | 0.00000 | 0.00002 |
| 1752 | 370000 | 3.70111 | 0.00023 | 0.14830 |
| 2393 | 350000 | 3.53566 | 0.00044 | 0.27733 |
| 458 | 359170 | 3.49194 | 0.00051 | 0.32590 |
| 5 | 140000 | -0.64693 | 0.51792 | 1.00000 |
| 6 | 126000 | -0.90954 | 0.36342 | 1.00000 |
| 7 | 170000 | -0.08479 | 0.93245 | 1.00000 |
| 8 | 130000 | -0.83448 | 0.40433 | 1.00000 |
| 17 | 185900 | 0.21303 | 0.83137 | 1.00000 |
| 18 | 129300 | -0.84761 | 0.39698 | 1.00000 |
19 Identifikácia štatisticky významných odľahlých pozorovaní
Za významne odľahlé budem považovať pozorovania, pri ktorých:
\[ p_{Bonferroni} < 0.05. \]
odlahle <- outlier_table[
outlier_table$Bonferroni_p < 0.05,
]Počet takýchto pozorovaní:
nrow(
odlahle
)[1] 1
Ak sa takéto pozorovania nachádzajú, zobrazím ich.
if (
nrow(odlahle) == 0
) {
cat(
"Model neobsahuje štatisticky významné odľahlé pozorovania podľa Bonferroniho korekcie."
)
} else {
knitr::kable(
odlahle,
digits = 5,
row.names = FALSE,
caption = "Štatisticky významné odľahlé pozorovania"
)
}| Pozorovanie | Plat_USD | Studentizovane_reziduum | p_hodnota | Bonferroni_p |
|---|---|---|---|---|
| 3296 | 412000 | 5.64894 | 0 | 2e-05 |
Odľahlé pozorovanie však nemusí byť automaticky chybou v databáze.
Môže predstavovať skutočného Data Scientist s nezvyčajne vysokým alebo nízkym platom.
Preto ho nemožno automaticky odstrániť iba preto, že ho test označil za odľahlé.
20 Leverage a Cookova vzdialenosť
Pri diagnostike vplyvných pozorovaní je dôležitá aj leverage.
Leverage získam pomocou:
leverage <- hatvalues(model)Cookovu vzdialenosť získam:
cook <- cooks.distance(model)Počet parametrov modelu:
p_model <- length(
coef(model)
)
p_model[1] 7
Priemerná leverage je:
p_model /
nobs(model)[1] 0.01102362
Ako orientačnú hranicu použijem:
\[ \frac{2p}{n}. \]
hranica_leverage <-
2 *
p_model /
nobs(model)
hranica_leverage[1] 0.02204724
Pre Cookovu vzdialenosť môžem ako orientačnú hodnotu použiť:
\[ \frac{4}{n}. \]
hranica_cook <-
4 /
nobs(model)
hranica_cook[1] 0.006299213
20.1 Tabuľka diagnostických hodnôt
diagnostika <- data.frame(
Pozorovanie =
rownames(
model.frame(model)
),
Leverage =
leverage,
Cookova_vzdialenost =
cook,
Standardizovane_reziduum =
rstandard(model),
row.names = NULL
)
diagnostika <- diagnostika[
order(
diagnostika$Cookova_vzdialenost,
decreasing = TRUE
),
]Zobrazím desať najvýraznejších pozorovaní.
knitr::kable(
head(
diagnostika,
10
),
digits = 4,
row.names = FALSE,
caption = "Pozorovania s najvyššou Cookovou vzdialenosťou"
)| Pozorovanie | Leverage | Cookova_vzdialenost | Standardizovane_reziduum |
|---|---|---|---|
| 3296 | 0.0295 | 0.1319 | 5.5148 |
| 383 | 0.0527 | 0.0377 | -2.1777 |
| 1941 | 0.0527 | 0.0348 | 2.0916 |
| 190 | 0.0527 | 0.0345 | 2.0820 |
| 1510 | 0.0527 | 0.0245 | -1.7546 |
| 800 | 0.0473 | 0.0242 | 1.8452 |
| 828 | 0.0925 | 0.0203 | -1.1802 |
| 3298 | 0.0494 | 0.0145 | 1.3976 |
| 939 | 0.0527 | 0.0134 | 1.2983 |
| 2516 | 0.0399 | 0.0130 | -1.4798 |
21 Graf leverage a Cookovej vzdialenosti
par(
mfrow = c(
1,
2
)
)
plot(
leverage,
type = "h",
lwd = 2,
xlab = "Pozorovanie",
ylab = "Leverage",
main = "Leverage"
)
abline(
h = hranica_leverage,
lty = 2
)
plot(
cook,
type = "h",
lwd = 2,
xlab = "Pozorovanie",
ylab = "Cookova vzdialenosť",
main = "Cookova vzdialenosť"
)
abline(
h = hranica_cook,
lty = 2
)par(
mfrow = c(
1,
1
)
)Leverage ukazuje nezvyčajnú polohu pozorovania vzhľadom na vysvetľujúce premenné.
Cookova vzdialenosť kombinuje informáciu o leverage a veľkosti rezídua.
Vysoká leverage preto nie je automaticky to isté ako vplyvné pozorovanie.
22 Úprava pôvodného modelu
Diagnostika môže ukázať, že jednoduchý lineárny vzťah medzi časom a platom nemusí úplne zachytiť vývoj platov.
V mojej databáze mám iba štyri roky – 2020 až 2023.
Preto vyskúšam model, ktorý okrem lineárneho trendu obsahuje aj kvadratický člen roku.
Nový model má tvar:
\[ Salary_i = \beta_0 + \beta_1 Rok_i + \beta_2 Rok_i^2 + \beta_3 Experience_i + \beta_4 CompanySize_i + u_i. \]
model2 <- lm(
Salary.in.USD ~
Rok.od.2020 +
I(Rok.od.2020^2) +
Experience.Level +
Company.Size,
data = data_scientist
)
summary(model2)
Call:
lm(formula = Salary.in.USD ~ Rok.od.2020 + I(Rok.od.2020^2) +
Experience.Level + Company.Size, data = data_scientist)
Residuals:
Min 1Q Median 3Q Max
-138013 -35873 -3706 30553 269280
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 46814 13399 3.494 0.00051 ***
Rok.od.2020 -15314 11907 -1.286 0.19886
I(Rok.od.2020^2) 6766 2906 2.328 0.02021 *
Experience.LevelMid 15966 8126 1.965 0.04986 *
Experience.LevelSenior 77409 7724 10.022 < 2e-16 ***
Experience.LevelExecutive 92891 14354 6.471 1.96e-10 ***
Company.SizeMedium 37706 11485 3.283 0.00108 **
Company.SizeLarge 18497 11864 1.559 0.11947
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 53240 on 627 degrees of freedom
Multiple R-squared: 0.3682, Adjusted R-squared: 0.3611
F-statistic: 52.19 on 7 and 627 DF, p-value: < 2.2e-16
23 Koeficienty upraveného modelu
coef_mat2 <-
summary(model2)$coefficients
coef_tab2 <- data.frame(
Premenna =
rownames(coef_mat2),
Estimate =
coef_mat2[, 1],
Std.Error =
coef_mat2[, 2],
t.value =
coef_mat2[, 3],
p.value =
coef_mat2[, 4],
row.names = NULL
)
knitr::kable(
coef_tab2,
digits = 4,
row.names = FALSE,
col.names = c(
"Premenná",
"Odhad",
"Štandardná chyba",
"t hodnota",
"p-hodnota"
),
caption = "Koeficienty upraveného regresného modelu"
)| Premenná | Odhad | Štandardná chyba | t hodnota | p-hodnota |
|---|---|---|---|---|
| (Intercept) | 46814.35 | 13399.132 | 3.4938 | 0.0005 |
| Rok.od.2020 | -15314.05 | 11906.726 | -1.2862 | 0.1989 |
| I(Rok.od.2020^2) | 6765.95 | 2905.864 | 2.3284 | 0.0202 |
| Experience.LevelMid | 15966.27 | 8125.504 | 1.9650 | 0.0499 |
| Experience.LevelSenior | 77408.69 | 7724.249 | 10.0215 | 0.0000 |
| Experience.LevelExecutive | 92891.22 | 14354.491 | 6.4712 | 0.0000 |
| Company.SizeMedium | 37706.18 | 11485.045 | 3.2831 | 0.0011 |
| Company.SizeLarge | 18497.36 | 11864.025 | 1.5591 | 0.1195 |
24 Porovnanie modelov pomocou AIC
Pri porovnávaní modelov s rovnakou vysvetľovanou premennou uprednostňujeme nižšiu hodnotu AIC.
porovnanie_modelov <- data.frame(
Model = c(
"Pôvodný model",
"Model s kvadratickým trendom"
),
AIC = c(
AIC(model),
AIC(model2)
),
BIC = c(
BIC(model),
BIC(model2)
),
R2 = c(
summary(model)$r.squared,
summary(model2)$r.squared
),
Upraveny_R2 = c(
summary(model)$adj.r.squared,
summary(model2)$adj.r.squared
)
)
knitr::kable(
porovnanie_modelov,
digits = 4,
row.names = FALSE,
caption = "Porovnanie pôvodného a upraveného modelu"
)| Model | AIC | BIC | R2 | Upraveny_R2 |
|---|---|---|---|---|
| Pôvodný model | 15636.34 | 15671.96 | 0.3627 | 0.3566 |
| Model s kvadratickým trendom | 15632.87 | 15672.95 | 0.3682 | 0.3611 |
Na základe AIC je vhodnejší:
model s kvadratickým trendom, pretože má nižšiu hodnotu AIC.
Rozdiel AIC je:
abs(
AIC(model) -
AIC(model2)
)[1] 3.466917
25 Grafická diagnostika upraveného modelu
par(
mfrow = c(
2,
2
)
)
plot(model2)par(
mfrow = c(
1,
1
)
)26 Normalita rezíduí upraveného modelu
Vypočítam Jarqueov-Berov test aj pre druhý model.
res2 <- residuals(model2)
n_res2 <- length(res2)
res2_c <- res2 -
mean(res2)
m2_2 <- mean(
res2_c^2
)
m3_2 <- mean(
res2_c^3
)
m4_2 <- mean(
res2_c^4
)
S2 <- m3_2 /
(
m2_2^(3 / 2)
)
K2 <- m4_2 /
(
m2_2^2
)
JB2 <- n_res2 / 6 *
(
S2^2 +
(
(K2 - 3)^2 / 4
)
)
JB2_p <- pchisq(
JB2,
df = 2,
lower.tail = FALSE
)JB2_tabulka <- data.frame(
JB_statistika = JB2,
p_hodnota = JB2_p,
Skewness = S2,
Kurtosis = K2
)
knitr::kable(
JB2_tabulka,
digits = 4,
row.names = FALSE,
caption = "Jarqueov-Berov test upraveného modelu"
)| JB_statistika | p_hodnota | Skewness | Kurtosis |
|---|---|---|---|
| 71.191 | 0 | 0.566 | 4.1872 |
Pri upravenom modeli je p-hodnota:
3.48e-16.
Preto:
normalitu rezíduí ani pri upravenom modeli na 5 % hladine významnosti neprijímame.
27 Odľahlé pozorovania upraveného modelu
rstud2 <- rstudent(model2)
df_outlier2 <-
df.residual(model2) - 1
p_unadjusted2 <- 2 *
pt(
abs(rstud2),
df = df_outlier2,
lower.tail = FALSE
)
p_bonferroni2 <- pmin(
1,
nobs(model2) *
p_unadjusted2
)
model_data2 <- model.frame(model2)
outlier_table2 <- data.frame(
Pozorovanie =
rownames(model_data2),
Plat_USD =
model_data2$Salary.in.USD,
Studentizovane_reziduum =
rstud2,
p_hodnota =
p_unadjusted2,
Bonferroni_p =
p_bonferroni2,
row.names = NULL
)
outlier_table2 <- outlier_table2[
order(
outlier_table2$Bonferroni_p
),
]
knitr::kable(
head(
outlier_table2,
10
),
digits = 5,
row.names = FALSE,
caption = "Odľahlé pozorovania upraveného modelu"
)| Pozorovanie | Plat_USD | Studentizovane_reziduum | p_hodnota | Bonferroni_p |
|---|---|---|---|---|
| 3296 | 412000 | 5.32401 | 0.00000 | 0.00009 |
| 1752 | 370000 | 3.66944 | 0.00026 | 0.16751 |
| 2393 | 350000 | 3.64554 | 0.00029 | 0.18352 |
| 458 | 359170 | 3.45961 | 0.00058 | 0.36685 |
| 5 | 140000 | -0.69361 | 0.48818 | 1.00000 |
| 6 | 126000 | -0.95724 | 0.33881 | 1.00000 |
| 7 | 170000 | -0.12936 | 0.89712 | 1.00000 |
| 8 | 130000 | -0.88189 | 0.37817 | 1.00000 |
| 17 | 185900 | 0.16957 | 0.86541 | 1.00000 |
| 18 | 129300 | -0.89508 | 0.37109 | 1.00000 |
28 Porovnanie normality oboch modelov
normalita_porovnanie <- data.frame(
Model = c(
"Pôvodný model",
"Kvadratický model"
),
JB = c(
JB,
JB2
),
p_hodnota = c(
JB_p,
JB2_p
)
)
knitr::kable(
normalita_porovnanie,
digits = 5,
row.names = FALSE,
caption = "Porovnanie normality rezíduí oboch modelov"
)| Model | JB | p_hodnota |
|---|---|---|
| Pôvodný model | 100.36046 | 0 |
| Kvadratický model | 71.19103 | 0 |
Nižšia Jarqueova-Berova štatistika znamená, že rozdelenie rezíduí je z pohľadu šikmosti a špicatosti bližšie k normálnemu rozdeleniu.
Ani nižšia hodnota JB však automaticky neznamená splnenie normality. Rozhodnutie robím podľa p-hodnoty a zároveň pomocou Q-Q grafu.
29 Záverečné porovnanie modelov
Pôvodný model:
\[ Salary_i = \beta_0 + \beta_1 Rok_i + \beta_2 Experience_i + \beta_3 CompanySize_i + u_i \]
Upravený model pridáva aj kvadratický časový trend:
\[ Salary_i = \beta_0 + \beta_1 Rok_i + \beta_2 Rok_i^2 + \beta_3 Experience_i + \beta_4 CompanySize_i + u_i. \]
Pri rozhodovaní medzi modelmi sledujem:
- AIC,
- upravené (R^2),
- diagnostické grafy,
- normalitu rezíduí,
- odľahlé a vplyvné pozorovania.
Na základe AIC vychádza ako lepší
upravený model s kvadratickým trendom.
Samotné AIC však nestačí. Model musí byť zároveň ekonomicky a vecne interpretovateľný a jeho diagnostické vlastnosti by mali byť prijateľné.
30 Záver
V tomto cvičení som pokračoval v analýze databázy Latest Data Science Salaries a pracovnej pozície Data Scientist.
Najskôr som si ukázal, ako uložiť celý workspace pomocou save.image() a ako ho neskôr obnoviť pomocou load().
Následne som odhadol viacnásobný lineárny regresný model platu Data Scientist.
Pri jednotlivých regresných koeficientoch som interpretoval Studentove t-testy a p-hodnoty.
Kvalitu modelu ako celku som posudzoval pomocou koeficientu determinácie (R^2), upraveného (R^2), reziduálnej štandardnej chyby a F-testu.
Ďalej som pomocou plot(model) vykonal grafickú diagnostiku rezíduí.
Normalitu rezíduí som formálne overil Jarqueovým-Berovým testom.
Pomocou externe studentizovaných rezíduí a Bonferroniho korekcie som identifikoval potenciálne odľahlé pozorovania.
S využitím leverage a Cookovej vzdialenosti som posúdil aj možné vplyvné pozorovania.
Nakoniec som vytvoril druhý regresný model s kvadratickým časovým trendom a oba modely som porovnal pomocou AIC, upraveného (R^2) a diagnostiky rezíduí.
Toto cvičenie ukazuje, že pri dátovej analýze nestačí regresný model iba odhadnúť. Dôležité je tiež overiť jeho štatistickú významnosť, kvalitu vyrovnania a základné diagnostické vlastnosti.
31 Uloženie workspace na ďalšie cvičenie
Na konci si uložím celý aktuálny workspace.
save.image(
"pamat_Cvicenie5.RData"
)Tento súbor môžem v ďalšom cvičení načítať pomocou:
# load("pamat_Cvicenie5.RData")