0.1 Exercise 1 One-way ANOVA

In this exercise you have to consider data set named PlantGrowth from R environment. It contains the weight of plants obtained under a control and two different treatment conditions. Please consider the following steps: * Data Preparation (One-way ANOVA) * One-way ANOVA Test * Check Assumptions (One-way ANOVA) * Pairwise-comparison (One-way ANOVA) * Tukey HSD (One-way ANOVA)

0.2 Exercise 2 Two-way ANOVA

Please apply Two-way ANOVA procedures to the rats data from the faraway package. There are two factors here: poison and treat. You can use the levels() function to extract the levels of a factor variable. Give your opinions accordingly!

0.3 Exercise 3 Three-way ANOVA

In this exercise you will use the headache dataset [in datarium package], which contains the measures of migraine headache episode pain score in 72 participants treated with three different treatments. The participants include 36 males and 36 females. Males and females were further subdivided into whether they were at low or high risk of migraine. You have to understand how each independent variable (type of treatments, risk of migraine and gender) interact to predict the pain score.

1 Exercise 1

1.1 Data Preparation (One-Way Anova)

We’ll use the data set named PlantGrowth from R environment. It contains the weight of plants obtained under a control and two different treatment conditions. So we need to download the data set, then we look at the data.

data("PlantGrowth")
head(PlantGrowth)
##   weight group
## 1   4.17  ctrl
## 2   5.58  ctrl
## 3   5.18  ctrl
## 4   6.11  ctrl
## 5   4.50  ctrl
## 6   4.61  ctrl

And now, we seperate the group name.

levels(PlantGrowth$group) 
## [1] "ctrl" "trt1" "trt2"

The one-way ANOVA can be used to determine whether the means plant growths are significantly different between the three conditions.

Now, we can see that in PlantGrowth data set, there is 3 groups named ctrl,trt1, and trt2. Before we begin to build the model, it’s important to consider the summary statistics and also identify if there is some outliers. We ca apply box plot methods or implement by using the R function identify_outliers() [rstatix package].

library(tidyr)                                        # to create tidy data
library(rstatix)                                      # use for basic Anova/statistical tests 
## 
## Attaching package: 'rstatix'
## The following object is masked from 'package:stats':
## 
##     filter
library(ggplot2)                                      # for visualization
# first get the summary statistics
PlantGrowth %>%
  group_by(group) %>%
  get_summary_stats(weight, type = "mean_sd")
## # A tibble: 3 x 5
##   group variable     n  mean    sd
##   <fct> <chr>    <dbl> <dbl> <dbl>
## 1 ctrl  weight      10  5.03 0.583
## 2 trt1  weight      10  4.66 0.794
## 3 trt2  weight      10  5.53 0.443
# plot(PlantGrowth ~ group, data = PlantGrowth, col = 2:5)    # boxplot of `weight` by `group`
ggplot(PlantGrowth, aes(x = group, y = weight, fill = group)) +
    geom_boxplot() +
    geom_jitter(shape = 15,
        color = "steelblue",
        position = position_jitter(0.21)) +
   theme_minimal()

# identify outliers
PlantGrowth %>% 
  group_by(group) %>%
  identify_outliers(weight)
## # A tibble: 2 x 4
##   group weight is.outlier is.extreme
##   <fct>  <dbl> <lgl>      <lgl>     
## 1 trt1    5.87 TRUE       FALSE     
## 2 trt1    6.03 TRUE       FALSE

There were no extreme outliers.

1.2 One-Way Anova Test

The aov() function is used to obtain the relevant sums of squares. Using the summary() function on the output from aov() creates the desired ANOVA table. (Without the unneeded row for total.)

res.aov = anova_test(weight ~ group, data = PlantGrowth)
## Coefficient covariances computed by hccm()
weight_aov = aov(weight ~ group, data = PlantGrowth)
summary(weight_aov)
##             Df Sum Sq Mean Sq F value Pr(>F)  
## group        2  3.766  1.8832   4.846 0.0159 *
## Residuals   27 10.492  0.3886                 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Because the pvalue < a, then we reject H0 that claims all of the group, gives the same mean value, we accept H1 that claims at least one group mean is not equal to the others.

However, we notice that the p-value of this test is low, so using any reasonable significance level we would reject the null hypothesis.

groups = data.frame(group = unique(PlantGrowth$group))
data.frame(groups, weight = predict(weight_aov, groups))
##   group weight
## 1  ctrl  5.032
## 2  trt1  4.661
## 3  trt2  5.526

Here, we have created a datafreame with a row for each group. By predicting on this dataframe, we obtain the sample means of each group, and none from al of them is close for each others.

1.3 Check Assumptions (One-Way Anova)

The ANOVA test assumes that, the data are normally distributed and the variance across groups are homogeneous. We can check that with some diagnostic plots.

plot(weight_aov, 1)         # 1. Homogeneity of variances

In the plot above, there is no evident relationships between residuals and fitted values (the mean of each groups), which is good. So, we can assume the homogeneity of variances. And we can see that the outliers are o17, o15, and 4o. Not much.

plot(weight_aov, 2)             # 2. Normality

We can see that the outliers are not much too.

PlantGrowth %>%
  group_by(group) %>%
  shapiro_test(weight)
## # A tibble: 3 x 4
##   group variable statistic     p
##   <fct> <chr>        <dbl> <dbl>
## 1 ctrl  weight       0.957 0.747
## 2 trt1  weight       0.930 0.452
## 3 trt2  weight       0.941 0.564

Points 4, 15, 17 are detected as outliers, which can severely affect normality and homogeneity of variance. It can be useful to remove outliers to meet the test assumptions. I recommend Levene’s test, which is less sensitive to departures from normal distribution.

library(car)                                          # `leveneTest()` in car package
## Loading required package: carData
leveneTest(weight ~ group, data = PlantGrowth)           # levene’s test (homogeneity of variances)
## Levene's Test for Homogeneity of Variance (center = median)
##       Df F value Pr(>F)
## group  2  1.1192 0.3412
##       27

From the output above, we can see that the p-value is < 0.05, which is not significant. This means that, there is not significant difference between variances across groups. Therefore, we can assume the homogeneity of variances in the different treatment groups.

oneway.test(weight ~ group, data = PlantGrowth)          # no assumption of equal variances
## 
##  One-way analysis of means (not assuming equal variances)
## 
## data:  weight and group
## F = 5.181, num df = 2.000, denom df = 17.128, p-value = 0.01739

From the output above we can see that the p-value is less than the significance level of 0.05. This means that there is evidence to suggest that the variance across groups is statistically significantly different. Therefore, we can assume the homogeneity of variances in the different treatment groups.

Note that a non-parametric alternative to one-way ANOVA is Kruskal-Wallis rank sum test, which can be used when ANNOVA assumptions are not met.

kruskal.test(weight ~ group, data = PlantGrowth)         # use if ANNOVA assumptions are not met
## 
##  Kruskal-Wallis rank sum test
## 
## data:  weight by group
## Kruskal-Wallis chi-squared = 7.9882, df = 2, p-value = 0.01842

1.4 Pairwise - comparison (One-way Anova)

Suppose we reject the null hypothesis from the ANOVA test for equal means. That tells us that the means are different. But which means? All of them? Some of them? The obvious strategy is to test all possible comparisons of two means. We can do this easily in R.

pwc <- PlantGrowth %>% 
  tukey_hsd(weight ~ group)
pwc
## # A tibble: 3 x 9
##   term  group1 group2 null.value estimate conf.low conf.high p.adj p.adj.signif
## * <chr> <chr>  <chr>       <dbl>    <dbl>    <dbl>     <dbl> <dbl> <chr>       
## 1 group ctrl   trt1            0   -0.371   -1.06      0.320 0.391 ns          
## 2 group ctrl   trt2            0    0.494   -0.197     1.19  0.198 ns          
## 3 group trt1   trt2            0    0.865    0.174     1.56  0.012 *

We can see that all of the group mean is different and doesnt close enough for each other.

The output contains the following columns:

estimate: estimate of the difference between means of the two groups conf.low, conf.high: the lower and the upper end point of the confidence interval at 95% (default) p.adj: p-value after adjustment for the multiple comparisons.

1.5 Tukey HSD (One-Way Anova)

Tukey’s Honest Significance difference can be applied directly to an object which was created using aov(). It will adjust the p-values of the pairwise comparisons of the means to control the FWER, in this case, for 0.05. Notice it also gives confidence intervals for the difference of the means.

TukeyHSD(weight_aov, conf.level = 0.95)
##   Tukey multiple comparisons of means
##     95% family-wise confidence level
## 
## Fit: aov(formula = weight ~ group, data = PlantGrowth)
## 
## $group
##             diff        lwr       upr     p adj
## trt1-ctrl -0.371 -1.0622161 0.3202161 0.3908711
## trt2-ctrl  0.494 -0.1972161 1.1852161 0.1979960
## trt2-trt1  0.865  0.1737839 1.5562161 0.0120064

Based on these results, we see a difference between all of the groups. All pairwise comparisons are significant. If you return to the original boxplot, these results should not be surprising.

Also, nicely, we can easily produce a plot of these confidence intervals.

plot(TukeyHSD(weight_aov, conf.level = 0.95))

We can see that the pair between trl2 group and ctrl are the closest one to the mean 0.0, but we still cant accept that between trl2 and ctrl are same.

2 Exercise 2

2.1 Data Preparation (Two-Way Anova)

library(faraway)                                      # dataset
## Registered S3 methods overwritten by 'lme4':
##   method                          from
##   cooks.distance.influence.merMod car 
##   influence.merMod                car 
##   dfbeta.influence.merMod         car 
##   dfbetas.influence.merMod        car
## 
## Attaching package: 'faraway'
## The following objects are masked from 'package:car':
## 
##     logit, vif
library(tidyr)
head(rats)
##   time poison treat
## 1 0.31      I     A
## 2 0.82      I     B
## 3 0.43      I     C
## 4 0.45      I     D
## 5 0.45      I     A
## 6 1.10      I     B
levels(rats$poison)
## [1] "I"   "II"  "III"

We can see that the poison have 3 type.

levels(rats$treat)
## [1] "A" "B" "C" "D"

We can see that the treats have 4 type.

library(rstatix)                                      # use for basic Anova/statistical tests
set.seed(1603)
data("rats", package = "faraway")
rats %>% 
  sample_n_by(poison, treat, size = 1)
## # A tibble: 12 x 3
##     time poison treat
##    <dbl> <fct>  <fct>
##  1  0.31 I      A    
##  2  0.88 I      B    
##  3  0.76 I      C    
##  4  0.66 I      D    
##  5  0.4  II     A    
##  6  1.24 II     B    
##  7  0.4  II     C    
##  8  0.71 II     D    
##  9  0.18 III    A    
## 10  0.38 III    B    
## 11  0.22 III    C    
## 12  0.36 III    D
library(tidyr)                                        # to create tidy data
library(rstatix)                                      # use for basic Anova/statistical tests 
library(ggplot2)                                      # for visualization
# first get the summary statistics
rats %>%
  group_by(poison, treat) %>%
  get_summary_stats( type = "mean_sd")
## # A tibble: 12 x 6
##    poison treat variable     n  mean    sd
##    <fct>  <fct> <chr>    <dbl> <dbl> <dbl>
##  1 I      A     time         4 0.412 0.069
##  2 I      B     time         4 0.88  0.161
##  3 I      C     time         4 0.568 0.157
##  4 I      D     time         4 0.61  0.113
##  5 II     A     time         4 0.32  0.075
##  6 II     B     time         4 0.815 0.336
##  7 II     C     time         4 0.375 0.057
##  8 II     D     time         4 0.667 0.271
##  9 III    A     time         4 0.21  0.022
## 10 III    B     time         4 0.335 0.047
## 11 III    C     time         4 0.235 0.013
## 12 III    D     time         4 0.325 0.026
# visualization
ggplot(rats, aes(x = poison, y = time, fill = treat)) +
    geom_boxplot() +
    geom_jitter(shape = 15,
        color = "steelblue",
        position = position_jitter(0.21)) +
   theme_minimal()

# identify outliers
rats %>%
  group_by(poison, treat) %>%
  identify_outliers(time)
## # A tibble: 1 x 5
##   poison treat  time is.outlier is.extreme
##   <fct>  <fct> <dbl> <lgl>      <lgl>     
## 1 I      A      0.31 TRUE       FALSE

There were no extreme outliers.

mice_int   = aov(time ~ treat * poison, data = rats)  # interaction model
mice_add   = aov(time ~ treat + poison, data = rats)  # additive model
mice_treat= aov(time ~treat, data = rats)                    # single factor model
mice_toxic  = aov(time ~ poison, data = rats)           # single factor model
mice_null  = aov(time ~ 1, data = rats)                         # null model
summary(mice_int)
##              Df Sum Sq Mean Sq F value   Pr(>F)    
## treat         3 0.9212  0.3071  13.806 3.78e-06 ***
## poison        2 1.0330  0.5165  23.222 3.33e-07 ***
## treat:poison  6 0.2501  0.0417   1.874    0.112    
## Residuals    36 0.8007  0.0222                     
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summary(mice_add)
##             Df Sum Sq Mean Sq F value  Pr(>F)    
## treat        3 0.9212  0.3071   12.27 6.7e-06 ***
## poison       2 1.0330  0.5165   20.64 5.7e-07 ***
## Residuals   42 1.0509  0.0250                    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summary(mice_treat)
##             Df Sum Sq Mean Sq F value   Pr(>F)    
## treat        3 0.9212 0.30707   6.484 0.000992 ***
## Residuals   44 2.0839 0.04736                     
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summary(mice_toxic)
##             Df Sum Sq Mean Sq F value   Pr(>F)    
## poison       2  1.033  0.5165   11.79 7.66e-05 ***
## Residuals   45  1.972  0.0438                     
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summary(mice_null)
##             Df Sum Sq Mean Sq F value Pr(>F)
## Residuals   47  3.005 0.06394

In the R code below, the asterisk represents the interaction effect and the main effect of each variable (and all lower-order interactions).

aov_test_int <- rats %>% 
  anova_test(time ~  poison * treat)
## Coefficient covariances computed by hccm()
aov_test_int
## ANOVA Table (type II tests)
## 
##         Effect DFn DFd      F        p p<.05   ges
## 1       poison   2  36 23.222 3.33e-07     * 0.563
## 2        treat   3  36 13.806 3.78e-06     * 0.535
## 3 poison:treat   6  36  1.874 1.12e-01       0.238

There was a statistically significant interaction between treat and poison for time score, F(6, 36) = 1.874, p = 0.238.

2.2 Check Assumptions (Two-way ANOVA)

The ANOVA test assumes that, the data are normally distributed and the variance across groups are homogeneous. We can check that with some diagnostic plots.

plot(mice_int, 1)                                     # 1. Homogeneity of variances

We can see that the outliers points are 24,26, and 30.

plot(mice_int, 2)                                     # 2. Normality

We can see that the outliers points are 24,26, and 30.

rats %>%
  group_by(treat, poison) %>%
  shapiro_test(time)
## # A tibble: 12 x 5
##    poison treat variable statistic      p
##    <fct>  <fct> <chr>        <dbl>  <dbl>
##  1 I      A     time         0.782 0.0741
##  2 II     A     time         0.971 0.848 
##  3 III    A     time         0.927 0.577 
##  4 I      B     time         0.947 0.700 
##  5 II     B     time         0.948 0.701 
##  6 III    B     time         0.831 0.171 
##  7 I      C     time         0.895 0.405 
##  8 II     C     time         0.983 0.921 
##  9 III    C     time         0.993 0.972 
## 10 I      D     time         0.899 0.427 
## 11 II     D     time         0.981 0.907 
## 12 III    D     time         0.946 0.689

The score were normally distributed (p > 0.05) for each cell, as assessed by Shapiro-Wilk’s test of normality.

library(car)                                                         
leveneTest(time~treat*poison,data=rats)  # levene’s test (car packages)
## Levene's Test for Homogeneity of Variance (center = median)
##       Df F value    Pr(>F)    
## group 11  4.1323 0.0005833 ***
##       36                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

The Levene’s test is significant (p < 0.05). Therefore, we cant assume the homogeneity of variances in the different groups.

levene_test(time~treat*poison,data=rats) # levene’s test (rstatix packages)
## # A tibble: 1 x 4
##     df1   df2 statistic        p
##   <int> <int>     <dbl>    <dbl>
## 1    11    36      4.13 0.000583

The Levene’s test is significant (p < 0.05).

oneway.test(time~treat*poison,data=rats) # no assumption of equal variances
## 
##  One-way analysis of means (not assuming equal variances)
## 
## data:  time and treat * poison
## F = 14.848, num df = 11.000, denom df = 13.801, p-value = 8.107e-06

2.3 Pairwise-comparison (Two-way ANOVA)

Suppose we reject the null hypothesis from the ANOVA test for equal means. That tells us that the means are different. But which means? All of them? Some of them? The obvious strategy is to test all possible comparisons of two means. We can do this easily in R.

library(emmeans)
pwc <- rats %>% 
  group_by(treat) %>%
  emmeans_test(time ~ poison, p.adjust.method = "bonferroni") 
pwc
## # A tibble: 12 x 10
##    treat term   .y.   group1 group2    df statistic       p   p.adj p.adj.signif
##  * <chr> <chr>  <chr> <chr>  <chr>  <dbl>     <dbl>   <dbl>   <dbl> <chr>       
##  1 A     poison time  I      II        36     0.877 3.86e-1 1.00e+0 ns          
##  2 A     poison time  I      III       36     1.92  6.28e-2 1.88e-1 ns          
##  3 A     poison time  II     III       36     1.04  3.04e-1 9.12e-1 ns          
##  4 B     poison time  I      II        36     0.616 5.42e-1 1.00e+0 ns          
##  5 B     poison time  I      III       36     5.17  8.98e-6 2.70e-5 ****        
##  6 B     poison time  II     III       36     4.55  5.86e-5 1.76e-4 ***         
##  7 C     poison time  I      II        36     1.83  7.62e-2 2.29e-1 ns          
##  8 C     poison time  I      III       36     3.15  3.25e-3 9.76e-3 **          
##  9 C     poison time  II     III       36     1.33  1.93e-1 5.78e-1 ns          
## 10 D     poison time  I      II        36    -0.545 5.89e-1 1.00e+0 ns          
## 11 D     poison time  I      III       36     2.70  1.04e-2 3.13e-2 *           
## 12 D     poison time  II     III       36     3.25  2.52e-3 7.56e-3 **

There wasn’t a significant difference of job satisfaction score between all groups for both males and females (p > 0.05).

2.4 Tukey HSD (Two Way Anova)

Tukey’s Honest Significance difference can be applied directly to an object which was created using aov(). It will adjust the p-values of the pairwise comparisons of the means to control the FWER, in this case, for 0.05. Notice it also gives confidence intervals for the difference of the means.

TukeyHSD(mice_int, conf.level = 0.95)
##   Tukey multiple comparisons of means
##     95% family-wise confidence level
## 
## Fit: aov(formula = time ~ treat * poison, data = rats)
## 
## $treat
##            diff         lwr         upr     p adj
## B-A  0.36250000  0.19852116  0.52647884 0.0000047
## C-A  0.07833333 -0.08564550  0.24231217 0.5772283
## D-A  0.22000000  0.05602116  0.38397884 0.0048556
## C-B -0.28416667 -0.44814550 -0.12018783 0.0002333
## D-B -0.14250000 -0.30647884  0.02147884 0.1077087
## D-C  0.14166667 -0.02231217  0.30564550 0.1107678
## 
## $poison
##             diff        lwr         upr     p adj
## II-I   -0.073125 -0.2020091  0.05575913 0.3583151
## III-I  -0.341250 -0.4701341 -0.21236587 0.0000005
## III-II -0.268125 -0.3970091 -0.13924087 0.0000339
## 
## $`treat:poison`
##                diff         lwr          upr     p adj
## B:I-A:I      0.4675  0.09942113  0.835578867 0.0041387
## C:I-A:I      0.1550 -0.21307887  0.523078867 0.9393871
## D:I-A:I      0.1975 -0.17057887  0.565578867 0.7673377
## A:II-A:I    -0.0925 -0.46057887  0.275578867 0.9989739
## B:II-A:I     0.4025  0.03442113  0.770578867 0.0220694
## C:II-A:I    -0.0375 -0.40557887  0.330578867 0.9999999
## D:II-A:I     0.2550 -0.11307887  0.623078867 0.4203275
## A:III-A:I   -0.2025 -0.57057887  0.165578867 0.7395476
## B:III-A:I   -0.0775 -0.44557887  0.290578867 0.9998038
## C:III-A:I   -0.1775 -0.54557887  0.190578867 0.8641326
## D:III-A:I   -0.0875 -0.45557887  0.280578867 0.9993833
## C:I-B:I     -0.3125 -0.68057887  0.055578867 0.1618705
## D:I-B:I     -0.2700 -0.63807887  0.098078867 0.3379251
## A:II-B:I    -0.5600 -0.92807887 -0.191921133 0.0003212
## B:II-B:I    -0.0650 -0.43307887  0.303078867 0.9999650
## C:II-B:I    -0.5050 -0.87307887 -0.136921133 0.0014940
## D:II-B:I    -0.2125 -0.58057887  0.155578867 0.6808722
## A:III-B:I   -0.6700 -1.03807887 -0.301921133 0.0000139
## B:III-B:I   -0.5450 -0.91307887 -0.176921133 0.0004903
## C:III-B:I   -0.6450 -1.01307887 -0.276921133 0.0000285
## D:III-B:I   -0.5550 -0.92307887 -0.186921133 0.0003699
## D:I-C:I      0.0425 -0.32557887  0.410578867 0.9999996
## A:II-C:I    -0.2475 -0.61557887  0.120578867 0.4645761
## B:II-C:I     0.2475 -0.12057887  0.615578867 0.4645761
## C:II-C:I    -0.1925 -0.56057887  0.175578867 0.7938454
## D:II-C:I     0.1000 -0.26807887  0.468078867 0.9979417
## A:III-C:I   -0.3575 -0.72557887  0.010578867 0.0634939
## B:III-C:I   -0.2325 -0.60057887  0.135578867 0.5569107
## C:III-C:I   -0.3325 -0.70057887  0.035578867 0.1086625
## D:III-C:I   -0.2425 -0.61057887  0.125578867 0.4949176
## A:II-D:I    -0.2900 -0.65807887  0.078078867 0.2439183
## B:II-D:I     0.2050 -0.16307887  0.573078867 0.7252287
## C:II-D:I    -0.2350 -0.60307887  0.133078867 0.5413031
## D:II-D:I     0.0575 -0.31057887  0.425578867 0.9999899
## A:III-D:I   -0.4000 -0.76807887 -0.031921133 0.0234641
## B:III-D:I   -0.2750 -0.64307887  0.093078867 0.3126107
## C:III-D:I   -0.3750 -0.74307887 -0.006921133 0.0426208
## D:III-D:I   -0.2850 -0.65307887  0.083078867 0.2655759
## B:II-A:II    0.4950  0.12692113  0.863078867 0.0019661
## C:II-A:II    0.0550 -0.31307887  0.423078867 0.9999936
## D:II-A:II    0.3475 -0.02057887  0.715578867 0.0790981
## A:III-A:II  -0.1100 -0.47807887  0.258078867 0.9953340
## B:III-A:II   0.0150 -0.35307887  0.383078867 1.0000000
## C:III-A:II  -0.0850 -0.45307887  0.283078867 0.9995291
## D:III-A:II   0.0050 -0.36307887  0.373078867 1.0000000
## C:II-B:II   -0.4400 -0.80807887 -0.071921133 0.0085466
## D:II-B:II   -0.1475 -0.51557887  0.220578867 0.9563176
## A:III-B:II  -0.6050 -0.97307887 -0.236921133 0.0000893
## B:III-B:II  -0.4800 -0.84807887 -0.111921133 0.0029569
## C:III-B:II  -0.5800 -0.94807887 -0.211921133 0.0001822
## D:III-B:II  -0.4900 -0.85807887 -0.121921133 0.0022537
## D:II-C:II    0.2925 -0.07557887  0.660578867 0.2335610
## A:III-C:II  -0.1650 -0.53307887  0.203078867 0.9105785
## B:III-C:II  -0.0400 -0.40807887  0.328078867 0.9999998
## C:III-C:II  -0.1400 -0.50807887  0.228078867 0.9695827
## D:III-C:II  -0.0500 -0.41807887  0.318078867 0.9999976
## A:III-D:II  -0.4575 -0.82557887 -0.089421133 0.0054007
## B:III-D:II  -0.3325 -0.70057887  0.035578867 0.1086625
## C:III-D:II  -0.4325 -0.80057887 -0.064421133 0.0103742
## D:III-D:II  -0.3425 -0.71057887  0.025578867 0.0880763
## B:III-A:III  0.1250 -0.24307887  0.493078867 0.9868993
## C:III-A:III  0.0250 -0.34307887  0.393078867 1.0000000
## D:III-A:III  0.1150 -0.25307887  0.483078867 0.9932581
## C:III-B:III -0.1000 -0.46807887  0.268078867 0.9979417
## D:III-B:III -0.0100 -0.37807887  0.358078867 1.0000000
## D:III-C:III  0.0900 -0.27807887  0.458078867 0.9992007

2.5 Estimations (Two Way Anova)

To get the estimates, we’ll create a table which we will predict on.

mice_table = expand.grid(treat = unique(rats$treat), 
                         poison= unique(rats$poison))
matrix(paste0(mice_table$treat, "-", mice_table$poison) , 4, 3, byrow = TRUE)
##      [,1]    [,2]    [,3]   
## [1,] "A-I"   "B-I"   "C-I"  
## [2,] "D-I"   "A-II"  "B-II" 
## [3,] "C-II"  "D-II"  "A-III"
## [4,] "B-III" "C-III" "D-III"
get_est_means = function(model, table) {
  mat = matrix(predict(model, table), nrow = 3, ncol = 2, byrow = TRUE)
  colnames(mat) = c("A", "B")
  rownames(mat) = c("I", "II", "III")
  mat
}
knitr::kable(get_est_means(model = mice_int, table = mice_table))
A B
I 0.4125 0.880
II 0.5675 0.610
III 0.3200 0.815
interaction_means = get_est_means(model = mice_int, table = mice_table)
interaction_means["I",] - interaction_means["II",]
##      A      B 
## -0.155  0.270
additive_means = get_est_means(model = mice_add, table = mice_table)
additive_means["I",] - additive_means["II",]
##           A           B 
## -0.07833333  0.14250000
knitr::kable(get_est_means(model = mice_treat, table = mice_table))
A B
I 0.3141667 0.6766667
II 0.3925000 0.5341667
III 0.3141667 0.6766667
knitr::kable(get_est_means(model = mice_toxic, table = mice_table))
A B
I 0.617500 0.617500
II 0.617500 0.617500
III 0.544375 0.544375
knitr::kable(get_est_means(model = mice_null, table = mice_table))
A B
I 0.479375 0.479375
II 0.479375 0.479375
III 0.479375 0.479375

3 Exercise 3

library(datarium)
library(ggpubr)
library(rstatix)
head(headache)
## # A tibble: 6 x 5
##      id gender risk  treatment pain_score
##   <int> <fct>  <fct> <fct>          <dbl>
## 1     1 male   low   X               79.3
## 2     2 male   low   X               76.8
## 3     3 male   low   X               70.8
## 4     4 male   low   X               81.2
## 5     5 male   low   X               75.1
## 6     6 male   low   X               73.1
levels(headache$risk)
## [1] "high" "low"
levels(headache$treatment)
## [1] "X" "Y" "Z"
levels(headache$gender)
## [1] "male"   "female"
set.seed(1604)
data("headache", package = "datarium")
headache %>% sample_n_by(gender, risk, treatment, size = 1)
## # A tibble: 12 x 5
##       id gender risk  treatment pain_score
##    <int> <fct>  <fct> <fct>          <dbl>
##  1    20 male   high  X              100  
##  2    30 male   high  Y               80.1
##  3    34 male   high  Z               75.7
##  4     1 male   low   X               79.3
##  5    11 male   low   Y               73.3
##  6    16 male   low   Z               78.9
##  7    55 female high  X               81.5
##  8    66 female high  Y               86.6
##  9    68 female high  Z               82.8
## 10    39 female low   X               74.3
## 11    44 female low   Y               63.7
## 12    53 female low   Z               73.1

In this example, the effect of the treatment types is our focal variable, that is our primary concern. It is thought that the effect of treatments will depend on two other factors, “gender” and “risk” level of migraine, which are called moderator variables.

headache %>%
  group_by(gender, risk, treatment) %>%
  get_summary_stats(pain_score, type = "mean_sd")
## # A tibble: 12 x 7
##    gender risk  treatment variable       n  mean    sd
##    <fct>  <fct> <fct>     <chr>      <dbl> <dbl> <dbl>
##  1 male   high  X         pain_score     6  92.7  5.12
##  2 male   high  Y         pain_score     6  82.3  5.00
##  3 male   high  Z         pain_score     6  79.7  4.05
##  4 male   low   X         pain_score     6  76.1  3.86
##  5 male   low   Y         pain_score     6  73.1  4.76
##  6 male   low   Z         pain_score     6  74.5  4.89
##  7 female high  X         pain_score     6  78.9  5.32
##  8 female high  Y         pain_score     6  81.2  4.62
##  9 female high  Z         pain_score     6  81.0  3.98
## 10 female low   X         pain_score     6  74.2  3.69
## 11 female low   Y         pain_score     6  68.4  4.08
## 12 female low   Z         pain_score     6  69.8  2.72
library(ggplot2)
library(boxplotdbl)
ggplot(headache, aes(x = treatment, y = pain_score, fill = treatment)) +
    geom_boxplot() +
    geom_jitter(shape = 15,
        color = "steelblue",
        position = position_jitter(0.21)) +
   theme_minimal()

ggplot(headache, aes(x = gender, y = pain_score, fill = gender)) +
    geom_boxplot() +
    geom_jitter(shape = 15,
        color = "steelblue",
        position = position_jitter(0.21)) +
   theme_minimal()

ggplot(headache, aes(x = risk, y = pain_score, fill = risk)) +
    geom_boxplot() +
    geom_jitter(shape = 15,
        color = "steelblue",
        position = position_jitter(0.21)) +
   theme_minimal()

headache %>%
  group_by(gender, risk, treatment) %>%
  identify_outliers(pain_score)
## # A tibble: 4 x 7
##   gender risk  treatment    id pain_score is.outlier is.extreme
##   <fct>  <fct> <fct>     <int>      <dbl> <lgl>      <lgl>     
## 1 female high  X            57       68.4 TRUE       TRUE      
## 2 female high  Y            62       73.1 TRUE       FALSE     
## 3 female high  Z            67       75.0 TRUE       FALSE     
## 4 female high  Z            71       87.1 TRUE       FALSE

It can be seen that, the data contain one extreme outlier (id = 57, female at high risk of migraine taking drug X)

model  <- lm(pain_score ~ gender*risk*treatment, data = headache)
# Compute Shapiro-Wilk test of normality
shapiro_test(residuals(model))
## # A tibble: 1 x 3
##   variable         statistic p.value
##   <chr>                <dbl>   <dbl>
## 1 residuals(model)     0.982   0.398
ggqqplot(residuals(model))

In the QQ plot, as all the points fall approximately along the reference line, we can assume normality. This conclusion is supported by the Shapiro-Wilk test. The p-value is not significant (p = 0.4), so we can assume normality.

headache %>%
  group_by(gender, risk, treatment) %>%
  shapiro_test(pain_score)
## # A tibble: 12 x 6
##    gender risk  treatment variable   statistic       p
##    <fct>  <fct> <fct>     <chr>          <dbl>   <dbl>
##  1 male   high  X         pain_score     0.958 0.808  
##  2 male   high  Y         pain_score     0.902 0.384  
##  3 male   high  Z         pain_score     0.955 0.784  
##  4 male   low   X         pain_score     0.982 0.962  
##  5 male   low   Y         pain_score     0.920 0.507  
##  6 male   low   Z         pain_score     0.924 0.535  
##  7 female high  X         pain_score     0.714 0.00869
##  8 female high  Y         pain_score     0.939 0.654  
##  9 female high  Z         pain_score     0.971 0.901  
## 10 female low   X         pain_score     0.933 0.600  
## 11 female low   Y         pain_score     0.927 0.555  
## 12 female low   Z         pain_score     0.958 0.801

The pain scores were normally distributed (p > 0.05) except for one group (female at high risk of migraine taking drug X, p = 0.0086), as assessed by Shapiro-Wilk’s test of normality.

headache %>% levene_test(pain_score ~ gender*risk*treatment)
## # A tibble: 1 x 4
##     df1   df2 statistic     p
##   <int> <int>     <dbl> <dbl>
## 1    11    60     0.179 0.998

The Levene’s test is not significant (p > 0.05). Therefore, we can assume the homogeneity of variances in the different groups.

ggqqplot(headache, "pain_score", ggtheme = theme_bw()) +
  facet_grid(gender + risk ~ treatment, labeller = "label_both")

All the points fall approximately along the reference line, except for one group (female at high risk of migraine taking drug X), where we already identified an extreme outlier.

res.aov <- headache %>% anova_test(pain_score ~ gender*risk*treatment)
## Coefficient covariances computed by hccm()
res.aov
## ANOVA Table (type II tests)
## 
##                  Effect DFn DFd      F        p p<.05   ges
## 1                gender   1  60 16.196 1.63e-04     * 0.213
## 2                  risk   1  60 92.699 8.80e-14     * 0.607
## 3             treatment   2  60  7.318 1.00e-03     * 0.196
## 4           gender:risk   1  60  0.141 7.08e-01       0.002
## 5      gender:treatment   2  60  3.338 4.20e-02     * 0.100
## 6        risk:treatment   2  60  0.713 4.94e-01       0.023
## 7 gender:risk:treatment   2  60  7.406 1.00e-03     * 0.198

There was a statistically significant three-way interaction between gender, risk and treatment, F(2, 60) = 7.41, p = 0.001.

# Group the data by gender and 
# fit simple two-way interaction 
model  <- lm(pain_score ~ gender*risk*treatment, data = headache)
headache %>%
  group_by(gender) %>%
  anova_test(pain_score ~ risk*treatment, error = model)
## Coefficient covariances computed by hccm()
## Coefficient covariances computed by hccm()
## # A tibble: 6 x 8
##   gender Effect           DFn   DFd      F             p `p<.05`   ges
##   <fct>  <chr>          <dbl> <dbl>  <dbl>         <dbl> <chr>   <dbl>
## 1 male   risk               1    60 50.0   0.00000000187 "*"     0.455
## 2 male   treatment          2    60 10.2   0.000157      "*"     0.253
## 3 male   risk:treatment     2    60  5.25  0.008         "*"     0.149
## 4 female risk               1    60 42.8   0.0000000150  "*"     0.416
## 5 female treatment          2    60  0.482 0.62          ""      0.016
## 6 female risk:treatment     2    60  2.87  0.065         ""      0.087

There was a statistically significant simple two-way interaction between risk and treatment (risk:treatment) for males, F(2, 60) = 5.25, p = 0.008, but not for females, F(2, 60) = 2.87, p = 0.065.

For males, this result suggests that the effect of treatment on “pain_score” depends on one’s “risk” of migraine. In other words, the risk moderates the effect of the type of treatment on pain_score.

# Group the data by gender and risk, and fit  anova
treatment.effect <- headache %>%
  group_by(gender, risk) %>%
  anova_test(pain_score ~ treatment, error = model)
## Coefficient covariances computed by hccm()
## Coefficient covariances computed by hccm()
## Coefficient covariances computed by hccm()
## Coefficient covariances computed by hccm()
treatment.effect %>% filter(gender == "male")
## # A tibble: 2 x 9
##   gender risk  Effect      DFn   DFd     F         p `p<.05`   ges
##   <fct>  <fct> <chr>     <dbl> <dbl> <dbl>     <dbl> <chr>   <dbl>
## 1 male   high  treatment     2    60 14.8  0.0000061 "*"     0.33 
## 2 male   low   treatment     2    60  0.66 0.521     ""      0.022

There was a statistically significant simple simple main effect of treatment for males at high risk of migraine, F(2, 60) = 14.8, p < 0.0001), but not for males at low risk of migraine, F(2, 60) = 0.66, p = 0.521.

This analysis indicates that, the type of treatment taken has a statistically significant effect on pain_score in males who are at high risk.

In other words, the mean pain_score in the treatment X, Y and Z groups was statistically significantly different for males who at high risk, but not for males at low risk.

# Pairwise comparisons
library(emmeans)
pwc <- headache %>%
  group_by(gender, risk) %>%
  emmeans_test(pain_score ~ treatment, p.adjust.method = "bonferroni") %>%
  select(-df, -statistic, -p) # Remove details
# Show comparison results for male at high risk
pwc %>% filter(gender == "male", risk == "high")
## # A tibble: 3 x 8
##   gender risk  term      .y.        group1 group2      p.adj p.adj.signif
##   <chr>  <chr> <chr>     <chr>      <chr>  <chr>       <dbl> <chr>       
## 1 male   high  treatment pain_score X      Y      0.000386   ***         
## 2 male   high  treatment pain_score X      Z      0.00000942 ****        
## 3 male   high  treatment pain_score Y      Z      0.897      ns
# Estimated marginal means (i.e. adjusted means) 
# with 95% confidence interval
get_emmeans(pwc) %>% filter(gender == "male", risk == "high")
## # A tibble: 3 x 9
##   gender risk  treatment emmean    se    df conf.low conf.high method      
##   <fct>  <fct> <fct>      <dbl> <dbl> <dbl>    <dbl>     <dbl> <chr>       
## 1 male   high  X           92.7  1.80    60     89.1      96.3 Emmeans test
## 2 male   high  Y           82.3  1.80    60     78.7      85.9 Emmeans test
## 3 male   high  Z           79.7  1.80    60     76.1      83.3 Emmeans test

In the pairwise comparisons table above, we are interested only in the simple simple comparisons for males at a high risk of a migraine headache. In our example, there are three possible combinations of group differences.

LS0tDQp0aXRsZTogIkNTLUxhYjExLUFOT1ZBIg0KYXV0aG9yOiAiWW9uYXRoYW4gQW5nZ3JhaXdhbiINCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGhpZ2hsaWdodDogbW9ub2Nocm9tZQ0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KLS0tDQpgYGB7ciBMb2dvLGVjaG89RkFMU0UsZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGggPSAnNDAlJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJDOi9Vc2Vycy9Zb25hdGhhbi9Eb3dubG9hZHMvbG9nb21hdGFuYS5qcGciKQ0KYGBgDQoNCkV4ZXJjaXNlIDEgT25lLXdheSBBTk9WQQ0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCkluIHRoaXMgZXhlcmNpc2UgeW91IGhhdmUgdG8gY29uc2lkZXIgZGF0YSBzZXQgbmFtZWQgUGxhbnRHcm93dGggZnJvbSBSIGVudmlyb25tZW50LiBJdCBjb250YWlucyB0aGUgd2VpZ2h0IG9mIHBsYW50cyBvYnRhaW5lZCB1bmRlciBhIGNvbnRyb2wgYW5kIHR3byBkaWZmZXJlbnQgdHJlYXRtZW50IGNvbmRpdGlvbnMuIFBsZWFzZSBjb25zaWRlciB0aGUgZm9sbG93aW5nIHN0ZXBzOg0KKiBEYXRhIFByZXBhcmF0aW9uIChPbmUtd2F5IEFOT1ZBKQ0KKiBPbmUtd2F5IEFOT1ZBIFRlc3QNCiogQ2hlY2sgQXNzdW1wdGlvbnMgKE9uZS13YXkgQU5PVkEpDQoqIFBhaXJ3aXNlLWNvbXBhcmlzb24gKE9uZS13YXkgQU5PVkEpDQoqIFR1a2V5IEhTRCAoT25lLXdheSBBTk9WQSkNCg0KRXhlcmNpc2UgMiBUd28td2F5IEFOT1ZBDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KUGxlYXNlIGFwcGx5IFR3by13YXkgQU5PVkEgcHJvY2VkdXJlcyB0byB0aGUgcmF0cyBkYXRhIGZyb20gdGhlIGZhcmF3YXkgcGFja2FnZS4gVGhlcmUgYXJlIHR3byBmYWN0b3JzIGhlcmU6IHBvaXNvbiBhbmQgdHJlYXQuIFlvdSBjYW4gdXNlIHRoZSBsZXZlbHMoKSBmdW5jdGlvbiB0byBleHRyYWN0IHRoZSBsZXZlbHMgb2YgYSBmYWN0b3IgdmFyaWFibGUuIEdpdmUgeW91ciBvcGluaW9ucyBhY2NvcmRpbmdseSENCg0KRXhlcmNpc2UgMyBUaHJlZS13YXkgQU5PVkENCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpJbiB0aGlzIGV4ZXJjaXNlIHlvdSB3aWxsIHVzZSB0aGUgaGVhZGFjaGUgZGF0YXNldCBbaW4gZGF0YXJpdW0gcGFja2FnZV0sIHdoaWNoIGNvbnRhaW5zIHRoZSBtZWFzdXJlcyBvZiBtaWdyYWluZSBoZWFkYWNoZSBlcGlzb2RlIHBhaW4gc2NvcmUgaW4gNzIgcGFydGljaXBhbnRzIHRyZWF0ZWQgd2l0aCB0aHJlZSBkaWZmZXJlbnQgdHJlYXRtZW50cy4gVGhlIHBhcnRpY2lwYW50cyBpbmNsdWRlIDM2IG1hbGVzIGFuZCAzNiBmZW1hbGVzLiBNYWxlcyBhbmQgZmVtYWxlcyB3ZXJlIGZ1cnRoZXIgc3ViZGl2aWRlZCBpbnRvIHdoZXRoZXIgdGhleSB3ZXJlIGF0IGxvdyBvciBoaWdoIHJpc2sgb2YgbWlncmFpbmUuIFlvdSBoYXZlIHRvIHVuZGVyc3RhbmQgaG93IGVhY2ggaW5kZXBlbmRlbnQgdmFyaWFibGUgKHR5cGUgb2YgdHJlYXRtZW50cywgcmlzayBvZiBtaWdyYWluZSBhbmQgZ2VuZGVyKSBpbnRlcmFjdCB0byBwcmVkaWN0IHRoZSBwYWluIHNjb3JlLg0KDQoNCiMgRXhlcmNpc2UgMQ0KDQojIyBEYXRhIFByZXBhcmF0aW9uIChPbmUtV2F5IEFub3ZhKQ0KDQpXZeKAmWxsIHVzZSB0aGUgZGF0YSBzZXQgbmFtZWQgYFBsYW50R3Jvd3RoYCBmcm9tIFIgZW52aXJvbm1lbnQuIEl0IGNvbnRhaW5zIHRoZSB3ZWlnaHQgb2YgcGxhbnRzIG9idGFpbmVkIHVuZGVyIGEgY29udHJvbCBhbmQgdHdvIGRpZmZlcmVudCB0cmVhdG1lbnQgY29uZGl0aW9ucy4gU28gd2UgbmVlZCB0byBkb3dubG9hZCB0aGUgZGF0YSBzZXQsIHRoZW4gd2UgbG9vayBhdCB0aGUgZGF0YS4NCmBgYHtyfQ0KZGF0YSgiUGxhbnRHcm93dGgiKQ0KaGVhZChQbGFudEdyb3d0aCkNCmBgYA0KQW5kIG5vdywgd2Ugc2VwZXJhdGUgdGhlIGdyb3VwIG5hbWUuDQpgYGB7cn0NCmxldmVscyhQbGFudEdyb3d0aCRncm91cCkgDQpgYGANClRoZSBvbmUtd2F5IEFOT1ZBIGNhbiBiZSB1c2VkIHRvIGRldGVybWluZSB3aGV0aGVyIHRoZSBtZWFucyBwbGFudCBncm93dGhzIGFyZSBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBiZXR3ZWVuIHRoZSB0aHJlZSBjb25kaXRpb25zLg0KDQpOb3csIHdlIGNhbiBzZWUgdGhhdCBpbiBgUGxhbnRHcm93dGhgIGRhdGEgc2V0LCB0aGVyZSBpcyAzIGdyb3VwcyBuYW1lZCBgY3RybGAsYHRydDFgLCBhbmQgYHRydDJgLiBCZWZvcmUgd2UgYmVnaW4gdG8gYnVpbGQgdGhlIG1vZGVsLCBpdOKAmXMgaW1wb3J0YW50IHRvIGNvbnNpZGVyIHRoZSBzdW1tYXJ5IHN0YXRpc3RpY3MgYW5kIGFsc28gaWRlbnRpZnkgaWYgdGhlcmUgaXMgc29tZSBvdXRsaWVycy4gV2UgY2EgYXBwbHkgYm94IHBsb3QgbWV0aG9kcyBvciBpbXBsZW1lbnQgYnkgdXNpbmcgdGhlIFIgZnVuY3Rpb24gaWRlbnRpZnlfb3V0bGllcnMoKSBbcnN0YXRpeCBwYWNrYWdlXS4NCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHlyKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHRvIGNyZWF0ZSB0aWR5IGRhdGENCmxpYnJhcnkocnN0YXRpeCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdXNlIGZvciBiYXNpYyBBbm92YS9zdGF0aXN0aWNhbCB0ZXN0cyANCmxpYnJhcnkoZ2dwbG90MikgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZm9yIHZpc3VhbGl6YXRpb24NCiMgZmlyc3QgZ2V0IHRoZSBzdW1tYXJ5IHN0YXRpc3RpY3MNClBsYW50R3Jvd3RoICU+JQ0KICBncm91cF9ieShncm91cCkgJT4lDQogIGdldF9zdW1tYXJ5X3N0YXRzKHdlaWdodCwgdHlwZSA9ICJtZWFuX3NkIikNCmBgYA0KDQpgYGB7cn0NCiMgcGxvdChQbGFudEdyb3d0aCB+IGdyb3VwLCBkYXRhID0gUGxhbnRHcm93dGgsIGNvbCA9IDI6NSkgICAgIyBib3hwbG90IG9mIGB3ZWlnaHRgIGJ5IGBncm91cGANCmdncGxvdChQbGFudEdyb3d0aCwgYWVzKHggPSBncm91cCwgeSA9IHdlaWdodCwgZmlsbCA9IGdyb3VwKSkgKw0KICAgIGdlb21fYm94cGxvdCgpICsNCiAgICBnZW9tX2ppdHRlcihzaGFwZSA9IDE1LA0KICAgICAgICBjb2xvciA9ICJzdGVlbGJsdWUiLA0KICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcigwLjIxKSkgKw0KICAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3J9DQojIGlkZW50aWZ5IG91dGxpZXJzDQpQbGFudEdyb3d0aCAlPiUgDQogIGdyb3VwX2J5KGdyb3VwKSAlPiUNCiAgaWRlbnRpZnlfb3V0bGllcnMod2VpZ2h0KQ0KYGBgDQpUaGVyZSB3ZXJlIG5vIGV4dHJlbWUgb3V0bGllcnMuDQoNCiMjIE9uZS1XYXkgQW5vdmEgVGVzdA0KDQpUaGUgYGFvdigpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIG9idGFpbiB0aGUgcmVsZXZhbnQgc3VtcyBvZiBzcXVhcmVzLiBVc2luZyB0aGUgYHN1bW1hcnkoKWAgZnVuY3Rpb24gb24gdGhlIG91dHB1dCBmcm9tIGBhb3YoKWAgY3JlYXRlcyB0aGUgZGVzaXJlZCBBTk9WQSB0YWJsZS4gKFdpdGhvdXQgdGhlIHVubmVlZGVkIHJvdyBmb3IgdG90YWwuKQ0KDQpgYGB7cn0NCnJlcy5hb3YgPSBhbm92YV90ZXN0KHdlaWdodCB+IGdyb3VwLCBkYXRhID0gUGxhbnRHcm93dGgpDQp3ZWlnaHRfYW92ID0gYW92KHdlaWdodCB+IGdyb3VwLCBkYXRhID0gUGxhbnRHcm93dGgpDQpzdW1tYXJ5KHdlaWdodF9hb3YpDQpgYGANCg0KQmVjYXVzZSB0aGUgcHZhbHVlIDwgYSwgdGhlbiB3ZSByZWplY3QgSDAgdGhhdCBjbGFpbXMgYWxsIG9mIHRoZSBncm91cCwgZ2l2ZXMgdGhlIHNhbWUgbWVhbiB2YWx1ZSwgd2UgYWNjZXB0IEgxIHRoYXQgY2xhaW1zIGF0IGxlYXN0IG9uZSBncm91cCBtZWFuIGlzIG5vdCBlcXVhbCB0byB0aGUgb3RoZXJzLg0KDQpIb3dldmVyLCB3ZSBub3RpY2UgdGhhdCB0aGUgcC12YWx1ZSBvZiB0aGlzIHRlc3QgaXMgbG93LCBzbyB1c2luZyBhbnkgcmVhc29uYWJsZSBzaWduaWZpY2FuY2UgbGV2ZWwgd2Ugd291bGQgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMuDQpgYGB7cn0NCmdyb3VwcyA9IGRhdGEuZnJhbWUoZ3JvdXAgPSB1bmlxdWUoUGxhbnRHcm93dGgkZ3JvdXApKQ0KZGF0YS5mcmFtZShncm91cHMsIHdlaWdodCA9IHByZWRpY3Qod2VpZ2h0X2FvdiwgZ3JvdXBzKSkNCmBgYA0KDQpIZXJlLCB3ZSBoYXZlIGNyZWF0ZWQgYSBkYXRhZnJlYW1lIHdpdGggYSByb3cgZm9yIGVhY2ggZ3JvdXAuIEJ5IHByZWRpY3Rpbmcgb24gdGhpcyBkYXRhZnJhbWUsIHdlIG9idGFpbiB0aGUgc2FtcGxlIG1lYW5zIG9mIGVhY2ggZ3JvdXAsIGFuZCBub25lIGZyb20gYWwgb2YgdGhlbSBpcyBjbG9zZSBmb3IgZWFjaCBvdGhlcnMuDQoNCiMjIENoZWNrIEFzc3VtcHRpb25zIChPbmUtV2F5IEFub3ZhKQ0KDQpUaGUgQU5PVkEgdGVzdCBhc3N1bWVzIHRoYXQsIHRoZSBkYXRhIGFyZSBub3JtYWxseSBkaXN0cmlidXRlZCBhbmQgdGhlIHZhcmlhbmNlIGFjcm9zcyBncm91cHMgYXJlIGhvbW9nZW5lb3VzLiBXZSBjYW4gY2hlY2sgdGhhdCB3aXRoIHNvbWUgZGlhZ25vc3RpYyBwbG90cy4NCmBgYHtyfQ0KcGxvdCh3ZWlnaHRfYW92LCAxKSAgICAgICAgICMgMS4gSG9tb2dlbmVpdHkgb2YgdmFyaWFuY2VzDQpgYGANCkluIHRoZSBwbG90IGFib3ZlLCB0aGVyZSBpcyBubyBldmlkZW50IHJlbGF0aW9uc2hpcHMgYmV0d2VlbiByZXNpZHVhbHMgYW5kIGZpdHRlZCB2YWx1ZXMgKHRoZSBtZWFuIG9mIGVhY2ggZ3JvdXBzKSwgd2hpY2ggaXMgZ29vZC4gU28sIHdlIGNhbiBhc3N1bWUgdGhlIGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlcy4gQW5kIHdlIGNhbiBzZWUgdGhhdCB0aGUgb3V0bGllcnMgYXJlIG8xNywgbzE1LCBhbmQgNG8uIE5vdCBtdWNoLg0KDQoNCmBgYHtyfQ0KcGxvdCh3ZWlnaHRfYW92LCAyKSAgICAgICAgICAgICAjIDIuIE5vcm1hbGl0eQ0KYGBgDQoNCldlIGNhbiBzZWUgdGhhdCB0aGUgb3V0bGllcnMgYXJlIG5vdCBtdWNoIHRvby4NCg0KYGBge3J9DQpQbGFudEdyb3d0aCAlPiUNCiAgZ3JvdXBfYnkoZ3JvdXApICU+JQ0KICBzaGFwaXJvX3Rlc3Qod2VpZ2h0KQ0KYGBgDQoNClBvaW50cyA0LCAxNSwgMTcgYXJlIGRldGVjdGVkIGFzIG91dGxpZXJzLCB3aGljaCBjYW4gc2V2ZXJlbHkgYWZmZWN0IG5vcm1hbGl0eSBhbmQgaG9tb2dlbmVpdHkgb2YgdmFyaWFuY2UuIEl0IGNhbiBiZSB1c2VmdWwgdG8gcmVtb3ZlIG91dGxpZXJzIHRvIG1lZXQgdGhlIHRlc3QgYXNzdW1wdGlvbnMuIEkgcmVjb21tZW5kIExldmVuZeKAmXMgdGVzdCwgd2hpY2ggaXMgbGVzcyBzZW5zaXRpdmUgdG8gZGVwYXJ0dXJlcyBmcm9tIG5vcm1hbCBkaXN0cmlidXRpb24uDQoNCmBgYHtyfQ0KbGlicmFyeShjYXIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBgbGV2ZW5lVGVzdCgpYCBpbiBjYXIgcGFja2FnZQ0KbGV2ZW5lVGVzdCh3ZWlnaHQgfiBncm91cCwgZGF0YSA9IFBsYW50R3Jvd3RoKSAgICAgICAgICAgIyBsZXZlbmXigJlzIHRlc3QgKGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlcykNCmBgYA0KRnJvbSB0aGUgb3V0cHV0IGFib3ZlLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHAtdmFsdWUgaXMgPCAwLjA1LCB3aGljaCBpcyBub3Qgc2lnbmlmaWNhbnQuIFRoaXMgbWVhbnMgdGhhdCwgdGhlcmUgaXMgbm90IHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiB2YXJpYW5jZXMgYWNyb3NzIGdyb3Vwcy4gVGhlcmVmb3JlLCB3ZSBjYW4gYXNzdW1lIHRoZSBob21vZ2VuZWl0eSBvZiB2YXJpYW5jZXMgaW4gdGhlIGRpZmZlcmVudCB0cmVhdG1lbnQgZ3JvdXBzLg0KDQoNCmBgYHtyfQ0Kb25ld2F5LnRlc3Qod2VpZ2h0IH4gZ3JvdXAsIGRhdGEgPSBQbGFudEdyb3d0aCkgICAgICAgICAgIyBubyBhc3N1bXB0aW9uIG9mIGVxdWFsIHZhcmlhbmNlcw0KYGBgDQoNCkZyb20gdGhlIG91dHB1dCBhYm92ZSB3ZSBjYW4gc2VlIHRoYXQgdGhlIHAtdmFsdWUgaXMgbGVzcyB0aGFuIHRoZSBzaWduaWZpY2FuY2UgbGV2ZWwgb2YgMC4wNS4gVGhpcyBtZWFucyB0aGF0IHRoZXJlIGlzIGV2aWRlbmNlIHRvIHN1Z2dlc3QgdGhhdCB0aGUgdmFyaWFuY2UgYWNyb3NzIGdyb3VwcyBpcyBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50bHkgZGlmZmVyZW50LiBUaGVyZWZvcmUsIHdlIGNhbiBhc3N1bWUgdGhlIGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlcyBpbiB0aGUgZGlmZmVyZW50IHRyZWF0bWVudCBncm91cHMuDQoNCk5vdGUgdGhhdCBhIG5vbi1wYXJhbWV0cmljIGFsdGVybmF0aXZlIHRvIG9uZS13YXkgQU5PVkEgaXMgS3J1c2thbC1XYWxsaXMgcmFuayBzdW0gdGVzdCwgd2hpY2ggY2FuIGJlIHVzZWQgd2hlbiBBTk5PVkEgYXNzdW1wdGlvbnMgYXJlIG5vdCBtZXQuDQpgYGB7cn0NCmtydXNrYWwudGVzdCh3ZWlnaHQgfiBncm91cCwgZGF0YSA9IFBsYW50R3Jvd3RoKSAgICAgICAgICMgdXNlIGlmIEFOTk9WQSBhc3N1bXB0aW9ucyBhcmUgbm90IG1ldA0KYGBgDQoNCiMjIFBhaXJ3aXNlIC0gY29tcGFyaXNvbiAoT25lLXdheSBBbm92YSkNCg0KU3VwcG9zZSB3ZSByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyBmcm9tIHRoZSBBTk9WQSB0ZXN0IGZvciBlcXVhbCBtZWFucy4gVGhhdCB0ZWxscyB1cyB0aGF0IHRoZSBtZWFucyBhcmUgZGlmZmVyZW50LiBCdXQgd2hpY2ggbWVhbnM/IEFsbCBvZiB0aGVtPyBTb21lIG9mIHRoZW0/IFRoZSBvYnZpb3VzIHN0cmF0ZWd5IGlzIHRvIHRlc3QgYWxsIHBvc3NpYmxlIGNvbXBhcmlzb25zIG9mIHR3byBtZWFucy4gV2UgY2FuIGRvIHRoaXMgZWFzaWx5IGluIGBSYC4NCg0KYGBge3J9DQpwd2MgPC0gUGxhbnRHcm93dGggJT4lIA0KICB0dWtleV9oc2Qod2VpZ2h0IH4gZ3JvdXApDQpwd2MNCmBgYA0KV2UgY2FuIHNlZSB0aGF0IGFsbCBvZiB0aGUgZ3JvdXAgbWVhbiBpcyBkaWZmZXJlbnQgYW5kIGRvZXNudCBjbG9zZSBlbm91Z2ggZm9yIGVhY2ggb3RoZXIuDQoNClRoZSBvdXRwdXQgY29udGFpbnMgdGhlIGZvbGxvd2luZyBjb2x1bW5zOg0KDQpgZXN0aW1hdGVgOiBlc3RpbWF0ZSBvZiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIG1lYW5zIG9mIHRoZSB0d28gZ3JvdXBzDQpgY29uZi5sb3csIGNvbmYuaGlnaGA6IHRoZSBsb3dlciBhbmQgdGhlIHVwcGVyIGVuZCBwb2ludCBvZiB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCBhdCA5NSUgKGRlZmF1bHQpDQpgcC5hZGpgOiBwLXZhbHVlIGFmdGVyIGFkanVzdG1lbnQgZm9yIHRoZSBtdWx0aXBsZSBjb21wYXJpc29ucy4NCg0KIyMgVHVrZXkgSFNEIChPbmUtV2F5IEFub3ZhKQ0KDQpUdWtleeKAmXMgSG9uZXN0IFNpZ25pZmljYW5jZSBkaWZmZXJlbmNlIGNhbiBiZSBhcHBsaWVkIGRpcmVjdGx5IHRvIGFuIG9iamVjdCB3aGljaCB3YXMgY3JlYXRlZCB1c2luZyBgYW92KClgLiBJdCB3aWxsIGFkanVzdCB0aGUgcC12YWx1ZXMgb2YgdGhlIHBhaXJ3aXNlIGNvbXBhcmlzb25zIG9mIHRoZSBtZWFucyB0byBjb250cm9sIHRoZSBGV0VSLCBpbiB0aGlzIGNhc2UsIGZvciAwLjA1LiBOb3RpY2UgaXQgYWxzbyBnaXZlcyBjb25maWRlbmNlIGludGVydmFscyBmb3IgdGhlIGRpZmZlcmVuY2Ugb2YgdGhlIG1lYW5zLg0KDQpgYGB7cn0NClR1a2V5SFNEKHdlaWdodF9hb3YsIGNvbmYubGV2ZWwgPSAwLjk1KQ0KYGBgDQoNCkJhc2VkIG9uIHRoZXNlIHJlc3VsdHMsIHdlIHNlZSBhIGRpZmZlcmVuY2UgYmV0d2VlbiBhbGwgb2YgdGhlIGdyb3Vwcy4gQWxsIHBhaXJ3aXNlIGNvbXBhcmlzb25zIGFyZSBzaWduaWZpY2FudC4gSWYgeW91IHJldHVybiB0byB0aGUgb3JpZ2luYWwgYm94cGxvdCwgdGhlc2UgcmVzdWx0cyBzaG91bGQgbm90IGJlIHN1cnByaXNpbmcuDQoNCkFsc28sIG5pY2VseSwgd2UgY2FuIGVhc2lseSBwcm9kdWNlIGEgcGxvdCBvZiB0aGVzZSBjb25maWRlbmNlIGludGVydmFscy4NCg0KYGBge3J9DQpwbG90KFR1a2V5SFNEKHdlaWdodF9hb3YsIGNvbmYubGV2ZWwgPSAwLjk1KSkNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgdGhlIHBhaXIgYmV0d2VlbiBgdHJsMmAgZ3JvdXAgYW5kIGBjdHJsYCBhcmUgdGhlIGNsb3Nlc3Qgb25lIHRvIHRoZSBtZWFuIDAuMCwgYnV0IHdlIHN0aWxsIGNhbnQgYWNjZXB0IHRoYXQgYmV0d2VlbiBgdHJsMmAgYW5kIGBjdHJsYCBhcmUgc2FtZS4NCg0KDQojIEV4ZXJjaXNlIDINCg0KIyMgRGF0YSBQcmVwYXJhdGlvbiAoVHdvLVdheSBBbm92YSkNCg0KYGBge3J9DQpsaWJyYXJ5KGZhcmF3YXkpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGRhdGFzZXQNCmxpYnJhcnkodGlkeXIpDQpoZWFkKHJhdHMpDQpgYGANCg0KYGBge3J9DQpsZXZlbHMocmF0cyRwb2lzb24pDQpgYGANCldlIGNhbiBzZWUgdGhhdCB0aGUgcG9pc29uIGhhdmUgMyB0eXBlLg0KDQpgYGB7cn0NCmxldmVscyhyYXRzJHRyZWF0KQ0KYGBgDQoNCldlIGNhbiBzZWUgdGhhdCB0aGUgdHJlYXRzIGhhdmUgNCB0eXBlLg0KDQpgYGB7cn0NCmxpYnJhcnkocnN0YXRpeCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdXNlIGZvciBiYXNpYyBBbm92YS9zdGF0aXN0aWNhbCB0ZXN0cw0Kc2V0LnNlZWQoMTYwMykNCmRhdGEoInJhdHMiLCBwYWNrYWdlID0gImZhcmF3YXkiKQ0KcmF0cyAlPiUgDQogIHNhbXBsZV9uX2J5KHBvaXNvbiwgdHJlYXQsIHNpemUgPSAxKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5cikgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0byBjcmVhdGUgdGlkeSBkYXRhDQpsaWJyYXJ5KHJzdGF0aXgpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHVzZSBmb3IgYmFzaWMgQW5vdmEvc3RhdGlzdGljYWwgdGVzdHMgDQpsaWJyYXJ5KGdncGxvdDIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGZvciB2aXN1YWxpemF0aW9uDQojIGZpcnN0IGdldCB0aGUgc3VtbWFyeSBzdGF0aXN0aWNzDQpyYXRzICU+JQ0KICBncm91cF9ieShwb2lzb24sIHRyZWF0KSAlPiUNCiAgZ2V0X3N1bW1hcnlfc3RhdHMoIHR5cGUgPSAibWVhbl9zZCIpDQpgYGANCg0KYGBge3J9DQojIHZpc3VhbGl6YXRpb24NCmdncGxvdChyYXRzLCBhZXMoeCA9IHBvaXNvbiwgeSA9IHRpbWUsIGZpbGwgPSB0cmVhdCkpICsNCiAgICBnZW9tX2JveHBsb3QoKSArDQogICAgZ2VvbV9qaXR0ZXIoc2hhcGUgPSAxNSwNCiAgICAgICAgY29sb3IgPSAic3RlZWxibHVlIiwNCiAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIoMC4yMSkpICsNCiAgIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyfQ0KIyBpZGVudGlmeSBvdXRsaWVycw0KcmF0cyAlPiUNCiAgZ3JvdXBfYnkocG9pc29uLCB0cmVhdCkgJT4lDQogIGlkZW50aWZ5X291dGxpZXJzKHRpbWUpDQpgYGANClRoZXJlIHdlcmUgbm8gZXh0cmVtZSBvdXRsaWVycy4NCg0KDQpgYGB7cn0NCm1pY2VfaW50ICAgPSBhb3YodGltZSB+IHRyZWF0ICogcG9pc29uLCBkYXRhID0gcmF0cykgICMgaW50ZXJhY3Rpb24gbW9kZWwNCm1pY2VfYWRkICAgPSBhb3YodGltZSB+IHRyZWF0ICsgcG9pc29uLCBkYXRhID0gcmF0cykgICMgYWRkaXRpdmUgbW9kZWwNCm1pY2VfdHJlYXQ9IGFvdih0aW1lIH50cmVhdCwgZGF0YSA9IHJhdHMpICAgICAgICAgICAgICAgICAgICAjIHNpbmdsZSBmYWN0b3IgbW9kZWwNCm1pY2VfdG94aWMgID0gYW92KHRpbWUgfiBwb2lzb24sIGRhdGEgPSByYXRzKSAgICAgICAgICAgIyBzaW5nbGUgZmFjdG9yIG1vZGVsDQptaWNlX251bGwgID0gYW92KHRpbWUgfiAxLCBkYXRhID0gcmF0cykgICAgICAgICAgICAgICAgICAgICAgICAgIyBudWxsIG1vZGVsDQpzdW1tYXJ5KG1pY2VfaW50KQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShtaWNlX2FkZCkNCmBgYA0KYGBge3J9DQpzdW1tYXJ5KG1pY2VfdHJlYXQpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KG1pY2VfdG94aWMpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KG1pY2VfbnVsbCkNCmBgYA0KDQpJbiB0aGUgUiBjb2RlIGJlbG93LCB0aGUgYXN0ZXJpc2sgcmVwcmVzZW50cyB0aGUgaW50ZXJhY3Rpb24gZWZmZWN0IGFuZCB0aGUgbWFpbiBlZmZlY3Qgb2YgZWFjaCB2YXJpYWJsZSAoYW5kIGFsbCBsb3dlci1vcmRlciBpbnRlcmFjdGlvbnMpLg0KDQpgYGB7cn0NCmFvdl90ZXN0X2ludCA8LSByYXRzICU+JSANCiAgYW5vdmFfdGVzdCh0aW1lIH4gIHBvaXNvbiAqIHRyZWF0KQ0KYW92X3Rlc3RfaW50DQpgYGANClRoZXJlIHdhcyBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgaW50ZXJhY3Rpb24gYmV0d2VlbiB0cmVhdCBhbmQgcG9pc29uIGZvciB0aW1lIHNjb3JlLCBGKDYsIDM2KSA9IDEuODc0LCBwID0gMC4yMzguDQoNCg0KIyMgQ2hlY2sgQXNzdW1wdGlvbnMgKFR3by13YXkgQU5PVkEpIA0KDQpUaGUgQU5PVkEgdGVzdCBhc3N1bWVzIHRoYXQsIHRoZSBkYXRhIGFyZSBub3JtYWxseSBkaXN0cmlidXRlZCBhbmQgdGhlIHZhcmlhbmNlIGFjcm9zcyBncm91cHMgYXJlIGhvbW9nZW5lb3VzLiBXZSBjYW4gY2hlY2sgdGhhdCB3aXRoIHNvbWUgZGlhZ25vc3RpYyBwbG90cy4NCg0KYGBge3J9DQpwbG90KG1pY2VfaW50LCAxKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIDEuIEhvbW9nZW5laXR5IG9mIHZhcmlhbmNlcw0KYGBgDQpXZSBjYW4gc2VlIHRoYXQgdGhlIG91dGxpZXJzIHBvaW50cyBhcmUgMjQsMjYsIGFuZCAzMC4NCg0KYGBge3J9DQpwbG90KG1pY2VfaW50LCAyKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIDIuIE5vcm1hbGl0eQ0KYGBgDQpXZSBjYW4gc2VlIHRoYXQgdGhlIG91dGxpZXJzIHBvaW50cyBhcmUgMjQsMjYsIGFuZCAzMC4NCg0KYGBge3J9DQpyYXRzICU+JQ0KICBncm91cF9ieSh0cmVhdCwgcG9pc29uKSAlPiUNCiAgc2hhcGlyb190ZXN0KHRpbWUpDQpgYGANClRoZSBzY29yZSB3ZXJlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIChwID4gMC4wNSkgZm9yIGVhY2ggY2VsbCwgYXMgYXNzZXNzZWQgYnkgU2hhcGlyby1XaWxr4oCZcyB0ZXN0IG9mIG5vcm1hbGl0eS4NCg0KDQpgYGB7cn0NCmxpYnJhcnkoY2FyKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KbGV2ZW5lVGVzdCh0aW1lfnRyZWF0KnBvaXNvbixkYXRhPXJhdHMpICAjIGxldmVuZeKAmXMgdGVzdCAoY2FyIHBhY2thZ2VzKQ0KYGBgDQpUaGUgTGV2ZW5l4oCZcyB0ZXN0IGlzIHNpZ25pZmljYW50IChwIDwgMC4wNSkuIFRoZXJlZm9yZSwgd2UgY2FudCBhc3N1bWUgdGhlIGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlcyBpbiB0aGUgZGlmZmVyZW50IGdyb3Vwcy4NCg0KYGBge3J9DQpsZXZlbmVfdGVzdCh0aW1lfnRyZWF0KnBvaXNvbixkYXRhPXJhdHMpICMgbGV2ZW5l4oCZcyB0ZXN0IChyc3RhdGl4IHBhY2thZ2VzKQ0KYGBgDQpUaGUgTGV2ZW5l4oCZcyB0ZXN0IGlzIHNpZ25pZmljYW50IChwIDwgMC4wNSkuIA0KDQpgYGB7cn0NCm9uZXdheS50ZXN0KHRpbWV+dHJlYXQqcG9pc29uLGRhdGE9cmF0cykgIyBubyBhc3N1bXB0aW9uIG9mIGVxdWFsIHZhcmlhbmNlcw0KYGBgDQoNCiMjIFBhaXJ3aXNlLWNvbXBhcmlzb24gKFR3by13YXkgQU5PVkEpDQoNClN1cHBvc2Ugd2UgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMgZnJvbSB0aGUgQU5PVkEgdGVzdCBmb3IgZXF1YWwgbWVhbnMuIFRoYXQgdGVsbHMgdXMgdGhhdCB0aGUgbWVhbnMgYXJlIGRpZmZlcmVudC4gQnV0IHdoaWNoIG1lYW5zPyBBbGwgb2YgdGhlbT8gU29tZSBvZiB0aGVtPyBUaGUgb2J2aW91cyBzdHJhdGVneSBpcyB0byB0ZXN0IGFsbCBwb3NzaWJsZSBjb21wYXJpc29ucyBvZiB0d28gbWVhbnMuIFdlIGNhbiBkbyB0aGlzIGVhc2lseSBpbiBgUmAuDQoNCmBgYHtyfQ0KbGlicmFyeShlbW1lYW5zKQ0KcHdjIDwtIHJhdHMgJT4lIA0KICBncm91cF9ieSh0cmVhdCkgJT4lDQogIGVtbWVhbnNfdGVzdCh0aW1lIH4gcG9pc29uLCBwLmFkanVzdC5tZXRob2QgPSAiYm9uZmVycm9uaSIpIA0KcHdjDQpgYGANClRoZXJlIHdhc24ndCBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2Ugb2Ygam9iIHNhdGlzZmFjdGlvbiBzY29yZSBiZXR3ZWVuIGFsbCBncm91cHMgZm9yIGJvdGggbWFsZXMgYW5kIGZlbWFsZXMgKHAgPiAwLjA1KS4NCg0KIyMgVHVrZXkgSFNEIChUd28gV2F5IEFub3ZhKQ0KDQpUdWtleeKAmXMgSG9uZXN0IFNpZ25pZmljYW5jZSBkaWZmZXJlbmNlIGNhbiBiZSBhcHBsaWVkIGRpcmVjdGx5IHRvIGFuIG9iamVjdCB3aGljaCB3YXMgY3JlYXRlZCB1c2luZyBgYW92KClgLiBJdCB3aWxsIGFkanVzdCB0aGUgcC12YWx1ZXMgb2YgdGhlIHBhaXJ3aXNlIGNvbXBhcmlzb25zIG9mIHRoZSBtZWFucyB0byBjb250cm9sIHRoZSBGV0VSLCBpbiB0aGlzIGNhc2UsIGZvciAwLjA1LiBOb3RpY2UgaXQgYWxzbyBnaXZlcyBjb25maWRlbmNlIGludGVydmFscyBmb3IgdGhlIGRpZmZlcmVuY2Ugb2YgdGhlIG1lYW5zLg0KYGBge3J9DQpUdWtleUhTRChtaWNlX2ludCwgY29uZi5sZXZlbCA9IDAuOTUpDQpgYGANCg0KIyMgRXN0aW1hdGlvbnMgKFR3byBXYXkgQW5vdmEpDQoNClRvIGdldCB0aGUgZXN0aW1hdGVzLCB3ZeKAmWxsIGNyZWF0ZSBhIHRhYmxlIHdoaWNoIHdlIHdpbGwgcHJlZGljdCBvbi4NCg0KYGBge3J9DQptaWNlX3RhYmxlID0gZXhwYW5kLmdyaWQodHJlYXQgPSB1bmlxdWUocmF0cyR0cmVhdCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBvaXNvbj0gdW5pcXVlKHJhdHMkcG9pc29uKSkNCm1hdHJpeChwYXN0ZTAobWljZV90YWJsZSR0cmVhdCwgIi0iLCBtaWNlX3RhYmxlJHBvaXNvbikgLCA0LCAzLCBieXJvdyA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQpnZXRfZXN0X21lYW5zID0gZnVuY3Rpb24obW9kZWwsIHRhYmxlKSB7DQogIG1hdCA9IG1hdHJpeChwcmVkaWN0KG1vZGVsLCB0YWJsZSksIG5yb3cgPSAzLCBuY29sID0gMiwgYnlyb3cgPSBUUlVFKQ0KICBjb2xuYW1lcyhtYXQpID0gYygiQSIsICJCIikNCiAgcm93bmFtZXMobWF0KSA9IGMoIkkiLCAiSUkiLCAiSUlJIikNCiAgbWF0DQp9DQpgYGANCg0KDQpgYGB7cn0NCmtuaXRyOjprYWJsZShnZXRfZXN0X21lYW5zKG1vZGVsID0gbWljZV9pbnQsIHRhYmxlID0gbWljZV90YWJsZSkpDQpgYGANCg0KDQpgYGB7cn0NCmludGVyYWN0aW9uX21lYW5zID0gZ2V0X2VzdF9tZWFucyhtb2RlbCA9IG1pY2VfaW50LCB0YWJsZSA9IG1pY2VfdGFibGUpDQppbnRlcmFjdGlvbl9tZWFuc1siSSIsXSAtIGludGVyYWN0aW9uX21lYW5zWyJJSSIsXQ0KYGBgDQoNCmBgYHtyfQ0KYWRkaXRpdmVfbWVhbnMgPSBnZXRfZXN0X21lYW5zKG1vZGVsID0gbWljZV9hZGQsIHRhYmxlID0gbWljZV90YWJsZSkNCmFkZGl0aXZlX21lYW5zWyJJIixdIC0gYWRkaXRpdmVfbWVhbnNbIklJIixdDQpgYGANCg0KYGBge3J9DQprbml0cjo6a2FibGUoZ2V0X2VzdF9tZWFucyhtb2RlbCA9IG1pY2VfdHJlYXQsIHRhYmxlID0gbWljZV90YWJsZSkpDQpgYGANCg0KYGBge3J9DQprbml0cjo6a2FibGUoZ2V0X2VzdF9tZWFucyhtb2RlbCA9IG1pY2VfdG94aWMsIHRhYmxlID0gbWljZV90YWJsZSkpDQpgYGANCg0KYGBge3J9DQprbml0cjo6a2FibGUoZ2V0X2VzdF9tZWFucyhtb2RlbCA9IG1pY2VfbnVsbCwgdGFibGUgPSBtaWNlX3RhYmxlKSkNCmBgYA0KDQoNCiMgRXhlcmNpc2UgMw0KYGBge3J9DQpsaWJyYXJ5KGRhdGFyaXVtKQ0KbGlicmFyeShnZ3B1YnIpDQpsaWJyYXJ5KHJzdGF0aXgpDQpoZWFkKGhlYWRhY2hlKQ0KYGBgDQoNCmBgYHtyfQ0KbGV2ZWxzKGhlYWRhY2hlJHJpc2spDQpgYGANCg0KYGBge3J9DQpsZXZlbHMoaGVhZGFjaGUkdHJlYXRtZW50KQ0KYGBgDQoNCmBgYHtyfQ0KbGV2ZWxzKGhlYWRhY2hlJGdlbmRlcikNCmBgYA0KDQpgYGB7cn0NCnNldC5zZWVkKDE2MDQpDQpkYXRhKCJoZWFkYWNoZSIsIHBhY2thZ2UgPSAiZGF0YXJpdW0iKQ0KaGVhZGFjaGUgJT4lIHNhbXBsZV9uX2J5KGdlbmRlciwgcmlzaywgdHJlYXRtZW50LCBzaXplID0gMSkNCmBgYA0KSW4gdGhpcyBleGFtcGxlLCB0aGUgZWZmZWN0IG9mIHRoZSB0cmVhdG1lbnQgdHlwZXMgaXMgb3VyIGZvY2FsIHZhcmlhYmxlLCB0aGF0IGlzIG91ciBwcmltYXJ5IGNvbmNlcm4uIEl0IGlzIHRob3VnaHQgdGhhdCB0aGUgZWZmZWN0IG9mIHRyZWF0bWVudHMgd2lsbCBkZXBlbmQgb24gdHdvIG90aGVyIGZhY3RvcnMsIOKAnGdlbmRlcuKAnSBhbmQg4oCccmlza+KAnSBsZXZlbCBvZiBtaWdyYWluZSwgd2hpY2ggYXJlIGNhbGxlZCBtb2RlcmF0b3IgdmFyaWFibGVzLg0KDQoNCmBgYHtyfQ0KaGVhZGFjaGUgJT4lDQogIGdyb3VwX2J5KGdlbmRlciwgcmlzaywgdHJlYXRtZW50KSAlPiUNCiAgZ2V0X3N1bW1hcnlfc3RhdHMocGFpbl9zY29yZSwgdHlwZSA9ICJtZWFuX3NkIikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoYm94cGxvdGRibCkNCmdncGxvdChoZWFkYWNoZSwgYWVzKHggPSB0cmVhdG1lbnQsIHkgPSBwYWluX3Njb3JlLCBmaWxsID0gdHJlYXRtZW50KSkgKw0KICAgIGdlb21fYm94cGxvdCgpICsNCiAgICBnZW9tX2ppdHRlcihzaGFwZSA9IDE1LA0KICAgICAgICBjb2xvciA9ICJzdGVlbGJsdWUiLA0KICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcigwLjIxKSkgKw0KICAgdGhlbWVfbWluaW1hbCgpDQpgYGANCmBgYHtyfQ0KZ2dwbG90KGhlYWRhY2hlLCBhZXMoeCA9IGdlbmRlciwgeSA9IHBhaW5fc2NvcmUsIGZpbGwgPSBnZW5kZXIpKSArDQogICAgZ2VvbV9ib3hwbG90KCkgKw0KICAgIGdlb21faml0dGVyKHNoYXBlID0gMTUsDQogICAgICAgIGNvbG9yID0gInN0ZWVsYmx1ZSIsDQogICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKDAuMjEpKSArDQogICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChoZWFkYWNoZSwgYWVzKHggPSByaXNrLCB5ID0gcGFpbl9zY29yZSwgZmlsbCA9IHJpc2spKSArDQogICAgZ2VvbV9ib3hwbG90KCkgKw0KICAgIGdlb21faml0dGVyKHNoYXBlID0gMTUsDQogICAgICAgIGNvbG9yID0gInN0ZWVsYmx1ZSIsDQogICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKDAuMjEpKSArDQogICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7cn0NCmhlYWRhY2hlICU+JQ0KICBncm91cF9ieShnZW5kZXIsIHJpc2ssIHRyZWF0bWVudCkgJT4lDQogIGlkZW50aWZ5X291dGxpZXJzKHBhaW5fc2NvcmUpDQpgYGANCkl0IGNhbiBiZSBzZWVuIHRoYXQsIHRoZSBkYXRhIGNvbnRhaW4gb25lIGV4dHJlbWUgb3V0bGllciAoaWQgPSA1NywgZmVtYWxlIGF0IGhpZ2ggcmlzayBvZiBtaWdyYWluZSB0YWtpbmcgZHJ1ZyBYKQ0KDQpgYGB7cn0NCm1vZGVsICA8LSBsbShwYWluX3Njb3JlIH4gZ2VuZGVyKnJpc2sqdHJlYXRtZW50LCBkYXRhID0gaGVhZGFjaGUpDQojIENvbXB1dGUgU2hhcGlyby1XaWxrIHRlc3Qgb2Ygbm9ybWFsaXR5DQpzaGFwaXJvX3Rlc3QocmVzaWR1YWxzKG1vZGVsKSkNCmdncXFwbG90KHJlc2lkdWFscyhtb2RlbCkpDQpgYGANCkluIHRoZSBRUSBwbG90LCBhcyBhbGwgdGhlIHBvaW50cyBmYWxsIGFwcHJveGltYXRlbHkgYWxvbmcgdGhlIHJlZmVyZW5jZSBsaW5lLCB3ZSBjYW4gYXNzdW1lIG5vcm1hbGl0eS4gVGhpcyBjb25jbHVzaW9uIGlzIHN1cHBvcnRlZCBieSB0aGUgU2hhcGlyby1XaWxrIHRlc3QuIFRoZSBwLXZhbHVlIGlzIG5vdCBzaWduaWZpY2FudCAocCA9IDAuNCksIHNvIHdlIGNhbiBhc3N1bWUgbm9ybWFsaXR5Lg0KDQpgYGB7cn0NCmhlYWRhY2hlICU+JQ0KICBncm91cF9ieShnZW5kZXIsIHJpc2ssIHRyZWF0bWVudCkgJT4lDQogIHNoYXBpcm9fdGVzdChwYWluX3Njb3JlKQ0KYGBgDQpUaGUgcGFpbiBzY29yZXMgd2VyZSBub3JtYWxseSBkaXN0cmlidXRlZCAocCA+IDAuMDUpIGV4Y2VwdCBmb3Igb25lIGdyb3VwIChmZW1hbGUgYXQgaGlnaCByaXNrIG9mIG1pZ3JhaW5lIHRha2luZyBkcnVnIFgsIHAgPSAwLjAwODYpLCBhcyBhc3Nlc3NlZCBieSBTaGFwaXJvLVdpbGvigJlzIHRlc3Qgb2Ygbm9ybWFsaXR5Lg0KDQpgYGB7cn0NCmhlYWRhY2hlICU+JSBsZXZlbmVfdGVzdChwYWluX3Njb3JlIH4gZ2VuZGVyKnJpc2sqdHJlYXRtZW50KQ0KYGBgDQpUaGUgTGV2ZW5l4oCZcyB0ZXN0IGlzIG5vdCBzaWduaWZpY2FudCAocCA+IDAuMDUpLiBUaGVyZWZvcmUsIHdlIGNhbiBhc3N1bWUgdGhlIGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlcyBpbiB0aGUgZGlmZmVyZW50IGdyb3Vwcy4NCg0KDQpgYGB7cn0NCmdncXFwbG90KGhlYWRhY2hlLCAicGFpbl9zY29yZSIsIGdndGhlbWUgPSB0aGVtZV9idygpKSArDQogIGZhY2V0X2dyaWQoZ2VuZGVyICsgcmlzayB+IHRyZWF0bWVudCwgbGFiZWxsZXIgPSAibGFiZWxfYm90aCIpDQpgYGANCkFsbCB0aGUgcG9pbnRzIGZhbGwgYXBwcm94aW1hdGVseSBhbG9uZyB0aGUgcmVmZXJlbmNlIGxpbmUsIGV4Y2VwdCBmb3Igb25lIGdyb3VwIChmZW1hbGUgYXQgaGlnaCByaXNrIG9mIG1pZ3JhaW5lIHRha2luZyBkcnVnIFgpLCB3aGVyZSB3ZSBhbHJlYWR5IGlkZW50aWZpZWQgYW4gZXh0cmVtZSBvdXRsaWVyLg0KDQoNCmBgYHtyfQ0KcmVzLmFvdiA8LSBoZWFkYWNoZSAlPiUgYW5vdmFfdGVzdChwYWluX3Njb3JlIH4gZ2VuZGVyKnJpc2sqdHJlYXRtZW50KQ0KcmVzLmFvdg0KYGBgDQpUaGVyZSB3YXMgYSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IHRocmVlLXdheSBpbnRlcmFjdGlvbiBiZXR3ZWVuIGdlbmRlciwgcmlzayBhbmQgdHJlYXRtZW50LCBGKDIsIDYwKSA9IDcuNDEsIHAgPSAwLjAwMS4NCg0KYGBge3J9DQojIEdyb3VwIHRoZSBkYXRhIGJ5IGdlbmRlciBhbmQgDQojIGZpdCBzaW1wbGUgdHdvLXdheSBpbnRlcmFjdGlvbiANCm1vZGVsICA8LSBsbShwYWluX3Njb3JlIH4gZ2VuZGVyKnJpc2sqdHJlYXRtZW50LCBkYXRhID0gaGVhZGFjaGUpDQpoZWFkYWNoZSAlPiUNCiAgZ3JvdXBfYnkoZ2VuZGVyKSAlPiUNCiAgYW5vdmFfdGVzdChwYWluX3Njb3JlIH4gcmlzayp0cmVhdG1lbnQsIGVycm9yID0gbW9kZWwpDQpgYGANClRoZXJlIHdhcyBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgc2ltcGxlIHR3by13YXkgaW50ZXJhY3Rpb24gYmV0d2VlbiByaXNrIGFuZCB0cmVhdG1lbnQgKHJpc2s6dHJlYXRtZW50KSBmb3IgbWFsZXMsIEYoMiwgNjApID0gNS4yNSwgcCA9IDAuMDA4LCBidXQgbm90IGZvciBmZW1hbGVzLCBGKDIsIDYwKSA9IDIuODcsIHAgPSAwLjA2NS4NCg0KRm9yIG1hbGVzLCB0aGlzIHJlc3VsdCBzdWdnZXN0cyB0aGF0IHRoZSBlZmZlY3Qgb2YgdHJlYXRtZW50IG9uIOKAnHBhaW5fc2NvcmXigJ0gZGVwZW5kcyBvbiBvbmXigJlzIOKAnHJpc2vigJ0gb2YgbWlncmFpbmUuIEluIG90aGVyIHdvcmRzLCB0aGUgcmlzayBtb2RlcmF0ZXMgdGhlIGVmZmVjdCBvZiB0aGUgdHlwZSBvZiB0cmVhdG1lbnQgb24gcGFpbl9zY29yZS4NCg0KDQpgYGB7cn0NCiMgR3JvdXAgdGhlIGRhdGEgYnkgZ2VuZGVyIGFuZCByaXNrLCBhbmQgZml0ICBhbm92YQ0KdHJlYXRtZW50LmVmZmVjdCA8LSBoZWFkYWNoZSAlPiUNCiAgZ3JvdXBfYnkoZ2VuZGVyLCByaXNrKSAlPiUNCiAgYW5vdmFfdGVzdChwYWluX3Njb3JlIH4gdHJlYXRtZW50LCBlcnJvciA9IG1vZGVsKQ0KdHJlYXRtZW50LmVmZmVjdCAlPiUgZmlsdGVyKGdlbmRlciA9PSAibWFsZSIpDQpgYGANClRoZXJlIHdhcyBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgc2ltcGxlIHNpbXBsZSBtYWluIGVmZmVjdCBvZiB0cmVhdG1lbnQgZm9yIG1hbGVzIGF0IGhpZ2ggcmlzayBvZiBtaWdyYWluZSwgRigyLCA2MCkgPSAxNC44LCBwIDwgMC4wMDAxKSwgYnV0IG5vdCBmb3IgbWFsZXMgYXQgbG93IHJpc2sgb2YgbWlncmFpbmUsIEYoMiwgNjApID0gMC42NiwgcCA9IDAuNTIxLg0KDQpUaGlzIGFuYWx5c2lzIGluZGljYXRlcyB0aGF0LCB0aGUgdHlwZSBvZiB0cmVhdG1lbnQgdGFrZW4gaGFzIGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBlZmZlY3Qgb24gcGFpbl9zY29yZSBpbiBtYWxlcyB3aG8gYXJlIGF0IGhpZ2ggcmlzay4NCg0KSW4gb3RoZXIgd29yZHMsIHRoZSBtZWFuIHBhaW5fc2NvcmUgaW4gdGhlIHRyZWF0bWVudCBYLCBZIGFuZCBaIGdyb3VwcyB3YXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBmb3IgbWFsZXMgd2hvIGF0IGhpZ2ggcmlzaywgYnV0IG5vdCBmb3IgbWFsZXMgYXQgbG93IHJpc2suDQoNCmBgYHtyfQ0KIyBQYWlyd2lzZSBjb21wYXJpc29ucw0KbGlicmFyeShlbW1lYW5zKQ0KcHdjIDwtIGhlYWRhY2hlICU+JQ0KICBncm91cF9ieShnZW5kZXIsIHJpc2spICU+JQ0KICBlbW1lYW5zX3Rlc3QocGFpbl9zY29yZSB+IHRyZWF0bWVudCwgcC5hZGp1c3QubWV0aG9kID0gImJvbmZlcnJvbmkiKSAlPiUNCiAgc2VsZWN0KC1kZiwgLXN0YXRpc3RpYywgLXApICMgUmVtb3ZlIGRldGFpbHMNCiMgU2hvdyBjb21wYXJpc29uIHJlc3VsdHMgZm9yIG1hbGUgYXQgaGlnaCByaXNrDQpwd2MgJT4lIGZpbHRlcihnZW5kZXIgPT0gIm1hbGUiLCByaXNrID09ICJoaWdoIikNCmBgYA0KDQpgYGB7cn0NCiMgRXN0aW1hdGVkIG1hcmdpbmFsIG1lYW5zIChpLmUuIGFkanVzdGVkIG1lYW5zKSANCiMgd2l0aCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbA0KZ2V0X2VtbWVhbnMocHdjKSAlPiUgZmlsdGVyKGdlbmRlciA9PSAibWFsZSIsIHJpc2sgPT0gImhpZ2giKQ0KYGBgDQpJbiB0aGUgcGFpcndpc2UgY29tcGFyaXNvbnMgdGFibGUgYWJvdmUsIHdlIGFyZSBpbnRlcmVzdGVkIG9ubHkgaW4gdGhlIHNpbXBsZSBzaW1wbGUgY29tcGFyaXNvbnMgZm9yIG1hbGVzIGF0IGEgaGlnaCByaXNrIG9mIGEgbWlncmFpbmUgaGVhZGFjaGUuIEluIG91ciBleGFtcGxlLCB0aGVyZSBhcmUgdGhyZWUgcG9zc2libGUgY29tYmluYXRpb25zIG9mIGdyb3VwIGRpZmZlcmVuY2VzLg==