Predicting Software Reselling Profits

Tayko Software is a software catalog firm that sells games and educational software. It started out as a software manufacturer and then added third-party titles to its offerings. It recently revised its collection of items in a new catalog, which it mailed out to its customers. This mailing yielded 2000 purchases. Based on these data, Tayko wants to devise a model for predicting the spending amount that a purchasing customer will yield.

Tykodata1 <- read.csv("Tayko.csv")
head(Tykodata) 
#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

Variables not needed are dropped from the dataset using the following codes.

Tykodata <- within(Tykodata1, 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(Tykodata)
# outputting data column names
names(Tykodata)
# outputting data
write.table(Tykodata)

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(Tykodata))
[1] 0
## Identify NAs in full data frame
#is.na(Tykodata)
## Identify NAs in specific data frame column
#is.na(Tykodata$Freq)
##To compute the total missing values in each column is to use colSums()
#colSums(is.na(Tykodata))
colSums(is.na(Tykodata))
                  US                 Freq last_update_days_ago            Web.order          Gender.male       Address_is_res             Spending 
                   0                    0                    0                    0                    0                    0                    0 
##Simple ways to deal with missing value https://uc-r.github.io/missing_values 

No Missing data

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(Tykodata)
'data.frame':   2000 obs. of  7 variables:
 $ US                  : num  1 1 1 1 1 1 1 1 1 1 ...
 $ Freq                : num  2 0 2 1 1 1 2 1 4 1 ...
 $ last_update_days_ago: num  3662 2900 3883 829 869 ...
 $ Web.order           : num  1 1 0 0 0 0 0 0 1 0 ...
 $ Gender.male         : num  0 1 0 1 0 0 0 1 1 0 ...
 $ Address_is_res      : num  1 0 0 0 0 1 1 0 0 0 ...
 $ Spending            : num  128 0 127 0 0 0 0 0 489 174 ...

plot(Tykodata$Freq, Tykodata$Spending)


plot(Tykodata$last_update_days_ago, Tykodata$Spending)

There seems to be not very good linear realtionship reflecting from the scatter plot as above. However, we carry out linear regression below to examine the relationship.

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

n= 2000 


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

High positive correlationship exists between Spending and Freq characterised by 0.69 and p-value is also 0. Moderate negative correlation exists between Spending and last_update_days-ago characterised by value -0.35 and p-value is 0.

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

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

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

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
qplot(Tykodata$Freq, Tykodata$Spending)


plot(fit1)

#The equatiion is

Spending = 91.831*Freq - 27.5

#Multiple R-squared: 0.4777 is not very high so a moderate linear relationship existsas reflected in the scatter lot above. # p-value is quite low and less than 0.05 hence this model fits data well.

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

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

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
qplot(Tykodata$last_update_days_ago, Tykodata$Spending)


plot(fit2)

#The equatiion is

Spending = -0.042046 *last_update_days_ago - 193.237411

#Multiple R-squared: 0.066 is very low hence very poor linear relationship exists as reflected in scatter plot above. # p-value is quite low and less than 0.05 hence this model fits data well.

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

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

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.567186 x Freq - 0.007670 x last_update_days_ago - 7.060852 x US + 15.070681 x Web.order - 1.721388 x Gender.male - 84.963342 x Address_is_res”

#Type of purchases resulting higher Spending are

#Customers with higher Freq

#Customer with Web Order

Using backward elimination

we carry out Multiple Regression by eliminating one variable at each time and repeat the process.

Here in this case we see that removing Gender.male doesn’t affect R-square value while removing others lowers it. Hence using backward elimination we would eliminate Gender.male first. This is reflected in the highest value of Pr value.

library(car)
vif(lm.fit)
                Freq last_update_days_ago                   US            Web.order          Gender.male       Address_is_res 
            1.182859             1.167494             1.005244             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 

Partition

# partition data
set.seed(1) # set seed for reproducing the partition
train.index <- sample(c(1:2000), 1400)
Tykodata_train <- Tykodata[train.index, ]
Tykodata_test <- Tykodata[-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 = Tykodata_train)
model_train_summ <- summary(model_train)
model_train_summ$r.squared
[1] 0.4719376

Interpreation

The model explains about 47.19% of variation in the value we obtain of Spending.

y_test <- Tykodata_test$Spending
yhat_test <- predict(model_train, newdata = Tykodata_test)
n_test <- length(Tykodata_test$Spending)
# test RMSE
rmse <- sqrt(sum((y_test - yhat_test)^2) / n_test)
rmse
[1] 123.228

Interpretation

This is a very high value of rmse which indicates wide scattering around the line of fit we obtained.

Regression diagnostics 1

hist(lm.fit$residuals)

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

LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQgb24gTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24gc3VibWl0dGVkIGJ5IEphd2FoYXIgTGFsIFMwNzIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCg0KLS0tDQoNCiMjIyBQcmVkaWN0aW5nIFNvZnR3YXJlIFJlc2VsbGluZyBQcm9maXRzICANClRheWtvIFNvZnR3YXJlIGlzIGEgc29mdHdhcmUgY2F0YWxvZyBmaXJtIHRoYXQgc2VsbHMgZ2FtZXMgYW5kIGVkdWNhdGlvbmFsIHNvZnR3YXJlLiBJdA0Kc3RhcnRlZCBvdXQgYXMgYSBzb2Z0d2FyZSBtYW51ZmFjdHVyZXIgYW5kIHRoZW4gYWRkZWQgdGhpcmQtcGFydHkgdGl0bGVzIHRvIGl0cyBvZmZlcmluZ3MuIEl0DQpyZWNlbnRseSByZXZpc2VkIGl0cyBjb2xsZWN0aW9uIG9mIGl0ZW1zIGluIGEgbmV3IGNhdGFsb2csIHdoaWNoIGl0IG1haWxlZCBvdXQgdG8gaXRzIGN1c3RvbWVycy4NClRoaXMgbWFpbGluZyB5aWVsZGVkIDIwMDAgcHVyY2hhc2VzLiBCYXNlZCBvbiB0aGVzZSBkYXRhLCBUYXlrbyB3YW50cyB0byBkZXZpc2UgYSBtb2RlbCBmb3INCnByZWRpY3RpbmcgdGhlIHNwZW5kaW5nIGFtb3VudCB0aGF0IGEgcHVyY2hhc2luZyBjdXN0b21lciB3aWxsIHlpZWxkLg0KDQpgYGB7cn0NClR5a29kYXRhMSA8LSByZWFkLmNzdigiVGF5a28uY3N2IikNCmhlYWQoVHlrb2RhdGEpIA0KI3ByaW50cyB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgdGhlIGRhdGFzZXQuIFlvdSBjYW4gdmlldyB0aGUgY29tcGxldGUgZGF0YXNldCBvbiBhIHNwcmVhZHNoZWV0IGJ5IGRvdWJsZSBjbGlja2luZyBvbiB0aGUgZmlsZSBuYW1lIGluIHRoZSBlbnZpcm9ubWVudCB3aW5kb3cNCmBgYA0KVmFyaWFibGVzIG5vdCBuZWVkZWQgYXJlIGRyb3BwZWQgZnJvbSB0aGUgZGF0YXNldCB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGVzLiANCg0KYGBge3J9DQpUeWtvZGF0YSA8LSB3aXRoaW4oVHlrb2RhdGExLCBybShzZXF1ZW5jZV9udW1iZXIsc291cmNlX2Esc291cmNlX2Isc291cmNlX2Msc291cmNlX2Qsc291cmNlX2Usc291cmNlX20sc291cmNlX28sc291cmNlX2gsc291cmNlX3Isc291cmNlX3Msc291cmNlX3Qsc291cmNlX3Usc291cmNlX3Asc291cmNlX3gsc291cmNlX3csWDFzdF91cGRhdGVfZGF5c19hZ28sUHVyY2hhc2UpKQ0KYGBgDQpJbnNwZWN0IHRoZSBkYXRhZnJhbWUgYXMgYSB0YWJsZSBhbmQgcHJpbnQgdmFyaWFibGUgbmFtZXMuIA0KYGBge3J9DQpmaXgoVHlrb2RhdGEpDQojIG91dHB1dHRpbmcgZGF0YSBjb2x1bW4gbmFtZXMNCm5hbWVzKFR5a29kYXRhKQ0KIyBvdXRwdXR0aW5nIGRhdGENCndyaXRlLnRhYmxlKFR5a29kYXRhKQ0KYGBgDQpTZWFyY2ggaWYgdGhlIGRhdGEgaGFzIGFueSBtaXNzaW5nIHZhbHVlcy4gTWFueSBwcm9jZWR1cmVzIGRvIG5vdCB3b3JrIGlmIHRoZSBkYXRhIGhhcyBtaXNzaW5nIHZhbHVlcyBvciBzcGVjaWFsIGluc3RydWN0aW9ucyBuZWVkIHRvIGJlIGdpdmVuIHdoZW4gdGhleSBhcmUgcHJlc2VudC4NCmBgYHtyfQ0KIyNOdW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMgKE5BcykgaW4gdGhlIGRhdGFmcmFtZQ0Kc3VtKGlzLm5hKFR5a29kYXRhKSkNCiMjIElkZW50aWZ5IE5BcyBpbiBmdWxsIGRhdGEgZnJhbWUNCiNpcy5uYShUeWtvZGF0YSkNCiMjIElkZW50aWZ5IE5BcyBpbiBzcGVjaWZpYyBkYXRhIGZyYW1lIGNvbHVtbg0KI2lzLm5hKFR5a29kYXRhJEZyZXEpDQojI1RvIGNvbXB1dGUgdGhlIHRvdGFsIG1pc3NpbmcgdmFsdWVzIGluIGVhY2ggY29sdW1uIGlzIHRvIHVzZSBjb2xTdW1zKCkNCiNjb2xTdW1zKGlzLm5hKFR5a29kYXRhKSkNCmNvbFN1bXMoaXMubmEoVHlrb2RhdGEpKQ0KIyNTaW1wbGUgd2F5cyB0byBkZWFsIHdpdGggbWlzc2luZyB2YWx1ZSBodHRwczovL3VjLXIuZ2l0aHViLmlvL21pc3NpbmdfdmFsdWVzIA0KYGBgDQojIE5vIE1pc3NpbmcgZGF0YSANCg0KTm93IHRoZSBkYXRhIGlzIHJlYWR5IGZvciBhbmFseXNpcy4gSXQgaXMgYWx3YXlzIGEgZ29vZCBpZGVhIHRvIHN0YXJ0IHdpdGggZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyB0ZWNobmlxdWVzLiANCmBgYHtyfQ0KI1RvIHByaW50IHRoZSB2YXJpYWJsZSBuYW1lcyBhbmQgdHlwZSBvZiBkYXRhIHVuZGVyIGVhY2ggdmFyaWFibGUNCnN0cihUeWtvZGF0YSkNCmBgYA0KYGBge3J9DQoNCnBsb3QoVHlrb2RhdGEkRnJlcSwgVHlrb2RhdGEkU3BlbmRpbmcpDQoNCnBsb3QoVHlrb2RhdGEkbGFzdF91cGRhdGVfZGF5c19hZ28sIFR5a29kYXRhJFNwZW5kaW5nKQ0KDQpgYGANCiMgVGhlcmUgc2VlbXMgdG8gYmUgbm90IHZlcnkgZ29vZCBsaW5lYXIgcmVhbHRpb25zaGlwIHJlZmxlY3RpbmcgZnJvbSB0aGUgc2NhdHRlciBwbG90IGFzIGFib3ZlLiBIb3dldmVyLCB3ZSBjYXJyeSBvdXQgbGluZWFyIHJlZ3Jlc3Npb24gYmVsb3cgdG8gZXhhbWluZSB0aGUgcmVsYXRpb25zaGlwLg0KDQpgYGB7cn0NCiMgQ29tcHV0ZSBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzDQpsaWJyYXJ5KHBhc3RlY3MpDQpyZXMgPC0gc3RhdC5kZXNjKFR5a29kYXRhKQ0Kcm91bmQocmVzLCAyKQ0KYGBgDQoNCmBgYHtyfQ0KIyBDb3JyZWxhdGlvbiBtYXRyaXggYW5kIHJlc3BlY3RpdmUgcC12YWx1ZXMNCmxpYnJhcnkoIkhtaXNjIikNCnJlczIgPC0gcmNvcnIoYXMubWF0cml4KFR5a29kYXRhKSkNCnJlczINCmBgYA0KIyBIaWdoIHBvc2l0aXZlIGNvcnJlbGF0aW9uc2hpcCBleGlzdHMgYmV0d2VlbiBTcGVuZGluZyBhbmQgRnJlcSBjaGFyYWN0ZXJpc2VkIGJ5IDAuNjkgYW5kIHAtdmFsdWUgaXMgYWxzbyAwLiBNb2RlcmF0ZSBuZWdhdGl2ZSBjb3JyZWxhdGlvbiBleGlzdHMgYmV0d2VlbiBTcGVuZGluZyBhbmQgbGFzdF91cGRhdGVfZGF5cy1hZ28gY2hhcmFjdGVyaXNlZCBieSB2YWx1ZSAtMC4zNSBhbmQgcC12YWx1ZSBpcyAwLg0KDQpFdmFsdWF0ZSB0aGUgbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHR3byB2YXJpYWJsZXMgYnkgYnVpbGRpbmcgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsDQpgYGB7cn0NCmZpdDEgPC0gbG0oU3BlbmRpbmd+RnJlcSwgZGF0YSA9IFR5a29kYXRhKQ0Kc3VtbWFyeShmaXQxKQ0KY29uZmludChmaXQxKSAjQ29uZmlkZW5jZSBpbnRlcnZhbCBlc3RpbWF0ZXMNCg0KcXBsb3QoVHlrb2RhdGEkRnJlcSwgVHlrb2RhdGEkU3BlbmRpbmcpDQoNCnBsb3QoZml0MSkNCmBgYA0KI1RoZSBlcXVhdGlpb24gaXMgDQoNClNwZW5kaW5nID0gOTEuODMxKkZyZXEgLSAyNy41DQoNCiNNdWx0aXBsZSBSLXNxdWFyZWQ6ICAwLjQ3NzcgaXMgbm90IHZlcnkgaGlnaCBzbyBhIG1vZGVyYXRlIGxpbmVhciByZWxhdGlvbnNoaXAgZXhpc3RzYXMgcmVmbGVjdGVkIGluIHRoZSBzY2F0dGVyIGxvdCBhYm92ZS4NCiMgcC12YWx1ZSBpcyBxdWl0ZSBsb3cgYW5kIGxlc3MgdGhhbiAwLjA1IGhlbmNlIHRoaXMgbW9kZWwgZml0cyBkYXRhIHdlbGwuIA0KDQpgYGB7cn0NCmZpdDIgPC0gbG0oU3BlbmRpbmd+bGFzdF91cGRhdGVfZGF5c19hZ28sIGRhdGEgPSBUeWtvZGF0YSkNCnN1bW1hcnkoZml0MikNCmNvbmZpbnQoZml0MikgI0NvbmZpZGVuY2UgaW50ZXJ2YWwgZXN0aW1hdGVzDQoNCnFwbG90KFR5a29kYXRhJGxhc3RfdXBkYXRlX2RheXNfYWdvLCBUeWtvZGF0YSRTcGVuZGluZykNCg0KcGxvdChmaXQyKQ0KYGBgDQojVGhlIGVxdWF0aWlvbiBpcyANCg0KU3BlbmRpbmcgPSAtMC4wNDIwNDYgKmxhc3RfdXBkYXRlX2RheXNfYWdvIC0gMTkzLjIzNzQxMQ0KDQojTXVsdGlwbGUgUi1zcXVhcmVkOiAgMC4wNjYgaXMgdmVyeSBsb3cgaGVuY2UgdmVyeSBwb29yIGxpbmVhciByZWxhdGlvbnNoaXAgZXhpc3RzIGFzIHJlZmxlY3RlZCBpbiBzY2F0dGVyIHBsb3QgYWJvdmUuDQojIHAtdmFsdWUgaXMgcXVpdGUgbG93IGFuZCBsZXNzIHRoYW4gMC4wNSBoZW5jZSB0aGlzIG1vZGVsIGZpdHMgZGF0YSB3ZWxsLg0KYGBge3J9DQojIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uDQpsbS5maXQ9bG0oU3BlbmRpbmcgfiBGcmVxICsgbGFzdF91cGRhdGVfZGF5c19hZ28gKyBVUyArIFdlYi5vcmRlciArIEdlbmRlci5tYWxlICsgQWRkcmVzc19pc19yZXMsIGRhdGEgPSBUeWtvZGF0YSkNCnN1bW1hcnkobG0uZml0KQ0KYGBgDQojVGhlIGVxdWF0aWlvbiBpcyANCg0KIlNwZW5kaW5nID0gNC4yMzUxOTAgKyA5NC41NjcxODYgeCBGcmVxIC0gMC4wMDc2NzAgeCBsYXN0X3VwZGF0ZV9kYXlzX2FnbyAtIDcuMDYwODUyIHggVVMgKyAxNS4wNzA2ODEgeCBXZWIub3JkZXIgLSAxLjcyMTM4OCB4IEdlbmRlci5tYWxlIC0gODQuOTYzMzQyIHggQWRkcmVzc19pc19yZXMgIg0KDQojVHlwZSBvZiBwdXJjaGFzZXMgcmVzdWx0aW5nIGhpZ2hlciBTcGVuZGluZyBhcmUgDQoNCiNDdXN0b21lcnMgd2l0aCBoaWdoZXIgRnJlcQ0KDQojQ3VzdG9tZXIgd2l0aCBXZWIgT3JkZXINCg0KIyBVc2luZyBiYWNrd2FyZCBlbGltaW5hdGlvbiANCg0Kd2UgY2Fycnkgb3V0IE11bHRpcGxlIFJlZ3Jlc3Npb24gYnkgZWxpbWluYXRpbmcgb25lIHZhcmlhYmxlIGF0IGVhY2ggdGltZSBhbmQgcmVwZWF0IHRoZSBwcm9jZXNzLg0KDQpIZXJlIGluIHRoaXMgY2FzZSB3ZSBzZWUgdGhhdCByZW1vdmluZyBHZW5kZXIubWFsZSBkb2Vzbid0IGFmZmVjdCBSLXNxdWFyZSB2YWx1ZSB3aGlsZSByZW1vdmluZyBvdGhlcnMgbG93ZXJzIGl0LiBIZW5jZSB1c2luZyBiYWNrd2FyZCBlbGltaW5hdGlvbiB3ZSB3b3VsZCBlbGltaW5hdGUgR2VuZGVyLm1hbGUgZmlyc3QuIFRoaXMgaXMgcmVmbGVjdGVkIGluIHRoZSBoaWdoZXN0IHZhbHVlIG9mIFByIHZhbHVlLiAgICANCmBgYHtyfQ0KbGlicmFyeShjYXIpDQp2aWYobG0uZml0KQ0KYGBgDQoNCmBgYHtyfQ0KU3BlbmRpbmdfcHJlZCA8LSBwcmVkaWN0KGxtLmZpdCkNCmhlYWQoU3BlbmRpbmdfcHJlZCkNClNwZW5kaW5nX3Jlc2lkIDwtIHJlc2lkdWFscyhsbS5maXQpDQpoZWFkKFNwZW5kaW5nX3Jlc2lkKQ0KDQpgYGANCiMjIyBQYXJ0aXRpb24NCmBgYHtyfQ0KIyBwYXJ0aXRpb24gZGF0YQ0Kc2V0LnNlZWQoMSkgIyBzZXQgc2VlZCBmb3IgcmVwcm9kdWNpbmcgdGhlIHBhcnRpdGlvbg0KdHJhaW4uaW5kZXggPC0gc2FtcGxlKGMoMToyMDAwKSwgMTQwMCkNClR5a29kYXRhX3RyYWluIDwtIFR5a29kYXRhW3RyYWluLmluZGV4LCBdDQpUeWtvZGF0YV90ZXN0IDwtIFR5a29kYXRhWy10cmFpbi5pbmRleCwgXQ0KYGBgDQoNCk5vdyBmaXQgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCB0aGUgdHJhaW4gZGF0ZXNldA0KYGBge3J9DQptb2RlbF90cmFpbiA8LSBsbShTcGVuZGluZyB+IEZyZXEgKyBsYXN0X3VwZGF0ZV9kYXlzX2FnbyArIFVTICsgV2ViLm9yZGVyICsgR2VuZGVyLm1hbGUgKyBBZGRyZXNzX2lzX3JlcywgZGF0YSA9IFR5a29kYXRhX3RyYWluKQ0KbW9kZWxfdHJhaW5fc3VtbSA8LSBzdW1tYXJ5KG1vZGVsX3RyYWluKQ0KbW9kZWxfdHJhaW5fc3VtbSRyLnNxdWFyZWQNCmBgYA0KIyBJbnRlcnByZWF0aW9uIA0KDQpUaGUgbW9kZWwgZXhwbGFpbnMgYWJvdXQgNDcuMTklIG9mIHZhcmlhdGlvbiBpbiB0aGUgdmFsdWUgd2Ugb2J0YWluIG9mIFNwZW5kaW5nLg0KDQpgYGB7cn0NCnlfdGVzdCA8LSBUeWtvZGF0YV90ZXN0JFNwZW5kaW5nDQp5aGF0X3Rlc3QgPC0gcHJlZGljdChtb2RlbF90cmFpbiwgbmV3ZGF0YSA9IFR5a29kYXRhX3Rlc3QpDQpuX3Rlc3QgPC0gbGVuZ3RoKFR5a29kYXRhX3Rlc3QkU3BlbmRpbmcpDQojIHRlc3QgUk1TRQ0Kcm1zZSA8LSBzcXJ0KHN1bSgoeV90ZXN0IC0geWhhdF90ZXN0KV4yKSAvIG5fdGVzdCkNCnJtc2UNCmBgYA0KIyBJbnRlcnByZXRhdGlvbg0KDQpUaGlzIGlzIGEgdmVyeSBoaWdoIHZhbHVlIG9mIHJtc2Ugd2hpY2ggaW5kaWNhdGVzIHdpZGUgc2NhdHRlcmluZyBhcm91bmQgdGhlIGxpbmUgb2YgZml0IHdlIG9idGFpbmVkLg0KDQojIyMgUmVncmVzc2lvbiBkaWFnbm9zdGljcyAxDQoNCmBgYHtyfQ0KaGlzdChsbS5maXQkcmVzaWR1YWxzKQ0KDQpgYGANCiMjSGlzdG9ncmFtIG9mIG1vZGVsIHJlc2lkdWFsIGlzIHBsb3R0ZWQuDQojI1RoZSBoaXN0b2dyYW0gaXMgYSBOb3JtYWwgRGlzdHJpYnV0aW8uIFRoaXMgaW1wcm92ZXMgYWNjdXJhY3kgb2YgdGhlIHByZWRpY3Rpb24uDQoNCg==