Example #1: ANCOVA with an experimental design and pretest as covariate

Load in some helpful packages

library(tidyverse)
library(haven)

Load in the dataset

write_ancova <- read_dta("Writing_Tech.dta")

Explore your data

glimpse(write_ancova)
Rows: 30
Columns: 3
$ Treatment  <dbl+lbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3,...
$ Cog_Engage <dbl> 3, 2, 5, 2, 2, 2, 7, 2, 4, 7, 5, 3, 4, 4, 7, 5, 4, 9, 2, ...
$ Pre_Engage <dbl> 4, 1, 5, 1, 2, 2, 7, 4, 5, 5, 3, 1, 2, 2, 6, 4, 2, 1, 3, ...

Clean your data

write_ancova.clean <- write_ancova %>%
mutate(.,
  treat.fac = as_factor(Treatment),
  cog.fac = as_factor(Cog_Engage),
  pre.fac = as_factor(Pre_Engage))

Check out descriptive statistics for the treatment factor and continuous variables

summary(write_ancova.clean)
   Treatment       Cog_Engage      Pre_Engage            treat.fac     cog.fac 
 Min.   :1.000   Min.   :2.000   Min.   :0.000   Paper/ Pencil: 9   4      :8  
 1st Qu.:1.000   1st Qu.:3.000   1st Qu.:1.000   Laptop       : 8   2      :7  
 Median :2.000   Median :4.000   Median :2.500   Tablet       :13   5      :4  
 Mean   :2.133   Mean   :4.367   Mean   :2.733                      3      :3  
 3rd Qu.:3.000   3rd Qu.:5.750   3rd Qu.:4.000                      6      :3  
 Max.   :3.000   Max.   :9.000   Max.   :7.000                      7      :3  
                                                                    (Other):2  
    pre.fac 
 1      :6  
 2      :6  
 3      :5  
 4      :4  
 5      :4  
 0      :3  
 (Other):2  

Correlate pre and post test results- how similar are they?

Hopefully, somewhat similar! If not, there may not be systematic change over time. The cor.test function in base R works well for simple correlations.

cor.test(write_ancova.clean$Cog_Engage, write_ancova.clean$Pre_Engage)

    Pearson's product-moment correlation

data:  write_ancova.clean$Cog_Engage and write_ancova.clean$Pre_Engage
t = 1.345, df = 28, p-value = 0.1894
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 -0.1250150  0.5571688
sample estimates:
      cor 
0.2463496 

Run a basic ANCOVA

Here, we have hap_post as the outcome, treat as the factor, and hap_pre as the covariate. Since we cleaned our data at the beginning, R recognizes that treat is a factor and hap_pre is continuous so it will run as an ANCOVA and not a two-way ANOVA.

ancova1<-aov(Cog_Engage ~ treat.fac + Pre_Engage, data = write_ancova.clean)
summary(ancova1)
            Df Sum Sq Mean Sq F value Pr(>F)  
treat.fac    2  16.84   8.422   2.770 0.0812 .
Pre_Engage   1  15.08  15.076   4.959 0.0348 *
Residuals   26  79.05   3.040                 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

If you had more than 2 groups, you could do post-hoc comparisons:

TukeyHSD(ancova1)
non-factors ignored: Pre_EngageError in rep.int(n, length(means)) : unimplemented type 'NULL' in 'rep3'

Get measures of effect size:

library(effectsize)
package 㤼㸱effectsize㤼㸲 was built under R version 4.0.3
eta_squared(ancova1)
Parameter  | Eta2 (partial) |       90% CI
------------------------------------------
treat.fac  |           0.18 | [0.00, 0.37]
Pre_Engage |           0.16 | [0.01, 0.37]
omega_squared(ancova1)
Parameter  | Omega2 (partial) |       90% CI
--------------------------------------------
treat.fac  |             0.11 | [0.00, 0.28]
Pre_Engage |             0.12 | [0.00, 0.32]

What if we didn’t have the pretest score?

Now, see the difference without the covariate- just an ANOVA for treat:

anova1<-aov(Cog_Engage ~ treat.fac, write_ancova.clean)
summary(anova1)
            Df Sum Sq Mean Sq F value Pr(>F)
treat.fac    2  16.84   8.422   2.416  0.108
Residuals   27  94.12   3.486               

And, of course- how to run this as a multiple regression!

regression1 <- lm(hap_post ~ treat.fac + hap_pre, data = hap_ancova.clean)
summary(regression1)

Example #3: Repeated Measures ANOVA

Reshaping data from wide to long

Hint for this week’s content review!

sleep <- read_dta("sleep_wide.dta")
glimpse(sleep)
summary(sleep)

Using pivot_longer to reshape from wide to long

sleep.long <- sleep %>%
pivot_longer(.,
              cols = starts_with("sleep"),
              names_to = "month",
              values_to = "sleep",
              names_prefix = "sleep",
              )
glimpse(sleep.long)

Reshape back to wide using pivot_wider

sleep.wide <- sleep.long %>%
  pivot_wider(.,
              id_cols = c("id"),
              names_from = month,
              values_from = sleep,
              names_prefix = "sleep",
              )

glimpse(sleep.wide)

Density Plot for Month 1

kdensity1 <- ggplot(sleep, aes(x=sleep1)) + 
  geom_density() + 
  stat_function(fun = dnorm, n = 100, args = list(mean = 350, sd = 30), color = "red", linetype = "dashed") +
  labs(title="Distribution of Sleep in Month 1",x="Sleep (minutes per night)", y = "Density", caption = "N = 100 participants. The red line indicates a normal distribution. The black lines represents the observed distribution.")
kdensity1

Density Plot for Month 2

kdensity2 <- ggplot(sleep, aes(x=sleep2)) + 
  geom_density() + 
  stat_function(fun = dnorm, n = 100, args = list(mean = 370, sd = 30), color = "red", linetype = "dashed") +
  labs(title="Distribution of Sleep in Month 2",x="Sleep (minutes per night)", y = "Density", caption = "N = 100 participants. The red line indicates a normal distribution. The black lines represents the observed distribution.")
kdensity2

Density Plot for Month 3

kdensity3 <- ggplot(sleep, aes(x=sleep3)) + 
  geom_density() + 
  stat_function(fun = dnorm, n = 100, args = list(mean = 370, sd = 30), color = "red", linetype = "dashed") +
  labs(title="Distribution of Sleep in Month 3",x="Sleep (minutes per night)", y = "Density", caption = "N = 100 participants. The red line indicates a normal distribution. The black lines represents the observed distribution.")
kdensity3

Using data in long form, we can facet by month to include all three graphs in one table

all_sleep<-ggplot(sleep.long, aes(x=sleep))+
  geom_density()+facet_wrap(month ~ ., ncol = 1)
all_sleep

Summary Statistics of Sleep by Month

library(rstatix)
library(ggpubr)
sleep.long %>%
  group_by(month) %>%
  get_summary_stats(sleep, type = "mean_sd")

Code for basic repeated measures ANOVA

Note:id is participant id in this case.

sleep.aov <- anova_test(data = sleep.long, dv = sleep, wid = id, within = month)
summary(sleep.aov)

Here is the basic ANOVA table:

get_anova_table(sleep.aov)

Here are the corrected p-values with the sphericity adjustments:

sleep.aov$`Sphericity Corrections`

Post-hoc comparisons by month, using Tukey’s method:

pairwise_t_test(
    formula = sleep ~ month, paired = TRUE,
    p.adjust.method = "bonferroni",
    data = sleep.long
    )

Check for sphericity and multivariate normality

Start with univariate normality, plots and statistics

library(MVN)
sleep_univariate <- mvn(data = sleep.wide, mvnTest = NULL, univariatePlot = "qqplot")

sleep_univariate2 <- mvn(data = sleep.wide, mvnTest = NULL, univariateTest = "SW", desc = TRUE)
sleep_univariate2$univariateNormality

Tests for MVN

Doornik-Hansen Test

The last column indicates whether dataset follows a multivariate normality or not (i.e, YES or NO) at significance level 0.05.

sleep_mvn <- mvn(data = sleep.wide, mvnTest = "dh")
sleep_mvn$multivariateNormality

Mardia’s MVN Test

This function performs multivariate skewness and kurtosis tests at the same time and combines test results for multivariate normality. If both tests indicate multivariate normality, then data follows a multivariate normal distribution at the 0.05 significance level.

sleep_mvn2 <- mvn(data = sleep.wide, mvnTest = "mardia")
sleep_mvn2$multivariateNormality

Check the sphericity assumption

sleep.aov$`Mauchly's Test for Sphericity`
LS0tDQp0aXRsZTogIk1WUyBNb2R1bGUgNDogQWR2YW5jZWQgQU5PVkEiDQphdXRob3I6ICJKYWtlIFJleW5vbGRzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBFeGFtcGxlICMxOiBBTkNPVkEgd2l0aCBhbiBleHBlcmltZW50YWwgZGVzaWduIGFuZCBwcmV0ZXN0IGFzIGNvdmFyaWF0ZQ0KIyMgTG9hZCBpbiBzb21lIGhlbHBmdWwgcGFja2FnZXMNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhhdmVuKQ0KYGBgDQoNCiMjIExvYWQgaW4gdGhlIGRhdGFzZXQNCmBgYHtyfQ0Kd3JpdGVfYW5jb3ZhIDwtIHJlYWRfZHRhKCJXcml0aW5nX1RlY2guZHRhIikNCmBgYA0KDQojIyBFeHBsb3JlIHlvdXIgZGF0YQ0KYGBge3J9DQpnbGltcHNlKHdyaXRlX2FuY292YSkNCmBgYA0KIyMgQ2xlYW4geW91ciBkYXRhDQpgYGB7cn0NCndyaXRlX2FuY292YS5jbGVhbiA8LSB3cml0ZV9hbmNvdmEgJT4lDQptdXRhdGUoLiwNCiAgdHJlYXQuZmFjID0gYXNfZmFjdG9yKFRyZWF0bWVudCksDQogIGNvZy5mYWMgPSBhc19mYWN0b3IoQ29nX0VuZ2FnZSksDQogIHByZS5mYWMgPSBhc19mYWN0b3IoUHJlX0VuZ2FnZSkpDQpgYGANCg0KIyMgQ2hlY2sgb3V0IGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MgZm9yIHRoZSB0cmVhdG1lbnQgZmFjdG9yIGFuZCBjb250aW51b3VzIHZhcmlhYmxlcyANCmBgYHtyfQ0Kc3VtbWFyeSh3cml0ZV9hbmNvdmEuY2xlYW4pDQpgYGANCg0KIyMgQ29ycmVsYXRlIHByZSBhbmQgcG9zdCB0ZXN0IHJlc3VsdHMtIGhvdyBzaW1pbGFyIGFyZSB0aGV5PyANCkhvcGVmdWxseSwgc29tZXdoYXQgc2ltaWxhciEgSWYgbm90LCB0aGVyZSBtYXkgbm90IGJlIHN5c3RlbWF0aWMgY2hhbmdlIG92ZXIgdGltZS4gVGhlIGBjb3IudGVzdGAgZnVuY3Rpb24gaW4gYmFzZSBgUmAgd29ya3Mgd2VsbCBmb3Igc2ltcGxlIGNvcnJlbGF0aW9ucy4NCmBgYHtyfQ0KY29yLnRlc3Qod3JpdGVfYW5jb3ZhLmNsZWFuJENvZ19FbmdhZ2UsIHdyaXRlX2FuY292YS5jbGVhbiRQcmVfRW5nYWdlKQ0KYGBgDQoNCiMjIFJ1biBhIGJhc2ljIEFOQ09WQSANCkhlcmUsIHdlIGhhdmUgYGhhcF9wb3N0YCBhcyB0aGUgb3V0Y29tZSwgYHRyZWF0YCBhcyB0aGUgZmFjdG9yLCBhbmQgYGhhcF9wcmVgIGFzIHRoZSBjb3ZhcmlhdGUuIFNpbmNlIHdlIGNsZWFuZWQgb3VyIGRhdGEgYXQgdGhlIGJlZ2lubmluZywgYFJgIHJlY29nbml6ZXMgdGhhdCBgdHJlYXRgIGlzIGEgZmFjdG9yIGFuZCBgaGFwX3ByZWAgaXMgY29udGludW91cyBzbyBpdCB3aWxsIHJ1biBhcyBhbiBBTkNPVkEgYW5kIG5vdCBhIHR3by13YXkgQU5PVkEuDQpgYGB7cn0NCmFuY292YTE8LWFvdihDb2dfRW5nYWdlIH4gdHJlYXQuZmFjICsgUHJlX0VuZ2FnZSwgZGF0YSA9IHdyaXRlX2FuY292YS5jbGVhbikNCnN1bW1hcnkoYW5jb3ZhMSkNCmBgYA0KDQojIyBJZiB5b3UgaGFkIG1vcmUgdGhhbiAyIGdyb3VwcywgeW91IGNvdWxkIGRvIHBvc3QtaG9jIGNvbXBhcmlzb25zOg0KYGBge3J9DQpUdWtleUhTRChhbmNvdmExKQ0KYGBgDQoNCiMjIEdldCBtZWFzdXJlcyBvZiBlZmZlY3Qgc2l6ZToNCmBgYHtyfQ0KbGlicmFyeShlZmZlY3RzaXplKQ0KZXRhX3NxdWFyZWQoYW5jb3ZhMSkNCm9tZWdhX3NxdWFyZWQoYW5jb3ZhMSkNCmBgYA0KDQojIyBXaGF0IGlmIHdlIGRpZG4ndCBoYXZlIHRoZSBwcmV0ZXN0IHNjb3JlPw0KTm93LCBzZWUgdGhlIGRpZmZlcmVuY2Ugd2l0aG91dCB0aGUgY292YXJpYXRlLSBqdXN0IGFuIEFOT1ZBIGZvciBgdHJlYXRgOg0KYGBge3J9DQphbm92YTE8LWFvdihDb2dfRW5nYWdlIH4gdHJlYXQuZmFjLCB3cml0ZV9hbmNvdmEuY2xlYW4pDQpzdW1tYXJ5KGFub3ZhMSkNCmBgYA0KDQojIyBBbmQsIG9mIGNvdXJzZS0gaG93IHRvIHJ1biB0aGlzIGFzIGEgbXVsdGlwbGUgcmVncmVzc2lvbiENCmBgYHtyfQ0KcmVncmVzc2lvbjEgPC0gbG0oaGFwX3Bvc3QgfiB0cmVhdC5mYWMgKyBoYXBfcHJlLCBkYXRhID0gaGFwX2FuY292YS5jbGVhbikNCnN1bW1hcnkocmVncmVzc2lvbjEpDQpgYGANCg0KIyBFeGFtcGxlICMyOiBBTkNPVkEgd2l0aCBleHBlcmltZW50YWwgZGVzaWduIGFuZCByZWxhdGVkIGNvdmFyaWF0ZXMgKG5vdCBwcmV0ZXN0KQ0KSXQgbWlnaHQgYmUgYmV0dGVyIGZvciBleHRlcm5hbCB2YWxpZGl0eSB0byB1c2UgcmVsYXRlZCBtZWFzdXJlcyBhcyBjb3ZhcmlhdGVzLCBpbnN0ZWFkIG9mIGEgcHJldGVzdC4NCiMjIExvYWQgaW4gdGhlIGRhdGFzZXQ6DQpgYGB7cn0NCmhhcF9hbmNvdmEyIDwtIHJlYWRfZHRhKCJoYXAtYW5jb3ZhMi5kdGEiKQ0KYGBgDQoNCiMjIEV4cGxvcmUgeW91ciBkYXRhOg0KYGBge3J9DQpnbGltcHNlKGhhcF9hbmNvdmEyKQ0KYGBgDQoNCiMjIENsZWFuIHlvdXIgZGF0YToNCmBgYHtyfQ0KaGFwX2FuY292YTIuY2xlYW4gPC0gaGFwX2FuY292YTIgJT4lDQogIG11dGF0ZSguLA0KICAgICAgICAgdHJlYXQuZmFjID0gYXNfZmFjdG9yKHRyZWF0KSkNCmBgYA0KDQojIyBFeHBsb3JlIHlvdXIgZGF0YQ0KYGBge3J9DQpzdW1tYXJ5KGhhcF9hbmNvdmEyLmNsZWFuKQ0KYGBgDQoNCiMjIEhvdyBtdWNoIHZhcmlhbmNlIGRvZXMgb3VyIHNldCBvZiBjb3ZhcmlhdGVzIGV4cGxhaW4/DQpgYGB7cn0NCnJlZ3Jlc3Npb24yIDwtIGxtKGhhcCB+IHNhdGZhbSArIGhlYWx0aCArIHNvY2ZyaWVuZCwgZGF0YSA9IGhhcF9hbmNvdmEyLmNsZWFuKQ0Kc3VtbWFyeShyZWdyZXNzaW9uMikNCmBgYA0KDQojIyBOb3csIHBsdWcgdGhlbSBpbnRvIHRoZSBBTkNPVkEgbW9kZWw6DQpgYGB7cn0NCmFuY292YTIgPC0gYW92KGhhcCB+IHRyZWF0LmZhYyArIHNhdGZhbSArIGhlYWx0aCArIHNvY2ZyaWVuZCwgZGF0YSA9IGhhcF9hbmNvdmEyLmNsZWFuKQ0Kc3VtbWFyeShhbmNvdmEyKQ0KYGBgDQoNCiMjIFJlZ3Jlc3Npb24gbW9kZWwgd2l0aCB0aGUgdmFyaWFibGUgYHRyZWF0LmZhY2ANCmBgYHtyfQ0KcmVncmVzc2lvbjMgPC0gbG0oaGFwIH4gdHJlYXQuZmFjICsgc2F0ZmFtICsgaGVhbHRoICsgc29jZnJpZW5kLCBkYXRhID0gaGFwX2FuY292YTIuY2xlYW4pDQpzdW1tYXJ5KHJlZ3Jlc3Npb24zKQ0KYGBgDQoNCiMgRXhhbXBsZSAjMzogUmVwZWF0ZWQgTWVhc3VyZXMgQU5PVkENCiMjIFJlc2hhcGluZyBkYXRhIGZyb20gd2lkZSB0byBsb25nIA0KSGludCBmb3IgdGhpcyB3ZWVrJ3MgY29udGVudCByZXZpZXchDQpgYGB7cn0NCnNsZWVwIDwtIHJlYWRfZHRhKCJzbGVlcF93aWRlLmR0YSIpDQpgYGANCg0KYGBge3J9DQpnbGltcHNlKHNsZWVwKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShzbGVlcCkNCmBgYA0KDQojIyBVc2luZyBgcGl2b3RfbG9uZ2VyYCB0byByZXNoYXBlIGZyb20gKndpZGUqIHRvICpsb25nKg0KYGBge3J9DQpzbGVlcC5sb25nIDwtIHNsZWVwICU+JQ0KcGl2b3RfbG9uZ2VyKC4sDQogICAgICAgICAgICAgIGNvbHMgPSBzdGFydHNfd2l0aCgic2xlZXAiKSwNCiAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibW9udGgiLA0KICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAic2xlZXAiLA0KICAgICAgICAgICAgICBuYW1lc19wcmVmaXggPSAic2xlZXAiLA0KICAgICAgICAgICAgICApDQpnbGltcHNlKHNsZWVwLmxvbmcpDQpgYGANCg0KIyMgUmVzaGFwZSBiYWNrIHRvIHdpZGUgdXNpbmcgYHBpdm90X3dpZGVyYA0KYGBge3J9DQpzbGVlcC53aWRlIDwtIHNsZWVwLmxvbmcgJT4lDQogIHBpdm90X3dpZGVyKC4sDQogICAgICAgICAgICAgIGlkX2NvbHMgPSBjKCJpZCIpLA0KICAgICAgICAgICAgICBuYW1lc19mcm9tID0gbW9udGgsDQogICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gc2xlZXAsDQogICAgICAgICAgICAgIG5hbWVzX3ByZWZpeCA9ICJzbGVlcCIsDQogICAgICAgICAgICAgICkNCg0KZ2xpbXBzZShzbGVlcC53aWRlKQ0KYGBgDQoNCiMjIERlbnNpdHkgUGxvdCBmb3IgTW9udGggMQ0KYGBge3J9DQprZGVuc2l0eTEgPC0gZ2dwbG90KHNsZWVwLCBhZXMoeD1zbGVlcDEpKSArIA0KICBnZW9tX2RlbnNpdHkoKSArIA0KICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRub3JtLCBuID0gMTAwLCBhcmdzID0gbGlzdChtZWFuID0gMzUwLCBzZCA9IDMwKSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKHRpdGxlPSJEaXN0cmlidXRpb24gb2YgU2xlZXAgaW4gTW9udGggMSIseD0iU2xlZXAgKG1pbnV0ZXMgcGVyIG5pZ2h0KSIsIHkgPSAiRGVuc2l0eSIsIGNhcHRpb24gPSAiTiA9IDEwMCBwYXJ0aWNpcGFudHMuIFRoZSByZWQgbGluZSBpbmRpY2F0ZXMgYSBub3JtYWwgZGlzdHJpYnV0aW9uLiBUaGUgYmxhY2sgbGluZXMgcmVwcmVzZW50cyB0aGUgb2JzZXJ2ZWQgZGlzdHJpYnV0aW9uLiIpDQprZGVuc2l0eTENCmBgYA0KIyMgRGVuc2l0eSBQbG90IGZvciBNb250aCAyDQpgYGB7cn0NCmtkZW5zaXR5MiA8LSBnZ3Bsb3Qoc2xlZXAsIGFlcyh4PXNsZWVwMikpICsgDQogIGdlb21fZGVuc2l0eSgpICsgDQogIHN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIG4gPSAxMDAsIGFyZ3MgPSBsaXN0KG1lYW4gPSAzNzAsIHNkID0gMzApLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnModGl0bGU9IkRpc3RyaWJ1dGlvbiBvZiBTbGVlcCBpbiBNb250aCAyIix4PSJTbGVlcCAobWludXRlcyBwZXIgbmlnaHQpIiwgeSA9ICJEZW5zaXR5IiwgY2FwdGlvbiA9ICJOID0gMTAwIHBhcnRpY2lwYW50cy4gVGhlIHJlZCBsaW5lIGluZGljYXRlcyBhIG5vcm1hbCBkaXN0cmlidXRpb24uIFRoZSBibGFjayBsaW5lcyByZXByZXNlbnRzIHRoZSBvYnNlcnZlZCBkaXN0cmlidXRpb24uIikNCmtkZW5zaXR5Mg0KYGBgDQoNCiMjIERlbnNpdHkgUGxvdCBmb3IgTW9udGggMw0KYGBge3J9DQprZGVuc2l0eTMgPC0gZ2dwbG90KHNsZWVwLCBhZXMoeD1zbGVlcDMpKSArIA0KICBnZW9tX2RlbnNpdHkoKSArIA0KICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRub3JtLCBuID0gMTAwLCBhcmdzID0gbGlzdChtZWFuID0gMzcwLCBzZCA9IDMwKSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKHRpdGxlPSJEaXN0cmlidXRpb24gb2YgU2xlZXAgaW4gTW9udGggMyIseD0iU2xlZXAgKG1pbnV0ZXMgcGVyIG5pZ2h0KSIsIHkgPSAiRGVuc2l0eSIsIGNhcHRpb24gPSAiTiA9IDEwMCBwYXJ0aWNpcGFudHMuIFRoZSByZWQgbGluZSBpbmRpY2F0ZXMgYSBub3JtYWwgZGlzdHJpYnV0aW9uLiBUaGUgYmxhY2sgbGluZXMgcmVwcmVzZW50cyB0aGUgb2JzZXJ2ZWQgZGlzdHJpYnV0aW9uLiIpDQprZGVuc2l0eTMNCmBgYA0KDQojIyBVc2luZyBkYXRhIGluICpsb25nKiBmb3JtLCB3ZSBjYW4gZmFjZXQgYnkgbW9udGggdG8gaW5jbHVkZSBhbGwgdGhyZWUgZ3JhcGhzIGluIG9uZSB0YWJsZQ0KYGBge3J9DQphbGxfc2xlZXA8LWdncGxvdChzbGVlcC5sb25nLCBhZXMoeD1zbGVlcCkpKw0KICBnZW9tX2RlbnNpdHkoKStmYWNldF93cmFwKG1vbnRoIH4gLiwgbmNvbCA9IDEpDQphbGxfc2xlZXANCmBgYA0KDQojIyBTdW1tYXJ5IFN0YXRpc3RpY3Mgb2YgU2xlZXAgYnkgTW9udGgNCmBgYHtyfQ0KbGlicmFyeShyc3RhdGl4KQ0KbGlicmFyeShnZ3B1YnIpDQpzbGVlcC5sb25nICU+JQ0KICBncm91cF9ieShtb250aCkgJT4lDQogIGdldF9zdW1tYXJ5X3N0YXRzKHNsZWVwLCB0eXBlID0gIm1lYW5fc2QiKQ0KYGBgDQoNCiMjIENvZGUgZm9yIGJhc2ljIHJlcGVhdGVkIG1lYXN1cmVzIEFOT1ZBIA0KTm90ZTpgaWRgIGlzIHBhcnRpY2lwYW50IGlkIGluIHRoaXMgY2FzZS4NCmBgYHtyfQ0Kc2xlZXAuYW92IDwtIGFub3ZhX3Rlc3QoZGF0YSA9IHNsZWVwLmxvbmcsIGR2ID0gc2xlZXAsIHdpZCA9IGlkLCB3aXRoaW4gPSBtb250aCkNCnN1bW1hcnkoc2xlZXAuYW92KQ0KYGBgDQpIZXJlIGlzIHRoZSBiYXNpYyBBTk9WQSB0YWJsZToNCmBgYHtyfQ0KZ2V0X2Fub3ZhX3RhYmxlKHNsZWVwLmFvdikNCmBgYA0KSGVyZSBhcmUgdGhlIGNvcnJlY3RlZCBwLXZhbHVlcyB3aXRoIHRoZSBzcGhlcmljaXR5IGFkanVzdG1lbnRzOg0KYGBge3J9DQpzbGVlcC5hb3YkYFNwaGVyaWNpdHkgQ29ycmVjdGlvbnNgDQpgYGANCg0KIyMgUG9zdC1ob2MgY29tcGFyaXNvbnMgYnkgbW9udGgsIHVzaW5nIFR1a2V5J3MgbWV0aG9kOg0KYGBge3J9DQpwYWlyd2lzZV90X3Rlc3QoDQogICAgZm9ybXVsYSA9IHNsZWVwIH4gbW9udGgsIHBhaXJlZCA9IFRSVUUsDQogICAgcC5hZGp1c3QubWV0aG9kID0gImJvbmZlcnJvbmkiLA0KICAgIGRhdGEgPSBzbGVlcC5sb25nDQogICAgKQ0KYGBgDQoNCiMgQ2hlY2sgZm9yIHNwaGVyaWNpdHkgYW5kIG11bHRpdmFyaWF0ZSBub3JtYWxpdHkNCiMjIyAgU3RhcnQgd2l0aCB1bml2YXJpYXRlIG5vcm1hbGl0eSwgcGxvdHMgYW5kIHN0YXRpc3RpY3MNCmBgYHtyfQ0KbGlicmFyeShNVk4pDQpzbGVlcF91bml2YXJpYXRlIDwtIG12bihkYXRhID0gc2xlZXAud2lkZSwgbXZuVGVzdCA9IE5VTEwsIHVuaXZhcmlhdGVQbG90ID0gInFxcGxvdCIpDQoNCnNsZWVwX3VuaXZhcmlhdGUyIDwtIG12bihkYXRhID0gc2xlZXAud2lkZSwgbXZuVGVzdCA9IE5VTEwsIHVuaXZhcmlhdGVUZXN0ID0gIlNXIiwgZGVzYyA9IFRSVUUpDQpzbGVlcF91bml2YXJpYXRlMiR1bml2YXJpYXRlTm9ybWFsaXR5DQpgYGANCiMjIyBUZXN0cyBmb3IgTVZODQojIyMjIERvb3JuaWstSGFuc2VuIFRlc3QgDQpUaGUgbGFzdCBjb2x1bW4gaW5kaWNhdGVzIHdoZXRoZXIgZGF0YXNldCBmb2xsb3dzIGEgbXVsdGl2YXJpYXRlIG5vcm1hbGl0eSBvciBub3QgKGkuZSwgWUVTIG9yIE5PKSBhdCBzaWduaWZpY2FuY2UgbGV2ZWwgMC4wNS4NCmBgYHtyfQ0Kc2xlZXBfbXZuIDwtIG12bihkYXRhID0gc2xlZXAud2lkZSwgbXZuVGVzdCA9ICJkaCIpDQpzbGVlcF9tdm4kbXVsdGl2YXJpYXRlTm9ybWFsaXR5DQpgYGANCg0KIyMjIyBNYXJkaWEncyBNVk4gVGVzdCANClRoaXMgZnVuY3Rpb24gcGVyZm9ybXMgbXVsdGl2YXJpYXRlIHNrZXduZXNzIGFuZCBrdXJ0b3NpcyB0ZXN0cyBhdCB0aGUgc2FtZSB0aW1lIGFuZCBjb21iaW5lcyB0ZXN0IHJlc3VsdHMgZm9yIG11bHRpdmFyaWF0ZSBub3JtYWxpdHkuIElmIGJvdGggdGVzdHMgaW5kaWNhdGUgbXVsdGl2YXJpYXRlIG5vcm1hbGl0eSwgdGhlbiBkYXRhIGZvbGxvd3MgYSBtdWx0aXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiBhdCB0aGUgMC4wNSBzaWduaWZpY2FuY2UgbGV2ZWwuDQpgYGB7cn0NCnNsZWVwX212bjIgPC0gbXZuKGRhdGEgPSBzbGVlcC53aWRlLCBtdm5UZXN0ID0gIm1hcmRpYSIpDQpzbGVlcF9tdm4yJG11bHRpdmFyaWF0ZU5vcm1hbGl0eQ0KYGBgDQojIyBDaGVjayB0aGUgc3BoZXJpY2l0eSBhc3N1bXB0aW9uDQpgYGB7cn0NCnNsZWVwLmFvdiRgTWF1Y2hseSdzIFRlc3QgZm9yIFNwaGVyaWNpdHlgDQpgYGANCg==