devtools::load_all() # if using the rproject dowloaded from the slides
# source("utils-glm.R") # if using a standard setup
library(here)
library(tidyr) # for data manipulation
library(dplyr) # for data manipulation
library(ggplot2) # plotting
library(car) # general utilities
library(effects) # for extracting and plotting effects 
library(emmeans) # for marginal effects
dat <- read.csv(here("data", "nwords.csv"))

Overview

This dataset nwords.csv represent a developmental psychology study investigating the factors that influence children language development. The dataset includes information about the number of words in a a language task, and some potential predictors: caregiver behavior, socioeconomic status, amount of time spent with the child during a week and the presence of a baby-sitter or not.

  • child: numeric variable representing the child ID number.
  • timebonding: numeric variable representing the average hours per week of child-parent activities
  • nwords: numeric variable representing the number of words produced by the child in a language task.
  • caregiving: numeric variable representing the caregiver’s behavior in a parent-child interaction task, measured on a scale from 0 to 10
  • ses: categorical variable representing the family socioeconomic status, with three levels: “Low”, “Middle”, and “High”
  1. Importing data and overall check
    • think about factors levels, scale of the numerical variables, etc.
  2. Exploratory data analysis of predictors and the relationships between predictors and the number of words
  3. Model fitting with glm() and poisson family starting from additive models and then adding the interactions.
  4. Model diagnostic of the chosen model
    • overdispersion
    • residuals
    • outliers and influential points
  5. Interpreting the effects and plotting
  6. Fit a quasi-poisson and negative binomial version of the chosen model, and check how parameters are affected

1. Importing data and overall check

dat <- read.csv("data/nwords.csv")
str(dat)
## 'data.frame':    150 obs. of  6 variables:
##  $ id         : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ timebonding: int  28 20 26 23 6 31 23 32 20 13 ...
##  $ caregiving : int  9 8 10 10 6 13 11 10 8 5 ...
##  $ ses        : chr  "low" "low" "low" "low" ...
##  $ babysitter : chr  "no" "yes" "yes" "yes" ...
##  $ nwords     : int  18 9 31 17 14 47 39 31 24 5 ...

Check for NA values:

count_na(dat) # from utils-glm.R
##          id timebonding  caregiving         ses  babysitter      nwords 
##           0           0           0           0           0           0
# equivalent to sapply(dat, function(x) sum(is.na(x)))

Everything seems good, we do not have NA values.

Let’s convert categorical variables into factor setting the appropriate order:

  • ses: low, middle, high
  • babysitter: no, yes
dat$ses <- factor(dat$ses, levels = c("low", "middle", "high"))
dat$babysitter <- factor(dat$babysitter, levels = c("no", "yes"))

levels(dat$ses)
## [1] "low"    "middle" "high"
levels(dat$babysitter)
## [1] "no"  "yes"

timebonding and caregiving are the two numerical predictors. Given that we are going to fit main effects and interactions and we want to interpret the intercept and test the interaction on a meaningful point, we create two centered versions of these variables:

dat$timebonding0 <- dat$timebonding - mean(dat$timebonding)
dat$caregiving0 <- dat$caregiving - mean(dat$caregiving)

Then we can see what happen to the variables:

cols <- grepl("timebonding|caregiving", names(dat))
lapply(dat[, cols], function(x) round(c(mean = mean(x), sd = sd(x)), 3))
## $timebonding
##   mean     sd 
## 15.953  8.345 
## 
## $caregiving
##  mean    sd 
## 5.867 2.532 
## 
## $timebonding0
##  mean    sd 
## 0.000 8.345 
## 
## $caregiving0
##  mean    sd 
## 0.000 2.532

2. Exploratory data analysis

summary(dat)
##        id          timebonding      caregiving         ses     babysitter
##  Min.   :  1.00   Min.   : 0.00   Min.   : 0.000   low   :45   no :83    
##  1st Qu.: 38.25   1st Qu.:10.00   1st Qu.: 4.000   middle:75   yes:67    
##  Median : 75.50   Median :16.00   Median : 6.000   high  :30             
##  Mean   : 75.50   Mean   :15.95   Mean   : 5.867                         
##  3rd Qu.:112.75   3rd Qu.:22.00   3rd Qu.: 8.000                         
##  Max.   :150.00   Max.   :36.00   Max.   :13.000                         
##      nwords       timebonding0        caregiving0     
##  Min.   : 0.00   Min.   :-15.95333   Min.   :-5.8667  
##  1st Qu.:10.00   1st Qu.: -5.95333   1st Qu.:-1.8667  
##  Median :15.50   Median :  0.04667   Median : 0.1333  
##  Mean   :18.35   Mean   :  0.00000   Mean   : 0.0000  
##  3rd Qu.:24.00   3rd Qu.:  6.04667   3rd Qu.: 2.1333  
##  Max.   :52.00   Max.   : 20.04667   Max.   : 7.1333

Let’s do some plotting of predictors:

par(mfrow = c(2,2))
hist(dat$timebonding)
hist(dat$caregiving)
barplot(table(dat$babysitter))
barplot(table(dat$ses))

Comments?

Also the response variable:

hist(dat$nwords, probability = TRUE)
lines(density(dat$nwords), col = "salmon", lwd = 2)

Let’s plot the theoretical distributions, Poisson and Gaussian:

m <- mean(dat$nwords)
s <- sd(dat$nwords)
xs <- seq(0, 50, 1)

hist(dat$nwords, probability = TRUE, xlim = c(-10, 50), ylim = c(0, 0.1))
curve(dnorm(x, m, s), add = TRUE, col = "green", lwd = 2)
lines(xs, dpois(xs, m), col = "red", lwd = 2)
lines(xs, dpois(xs, m), col = "red", lwd = 2)

Comments?

Let’s plot some bivariate distributions:

# caregiving ~ timebonding
r <- cor(dat$timebonding, dat$caregiving)
plot(dat$timebonding, dat$caregiving, pch = 19)
abline(lm(dat$caregiving ~ dat$timebonding), col = "red", lwd = 2)
text(30, 2, label = paste("r =", round(r, 2)))

par(mfrow = c(1,2))
boxplot(caregiving ~ ses, data = dat)
boxplot(timebonding ~ ses, data = dat)

mosaicplot(table(dat$ses, dat$babysitter))

# or
barplot(table(dat$babysitter, dat$ses), col = c("red", "green"), beside = TRUE)
legend(7,45, legend = c("no", "yes"), fill = c("red", "green"))

par(mfrow = c(1,2))
boxplot(caregiving ~ babysitter, data = dat)
boxplot(timebonding ~ babysitter, data = dat)

Then the relationships with the nwords variable:

par(mfrow = c(2,2))
plot(dat$timebonding, dat$nwords, pch = 19)
plot(dat$caregiving, dat$nwords, pch = 19)
boxplot(dat$nwords ~ dat$ses)
boxplot(dat$nwords ~ dat$babysitter)

Comments?

Finally some interactions plot (with lm):

par(mfrow = c(2,1))
colors <- c(low = "red", middle = "blue", high = "green")
plot(dat$timebonding, dat$nwords, col = colors[dat$ses], pch = 19)
lms <- lapply(split(dat, dat$ses), function(x) lm(nwords ~ timebonding, data = x))
lapply(1:length(lms), function(i) abline(lms[[i]], col = colors[i], lwd = 2))
## [[1]]
## NULL
## 
## [[2]]
## NULL
## 
## [[3]]
## NULL
plot(dat$caregiving, dat$nwords, col = colors[dat$ses], pch = 19)
lms <- lapply(split(dat, dat$ses), function(x) lm(nwords ~ caregiving, data = x))
lapply(1:length(lms), function(i) abline(lms[[i]], col = colors[i], lwd = 2))

## [[1]]
## NULL
## 
## [[2]]
## NULL
## 
## [[3]]
## NULL
par(mfrow = c(2,1))
colors <- c(no = "orange", yes = "purple")

plot(dat$timebonding, dat$nwords, col = colors[dat$babysitter], pch = 19)
lms <- lapply(split(dat, dat$babysitter), function(x) lm(nwords ~ timebonding, data = x))
lapply(1:length(lms), function(i) abline(lms[[i]], col = colors[i], lwd = 2))
## [[1]]
## NULL
## 
## [[2]]
## NULL
plot(dat$caregiving, dat$nwords, col = colors[dat$babysitter], pch = 19)
lms <- lapply(split(dat, dat$babysitter), function(x) lm(nwords ~ caregiving, data = x))
lapply(1:length(lms), function(i) abline(lms[[i]], col = colors[i], lwd = 2))

## [[1]]
## NULL
## 
## [[2]]
## NULL

3. Model fitting with glm() and poisson

Let’s start by using an additive model:

fit <- glm(nwords ~ timebonding + caregiving + babysitter + ses, family = poisson(link = "log"), data = dat)
summary(fit)
## 
## Call:
## glm(formula = nwords ~ timebonding + caregiving + babysitter + 
##     ses, family = poisson(link = "log"), data = dat)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -5.0343  -1.4787  -0.2251   1.0048   5.2483  
## 
## Coefficients:
##                Estimate Std. Error z value Pr(>|z|)    
## (Intercept)    1.872174   0.098615  18.985  < 2e-16 ***
## timebonding    0.032716   0.003387   9.660  < 2e-16 ***
## caregiving     0.071359   0.013189   5.410 6.29e-08 ***
## babysitteryes -0.054370   0.042762  -1.271    0.204    
## sesmiddle      0.039277   0.050956   0.771    0.441    
## seshigh        0.081874   0.071100   1.152    0.250    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 1030.28  on 149  degrees of freedom
## Residual deviance:  573.72  on 144  degrees of freedom
## AIC: 1264.9
## 
## Number of Fisher Scoring iterations: 5

And always plotting before anything else:

plot(allEffects(fit))

Comments? How could you describe the results? Something different from the descriptive statistics?

car::residualPlots(fit)

##             Test stat Pr(>|Test stat|)
## timebonding    0.0352           0.8512
## caregiving     1.6690           0.1964
## babysitter                            
## ses

Comments? Are we missing something?

dat |> 
    ggplot(aes(x = timebonding, y = nwords, color = ses)) +
    geom_point() +
    stat_smooth(method = "glm", method.args = list(family = poisson()), se = FALSE)

dat |> 
    ggplot(aes(x = caregiving, y = nwords, color = ses)) +
    geom_point() +
    stat_smooth(method = "glm", method.args = list(family = poisson()), se = FALSE)

dat |> 
    ggplot(aes(x = timebonding, y = nwords, color = babysitter)) +
    geom_point() +
    stat_smooth(method = "glm", method.args = list(family = poisson()), se = FALSE)

dat |> 
    ggplot(aes(x = caregiving, y = nwords, color = babysitter)) +
    geom_point() +
    stat_smooth(method = "glm", method.args = list(family = poisson()), se = FALSE)

Let’s add some interactions:

fit2 <- glm(nwords ~ timebonding*ses + caregiving*ses + timebonding*babysitter + caregiving*babysitter, family = poisson(link = "log"), data = dat)
summary(fit2)
## 
## Call:
## glm(formula = nwords ~ timebonding * ses + caregiving * ses + 
##     timebonding * babysitter + caregiving * babysitter, family = poisson(link = "log"), 
##     data = dat)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -5.1745  -1.3330  -0.1485   1.0867   5.2217  
## 
## Coefficients:
##                            Estimate Std. Error z value Pr(>|z|)    
## (Intercept)                2.247867   0.155725  14.435  < 2e-16 ***
## timebonding                0.016213   0.005788   2.801 0.005091 ** 
## sesmiddle                 -0.350012   0.178986  -1.956 0.050520 .  
## seshigh                   -0.434708   0.215428  -2.018 0.043604 *  
## caregiving                 0.070537   0.020455   3.448 0.000564 ***
## babysitteryes             -0.147848   0.129801  -1.139 0.254687    
## timebonding:sesmiddle      0.010292   0.007222   1.425 0.154123    
## timebonding:seshigh        0.036204   0.014669   2.468 0.013583 *  
## sesmiddle:caregiving       0.020211   0.027938   0.723 0.469420    
## seshigh:caregiving        -0.018795   0.052789  -0.356 0.721808    
## timebonding:babysitteryes  0.021889   0.007805   2.805 0.005038 ** 
## caregiving:babysitteryes  -0.048280   0.027164  -1.777 0.075507 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 1030.28  on 149  degrees of freedom
## Residual deviance:  545.27  on 138  degrees of freedom
## AIC: 1248.5
## 
## Number of Fisher Scoring iterations: 5
plot(allEffects(fit2))

4. Model fitting with MASS::glm.nb()

There is still evidence for overdispersion, even after including all predictors and a series of interactions. Let’s assume that this is our most complex model, we need to take into account the overdispersion:

performance::check_overdispersion(fit2)
## # Overdispersion test
## 
##        dispersion ratio =   3.888
##   Pearson's Chi-Squared = 536.486
##                 p-value = < 0.001
fit3 <- MASS::glm.nb(nwords ~ timebonding*ses + caregiving*ses + timebonding*babysitter + caregiving*babysitter, data = dat)

summary(fit3)
## 
## Call:
## MASS::glm.nb(formula = nwords ~ timebonding * ses + caregiving * 
##     ses + timebonding * babysitter + caregiving * babysitter, 
##     data = dat, init.theta = 6.373674959, link = log)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -3.1915  -0.7045  -0.0837   0.5252   2.3301  
## 
## Coefficients:
##                            Estimate Std. Error z value Pr(>|z|)    
## (Intercept)                2.338972   0.327310   7.146 8.93e-13 ***
## timebonding                0.015621   0.012609   1.239    0.215    
## sesmiddle                 -0.387638   0.363066  -1.068    0.286    
## seshigh                   -0.449211   0.407454  -1.102    0.270    
## caregiving                 0.060868   0.046107   1.320    0.187    
## babysitteryes             -0.214001   0.230980  -0.926    0.354    
## timebonding:sesmiddle      0.007400   0.015144   0.489    0.625    
## timebonding:seshigh        0.032108   0.026562   1.209    0.227    
## sesmiddle:caregiving       0.031846   0.058583   0.544    0.587    
## seshigh:caregiving        -0.008505   0.094220  -0.090    0.928    
## timebonding:babysitteryes  0.021072   0.015598   1.351    0.177    
## caregiving:babysitteryes  -0.036492   0.053785  -0.678    0.497    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for Negative Binomial(6.3737) family taken to be 1)
## 
##     Null deviance: 289.68  on 149  degrees of freedom
## Residual deviance: 161.78  on 138  degrees of freedom
## AIC: 1057.4
## 
## Number of Fisher Scoring iterations: 1
## 
## 
##               Theta:  6.37 
##           Std. Err.:  1.06 
## 
##  2 x log-likelihood:  -1031.391

Now overdispersion is taken into account and standard errors are larger:

car::compareCoefs(fit2, fit3)
## Calls:
## 1: glm(formula = nwords ~ timebonding * ses + caregiving * ses + 
##   timebonding * babysitter + caregiving * babysitter, family = poisson(link = 
##   "log"), data = dat)
## 2: MASS::glm.nb(formula = nwords ~ timebonding * ses + caregiving * ses + 
##   timebonding * babysitter + caregiving * babysitter, data = dat, init.theta =
##    6.373674959, link = log)
## 
##                           Model 1 Model 2
## (Intercept)                 2.248   2.339
## SE                          0.156   0.327
##                                          
## timebonding               0.01621 0.01562
## SE                        0.00579 0.01261
##                                          
## sesmiddle                  -0.350  -0.388
## SE                          0.179   0.363
##                                          
## seshigh                    -0.435  -0.449
## SE                          0.215   0.407
##                                          
## caregiving                 0.0705  0.0609
## SE                         0.0205  0.0461
##                                          
## babysitteryes              -0.148  -0.214
## SE                          0.130   0.231
##                                          
## timebonding:sesmiddle     0.01029 0.00740
## SE                        0.00722 0.01514
##                                          
## timebonding:seshigh        0.0362  0.0321
## SE                         0.0147  0.0266
##                                          
## sesmiddle:caregiving       0.0202  0.0318
## SE                         0.0279  0.0586
##                                          
## seshigh:caregiving        -0.0188 -0.0085
## SE                         0.0528  0.0942
##                                          
## timebonding:babysitteryes  0.0219  0.0211
## SE                         0.0078  0.0156
##                                          
## caregiving:babysitteryes  -0.0483 -0.0365
## SE                         0.0272  0.0538
## 
# test statistics for the poisson model

data.frame(
    poisson = fit2$coefficients/sqrt(diag(vcov(fit2))),
    negative_binomial = fit3$coefficients/sqrt(diag(vcov(fit3)))
)
##                              poisson negative_binomial
## (Intercept)               14.4348724        7.14604731
## timebonding                2.8011983        1.23888446
## sesmiddle                 -1.9555334       -1.06767938
## seshigh                   -2.0178825       -1.10248163
## caregiving                 3.4484763        1.32016562
## babysitteryes             -1.1390397       -0.92649153
## timebonding:sesmiddle      1.4251192        0.48865248
## timebonding:seshigh        2.4681042        1.20876478
## sesmiddle:caregiving       0.7234224        0.54360423
## seshigh:caregiving        -0.3560440       -0.09026367
## timebonding:babysitteryes  2.8045832        1.35099433
## caregiving:babysitteryes  -1.7773722       -0.67847958
LS0tDQp0aXRsZTogIlN0YXRpc3RpY2FsIE1ldGhvZHMgYW5kIERhdGEgQW5hbHlzaXMgaW4gRGV2ZWxvcG1lbnRhbCBQc3ljaG9sb2d5Ig0Kc3VidGl0bGU6ICJMYWIgMTEiDQphdXRob3I6ICJGaWxpcHBvIEdhbWJhcm90YSINCm91dHB1dDogDQogICAgaHRtbF9kb2N1bWVudDoNCiAgICAgICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgICAgIHRvYzogdHJ1ZQ0KICAgICAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KZGF0ZTogIlVwZGF0ZWQgb24gYHIgU3lzLkRhdGUoKWAiDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBkZXYgPSAic3ZnIikNCmBgYA0KDQpgYGB7ciBwYWNrYWdlcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRldnRvb2xzOjpsb2FkX2FsbCgpICMgaWYgdXNpbmcgdGhlIHJwcm9qZWN0IGRvd2xvYWRlZCBmcm9tIHRoZSBzbGlkZXMNCiMgc291cmNlKCJ1dGlscy1nbG0uUiIpICMgaWYgdXNpbmcgYSBzdGFuZGFyZCBzZXR1cA0KbGlicmFyeShoZXJlKQ0KbGlicmFyeSh0aWR5cikgIyBmb3IgZGF0YSBtYW5pcHVsYXRpb24NCmxpYnJhcnkoZHBseXIpICMgZm9yIGRhdGEgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KGdncGxvdDIpICMgcGxvdHRpbmcNCmxpYnJhcnkoY2FyKSAjIGdlbmVyYWwgdXRpbGl0aWVzDQpsaWJyYXJ5KGVmZmVjdHMpICMgZm9yIGV4dHJhY3RpbmcgYW5kIHBsb3R0aW5nIGVmZmVjdHMgDQpsaWJyYXJ5KGVtbWVhbnMpICMgZm9yIG1hcmdpbmFsIGVmZmVjdHMNCmBgYA0KDQpgYGB7ciBvcHRpb25zLCBpbmNsdWRlID0gRkFMU0V9DQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNSkpDQpgYGANCg0KYGBge3Igc2NyaXB0LCBlY2hvPUZBTFNFfQ0KIyMgTGluayBpbiBHaXRodWIgcmVwbw0KZG93bmxvYWR0aGlzOjpkb3dubG9hZF9saW5rKA0KICBsaW5rID0gZG93bmxvYWRfbGluaygiaHR0cHM6Ly9naXRodWIuY29tL3N0YXQtdGVhY2hpbmcvU01EQS0yMDIzL2Jsb2IvbWFzdGVyL2xhYnMvbGFiMTEuUiIpLA0KICBidXR0b25fbGFiZWwgPSAiRG93bmxvYWQgdGhlIFIgc2NyaXB0IiwNCiAgYnV0dG9uX3R5cGUgPSAiZGFuZ2VyIiwNCiAgaGFzX2ljb24gPSBUUlVFLA0KICBpY29uID0gImZhIGZhLXNhdmUiLA0KICBzZWxmX2NvbnRhaW5lZCA9IEZBTFNFDQopDQpgYGANCg0KYGBge3IgbG9hZGluZy1kYXRhfQ0KZGF0IDwtIHJlYWQuY3N2KGhlcmUoImRhdGEiLCAibndvcmRzLmNzdiIpKQ0KYGBgDQoNCiMgT3ZlcnZpZXcNCg0KVGhpcyBkYXRhc2V0IGBud29yZHMuY3N2YCByZXByZXNlbnQgYSBkZXZlbG9wbWVudGFsIHBzeWNob2xvZ3kgc3R1ZHkgaW52ZXN0aWdhdGluZyB0aGUgZmFjdG9ycyB0aGF0IGluZmx1ZW5jZSBjaGlsZHJlbiBsYW5ndWFnZSBkZXZlbG9wbWVudC4gVGhlIGRhdGFzZXQgaW5jbHVkZXMgaW5mb3JtYXRpb24gYWJvdXQgdGhlIG51bWJlciBvZiB3b3JkcyBpbiBhIGEgbGFuZ3VhZ2UgdGFzaywgYW5kIHNvbWUgcG90ZW50aWFsIHByZWRpY3RvcnM6IGNhcmVnaXZlciBiZWhhdmlvciwgc29jaW9lY29ub21pYyBzdGF0dXMsIGFtb3VudCBvZiB0aW1lIHNwZW50IHdpdGggdGhlIGNoaWxkIGR1cmluZyBhIHdlZWsgYW5kIHRoZSBwcmVzZW5jZSBvZiBhIGJhYnktc2l0dGVyIG9yIG5vdC4NCg0KLSBgY2hpbGRgOiBudW1lcmljIHZhcmlhYmxlIHJlcHJlc2VudGluZyB0aGUgY2hpbGQgSUQgbnVtYmVyLg0KLSBgdGltZWJvbmRpbmdgOiBudW1lcmljIHZhcmlhYmxlIHJlcHJlc2VudGluZyB0aGUgYXZlcmFnZSBob3VycyBwZXIgd2VlayBvZiBjaGlsZC1wYXJlbnQgYWN0aXZpdGllcw0KLSBgbndvcmRzYDogbnVtZXJpYyB2YXJpYWJsZSByZXByZXNlbnRpbmcgdGhlIG51bWJlciBvZiB3b3JkcyBwcm9kdWNlZCBieSB0aGUgY2hpbGQgaW4gYSBsYW5ndWFnZSB0YXNrLg0KLSBgY2FyZWdpdmluZ2A6IG51bWVyaWMgdmFyaWFibGUgcmVwcmVzZW50aW5nIHRoZSBjYXJlZ2l2ZXIncyBiZWhhdmlvciBpbiBhIHBhcmVudC1jaGlsZCBpbnRlcmFjdGlvbiB0YXNrLCBtZWFzdXJlZCBvbiBhIHNjYWxlIGZyb20gMCB0byAxMA0KLSBgc2VzYDogY2F0ZWdvcmljYWwgdmFyaWFibGUgcmVwcmVzZW50aW5nIHRoZSBmYW1pbHkgc29jaW9lY29ub21pYyBzdGF0dXMsIHdpdGggdGhyZWUgbGV2ZWxzOiAiTG93IiwgIk1pZGRsZSIsIGFuZCAiSGlnaCINCg0KMS4gSW1wb3J0aW5nIGRhdGEgYW5kIG92ZXJhbGwgY2hlY2sNCiAgICAtIHRoaW5rIGFib3V0IGZhY3RvcnMgbGV2ZWxzLCBzY2FsZSBvZiB0aGUgbnVtZXJpY2FsIHZhcmlhYmxlcywgZXRjLg0KMi4gRXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyBvZiBwcmVkaWN0b3JzIGFuZCB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHByZWRpY3RvcnMgYW5kIHRoZSBudW1iZXIgb2Ygd29yZHMNCjMuIE1vZGVsIGZpdHRpbmcgd2l0aCBgZ2xtKClgIGFuZCBgcG9pc3NvbmAgZmFtaWx5IHN0YXJ0aW5nIGZyb20gYWRkaXRpdmUgbW9kZWxzIGFuZCB0aGVuIGFkZGluZyB0aGUgaW50ZXJhY3Rpb25zLg0KNC4gTW9kZWwgZGlhZ25vc3RpYyBvZiB0aGUgY2hvc2VuIG1vZGVsDQogICAgLSBvdmVyZGlzcGVyc2lvbg0KICAgIC0gcmVzaWR1YWxzDQogICAgLSBvdXRsaWVycyBhbmQgaW5mbHVlbnRpYWwgcG9pbnRzDQo1LiBJbnRlcnByZXRpbmcgdGhlIGVmZmVjdHMgYW5kIHBsb3R0aW5nDQo2LiBGaXQgYSBxdWFzaS1wb2lzc29uIGFuZCBuZWdhdGl2ZSBiaW5vbWlhbCB2ZXJzaW9uIG9mIHRoZSBjaG9zZW4gbW9kZWwsIGFuZCBjaGVjayBob3cgcGFyYW1ldGVycyBhcmUgYWZmZWN0ZWQNCg0KIyAxLiBJbXBvcnRpbmcgZGF0YSBhbmQgb3ZlcmFsbCBjaGVjaw0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KZGF0IDwtIHJlYWQuY3N2KCJkYXRhL253b3Jkcy5jc3YiKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RyKGRhdCkNCmBgYA0KDQpDaGVjayBmb3IgYE5BYCB2YWx1ZXM6DQoNCmBgYHtyfQ0KY291bnRfbmEoZGF0KSAjIGZyb20gdXRpbHMtZ2xtLlINCg0KIyBlcXVpdmFsZW50IHRvIHNhcHBseShkYXQsIGZ1bmN0aW9uKHgpIHN1bShpcy5uYSh4KSkpDQpgYGANCg0KRXZlcnl0aGluZyBzZWVtcyBnb29kLCB3ZSBkbyBub3QgaGF2ZSBgTkFgIHZhbHVlcy4NCg0KTGV0J3MgY29udmVydCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW50byBmYWN0b3Igc2V0dGluZyB0aGUgYXBwcm9wcmlhdGUgb3JkZXI6DQoNCi0gYHNlc2A6IGxvdywgbWlkZGxlLCBoaWdoDQotIGBiYWJ5c2l0dGVyYDogbm8sIHllcw0KDQpgYGB7cn0NCmRhdCRzZXMgPC0gZmFjdG9yKGRhdCRzZXMsIGxldmVscyA9IGMoImxvdyIsICJtaWRkbGUiLCAiaGlnaCIpKQ0KZGF0JGJhYnlzaXR0ZXIgPC0gZmFjdG9yKGRhdCRiYWJ5c2l0dGVyLCBsZXZlbHMgPSBjKCJubyIsICJ5ZXMiKSkNCg0KbGV2ZWxzKGRhdCRzZXMpDQpsZXZlbHMoZGF0JGJhYnlzaXR0ZXIpDQpgYGANCg0KYHRpbWVib25kaW5nYCBhbmQgYGNhcmVnaXZpbmdgIGFyZSB0aGUgdHdvIG51bWVyaWNhbCBwcmVkaWN0b3JzLiBHaXZlbiB0aGF0IHdlIGFyZSBnb2luZyB0byBmaXQgbWFpbiBlZmZlY3RzIGFuZCBpbnRlcmFjdGlvbnMgYW5kIHdlIHdhbnQgdG8gaW50ZXJwcmV0IHRoZSBpbnRlcmNlcHQgYW5kIHRlc3QgdGhlIGludGVyYWN0aW9uIG9uIGEgbWVhbmluZ2Z1bCBwb2ludCwgd2UgY3JlYXRlIHR3byBjZW50ZXJlZCB2ZXJzaW9ucyBvZiB0aGVzZSB2YXJpYWJsZXM6DQoNCmBgYHtyfQ0KZGF0JHRpbWVib25kaW5nMCA8LSBkYXQkdGltZWJvbmRpbmcgLSBtZWFuKGRhdCR0aW1lYm9uZGluZykNCmRhdCRjYXJlZ2l2aW5nMCA8LSBkYXQkY2FyZWdpdmluZyAtIG1lYW4oZGF0JGNhcmVnaXZpbmcpDQpgYGANCg0KVGhlbiB3ZSBjYW4gc2VlIHdoYXQgaGFwcGVuIHRvIHRoZSB2YXJpYWJsZXM6DQoNCmBgYHtyfQ0KY29scyA8LSBncmVwbCgidGltZWJvbmRpbmd8Y2FyZWdpdmluZyIsIG5hbWVzKGRhdCkpDQpsYXBwbHkoZGF0WywgY29sc10sIGZ1bmN0aW9uKHgpIHJvdW5kKGMobWVhbiA9IG1lYW4oeCksIHNkID0gc2QoeCkpLCAzKSkNCmBgYA0KDQojIDIuIEV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMNCg0KYGBge3J9DQpzdW1tYXJ5KGRhdCkNCmBgYA0KDQpMZXQncyBkbyBzb21lIHBsb3R0aW5nIG9mIHByZWRpY3RvcnM6DQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLDIpKQ0KaGlzdChkYXQkdGltZWJvbmRpbmcpDQpoaXN0KGRhdCRjYXJlZ2l2aW5nKQ0KYmFycGxvdCh0YWJsZShkYXQkYmFieXNpdHRlcikpDQpiYXJwbG90KHRhYmxlKGRhdCRzZXMpKQ0KYGBgDQoNCkNvbW1lbnRzPw0KDQpBbHNvIHRoZSByZXNwb25zZSB2YXJpYWJsZToNCg0KYGBge3J9DQpoaXN0KGRhdCRud29yZHMsIHByb2JhYmlsaXR5ID0gVFJVRSkNCmxpbmVzKGRlbnNpdHkoZGF0JG53b3JkcyksIGNvbCA9ICJzYWxtb24iLCBsd2QgPSAyKQ0KYGBgDQoNCkxldCdzIHBsb3QgdGhlIHRoZW9yZXRpY2FsIGRpc3RyaWJ1dGlvbnMsIFBvaXNzb24gYW5kIEdhdXNzaWFuOg0KDQpgYGB7cn0NCm0gPC0gbWVhbihkYXQkbndvcmRzKQ0KcyA8LSBzZChkYXQkbndvcmRzKQ0KeHMgPC0gc2VxKDAsIDUwLCAxKQ0KDQpoaXN0KGRhdCRud29yZHMsIHByb2JhYmlsaXR5ID0gVFJVRSwgeGxpbSA9IGMoLTEwLCA1MCksIHlsaW0gPSBjKDAsIDAuMSkpDQpjdXJ2ZShkbm9ybSh4LCBtLCBzKSwgYWRkID0gVFJVRSwgY29sID0gImdyZWVuIiwgbHdkID0gMikNCmxpbmVzKHhzLCBkcG9pcyh4cywgbSksIGNvbCA9ICJyZWQiLCBsd2QgPSAyKQ0KbGluZXMoeHMsIGRwb2lzKHhzLCBtKSwgY29sID0gInJlZCIsIGx3ZCA9IDIpDQpgYGANCg0KQ29tbWVudHM/DQoNCkxldCdzIHBsb3Qgc29tZSBiaXZhcmlhdGUgZGlzdHJpYnV0aW9uczoNCg0KYGBge3J9DQojIGNhcmVnaXZpbmcgfiB0aW1lYm9uZGluZw0KciA8LSBjb3IoZGF0JHRpbWVib25kaW5nLCBkYXQkY2FyZWdpdmluZykNCnBsb3QoZGF0JHRpbWVib25kaW5nLCBkYXQkY2FyZWdpdmluZywgcGNoID0gMTkpDQphYmxpbmUobG0oZGF0JGNhcmVnaXZpbmcgfiBkYXQkdGltZWJvbmRpbmcpLCBjb2wgPSAicmVkIiwgbHdkID0gMikNCnRleHQoMzAsIDIsIGxhYmVsID0gcGFzdGUoInIgPSIsIHJvdW5kKHIsIDIpKSkNCmBgYA0KDQpgYGB7cn0NCnBhcihtZnJvdyA9IGMoMSwyKSkNCmJveHBsb3QoY2FyZWdpdmluZyB+IHNlcywgZGF0YSA9IGRhdCkNCmJveHBsb3QodGltZWJvbmRpbmcgfiBzZXMsIGRhdGEgPSBkYXQpDQpgYGANCg0KYGBge3J9DQptb3NhaWNwbG90KHRhYmxlKGRhdCRzZXMsIGRhdCRiYWJ5c2l0dGVyKSkNCiMgb3INCmJhcnBsb3QodGFibGUoZGF0JGJhYnlzaXR0ZXIsIGRhdCRzZXMpLCBjb2wgPSBjKCJyZWQiLCAiZ3JlZW4iKSwgYmVzaWRlID0gVFJVRSkNCmxlZ2VuZCg3LDQ1LCBsZWdlbmQgPSBjKCJubyIsICJ5ZXMiKSwgZmlsbCA9IGMoInJlZCIsICJncmVlbiIpKQ0KYGBgDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygxLDIpKQ0KYm94cGxvdChjYXJlZ2l2aW5nIH4gYmFieXNpdHRlciwgZGF0YSA9IGRhdCkNCmJveHBsb3QodGltZWJvbmRpbmcgfiBiYWJ5c2l0dGVyLCBkYXRhID0gZGF0KQ0KYGBgDQoNClRoZW4gdGhlIHJlbGF0aW9uc2hpcHMgd2l0aCB0aGUgYG53b3Jkc2AgdmFyaWFibGU6DQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLDIpKQ0KcGxvdChkYXQkdGltZWJvbmRpbmcsIGRhdCRud29yZHMsIHBjaCA9IDE5KQ0KcGxvdChkYXQkY2FyZWdpdmluZywgZGF0JG53b3JkcywgcGNoID0gMTkpDQpib3hwbG90KGRhdCRud29yZHMgfiBkYXQkc2VzKQ0KYm94cGxvdChkYXQkbndvcmRzIH4gZGF0JGJhYnlzaXR0ZXIpDQpgYGANCg0KQ29tbWVudHM/DQoNCkZpbmFsbHkgc29tZSBpbnRlcmFjdGlvbnMgcGxvdCAod2l0aCBgbG1gKToNCg0KYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0NCnBhcihtZnJvdyA9IGMoMiwxKSkNCmNvbG9ycyA8LSBjKGxvdyA9ICJyZWQiLCBtaWRkbGUgPSAiYmx1ZSIsIGhpZ2ggPSAiZ3JlZW4iKQ0KcGxvdChkYXQkdGltZWJvbmRpbmcsIGRhdCRud29yZHMsIGNvbCA9IGNvbG9yc1tkYXQkc2VzXSwgcGNoID0gMTkpDQpsbXMgPC0gbGFwcGx5KHNwbGl0KGRhdCwgZGF0JHNlcyksIGZ1bmN0aW9uKHgpIGxtKG53b3JkcyB+IHRpbWVib25kaW5nLCBkYXRhID0geCkpDQpsYXBwbHkoMTpsZW5ndGgobG1zKSwgZnVuY3Rpb24oaSkgYWJsaW5lKGxtc1tbaV1dLCBjb2wgPSBjb2xvcnNbaV0sIGx3ZCA9IDIpKQ0KDQpwbG90KGRhdCRjYXJlZ2l2aW5nLCBkYXQkbndvcmRzLCBjb2wgPSBjb2xvcnNbZGF0JHNlc10sIHBjaCA9IDE5KQ0KbG1zIDwtIGxhcHBseShzcGxpdChkYXQsIGRhdCRzZXMpLCBmdW5jdGlvbih4KSBsbShud29yZHMgfiBjYXJlZ2l2aW5nLCBkYXRhID0geCkpDQpsYXBwbHkoMTpsZW5ndGgobG1zKSwgZnVuY3Rpb24oaSkgYWJsaW5lKGxtc1tbaV1dLCBjb2wgPSBjb2xvcnNbaV0sIGx3ZCA9IDIpKQ0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9DQpwYXIobWZyb3cgPSBjKDIsMSkpDQpjb2xvcnMgPC0gYyhubyA9ICJvcmFuZ2UiLCB5ZXMgPSAicHVycGxlIikNCg0KcGxvdChkYXQkdGltZWJvbmRpbmcsIGRhdCRud29yZHMsIGNvbCA9IGNvbG9yc1tkYXQkYmFieXNpdHRlcl0sIHBjaCA9IDE5KQ0KbG1zIDwtIGxhcHBseShzcGxpdChkYXQsIGRhdCRiYWJ5c2l0dGVyKSwgZnVuY3Rpb24oeCkgbG0obndvcmRzIH4gdGltZWJvbmRpbmcsIGRhdGEgPSB4KSkNCmxhcHBseSgxOmxlbmd0aChsbXMpLCBmdW5jdGlvbihpKSBhYmxpbmUobG1zW1tpXV0sIGNvbCA9IGNvbG9yc1tpXSwgbHdkID0gMikpDQoNCnBsb3QoZGF0JGNhcmVnaXZpbmcsIGRhdCRud29yZHMsIGNvbCA9IGNvbG9yc1tkYXQkYmFieXNpdHRlcl0sIHBjaCA9IDE5KQ0KbG1zIDwtIGxhcHBseShzcGxpdChkYXQsIGRhdCRiYWJ5c2l0dGVyKSwgZnVuY3Rpb24oeCkgbG0obndvcmRzIH4gY2FyZWdpdmluZywgZGF0YSA9IHgpKQ0KbGFwcGx5KDE6bGVuZ3RoKGxtcyksIGZ1bmN0aW9uKGkpIGFibGluZShsbXNbW2ldXSwgY29sID0gY29sb3JzW2ldLCBsd2QgPSAyKSkNCmBgYA0KDQojIDMuIE1vZGVsIGZpdHRpbmcgd2l0aCBgZ2xtKClgIGFuZCBgcG9pc3NvbmANCg0KTGV0J3Mgc3RhcnQgYnkgdXNpbmcgYW4gYWRkaXRpdmUgbW9kZWw6DQoNCmBgYHtyfQ0KZml0IDwtIGdsbShud29yZHMgfiB0aW1lYm9uZGluZyArIGNhcmVnaXZpbmcgKyBiYWJ5c2l0dGVyICsgc2VzLCBmYW1pbHkgPSBwb2lzc29uKGxpbmsgPSAibG9nIiksIGRhdGEgPSBkYXQpDQpzdW1tYXJ5KGZpdCkNCmBgYA0KDQpBbmQgYWx3YXlzIHBsb3R0aW5nIGJlZm9yZSBhbnl0aGluZyBlbHNlOg0KDQpgYGB7cn0NCnBsb3QoYWxsRWZmZWN0cyhmaXQpKQ0KYGBgDQoNCkNvbW1lbnRzPyBIb3cgY291bGQgeW91IGRlc2NyaWJlIHRoZSByZXN1bHRzPyBTb21ldGhpbmcgZGlmZmVyZW50IGZyb20gdGhlIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3M/DQoNCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0NCmNhcjo6cmVzaWR1YWxQbG90cyhmaXQpDQpgYGANCg0KQ29tbWVudHM/IEFyZSB3ZSBtaXNzaW5nIHNvbWV0aGluZz8NCg0KYGBge3J9DQpkYXQgfD4gDQogICAgZ2dwbG90KGFlcyh4ID0gdGltZWJvbmRpbmcsIHkgPSBud29yZHMsIGNvbG9yID0gc2VzKSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgc3RhdF9zbW9vdGgobWV0aG9kID0gImdsbSIsIG1ldGhvZC5hcmdzID0gbGlzdChmYW1pbHkgPSBwb2lzc29uKCkpLCBzZSA9IEZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0IHw+IA0KICAgIGdncGxvdChhZXMoeCA9IGNhcmVnaXZpbmcsIHkgPSBud29yZHMsIGNvbG9yID0gc2VzKSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgc3RhdF9zbW9vdGgobWV0aG9kID0gImdsbSIsIG1ldGhvZC5hcmdzID0gbGlzdChmYW1pbHkgPSBwb2lzc29uKCkpLCBzZSA9IEZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0IHw+IA0KICAgIGdncGxvdChhZXMoeCA9IHRpbWVib25kaW5nLCB5ID0gbndvcmRzLCBjb2xvciA9IGJhYnlzaXR0ZXIpKSArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICBzdGF0X3Ntb290aChtZXRob2QgPSAiZ2xtIiwgbWV0aG9kLmFyZ3MgPSBsaXN0KGZhbWlseSA9IHBvaXNzb24oKSksIHNlID0gRkFMU0UpDQpgYGANCg0KYGBge3J9DQpkYXQgfD4gDQogICAgZ2dwbG90KGFlcyh4ID0gY2FyZWdpdmluZywgeSA9IG53b3JkcywgY29sb3IgPSBiYWJ5c2l0dGVyKSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgc3RhdF9zbW9vdGgobWV0aG9kID0gImdsbSIsIG1ldGhvZC5hcmdzID0gbGlzdChmYW1pbHkgPSBwb2lzc29uKCkpLCBzZSA9IEZBTFNFKQ0KYGBgDQoNCkxldCdzIGFkZCBzb21lIGludGVyYWN0aW9uczoNCg0KYGBge3J9DQpmaXQyIDwtIGdsbShud29yZHMgfiB0aW1lYm9uZGluZypzZXMgKyBjYXJlZ2l2aW5nKnNlcyArIHRpbWVib25kaW5nKmJhYnlzaXR0ZXIgKyBjYXJlZ2l2aW5nKmJhYnlzaXR0ZXIsIGZhbWlseSA9IHBvaXNzb24obGluayA9ICJsb2ciKSwgZGF0YSA9IGRhdCkNCnN1bW1hcnkoZml0MikNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9DQpwbG90KGFsbEVmZmVjdHMoZml0MikpDQpgYGANCg0KIyA0LiBNb2RlbCBmaXR0aW5nIHdpdGggYE1BU1M6OmdsbS5uYigpYA0KDQpUaGVyZSBpcyBzdGlsbCBldmlkZW5jZSBmb3Igb3ZlcmRpc3BlcnNpb24sIGV2ZW4gYWZ0ZXIgaW5jbHVkaW5nIGFsbCBwcmVkaWN0b3JzIGFuZCBhIHNlcmllcyBvZiBpbnRlcmFjdGlvbnMuIExldCdzIGFzc3VtZSB0aGF0IHRoaXMgaXMgb3VyIG1vc3QgY29tcGxleCBtb2RlbCwgd2UgbmVlZCB0byB0YWtlIGludG8gYWNjb3VudCB0aGUgb3ZlcmRpc3BlcnNpb246DQoNCmBgYHtyfQ0KcGVyZm9ybWFuY2U6OmNoZWNrX292ZXJkaXNwZXJzaW9uKGZpdDIpDQoNCmZpdDMgPC0gTUFTUzo6Z2xtLm5iKG53b3JkcyB+IHRpbWVib25kaW5nKnNlcyArIGNhcmVnaXZpbmcqc2VzICsgdGltZWJvbmRpbmcqYmFieXNpdHRlciArIGNhcmVnaXZpbmcqYmFieXNpdHRlciwgZGF0YSA9IGRhdCkNCg0Kc3VtbWFyeShmaXQzKQ0KYGBgDQoNCk5vdyBvdmVyZGlzcGVyc2lvbiBpcyB0YWtlbiBpbnRvIGFjY291bnQgYW5kIHN0YW5kYXJkIGVycm9ycyBhcmUgbGFyZ2VyOg0KDQpgYGB7cn0NCmNhcjo6Y29tcGFyZUNvZWZzKGZpdDIsIGZpdDMpDQoNCiMgdGVzdCBzdGF0aXN0aWNzIGZvciB0aGUgcG9pc3NvbiBtb2RlbA0KDQpkYXRhLmZyYW1lKA0KICAgIHBvaXNzb24gPSBmaXQyJGNvZWZmaWNpZW50cy9zcXJ0KGRpYWcodmNvdihmaXQyKSkpLA0KICAgIG5lZ2F0aXZlX2Jpbm9taWFsID0gZml0MyRjb2VmZmljaWVudHMvc3FydChkaWFnKHZjb3YoZml0MykpKQ0KKQ0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==