Predicting Software Reselling Profits

Mydata1 <- read.csv("Tayko.csv")
head(Mydata1) 
#prints the first few rows of the dataset. You can view the complete dataset on a spreadsheet by double clicking on the file name in the environment window

You may not need in the dataset, the first column (i.e. variable X). It can be dropped using the following codes

Mydata <- within(Mydata1, rm(sequence_number,source_a,source_b,source_c,source_d,source_e,source_m,source_o,source_h,source_r,source_s,source_t,source_u,source_p,source_x,source_w,X1st_update_days_ago,Purchase))

Inspect the dataframe as a table and print variable names.

fix(Mydata)
# outputting data column names
names(Mydata)
# outputting data
write.table(Mydata)

Search if the data has any missing values. Many procedures do not work if the data has missing values or special instructions need to be given when they are present.

##Number of missing values (NAs) in the dataframe
sum(is.na(Mydata))
[1] 0
##To compute the total missing values in each column is to use colSums()
#colSums(is.na(Mydata))
colSums(is.na(Mydata))
                  US                 Freq last_update_days_ago 
                   0                    0                    0 
           Web.order          Gender.male       Address_is_res 
                   0                    0                    0 
            Spending 
                   0 
##Simple ways to deal with missing value https://uc-r.github.io/missing_values 

Now the data is ready for analysis. It is always a good idea to start with exploratory data analysis techniques.

#To print the variable names and type of data under each variable
str(Mydata)
'data.frame':   2000 obs. of  7 variables:
 $ US                  : int  1 1 1 1 1 1 1 1 1 1 ...
 $ Freq                : int  2 0 2 1 1 1 2 1 4 1 ...
 $ last_update_days_ago: int  3662 2900 3883 829 869 1995 1498 3397 525 3215 ...
 $ Web.order           : int  1 1 0 0 0 0 0 0 1 0 ...
 $ Gender.male         : int  0 1 0 1 0 0 0 1 1 0 ...
 $ Address_is_res      : int  1 0 0 0 0 1 1 0 0 0 ...
 $ Spending            : int  128 0 127 0 0 0 0 0 489 174 ...
# Plot the data for each column to get a feel for it
# as well as a histogram to see the data distribution
#loading packages
require("ggplot2")
Loading required package: ggplot2
plot(Mydata$Freq, Mydata$Spending)


plot(Mydata$last_update_days_ago, Mydata$Spending)

For descriptive statistics, the function ‘summary’ can be used. The package ‘pastecs’ is used below for a more detailed output. Do check for missing values and type of variable (must be numeric).

# Compute descriptive statistics
library(pastecs)
res <- stat.desc(Mydata)
round(res, 2)
# Correlation matrix and respective p-values
library("Hmisc")
res2 <- rcorr(as.matrix(Mydata))
res2
                       US  Freq last_update_days_ago Web.order Gender.male
US                   1.00  0.03                 0.04      0.00        0.03
Freq                 0.03  1.00                -0.35      0.10       -0.04
last_update_days_ago 0.04 -0.35                 1.00     -0.03        0.02
Web.order            0.00  0.10                -0.03      1.00       -0.01
Gender.male          0.03 -0.04                 0.02     -0.01        1.00
Address_is_res       0.02  0.22                -0.21     -0.04       -0.05
Spending             0.00  0.69                -0.26      0.12       -0.02
                     Address_is_res Spending
US                             0.02     0.00
Freq                           0.22     0.69
last_update_days_ago          -0.21    -0.26
Web.order                     -0.04     0.12
Gender.male                   -0.05    -0.02
Address_is_res                 1.00    -0.03
Spending                      -0.03     1.00

n= 2000 


P
                     US     Freq   last_update_days_ago Web.order Gender.male
US                          0.1392 0.0880               0.8561    0.2347     
Freq                 0.1392        0.0000               0.0000    0.0888     
last_update_days_ago 0.0880 0.0000                      0.1188    0.4675     
Web.order            0.8561 0.0000 0.1188                         0.7948     
Gender.male          0.2347 0.0888 0.4675               0.7948               
Address_is_res       0.3521 0.0000 0.0000               0.0759    0.0324     
Spending             0.8764 0.0000 0.0000               0.0000    0.2826     
                     Address_is_res Spending
US                   0.3521         0.8764  
Freq                 0.0000         0.0000  
last_update_days_ago 0.0000         0.0000  
Web.order            0.0759         0.0000  
Gender.male          0.0324         0.2826  
Address_is_res                      0.2282  
Spending             0.2282                 

Evaluate the linear relationship between two variables by building simple linear regression model

fit1 <- lm(Spending~Freq, data = Mydata)
summary(fit1)

Call:
lm(formula = Spending ~ Freq, data = Mydata)

Residuals:
    Min      1Q  Median      3Q     Max 
-465.32  -64.33   -6.33   27.50 1343.84 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -27.500      4.288  -6.414 1.77e-10 ***
Freq          91.831      2.148  42.744  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 135 on 1998 degrees of freedom
Multiple R-squared:  0.4777,    Adjusted R-squared:  0.4774 
F-statistic:  1827 on 1 and 1998 DF,  p-value: < 2.2e-16
confint(fit1) #Confidence interval estimates
                2.5 %    97.5 %
(Intercept) -35.90855 -19.09116
Freq         87.61792  96.04454
plot(fit1)

confint(fit1) #Confidence interval estimates
                2.5 %    97.5 %
(Intercept) -35.90855 -19.09116
Freq         87.61792  96.04454

#The equatiion is Spending = -27.5 + 91.831*Freq #Multiple R-squared: 0.4777 is not very high. # p-value is quite low and less than 0.05.

fit2 <- lm(Spending~last_update_days_ago, data = Mydata)
summary(fit2)

Call:
lm(formula = Spending ~ last_update_days_ago, data = Mydata)

Residuals:
    Min      1Q  Median      3Q     Max 
-192.61  -95.29  -45.15   56.68 1371.34 

Coefficients:
                       Estimate Std. Error t value Pr(>|t|)    
(Intercept)          193.237411   8.628528   22.39   <2e-16 ***
last_update_days_ago  -0.042046   0.003538  -11.88   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 180.6 on 1998 degrees of freedom
Multiple R-squared:  0.066, Adjusted R-squared:  0.06554 
F-statistic: 141.2 on 1 and 1998 DF,  p-value: < 2.2e-16
confint(fit2) #Confidence interval estimates
                            2.5 %       97.5 %
(Intercept)          176.31555606 210.15926689
last_update_days_ago  -0.04898495  -0.03510616
plot(fit2)

#The equatiion is Spending = - 193.237411 -0.042046 *last_update_days_ago #Multiple R-squared: 0.066 is very low. # However, p-value is quite low and less than 0.05..

# Multiple Linear Regression
lm.fit=lm(Spending ~ Freq + last_update_days_ago + US + Web.order + Gender.male + Address_is_res, data = Mydata)
summary(lm.fit)

Call:
lm(formula = Spending ~ Freq + last_update_days_ago + US + Web.order + 
    Gender.male + Address_is_res, data = Mydata)

Residuals:
    Min      1Q  Median      3Q     Max 
-435.87  -79.10   -0.17   33.29 1327.19 

Coefficients:
                       Estimate Std. Error t value Pr(>|t|)    
(Intercept)            4.235190  10.888305   0.389  0.69734    
Freq                  94.567186   2.256709  41.905  < 2e-16 ***
last_update_days_ago  -0.007670   0.002761  -2.778  0.00553 ** 
US                    -7.060852   7.686109  -0.919  0.35839    
Web.order             15.070681   5.941364   2.537  0.01127 *  
Gender.male           -1.721388   5.850799  -0.294  0.76862    
Address_is_res       -84.963342   7.301762 -11.636  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 130.4 on 1993 degrees of freedom
Multiple R-squared:  0.514, Adjusted R-squared:  0.5125 
F-statistic: 351.3 on 6 and 1993 DF,  p-value: < 2.2e-16

#The equatiion is “Spending =4.235190 + 94.56Freq - 0.0076last_update_days_ago - 7.06US + 15.07Web.order - 1.721388Gender.male - 84.96 Address_is_res” #Type of purchases resulting higher Spending are #Customers with higher Freq #Customer with Web Order # Using backward elimination we would first eliminate “Gender,male with highest Pr value.

library(car)
vif(lm.fit)
                Freq last_update_days_ago                   US 
            1.182859             1.167494             1.005244 
           Web.order          Gender.male       Address_is_res 
            1.015042             1.003961             1.079383 
Spending_pred <- predict(lm.fit)
head(Spending_pred)
         1          2          3          4          5          6 
 88.326914 -11.720632 156.524408  83.661345  85.075916  -8.524337 
Spending_resid <- residuals(lm.fit)
head(Spending_resid)
         1          2          3          4          5          6 
 39.673086  11.720632 -29.524408 -83.661345 -85.075916   8.524337 

Another way of partition

# partition data
set.seed(1) # set seed for reproducing the partition
train.index <- sample(c(1:2000), 1600)
Mydata_train <- Mydata[train.index, ]
Mydata_test <- Mydata[-train.index, ]

Now fit the regression model with the train dateset

model_train <- lm(Spending ~ Freq + last_update_days_ago + US + Web.order + Gender.male + Address_is_res, data = Mydata_train)
model_train_summ <- summary(model_train)
model_train_summ$r.squared
[1] 0.4859717
y_test <- Mydata_test$Spending
yhat_test <- predict(model_train, newdata = Mydata_test)
n_test <- length(Mydata_test$Spending)
# test RMSE
rmse <- sqrt(sum((y_test - yhat_test)^2) / n_test)
rmse
[1] 116.5629

Try doing this analysis, starting with one predictor variables. Then sequentially add or drop variables till you find the best combination of predictor variables. Is this the best way to find the bext regression model?

Regression diagnostics 1

hist(lm.fit$residuals)

NA
NA

##Histogram of model residual is plotted. ##The histogram is a Normal Distributio. This improves accuracy of the prediction.

Note that in Normal Q-Q plot residuals are very close to the straight line. Hence, we can conclude that Normality Assumption is followed considering the size of dataset.

Testing for the assumption of Homoscedasticity.

Null hypothesis : Residuals are homoscedastic.

Alternate hypothesis : Residuals are not homoscedastic.

lmtest::bptest(lm.fit)  # Breusch-Pagan test
car::ncvTest(lm.fit)

Testing for assumption of non auto-correlation

Null hypothesis : Residuals have zero auto-correlation.

Are there unusual observations? Interpret and discuss.

#Single variable analysis through box-plots
outlier_values <- boxplot.stats(Mydata$Spending)$out  # outlier values.
outlier_values
boxplot(Mydata$Spending, main="Spending", boxwex=0.1)
mtext(paste("Outliers: ", paste(outlier_values, collapse=", ")), cex=0.6)

Data sets can sometimes contain outliers that are suspected to be anomalies (perhaps because of data collection errors or just plain old flukes). If outliers are present, the whisker on the appropriate side is drawn to 1.5 * Inter Quartile Range (IQR) rather than the data minimum or the data maximum. Small circles or unfilled dots are drawn on the chart to indicate where suspected outliers lie. Filled circles are used for known outliers. (http://r-statistics.co/Outlier-Treatment-With-R.html)

LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQgb24gTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24iDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyMgUHJlZGljdGluZyBTb2Z0d2FyZSBSZXNlbGxpbmcgUHJvZml0cyAgDQoNCmBgYHtyfQ0KTXlkYXRhMSA8LSByZWFkLmNzdigiVGF5a28uY3N2IikNCmhlYWQoTXlkYXRhMSkgDQojcHJpbnRzIHRoZSBmaXJzdCBmZXcgcm93cyBvZiB0aGUgZGF0YXNldC4gWW91IGNhbiB2aWV3IHRoZSBjb21wbGV0ZSBkYXRhc2V0IG9uIGEgc3ByZWFkc2hlZXQgYnkgZG91YmxlIGNsaWNraW5nIG9uIHRoZSBmaWxlIG5hbWUgaW4gdGhlIGVudmlyb25tZW50IHdpbmRvdw0KYGBgDQpZb3UgbWF5IG5vdCBuZWVkIGluIHRoZSBkYXRhc2V0LCB0aGUgZmlyc3QgY29sdW1uIChpLmUuIHZhcmlhYmxlIFgpLiBJdCBjYW4gYmUgZHJvcHBlZCB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGVzIA0KDQpgYGB7cn0NCk15ZGF0YSA8LSB3aXRoaW4oTXlkYXRhMSwgcm0oc2VxdWVuY2VfbnVtYmVyLHNvdXJjZV9hLHNvdXJjZV9iLHNvdXJjZV9jLHNvdXJjZV9kLHNvdXJjZV9lLHNvdXJjZV9tLHNvdXJjZV9vLHNvdXJjZV9oLHNvdXJjZV9yLHNvdXJjZV9zLHNvdXJjZV90LHNvdXJjZV91LHNvdXJjZV9wLHNvdXJjZV94LHNvdXJjZV93LFgxc3RfdXBkYXRlX2RheXNfYWdvLFB1cmNoYXNlKSkNCmBgYA0KSW5zcGVjdCB0aGUgZGF0YWZyYW1lIGFzIGEgdGFibGUgYW5kIHByaW50IHZhcmlhYmxlIG5hbWVzLiANCmBgYHtyfQ0KZml4KE15ZGF0YSkNCiMgb3V0cHV0dGluZyBkYXRhIGNvbHVtbiBuYW1lcw0KbmFtZXMoTXlkYXRhKQ0KIyBvdXRwdXR0aW5nIGRhdGENCndyaXRlLnRhYmxlKE15ZGF0YSkNCmBgYA0KU2VhcmNoIGlmIHRoZSBkYXRhIGhhcyBhbnkgbWlzc2luZyB2YWx1ZXMuIE1hbnkgcHJvY2VkdXJlcyBkbyBub3Qgd29yayBpZiB0aGUgZGF0YSBoYXMgbWlzc2luZyB2YWx1ZXMgb3Igc3BlY2lhbCBpbnN0cnVjdGlvbnMgbmVlZCB0byBiZSBnaXZlbiB3aGVuIHRoZXkgYXJlIHByZXNlbnQuDQpgYGB7cn0NCiMjTnVtYmVyIG9mIG1pc3NpbmcgdmFsdWVzIChOQXMpIGluIHRoZSBkYXRhZnJhbWUNCnN1bShpcy5uYShNeWRhdGEpKQ0KIyNUbyBjb21wdXRlIHRoZSB0b3RhbCBtaXNzaW5nIHZhbHVlcyBpbiBlYWNoIGNvbHVtbiBpcyB0byB1c2UgY29sU3VtcygpDQojY29sU3Vtcyhpcy5uYShNeWRhdGEpKQ0KY29sU3Vtcyhpcy5uYShNeWRhdGEpKQ0KIyNTaW1wbGUgd2F5cyB0byBkZWFsIHdpdGggbWlzc2luZyB2YWx1ZSBodHRwczovL3VjLXIuZ2l0aHViLmlvL21pc3NpbmdfdmFsdWVzIA0KYGBgDQpOb3cgdGhlIGRhdGEgaXMgcmVhZHkgZm9yIGFuYWx5c2lzLiBJdCBpcyBhbHdheXMgYSBnb29kIGlkZWEgdG8gc3RhcnQgd2l0aCBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIHRlY2huaXF1ZXMuIA0KYGBge3J9DQojVG8gcHJpbnQgdGhlIHZhcmlhYmxlIG5hbWVzIGFuZCB0eXBlIG9mIGRhdGEgdW5kZXIgZWFjaCB2YXJpYWJsZQ0Kc3RyKE15ZGF0YSkNCmBgYA0KYGBge3J9DQojIFBsb3QgdGhlIGRhdGEgZm9yIGVhY2ggY29sdW1uIHRvIGdldCBhIGZlZWwgZm9yIGl0DQojIGFzIHdlbGwgYXMgYSBoaXN0b2dyYW0gdG8gc2VlIHRoZSBkYXRhIGRpc3RyaWJ1dGlvbg0KI2xvYWRpbmcgcGFja2FnZXMNCnJlcXVpcmUoImdncGxvdDIiKQ0KDQpwbG90KE15ZGF0YSRGcmVxLCBNeWRhdGEkU3BlbmRpbmcpDQoNCnBsb3QoTXlkYXRhJGxhc3RfdXBkYXRlX2RheXNfYWdvLCBNeWRhdGEkU3BlbmRpbmcpDQoNCmBgYA0KDQpGb3IgZGVzY3JpcHRpdmUgc3RhdGlzdGljcywgdGhlIGZ1bmN0aW9uICdzdW1tYXJ5JyBjYW4gYmUgdXNlZC4gVGhlIHBhY2thZ2UgJ3Bhc3RlY3MnIGlzIHVzZWQgYmVsb3cgZm9yIGEgbW9yZSBkZXRhaWxlZCBvdXRwdXQuIERvIGNoZWNrIGZvciBtaXNzaW5nIHZhbHVlcyBhbmQgdHlwZSBvZiB2YXJpYWJsZSAobXVzdCBiZSBudW1lcmljKS4gDQpgYGB7cn0NCiMgQ29tcHV0ZSBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzDQpsaWJyYXJ5KHBhc3RlY3MpDQpyZXMgPC0gc3RhdC5kZXNjKE15ZGF0YSkNCnJvdW5kKHJlcywgMikNCmBgYA0KDQpgYGB7cn0NCiMgQ29ycmVsYXRpb24gbWF0cml4IGFuZCByZXNwZWN0aXZlIHAtdmFsdWVzDQpsaWJyYXJ5KCJIbWlzYyIpDQpyZXMyIDwtIHJjb3JyKGFzLm1hdHJpeChNeWRhdGEpKQ0KcmVzMg0KYGBgDQoNCg0KRXZhbHVhdGUgdGhlIGxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiB0d28gdmFyaWFibGVzIGJ5IGJ1aWxkaW5nIHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbA0KYGBge3J9DQpmaXQxIDwtIGxtKFNwZW5kaW5nfkZyZXEsIGRhdGEgPSBNeWRhdGEpDQpzdW1tYXJ5KGZpdDEpDQpjb25maW50KGZpdDEpICNDb25maWRlbmNlIGludGVydmFsIGVzdGltYXRlcw0KDQpwbG90KGZpdDEpDQpjb25maW50KGZpdDEpICNDb25maWRlbmNlIGludGVydmFsIGVzdGltYXRlcw0KDQoNCmBgYA0KI1RoZSBlcXVhdGlpb24gaXMgU3BlbmRpbmcgPSAtMjcuNSArIDkxLjgzMSpGcmVxIA0KI011bHRpcGxlIFItc3F1YXJlZDogIDAuNDc3NyBpcyBub3QgdmVyeSBoaWdoLg0KIyBwLXZhbHVlIGlzIHF1aXRlIGxvdyBhbmQgbGVzcyB0aGFuIDAuMDUuIA0KDQpgYGB7cn0NCmZpdDIgPC0gbG0oU3BlbmRpbmd+bGFzdF91cGRhdGVfZGF5c19hZ28sIGRhdGEgPSBNeWRhdGEpDQpzdW1tYXJ5KGZpdDIpDQpjb25maW50KGZpdDIpICNDb25maWRlbmNlIGludGVydmFsIGVzdGltYXRlcw0KDQpwbG90KGZpdDIpDQpgYGANCiNUaGUgZXF1YXRpaW9uIGlzIFNwZW5kaW5nID0gLSAxOTMuMjM3NDExIC0wLjA0MjA0NiAqbGFzdF91cGRhdGVfZGF5c19hZ28gDQojTXVsdGlwbGUgUi1zcXVhcmVkOiAgMC4wNjYgaXMgdmVyeSBsb3cuDQojIEhvd2V2ZXIsIHAtdmFsdWUgaXMgcXVpdGUgbG93IGFuZCBsZXNzIHRoYW4gMC4wNS4uDQpgYGB7cn0NCiMgTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24NCmxtLmZpdD1sbShTcGVuZGluZyB+IEZyZXEgKyBsYXN0X3VwZGF0ZV9kYXlzX2FnbyArIFVTICsgV2ViLm9yZGVyICsgR2VuZGVyLm1hbGUgKyBBZGRyZXNzX2lzX3JlcywgZGF0YSA9IE15ZGF0YSkNCnN1bW1hcnkobG0uZml0KQ0KYGBgDQojVGhlIGVxdWF0aWlvbiBpcyAiU3BlbmRpbmcgPTQuMjM1MTkwICsgOTQuNTYqRnJlcSAtIDAuMDA3NipsYXN0X3VwZGF0ZV9kYXlzX2FnbyAtIDcuMDYqVVMgKyAxNS4wNypXZWIub3JkZXIgLSAxLjcyMTM4OCpHZW5kZXIubWFsZSAtIDg0Ljk2KiBBZGRyZXNzX2lzX3JlcyAiDQojVHlwZSBvZiBwdXJjaGFzZXMgcmVzdWx0aW5nIGhpZ2hlciBTcGVuZGluZyBhcmUgDQojQ3VzdG9tZXJzIHdpdGggaGlnaGVyIEZyZXENCiNDdXN0b21lciB3aXRoIFdlYiBPcmRlcg0KIyBVc2luZyBiYWNrd2FyZCBlbGltaW5hdGlvbiB3ZSB3b3VsZCBmaXJzdCBlbGltaW5hdGUgIkdlbmRlcixtYWxlIHdpdGggaGlnaGVzdCBQciB2YWx1ZS4gDQpgYGB7cn0NCmxpYnJhcnkoY2FyKQ0KdmlmKGxtLmZpdCkNCmBgYA0KDQpgYGB7cn0NClNwZW5kaW5nX3ByZWQgPC0gcHJlZGljdChsbS5maXQpDQpoZWFkKFNwZW5kaW5nX3ByZWQpDQpTcGVuZGluZ19yZXNpZCA8LSByZXNpZHVhbHMobG0uZml0KQ0KaGVhZChTcGVuZGluZ19yZXNpZCkNCg0KYGBgDQojIyMgQW5vdGhlciB3YXkgb2YgcGFydGl0aW9uDQpgYGB7cn0NCiMgcGFydGl0aW9uIGRhdGENCnNldC5zZWVkKDEpICMgc2V0IHNlZWQgZm9yIHJlcHJvZHVjaW5nIHRoZSBwYXJ0aXRpb24NCnRyYWluLmluZGV4IDwtIHNhbXBsZShjKDE6MjAwMCksIDE2MDApDQpNeWRhdGFfdHJhaW4gPC0gTXlkYXRhW3RyYWluLmluZGV4LCBdDQpNeWRhdGFfdGVzdCA8LSBNeWRhdGFbLXRyYWluLmluZGV4LCBdDQpgYGANCg0KDQoNCg0KDQpOb3cgZml0IHRoZSByZWdyZXNzaW9uIG1vZGVsIHdpdGggdGhlIHRyYWluIGRhdGVzZXQNCmBgYHtyfQ0KbW9kZWxfdHJhaW4gPC0gbG0oU3BlbmRpbmcgfiBGcmVxICsgbGFzdF91cGRhdGVfZGF5c19hZ28gKyBVUyArIFdlYi5vcmRlciArIEdlbmRlci5tYWxlICsgQWRkcmVzc19pc19yZXMsIGRhdGEgPSBNeWRhdGFfdHJhaW4pDQptb2RlbF90cmFpbl9zdW1tIDwtIHN1bW1hcnkobW9kZWxfdHJhaW4pDQptb2RlbF90cmFpbl9zdW1tJHIuc3F1YXJlZA0KYGBgDQoNCmBgYHtyfQ0KeV90ZXN0IDwtIE15ZGF0YV90ZXN0JFNwZW5kaW5nDQp5aGF0X3Rlc3QgPC0gcHJlZGljdChtb2RlbF90cmFpbiwgbmV3ZGF0YSA9IE15ZGF0YV90ZXN0KQ0Kbl90ZXN0IDwtIGxlbmd0aChNeWRhdGFfdGVzdCRTcGVuZGluZykNCiMgdGVzdCBSTVNFDQpybXNlIDwtIHNxcnQoc3VtKCh5X3Rlc3QgLSB5aGF0X3Rlc3QpXjIpIC8gbl90ZXN0KQ0Kcm1zZQ0KYGBgDQoNCg0KVHJ5IGRvaW5nIHRoaXMgYW5hbHlzaXMsIHN0YXJ0aW5nIHdpdGggb25lIHByZWRpY3RvciB2YXJpYWJsZXMuIFRoZW4gc2VxdWVudGlhbGx5IGFkZCBvciBkcm9wIHZhcmlhYmxlcyB0aWxsIHlvdSBmaW5kIHRoZSBiZXN0IGNvbWJpbmF0aW9uIG9mIHByZWRpY3RvciB2YXJpYWJsZXMuIElzIHRoaXMgdGhlIGJlc3Qgd2F5IHRvIGZpbmQgdGhlIGJleHQgcmVncmVzc2lvbiBtb2RlbD8NCg0KDQojIyMgUmVncmVzc2lvbiBkaWFnbm9zdGljcyAxDQoNCmBgYHtyfQ0KaGlzdChsbS5maXQkcmVzaWR1YWxzKQ0KDQoNCmBgYA0KIyNIaXN0b2dyYW0gb2YgbW9kZWwgcmVzaWR1YWwgaXMgcGxvdHRlZC4NCiMjVGhlIGhpc3RvZ3JhbSBpcyBhIE5vcm1hbCBEaXN0cmlidXRpby4gVGhpcyBpbXByb3ZlcyBhY2N1cmFjeSBvZiB0aGUgcHJlZGljdGlvbi4NCg0KIyMjIyBOb3RlIHRoYXQgaW4gTm9ybWFsIFEtUSBwbG90IHJlc2lkdWFscyBhcmUgdmVyeSBjbG9zZSB0byB0aGUgc3RyYWlnaHQgbGluZS4gSGVuY2UsIHdlIGNhbiBjb25jbHVkZSB0aGF0IE5vcm1hbGl0eSBBc3N1bXB0aW9uIGlzIGZvbGxvd2VkIGNvbnNpZGVyaW5nIHRoZSBzaXplIG9mIGRhdGFzZXQuDQoNCiMjIFRlc3RpbmcgZm9yIHRoZSBhc3N1bXB0aW9uIG9mIEhvbW9zY2VkYXN0aWNpdHkuIA0KIyMjIE51bGwgaHlwb3RoZXNpcyA6IFJlc2lkdWFscyBhcmUgaG9tb3NjZWRhc3RpYy4NCiMjIyBBbHRlcm5hdGUgaHlwb3RoZXNpcyA6IFJlc2lkdWFscyBhcmUgbm90IGhvbW9zY2VkYXN0aWMuDQoNCmBgYHtyfQ0KbG10ZXN0OjpicHRlc3QobG0uZml0KSAgIyBCcmV1c2NoLVBhZ2FuIHRlc3QNCmNhcjo6bmN2VGVzdChsbS5maXQpDQpgYGANCg0KIyMjIFRlc3RpbmcgZm9yIGFzc3VtcHRpb24gb2Ygbm9uIGF1dG8tY29ycmVsYXRpb24NCiMjIyMgTnVsbCBoeXBvdGhlc2lzIDogUmVzaWR1YWxzIGhhdmUgemVybyBhdXRvLWNvcnJlbGF0aW9uLg0KDQoNCkFyZSB0aGVyZSB1bnVzdWFsIG9ic2VydmF0aW9ucz8gSW50ZXJwcmV0IGFuZCBkaXNjdXNzLg0KDQpgYGB7cn0NCiNTaW5nbGUgdmFyaWFibGUgYW5hbHlzaXMgdGhyb3VnaCBib3gtcGxvdHMNCm91dGxpZXJfdmFsdWVzIDwtIGJveHBsb3Quc3RhdHMoTXlkYXRhJFNwZW5kaW5nKSRvdXQgICMgb3V0bGllciB2YWx1ZXMuDQpvdXRsaWVyX3ZhbHVlcw0KYm94cGxvdChNeWRhdGEkU3BlbmRpbmcsIG1haW49IlNwZW5kaW5nIiwgYm94d2V4PTAuMSkNCm10ZXh0KHBhc3RlKCJPdXRsaWVyczogIiwgcGFzdGUob3V0bGllcl92YWx1ZXMsIGNvbGxhcHNlPSIsICIpKSwgY2V4PTAuNikNCmBgYA0KIyMjIERhdGEgc2V0cyBjYW4gc29tZXRpbWVzIGNvbnRhaW4gb3V0bGllcnMgdGhhdCBhcmUgc3VzcGVjdGVkIHRvIGJlIGFub21hbGllcyAocGVyaGFwcyBiZWNhdXNlIG9mIGRhdGEgY29sbGVjdGlvbiBlcnJvcnMgb3IganVzdCBwbGFpbiBvbGQgZmx1a2VzKS4gSWYgb3V0bGllcnMgYXJlIHByZXNlbnQsIHRoZSB3aGlza2VyIG9uIHRoZSBhcHByb3ByaWF0ZSBzaWRlIGlzIGRyYXduIHRvIDEuNSAqIEludGVyIFF1YXJ0aWxlIFJhbmdlIChJUVIpIHJhdGhlciB0aGFuIHRoZSBkYXRhIG1pbmltdW0gb3IgdGhlIGRhdGEgbWF4aW11bS4gU21hbGwgY2lyY2xlcyBvciB1bmZpbGxlZCBkb3RzIGFyZSBkcmF3biBvbiB0aGUgY2hhcnQgdG8gaW5kaWNhdGUgd2hlcmUgc3VzcGVjdGVkIG91dGxpZXJzIGxpZS4gRmlsbGVkIGNpcmNsZXMgYXJlIHVzZWQgZm9yIGtub3duIG91dGxpZXJzLiAoaHR0cDovL3Itc3RhdGlzdGljcy5jby9PdXRsaWVyLVRyZWF0bWVudC1XaXRoLVIuaHRtbCkNCg==