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+γ2yt2+γ3yt3+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ť:
- 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
- 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