Cieľom analýzy je modelovať výsledné skúškové skóre študentov (exam_score) na základe:

hours_studied – počet hodín štúdia, sleep_hours – priemerný počet hodín spánku, attendance_percent – percentuálna účasť na vyučovaní.

#######################################################################
# PRIPRAVA UDAJOV
#######################################################################
df <- read_csv("student_exam_scores.csv") %>%
select(exam_score, hours_studied, sleep_hours, attendance_percent)

# Imputácia mediánom

column_medians <- sapply(df, median, na.rm = TRUE)
for(col in names(df)){
df[[col]][is.na(df[[col]])] <- column_medians[col]
}

summary(df)
   exam_score    hours_studied     sleep_hours    attendance_percent
 Min.   :17.10   Min.   : 1.000   Min.   :4.000   Min.   : 50.30    
 1st Qu.:29.50   1st Qu.: 3.500   1st Qu.:5.300   1st Qu.: 62.20    
 Median :34.05   Median : 6.150   Median :6.700   Median : 75.25    
 Mean   :33.95   Mean   : 6.325   Mean   :6.622   Mean   : 74.83    
 3rd Qu.:38.75   3rd Qu.: 9.000   3rd Qu.:8.025   3rd Qu.: 87.42    
 Max.   :51.30   Max.   :12.000   Max.   :9.000   Max.   :100.00    
par(mfrow=c(2,2))
cols <- c("#d62828","#006d2c","#f4d35e","#ffffff") # červená, zelená, zlatá, biela
i <- 1
for(col in names(df)){
boxplot(df[[col]], main=col, col=cols[i %% length(cols) + 1], border=cols[(i+1) %% length(cols) + 1])
i <- i+1
}
par(mfrow=c(1,1))

Diagnostické grafy základného modelu nám poskytujú cenné informácie o vhodnosti špecifikácie regresie a o vlastnostiach rezíduí.

Graf rezíduá versus vyrovnané hodnoty ukazuje, že rezíduá sú rozptýlené náhodne okolo nulovej línie, bez výrazného zakrivenia alebo systematického vzoru, čo naznačuje, že lineárna špecifikácia modelu je primeraná a nie je potrebné transformovať premenné.

Q–Q graf, ktorý porovnáva kvantily rezíduí s kvantilmi normálneho rozdelenia, ukazuje, že body sa pohybujú veľmi blízko diagonálnej línie, len s menšími odchýlkami na okrajoch, čo potvrdzuje, že rezíduá sú približne normálne rozložené a predpoklad normality nie je porušený.

Scale–Location graf, ktorý znázorňuje štandardizované rezíduá voči vyrovnaným hodnotám, ukazuje rovnomerné rozptýlenie bodov bez zjavného rastúceho alebo klesajúceho vzoru, čo naznačuje, že predpoklad homoskedasticity je splnený.

Nakoniec, diagnostika pomocou Cookovej vzdialenosti a identifikácie odľahlých hodnôt neodhalila žiadne extrémne pozorovania, ktoré by významne deformovali model, takže všetky pozorovania sú spoľahlivé a model je stabilný. Celkovo tieto grafy podporujú tvrdenie, že lineárny model pre vysvetľovanie skúškového skóre študentov je adekvátny, rezíduá majú požadované vlastnosti a výsledky regresie sú interpretovateľné.

1. Test RESET (test chyby špecifikácie Ramseyho regresnej rovnice - Ramsey Reset Test)

Testovanie, či lineárny model pre exam_score je správne špecifikovaný, vykonáme pridaním mocnín vyrovnaných hodnôt (fitted values) do modelu. Pôvodný model:

exam_scoret​=β0​+β1​⋅hours_studiedt​+β2​⋅sleep_hourst​+β3​⋅attendance_percentt​+ut​

a rozšírený model pre test:

exam_scoret​=β0​+β1​⋅hours_studiedt​+β2​⋅sleep_hourst​+β3​⋅attendance_percentt​+γ2​y​t2​+γ3​y​t3​+ut​

kde y^​t sú vyrovnané hodnoty z pôvodného modelu. Testuje sa hypotéza:

H0: model je správne špecifikovaný (y2 je rovné y3 a to je rovné 0) H1: model je nesprávne špecifikovaný (y2 nie je rovné 0 alebo y3 nie je rovné 0)

# základný model
model <- lm(exam_score ~ hours_studied + sleep_hours + attendance_percent, data=df)

# RESET test
library(lmtest)
resettest(model)

    RESET test

data:  model
RESET = 0.56353, df1 = 2, df2 = 194, p-value = 0.5701

Interpretácia:

Ak je p-hodnota testu menšia ako 0.05, zamietame nulovú hypotézu a model je zrejme nesprávne špecifikovaný. To by znamenalo, že lineárna forma nemusí byť úplne vhodná, môže chýbať niektorá premenná alebo je vhodná nelineárna transformácia (napr. logaritmus hodín štúdia alebo kvadrát premenných). Ak je p-hodnota väčšia ako 0.05, nulovú hypotézu neprijímame a lineárny model je adekvátny pre naše dáta.

2. Grafická analýza

Graf Residuals vs. Fitted

Najprv vykreslíme klasický graf rezíduá vs. vyrovnané hodnoty pre základný model. Tento graf nám ukáže, či rezíduá vykazujú náhodný vzor. Ak je rezíduí príliš zakrivený alebo systematický, môže to naznačovať potrebu nelineárnej transformácie premenných.

# Residuals vs Fitted

plot(model, which = 1, col="#d62828", pch=19, cex=0.6)

Ak rezíduá vykazujú náhodný rozptyl okolo nuly a nie sú žiadne jasné zakrivenia, model je lineárny a vhodne špecifikovaný. Ak by sa objavila zakrivená štruktúra, zvážime transformáciu niektorých premenných, napríklad logaritmus alebo druhú mocninu.

Grafy C+R **

# C+R Plots pre všetky vysvetľujúce premenné

car::crPlots(model, col="#006d2c", pch=19)

Ak krivka pre danú premennú sleduje približne priamku, lineárny predpoklad je vhodný.

Ak krivka vykazuje zakrivenie, odporúča sa transformácia danej premennej, napríklad logaritmus hours_studied alebo kvadratický člen I(attendance_percent^2).

V prípade dát môžeme očakávať, že hours_studied môže vykazovať miernu nelinearitu (napr. úbytok prínosu pri vysokom počte hodín štúdia), zatiaľ čo sleep_hours a attendance_percent môžu byť lineárne.

3. Porovnanie základného a modifikovaného modelu

Predpokladajme, že C+R grafy naznačujú, že vzťah medzi výsledným skóre študentov a premennými hours_studied a attendance_percent môže byť nelineárny. Zavedieme preto do modelu kvadratické členy pre tieto premenné. Pôvodný lineárny model:

model <- lm(exam_score ~ hours_studied + sleep_hours + attendance_percent, data = df)
model_poly <- lm(exam_score ~ hours_studied + I(hours_studied^2) +
                               sleep_hours +
                               attendance_percent + I(attendance_percent^2),
                 data = df)
summary(model_poly)

Call:
lm(formula = exam_score ~ hours_studied + I(hours_studied^2) + 
    sleep_hours + attendance_percent + I(attendance_percent^2), 
    data = df)

Residuals:
    Min      1Q  Median      3Q     Max 
-8.8885 -2.5420 -0.1928  2.9303  7.9036 

Coefficients:
                         Estimate Std. Error t value Pr(>|t|)   
(Intercept)              2.095799   8.359165   0.251  0.80230   
hours_studied            1.196918   0.399812   2.994  0.00311 **
I(hours_studied^2)       0.033364   0.029985   1.113  0.26721   
sleep_hours              0.576782   0.182831   3.155  0.00186 **
attendance_percent       0.395018   0.225507   1.752  0.08141 . 
I(attendance_percent^2) -0.001857   0.001512  -1.228  0.22086   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.848 on 194 degrees of freedom
Multiple R-squared:  0.6868,    Adjusted R-squared:  0.6787 
F-statistic: 85.08 on 5 and 194 DF,  p-value: < 2.2e-16
anova(model, model_poly)
Analysis of Variance Table

Model 1: exam_score ~ hours_studied + sleep_hours + attendance_percent
Model 2: exam_score ~ hours_studied + I(hours_studied^2) + sleep_hours + 
    attendance_percent + I(attendance_percent^2)
  Res.Df    RSS Df Sum of Sq      F Pr(>F)
1    196 2915.8                           
2    194 2873.1  2    42.717 1.4422 0.2389
library(lmtest)
resettest(model_poly)

    RESET test

data:  model_poly
RESET = 0.065584, df1 = 2, df2 = 192, p-value = 0.9365

V modifikovanom modeli môžu byť kvadratické členy štatisticky významné, čo naznačuje, že vzťah nie je priamo lineárny. Ak upravený koeficient determinácie R2adj stúpol, znamená to, že model lepšie vysvetľuje variabilitu skúškových výsledkov. Ak niektorý kvadratický člen (napr. pre attendance_percent) nie je štatisticky významný, môžeme ho vylúčiť a zachovať len významné kvadratické členy, čím získame jednoduchejší a interpretovateľný model.

4. Transformácia pomocou dummy premennej a lineárnej lomenej funkcie

Predpokladajme, že chceme analyzovať, či sa vzťah medzi počtom hodín štúdia (hours_studied) a skúškovým skóre mení nad určitým prahom. Vytvoríme dummy premennú DUM, ktorá nad určitým počtom hodín nadobúda hodnotu 1 a inak 0. Tým môžeme testovať:

  1. Zlom v autonómnom člene – posun regresnej priamky pre študentov, ktorí študujú nad daný prah:
df$DUM <- ifelse(df$hours_studied > 10, 1, 0)  # prah 10 hodín, upravi podľa potreby
modelD_auto <- lm(exam_score ~ DUM + hours_studied + sleep_hours + attendance_percent, data=df)
summary(modelD_auto)

Call:
lm(formula = exam_score ~ DUM + hours_studied + sleep_hours + 
    attendance_percent, data = df)

Residuals:
   Min     1Q Median     3Q    Max 
-8.594 -2.584 -0.150  3.167  7.444 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)        11.06387    1.98162   5.583 7.84e-08 ***
DUM                 0.57099    0.97955   0.583  0.56063    
hours_studied       1.58173    0.11836  13.364  < 2e-16 ***
sleep_hours         0.58107    0.18352   3.166  0.00179 ** 
attendance_percent  0.11937    0.01924   6.205 3.21e-09 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.864 on 195 degrees of freedom
Multiple R-squared:  0.6827,    Adjusted R-squared:  0.6762 
F-statistic: 104.9 on 4 and 195 DF,  p-value: < 2.2e-16
  1. Zlom v sklone – efekt hours_studied môže byť iný pre študentov nad a pod prahom:
modelD_sklon <- lm(exam_score ~ hours_studied + I(DUM*hours_studied) +
                                     sleep_hours + attendance_percent,
                   data=df)
summary(modelD_sklon)

Call:
lm(formula = exam_score ~ hours_studied + I(DUM * hours_studied) + 
    sleep_hours + attendance_percent, data = df)

Residuals:
   Min     1Q Median     3Q    Max 
-8.602 -2.557 -0.121  3.149  7.353 

Coefficients:
                       Estimate Std. Error t value Pr(>|t|)    
(Intercept)            11.09197    1.98099   5.599 7.24e-08 ***
hours_studied           1.57297    0.11888  13.232  < 2e-16 ***
I(DUM * hours_studied)  0.06085    0.08909   0.683  0.49540    
sleep_hours             0.58164    0.18346   3.170  0.00177 ** 
attendance_percent      0.11944    0.01923   6.211 3.12e-09 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.862 on 195 degrees of freedom
Multiple R-squared:  0.6829,    Adjusted R-squared:  0.6764 
F-statistic:   105 on 4 and 195 DF,  p-value: < 2.2e-16

Týmto modelom umožníme, aby sa sklon vzťahu medzi hours_studied a exam_score líšil pre študentov, ktorí študujú menej alebo viac ako 10 hodín.

Porovnanie pôvodného lineárneho modelu a modelu so zlomom vykonáme ANOVA testom a overíme pomocou RESET testu:

anova(model, modelD_sklon)
Analysis of Variance Table

Model 1: exam_score ~ hours_studied + sleep_hours + attendance_percent
Model 2: exam_score ~ hours_studied + I(DUM * hours_studied) + sleep_hours + 
    attendance_percent
  Res.Df    RSS Df Sum of Sq      F Pr(>F)
1    196 2915.8                           
2    195 2908.9  1    6.9593 0.4665 0.4954
library(lmtest)
resettest(modelD_sklon)

    RESET test

data:  modelD_sklon
RESET = 0.2949, df1 = 2, df2 = 193, p-value = 0.7449

Ak sa upravený koeficient determinácie R2adj zvýši, model lepšie vysvetľuje variabilitu výsledkov. Koeficient pri interakcii DUM*hours_studied nám ukazuje, ako sa efekt hodín štúdia mení pre študentov nad prahom. Ak je interakcia významná, vzťah medzi štúdiom a skóre nie je konštantný a zlom je opodstatnený. Ak nie, lineárny efekt hours_studied je postačujúci.

LS0tCnRpdGxlOiAixaBwZWNpZmlrw6FjaWEgbW9kZWx1IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBEaWFuYSBIcnXFoW92c2vDoQplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShjYXIpCmxpYnJhcnkobG10ZXN0KQpsaWJyYXJ5KHNhbmR3aWNoKQpsaWJyYXJ5KHRzZXJpZXMpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwYXRjaHdvcmspCnJtKGxpc3QgPSBscygpKQpgYGAKCkNpZcS+b20gYW5hbMO9enkgamUgbW9kZWxvdmHFpSB2w71zbGVkbsOpIHNrw7rFoWtvdsOpIHNrw7NyZSDFoXR1ZGVudG92CihleGFtX3Njb3JlKSBuYSB6w6FrbGFkZToKCipob3Vyc19zdHVkaWVkKiDigJMgcG/EjWV0IGhvZMOtbiDFoXTDumRpYSwgKnNsZWVwX2hvdXJzKiDigJMgcHJpZW1lcm7DvSBwb8SNZXQKaG9kw61uIHNww6Fua3UsICphdHRlbmRhbmNlX3BlcmNlbnQqIOKAkyBwZXJjZW50dcOhbG5hIMO6xI1hc8WlIG5hIHZ5dcSNb3ZhbsOtLgoKYGBge3J9CiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgUFJJUFJBVkEgVURBSk9WCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCmRmIDwtIHJlYWRfY3N2KCJzdHVkZW50X2V4YW1fc2NvcmVzLmNzdiIpICU+JQpzZWxlY3QoZXhhbV9zY29yZSwgaG91cnNfc3R1ZGllZCwgc2xlZXBfaG91cnMsIGF0dGVuZGFuY2VfcGVyY2VudCkKCiMgSW1wdXTDoWNpYSBtZWRpw6Fub20KCmNvbHVtbl9tZWRpYW5zIDwtIHNhcHBseShkZiwgbWVkaWFuLCBuYS5ybSA9IFRSVUUpCmZvcihjb2wgaW4gbmFtZXMoZGYpKXsKZGZbW2NvbF1dW2lzLm5hKGRmW1tjb2xdXSldIDwtIGNvbHVtbl9tZWRpYW5zW2NvbF0KfQoKc3VtbWFyeShkZikKCnBhcihtZnJvdz1jKDIsMikpCmNvbHMgPC0gYygiI2Q2MjgyOCIsIiMwMDZkMmMiLCIjZjRkMzVlIiwiI2ZmZmZmZiIpICMgxI1lcnZlbsOhLCB6ZWxlbsOhLCB6bGF0w6EsIGJpZWxhCmkgPC0gMQpmb3IoY29sIGluIG5hbWVzKGRmKSl7CmJveHBsb3QoZGZbW2NvbF1dLCBtYWluPWNvbCwgY29sPWNvbHNbaSAlJSBsZW5ndGgoY29scykgKyAxXSwgYm9yZGVyPWNvbHNbKGkrMSkgJSUgbGVuZ3RoKGNvbHMpICsgMV0pCmkgPC0gaSsxCn0KcGFyKG1mcm93PWMoMSwxKSkKCmBgYAoKYGBge3J9CiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgWkFLTEFETkEgUkVHUkVTSUEKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKbW9kZWwgPC0gbG0oZXhhbV9zY29yZSB+IGhvdXJzX3N0dWRpZWQgKyBzbGVlcF9ob3VycyArIGF0dGVuZGFuY2VfcGVyY2VudCwgZGF0YT1kZikKc3VtbWFyeShtb2RlbCkKYGBgCgpgYGB7cn0KcGFyKG1mcm93PWMoMiwyKSkKcGxvdChtb2RlbCwgY29sPSIjZDYyODI4IiwgcGNoPTE5LCBjZXg9MC42KQpwYXIobWZyb3c9YygxLDEpKQoKYGBgCgpEaWFnbm9zdGlja8OpIGdyYWZ5IHrDoWtsYWRuw6lobyBtb2RlbHUgbsOhbSBwb3NreXR1asO6IGNlbm7DqSBpbmZvcm3DoWNpZSBvCnZob2Rub3N0aSDFoXBlY2lmaWvDoWNpZSByZWdyZXNpZSBhIG8gdmxhc3Rub3N0aWFjaCByZXrDrWR1w60uCgpHcmFmIHJlesOtZHXDoSB2ZXJzdXMgdnlyb3ZuYW7DqSBob2Rub3R5IHVrYXp1amUsIMW+ZSByZXrDrWR1w6Egc8O6IHJvenB0w71sZW7DqQpuw6Fob2RuZSBva29sbyBudWxvdmVqIGzDrW5pZSwgYmV6IHbDvXJhem7DqWhvIHpha3JpdmVuaWEgYWxlYm8Kc3lzdGVtYXRpY2vDqWhvIHZ6b3J1LCDEjW8gbmF6bmHEjXVqZSwgxb5lIGxpbmXDoXJuYSDFoXBlY2lmaWvDoWNpYSBtb2RlbHUgamUKcHJpbWVyYW7DoSBhIG5pZSBqZSBwb3RyZWJuw6kgdHJhbnNmb3Jtb3ZhxaUgcHJlbWVubsOpLgoKUeKAk1EgZ3JhZiwga3RvcsO9IHBvcm92bsOhdmEga3ZhbnRpbHkgcmV6w61kdcOtIHMga3ZhbnRpbG1pIG5vcm3DoWxuZWhvCnJvemRlbGVuaWEsIHVrYXp1amUsIMW+ZSBib2R5IHNhIHBvaHlidWrDuiB2ZcS+bWkgYmzDrXprbyBkaWFnb27DoWxuZWogbMOtbmllLApsZW4gcyBtZW7FocOtbWkgb2RjaMO9bGthbWkgbmEgb2tyYWpvY2gsIMSNbyBwb3R2cmR6dWplLCDFvmUgcmV6w61kdcOhIHPDugpwcmlibGnFvm5lIG5vcm3DoWxuZSByb3psb8W+ZW7DqSBhIHByZWRwb2tsYWQgbm9ybWFsaXR5IG5pZSBqZSBwb3J1xaFlbsO9LgoKU2NhbGXigJNMb2NhdGlvbiBncmFmLCBrdG9yw70gem7DoXpvcsWIdWplIMWhdGFuZGFyZGl6b3ZhbsOpIHJlesOtZHXDoSB2b8SNaQp2eXJvdm5hbsO9bSBob2Rub3TDoW0sIHVrYXp1amUgcm92bm9tZXJuw6kgcm96cHTDvWxlbmllIGJvZG92IGJleiB6amF2bsOpaG8KcmFzdMO6Y2VobyBhbGVibyBrbGVzYWrDumNlaG8gdnpvcnUsIMSNbyBuYXpuYcSNdWplLCDFvmUgcHJlZHBva2xhZApob21vc2tlZGFzdGljaXR5IGplIHNwbG5lbsO9LgoKTmFrb25pZWMsIGRpYWdub3N0aWthIHBvbW9jb3UgQ29va292ZWogdnpkaWFsZW5vc3RpIGEgaWRlbnRpZmlrw6FjaWUKb2TEvmFobMO9Y2ggaG9kbsO0dCBuZW9kaGFsaWxhIMW+aWFkbmUgZXh0csOpbW5lIHBvem9yb3ZhbmlhLCBrdG9yw6kgYnkKdsO9em5hbW5lIGRlZm9ybW92YWxpIG1vZGVsLCB0YWvFvmUgdsWhZXRreSBwb3pvcm92YW5pYSBzw7ogc3BvxL5haGxpdsOpIGEKbW9kZWwgamUgc3RhYmlsbsO9LiBDZWxrb3ZvIHRpZXRvIGdyYWZ5IHBvZHBvcnVqw7ogdHZyZGVuaWUsIMW+ZSBsaW5lw6FybnkKbW9kZWwgcHJlIHZ5c3ZldMS+b3ZhbmllIHNrw7rFoWtvdsOpaG8gc2vDs3JlIMWhdHVkZW50b3YgamUgYWRla3bDoXRueSwgcmV6w61kdcOhCm1hasO6IHBvxb5hZG92YW7DqSB2bGFzdG5vc3RpIGEgdsO9c2xlZGt5IHJlZ3Jlc2llIHPDuiBpbnRlcnByZXRvdmF0ZcS+bsOpLgoKIyMjIDEuIFRlc3QgUkVTRVQgKHRlc3QgY2h5YnkgxaFwZWNpZmlrw6FjaWUgUmFtc2V5aG8gcmVncmVzbmVqIHJvdm5pY2UgLSBSYW1zZXkgUmVzZXQgVGVzdCkKClRlc3RvdmFuaWUsIMSNaSBsaW5lw6FybnkgbW9kZWwgcHJlIGV4YW1fc2NvcmUgamUgc3Byw6F2bmUgxaFwZWNpZmlrb3ZhbsO9LAp2eWtvbsOhbWUgcHJpZGFuw61tIG1vY27DrW4gdnlyb3ZuYW7DvWNoIGhvZG7DtHQgKGZpdHRlZCB2YWx1ZXMpIGRvIG1vZGVsdS4KUMO0dm9kbsO9IG1vZGVsOgoKZXhhbV9zY29yZXTigIs9zrIw4oCLK86yMeKAi+KLhWhvdXJzX3N0dWRpZWR04oCLK86yMuKAi+KLhXNsZWVwX2hvdXJzdOKAiyvOsjPigIvii4VhdHRlbmRhbmNlX3BlcmNlbnR04oCLK3V04oCLCgphIHJvesWhw61yZW7DvSBtb2RlbCBwcmUgdGVzdDoKCmV4YW1fc2NvcmV04oCLPc6yMOKAiyvOsjHigIvii4Vob3Vyc19zdHVkaWVkdOKAiyvOsjLigIvii4VzbGVlcF9ob3Vyc3TigIsrzrIz4oCL4ouFYXR0ZW5kYW5jZV9wZXJjZW50dOKAiyvOszLigIt5XuKAi3Qy4oCLK86zM+KAi3le4oCLdDPigIsrdXTigIsKCmtkZSB5XF7igIt0IHPDuiB2eXJvdm5hbsOpIGhvZG5vdHkgeiBww7R2b2Ruw6lobyBtb2RlbHUuIFRlc3R1amUgc2EgaHlwb3TDqXphOgoKSDA6IG1vZGVsIGplIHNwcsOhdm5lIMWhcGVjaWZpa292YW7DvSAoeTIgamUgcm92bsOpIHkzIGEgdG8gamUgcm92bsOpIDApIEgxOgptb2RlbCBqZSBuZXNwcsOhdm5lIMWhcGVjaWZpa292YW7DvSAoeTIgbmllIGplIHJvdm7DqSAwIGFsZWJvIHkzIG5pZSBqZQpyb3Zuw6kgMCkKCmBgYHtyfQojIHrDoWtsYWRuw70gbW9kZWwKbW9kZWwgPC0gbG0oZXhhbV9zY29yZSB+IGhvdXJzX3N0dWRpZWQgKyBzbGVlcF9ob3VycyArIGF0dGVuZGFuY2VfcGVyY2VudCwgZGF0YT1kZikKCiMgUkVTRVQgdGVzdApsaWJyYXJ5KGxtdGVzdCkKcmVzZXR0ZXN0KG1vZGVsKQoKYGBgCgoqKkludGVycHJldMOhY2lhOioqCgpBayBqZSBwLWhvZG5vdGEgdGVzdHUgbWVuxaFpYSBha28gMC4wNSwgemFtaWV0YW1lIG51bG92w7ogaHlwb3TDqXp1IGEgbW9kZWwKamUgenJlam1lIG5lc3Byw6F2bmUgxaFwZWNpZmlrb3ZhbsO9LiBUbyBieSB6bmFtZW5hbG8sIMW+ZSBsaW5lw6FybmEgZm9ybWEKbmVtdXPDrSBiecWlIMO6cGxuZSB2aG9kbsOhLCBtw7TFvmUgY2jDvWJhxaUgbmlla3RvcsOhIHByZW1lbm7DoSBhbGVibyBqZSB2aG9kbsOhCm5lbGluZcOhcm5hIHRyYW5zZm9ybcOhY2lhIChuYXByLiBsb2dhcml0bXVzIGhvZMOtbiDFoXTDumRpYSBhbGVibyBrdmFkcsOhdApwcmVtZW5uw71jaCkuIEFrIGplIHAtaG9kbm90YSB2w6TEjcWhaWEgYWtvIDAuMDUsIG51bG92w7ogaHlwb3TDqXp1Cm5lcHJpasOtbWFtZSBhIGxpbmXDoXJueSBtb2RlbCBqZSBhZGVrdsOhdG55IHByZSBuYcWhZSBkw6F0YS4KCiMjIyAyLiBHcmFmaWNrw6EgYW5hbMO9emEKCiMjIyMgR3JhZiAqUmVzaWR1YWxzIHZzLiBGaXR0ZWQqCgpOYWpwcnYgdnlrcmVzbMOtbWUga2xhc2lja8O9IGdyYWYgcmV6w61kdcOhIHZzLiB2eXJvdm5hbsOpIGhvZG5vdHkgcHJlCnrDoWtsYWRuw70gbW9kZWwuIFRlbnRvIGdyYWYgbsOhbSB1a8Ohxb5lLCDEjWkgcmV6w61kdcOhIHZ5a2F6dWrDuiBuw6Fob2Ruw70gdnpvci4KQWsgamUgcmV6w61kdcOtIHByw61sacWhIHpha3JpdmVuw70gYWxlYm8gc3lzdGVtYXRpY2vDvSwgbcO0xb5lIHRvIG5hem5hxI1vdmHFpQpwb3RyZWJ1IG5lbGluZcOhcm5laiB0cmFuc2Zvcm3DoWNpZSBwcmVtZW5uw71jaC4KCmBgYHtyfQojIFJlc2lkdWFscyB2cyBGaXR0ZWQKCnBsb3QobW9kZWwsIHdoaWNoID0gMSwgY29sPSIjZDYyODI4IiwgcGNoPTE5LCBjZXg9MC42KQoKYGBgCgpBayByZXrDrWR1w6EgdnlrYXp1asO6IG7DoWhvZG7DvSByb3pwdHlsIG9rb2xvIG51bHkgYSBuaWUgc8O6IMW+aWFkbmUgamFzbsOpCnpha3JpdmVuaWEsIG1vZGVsIGplIGxpbmXDoXJueSBhIHZob2RuZSDFoXBlY2lmaWtvdmFuw70uIEFrIGJ5IHNhIG9iamF2aWxhCnpha3JpdmVuw6EgxaF0cnVrdMO6cmEsIHp2w6HFvmltZSB0cmFuc2Zvcm3DoWNpdSBuaWVrdG9yw71jaCBwcmVtZW5uw71jaCwKbmFwcsOta2xhZCBsb2dhcml0bXVzIGFsZWJvIGRydWjDuiBtb2NuaW51LgoKIyMjIyBHcmFmeSBDK1IgXCpcKgoKYGBge3J9CiMgQytSIFBsb3RzIHByZSB2xaFldGt5IHZ5c3ZldMS+dWrDumNlIHByZW1lbm7DqQoKY2FyOjpjclBsb3RzKG1vZGVsLCBjb2w9IiMwMDZkMmMiLCBwY2g9MTkpCgpgYGAKCkFrIGtyaXZrYSBwcmUgZGFuw7ogcHJlbWVubsO6IHNsZWR1amUgcHJpYmxpxb5uZSBwcmlhbWt1LCBsaW5lw6FybnkKcHJlZHBva2xhZCBqZSB2aG9kbsO9LgoKQWsga3JpdmthIHZ5a2F6dWplIHpha3JpdmVuaWUsIG9kcG9yw7rEjWEgc2EgdHJhbnNmb3Jtw6FjaWEgZGFuZWoKcHJlbWVubmVqLCBuYXByw61rbGFkIGxvZ2FyaXRtdXMgaG91cnNfc3R1ZGllZCBhbGVibyBrdmFkcmF0aWNrw70gxI1sZW4KSShhdHRlbmRhbmNlX3BlcmNlbnRcXjIpLgoKViBwcsOtcGFkZSBkw6F0IG3DtMW+ZW1lIG/EjWFrw6F2YcWlLCDFvmUgaG91cnNfc3R1ZGllZCBtw7TFvmUgdnlrYXpvdmHFpSBtaWVybnUKbmVsaW5lYXJpdHUgKG5hcHIuIMO6Ynl0b2sgcHLDrW5vc3UgcHJpIHZ5c29rb20gcG/EjXRlIGhvZMOtbiDFoXTDumRpYSksCnphdGlhxL4gxI1vIHNsZWVwX2hvdXJzIGEgYXR0ZW5kYW5jZV9wZXJjZW50IG3DtMW+dSBiecWlIGxpbmXDoXJuZS4KCiMjIyAzLiBQb3Jvdm5hbmllIHrDoWtsYWRuw6lobyBhIG1vZGlmaWtvdmFuw6lobyBtb2RlbHUKClByZWRwb2tsYWRham1lLCDFvmUgQytSIGdyYWZ5IG5hem5hxI11asO6LCDFvmUgdnrFpWFoIG1lZHppIHbDvXNsZWRuw71tIHNrw7NyZQrFoXR1ZGVudG92IGEgcHJlbWVubsO9bWkgaG91cnNfc3R1ZGllZCBhIGF0dGVuZGFuY2VfcGVyY2VudCBtw7TFvmUgYnnFpQpuZWxpbmXDoXJueS4gWmF2ZWRpZW1lIHByZXRvIGRvIG1vZGVsdSBrdmFkcmF0aWNrw6kgxI1sZW55IHByZSB0aWV0bwpwcmVtZW5uw6kuIFDDtHZvZG7DvSBsaW5lw6FybnkgbW9kZWw6CgpgYGB7cn0KbW9kZWwgPC0gbG0oZXhhbV9zY29yZSB+IGhvdXJzX3N0dWRpZWQgKyBzbGVlcF9ob3VycyArIGF0dGVuZGFuY2VfcGVyY2VudCwgZGF0YSA9IGRmKQptb2RlbF9wb2x5IDwtIGxtKGV4YW1fc2NvcmUgfiBob3Vyc19zdHVkaWVkICsgSShob3Vyc19zdHVkaWVkXjIpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsZWVwX2hvdXJzICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF0dGVuZGFuY2VfcGVyY2VudCArIEkoYXR0ZW5kYW5jZV9wZXJjZW50XjIpLAogICAgICAgICAgICAgICAgIGRhdGEgPSBkZikKc3VtbWFyeShtb2RlbF9wb2x5KQphbm92YShtb2RlbCwgbW9kZWxfcG9seSkKbGlicmFyeShsbXRlc3QpCnJlc2V0dGVzdChtb2RlbF9wb2x5KQoKYGBgCgpWIG1vZGlmaWtvdmFub20gbW9kZWxpIG3DtMW+dSBiecWlIGt2YWRyYXRpY2vDqSDEjWxlbnkgxaF0YXRpc3RpY2t5IHbDvXpuYW1uw6ksCsSNbyBuYXpuYcSNdWplLCDFvmUgdnrFpWFoIG5pZSBqZSBwcmlhbW8gbGluZcOhcm55LiBBayB1cHJhdmVuw70ga29lZmljaWVudApkZXRlcm1pbsOhY2llIFIyYWRqIHN0w7pwb2wsIHpuYW1lbsOhIHRvLCDFvmUgbW9kZWwgbGVwxaFpZSB2eXN2ZXTEvnVqZQp2YXJpYWJpbGl0dSBza8O6xaFrb3bDvWNoIHbDvXNsZWRrb3YuIEFrIG5pZWt0b3LDvSBrdmFkcmF0aWNrw70gxI1sZW4gKG5hcHIuCnByZSBhdHRlbmRhbmNlX3BlcmNlbnQpIG5pZSBqZSDFoXRhdGlzdGlja3kgdsO9em5hbW7DvSwgbcO0xb5lbWUgaG8gdnlsw7rEjWnFpSBhCnphY2hvdmHFpSBsZW4gdsO9em5hbW7DqSBrdmFkcmF0aWNrw6kgxI1sZW55LCDEjcOtbSB6w61za2FtZSBqZWRub2R1Y2hlasWhw60gYQppbnRlcnByZXRvdmF0ZcS+bsO9IG1vZGVsLgoKIyMjIDQuIFRyYW5zZm9ybcOhY2lhIHBvbW9jb3UgZHVtbXkgcHJlbWVubmVqIGEgbGluZcOhcm5laiBsb21lbmVqIGZ1bmtjaWUKClByZWRwb2tsYWRham1lLCDFvmUgY2hjZW1lIGFuYWx5em92YcWlLCDEjWkgc2EgdnrFpWFoIG1lZHppIHBvxI10b20gaG9kw61uCsWhdMO6ZGlhIChob3Vyc19zdHVkaWVkKSBhIHNrw7rFoWtvdsO9bSBza8OzcmUgbWVuw60gbmFkIHVyxI1pdMO9bSBwcmFob20uClZ5dHZvcsOtbWUgZHVtbXkgcHJlbWVubsO6IERVTSwga3RvcsOhIG5hZCB1csSNaXTDvW0gcG/EjXRvbSBob2TDrW4gbmFkb2LDumRhCmhvZG5vdHUgMSBhIGluYWsgMC4gVMO9bSBtw7TFvmVtZSB0ZXN0b3ZhxaU6CgoxLiAgWmxvbSB2IGF1dG9uw7Ntbm9tIMSNbGVuZSDigJMgcG9zdW4gcmVncmVzbmVqIHByaWFta3kgcHJlIMWhdHVkZW50b3YsCiAgICBrdG9yw60gxaF0dWR1asO6IG5hZCBkYW7DvSBwcmFoOgoKYGBge3J9CmRmJERVTSA8LSBpZmVsc2UoZGYkaG91cnNfc3R1ZGllZCA+IDEwLCAxLCAwKSAgIyBwcmFoIDEwIGhvZMOtbiwgdXByYXZpIHBvZMS+YSBwb3RyZWJ5Cm1vZGVsRF9hdXRvIDwtIGxtKGV4YW1fc2NvcmUgfiBEVU0gKyBob3Vyc19zdHVkaWVkICsgc2xlZXBfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnQsIGRhdGE9ZGYpCnN1bW1hcnkobW9kZWxEX2F1dG8pCgpgYGAKCjIuICBabG9tIHYgc2tsb25lIOKAkyBlZmVrdCBob3Vyc19zdHVkaWVkIG3DtMW+ZSBiecWlIGluw70gcHJlIMWhdHVkZW50b3YgbmFkIGEKICAgIHBvZCBwcmFob206CgpgYGB7cn0KbW9kZWxEX3NrbG9uIDwtIGxtKGV4YW1fc2NvcmUgfiBob3Vyc19zdHVkaWVkICsgSShEVU0qaG91cnNfc3R1ZGllZCkgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xlZXBfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnQsCiAgICAgICAgICAgICAgICAgICBkYXRhPWRmKQpzdW1tYXJ5KG1vZGVsRF9za2xvbikKCmBgYAoKVMO9bXRvIG1vZGVsb20gdW1vxb5uw61tZSwgYWJ5IHNhIHNrbG9uIHZ6xaVhaHUgbWVkemkgaG91cnNfc3R1ZGllZCBhCmV4YW1fc2NvcmUgbMOtxaFpbCBwcmUgxaF0dWRlbnRvdiwga3RvcsOtIMWhdHVkdWrDuiBtZW5laiBhbGVibyB2aWFjIGFrbyAxMApob2TDrW4uCgpQb3Jvdm5hbmllIHDDtHZvZG7DqWhvIGxpbmXDoXJuZWhvIG1vZGVsdSBhIG1vZGVsdSBzbyB6bG9tb20gdnlrb27DoW1lIEFOT1ZBCnRlc3RvbSBhIG92ZXLDrW1lIHBvbW9jb3UgUkVTRVQgdGVzdHU6CgpgYGB7cn0KYW5vdmEobW9kZWwsIG1vZGVsRF9za2xvbikKbGlicmFyeShsbXRlc3QpCnJlc2V0dGVzdChtb2RlbERfc2tsb24pCgpgYGAKCkFrIHNhIHVwcmF2ZW7DvSBrb2VmaWNpZW50IGRldGVybWluw6FjaWUgUjJhZGogenbDvcWhaSwgbW9kZWwgbGVwxaFpZQp2eXN2ZXTEvnVqZSB2YXJpYWJpbGl0dSB2w71zbGVka292LiBLb2VmaWNpZW50IHByaSBpbnRlcmFrY2lpCkRVTVwqaG91cnNfc3R1ZGllZCBuw6FtIHVrYXp1amUsIGFrbyBzYSBlZmVrdCBob2TDrW4gxaF0w7pkaWEgbWVuw60gcHJlCsWhdHVkZW50b3YgbmFkIHByYWhvbS4gQWsgamUgaW50ZXJha2NpYSB2w716bmFtbsOhLCB2esWlYWggbWVkemkgxaF0w7pkaW9tIGEKc2vDs3JlIG5pZSBqZSBrb27FoXRhbnRuw70gYSB6bG9tIGplIG9wb2RzdGF0bmVuw70uIEFrIG5pZSwgbGluZcOhcm55IGVmZWt0CmhvdXJzX3N0dWRpZWQgamUgcG9zdGHEjXVqw7pjaS4K