Load the Dataset
if (!"pacman" %in% installed.packages()[, "Package"]) {
install.packages("pacman", dependencies = TRUE)
library("pacman", character.only = TRUE)
}
pacman::p_load("here")
knitr::opts_knit$set(root.dir = here::here())
pacman::p_load("readr")
clv_data <- read_csv("./data/clv_data.csv")
head(clv_data)
View the Data Types
sapply(clv_data, class)
purchase_frequency customer_lifetime_value
"numeric" "numeric"
str(clv_data)
spc_tbl_ [500 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
$ purchase_frequency : num [1:500] 3 7 6 2 4 8 0 4 8 3 ...
$ customer_lifetime_value: num [1:500] 110.3 190.2 160 94.4 133.2 ...
- attr(*, "spec")=
.. cols(
.. purchase_frequency = col_double(),
.. customer_lifetime_value = col_double()
.. )
- attr(*, "problems")=<externalptr>
summary(clv_data)
purchase_frequency customer_lifetime_value
Min. :-1.000 Min. : 26.13
1st Qu.: 4.000 1st Qu.:122.04
Median : 5.000 Median :148.21
Mean : 4.914 Mean :148.25
3rd Qu.: 6.000 3rd Qu.:175.88
Max. :11.000 Max. :262.04
Variance:
#'sapply()' is designed to apply a function to a variable in a dataset
#In this case, I used 'sapply()' to apply the 'var()' function used to compute the variance.
#High variability means that the values are less consistent, thus making it harder to make predictions.
sapply(clv_data[,], var)
purchase_frequency customer_lifetime_value
4.146898 1642.315996
Standard Deviation:
sapply(clv_data[,],sd)
purchase_frequency customer_lifetime_value
2.036393 40.525498
Kurtosis
#Informs how often outliers occur
#Different formulas for calculating hence we specify type 2 which is used in other software
#Kurtosis = 3 -> medium no. of outliers
#Kurtosis<3 -> low no. of ouliers and vice versa
pacman::p_load("e1071")
sapply(clv_data[,],kurtosis, type=2)
purchase_frequency customer_lifetime_value
-0.1220038 -0.1484811
Skewness
#Used to ID the asymmetry of distribution of results
#Similar to kurtosis we have type 2 which is widely used by other apps :)
#-0.4<Skewness<0.4 inclusive implies no skew i.e it is a normal distribution
#Above 0.4 implies +ve skew
#below -0.4 implies -ve skew: a left-skewed distribution
sapply(clv_data[,], skewness, type = 2)
purchase_frequency customer_lifetime_value
-0.04021915 -0.01608242
Covariance
#Indicates the direction of the linear relationship betweeen 2 variables
#Assesses whether increase in one leads to an increase in the other
#+ve covariance -> when one increases the other increases
#-ve covariance -> when one increases the other decreases
#Zero covariance -> no relationship
#Shows direction of relationship but not strength
cov(clv_data, method = "spearman")
purchase_frequency customer_lifetime_value
purchase_frequency 20409.91 20235.73
customer_lifetime_value 20235.73 20874.99
Correlation
#Strong correlation enables better prediction of independent variable
#Only useful if there is linear association/strong correlation
#Spearman's rank correlation rho is used to measure statistical significance of the correlation
#Monotomic relationship -> one var increases and the other either increases consistently or consistently decreases
#Rate of change may vary but direction is preserved
cor.test(clv_data$customer_lifetime_value, clv_data$purchase_frequency, method = "spearman")
Spearman's rank correlation rho
data: clv_data$customer_lifetime_value and clv_data$purchase_frequency
S = 409190, p-value < 2.2e-16
alternative hypothesis: true rho is not equal to 0
sample estimates:
rho
0.9803588
To view correlation of all variables
cor(clv_data, method = "spearman")
purchase_frequency customer_lifetime_value
purchase_frequency 1.0000000 0.9803588
customer_lifetime_value 0.9803588 1.0000000
Basic Visualizations
# par(mfrow = c(1, 2)) This is used to divide the area used to plot the visualization into a 1 row by 2 columns grid
# for (i in 1:2) This is used to identify the variable (column) that is being processed
# clv_data[[i]] This is used to extract the i-th column as a vector
# hist() This is the fnctn used to plot the histogram
par(mfrow = c(1, 2))
for (i in 1:2) {
if (is.numeric(clv_data[[i]])){
hist(clv_data[[i]],
main = names(clv_data)[i],
xlab = names(clv_data)[i])
} else {
message(paste("Column", names(clv_data)[i], "is not numeric and will be skipped"))
}
}

par(mfrow = c(1, 2))
for (i in 1:2) {
if (is.numeric(clv_data[[i]])) {
boxplot(clv_data[[i]], main = names(clv_data)[i])
} else {
message(paste("Column", names(clv_data)[i], "is not numeric and will be skipped"))
}
}

pacman::p_load("Amelia")
missmap(clv_data, col = c("red", "grey"), legend = TRUE)

Correlation Plot
pacman::p_load("ggcorrplot")
ggcorrplot(cor(clv_data[,]))

Scatter Plot
pacman::p_load("corrplot")
pairs(clv_data$customer_lifetime_value ~ . , data = clv_data, col = clv_data$customer_lifetime_value)

pacman::p_load("ggplot2")
ggplot(clv_data,
aes(x = purchase_frequency, y = customer_lifetime_value)) +
geom_point() +
geom_smooth(method = lm) +
labs(
title = "Relationship between customer lifetime value and purchase frequency",
x = "Purchase Frequency",
y = "Customer Lifetime Value"
)

Statistical test of Linear Regression
slr_test <- lm(customer_lifetime_value ~ purchase_frequency, data = clv_data)
#To view result
summary(slr_test)
Call:
lm(formula = customer_lifetime_value ~ purchase_frequency, data = clv_data)
Residuals:
Min 1Q Median 3Q Max
-19.1176 -5.6169 -0.0491 5.6618 20.4837
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 52.2538 0.9042 57.79 <2e-16 ***
purchase_frequency 19.5356 0.1700 114.91 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 7.734 on 498 degrees of freedom
Multiple R-squared: 0.9637, Adjusted R-squared: 0.9636
F-statistic: 1.32e+04 on 1 and 498 DF, p-value: < 2.2e-16
#Obtain confidence intervals
confint(slr_test, level = 0.95)
2.5 % 97.5 %
(Intercept) 50.47731 54.03036
purchase_frequency 19.20159 19.86965
Diagnostic EDA
Diagnostic EDA tests validity of the model’s assumptions before
interpreting results. This helps prevent incorrect conclusions
Test of Linearity
plot(slr_test, which = 1)

# Tests whether relationship between dependent and independent variables is linear
# A plot of residuals vs fitted values enables test for linearity
# For the model to pass there should be no pattern in the distribution of residuals and the residuals should be randomly placed around the 0.0 residual line
# i.e the residuals should randomly vary around the mean of the value of the response variable
Test of Independence of Errors
This test is necessary to confirm each observation is independent of
each other.
It helps to identify autocorrelation which occurs when data is
collected over a close period of time or when an observation is related
to another.
Autocorrelation leads to underestimated standard errors and inflated
t-statistics / findings appear bigger than they actually are.
Durbin Watson Test
If the p-value > 5, no evidence to reject null hypothesis “There
is no autocorrelation”
pacman::p_load("lmtest")
dwtest(slr_test)
Durbin-Watson test
data: slr_test
DW = 1.9104, p-value = 0.1573
alternative hypothesis: true autocorrelation is greater than 0
#The results show a p-value of 0.1573 therefore the test of independence of errors around the regression line passes
Test of Normality
It assesses whether the residuals are normally distributed i.e most
residuals(errors) are close to zero and large errors are rare
A Q-Q plot can be used for this
It is a scatter-plot of the quantities of the residuals against
quantiles of a normal distribution
Quantiles are statistical values that divide a data set or
probability into equal-sized intervals e.g quartiles, percentiles,
deciles(10 equal parts) etc
If the points in the plot fall along a straight line, then the
normality assumption is satisfied.
plot(slr_test, which = 2)

Test of Homoscedasticity
Homoscedasticity requires that the spread of residuals should be
constant across all levels of the independent variable. A scale-location
plot (a.k.a. spread-location plot) can be used to conduct a test of
homoscedasticity.
The x-axis shows the fitted (predicted) values from the model and the
y-axis shows the square root of the standardized residuals. The red line
is added to help visualize any patterns.
In a model with homoscedastic errors (equal variance across all
predicted values):
• Points should be randomly scattered around a horizontal line
• The smooth line should be approximately horizontal
• The vertical spread of points should be roughly equal across all
fitted values
• No obvious patterns, funnels, or trends should be visible
Points forming a cone shape that widens from left to right suggests
heteroscedasticity with increasing variance for larger fitted
values.
plot(slr_test, which = 3)

Quantitative Validation of Assumptions
The graphical representations of the various tests of assumptions
should be accompanied by quantitative values. The gvlma package(Global
Validation of Linear Models Assumptions) is useful for this purpose.
pacman::p_load("gvlma")
gvlma_results <- gvlma(slr_test)
summary(gvlma_results)
Call:
lm(formula = customer_lifetime_value ~ purchase_frequency, data = clv_data)
Residuals:
Min 1Q Median 3Q Max
-19.1176 -5.6169 -0.0491 5.6618 20.4837
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 52.2538 0.9042 57.79 <2e-16 ***
purchase_frequency 19.5356 0.1700 114.91 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 7.734 on 498 degrees of freedom
Multiple R-squared: 0.9637, Adjusted R-squared: 0.9636
F-statistic: 1.32e+04 on 1 and 498 DF, p-value: < 2.2e-16
ASSESSMENT OF THE LINEAR MODEL ASSUMPTIONS
USING THE GLOBAL TEST ON 4 DEGREES-OF-FREEDOM:
Level of Significance = 0.05
Call:
gvlma(x = slr_test)
LS0tDQp0aXRsZTogIlNpbXBsZSBMaW5lYXIgUmVncmVzc2lvbiINCmF1dGhvcjogIjxEZW56ZWwgV2FtYnVhPiINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogNA0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgIGZpZ193aWR0aDogNg0KICAgIGZpZ19oZWlnaHQ6IDYNCiAgICBzZWxmX2NvbnRhaW5lZDogZmFsc2UNCiAgICBrZWVwX21kOiB0cnVlDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiA0DQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgZmlnX2hlaWdodDogNg0KICAgIGZpZ19jcm9wOiBmYWxzZQ0KICAgIGtlZXBfdGV4OiB0cnVlDQogICAgbGF0ZXhfZW5naW5lOiB4ZWxhdGV4DQogIHdvcmRfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiA0DQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgZmlnX3dpZHRoOiA2DQogICAga2VlcF9tZDogdHJ1ZQ0KLS0tDQoNCiMgTG9hZCB0aGUgRGF0YXNldA0KDQpgYGB7cn0NCmlmICghInBhY21hbiIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssICJQYWNrYWdlIl0pIHsNCmluc3RhbGwucGFja2FnZXMoInBhY21hbiIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQpsaWJyYXJ5KCJwYWNtYW4iLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQp9DQpwYWNtYW46OnBfbG9hZCgiaGVyZSIpDQprbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9IGhlcmU6OmhlcmUoKSkNCmBgYA0KDQpgYGB7cn0NCnBhY21hbjo6cF9sb2FkKCJyZWFkciIpDQpjbHZfZGF0YSA8LSByZWFkX2NzdigiLi9kYXRhL2Nsdl9kYXRhLmNzdiIpDQpoZWFkKGNsdl9kYXRhKQ0KYGBgDQoNCiMgVmlldyB0aGUgRGF0YSBUeXBlcw0KDQpgYGB7ciBzaG93X2RhdGFfdHlwZXNfMSwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2FwcGx5KGNsdl9kYXRhLCBjbGFzcykNCg0KDQpgYGANCg0KYGBge3Igc2hvd19kYXRhX3R5cGVzXzIsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN0cihjbHZfZGF0YSkNCg0KYGBgDQoNCmBgYHtyIHNob3dfZGF0YV90eXBlc18zLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5KGNsdl9kYXRhKQ0KYGBgDQoNCiMgVmFyaWFuY2U6DQoNCmBgYHtyIGRpc3RyaWJ1dGlvbl92YXJpYW5jZSwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIydzYXBwbHkoKScgaXMgZGVzaWduZWQgdG8gYXBwbHkgYSBmdW5jdGlvbiB0byBhIHZhcmlhYmxlIGluIGEgZGF0YXNldA0KI0luIHRoaXMgY2FzZSwgSSB1c2VkICdzYXBwbHkoKScgdG8gYXBwbHkgdGhlICd2YXIoKScgZnVuY3Rpb24gdXNlZCB0byBjb21wdXRlIHRoZSB2YXJpYW5jZS4NCiNIaWdoIHZhcmlhYmlsaXR5IG1lYW5zIHRoYXQgdGhlIHZhbHVlcyBhcmUgbGVzcyBjb25zaXN0ZW50LCB0aHVzIG1ha2luZyBpdCBoYXJkZXIgdG8gbWFrZSBwcmVkaWN0aW9ucy4NCnNhcHBseShjbHZfZGF0YVssXSwgdmFyKQ0KDQpgYGANCg0KIyBTdGFuZGFyZCBEZXZpYXRpb246DQoNCmBgYHtyIGRpc3RyaWJ1dGlvbV9zdGFuZGFyZF9kZXZpYXRpb24sIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNhcHBseShjbHZfZGF0YVssXSxzZCkNCg0KYGBgDQoNCiMgS3VydG9zaXMNCg0KYGBge3IgZGlzdHJpYnV0aW9uX2t1cnRvc2lzLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojSW5mb3JtcyBob3cgb2Z0ZW4gb3V0bGllcnMgb2NjdXIgDQojRGlmZmVyZW50IGZvcm11bGFzIGZvciBjYWxjdWxhdGluZyBoZW5jZSB3ZSBzcGVjaWZ5IHR5cGUgMiB3aGljaCBpcyB1c2VkIGluIG90aGVyIHNvZnR3YXJlDQojS3VydG9zaXMgPSAzIC0+IG1lZGl1bSBuby4gb2Ygb3V0bGllcnMNCiNLdXJ0b3NpczwzIC0+IGxvdyBuby4gb2Ygb3VsaWVycyBhbmQgdmljZSB2ZXJzYQ0KcGFjbWFuOjpwX2xvYWQoImUxMDcxIikNCnNhcHBseShjbHZfZGF0YVssXSxrdXJ0b3NpcywgdHlwZT0yKQ0KDQpgYGANCg0KIyBTa2V3bmVzcw0KDQpgYGB7ciBkaXN0cmlidXRpb25fc2tld25lc3MsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNVc2VkIHRvIElEIHRoZSBhc3ltbWV0cnkgb2YgZGlzdHJpYnV0aW9uIG9mIHJlc3VsdHMNCiNTaW1pbGFyIHRvIGt1cnRvc2lzIHdlIGhhdmUgdHlwZSAyIHdoaWNoIGlzIHdpZGVseSB1c2VkIGJ5IG90aGVyIGFwcHMgOikNCiMtMC40PFNrZXduZXNzPDAuNCBpbmNsdXNpdmUgaW1wbGllcyBubyBza2V3IGkuZSBpdCBpcyBhIG5vcm1hbCBkaXN0cmlidXRpb24NCiNBYm92ZSAwLjQgaW1wbGllcyArdmUgc2tldw0KI2JlbG93IC0wLjQgaW1wbGllcyAtdmUgc2tldzogYSBsZWZ0LXNrZXdlZCBkaXN0cmlidXRpb24NCnNhcHBseShjbHZfZGF0YVssXSwgc2tld25lc3MsIHR5cGUgPSAyKQ0KDQpgYGANCg0KIyBDb3ZhcmlhbmNlDQoNCmBgYHtyIGRpc3RyaWJ1dGlvbl9jb3ZhcmlhbmNlLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojSW5kaWNhdGVzIHRoZSBkaXJlY3Rpb24gb2YgdGhlIGxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlZW4gMiB2YXJpYWJsZXMNCiNBc3Nlc3NlcyB3aGV0aGVyIGluY3JlYXNlIGluIG9uZSBsZWFkcyB0byBhbiBpbmNyZWFzZSBpbiB0aGUgb3RoZXINCiMrdmUgY292YXJpYW5jZSAtPiB3aGVuIG9uZSBpbmNyZWFzZXMgdGhlIG90aGVyIGluY3JlYXNlcw0KIy12ZSAgY292YXJpYW5jZSAtPiB3aGVuIG9uZSBpbmNyZWFzZXMgdGhlIG90aGVyIGRlY3JlYXNlcw0KI1plcm8gY292YXJpYW5jZSAtPiBubyByZWxhdGlvbnNoaXANCiNTaG93cyBkaXJlY3Rpb24gb2YgcmVsYXRpb25zaGlwIGJ1dCBub3Qgc3RyZW5ndGgNCmNvdihjbHZfZGF0YSwgbWV0aG9kID0gInNwZWFybWFuIikNCmBgYA0KDQojIENvcnJlbGF0aW9uDQoNCmBgYHtyIGRpc3RyaWJ1dGlvbl9jb3JyZWxhdGlvbl8xLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojU3Ryb25nIGNvcnJlbGF0aW9uIGVuYWJsZXMgYmV0dGVyIHByZWRpY3Rpb24gb2YgaW5kZXBlbmRlbnQgdmFyaWFibGUNCiNPbmx5IHVzZWZ1bCBpZiB0aGVyZSBpcyBsaW5lYXIgYXNzb2NpYXRpb24vc3Ryb25nIGNvcnJlbGF0aW9uDQojU3BlYXJtYW4ncyByYW5rIGNvcnJlbGF0aW9uIHJobyBpcyB1c2VkIHRvIG1lYXN1cmUgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIG9mIHRoZSBjb3JyZWxhdGlvbg0KI01vbm90b21pYyByZWxhdGlvbnNoaXAgLT4gb25lIHZhciBpbmNyZWFzZXMgYW5kIHRoZSBvdGhlciBlaXRoZXIgaW5jcmVhc2VzIGNvbnNpc3RlbnRseSBvciBjb25zaXN0ZW50bHkgZGVjcmVhc2VzDQojUmF0ZSBvZiBjaGFuZ2UgbWF5IHZhcnkgYnV0IGRpcmVjdGlvbiBpcyBwcmVzZXJ2ZWQNCmNvci50ZXN0KGNsdl9kYXRhJGN1c3RvbWVyX2xpZmV0aW1lX3ZhbHVlLCBjbHZfZGF0YSRwdXJjaGFzZV9mcmVxdWVuY3ksIG1ldGhvZCA9ICJzcGVhcm1hbiIpDQpgYGANCg0KVG8gdmlldyBjb3JyZWxhdGlvbiBvZiBhbGwgdmFyaWFibGVzDQoNCmBgYHtyIGRpc3RyaWJ1dGlvbl9jb3JyZWxhdGlvbl8yLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpjb3IoY2x2X2RhdGEsIG1ldGhvZCA9ICJzcGVhcm1hbiIpDQoNCmBgYA0KDQojIEJhc2ljIFZpc3VhbGl6YXRpb25zDQoNCmBgYHtyIHZpc3VhbGl6YXRpb25faGlzdG9ncmFtLCBlY2hvPVRSVUUsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBwYXIobWZyb3cgPSBjKDEsIDIpKSBUaGlzICBpcyB1c2VkIHRvIGRpdmlkZSB0aGUgYXJlYSB1c2VkIHRvIHBsb3QgdGhlIHZpc3VhbGl6YXRpb24gaW50byBhIDEgcm93IGJ5IDIgY29sdW1ucyBncmlkDQojIGZvciAoaSBpbiAxOjIpIFRoaXMgaXMgdXNlZCB0byBpZGVudGlmeSB0aGUgdmFyaWFibGUgKGNvbHVtbikgdGhhdCBpcyBiZWluZyBwcm9jZXNzZWQNCiMgY2x2X2RhdGFbW2ldXSBUaGlzIGlzIHVzZWQgdG8gZXh0cmFjdCB0aGUgaS10aCBjb2x1bW4gYXMgYSB2ZWN0b3INCiMgaGlzdCgpIFRoaXMgaXMgdGhlIGZuY3RuIHVzZWQgdG8gcGxvdCB0aGUgaGlzdG9ncmFtDQpwYXIobWZyb3cgPSBjKDEsIDIpKQ0KZm9yIChpIGluIDE6Mikgew0KICBpZiAoaXMubnVtZXJpYyhjbHZfZGF0YVtbaV1dKSl7DQogICAgaGlzdChjbHZfZGF0YVtbaV1dLA0KICAgICAgICAgbWFpbiA9IG5hbWVzKGNsdl9kYXRhKVtpXSwNCiAgICAgICAgIHhsYWIgPSBuYW1lcyhjbHZfZGF0YSlbaV0pDQogIH0gZWxzZSB7DQogICAgbWVzc2FnZShwYXN0ZSgiQ29sdW1uIiwgbmFtZXMoY2x2X2RhdGEpW2ldLCAiaXMgbm90IG51bWVyaWMgYW5kIHdpbGwgYmUgc2tpcHBlZCIpKQ0KICB9DQp9DQoNCmBgYA0KDQpgYGB7ciB2aXN1YWxpemF0aW9uX2JveHBsb3QsIGVjaG89VFJVRSwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwYXIobWZyb3cgPSBjKDEsIDIpKQ0KZm9yIChpIGluIDE6Mikgew0KICBpZiAoaXMubnVtZXJpYyhjbHZfZGF0YVtbaV1dKSkgew0KICAgIGJveHBsb3QoY2x2X2RhdGFbW2ldXSwgbWFpbiA9IG5hbWVzKGNsdl9kYXRhKVtpXSkNCiAgfSBlbHNlIHsNCiAgICBtZXNzYWdlKHBhc3RlKCJDb2x1bW4iLCBuYW1lcyhjbHZfZGF0YSlbaV0sICJpcyBub3QgbnVtZXJpYyBhbmQgd2lsbCBiZSBza2lwcGVkIikpDQogIH0NCn0NCg0KYGBgDQoNCmBgYHtyIG1pc3NpbmdfZGF0YV9wbG90LCBlY2hvPVRSVUUsIGZpZy53aWR0aD02LCBlcnJvcj1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBhY21hbjo6cF9sb2FkKCJBbWVsaWEiKQ0KDQptaXNzbWFwKGNsdl9kYXRhLCBjb2wgPSBjKCJyZWQiLCAiZ3JleSIpLCBsZWdlbmQgPSBUUlVFKQ0KDQpgYGANCg0KIyBDb3JyZWxhdGlvbiBQbG90DQoNCmBgYHtyIGNvcnJlbGF0aW9uX3Bsb3QsIGVjaG89VFJVRSwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwYWNtYW46OnBfbG9hZCgiZ2djb3JycGxvdCIpDQpnZ2NvcnJwbG90KGNvcihjbHZfZGF0YVssXSkpDQoNCmBgYA0KDQojIFNjYXR0ZXIgUGxvdA0KDQpgYGB7ciBzY2F0dGVyX3Bsb3RfMSwgZWNobz1UUlVFLCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBhY21hbjo6cF9sb2FkKCJjb3JycGxvdCIpDQoNCnBhaXJzKGNsdl9kYXRhJGN1c3RvbWVyX2xpZmV0aW1lX3ZhbHVlIH4gLiAsIGRhdGEgPSBjbHZfZGF0YSwgY29sID0gY2x2X2RhdGEkY3VzdG9tZXJfbGlmZXRpbWVfdmFsdWUpDQoNCmBgYA0KDQpgYGB7ciBzY2F0dGVyX3Bsb3RfMiwgZWNobz1UUlVFLCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBhY21hbjo6cF9sb2FkKCJnZ3Bsb3QyIikNCmdncGxvdChjbHZfZGF0YSwNCiAgICAgICBhZXMoeCA9IHB1cmNoYXNlX2ZyZXF1ZW5jeSwgeSA9IGN1c3RvbWVyX2xpZmV0aW1lX3ZhbHVlKSkgKyANCiAgZ2VvbV9wb2ludCgpICsNCmdlb21fc21vb3RoKG1ldGhvZCA9IGxtKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUmVsYXRpb25zaGlwIGJldHdlZW4gY3VzdG9tZXIgbGlmZXRpbWUgdmFsdWUgYW5kIHB1cmNoYXNlIGZyZXF1ZW5jeSIsDQogICAgeCA9ICJQdXJjaGFzZSBGcmVxdWVuY3kiLA0KICAgIHkgPSAiQ3VzdG9tZXIgTGlmZXRpbWUgVmFsdWUiDQogICkNCmBgYA0KDQojIFN0YXRpc3RpY2FsIHRlc3Qgb2YgTGluZWFyIFJlZ3Jlc3Npb24NCg0KYGBge3J9DQpzbHJfdGVzdCA8LSBsbShjdXN0b21lcl9saWZldGltZV92YWx1ZSB+IHB1cmNoYXNlX2ZyZXF1ZW5jeSwgZGF0YSA9IGNsdl9kYXRhKQ0KDQojVG8gdmlldyByZXN1bHQNCnN1bW1hcnkoc2xyX3Rlc3QpDQpgYGANCg0KYGBge3J9DQojT2J0YWluIGNvbmZpZGVuY2UgaW50ZXJ2YWxzDQpjb25maW50KHNscl90ZXN0LCBsZXZlbCA9IDAuOTUpDQpgYGANCg0KIyBEaWFnbm9zdGljIEVEQQ0KDQpEaWFnbm9zdGljIEVEQSB0ZXN0cyB2YWxpZGl0eSBvZiB0aGUgbW9kZWwncyBhc3N1bXB0aW9ucyBiZWZvcmUgaW50ZXJwcmV0aW5nIHJlc3VsdHMuIFRoaXMgaGVscHMgcHJldmVudCBpbmNvcnJlY3QgY29uY2x1c2lvbnMNCg0KIyMgVGVzdCBvZiBMaW5lYXJpdHkNCg0KYGBge3IgdGVzdF9vZl9saW5lYXJpdHksIGVjaG89VFJVRSwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwbG90KHNscl90ZXN0LCB3aGljaCA9IDEpDQoNCiMgVGVzdHMgd2hldGhlciByZWxhdGlvbnNoaXAgYmV0d2VlbiBkZXBlbmRlbnQgYW5kIGluZGVwZW5kZW50IHZhcmlhYmxlcyBpcyBsaW5lYXINCiMgQSBwbG90IG9mIHJlc2lkdWFscyB2cyBmaXR0ZWQgdmFsdWVzIGVuYWJsZXMgdGVzdCBmb3IgbGluZWFyaXR5DQojIEZvciB0aGUgbW9kZWwgdG8gcGFzcyB0aGVyZSBzaG91bGQgYmUgbm8gcGF0dGVybiBpbiB0aGUgZGlzdHJpYnV0aW9uIG9mIHJlc2lkdWFscyBhbmQgdGhlIHJlc2lkdWFscyBzaG91bGQgYmUgcmFuZG9tbHkgcGxhY2VkIGFyb3VuZCB0aGUgMC4wIHJlc2lkdWFsIGxpbmUNCiMgaS5lIHRoZSByZXNpZHVhbHMgc2hvdWxkIHJhbmRvbWx5IHZhcnkgYXJvdW5kIHRoZSBtZWFuIG9mIHRoZSB2YWx1ZSBvZiB0aGUgcmVzcG9uc2UgdmFyaWFibGUNCmBgYA0KDQojIyBUZXN0IG9mIEluZGVwZW5kZW5jZSBvZiBFcnJvcnMNCg0KVGhpcyB0ZXN0IGlzIG5lY2Vzc2FyeSB0byBjb25maXJtIGVhY2ggb2JzZXJ2YXRpb24gaXMgaW5kZXBlbmRlbnQgb2YgZWFjaCBvdGhlci4NCg0KSXQgaGVscHMgdG8gaWRlbnRpZnkgYXV0b2NvcnJlbGF0aW9uIHdoaWNoIG9jY3VycyB3aGVuIGRhdGEgaXMgY29sbGVjdGVkIG92ZXIgYSBjbG9zZSBwZXJpb2Qgb2YgdGltZSBvciB3aGVuIGFuIG9ic2VydmF0aW9uIGlzIHJlbGF0ZWQgdG8gYW5vdGhlci4NCg0KQXV0b2NvcnJlbGF0aW9uIGxlYWRzIHRvIHVuZGVyZXN0aW1hdGVkIHN0YW5kYXJkIGVycm9ycyBhbmQgaW5mbGF0ZWQgdC1zdGF0aXN0aWNzIC8gZmluZGluZ3MgYXBwZWFyIGJpZ2dlciB0aGFuIHRoZXkgYWN0dWFsbHkgYXJlLg0KDQpEdXJiaW4gV2F0c29uIFRlc3QNCg0KLSAgIEgwIC1cPiBUaGVyZSBpcyBubyBhdXRvY29ycmVsYXRpb24gKG51bGwgaHlwb3RoZXNpcykNCg0KLSAgIEgxIC1cPiBUaGVyZSBpcyBhdXRvY29ycmVsYXRpb24NCg0KSWYgdGhlIHAtdmFsdWUgXD4gNSwgbm8gZXZpZGVuY2UgdG8gcmVqZWN0IG51bGwgaHlwb3RoZXNpcyAiVGhlcmUgaXMgbm8gYXV0b2NvcnJlbGF0aW9uIg0KDQpgYGB7ciB0ZXN0X29mX2luZGVwZW5kZW5jZV9vZl9lcnJvcnMsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBhY21hbjo6cF9sb2FkKCJsbXRlc3QiKQ0KZHd0ZXN0KHNscl90ZXN0KQ0KI1RoZSByZXN1bHRzIHNob3cgYSBwLXZhbHVlIG9mIDAuMTU3MyB0aGVyZWZvcmUgdGhlIHRlc3Qgb2YgaW5kZXBlbmRlbmNlIG9mIGVycm9ycyBhcm91bmQgdGhlIHJlZ3Jlc3Npb24gbGluZSBwYXNzZXMNCmBgYA0KDQojIyBUZXN0IG9mIE5vcm1hbGl0eQ0KDQpJdCBhc3Nlc3NlcyB3aGV0aGVyIHRoZSByZXNpZHVhbHMgYXJlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIGkuZSBtb3N0IHJlc2lkdWFscyhlcnJvcnMpIGFyZSBjbG9zZSB0byB6ZXJvIGFuZCBsYXJnZSBlcnJvcnMgYXJlIHJhcmUNCg0KQSBRLVEgcGxvdCBjYW4gYmUgdXNlZCBmb3IgdGhpcw0KDQpJdCBpcyBhIHNjYXR0ZXItcGxvdCBvZiB0aGUgcXVhbnRpdGllcyBvZiB0aGUgcmVzaWR1YWxzIGFnYWluc3QgcXVhbnRpbGVzIG9mIGEgbm9ybWFsIGRpc3RyaWJ1dGlvbg0KDQpRdWFudGlsZXMgYXJlIHN0YXRpc3RpY2FsIHZhbHVlcyB0aGF0IGRpdmlkZSBhIGRhdGEgc2V0IG9yIHByb2JhYmlsaXR5IGludG8gZXF1YWwtc2l6ZWQgaW50ZXJ2YWxzIGUuZyBxdWFydGlsZXMsIHBlcmNlbnRpbGVzLCBkZWNpbGVzKDEwIGVxdWFsIHBhcnRzKSBldGMNCg0KSWYgdGhlIHBvaW50cyBpbiB0aGUgcGxvdCBmYWxsIGFsb25nIGEgc3RyYWlnaHQgbGluZSwgdGhlbiB0aGUgbm9ybWFsaXR5IGFzc3VtcHRpb24gaXMgc2F0aXNmaWVkLg0KDQpgYGB7ciB0ZXN0X29mX25vcm1hbGl0eSwgZWNobz1UUlVFLCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBsb3Qoc2xyX3Rlc3QsIHdoaWNoID0gMikNCg0KYGBgDQoNCiMgVGVzdCBvZiBIb21vc2NlZGFzdGljaXR5DQoNCkhvbW9zY2VkYXN0aWNpdHkgcmVxdWlyZXMgdGhhdCB0aGUgc3ByZWFkIG9mIHJlc2lkdWFscyBzaG91bGQgYmUgY29uc3RhbnQgYWNyb3NzIGFsbCBsZXZlbHMgb2YgdGhlIGluZGVwZW5kZW50IHZhcmlhYmxlLiBBIHNjYWxlLWxvY2F0aW9uIHBsb3QgKGEuay5hLiBzcHJlYWQtbG9jYXRpb24gcGxvdCkgY2FuIGJlIHVzZWQgdG8gY29uZHVjdCBhIHRlc3Qgb2YgaG9tb3NjZWRhc3RpY2l0eS4NCg0KVGhlIHgtYXhpcyBzaG93cyB0aGUgZml0dGVkIChwcmVkaWN0ZWQpIHZhbHVlcyBmcm9tIHRoZSBtb2RlbCBhbmQgdGhlIHktYXhpcyBzaG93cyB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIHN0YW5kYXJkaXplZCByZXNpZHVhbHMuIFRoZSByZWQgbGluZSBpcyBhZGRlZCB0byBoZWxwIHZpc3VhbGl6ZSBhbnkgcGF0dGVybnMuDQoNCkluIGEgbW9kZWwgd2l0aCBob21vc2NlZGFzdGljIGVycm9ycyAoZXF1YWwgdmFyaWFuY2UgYWNyb3NzIGFsbCBwcmVkaWN0ZWQgdmFsdWVzKToNCg0K4oCiIFBvaW50cyBzaG91bGQgYmUgcmFuZG9tbHkgc2NhdHRlcmVkIGFyb3VuZCBhIGhvcml6b250YWwgbGluZQ0KDQrigKIgVGhlIHNtb290aCBsaW5lIHNob3VsZCBiZSBhcHByb3hpbWF0ZWx5IGhvcml6b250YWwNCg0K4oCiIFRoZSB2ZXJ0aWNhbCBzcHJlYWQgb2YgcG9pbnRzIHNob3VsZCBiZSByb3VnaGx5IGVxdWFsIGFjcm9zcyBhbGwgZml0dGVkIHZhbHVlcw0KDQrigKIgTm8gb2J2aW91cyBwYXR0ZXJucywgZnVubmVscywgb3IgdHJlbmRzIHNob3VsZCBiZSB2aXNpYmxlDQoNClBvaW50cyBmb3JtaW5nIGEgY29uZSBzaGFwZSB0aGF0IHdpZGVucyBmcm9tIGxlZnQgdG8gcmlnaHQgc3VnZ2VzdHMgaGV0ZXJvc2NlZGFzdGljaXR5IHdpdGggaW5jcmVhc2luZyB2YXJpYW5jZSBmb3IgbGFyZ2VyIGZpdHRlZCB2YWx1ZXMuDQoNCmBgYHtyIHRlc3Rfb2ZfaG9tb3NjZWRhc3RpY2l0eSwgZWNobz1UUlVFLCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBsb3Qoc2xyX3Rlc3QsIHdoaWNoID0gMykNCg0KYGBgDQoNCiMgUXVhbnRpdGF0aXZlIFZhbGlkYXRpb24gb2YgQXNzdW1wdGlvbnMNCg0KVGhlIGdyYXBoaWNhbCByZXByZXNlbnRhdGlvbnMgb2YgdGhlIHZhcmlvdXMgdGVzdHMgb2YgYXNzdW1wdGlvbnMgc2hvdWxkIGJlIGFjY29tcGFuaWVkIGJ5IHF1YW50aXRhdGl2ZSB2YWx1ZXMuIFRoZSBndmxtYSBwYWNrYWdlKEdsb2JhbCBWYWxpZGF0aW9uIG9mIExpbmVhciBNb2RlbHMgQXNzdW1wdGlvbnMpIGlzIHVzZWZ1bCBmb3IgdGhpcyBwdXJwb3NlLg0KDQpgYGB7cn0NCnBhY21hbjo6cF9sb2FkKCJndmxtYSIpDQpndmxtYV9yZXN1bHRzIDwtIGd2bG1hKHNscl90ZXN0KQ0Kc3VtbWFyeShndmxtYV9yZXN1bHRzKQ0KYGBgDQo=