Exercise 1: Boston dataset
DV: medv (median house value)
IVs: 12 quantitative variables
Boston_df = read.csv("Boston.csv", header = T)
Boston_df
str(Boston_df)
## 'data.frame': 506 obs. of 13 variables:
## $ crim : num 0.00632 0.02731 0.02729 0.03237 0.06905 ...
## $ zn : num 18 0 0 0 0 0 12.5 12.5 12.5 12.5 ...
## $ indus : num 2.31 7.07 7.07 2.18 2.18 2.18 7.87 7.87 7.87 7.87 ...
## $ chas : int 0 0 0 0 0 0 0 0 0 0 ...
## $ nox : num 0.538 0.469 0.469 0.458 0.458 0.458 0.524 0.524 0.524 0.524 ...
## $ rm : num 6.58 6.42 7.18 7 7.15 ...
## $ age : num 65.2 78.9 61.1 45.8 54.2 58.7 66.6 96.1 100 85.9 ...
## $ dis : num 4.09 4.97 4.97 6.06 6.06 ...
## $ rad : int 1 2 2 3 3 3 5 5 5 5 ...
## $ tax : int 296 242 242 222 222 222 311 311 311 311 ...
## $ ptratio: num 15.3 17.8 17.8 18.7 18.7 18.7 15.2 15.2 15.2 15.2 ...
## $ lstat : num 4.98 9.14 4.03 2.94 5.33 ...
## $ medv : num 24 21.6 34.7 33.4 36.2 28.7 22.9 27.1 16.5 18.9 ...
Let’s obtain the correlation matrix (a matrix that shows the correlation between each pair of variables)
cor(Boston_df)
## crim zn indus chas nox
## crim 1.00000000 -0.20046922 0.40658341 -0.055891582 0.42097171
## zn -0.20046922 1.00000000 -0.53382819 -0.042696719 -0.51660371
## indus 0.40658341 -0.53382819 1.00000000 0.062938027 0.76365145
## chas -0.05589158 -0.04269672 0.06293803 1.000000000 0.09120281
## nox 0.42097171 -0.51660371 0.76365145 0.091202807 1.00000000
## rm -0.21924670 0.31199059 -0.39167585 0.091251225 -0.30218819
## age 0.35273425 -0.56953734 0.64477851 0.086517774 0.73147010
## dis -0.37967009 0.66440822 -0.70802699 -0.099175780 -0.76923011
## rad 0.62550515 -0.31194783 0.59512927 -0.007368241 0.61144056
## tax 0.58276431 -0.31456332 0.72076018 -0.035586518 0.66802320
## ptratio 0.28994558 -0.39167855 0.38324756 -0.121515174 0.18893268
## lstat 0.45562148 -0.41299457 0.60379972 -0.053929298 0.59087892
## medv -0.38830461 0.36044534 -0.48372516 0.175260177 -0.42732077
## rm age dis rad tax ptratio
## crim -0.21924670 0.35273425 -0.37967009 0.625505145 0.58276431 0.2899456
## zn 0.31199059 -0.56953734 0.66440822 -0.311947826 -0.31456332 -0.3916785
## indus -0.39167585 0.64477851 -0.70802699 0.595129275 0.72076018 0.3832476
## chas 0.09125123 0.08651777 -0.09917578 -0.007368241 -0.03558652 -0.1215152
## nox -0.30218819 0.73147010 -0.76923011 0.611440563 0.66802320 0.1889327
## rm 1.00000000 -0.24026493 0.20524621 -0.209846668 -0.29204783 -0.3555015
## age -0.24026493 1.00000000 -0.74788054 0.456022452 0.50645559 0.2615150
## dis 0.20524621 -0.74788054 1.00000000 -0.494587930 -0.53443158 -0.2324705
## rad -0.20984667 0.45602245 -0.49458793 1.000000000 0.91022819 0.4647412
## tax -0.29204783 0.50645559 -0.53443158 0.910228189 1.00000000 0.4608530
## ptratio -0.35550149 0.26151501 -0.23247054 0.464741179 0.46085304 1.0000000
## lstat -0.61380827 0.60233853 -0.49699583 0.488676335 0.54399341 0.3740443
## medv 0.69535995 -0.37695457 0.24992873 -0.381626231 -0.46853593 -0.5077867
## lstat medv
## crim 0.4556215 -0.3883046
## zn -0.4129946 0.3604453
## indus 0.6037997 -0.4837252
## chas -0.0539293 0.1752602
## nox 0.5908789 -0.4273208
## rm -0.6138083 0.6953599
## age 0.6023385 -0.3769546
## dis -0.4969958 0.2499287
## rad 0.4886763 -0.3816262
## tax 0.5439934 -0.4685359
## ptratio 0.3740443 -0.5077867
## lstat 1.0000000 -0.7376627
## medv -0.7376627 1.0000000
If you only want to get the correlation between medv and each predictor, then:
cor(Boston_df)[,"medv"]
## crim zn indus chas nox rm age
## -0.3883046 0.3604453 -0.4837252 0.1752602 -0.4273208 0.6953599 -0.3769546
## dis rad tax ptratio lstat medv
## 0.2499287 -0.3816262 -0.4685359 -0.5077867 -0.7376627 1.0000000
sort(cor(Boston_df)[,"medv"])
## lstat ptratio indus tax nox crim rad
## -0.7376627 -0.5077867 -0.4837252 -0.4685359 -0.4273208 -0.3883046 -0.3816262
## age chas dis zn rm medv
## -0.3769546 0.1752602 0.2499287 0.3604453 0.6953599 1.0000000
If you want the correlations sorted by absolute value:
sort(abs(cor(Boston_df)[,"medv"]))
## chas dis zn age rad crim nox tax
## 0.1752602 0.2499287 0.3604453 0.3769546 0.3816262 0.3883046 0.4273208 0.4685359
## indus ptratio rm lstat medv
## 0.4837252 0.5077867 0.6953599 0.7376627 1.0000000
Let’s run a multiple linear regression with all 12 predictors
medv_multiple_out = lm(medv ~., data= Boston_df)
summary(medv_multiple_out)
##
## Call:
## lm(formula = medv ~ ., data = Boston_df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.1304 -2.7673 -0.5814 1.9414 26.2526
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 41.617270 4.936039 8.431 3.79e-16 ***
## crim -0.121389 0.033000 -3.678 0.000261 ***
## zn 0.046963 0.013879 3.384 0.000772 ***
## indus 0.013468 0.062145 0.217 0.828520
## chas 2.839993 0.870007 3.264 0.001173 **
## nox -18.758022 3.851355 -4.870 1.50e-06 ***
## rm 3.658119 0.420246 8.705 < 2e-16 ***
## age 0.003611 0.013329 0.271 0.786595
## dis -1.490754 0.201623 -7.394 6.17e-13 ***
## rad 0.289405 0.066908 4.325 1.84e-05 ***
## tax -0.012682 0.003801 -3.337 0.000912 ***
## ptratio -0.937533 0.132206 -7.091 4.63e-12 ***
## lstat -0.552019 0.050659 -10.897 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 4.798 on 493 degrees of freedom
## Multiple R-squared: 0.7343, Adjusted R-squared: 0.7278
## F-statistic: 113.5 on 12 and 493 DF, p-value: < 2.2e-16
Interpretation of results. The t values and their corresponding PV values
The first predictor shown in the results is crim (the crime rate in the neighborhood). The PV associated to the t value for crim is 0.000261, very small. We reject Ho, thus, we conclude that the relationship between crime rate and medv is statistically significant… However, these t tests are PARTIAL t tests !!!
What is a partial t test?
In the case of crim, partial t test means that adding crim to an equation that contains all other predictors does some meaningful from a statistical point of view.
In more technical terms, adding crim to an equation that contains all other predictors reduces an amount of variability in medv that is statistically significant (= the amount of variability reduced by adding crim is not random, it is a real amount of variability).
The PV for the partial t test for crim does NOT necessarily mean that crim is a good predictor by itself. It means that adding crim to an equation that already has all other predictors has a positive contribution from a statistical point of view.
The difference between a partial t test and a regular t test
Let’s analyze the partial t test for age. The p value (0.786) is above 0.05. Thus, we can conclude that when all other predictors are considered part of the equation, adding age does NOT produce a statistically significant effect on medv.
Interestingly, when we study age by itself, it has a significant effect on medv. See next:
# Regression of medv VS age
summary(lm(medv~age, data= Boston_df))
##
## Call:
## lm(formula = medv ~ age, data = Boston_df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.097 -5.138 -1.958 2.397 31.338
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 30.97868 0.99911 31.006 <2e-16 ***
## age -0.12316 0.01348 -9.137 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 8.527 on 504 degrees of freedom
## Multiple R-squared: 0.1421, Adjusted R-squared: 0.1404
## F-statistic: 83.48 on 1 and 504 DF, p-value: < 2.2e-16
BACK TO THE SLIDES !!!
Trying several multiple linear regression equations
Correlations between the predictors and the dependent variable, sorted by absolute value.
sort(abs(cor(Boston_df)[,"medv"]))
## chas dis zn age rad crim nox tax
## 0.1752602 0.2499287 0.3604453 0.3769546 0.3816262 0.3883046 0.4273208 0.4685359
## indus ptratio rm lstat medv
## 0.4837252 0.5077867 0.6953599 0.7376627 1.0000000
Because “lstat” is the predictor with the highest absolute correlation with the DV, we can claim that it is the best single predictor to include in a regression equation. We already obtained the equation between “lstat” and the DV in a previous notebook.
Does adding a second predictor to the equation that only has “lstat” as predictor produce a better prediction performance?
Let’s obtain the equation using “lstat” and “rm” and comparing it to the equation that only includes “lstat”.
Equation with “lstat” only
summary(lm(medv ~ lstat, data=Boston_df))
##
## Call:
## lm(formula = medv ~ lstat, data = Boston_df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -15.168 -3.990 -1.318 2.034 24.500
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 34.55384 0.56263 61.41 <2e-16 ***
## lstat -0.95005 0.03873 -24.53 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.216 on 504 degrees of freedom
## Multiple R-squared: 0.5441, Adjusted R-squared: 0.5432
## F-statistic: 601.6 on 1 and 504 DF, p-value: < 2.2e-16
Equation with “lstat” and “rm”
summary(lm(medv ~ lstat + rm, data=Boston_df))
##
## Call:
## lm(formula = medv ~ lstat + rm, data = Boston_df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -18.076 -3.516 -1.010 1.909 28.131
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.35827 3.17283 -0.428 0.669
## lstat -0.64236 0.04373 -14.689 <2e-16 ***
## rm 5.09479 0.44447 11.463 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 5.54 on 503 degrees of freedom
## Multiple R-squared: 0.6386, Adjusted R-squared: 0.6371
## F-statistic: 444.3 on 2 and 503 DF, p-value: < 2.2e-16
Because these two equations have different number of predictors, we should compare them using either Adjusted R Squared or RSE. Adjusted R Squared or RSE._ <—– ACCOUNTS FOR OVERFITTING
Comparison based on Adjusted R squared
In this course, unless I tell you otherwise, we will consider that one equation is better than the other only if the increase in Adjusted R Squared is deemed to be practically significant. When is an increase in Adjusted R squared practically significant? If it is at least a 5% increase (% increase in Adj R squared >= 5%)
In some context, people are taught that as long as Adjusted R Squared increases, even if only by a little bit, that should be taken as a sing of improvement. However, such an approach is too naive and simplistic.
Does the adjusted R squared for the second equation is at least 5% larger than the one for the first equation?
Yes, the equation with “lstat” and “rm” produces an increase in Adjusted R Squared of 17.3% when compared to the equation that only includes “lstat”. See next code chunks:
summary(lm(medv ~ lstat, data=Boston_df))$adj.r.squared
## [1] 0.5432418
summary(lm(medv ~ lstat + rm, data=Boston_df))$adj.r.squared
## [1] 0.6371245
# Compute the % increase
(summary(lm(medv ~ lstat + rm, data=Boston_df))$adj.r.squared - summary(lm(medv ~ lstat, data=Boston_df))$adj.r.squared) / summary(lm(medv ~ lstat, data=Boston_df))$adj.r.squared
## [1] 0.1728193
Adj R squared 17.3% >= 5%
Comparison based on RSE
In this course, unless I tell you otherwise, we will consider that one equation is better than the other only if the decrease in RSE is deemed to be practically significant. When is decrease in RSE practically significant? If it is at least a 5% decrease (% decrease in RSE >= 5%).
In some context, people are taught that as long as RSE decreases, even if only by a little bit, that should be taken as a sing of improvement. However, such an approach is too naive and simplistic.
Does the RSE for the second equation is at least 5% smaller than the one for the first equation?
Yes, the equation with “lstat” and “rm” produces a decrease in RSE of 10.87% when compared to the equation that only includes “lstat”. See next code chunks:
summary(lm(medv ~ lstat, data=Boston_df))$sigma
## [1] 6.21576
summary(lm(medv ~ lstat + rm, data=Boston_df))$sigma
## [1] 5.540257
# Compute the % decrease
(summary(lm(medv ~ lstat, data=Boston_df))$sigma - summary(lm(medv ~ lstat + rm, data=Boston_df))$sigma) / summary(lm(medv ~ lstat, data=Boston_df))$sigma
## [1] 0.1086758
Yes, the equation with “lstat” and “rm” produces a decrease in RSE of 10.87% when compared to the equation that only includes “lstat”. See next code chunks:
10.87% >= 5%
_Does adding a third predictor, let’s say, “ptratio” to the equation that includes “lstat” and “rm” produce a better prediction performance?__
Comparison based on Adjusted R squared
# Compute the % increase
(summary(lm(medv ~ lstat + rm + ptratio, data=Boston_df))$adj.r.squared - summary(lm(medv ~ lstat + rm, data=Boston_df))$adj.r.squared) / summary(lm(medv ~ lstat + rm, data=Boston_df))$adj.r.squared
## [1] 0.06212147
Yes, the equation with three predictors produces an increase in Adjusted R Squared of 6.21% (> 5%) when compared to the equation with two predictors.
Comparison based on RSE
# Compute the % decrease
(summary(lm(medv ~ lstat + rm , data=Boston_df))$sigma - summary(lm(medv ~ lstat + rm + ptratio, data=Boston_df))$sigma) / summary(lm(medv ~ lstat + rm, data=Boston_df))$sigma
## [1] 0.05610952
Yes, the equation with three predictors produces a decrease in RSE of 5.61% (> 5%) when compared to the equation with two predictors.
Analyze the residuals from the equation with “lstat”, “rm”, and “ptratio”. Check assumptions 1, 3, and 4
To check assumptions 1 (linearity) and 4 (all residuals have equal variance) we do a graph to plot the residuals versus the predicted values of Y.
medv_multiple_out = lm(medv ~ lstat + rm + ptratio, data=Boston_df)
medv_multiple_out
##
## Call:
## lm(formula = medv ~ lstat + rm + ptratio, data = Boston_df)
##
## Coefficients:
## (Intercept) lstat rm ptratio
## 18.5671 -0.5718 4.5154 -0.9307
plot(predict(medv_multiple_out), residuals (medv_multiple_out), xlab = "Predict", ylab="Residuals")
abline(h=0, col="red")
Some signs of non-linearity in the residuals are viewed. I can see the residuals following a parabolic shape (parabolic= parabola= non-linear). This means that the linear equation is not the best one. A non-linear equation should be better. In conclusion, assumption 1 is not satisfied.
When assumption 1 is not satisfied, it is really hard to evaluate assumption 4. So, NO need to assess assumption 4 in this case.
To check assumption 3 (whether the residuals follow a normal distribution), we are going to conduct a hypothesis test: the Shapiro test to check for normality
Ho: The residuals follow a Normal distribution Ha: The residuals do NOT follow a Normal distribution
shapiro.test(residuals (medv_multiple_out))
##
## Shapiro-Wilk normality test
##
## data: residuals(medv_multiple_out)
## W = 0.88804, p-value < 2.2e-16
PV= 2.2e-16, which is a lot smaller than alpha (0.05). Thus, we reject Ho and support Ha. The data is giving us evidence that the residuals do NOT follow a Normal distribution. Assumption 3 is not satisfied either!
This usually happens. When assumption 1 (the linearity assumption) is not satisfied and a non-linear model is obviously better than a linear model, assumption 3 tends to be compromised too.
Exercise 2: Credit dataset
In the Credit data set example, we want to predict Balance (i.e., credit card debt)) based on the other variables available in the data set.
To be able to assess the Credit data set, you had to install the ISLR package (which you already did when you opened this notebook).
After you have installed the ISLR package, you need to load it by calling library(). See next code chunk:
library (ISLR)
Now take a look at the Credit data frame:
str(Credit)
## 'data.frame': 400 obs. of 12 variables:
## $ ID : int 1 2 3 4 5 6 7 8 9 10 ...
## $ Income : num 14.9 106 104.6 148.9 55.9 ...
## $ Limit : int 3606 6645 7075 9504 4897 8047 3388 7114 3300 6819 ...
## $ Rating : int 283 483 514 681 357 569 259 512 266 491 ...
## $ Cards : int 2 3 4 3 2 4 2 2 5 3 ...
## $ Age : int 34 82 71 36 68 77 37 87 66 41 ...
## $ Education: int 11 15 11 11 16 10 12 9 13 19 ...
## $ Gender : Factor w/ 2 levels " Male","Female": 1 2 1 2 1 1 2 1 2 2 ...
## $ Student : Factor w/ 2 levels "No","Yes": 1 2 1 1 1 1 1 1 1 2 ...
## $ Married : Factor w/ 2 levels "No","Yes": 2 2 1 1 2 1 1 1 1 2 ...
## $ Ethnicity: Factor w/ 3 levels "African American",..: 3 2 2 2 3 3 1 2 3 1 ...
## $ Balance : int 333 903 580 964 331 1151 203 872 279 1350 ...
balance_multiple_out = lm (Balance~.-ID -Limit, data= Credit)
summary(balance_multiple_out)
##
## Call:
## lm(formula = Balance ~ . - ID - Limit, data = Credit)
##
## Residuals:
## Min 1Q Median 3Q Max
## -204.86 -79.28 -12.15 70.47 296.61
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -549.31402 35.08452 -15.657 <2e-16 ***
## Income -7.77460 0.24389 -31.878 <2e-16 ***
## Rating 3.97896 0.05501 72.332 <2e-16 ***
## Cards 3.96537 3.79288 1.045 0.2965
## Age -0.64159 0.30614 -2.096 0.0367 *
## Education -0.37986 1.65922 -0.229 0.8190
## GenderFemale -10.71056 10.32498 -1.037 0.3002
## StudentYes 416.43756 17.33606 24.021 <2e-16 ***
## MarriedYes -15.10961 10.72822 -1.408 0.1598
## EthnicityAsian 21.76158 14.67762 1.483 0.1390
## EthnicityCaucasian 10.64919 12.71571 0.837 0.4028
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 102.9 on 389 degrees of freedom
## Multiple R-squared: 0.9512, Adjusted R-squared: 0.9499
## F-statistic: 757.8 on 10 and 389 DF, p-value: < 2.2e-16
If we were to choose as our final equation the one including all the predictors with significant partial t-test, what would be that equation? Write the equation.
Predicted Balance= -549.31402 -7.77460 * Income + 3.97896 * Rating - 0.64159 * Age + 416.43756 * StudentYes
sort(abs(cor(Credit[,-c(1,3,8,9,10,11)])[,"Balance"]))
## Age Education Cards Income Rating Balance
## 0.001835119 0.008061576 0.086456347 0.463656457 0.863625161 1.000000000
summary(lm(Balance ~ Rating , data= Credit))
##
## Call:
## lm(formula = Balance ~ Rating, data = Credit)
##
## Residuals:
## Min 1Q Median 3Q Max
## -712.28 -135.32 -9.58 125.67 829.04
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -390.84634 29.06851 -13.45 <2e-16 ***
## Rating 2.56624 0.07509 34.18 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 232.1 on 398 degrees of freedom
## Multiple R-squared: 0.7458, Adjusted R-squared: 0.7452
## F-statistic: 1168 on 1 and 398 DF, p-value: < 2.2e-16
Predicted Balance= -390.84634 + 2.56624 * Rating
# Compute the % decrease in RSE
(summary(lm(Balance ~ Rating, data= Credit))$sigma - summary(lm(Balance ~ Rating + Income, data= Credit))$sigma) / summary(lm(Balance ~ Rating , data= Credit))$sigma
## [1] 0.298141
Yes, according to RSE, the equation with Rating and Income is better than the equation with Rating only because the % decrease in RSE is above 5% (it is 29.8%)
summary(lm(Balance ~ Rating + Income + Cards, data= Credit))
##
## Call:
## lm(formula = Balance ~ Rating + Income + Cards, data = Credit)
##
## Residuals:
## Min 1Q Median 3Q Max
## -277.39 -113.55 -35.11 56.51 578.89
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -539.30497 26.90803 -20.043 <2e-16 ***
## Rating 3.94657 0.08684 45.445 <2e-16 ***
## Income -7.66155 0.38077 -20.121 <2e-16 ***
## Cards 1.68143 5.99125 0.281 0.779
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 163.1 on 396 degrees of freedom
## Multiple R-squared: 0.8751, Adjusted R-squared: 0.8742
## F-statistic: 925.2 on 3 and 396 DF, p-value: < 2.2e-16
summary(lm(Balance ~ Rating + Income, data= Credit))$sigma
## [1] 162.8813
summary(lm(Balance ~ Rating + Income + Cards, data= Credit))$sigma
## [1] 163.0707
# Compute the % decrease in RSE
(summary(lm(Balance ~ Rating + Income, data= Credit))$sigma - summary(lm(Balance ~ Rating + Income + Cards, data= Credit))$sigma) / summary(lm(Balance ~ Rating + Income, data= Credit))$sigma
## [1] -0.001162271
No, according to RSE, the equation with Rating, Income, and Cards is worse than the equation with Rating and Income only because there is no decrease in RSE. We observed an increase in RSE (very bad!).
The best equation we found among the ones we tried in b, c, and d is the one that uses “Rating” and “Income” as predictors.
How would you judge the quality of this equation based on its RSE value? Does the RSE value is good enough to suggest the equation has a good quality? Show your work.
summary(lm(Balance ~ Rating + Income , data= Credit))$sigma
## [1] 162.8813
# Coefficient of variation
summary(lm(Balance ~ Rating + Income, data= Credit))$sigma / mean(Credit$Balance)
## [1] 0.3132243
(summary(lm(Balance ~ Rating + Income, data= Credit))$sigma / mean(Credit$Balance))*100
## [1] 31.32243
We want a coeff of var of at most 20%. In this case, it is quite above that (31.3%). Therefore, we consider that the RSE is not as low as we want. We want an equation with a lower RSE.
Although there is NOT a universally valid criterion to know when the coefficient of variation is low enough, in this class, we will consider a coefficient of variation <= 20% as a desirable outcome ((we want the magnitude of the error to be at most 20% compared to a typical value of Y). A value <= 10% would be a very desirable outcome.