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

Load in some helpful packages

library(tidyverse)
library(haven)

Load in the dataset

hap_ancova <- read_dta("hap-ancova1.dta")

Explore your data

glimpse(hap_ancova)
Rows: 82
Columns: 4
$ id       <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, …
$ hap_pre  <dbl> 40, 51, 54, 58, 52, 44, 58, 41, 42, 66, 47, 66, 47, 54,…
$ hap_post <dbl> 49, 56, 51, 52, 68, 53, 47, 49, 47, 58, 36, 59, 49, 54,…
$ treat    <dbl+lbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …

Clean your data

hap_ancova.clean <- hap_ancova %>%
mutate(.,
  treat.fac = as_factor(treat))

Check out descriptive statistics for the treatment factor and continuous variables

The summary package in base R is a quick and easy way to do this!

summary(hap_ancova.clean)
       id           hap_pre        hap_post         treat    
 Min.   : 1.00   Min.   :28.0   Min.   :30.00   Min.   :1.0  
 1st Qu.:21.25   1st Qu.:44.0   1st Qu.:47.00   1st Qu.:1.0  
 Median :41.50   Median :51.5   Median :51.00   Median :1.5  
 Mean   :41.50   Mean   :51.3   Mean   :53.46   Mean   :1.5  
 3rd Qu.:61.75   3rd Qu.:58.0   3rd Qu.:59.00   3rd Qu.:2.0  
 Max.   :82.00   Max.   :81.0   Max.   :77.00   Max.   :2.0  
                  treat.fac 
 Control group         :41  
 Optimism therapy group:41  
                            
                            
                            
                            

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(hap_ancova.clean$hap_pre, hap_ancova.clean$hap_post)

    Pearson's product-moment correlation

data:  hap_ancova.clean$hap_pre and hap_ancova.clean$hap_post
t = 6.3349, df = 80, p-value = 1.302e-08
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.4127435 0.7063892
sample estimates:
      cor 
0.5779818 

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(hap_post~ treat.fac + hap_pre,data=hap_ancova.clean)
summary(ancova1)
            Df Sum Sq Mean Sq F value   Pr(>F)    
treat.fac    1    226   225.6   3.653   0.0596 .  
hap_pre      1   2700  2700.4  43.730 4.07e-09 ***
Residuals   79   4878    61.8                     
---
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: hap_pre'which' specified some non-factors which will be dropped
  Tukey multiple comparisons of means
    95% family-wise confidence level

Fit: aov(formula = hap_post ~ treat.fac + hap_pre, data = hap_ancova.clean)

$treat.fac
                                         diff        lwr      upr
Optimism therapy group-Control group 3.317073 -0.1375479 6.771694
                                         p adj
Optimism therapy group-Control group 0.0596061

Get measures of effect size:

library(effectsize)
eta_squared(ancova1)
 treat.fac    hap_pre 
0.02890181 0.34601162 
omega_squared(ancova1)
Parameter | Omega2 (partial) |       90% CI
-------------------------------------------
treat.fac |             0.03 | [0.00, 0.12]
hap_pre   |             0.34 | [0.21, 0.46]

What if we didn’t have the pretest score?

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

anova1<-aov(hap_post~ treat.fac, data=hap_ancova.clean)
summary(anova1)
            Df Sum Sq Mean Sq F value Pr(>F)
treat.fac    1    226  225.56   2.381  0.127
Residuals   80   7579   94.74               

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)

Call:
lm(formula = hap_post ~ treat.fac + hap_pre, data = hap_ancova.clean)

Residuals:
     Min       1Q   Median       3Q      Max 
-20.7017  -3.7916  -0.0503   4.2741  18.3137 

Coefficients:
                                Estimate Std. Error t value Pr(>|t|)    
(Intercept)                      20.5479     4.8834   4.208 6.76e-05 ***
treat.facOptimism therapy group   3.9496     1.7382   2.272   0.0258 *  
hap_pre                           0.6031     0.0912   6.613 4.07e-09 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 7.858 on 79 degrees of freedom
Multiple R-squared:  0.3749,    Adjusted R-squared:  0.3591 
F-statistic: 23.69 on 2 and 79 DF,  p-value: 8.702e-09

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)
Rows: 100
Columns: 4
$ id     <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17…
$ sleep1 <dbl> 303, 331, 374, 374, 349, 353, 343, 390, 307, 360, 327, 36…
$ sleep2 <dbl> 349, 380, 400, 385, 442, 365, 370, 399, 410, 346, 371, 34…
$ sleep3 <dbl> 382, 350, 345, 356, 366, 372, 354, 352, 375, 403, 336, 40…
summary(sleep)
       id             sleep1          sleep2          sleep3     
 Min.   :  1.00   Min.   :276.0   Min.   :308.0   Min.   :297.0  
 1st Qu.: 25.75   1st Qu.:326.5   1st Qu.:348.0   1st Qu.:348.8  
 Median : 50.50   Median :349.0   Median :368.0   Median :366.5  
 Mean   : 50.50   Mean   :348.2   Mean   :369.3   Mean   :366.7  
 3rd Qu.: 75.25   3rd Qu.:369.2   3rd Qu.:389.0   3rd Qu.:382.2  
 Max.   :100.00   Max.   :434.0   Max.   :466.0   Max.   :437.0  

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)
Rows: 300
Columns: 3
$ id    <dbl> 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7…
$ month <chr> "1", "2", "3", "1", "2", "3", "1", "2", "3", "1", "2", "3"…
$ sleep <dbl> 303, 349, 382, 331, 380, 350, 374, 400, 345, 374, 385, 356…

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)
Rows: 100
Columns: 4
$ id     <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17…
$ sleep1 <dbl> 303, 331, 374, 374, 349, 353, 343, 390, 307, 360, 327, 36…
$ sleep2 <dbl> 349, 380, 400, 385, 442, 365, 370, 399, 410, 346, 371, 34…
$ sleep3 <dbl> 382, 350, 345, 356, 366, 372, 354, 352, 375, 403, 336, 40…

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=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 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)
                              Length Class      Mode
ANOVA                         7      data.frame list
Mauchly's Test for Sphericity 4      data.frame list
Sphericity Corrections        9      data.frame list

Here is the basic ANOVA table:

get_anova_table(sleep.aov)
ANOVA Table (type III tests)

  Effect DFn DFd      F        p p<.05  ges
1  month   2 198 17.938 6.92e-08     * 0.09

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`
LS0tCnRpdGxlOiAiTVZTIE1vZHVsZSA0IEhhbmRvdXQ6IEFkdmFuY2VkIEFOT1ZBIgphdXRob3I6ICJEci4gQnJvZGEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgRXhhbXBsZSAjMTogQU5DT1ZBIHdpdGggYW4gZXhwZXJpbWVudGFsIGRlc2lnbiBhbmQgcHJldGVzdCBhcyBjb3ZhcmlhdGUKIyMgTG9hZCBpbiBzb21lIGhlbHBmdWwgcGFja2FnZXMKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGhhdmVuKQpgYGAKCiMjIExvYWQgaW4gdGhlIGRhdGFzZXQKYGBge3J9CmhhcF9hbmNvdmEgPC0gcmVhZF9kdGEoImhhcC1hbmNvdmExLmR0YSIpCmBgYAoKIyMgRXhwbG9yZSB5b3VyIGRhdGEKYGBge3J9CmdsaW1wc2UoaGFwX2FuY292YSkKYGBgCiMjIENsZWFuIHlvdXIgZGF0YQpgYGB7cn0KaGFwX2FuY292YS5jbGVhbiA8LSBoYXBfYW5jb3ZhICU+JQptdXRhdGUoLiwKICB0cmVhdC5mYWMgPSBhc19mYWN0b3IodHJlYXQpKQpgYGAKCiMjIENoZWNrIG91dCBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzIGZvciB0aGUgdHJlYXRtZW50IGZhY3RvciBhbmQgY29udGludW91cyB2YXJpYWJsZXMgClRoZSBgc3VtbWFyeWAgcGFja2FnZSBpbiBiYXNlIGBSYCBpcyBhIHF1aWNrIGFuZCBlYXN5IHdheSB0byBkbyB0aGlzISAKYGBge3J9CnN1bW1hcnkoaGFwX2FuY292YS5jbGVhbikKYGBgCgojIyBDb3JyZWxhdGUgcHJlIGFuZCBwb3N0IHRlc3QgcmVzdWx0cy0gaG93IHNpbWlsYXIgYXJlIHRoZXk/IApIb3BlZnVsbHksIHNvbWV3aGF0IHNpbWlsYXIhIElmIG5vdCwgdGhlcmUgbWF5IG5vdCBiZSBzeXN0ZW1hdGljIGNoYW5nZSBvdmVyIHRpbWUuIFRoZSBgY29yLnRlc3RgIGZ1bmN0aW9uIGluIGJhc2UgYFJgIHdvcmtzIHdlbGwgZm9yIHNpbXBsZSBjb3JyZWxhdGlvbnMuCmBgYHtyfQpjb3IudGVzdChoYXBfYW5jb3ZhLmNsZWFuJGhhcF9wcmUsIGhhcF9hbmNvdmEuY2xlYW4kaGFwX3Bvc3QpCmBgYAoKIyMgUnVuIGEgYmFzaWMgQU5DT1ZBIApIZXJlLCB3ZSBoYXZlIGBoYXBfcG9zdGAgYXMgdGhlIG91dGNvbWUsIGB0cmVhdGAgYXMgdGhlIGZhY3RvciwgYW5kIGBoYXBfcHJlYCBhcyB0aGUgY292YXJpYXRlLiBTaW5jZSB3ZSBjbGVhbmVkIG91ciBkYXRhIGF0IHRoZSBiZWdpbm5pbmcsIGBSYCByZWNvZ25pemVzIHRoYXQgYHRyZWF0YCBpcyBhIGZhY3RvciBhbmQgYGhhcF9wcmVgIGlzIGNvbnRpbnVvdXMgc28gaXQgd2lsbCBydW4gYXMgYW4gQU5DT1ZBIGFuZCBub3QgYSB0d28td2F5IEFOT1ZBLgpgYGB7cn0KYW5jb3ZhMTwtYW92KGhhcF9wb3N0fiB0cmVhdC5mYWMgKyBoYXBfcHJlLGRhdGE9aGFwX2FuY292YS5jbGVhbikKc3VtbWFyeShhbmNvdmExKQpgYGAKCiMjIElmIHlvdSBoYWQgbW9yZSB0aGFuIDIgZ3JvdXBzLCB5b3UgY291bGQgZG8gcG9zdC1ob2MgY29tcGFyaXNvbnM6CmBgYHtyfQpUdWtleUhTRChhbmNvdmExKQpgYGAKCiMjIEdldCBtZWFzdXJlcyBvZiBlZmZlY3Qgc2l6ZToKYGBge3J9CmxpYnJhcnkoZWZmZWN0c2l6ZSkKZXRhX3NxdWFyZWQoYW5jb3ZhMSkKb21lZ2Ffc3F1YXJlZChhbmNvdmExKQpgYGAKCiMjIFdoYXQgaWYgd2UgZGlkbid0IGhhdmUgdGhlIHByZXRlc3Qgc2NvcmU/Ck5vdywgc2VlIHRoZSBkaWZmZXJlbmNlIHdpdGhvdXQgdGhlIGNvdmFyaWF0ZS0ganVzdCBhbiBBTk9WQSBmb3IgYHRyZWF0YDoKYGBge3J9CmFub3ZhMTwtYW92KGhhcF9wb3N0fiB0cmVhdC5mYWMsIGRhdGE9aGFwX2FuY292YS5jbGVhbikKc3VtbWFyeShhbm92YTEpCmBgYAoKIyMgQW5kLCBvZiBjb3Vyc2UtIGhvdyB0byBydW4gdGhpcyBhcyBhIG11bHRpcGxlIHJlZ3Jlc3Npb24hCmBgYHtyfQpyZWdyZXNzaW9uMSA8LSBsbShoYXBfcG9zdCB+IHRyZWF0LmZhYyArIGhhcF9wcmUsIGRhdGEgPSBoYXBfYW5jb3ZhLmNsZWFuKQpzdW1tYXJ5KHJlZ3Jlc3Npb24xKQpgYGAKCiMgRXhhbXBsZSAjMjogQU5DT1ZBIHdpdGggZXhwZXJpbWVudGFsIGRlc2lnbiBhbmQgcmVsYXRlZCBjb3ZhcmlhdGVzIChub3QgcHJldGVzdCkKSXQgbWlnaHQgYmUgYmV0dGVyIGZvciBleHRlcm5hbCB2YWxpZGl0eSB0byB1c2UgcmVsYXRlZCBtZWFzdXJlcyBhcyBjb3ZhcmlhdGVzLCBpbnN0ZWFkIG9mIGEgcHJldGVzdC4KIyMgTG9hZCBpbiB0aGUgZGF0YXNldDoKYGBge3J9CmhhcF9hbmNvdmEyIDwtIHJlYWRfZHRhKCJoYXAtYW5jb3ZhMi5kdGEiKQpgYGAKCiMjIEV4cGxvcmUgeW91ciBkYXRhOgpgYGB7cn0KZ2xpbXBzZShoYXBfYW5jb3ZhMikKYGBgCgojIyBDbGVhbiB5b3VyIGRhdGE6CmBgYHtyfQpoYXBfYW5jb3ZhMi5jbGVhbiA8LSBoYXBfYW5jb3ZhMiAlPiUKICBtdXRhdGUoLiwKICAgICAgICAgdHJlYXQuZmFjID0gYXNfZmFjdG9yKHRyZWF0KSkKYGBgCgojIyBFeHBsb3JlIHlvdXIgZGF0YQpgYGB7cn0Kc3VtbWFyeShoYXBfYW5jb3ZhMi5jbGVhbikKYGBgCgojIyBIb3cgbXVjaCB2YXJpYW5jZSBkb2VzIG91ciBzZXQgb2YgY292YXJpYXRlcyBleHBsYWluPwpgYGB7cn0KcmVncmVzc2lvbjIgPC0gbG0oaGFwIH4gc2F0ZmFtICsgaGVhbHRoICsgc29jZnJpZW5kLCBkYXRhID0gaGFwX2FuY292YTIuY2xlYW4pCnN1bW1hcnkocmVncmVzc2lvbjIpCmBgYAoKIyMgTm93LCBwbHVnIHRoZW0gaW50byB0aGUgQU5DT1ZBIG1vZGVsOgpgYGB7cn0KYW5jb3ZhMiA8LSBhb3YoaGFwIH4gdHJlYXQuZmFjICsgc2F0ZmFtICsgaGVhbHRoICsgc29jZnJpZW5kLCBkYXRhID0gaGFwX2FuY292YTIuY2xlYW4pCnN1bW1hcnkoYW5jb3ZhMikKYGBgCgojIyBSZWdyZXNzaW9uIG1vZGVsIHdpdGggdGhlIHZhcmlhYmxlIGB0cmVhdC5mYWNgCmBgYHtyfQpyZWdyZXNzaW9uMyA8LSBsbShoYXAgfiB0cmVhdC5mYWMgKyBzYXRmYW0gKyBoZWFsdGggKyBzb2NmcmllbmQsIGRhdGEgPSBoYXBfYW5jb3ZhMi5jbGVhbikKc3VtbWFyeShyZWdyZXNzaW9uMykKYGBgCgojIEV4YW1wbGUgIzM6IFJlcGVhdGVkIE1lYXN1cmVzIEFOT1ZBCiMjIFJlc2hhcGluZyBkYXRhIGZyb20gd2lkZSB0byBsb25nIApIaW50IGZvciB0aGlzIHdlZWsncyBjb250ZW50IHJldmlldyEKYGBge3J9CnNsZWVwIDwtIHJlYWRfZHRhKCJzbGVlcF93aWRlLmR0YSIpCmBgYAoKYGBge3J9CmdsaW1wc2Uoc2xlZXApCmBgYAoKYGBge3J9CnN1bW1hcnkoc2xlZXApCmBgYAoKIyMgVXNpbmcgYHBpdm90X2xvbmdlcmAgdG8gcmVzaGFwZSBmcm9tICp3aWRlKiB0byAqbG9uZyoKYGBge3J9CnNsZWVwLmxvbmcgPC0gc2xlZXAgJT4lCnBpdm90X2xvbmdlciguLAogICAgICAgICAgICAgIGNvbHMgPSBzdGFydHNfd2l0aCgic2xlZXAiKSwKICAgICAgICAgICAgICBuYW1lc190byA9ICJtb250aCIsCiAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInNsZWVwIiwKICAgICAgICAgICAgICBuYW1lc19wcmVmaXggPSAic2xlZXAiLAogICAgICAgICAgICAgICkKZ2xpbXBzZShzbGVlcC5sb25nKQpgYGAKCiMjIFJlc2hhcGUgYmFjayB0byB3aWRlIHVzaW5nIGBwaXZvdF93aWRlcmAKYGBge3J9CnNsZWVwLndpZGUgPC0gc2xlZXAubG9uZyAlPiUKICBwaXZvdF93aWRlciguLAogICAgICAgICAgICAgIGlkX2NvbHMgPSBjKCJpZCIpLAogICAgICAgICAgICAgIG5hbWVzX2Zyb20gPSBtb250aCwKICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHNsZWVwLAogICAgICAgICAgICAgIG5hbWVzX3ByZWZpeCA9ICJzbGVlcCIsCiAgICAgICAgICAgICAgKQoKZ2xpbXBzZShzbGVlcC53aWRlKQpgYGAKCiMjIERlbnNpdHkgUGxvdCBmb3IgTW9udGggMQpgYGB7cn0Ka2RlbnNpdHkxIDwtIGdncGxvdChzbGVlcCwgYWVzKHg9c2xlZXAxKSkgKyAKICBnZW9tX2RlbnNpdHkoKSArIAogIHN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIG4gPSAxMDAsIGFyZ3MgPSBsaXN0KG1lYW4gPSAzNTAsIHNkID0gMzApLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIFNsZWVwIGluIE1vbnRoIDEiLHg9IlNsZWVwIChtaW51dGVzIHBlciBuaWdodCkiLCB5ID0gIkRlbnNpdHkiLCBjYXB0aW9uID0gIk4gPSAxMDAgcGFydGljaXBhbnRzLiBUaGUgcmVkIGxpbmUgaW5kaWNhdGVzIGEgbm9ybWFsIGRpc3RyaWJ1dGlvbi4gVGhlIGJsYWNrIGxpbmVzIHJlcHJlc2VudHMgdGhlIG9ic2VydmVkIGRpc3RyaWJ1dGlvbi4iKQprZGVuc2l0eTEKYGBgCiMjIERlbnNpdHkgUGxvdCBmb3IgTW9udGggMgpgYGB7cn0Ka2RlbnNpdHkyIDwtIGdncGxvdChzbGVlcCwgYWVzKHg9c2xlZXAyKSkgKyAKICBnZW9tX2RlbnNpdHkoKSArIAogIHN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIG4gPSAxMDAsIGFyZ3MgPSBsaXN0KG1lYW4gPSAzNzAsIHNkID0gMzApLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIFNsZWVwIGluIE1vbnRoIDIiLHg9IlNsZWVwIChtaW51dGVzIHBlciBuaWdodCkiLCB5ID0gIkRlbnNpdHkiLCBjYXB0aW9uID0gIk4gPSAxMDAgcGFydGljaXBhbnRzLiBUaGUgcmVkIGxpbmUgaW5kaWNhdGVzIGEgbm9ybWFsIGRpc3RyaWJ1dGlvbi4gVGhlIGJsYWNrIGxpbmVzIHJlcHJlc2VudHMgdGhlIG9ic2VydmVkIGRpc3RyaWJ1dGlvbi4iKQprZGVuc2l0eTIKYGBgCgojIyBEZW5zaXR5IFBsb3QgZm9yIE1vbnRoIDMKYGBge3J9CmtkZW5zaXR5MyA8LSBnZ3Bsb3Qoc2xlZXAsIGFlcyh4PXNsZWVwMikpICsgCiAgZ2VvbV9kZW5zaXR5KCkgKyAKICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRub3JtLCBuID0gMTAwLCBhcmdzID0gbGlzdChtZWFuID0gMzcwLCBzZCA9IDMwKSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGU9IkRpc3RyaWJ1dGlvbiBvZiBTbGVlcCBpbiBNb250aCAzIix4PSJTbGVlcCAobWludXRlcyBwZXIgbmlnaHQpIiwgeSA9ICJEZW5zaXR5IiwgY2FwdGlvbiA9ICJOID0gMTAwIHBhcnRpY2lwYW50cy4gVGhlIHJlZCBsaW5lIGluZGljYXRlcyBhIG5vcm1hbCBkaXN0cmlidXRpb24uIFRoZSBibGFjayBsaW5lcyByZXByZXNlbnRzIHRoZSBvYnNlcnZlZCBkaXN0cmlidXRpb24uIikKa2RlbnNpdHkzCmBgYAoKIyMgVXNpbmcgZGF0YSBpbiAqbG9uZyogZm9ybSwgd2UgY2FuIGZhY2V0IGJ5IG1vbnRoIHRvIGluY2x1ZGUgYWxsIHRocmVlIGdyYXBocyBpbiBvbmUgdGFibGUKYGBge3J9CmFsbF9zbGVlcDwtZ2dwbG90KHNsZWVwLmxvbmcsIGFlcyh4PXNsZWVwKSkrCiAgZ2VvbV9kZW5zaXR5KCkrZmFjZXRfd3JhcChtb250aCB+IC4sIG5jb2wgPSAxKQphbGxfc2xlZXAKYGBgCgojIyBTdW1tYXJ5IFN0YXRpc3RpY3Mgb2YgU2xlZXAgYnkgTW9udGgKYGBge3J9CmxpYnJhcnkocnN0YXRpeCkKbGlicmFyeShnZ3B1YnIpCnNsZWVwLmxvbmcgJT4lCiAgZ3JvdXBfYnkobW9udGgpICU+JQogIGdldF9zdW1tYXJ5X3N0YXRzKHNsZWVwLCB0eXBlID0gIm1lYW5fc2QiKQpgYGAKCiMjIENvZGUgZm9yIGJhc2ljIHJlcGVhdGVkIG1lYXN1cmVzIEFOT1ZBIApOb3RlOmBpZGAgaXMgcGFydGljaXBhbnQgaWQgaW4gdGhpcyBjYXNlLgpgYGB7cn0Kc2xlZXAuYW92IDwtIGFub3ZhX3Rlc3QoZGF0YSA9IHNsZWVwLmxvbmcsIGR2ID0gc2xlZXAsIHdpZCA9IGlkLCB3aXRoaW4gPSBtb250aCkKc3VtbWFyeShzbGVlcC5hb3YpCmBgYApIZXJlIGlzIHRoZSBiYXNpYyBBTk9WQSB0YWJsZToKYGBge3J9CmdldF9hbm92YV90YWJsZShzbGVlcC5hb3YpCmBgYApIZXJlIGFyZSB0aGUgY29ycmVjdGVkIHAtdmFsdWVzIHdpdGggdGhlIHNwaGVyaWNpdHkgYWRqdXN0bWVudHM6CmBgYHtyfQpzbGVlcC5hb3YkYFNwaGVyaWNpdHkgQ29ycmVjdGlvbnNgCmBgYAoKIyMgUG9zdC1ob2MgY29tcGFyaXNvbnMgYnkgbW9udGgsIHVzaW5nIFR1a2V5J3MgbWV0aG9kOgpgYGB7cn0KcGFpcndpc2VfdF90ZXN0KAogICAgZm9ybXVsYSA9IHNsZWVwIH4gbW9udGgsIHBhaXJlZCA9IFRSVUUsCiAgICBwLmFkanVzdC5tZXRob2QgPSAiYm9uZmVycm9uaSIsCiAgICBkYXRhID0gc2xlZXAubG9uZwogICAgKQpgYGAKCiMgQ2hlY2sgZm9yIHNwaGVyaWNpdHkgYW5kIG11bHRpdmFyaWF0ZSBub3JtYWxpdHkKIyMjICBTdGFydCB3aXRoIHVuaXZhcmlhdGUgbm9ybWFsaXR5LCBwbG90cyBhbmQgc3RhdGlzdGljcwpgYGB7cn0KbGlicmFyeShNVk4pCnNsZWVwX3VuaXZhcmlhdGUgPC0gbXZuKGRhdGEgPSBzbGVlcC53aWRlLCBtdm5UZXN0ID0gTlVMTCwgdW5pdmFyaWF0ZVBsb3QgPSAicXFwbG90IikKCnNsZWVwX3VuaXZhcmlhdGUyIDwtIG12bihkYXRhID0gc2xlZXAud2lkZSwgbXZuVGVzdCA9IE5VTEwsIHVuaXZhcmlhdGVUZXN0ID0gIlNXIiwgZGVzYyA9IFRSVUUpCnNsZWVwX3VuaXZhcmlhdGUyJHVuaXZhcmlhdGVOb3JtYWxpdHkKYGBgCiMjIyBUZXN0cyBmb3IgTVZOCiMjIyMgRG9vcm5pay1IYW5zZW4gVGVzdCAKVGhlIGxhc3QgY29sdW1uIGluZGljYXRlcyB3aGV0aGVyIGRhdGFzZXQgZm9sbG93cyBhIG11bHRpdmFyaWF0ZSBub3JtYWxpdHkgb3Igbm90IChpLmUsIFlFUyBvciBOTykgYXQgc2lnbmlmaWNhbmNlIGxldmVsIDAuMDUuCmBgYHtyfQpzbGVlcF9tdm4gPC0gbXZuKGRhdGEgPSBzbGVlcC53aWRlLCBtdm5UZXN0ID0gImRoIikKc2xlZXBfbXZuJG11bHRpdmFyaWF0ZU5vcm1hbGl0eQpgYGAKCiMjIyMgTWFyZGlhJ3MgTVZOIFRlc3QgClRoaXMgZnVuY3Rpb24gcGVyZm9ybXMgbXVsdGl2YXJpYXRlIHNrZXduZXNzIGFuZCBrdXJ0b3NpcyB0ZXN0cyBhdCB0aGUgc2FtZSB0aW1lIGFuZCBjb21iaW5lcyB0ZXN0IHJlc3VsdHMgZm9yIG11bHRpdmFyaWF0ZSBub3JtYWxpdHkuIElmIGJvdGggdGVzdHMgaW5kaWNhdGUgbXVsdGl2YXJpYXRlIG5vcm1hbGl0eSwgdGhlbiBkYXRhIGZvbGxvd3MgYSBtdWx0aXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiBhdCB0aGUgMC4wNSBzaWduaWZpY2FuY2UgbGV2ZWwuCmBgYHtyfQpzbGVlcF9tdm4yIDwtIG12bihkYXRhID0gc2xlZXAud2lkZSwgbXZuVGVzdCA9ICJtYXJkaWEiKQpzbGVlcF9tdm4yJG11bHRpdmFyaWF0ZU5vcm1hbGl0eQpgYGAKIyMgQ2hlY2sgdGhlIHNwaGVyaWNpdHkgYXNzdW1wdGlvbgpgYGB7cn0Kc2xlZXAuYW92JGBNYXVjaGx5J3MgVGVzdCBmb3IgU3BoZXJpY2l0eWAKYGBgCg==